From 1be261c95ff8793407d6d98e5a8eabebd50bfd90 Mon Sep 17 00:00:00 2001 From: Akira Date: Tue, 3 Mar 2026 12:56:26 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E4=BD=9C=E6=88=90=E3=81=AE=E6=BA=96=E5=82=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/alexa-api/12_ローカルで試したこと.md | 40 +- .../!!!meta.json | 36 ++ .../Alexa TTS API 実装記録 (2026-03-02.html | 318 ++++++++++ .../index.html | 11 + .../navigation.html | 16 + .../style.css | 551 ++++++++++++++++++ 6 files changed, 965 insertions(+), 7 deletions(-) create mode 100644 docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/!!!meta.json create mode 100644 docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/Alexa TTS API 実装記録 (2026-03-02.html create mode 100644 docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/index.html create mode 100644 docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/navigation.html create mode 100644 docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/style.css diff --git a/docs/alexa-api/12_ローカルで試したこと.md b/docs/alexa-api/12_ローカルで試したこと.md index 29437b5..98e5699 100644 --- a/docs/alexa-api/12_ローカルで試したこと.md +++ b/docs/alexa-api/12_ローカルで試したこと.md @@ -121,17 +121,43 @@ Amazon の `/api/behaviors/preview` エンドポイントが、 --- -## 次に試すべきこと +## 試行ログ続き(2026-03-03 午後) -- [ ] `auth4.js` で Cookie を新規取得してテスト(セッションリセット) -- [ ] `/api/behaviors/preview` 以外のエンドポイントを探す(例: `/api/ap/d-notification`など) -- [ ] `Alexa.TextCommand` ノードタイプ(「テキストで命令を送る」別ルート) -- [ ] ローカルブリッジ方式(ローカルPCをプロキシにしてAmazonにリクエストを転送する) -- [ ] alexa-cookie / alexa-remote2 のソースコードから別APIを調査する +#### Cookie 新規取得(auth4.js 再実行) +→ 変化なし。Cookie は原因ではなかった。 + +#### ❌ `AlexaAnnouncement` ノードタイプ +→ 「えんえせんと」("AlexaAnnouncement" を日本語で読んだ)。コンテンツではなくノード名が読まれた。別用途のノード。 + +#### ✅ **解決!** `sequenceJson` の non-ASCII を `\uXXXX` エスケープに変換 + +```javascript +var rawSequenceJson = JSON.stringify(sequenceObj).replace( + /[\u0080-\uffff]/g, + function(c) { return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); } +); +``` + +→ 「これは日本語のテストです」が完璧に発話された! + +--- + +## ✅ 解決済み(2026-03-03) + +**根本原因**: `sequenceJson` 内の日本語文字を raw UTF-8 のまま Amazon に送ると、Amazon 側のパーサーがそれをフィルタリングして無視する。 + +**解決策**: `JSON.stringify(sequenceObj)` 後に non-ASCII 文字(`\u0080` 以上)を `\uXXXX` 形式のJSONエスケープシーケンスに変換してから `sequenceJson` として送る。 + +**修正箇所**: `alexa-api/server.js` と `alexa-api/test_tts.js` + +**確定したパラメータ**: +- `type: 'Alexa.Speak'` +- `locale: 'ja-JP'` +- `textToSpeak: <日本語テキスト>` +- `sequenceJson` は non-ASCII を `\uXXXX` エスケープして送る --- ## 参考 - 実装記録: `docs/alexa-api/10_Alexa TTS API 実装記録 (2026-03-02).md` -- 上記ファイルに記録されていた未解決事項がこのファイルに続く diff --git a/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/!!!meta.json b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/!!!meta.json new file mode 100644 index 0000000..cda7e4a --- /dev/null +++ b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/!!!meta.json @@ -0,0 +1,36 @@ +{ + "formatVersion": 2, + "appVersion": "0.63.7", + "files": [ + { + "noteId": "IRnO9uub2Bwg", + "notePath": [ + "IRnO9uub2Bwg" + ], + "isClone": false, + "title": "Alexa TTS API 実装記録 (2026-03-02)", + "notePosition": 50, + "prefix": null, + "isExpanded": false, + "type": "text", + "mime": "text/html", + "format": "html", + "dataFileName": "Alexa TTS API 実装記録 (2026-03-02.html", + "noImport": false, + "attributes": [], + "attachments": [] + }, + { + "noImport": true, + "dataFileName": "navigation.html" + }, + { + "noImport": true, + "dataFileName": "index.html" + }, + { + "noImport": true, + "dataFileName": "style.css" + } + ] +} \ No newline at end of file diff --git a/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/Alexa TTS API 実装記録 (2026-03-02.html b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/Alexa TTS API 実装記録 (2026-03-02.html new file mode 100644 index 0000000..81729b1 --- /dev/null +++ b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/Alexa TTS API 実装記録 (2026-03-02.html @@ -0,0 +1,318 @@ + + + + + + + + Alexa TTS API 実装記録 (2026-03-02) + + + +
+

Alexa TTS API 実装記録 (2026-03-02)

+ +
+

Alexa TTS API マスタードキュメント

+

最終更新: 2026-03-03 状態: サーバーからの日本語TTS未解決(調査中) +
------------ +
2026/03/03 10:24 akira記録 +
akiraが下記の変更をしましたので、内容を読んでください。 +
+
1) 構成とサーバーへのファイル受け渡し方法を変更しました +
/home/claude/windmill_workflow +
に、https://gitea.keinafarm.net/akira/windmill_workflow.gitをcloneしました +
これにより、 +
C:\Users\akira\Develop\windmill_workflow +
とのやり取りはgiteaを使って出来るようになります。 +
+
2) docker compose up -dで、「 Docker 内部の IP アドレスが変わり、Traefik のルーティングが古い IP + を参照したまま 502/504 エラーになる」のは、原因があるはずです。(他のコンテナで、問題になっていないので) +
調査して、Traefik 再起動が不必要になるようにしたいです +
+
+
+
------------

+
+

概要

+

Windmill から家中の Echo デバイスに任意のテキストを読み上げさせる API サーバー。Node.js + Express で実装し、Docker + コンテナとして Windmill サーバー上で稼働する。

+

⚠ 現在の問題: ローカル PC からは日本語TTS動作確認済み。しかしサーバー(keinafarm.net)のコンテナからリクエストすると日本語文字が + Amazon 側で除去されて発話されない。原因は Amazon の IP ベースフィルタリング(海外IPからは日本語 textToSpeak + を無視する模様)。調査継続中。

+

ファイル構成

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ファイル場所役割備考
server.jsalexa-api/(リポジトリ)Express API サーバー本体本番コード。変更したらビルド・再デプロイが必要
Dockerfilealexa-api/(リポジトリ)Docker イメージ定義node:20-alpine ベース。server.js と package*.json をコピー
docker-compose.ymlalexa-api/(リポジトリ)コンテナ起動設定windmill_windmill-internal ネットワーク接続。外部ポート非公開
package.json / package-lock.jsonalexa-api/(リポジトリ)npm 依存関係本番: express のみ。devDeps に alexa-remote2(不使用)
.env.examplealexa-api/(リポジトリ)環境変数テンプレートALEXA_COOKIE=xxx の形式
.envalexa-api/(リポジトリ、.gitignore 対象)実際の Cookie 保管Git にコミットしない。ローカル作業後に scp でサーバーへ転送
auth4.jsalexa-api/(リポジトリ)Amazon 認証・Cookie 取得スクリプトローカルのみで実行(Windows PC)
auth.js / auth2.js / auth3.jsalexa-api/(リポジトリ)auth4.js の旧バージョン参考用。実際は auth4.js を使う
test_tts.jsalexa-api/(リポジトリ)ローカルテスト用スクリプト直接 alexa.amazon.co.jp を叩いて動作確認
+
+

サーバー上のファイル場所: /home/claude/alexa-api/(git + リポジトリとは別にコピーして管理)

+

サーバーへのデプロイ手順

+

server.js や Dockerfile、package.json を変更した場合は以下の手順でサーバーに反映する。

+

Step 1: ローカルでファイルを編集

+

リポジトリ(c:\Users\akira\Develop\windmill_workflow\alexa-api\)でファイルを編集する。

+

Step 2: scp でサーバーに転送

+

変更したファイルをサーバーに scp で転送する:

+

# server.js を変更した場合 scp alexa-api/server.js keinafarm-claude:/home/claude/alexa-api/server.js + # Dockerfile や package.json を変更した場合 scp alexa-api/Dockerfile keinafarm-claude:/home/claude/alexa-api/Dockerfile + scp alexa-api/package.json keinafarm-claude:/home/claude/alexa-api/package.json + scp alexa-api/package-lock.json keinafarm-claude:/home/claude/alexa-api/package-lock.json + # .env を更新した場合(Cookie 更新時など) scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env

+

Step 3: サーバーでビルドして再起動

+

⚠ 重要: docker compose restart はイメージをリビルドしない。server.js + 等を変更した場合は必ず build + up -d を実行すること。

+

# SSH でサーバーに接続してビルド+起動 ssh keinafarm-claude cd /home/claude/alexa-api + # イメージをビルド(server.js 等の変更を反映) sudo docker compose build # コンテナを再作成して起動 + sudo docker compose up -d # Traefik を再起動(コンテナ再作成後は必須) sudo docker restart + traefik

+

Step 4: 動作確認

+

# ヘルスチェック(Windmill ワーカーコンテナ内から) curl http://alexa_api:3500/health # ログ確認 + sudo docker logs alexa_api -f # デバイス一覧確認 curl http://alexa_api:3500/devices

+

Cookie だけ更新する場合(server.js 変更なし)

+

# .env をサーバーに転送 scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env + # コンテナを再起動(restart で OK → イメージのリビルド不要) ssh keinafarm-claude 'sudo docker + compose -f /home/claude/alexa-api/docker-compose.yml restart' # ※ Traefik + の再起動は不要(コンテナ再作成しないため)

+

Traefik 再起動が必要な理由

+

docker compose up -d はコンテナを「再作成」する(docker compose restart は既存コンテナを再起動するだけ)。コンテナが再作成されると + Docker 内部の IP アドレスが変わり、Traefik のルーティングが古い IP を参照したまま 502/504 エラーになる。

+

対処: sudo docker restart traefik で Traefik + に新しい IP を再検出させる。

+

この問題は Traefik の設定で watch: true にすれば自動解消できるが、現状はコンテナ再作成のたびに手動で + Traefik を再起動する運用としている。

+

docker-compose.yml の内容

+

services:  alexa-api:    build: .    container_name: + alexa_api    restart: unless-stopped    env_file:   +    - .env    environment:      - PORT=3500 +    networks:      - windmill_windmill-internal +    # 外部には公開しない(Windmill ワーカーから内部ネットワーク経由でのみアクセス)    # + デバッグ時は以下のコメントを外す:    # ports:    #   - "127.0.0.1:3500:3500" + networks:  windmill_windmill-internal:    external: true

+

認証方法(auth4.js)

+

Amazon Japan OpenID フローを自前で実装。ローカル PC(Windows)でのみ実行する:

+

# ローカルPC の alexa-api ディレクトリで実行 cd alexa-api AMAZON_EMAIL="メールアドレス" AMAZON_PASSWORD="パスワード" + node auth4.js

+

成功すると alexa-api/.env が生成または更新される。

+

ログインフローの概要:

+
    +
  1. GET https://www.amazon.co.jp/ap/signin?openid.assoc_handle=amzn_dp_project_dee_jp
  2. +
  3. hidden フィールド(anti-csrftoken-a2z, appActionToken, workflowState 等)を抽出
  4. +
  5. POST でメール/パスワードを送信
  6. +
  7. alexa.amazon.co.jp/api/apps/v1/token へのリダイレクトをたどる
  8. +
  9. 取得した Cookie(at-acbjp, sess-at-acbjp, sst-acbjp, session-token 等)を .env + に保存
  10. +
+

TTS の仕組み(server.js)

+

alexa-remote2 は使わない直接 API 実装。Endpoints:

+
    +
  • POST /speak — { device: "デバイス名 or serial", text: "しゃべる内容" }
  • +
  • GET /devices — デバイス一覧
  • +
  • GET /health — ヘルスチェック
  • +
+

内部の API 呼び出し順序:

+
    +
  1. GET /api/language → Set-Cookie: csrf=XXXXX を取得(毎リクエストごと)
  2. +
  3. GET /api/bootstrap → customerId を取得(キャッシュ: 永続)(A1AE8HXD8IJ61L)
  4. +
  5. GET /api/devices-v2/device → デバイス一覧(5分キャッシュ)
  6. +
  7. POST /api/behaviors/preview にシーケンス JSON を送信
  8. +
+

POST /api/behaviors/preview のボディ構造:

+

{  behaviorId: "PREVIEW",  sequenceJson: JSON.stringify({   +  "@type": "com.amazon.alexa.behaviors.model.Sequence",    startNode: + {      "@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode", +      type: "Alexa.Speak",      operationPayload: + {        deviceType: "...",        deviceSerialNumber: + "...",        customerId: "A1AE8HXD8IJ61L",   +      locale: "ja-JP",    // ← 重要(下記参照)   +      textToSpeak: "発話内容"      }    } +  }),  status: "ENABLED" }

+

ヘッダーに csrf: XXXXX と Cookie に csrf=XXXXX の両方が必要。Content-Length + は Buffer.byteLength で計算(マルチバイト文字対応)。

+

⚠ locale パラメータについて(重要・未解決)

+
+ + + + + + + + + + + + + + + + + + + + +
locale 値ローカル PC からサーバー(keinafarm.net)から
""(空文字)✅ 日本語・英語・漢字全て発話❌ 英語TTSになり日本語部分が発話されない
"ja-JP"❌ 一瞬音が出るだけ(失敗)❌ 日本語文字が Amazon 側で除去され英字のみ発話
+
+

現在 server.js では locale: "ja-JP" に設定している。

+

仮説: Amazon が海外IP(keinafarm.net = 非日本IP)からのリクエストを IP ベースでフィルタリングし、textToSpeak + の日本語文字を除去している。Alexa.TextCommand は同じ問題がない(異なる API パス)。

+

確認済み事実: alexa_api の server.js ログには日本語テキストが正しく届いている。除去は + Amazon サーバー側で発生。

+

次の調査候補:

+
    +
  • SSML の <lang xml:lang="ja-JP"> タグで強制的に日本語 TTS を指定できるか
  • +
  • Alexa.TextCommand で「次を読み上げて:{text}」形式が使えるか
  • +
  • ローカルブリッジ方式(ユーザーのローカル PC で小さなプロキシサーバーを動かし、クラウドサーバーからローカル経由で alexa.amazon.co.jp + を叩く)
  • +
+

デバイス一覧(Echo デバイスのみ)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
名前deviceTypeserialNumber
プレハブA4ZXE0RM7LQ7AG0922H085165007R
リビングエコー1ASQZWP4GPYUT7G8M2DB08522600RL
リビングエコー2ASQZWP4GPYUT7G8M2DB08522503WF
オフィスの右エコーA4ZXE0RM7LQ7AG0922H08525302K5
オフィスの左エコーA4ZXE0RM7LQ7AG0922H08525302J9
寝室のエコーASQZWP4GPYUT7G8M2HN08534302XH
+
+

Windmill スクリプト(u/admin/alexa_speak)

+

export async function main(device: string, text: string) {  const + res = await fetch("http://alexa_api:3500/speak", {    method: + "POST",    headers: { "Content-Type": "application/json" },   +  body: JSON.stringify({ device, text }),  });  if (!res.ok) + throw new Error("alexa-api error " + res.status);  return res.json(); + }

+

device はデバイス名(日本語)またはシリアル番号で指定可能。Windmill ワーカーから http://alexa_api:3500 でアクセス(windmill_windmill-internal + ネットワーク経由)。

+

Cookie の更新手順

+

Cookie は数日〜数週間で期限切れ。切れたら:

+

# 1. ローカル PC で Cookie を取得 cd alexa-api AMAZON_EMAIL="メールアドレス" AMAZON_PASSWORD="パスワード" + node auth4.js # → alexa-api/.env が更新される # 2. サーバーに .env を転送 scp alexa-api/.env + keinafarm-claude:/home/claude/alexa-api/.env # 3. コンテナを再起動(restart で OK、リビルド不要) + ssh keinafarm-claude 'sudo docker compose -f /home/claude/alexa-api/docker-compose.yml + restart' # ※ Traefik 再起動は不要(コンテナ再作成なし)

+

既知の問題・落とし穴

+
    +
  • docker compose restart ≠ リビルド: server.js を変更しても restart + ではコンテナ内のコードは古いまま。build + up -d が必要。
  • +
  • コンテナ再作成後は Traefik 再起動必須: up -d でコンテナ再作成すると Docker 内部 + IP が変わり Traefik が 502/504 を返す。sudo docker restart traefik で解消。
  • +
  • alexa-remote2 は使えない: 取得した Cookie 文字列を受け付けない(内部で再認証しようとして失敗)。直接 + API 実装が必要。
  • +
  • CSRF トークンは Cookie と ヘッダーの両方に必要: csrf ヘッダーだけ、または Cookie + だけでは認証失敗。
  • +
  • operationPayload に customerId 必須: ないと 400 エラー。
  • +
  • レート制限: 短時間に連続リクエストすると HTTP 429 または 200 で音が出ない。通常の通知用途では問題なし。
  • +
  • git push がブロックされる: Gitea の pre-receive フック(remote: Gitea: User permission denied for writing)で + push が失敗する。根本原因は未調査。ファイル転送は scp で行っている。
  • +
  • firstRunCompleted: false はデバイス設定の未完了フラグ: TTS には直接影響しない(root + cause ではなかった)。
  • +
+

サーバー上の運用コマンド一覧

+

# コンテナ状態確認 sudo docker ps | grep alexa # リアルタイムログ確認 sudo docker logs alexa_api + -f # コンテナ停止 sudo docker compose -f /home/claude/alexa-api/docker-compose.yml + stop # ビルド+起動(コード変更後) cd /home/claude/alexa-api sudo docker compose build + sudo docker compose up -d sudo docker restart traefik # Cookie 更新時(再起動のみ) + sudo docker compose -f /home/claude/alexa-api/docker-compose.yml restart

+
+
+ + + \ No newline at end of file diff --git a/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/index.html b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/index.html new file mode 100644 index 0000000..f7d5dba --- /dev/null +++ b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/index.html @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/navigation.html b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/navigation.html new file mode 100644 index 0000000..8564109 --- /dev/null +++ b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/navigation.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/style.css b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/style.css new file mode 100644 index 0000000..47274e5 --- /dev/null +++ b/docs/alexa-api/Alexa TTS API 実装記録 (2026-03-02)/style.css @@ -0,0 +1,551 @@ +/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */ + +.printed-content .ck-widget__selection-handle, .printed-content .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */ + display: none; +} + +/* + * CKEditor 5 (v41.0.0) content styles. + * Generated on Fri, 26 Jan 2024 10:23:49 GMT. + * For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html + */ + +:root { + --ck-color-image-caption-background: hsl(0, 0%, 97%); + --ck-color-image-caption-text: hsl(0, 0%, 20%); + --ck-color-mention-background: hsla(341, 100%, 30%, 0.1); + --ck-color-mention-text: hsl(341, 100%, 30%); + --ck-color-selector-caption-background: hsl(0, 0%, 97%); + --ck-color-selector-caption-text: hsl(0, 0%, 20%); + --ck-highlight-marker-blue: hsl(201, 97%, 72%); + --ck-highlight-marker-green: hsl(120, 93%, 68%); + --ck-highlight-marker-pink: hsl(345, 96%, 73%); + --ck-highlight-marker-yellow: hsl(60, 97%, 73%); + --ck-highlight-pen-green: hsl(112, 100%, 27%); + --ck-highlight-pen-red: hsl(0, 85%, 49%); + --ck-image-style-spacing: 1.5em; + --ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2); + --ck-todo-list-checkmark-size: 16px; +} + +/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */ +.ck-content .table .ck-table-resized { + table-layout: fixed; +} +/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */ +.ck-content .table table { + overflow: hidden; +} +/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */ +.ck-content .table td, +.ck-content .table th { + overflow-wrap: break-word; + position: relative; +} +/* @ckeditor/ckeditor5-table/theme/table.css */ +.ck-content .table { + margin: 0.9em auto; + display: table; +} +/* @ckeditor/ckeditor5-table/theme/table.css */ +.ck-content .table table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; + height: 100%; + border: 1px double hsl(0, 0%, 70%); +} +/* @ckeditor/ckeditor5-table/theme/table.css */ +.ck-content .table table td, +.ck-content .table table th { + min-width: 2em; + padding: .4em; + border: 1px solid hsl(0, 0%, 75%); +} +/* @ckeditor/ckeditor5-table/theme/table.css */ +.ck-content .table table th { + font-weight: bold; + background: hsla(0, 0%, 0%, 5%); +} +/* @ckeditor/ckeditor5-table/theme/table.css */ +.ck-content[dir="rtl"] .table th { + text-align: right; +} +/* @ckeditor/ckeditor5-table/theme/table.css */ +.ck-content[dir="ltr"] .table th { + text-align: left; +} +/* @ckeditor/ckeditor5-table/theme/tablecaption.css */ +.ck-content .table > figcaption { + display: table-caption; + caption-side: top; + word-break: break-word; + text-align: center; + color: var(--ck-color-selector-caption-text); + background-color: var(--ck-color-selector-caption-background); + padding: .6em; + font-size: .75em; + outline-offset: -1px; +} +/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */ +.ck-content .page-break { + position: relative; + clear: both; + padding: 5px 0; + display: flex; + align-items: center; + justify-content: center; +} +/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */ +.ck-content .page-break::after { + content: ''; + position: absolute; + border-bottom: 2px dashed hsl(0, 0%, 77%); + width: 100%; +} +/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */ +.ck-content .page-break__label { + position: relative; + z-index: 1; + padding: .3em .6em; + display: block; + text-transform: uppercase; + border: 1px solid hsl(0, 0%, 77%); + border-radius: 2px; + font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif; + font-size: 0.75em; + font-weight: bold; + color: hsl(0, 0%, 20%); + background: hsl(0, 0%, 100%); + box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */ +.ck-content .media { + clear: both; + margin: 0.9em 0; + display: block; + min-width: 15em; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list { + list-style: none; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list li { + position: relative; + margin-bottom: 5px; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list li .todo-list { + margin-top: 5px; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list .todo-list__label > input { + -webkit-appearance: none; + display: inline-block; + position: relative; + width: var(--ck-todo-list-checkmark-size); + height: var(--ck-todo-list-checkmark-size); + vertical-align: middle; + border: 0; + left: -25px; + margin-right: -15px; + right: 0; + margin-left: 0; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content[dir=rtl] .todo-list .todo-list__label > input { + left: 0; + margin-right: 0; + right: -25px; + margin-left: -15px; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list .todo-list__label > input::before { + display: block; + position: absolute; + box-sizing: border-box; + content: ''; + width: 100%; + height: 100%; + border: 1px solid hsl(0, 0%, 20%); + border-radius: 2px; + transition: 250ms ease-in-out box-shadow; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list .todo-list__label > input::after { + display: block; + position: absolute; + box-sizing: content-box; + pointer-events: none; + content: ''; + left: calc( var(--ck-todo-list-checkmark-size) / 3 ); + top: calc( var(--ck-todo-list-checkmark-size) / 5.3 ); + width: calc( var(--ck-todo-list-checkmark-size) / 5.3 ); + height: calc( var(--ck-todo-list-checkmark-size) / 2.6 ); + border-style: solid; + border-color: transparent; + border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0; + transform: rotate(45deg); +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list .todo-list__label > input[checked]::before { + background: hsl(126, 64%, 41%); + border-color: hsl(126, 64%, 41%); +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list .todo-list__label > input[checked]::after { + border-color: hsl(0, 0%, 100%); +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list .todo-list__label .todo-list__label__description { + vertical-align: middle; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] { + position: absolute; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content .todo-list .todo-list__label > input, +.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input { + cursor: pointer; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content .todo-list .todo-list__label > input:hover::before, .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input:hover::before { + box-shadow: 0 0 0 5px hsla(0, 0%, 0%, 0.1); +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input { + -webkit-appearance: none; + display: inline-block; + position: relative; + width: var(--ck-todo-list-checkmark-size); + height: var(--ck-todo-list-checkmark-size); + vertical-align: middle; + border: 0; + left: -25px; + margin-right: -15px; + right: 0; + margin-left: 0; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content[dir=rtl] .todo-list .todo-list__label > span[contenteditable=false] > input { + left: 0; + margin-right: 0; + right: -25px; + margin-left: -15px; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::before { + display: block; + position: absolute; + box-sizing: border-box; + content: ''; + width: 100%; + height: 100%; + border: 1px solid hsl(0, 0%, 20%); + border-radius: 2px; + transition: 250ms ease-in-out box-shadow; +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::after { + display: block; + position: absolute; + box-sizing: content-box; + pointer-events: none; + content: ''; + left: calc( var(--ck-todo-list-checkmark-size) / 3 ); + top: calc( var(--ck-todo-list-checkmark-size) / 5.3 ); + width: calc( var(--ck-todo-list-checkmark-size) / 5.3 ); + height: calc( var(--ck-todo-list-checkmark-size) / 2.6 ); + border-style: solid; + border-color: transparent; + border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0; + transform: rotate(45deg); +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::before { + background: hsl(126, 64%, 41%); + border-color: hsl(126, 64%, 41%); +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::after { + border-color: hsl(0, 0%, 100%); +} +/* @ckeditor/ckeditor5-list/theme/todolist.css */ +.ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] { + position: absolute; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ol { + list-style-type: decimal; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ol ol { + list-style-type: lower-latin; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ol ol ol { + list-style-type: lower-roman; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ol ol ol ol { + list-style-type: upper-latin; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ol ol ol ol ol { + list-style-type: upper-roman; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ul { + list-style-type: disc; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ul ul { + list-style-type: circle; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ul ul ul { + list-style-type: square; +} +/* @ckeditor/ckeditor5-list/theme/list.css */ +.ck-content ul ul ul ul { + list-style-type: square; +} +/* @ckeditor/ckeditor5-image/theme/image.css */ +.ck-content .image { + display: table; + clear: both; + text-align: center; + margin: 0.9em auto; + min-width: 50px; +} +/* @ckeditor/ckeditor5-image/theme/image.css */ +.ck-content .image img { + display: block; + margin: 0 auto; + max-width: 100%; + min-width: 100%; + height: auto; +} +/* @ckeditor/ckeditor5-image/theme/image.css */ +.ck-content .image-inline { + /* + * Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).; + * Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root. + * This strange behavior does not happen with inline-flex. + */ + display: inline-flex; + max-width: 100%; + align-items: flex-start; +} +/* @ckeditor/ckeditor5-image/theme/image.css */ +.ck-content .image-inline picture { + display: flex; +} +/* @ckeditor/ckeditor5-image/theme/image.css */ +.ck-content .image-inline picture, +.ck-content .image-inline img { + flex-grow: 1; + flex-shrink: 1; + max-width: 100%; +} +/* @ckeditor/ckeditor5-image/theme/imageresize.css */ +.ck-content img.image_resized { + height: auto; +} +/* @ckeditor/ckeditor5-image/theme/imageresize.css */ +.ck-content .image.image_resized { + max-width: 100%; + display: block; + box-sizing: border-box; +} +/* @ckeditor/ckeditor5-image/theme/imageresize.css */ +.ck-content .image.image_resized img { + width: 100%; +} +/* @ckeditor/ckeditor5-image/theme/imageresize.css */ +.ck-content .image.image_resized > figcaption { + display: block; +} +/* @ckeditor/ckeditor5-image/theme/imagecaption.css */ +.ck-content .image > figcaption { + display: table-caption; + caption-side: bottom; + word-break: break-word; + color: var(--ck-color-image-caption-text); + background-color: var(--ck-color-image-caption-background); + padding: .6em; + font-size: .75em; + outline-offset: -1px; +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-style-block-align-left, +.ck-content .image-style-block-align-right { + max-width: calc(100% - var(--ck-image-style-spacing)); +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-style-align-left, +.ck-content .image-style-align-right { + clear: none; +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-style-side { + float: right; + margin-left: var(--ck-image-style-spacing); + max-width: 50%; +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-style-align-left { + float: left; + margin-right: var(--ck-image-style-spacing); +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-style-align-center { + margin-left: auto; + margin-right: auto; +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-style-align-right { + float: right; + margin-left: var(--ck-image-style-spacing); +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-style-block-align-right { + margin-right: 0; + margin-left: auto; +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-style-block-align-left { + margin-left: 0; + margin-right: auto; +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content p + .image-style-align-left, +.ck-content p + .image-style-align-right, +.ck-content p + .image-style-side { + margin-top: 0; +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-inline.image-style-align-left, +.ck-content .image-inline.image-style-align-right { + margin-top: var(--ck-inline-image-style-spacing); + margin-bottom: var(--ck-inline-image-style-spacing); +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-inline.image-style-align-left { + margin-right: var(--ck-inline-image-style-spacing); +} +/* @ckeditor/ckeditor5-image/theme/imagestyle.css */ +.ck-content .image-inline.image-style-align-right { + margin-left: var(--ck-inline-image-style-spacing); +} +/* @ckeditor/ckeditor5-highlight/theme/highlight.css */ +.ck-content .marker-yellow { + background-color: var(--ck-highlight-marker-yellow); +} +/* @ckeditor/ckeditor5-highlight/theme/highlight.css */ +.ck-content .marker-green { + background-color: var(--ck-highlight-marker-green); +} +/* @ckeditor/ckeditor5-highlight/theme/highlight.css */ +.ck-content .marker-pink { + background-color: var(--ck-highlight-marker-pink); +} +/* @ckeditor/ckeditor5-highlight/theme/highlight.css */ +.ck-content .marker-blue { + background-color: var(--ck-highlight-marker-blue); +} +/* @ckeditor/ckeditor5-highlight/theme/highlight.css */ +.ck-content .pen-red { + color: var(--ck-highlight-pen-red); + background-color: transparent; +} +/* @ckeditor/ckeditor5-highlight/theme/highlight.css */ +.ck-content .pen-green { + color: var(--ck-highlight-pen-green); + background-color: transparent; +} +/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */ +.ck-content blockquote { + overflow: hidden; + padding-right: 1.5em; + padding-left: 1.5em; + margin-left: 0; + margin-right: 0; + font-style: italic; + border-left: solid 5px hsl(0, 0%, 80%); +} +/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */ +.ck-content[dir="rtl"] blockquote { + border-left: 0; + border-right: solid 5px hsl(0, 0%, 80%); +} +/* @ckeditor/ckeditor5-basic-styles/theme/code.css */ +.ck-content code { + background-color: hsla(0, 0%, 78%, 0.3); + padding: .15em; + border-radius: 2px; +} +/* @ckeditor/ckeditor5-font/theme/fontsize.css */ +.ck-content .text-tiny { + font-size: .7em; +} +/* @ckeditor/ckeditor5-font/theme/fontsize.css */ +.ck-content .text-small { + font-size: .85em; +} +/* @ckeditor/ckeditor5-font/theme/fontsize.css */ +.ck-content .text-big { + font-size: 1.4em; +} +/* @ckeditor/ckeditor5-font/theme/fontsize.css */ +.ck-content .text-huge { + font-size: 1.8em; +} +/* @ckeditor/ckeditor5-mention/theme/mention.css */ +.ck-content .mention { + background: var(--ck-color-mention-background); + color: var(--ck-color-mention-text); +} +/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */ +.ck-content hr { + margin: 15px 0; + height: 4px; + background: hsl(0, 0%, 87%); + border: 0; +} +/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */ +.ck-content pre { + padding: 1em; + color: hsl(0, 0%, 20.8%); + background: hsla(0, 0%, 78%, 0.3); + border: 1px solid hsl(0, 0%, 77%); + border-radius: 2px; + text-align: left; + direction: ltr; + tab-size: 4; + white-space: pre-wrap; + font-style: normal; + min-width: 200px; +} +/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */ +.ck-content pre code { + background: unset; + padding: 0; + border-radius: 0; +} +@media print { + /* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */ + .ck-content .page-break { + padding: 0; + } + /* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */ + .ck-content .page-break::after { + display: none; + } +}