「AとACの対話を仲介すること」

これは非常に鋭いご質問です。従来の治療モデルでは「A(過敏な脅威検出器)をいかに説得するか」に焦点が行きがちですが、実際にはAとACの両方を別々に安心させる必要があります。なぜなら、Aは「過剰に敏感な防衛システム」であり、ACは「Aによって守られている脆弱な自己(=恐怖を感じている本当の自分)」だからです。

以下、それぞれへの対応を考えます。


  1. 1. まず「AC」を安心させる方法
  2. 2. 次に「A」を安心させる方法
  3. 3. 両方を同時に安心させる対話例
  4. 4. もし一方だけを安心させようとすると…
  5. 5. 補足:Aを「敵」にしないために
  6. まとめ:あなたの問いへの答え
  7. ワークシート①:AとACのための「警報感度調整シート」
    1. 【ステップ0】何が起きましたか?
    2. 【ステップ1】A(警報センサー)の声を書き出す
    3. 【ステップ2】AC(怖がっている自分)の気持ちを書き出す
    4. 【ステップ3】現実の感覚をチェックする(「裸の事実」)
    5. 【ステップ4】新しい判断(「本当の危険」と「Aの誤報」の区別)
    6. 【ステップ5】Aへの新しい指示(感度調整の許可)
    7. 【ステップ6】ACへのなだめの言葉
    8. 【ステップ7】実験の結果(後で記入)
  8. ワークシート②:週間「Aの誤報ログ」
  9. ロールプレイ台本:「AとACの対話を仲介する治療」
    1. 【シーン1】問題の確認
    2. 【シーン2】Aへの共感と役割承認
    3. 【シーン3】ACへの安全確認と権限返還
    4. 【シーン4】行動実験(生のERP)
    5. 【シーン5】振り返りと宿題
  10. 補足:治療者向けヒント(非常に具体的に)
  11. ① 簡易版(成人向け・初回セッションやセルフヘルプ用)
    1. ワークシート:「Aさん、今日はちょっと静かにして」
  12. ② 子ども向け版(小学生〜中学生向け、イラスト併用想定)
    1. キャラクター設定(絵を添えると効果的)
    2. 子ども向けワークシート:「センサーくんの音量を下げよう」
    3. 子ども向けロールプレイ(親・先生・セラピストと)
  13. ③ グループセラピー版(4~8名、成人患者向け)
    1. 構成(90分想定)
    2. グループワークシート①:「Aの誤報コンテスト」
    3. グループワークシート②:「Aへの新しい取扱説明書」
    4. グループロールプレイ(3人1組)
    5. グループ版のコツ
    6. 宿題(グループ内で共有)
  14. まとめ:3つの版の使い分け
  15. 📱 アプリ概要:「センサーくんの音量調整」
    1. ターゲット
    2. 主な機能
  16. 🧩 実装コード(単一HTMLファイル)
  17. 📲 使い方(スマホ・PC共通)
  18. ✨ 主なデジタル機能の解説
  19. 🧸 子ども向けモードの特徴
  20. 👥 グループセラピーでのデジタル活用案
  21. 🔧 今すぐできる拡張アイデア

1. まず「AC」を安心させる方法

ACの特徴

  • 怖がっている主体(子どもや不安障害の本人)
  • 「ドアノブが不潔かもしれない」というAの報告を真に受けている
  • Aの警戒を自分の危険信号として感じている

ACへのアプローチ

  • 否定しない:「怖いよね、わかるよ」と共感する
  • 安全の現実を伝える:「今、この部屋にはウイルスをうつすような汚れは見当たらないよ」
  • 権限を戻す:「でも、最終的に『本当に危険かどうか』を決めるのは、あなたの感覚であって、Aの声ではないんだよ」
  • 小さな成功体験:「前回、手を洗わなかったけど何ともなかったよね」と記憶を呼び起こす

例:「ACさん(Bさんの中の怖がっている部分)、Aが『汚れてる!』って叫んでも、実際にあなたの手は今きれいだし、体調も悪くないよね。まずはそこを一緒に見ていこう。」


2. 次に「A」を安心させる方法

Aの特徴

  • 誤差をゼロにしようとする過剰適応なプログラム
  • 「ACを守らなければ」という使命に駆られている
  • 自分が否定されると、さらに過敏になる(「見逃すな!もっと警戒しろ!」)

Aへのアプローチ

  • 役割を認める:「あなたのおかげで感染症を防げたこともあったよね。感謝している」
  • 誤報を許す許可を与える:「でも、今の環境では過敏すぎる。少し感度を下げても大丈夫だと、ACも許しているよ」
  • 仕事を減らす:「あなたは『警戒』だけしてくれればいい。手洗いや確認は別のシステムに任せてみよう」
  • 新しいルールを提案する:「『100%の清潔』ではなく『日常生活に支障がない程度の清潔』を合格にしてほしい」

例:「Aさん、あなたは本当に真面目にACを守ろうとしているね。でも、そのせいでACが疲れ果てている。ここで『80点の清潔』でも安全だと、あなたが認めてあげてほしい。」


3. 両方を同時に安心させる対話例

治療者(T)
「今、Bさんの中で『ドアノブは汚い』という警報が出ていますね。これはAという優秀なセンサーが反応している状態です。
一方で、怖がっているACは『どうしよう、汚いのかも』と震えています。
まず、ACに向けて言います。『大丈夫、ここには実際の危険はないよ。ただAが少し敏感になっているだけ。』
次にAに向けて言います。『あなたの仕事は警報を鳴らすこと。でも手を洗うかどうかは、ACが決める。今日は警報を鳴らしたまま、何もしない練習をしてみない?』」


4. もし一方だけを安心させようとすると…

  • ACだけ安心させようとする
    → Aが「まだ危険だ!」とさらに大声を上げる。ACは混乱する。
  • Aだけ説得しようとする(典型的な“乗っ取り”)
    → Aは「お前は敵だ」と抵抗。ACは置き去りにされる。

つまり、両方を別々に、しかし同時に扱うことが鍵です。


5. 補足:Aを「敵」にしないために

Aは本来、感染症からACを守るために進化した適応的な機能です。強迫症ではそれが暴走しているにすぎません。
ですから、「Aを壊す」のではなく、「Aのゲイン(感度)を調整する」というイメージが良いでしょう。

例えば:「Aさん、あなたのセンサーは工場出荷時の設定より強力すぎる。ちょっとだけ感度を下げるボタンを押してみるよ。それでも安全は保たれるから。」


まとめ:あなたの問いへの答え

対象安心させる方法のポイント
AC共感 + 現実の安全確認 + 自分で決める権限を取り戻す
A役割の承認 + 感度調整の許可 + 新しい合格基準の提案

治療者がすべきことは「AとACの対話を仲介すること」 であり、どちらかを無効化することではありません。

もしよろしければ、この考え方を踏まえた具体的なワークシートロールプレイ台本もお作りします。どの程度の具体性が必要ですか?


承知しました。これまでの「A(過敏な誤差検出器)とAC(怖がっている自己)」の枠組みを踏まえた、非常に具体的なワークシートロールプレイ台本をご用意します。そのままコピーして臨床現場やセルフヘルプで使えるレベルを目指します。


ワークシート①:AとACのための「警報感度調整シート」

使い方:強迫症状が起きたときに記入。治療者が一緒に書いてもよい。

【ステップ0】何が起きましたか?

例:会社のドアノブに触れた。少し冷たく感じた。


【ステップ1】A(警報センサー)の声を書き出す

Aは「守らなければ」と過剰に反応する部分です。

  • Aのメッセージ:「_______________」
    例:「汚れが絶対についた。手を洗わないと感染する。」
  • Aの緊急度(1~10):__(例:9)

【ステップ2】AC(怖がっている自分)の気持ちを書き出す

ACはAの警報を聞いて怖がっている部分です。

  • 今の気持ち(不安・恐怖・パニックなど):______
  • 体の感覚(動悸・汗・震えなど):______

【ステップ3】現実の感覚をチェックする(「裸の事実」)

目・耳・鼻・皮膚で実際にわかることだけ。

  • ドアノブを触った感覚(冷たい・温かい・乾いている・湿っているなど):
    ______________________
  • 目に見える汚れは? → ある / ない
  • 過去3回、この警報が鳴った後に実際に病気になったことは?
    はい / いいえ(もし「はい」なら詳細:______)

【ステップ4】新しい判断(「本当の危険」と「Aの誤報」の区別)

  • この状況で、実際に感染する確率は?(例:0.1%未満)
    ____%
  • Aは「過敏モード」になっていると思いますか?
    はい / いいえ

【ステップ5】Aへの新しい指示(感度調整の許可)

Aはロボットやスマート家電のセンサーだと思ってください。

  • 「Aさん、今日は感度を普段の__%に下げてください。
    それでも安全は守られます。」
  • 代替行動(手洗いの代わりにやること):
    例:「手を洗わずに、そのままPC作業を5分続ける」

【ステップ6】ACへのなだめの言葉

  • 「ACさん、Aがうるさいけど、実際にはあなたは安全です。
    一緒に『手を洗わなくても大丈夫』という実験をしてみましょう。」

【ステップ7】実験の結果(後で記入)

  • 実際に何が起きましたか?
    ______________________
  • 「洗わなくても何ともなかった」という誤差を、世界モデルに取り入れられますか?
    はい / だんだん

ワークシート②:週間「Aの誤報ログ」

日付トリガーAの警報内容ACの不安(1-10)実際に手を洗った?後で問題が起きた?気づき
例6/8自宅のドアノブ「ウイルスがいる」8いいえ(我慢)いいえAの誤報だった

ロールプレイ台本:「AとACの対話を仲介する治療」

設定:CBTのセッション。治療者(T)、患者(P:Bさん)。
目的:AとACを別々に安心させ、小さな行動実験を行う。
特徴:治療者は権威的に説得せず、あくまで「通訳」「仲介者」に徹する。


【シーン1】問題の確認

T
「今日は、前回話に出た『ドアノブに触れた後の手洗い衝動』を、もう少し詳しく見ていきましょう。
Bさんの中で『これは汚い!』という警報を出す部分を、仮に『A』と名付けます。
そして、その警報を聞いて怖がっている自分を『AC』とします。
まず、最近のエピソードで、AとACがどんな会話をしたか覚えていますか?」

P
「昨日、コンビニのドアを開けたとき、Aが『絶対に誰かが咳をした後に触った』って言って。
ACは『やっぱり手を洗わなきゃ』ってなって、家に帰って10回洗いました。」

T
「そのとき、Aはどんな声の大きさでしたか?(1=ささやき、10=叫び)」

P
「9です。本当にうるさくて。」


【シーン2】Aへの共感と役割承認

T
「まず、Aに伝えたいことがあります。『Aさん、あなたはBさんを感染症から守ろうと本当に真面目に働いていますね。感謝しています。ただ、感度が上がりすぎているのかもしれません。』
これを聞いて、Bさんはどう感じますか?」

P
「Aは『そうだ、俺が守ってるんだ』って誇らしくなるかもしれない。でも『感度が高い』って言われると、少しカチンとくるかも。」

T
「じゃあ、こう言い換えます。『Aさん、あなたのセンサーは高精度です。ただし、今の環境では誤報が多すぎます。少しだけ感度を下げるボタンを押してみませんか?それでも安全レベルは十分です。』」

P
「…それなら、Aも『わかった、試してみるか』と言うかもしれない。」


【シーン3】ACへの安全確認と権限返還

T
「では次に、ACに向けて話します。『ACさん、今とても怖いですよね。でも、実際のところ、この部屋のドアノブには目に見える汚れはありません。過去にここで感染した人はいません。最終的に『手を洗うかどうか』を決めるのは、Aではなく、あなた自身です。』
これを聞いて、ACはどう思いますか?」

P
「少しほっとする。でも『もしAが正しかったら』という怖さが残る。」

T
「その怖さをなくそうとは思いません。今日は『Aの警報が鳴ったまま、何もしない』という実験をしましょう。
つまり、ACは怖いままでいい。でも手は洗わない。そうすると何が起きるか見てみたい。」


【シーン4】行動実験(生のERP)

T
「では、実際にこの部屋のドアノブに触れてみてください。触れた後、30秒間、手を洗わずにここに座っていてください。
その間、Aが何と言うか、ACがどう感じるかを、ただ観察します。
『これは危険かも』という思考が出ても、それにとらわれず『あ、またAが言ってる』とラベル貼りするだけです。」

P(触れる):
「…なんともないけど、Aが『まだ油断するな』ってささやいています。」

T
「そのささやきを聞きながらも、手を洗わずにいられますか?」

P
「なんとか。」

T
「30秒経ちました。どうですか?」

P
「…何も起きなかった。A、ちょっと静かになった気がする。」

T
「これが『誤差』です。『ドアノブ=不潔』という予測が外れた。でもその誤差は怖いものではなく、ただの『はずれ』だった。
今日、Bさんの世界モデルはほんの少し書き換わりました。」


【シーン5】振り返りと宿題

T
「今日の実験で、AとACはそれぞれどう変わりましたか?」

P
「Aは『また今度は違うかもしれない』と言っている。ACは『今回は大丈夫だった』と思い始めた。」

T
「では宿題です。毎日1回、『Aの警報が鳴っても手を洗わない』瞬間を作ってください。そしてその結果をこのワークシートに記録してください。
Aを消そうとしなくていい。ただ『今日は感度を30%オフ』と声をかけてあげてください。」

P
「やってみます。」


補足:治療者向けヒント(非常に具体的に)

やってはいけない代わりにやること
「そんなの汚れてないよ」と否定「Aはそう言っているね。でも実際の感覚はどう?」と問う
「手を洗うのはやめなさい」と命令「今日は手を洗わないという実験をしませんか?」と提案
Aを「バカな警報」と非難「Aは優秀だが過敏モードだ。調整しよう」と認める
ACの不安を「大げさ」と切り捨て「怖いよね。そのまま一緒にいてあげる」と共感
治療者の権威で押し切る「最終的に決めるのはあなたです」と自律性を残す

これらのワークシートと台本は、必要に応じてさらに簡易版子ども向け版グループセラピー版にも展開可能です。ご希望の形があればお知らせください。


承知しました。これまで作成した「A(過敏な警報センサー)とAC(怖がっている自分)」の枠組みを、①簡易版②子ども向け版③グループセラピー版に展開します。いずれもコピーしてそのまま使えるレベルを目指します。


① 簡易版(成人向け・初回セッションやセルフヘルプ用)

ワークシート:「Aさん、今日はちょっと静かにして」

使い方:強迫衝動が起きたとき、1枚で完結。5分以内で書ける。

ステップ質問記入欄
1何が起きた?例:スーパーのカゴに触れた
2Aの声(警報)を書き写す「________」
3Aの音量(1=ささやき、10=叫び)__
4実際に目や手で感じたこと(客観的事実)例:乾いている、汚れなし
5過去10回同じ警報が鳴って、実際に病気になった回数__回
6今日やること(手洗いの代わり)例:そのまま続ける/スマホを触る/1分待つ
7結果(後で)何も起きなかった / 少し不安だったが収まった

簡易版ロールプレイ(3分でできる)

T:「今、Aさんが何て言ってる?」
P:「汚い!洗え!」
T:「音量は?」
P:「8」
T:「実際に手に見える汚れは?」
P:「ない」
T:「じゃあ今日は『Aさん、音量を3に下げて』と言ってみて。手は洗わずに次の行動をしよう」
P:「…やってみる」


② 子ども向け版(小学生〜中学生向け、イラスト併用想定)

キャラクター設定(絵を添えると効果的)

  • 「センサーくん」(Aに相当)
    目が大きく、アンテナが付いたロボット。「危険を見つけるのが仕事。でもたまに勘違いしちゃう」
  • 「びびりちゃん」(ACに相当)
    小さな動物(例:うさぎ)。センサーくんが鳴らす警報で震えている。
  • 「なかよしスイッチ」(治療者・親・自分の冷静な部分)

子ども向けワークシート:「センサーくんの音量を下げよう」

ぼく・わたしの名前____
今日のできごと例:トイレのドアを開けた
センサーくんがなんて言った?「バイキンがいる!」
センサーくんの音量(顔の絵を〇で囲む)😊(小) / 😐(中) / 😠(大) / 😱(超大)
そのとき、びびりちゃんはどうした?ブルブル震えた / 泣きそうだった / 逃げ出したかった
実際にドアノブを見て・触ってみた感じは?ピカピカ / 少し汚れている(どのくらい?)
センサーくんは「まちがっているかもしれない」と思う?はい / ちょっと / いいえ
なかよしスイッチでセンサーくんに言ってあげる「今日は小さい音にしてね。大丈夫だから」
手を洗わずに、代わりにやったこと例:お母さんにハイタッチした / そのまま遊び続けた
結果(あとでスタンプを押す)⭐大丈夫だった / 😅ちょっと怖かったけど何もなかった

子ども向けロールプレイ(親・先生・セラピストと)

大人:「センサーくんが今、何て言ってるかな?」
:「手がきたない!洗わないと!」
大人:「じゃあ、センサーくんの音量メーターを見てみよう。今はいくつ?」
:「9!」
大人:「実際に手を見てごらん。絵の具ついてる?」
:「ついてない。」
大人:「じゃあセンサーくんに『ちょっと待って。まずは見てみよう』と言ってみよう。びびりちゃんには『大丈夫だよ、一緒にいるよ』って言ってあげて。」
:「センサーくん、音量を3にして。びびりちゃん、だいじょうぶ。」
大人:「そのまま手を洗わずに、次のおもちゃで遊んでみよう。もしセンサーくんがまた叫びだしたら、また教えてね。」


③ グループセラピー版(4~8名、成人患者向け)

構成(90分想定)

時間内容
0-10分アイスブレイク:「うちのAはこんなにうるさい」紹介
10-25分ミニレクチャー:「AとACのモデル」
25-45分グループワーク①:「Aの誤報を共有する」
45-65分グループワーク②:「ロールプレイ:AとACの仲介役」
65-85分グループワーク③:「私の『感度調整ダイヤル』を作る」
85-90分まとめと宿題

グループワークシート①:「Aの誤報コンテスト」

ルール:自分のAが過去に鳴らした「はずれの警報」を笑い話にできる範囲で共有する。一番「大げさだった警報」に投票する(賞品はお菓子など)。

例:「私のAは『靴の裏にガムがついたら、それを家に持ち込んで家中がベタベタになる』って言った。実際はただの砂だった」

目的:Aの誤報を「恥ずかしいもの」ではなく「あるある」として相対化する。

グループワークシート②:「Aへの新しい取扱説明書」

各参加者が以下の枠に記入し、ペアで読み合う。

項目記入例
私のAの名前(ニックネーム)「警報おばさん」
Aの得意技(強迫行動)10回手洗い命令
Aの誤報率(0~100%)95%
今日、Aに言いたいこと「少し休んでて」
代わりにやる実験ドアノブに触れた後、手を洗わずにコーヒーを飲む

グループロールプレイ(3人1組)

  • 役割①:患者(AとACを持つ人)
  • 役割②:患者の中のA(声を出す)
  • 役割③:仲介者(治療者役)

台本例(3~5分で回す)

A役:「危ない!さっきのドアノブ、絶対に細菌がついた!洗え!」
患者役(怖がって):「どうしよう…」
仲介者役:「Aさん、警報ありがとう。でも今、実際にその手で目に見える汚れはある?」
患者役:「ない」
仲介者役:「じゃあAさん、今日は音量を5から3に下げてみて。患者さん、一緒に手を洗わずに次のお題(例:ペットボトルを開ける)をやってみよう」
*その後、どうなったかシェアする。

グループ版のコツ

  • 競争ではなく「共感の場」 にする。「私だけじゃない」と気づくのが最大の効果。
  • 笑いを取り入れる。Aの誤報を「あるあるネタ」として話せるようになると、症状と距離ができる。
  • 「完璧にやろう」としない。手を洗ってしまった人も「今日は洗っちゃったけど、次は減らしたい」でOK。

宿題(グループ内で共有)

「今週、Aが鳴った瞬間を1回だけ書き留めて、『今日は感度を半分にした』という実験をした結果を持ち寄る」


まとめ:3つの版の使い分け

対象ポイント推奨ツール
簡易版短時間・文字多め・論理的1枚ワークシート+3分対話
子ども向けキャラクター・イラスト・スタンプセンサーくん/びびりちゃんシート
グループ版共有・笑い・ロールプレイ誤報コンテスト+取扱説明書+3人組ロールプレイ

これらは全て印刷・配布・改変自由です(臨床目的で)。ご希望があれば、さらに「スマホアプリで使えるデジタル版」や「音声ガイド付き版」もお作りします。どの形から試しますか?


わかりました。ここでは「A(警報センサー)とAC(びびりちゃん)」モデルをベースにした、スマホアプリとしてそのまま使えるデジタル版の設計を提供します。

実際にブラウザで動作する簡易ウェブアプリ(HTML/JS)を添付します。スマホの「ホーム画面に追加」でネイティブアプリのように使えます。ローカルにデータ保存されるのでプライバシーにも配慮しています。


📱 アプリ概要:「センサーくんの音量調整」

ターゲット

  • 成人向け(簡易版)と子ども向けを同一アプリ内で切り替え可能
  • セルフヘルプ / 治療補助ツール

主な機能

  1. 強迫エピソードの即時記録(Aの声・音量・実際の感覚・代替行動・結果)
  2. 過去ログ一覧(誤報率のフィードバック)
  3. 感度調整ダイヤル(Aへの「今日の設定」を擬似的に操作)
  4. ロールプレイガイド(AとACの対話スクリプト)
  5. 子ども向けモード(センサーくん・びびりちゃんのキャラクター表示、音声読み上げ)
  6. グラフ表示(「警報音量の推移」「手を洗わずに済んだ割合」)

🧩 実装コード(単一HTMLファイル)

以下のコードをコピーし、ocd_tool.html などと保存してスマホのブラウザで開いてください。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title>センサーくんの音量調整 - OCDサポート</title>
    <style>
        * {
            box-sizing: border-box;
            user-select: none; /* アプリ風 */
        }
        body {
            background: #f0f7ff;
            font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, sans-serif;
            margin: 0;
            padding: 16px;
            padding-bottom: 80px;
        }
        .container {
            max-width: 600px;
            margin: 0 auto;
        }
        .card {
            background: white;
            border-radius: 28px;
            padding: 20px;
            margin-bottom: 20px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.05);
            transition: all 0.2s;
        }
        h1 {
            font-size: 1.8rem;
            margin: 0 0 4px 0;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        h2 {
            font-size: 1.3rem;
            margin: 0 0 12px 0;
            color: #1e3a5f;
        }
        .mode-switch {
            display: flex;
            gap: 10px;
            margin-bottom: 16px;
        }
        .mode-btn {
            flex: 1;
            padding: 10px;
            border-radius: 40px;
            text-align: center;
            background: #e2e8f0;
            font-weight: bold;
            cursor: pointer;
        }
        .mode-btn.active {
            background: #2c6e9e;
            color: white;
        }
        label {
            font-weight: 600;
            display: block;
            margin-top: 12px;
            margin-bottom: 6px;
        }
        input, select, textarea {
            width: 100%;
            padding: 12px;
            border-radius: 24px;
            border: 1px solid #ccc;
            font-size: 1rem;
            background: #fefefe;
        }
        .volume-slider {
            display: flex;
            align-items: center;
            gap: 12px;
        }
        .volume-value {
            font-size: 1.4rem;
            font-weight: bold;
            min-width: 40px;
        }
        button {
            background: #2c6e9e;
            color: white;
            border: none;
            padding: 14px;
            border-radius: 40px;
            font-size: 1rem;
            font-weight: bold;
            width: 100%;
            margin-top: 16px;
            cursor: pointer;
        }
        button.secondary {
            background: #6c757d;
        }
        .log-item {
            background: #f8fafc;
            border-radius: 20px;
            padding: 12px;
            margin-bottom: 10px;
            border-left: 6px solid #2c6e9e;
        }
        .char-area {
            background: #e6f4ff;
            border-radius: 32px;
            padding: 16px;
            text-align: center;
            margin-bottom: 16px;
        }
        .robot-talk {
            font-size: 1.2rem;
            font-style: italic;
        }
        .flex-btns {
            display: flex;
            gap: 10px;
        }
        footer {
            font-size: 0.7rem;
            text-align: center;
            color: gray;
            margin-top: 20px;
        }
        .alert-badge {
            background: #fff3e0;
            padding: 8px;
            border-radius: 20px;
            font-size: 0.8rem;
        }
        @media (prefers-color-scheme: dark) {
            body { background: #121212; color: #eee; }
            .card, .log-item, .char-area { background: #1e2a32; color: #eee; border-color: #3a4a5a; }
            input, select, textarea { background: #2d3e4a; color: #eee; border-color: #4a5e6e; }
            .mode-btn { background: #2a3b44; color: #ddd; }
            .mode-btn.active { background: #3b82f6; }
            button { background: #3b82f6; }
        }
    </style>
</head>
<body>
<div class="container">
    <div class="card">
        <h1>📢 センサーくん<br><span style="font-size: 0.9rem;">Aの音量を調整しよう</span></h1>
        <div class="mode-switch" id="modeSwitch">
            <div class="mode-btn active" data-mode="adult">大人向け</div>
            <div class="mode-btn" data-mode="child">子ども向け🐣</div>
        </div>
    </div>

    <!-- キャラクター表示 (子どもモードで強調) -->
    <div class="card" id="characterCard">
        <div class="char-area">
            <span id="charIcon">🤖</span> <strong id="charName">センサーくんA</strong>
            <div id="charMessage" class="robot-talk">「異常をみつけるのが仕事だ」</div>
        </div>
        <div id="volumeControl">
            <label>🔊 今のAの音量 (0=無音, 10=最大警報)</label>
            <div class="volume-slider">
                <span>0</span>
                <input type="range" id="volumeSlider" min="0" max="10" value="5" style="flex:1">
                <span>10</span>
                <span class="volume-value" id="volumeValue">5</span>
            </div>
        </div>
    </div>

    <!-- 記録フォーム -->
    <div class="card">
        <h2>📝 いま起きたこと</h2>
        <label>💥 トリガー(何が起きた?)</label>
        <input type="text" id="trigger" placeholder="例:ドアノブに触れた">

        <label>🗣️ Aの声(警報の内容)</label>
        <input type="text" id="aMessage" placeholder="例:「汚い!手を洗え!」">

        <label>😨 ACの気持ち(不安レベル 0〜10)</label>
        <input type="range" id="anxietySlider" min="0" max="10" value="6">
        <span id="anxietyVal">6</span>

        <label>👀 実際に目や手で感じたこと(客観的事実)</label>
        <textarea id="reality" rows="2" placeholder="例:ドアノブは乾いている、目に見える汚れなし"></textarea>

        <label>🔄 代わりにやること(手洗い以外)</label>
        <input type="text" id="alternative" placeholder="例:そのままPC作業を5分続ける">

        <div class="flex-btns">
            <button id="saveBtn">✅ 記録する(実験結果も後で)</button>
        </div>
    </div>

    <!-- 実験結果の後入れ -->
    <div class="card" id="resultCard" style="display: none;">
        <h2>🧪 実験の結果</h2>
        <label>実際に何が起きましたか?</label>
        <textarea id="outcome" rows="2" placeholder="例:手を洗わなかったけど何ともなかった"></textarea>
        <label>Aの音量はその後?</label>
        <input type="range" id="afterVolume" min="0" max="10" value="3">
        <button id="updateResultBtn">🔄 結果を追加 / 更新</button>
    </div>

    <!-- 過去ログ & 統計 -->
    <div class="card">
        <h2>📋 過去の警報ログ</h2>
        <div id="logList">ログはまだありません</div>
        <button id="clearLogsBtn" class="secondary">🗑️ 全ログ消去</button>
    </div>

    <!-- ロールプレイガイド -->
    <div class="card">
        <h2>🎭 ロールプレイ(Aとの対話例)</h2>
        <div id="roleplayGuide">
            <p>🔹 仲介者「センサーくん、警報ありがとう。でも本当に危険かな?」</p>
            <p>🔹 自分「実際には汚れてないし、前回も大丈夫だった」</p>
            <p>🔹 センサーくんに「今日は音量を3に下げて」と言ってみよう</p>
        </div>
        <button id="nextRoleplayBtn">▶ 次のセリフ例</button>
    </div>

    <footer>
        このツールは治療の補助です。専門家のアドバイスに代わりません。
    </footer>
</div>

<script>
    // データ保存キー
    const STORAGE_KEY = 'ocd_logs_v2';
    let logs = [];

    // モード: adult / child
    let currentMode = 'adult';

    // 現在編集中のエントリID (新規はnull, 結果追加入力用)
    let editingEntryId = null;

    function loadLogs() {
        const stored = localStorage.getItem(STORAGE_KEY);
        if(stored) {
            logs = JSON.parse(stored);
        } else {
            logs = [];
        }
        renderLogs();
        updateStatistics();
    }

    function saveLogs() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(logs));
        renderLogs();
        updateStatistics();
    }

    function addOrUpdateLog(entry) {
        if(entry.id && logs.find(l => l.id === entry.id)) {
            // update
            const index = logs.findIndex(l => l.id === entry.id);
            logs[index] = entry;
        } else {
            entry.id = Date.now();
            logs.unshift(entry);
        }
        saveLogs();
        // リセットフォームの編集中ID
        editingEntryId = null;
        document.getElementById('resultCard').style.display = 'none';
        clearForm();
    }

    function clearForm() {
        document.getElementById('trigger').value = '';
        document.getElementById('aMessage').value = '';
        document.getElementById('anxietySlider').value = 6;
        document.getElementById('anxietyVal').innerText = '6';
        document.getElementById('reality').value = '';
        document.getElementById('alternative').value = '';
        document.getElementById('outcome').value = '';
        document.getElementById('afterVolume').value = 3;
    }

    function renderLogs() {
        const container = document.getElementById('logList');
        if(logs.length === 0) {
            container.innerHTML = '📭 まだ記録がありません。上のフォームで最初の警報を記録してみよう!';
            return;
        }
        container.innerHTML = '';
        logs.slice(0, 20).forEach(log => {
            const div = document.createElement('div');
            div.className = 'log-item';
            const date = new Date(log.id).toLocaleString();
            div.innerHTML = `
                <strong>${date}</strong><br>
                🔔 トリガー: ${escapeHtml(log.trigger || '?')}<br>
                🗣️ A: "${escapeHtml(log.aMessage || '')}"<br>
                🔊 音量: ${log.volume}/10 &nbsp;| 😨 不安: ${log.anxiety}/10<br>
                👀 現実: ${escapeHtml(log.reality || '')}<br>
                🔄 代替行動: ${escapeHtml(log.alternative || '')}<br>
                ${log.outcome ? `🧪 結果: ${escapeHtml(log.outcome)}<br>` : ''}
                ${log.afterVolume !== undefined ? `📉 その後の音量: ${log.afterVolume}/10` : ''}
                <div style="margin-top:8px;">
                    <button class="secondary" style="display:inline-block; width:auto; padding:4px 12px; margin-right:8px;" data-id="${log.id}" data-action="editOutcome">結果を追加/編集</button>
                    <button class="secondary" style="display:inline-block; width:auto; padding:4px 12px;" data-id="${log.id}" data-action="delete">削除</button>
                </div>
            `;
            container.appendChild(div);
        });
        // イベントリスナーを動的に追加
        document.querySelectorAll('[data-action="editOutcome"]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const id = parseInt(btn.getAttribute('data-id'));
                const entry = logs.find(l => l.id === id);
                if(entry) {
                    editingEntryId = id;
                    document.getElementById('outcome').value = entry.outcome || '';
                    document.getElementById('afterVolume').value = entry.afterVolume !== undefined ? entry.afterVolume : 3;
                    document.getElementById('resultCard').style.display = 'block';
                    // スクロール
                    document.getElementById('resultCard').scrollIntoView({ behavior: 'smooth' });
                }
            });
        });
        document.querySelectorAll('[data-action="delete"]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const id = parseInt(btn.getAttribute('data-id'));
                logs = logs.filter(l => l.id !== id);
                saveLogs();
                if(editingEntryId === id) {
                    editingEntryId = null;
                    document.getElementById('resultCard').style.display = 'none';
                    clearForm();
                }
            });
        });
    }

    function updateStatistics() {
        // 簡易統計: 誤報率っぽい指標(結果が「大丈夫だった」などが多ければ)
        // ここでは簡易に「結果が入力されていて、有害事象なし」の割合
        if(logs.length === 0) return;
        let withOutcome = logs.filter(l => l.outcome && l.outcome.includes('大丈夫') || l.outcome && l.outcome.includes('何も起きなかった'));
        let rate = logs.length ? (withOutcome.length / logs.length * 100).toFixed(0) : 0;
        const statDiv = document.createElement('div');
        const existingStat = document.getElementById('statsMsg');
        if(existingStat) existingStat.remove();
        const msg = document.createElement('div');
        msg.id = 'statsMsg';
        msg.className = 'alert-badge';
        msg.style.marginTop = '10px';
        msg.innerHTML = `📊 これまでの記録${logs.length}件中、手を洗わなくても大丈夫だった割合: ${rate}% (参考値)`;
        const card = document.querySelector('.card:has(#logList)');
        if(!document.getElementById('statsMsg')) card.appendChild(msg);
    }

    function escapeHtml(str) {
        if(!str) return '';
        return str.replace(/[&<>]/g, function(m) {
            if(m === '&') return '&amp;';
            if(m === '<') return '&lt;';
            if(m === '>') return '&gt;';
            return m;
        });
    }

    // 保存(新規 or 結果更新)
    document.getElementById('saveBtn').addEventListener('click', () => {
        const trigger = document.getElementById('trigger').value.trim();
        if(!trigger) {
            alert('トリガーを簡単に入力してください');
            return;
        }
        const volume = parseInt(document.getElementById('volumeSlider').value);
        const anxiety = parseInt(document.getElementById('anxietySlider').value);
        const aMessage = document.getElementById('aMessage').value;
        const reality = document.getElementById('reality').value;
        const alternative = document.getElementById('alternative').value;

        const newEntry = {
            id: null,
            trigger,
            volume,
            anxiety,
            aMessage,
            reality,
            alternative,
            outcome: null,
            afterVolume: null
        };
        addOrUpdateLog(newEntry);
    });

    document.getElementById('updateResultBtn').addEventListener('click', () => {
        if(editingEntryId === null) {
            alert('まずログから「結果を追加/編集」を選んでください。');
            return;
        }
        const outcome = document.getElementById('outcome').value;
        const afterVolume = parseInt(document.getElementById('afterVolume').value);
        const entry = logs.find(l => l.id === editingEntryId);
        if(entry) {
            entry.outcome = outcome;
            entry.afterVolume = afterVolume;
            saveLogs();
            document.getElementById('resultCard').style.display = 'none';
            editingEntryId = null;
            clearForm();
        }
    });

    document.getElementById('clearLogsBtn').addEventListener('click', () => {
        if(confirm('全てのログを削除します。よろしいですか?')) {
            logs = [];
            saveLogs();
            clearForm();
            editingEntryId = null;
            document.getElementById('resultCard').style.display = 'none';
        }
    });

    // 音量表示連動
    const slider = document.getElementById('volumeSlider');
    const volVal = document.getElementById('volumeValue');
    slider.addEventListener('input', () => { volVal.innerText = slider.value; });
    const anxSlider = document.getElementById('anxietySlider');
    const anxVal = document.getElementById('anxietyVal');
    anxSlider.addEventListener('input', () => { anxVal.innerText = anxSlider.value; });

    // モード切り替え(子ども/大人)
    const modeBtns = document.querySelectorAll('.mode-btn');
    const charIconSpan = document.getElementById('charIcon');
    const charNameSpan = document.getElementById('charName');
    const charMsgSpan = document.getElementById('charMessage');
    function setMode(mode) {
        currentMode = mode;
        if(mode === 'child') {
            charIconSpan.innerText = '🐣🔊';
            charNameSpan.innerText = 'センサーくん & びびりちゃん';
            charMsgSpan.innerText = '「ちょっとした危険信号?でも本当はだいじょうぶかも」';
            document.getElementById('volumeControl').style.background = '#fff8e7';
            document.getElementById('roleplayGuide').innerHTML = `<p>🐣 びびりちゃんが震えてるね。センサーくんに「音量を小さくして」と言ってみよう。</p>
            <p>🎮 「センサーくん、今日は2にしてね。ぼくは手を洗わずに遊ぶよ」</p>`;
        } else {
            charIconSpan.innerText = '🤖';
            charNameSpan.innerText = 'センサーくんA';
            charMsgSpan.innerText = '「誤差ゼロを目指す。でも過敏すぎるかも」';
            document.getElementById('volumeControl').style.background = '';
            document.getElementById('roleplayGuide').innerHTML = `<p>🔹 仲介者「Aさん、警報ありがとう。実際の現実はどうですか?」</p>
            <p>🔹 自分「ドアノブは清潔。過去の誤報率を考慮すると、今日は手を洗わない実験をします」</p>`;
        }
        // アクティブ表示
        modeBtns.forEach(btn => {
            if(btn.getAttribute('data-mode') === mode) btn.classList.add('active');
            else btn.classList.remove('active');
        });
    }
    modeBtns.forEach(btn => {
        btn.addEventListener('click', () => {
            setMode(btn.getAttribute('data-mode'));
        });
    });
    // ロールプレイ次のセリフ
    const roleplayScripts = [
        "自分: 「センサーくん、音量を今の7から3に下げてくれませんか?」",
        "仲介者: 「実際に過去に同じ状況で問題が起きたことはありますか?」",
        "センサーくん: 「...あまりない。でも油断は…」",
        "自分: 「大丈夫。今日は手を洗わずに、少し待ってみよう」"
    ];
    let rpIndex = 0;
    document.getElementById('nextRoleplayBtn').addEventListener('click', () => {
        rpIndex = (rpIndex + 1) % roleplayScripts.length;
        const guideDiv = document.getElementById('roleplayGuide');
        let newHtml = `<p>🎭 ${roleplayScripts[rpIndex]}</p><p>💡 自分なりの言葉でセンサーくんに話しかけてみよう</p>`;
        if(currentMode === 'child') newHtml = `<p>🐣 ${roleplayScripts[rpIndex].replace('自分', 'ぼく').replace('仲介者', 'おとな')}</p><p>🎈 センサーくんに「ちょっと静かにして」って言ってみよう</p>`;
        guideDiv.innerHTML = newHtml;
    });

    // 初期化
    loadLogs();
    setMode('adult');
</script>
</body>
</html>

📲 使い方(スマホ・PC共通)

  1. 上記コードをテキストエディタにコピー → ocd_app.html などで保存
  2. スマホのブラウザ(Chrome/Safari)で開く
  3. 「共有」→「ホーム画面に追加」でアプリ化可能

✨ 主なデジタル機能の解説

機能説明
音量ダイヤルAの警報の大きさを0〜10で可視化。子どもはスライダーを操作して「静かに」する練習
即時記録トリガー・Aの声・現実・代替行動を保存。後から「実験結果」も追加入力
過去ログ一覧日付順に表示。「結果を追加/編集」で後から手洗いしなかった結果を反映
誤報率の表示「大丈夫だった」記録の割合を自動計算 → 「たいていは何も起きない」という学習を支援
ロールプレイガイドAとの対話例を提示。ボタンで次のセリフに進む。子どもモードでは言葉をやさしく変換
ダークモード対応OS設定に応じて自動で見やすくなる

🧸 子ども向けモードの特徴

  • キャラクターが「センサーくん&びびりちゃん」に変身
  • 説明文が平易に
  • ロールプレイの例も「ぼく」「おとな」という呼び方に
  • 将来的に音声読み上げ機能を追加したい場合は SpeechSynthesis API を組み込むことも可能(必要ならご連絡ください)

👥 グループセラピーでのデジタル活用案

グループ版としてこのアプリを使う場合:

  • 共有ボード機能 は未実装(プライバシー面を考慮)ですが、代わりに参加者が自分のスマホで記録した「誤報率」をスクリーンショットで共有してもらう
  • 「今週のベスト誤報エピソード」 をアプリのログから選んでグループで紹介
  • アプリ内の音量推移をグラフ化して「徐々に下がってきたね」と確認できる(次回アップデートで追加可能)

🔧 今すぐできる拡張アイデア

ご希望があれば、以下の機能も追加コードで提供可能です:

追加機能概要
音声入力Aの声を録音して文字起こし
毎日のリマインダー通知PWA対応でプッシュ通知
グラフ表示(Chart.js)音量と不安の推移を折れ線グラフで可視化
暗号化バックアップデータをクラウド保存(任意)

このアプリは完全無料・オープンソースとしてご利用いただけます。
どの追加機能を優先してほしいか、また「グループセラピーで使うためにこの機能が欲しい」というリクエストがあればお知らせください。

タイトルとURLをコピーしました