
å³č ćåććŖćēµ¶ęććēć¾ćććAIćØåµć£ćććøćæć«ęåē·“ēæåø³ć¢ććŖšš± #AI ćØć§ććć㨠#Gemini #Canvas #Notebook
ęåććęøććććØćÆććć£ćØå½ććåć ćØęć£ć¦ćć¾ććć
失ćććć®ę„ć¾ć§ćÆć
ććęčæćä½čŖæćå“©ććęćććć«ä½ćåćććŖćę„ć ćē¶ćć¦ćć¾ćććē¹ć«č¾ćć£ćć®ćÆćå³č ćę²ćććŖććŖć£ć¦ćć¾ć£ćććØć
å©ćęć§ććå³ęć使ććŖććŖćć
ęåćęøćććØćććć¼ćć¼ćć§ęåćęć¤ććØćć
ęćććć«ć§ćć¾ććć
ę„åøøć®ä½ę°ćŖćåä½ćꬔć
ćØé£ćććŖćć
å½ććåć«ć§ćć¦ććććØćć
ćććŖć«ć大åć ć£ćć®ć ćØåćć¦ę°ć„ćććć¾ććć
ę
£ććŖćå·¦ęć§ć®ćæć¤ćć³ć°ćÆć
ę³å仄äøć«ęéćććććć©ć£ćØē²ćć¦ćć¾ćć¾ćć
ć¹ććć®é³å£°å
„åć«é ¼ć£ć¦ćæć¦ćć
å°čŖ¬ć®ćććŖé·ęćē¶“ćć®ćÆćč³é£ć®ę„ā¦š¦
ćä»å¹“ć®ćåµä½å¤§č³2026ććøć®ę°č¦åæåćÆć
ćć諦ćććććŖćć®ććŖā¦ā¦ćć
ććč½ć”č¾¼ćć§ćć¾ććć
åµä½å¤§č³2026ćŖć¼ć«ć«ćć“ćŖéØéćå®å Øē”ęć»ē„ć¢ććŖćå¤ę©č½ććøćæć«ęåē·“ēæåø³ć¢ććŖš±iPadć»ć¹ććć»PCć§ćć³åē·“ēæš #ē¾ęå #ć¢ććŖ #ai #Gemini #canvas
ę„ę¬čŖēā

č±čŖēāćā»AIć«ććčŖå翻訳notečØäŗ

š„ åµä½ć®ēÆćēµ¶ćććććŖćć¦
ććć§ć諦ććććŖćć£ćć®ćÆć
ē§ć«ćÆå¤§åćŖä½åćØć
ćććęÆćć¦ćććäŗŗćć”ćććććć§ćć
Shionć”ććć®ć¹ćæć³ć - LINE ć¹ćæć³ć | LINE STORE
ęØå¹“ććØć³ćæć”åä½éØéć§åæåććå°čŖ¬
ćå
ē-ćć«ćŖćć«ć±ć©-ćāØ
ä»å¹“ćÆć«ćć“ćŖć¼ćå¤ę“ćć
ćć”ć³ćæćøć¼å°čŖ¬éØéļ¼TALESļ¼ć«ć¦
åć³åæåććć¦ććć ćć¾ććš
š å°čŖ¬ćå ē-ćć«ćŖćć«ć±ć©-ć
šćć¬ćøć³ćÆćć”ć
š åµä½å¤§č³2026šå
ē-ćć«ćŖćć«ć±ć©-ļ½note
ćć®ä½åćÆćęØå¹“ćŖććØ
10äøåćč¶
ććęę³ćććć ćć
ććććć®ę¹ć«ćć”ć³ć¢ć¼ććęćć¦ććć ććć
ē§ć®å®ē©ć®ćććŖä½åć§ćš
ććć«ćę ē»åćē®ęćć¦åć®ęµ·å¤ć”ć³ćć¼ćØååćć
ć¢ćć”ę ååļ¼ć¹ćć·ć£ć«ć”ćć¬ć¼MVļ¼ć¾ć§
ęććććØćć§ćć¾ććš«
š¬ ęę³äøč¦§ļ¼ćć”ć³ć¢ć¼ćē¹é
š10äøåć®ęćč©°ć¾ć£ćęę³ć¾ćØć
š ćåµä½å¤§č³ęę³ćä½åę”å
ļ¼ēę§ć®ęę³äøč¦§āØ
šØ ęµ·å¤éčŖé¢Øļ¼ćć”ć³ć¢ć¼ćē¹é
š AI ART TOKYO 2025 - Kawaii Wonderland
š¬ ć¢ćć”ę ååļ¼YouTubeļ¼
åć®ęµ·å¤ćć¼ć ćØć®å
±åå¶ä½ļ¼
ę„ę¬čŖć®ć¹ćć·ć£ć«ć”ćć¬ć¼ć§ćāŖ
ćć²ćć覧ćć ććš
šµå ē-ćć«ćŖćć«ć±ć©-åēćŖć¹ć
ē·ē¹éš«å
ēćć«ćŖćć«ć±ć©š
Hikari no KakerašŖFragments of Lightš - YouTube
ā¶ļø Hikari no Kakera ā The Miracle Answer | Cinematic Anime Music Video
ā¶ļø Hikari no Kakera MV: ~ Dreams of Parallel Lines ~
š” éå¢ććēć¾ćććå·¦ęć®ććć®ćć¢ććŖ

å³č ćåććŖćēµ¶ęććēć¾ććć
AIćØåµć£ććå·¦ęć§ćęøćććććććøćæć«ęåē·“ēæåø³š
iPadć§åćå¤ę©č½WEBć¢ććŖļ¼
ć²ćććŖć»ć«ćæć«ćć»ćć¼ćåć»ę°å対åæ
ę°ä½å°čŖ¬ć®å·ēćÆé£ććć
ć§ććä»ć®ē§ć ććććēćæåŗćććć®ććććÆćć
ććčććē§ćÆć
ćAIćé§ä½æćć¦ćä»ć®čŖåć®äøä¾æćć
ęéććć¢ććŖćä½ććļ¼ććØę±ŗęćć¾ććć
ćććć¦čŖēććć®ććä»å
ćåµä½å¤§č³2026ćŖć¼ć«ć«ćć“ćŖéØéćć§åæåććć
ćć©ć¦ć¶äøć§åć
ćå¤ę©č½ććøćæć«ęåē·“ēæåø³ć¢ććŖćć§ćć
ē§ćØåćććć«ćå·¦ęććŖć©ēęć§ęåćęøćč¦å“ććć¦ććę¹ć
ćå³ęćć§åćē¶ŗéŗć«ęøćććļ¼ćŖćććŖćććļ¼ę¹ć
ęåćå¦ć³å§ććåć©ććć”ćå¤å½äŗŗć®ę¹ć
ćć³åē·“ēæåø³ćććøćæć«åćććę¹ćøć
ćććććØä½æćććććč©°ćč¾¼ćć ć
5ć¤ć®ē¹å¾“ćć瓹ä»ćć¾ćļ¼

QRć³ć¼ććčŖćæč¾¼ććØććć®noteć«ćøć£ć³ććć¾ćš
šå¤ę©č½ććøćæć«ęåē·“ēæåø³ć¢ććŖ

ćććć¼ć®å¤ę©č½ććæć³ćććććććęä½ćē“ęēć«č”ćć¾ćļ¼
ä¾ćć°ććć
ć²ćććŖ ā ć«ćæć«ć ā ćć¼ćå ā ę°å ć®åćęæć
å·¦ęēØāå³ęēØ
äæåå½¢å¼ļ¼PNGē»å ā PDFļ¼ć®éøę
čØčŖå¤ęļ¼ę„ę¬čŖāč±čŖļ¼
é³å£°čŖćæäøćććŗć¼ć ęä½ćŖć©

š± ććøćæć«ęåē·“ēæåø³ć¢ććŖć®5大ē¹å¾“

5ć¤ć®ē¹å¾“
1. ćå·¦å©ććć«å®å ØåƾåæććććććUI
ę大ć®ćć ćććÆćå©ćęåćęæćę©č½ćć§ćć
ćå·¦ęēØļ¼čµ¤č²ćć¼ćļ¼ćć«åćęæćććØć
ęøćć¦ććęć§ęåćé ćć¦ćć¾ććŖćććć«ć
ććęę¬ć»ćŖććęøćć»ē·“ēæćć®ćć¹ē®ć®äø¦ć³é ćå転ćć¾ćć
å·¦å©ćć®ćåę§ć®å¦ēæć«ćęé©ć§ćļ¼
2. č±åÆćŖęå種ćØćꮵéēććŖå¦ēæć¹ććć
ćć äŗåé³ćäø¦ć¹ćć®ć§ćÆćŖćć
ęøććććęåććē·“ēæć§ćć
ćć¹ćććć¢ććę¹å¼ććę”ēØć
ć²ćććŖć»ć«ćæć«ćļ¼
1ē»ć®ē°”åćŖęåććč¤éćŖęåćøć
äŗåé³č”Øļ¼ēø¦ęØŖļ¼ćå®åćć¼ćåļ¼
大ęåć»å°ęåćęæé³ć»ęé³ććć
ćććć³å¼ćØčØä»¤å¼ć®éć
ļ¼ćć¹ćć¼ć蔨čØćØå¦ę ”ęč²ć®éćļ¼ććØćć£ć
å®č·µēćŖå 容ć¾ć§ć«ćć¼ę°åļ¼
ē®ēØę°åļ¼0ć9ļ¼ćØę¼¢ę°åļ¼äøćäøćććåļ¼
3. ćæććć§ēŗé³ļ¼ćé³å£°čŖćæäøćę©č½ć
ćęę¬ć®ęåććæćććććØććć®ęåćé³å£°ć§čŖćæäøćć¾ćšµ
ć¢ć«ćć”ććććÆćć¤ćć£ć風ć«ć
ćć¼ćåćÆę„ę¬čŖć®é³ć§čŖććŖć©ē“°ććčŖæę“ć
ćć¼ćøå
Øä½ćäøę°ć«čŖćæäøććććæć³ćććć
ćč³ćććć®å¦ēæććµćć¼ććć¾ćć
4. ććøćæć«ćŖćć§ćÆć®ćęøćč¾¼ćæļ¼äæåę©č½ć
ćć³ļ¼ę¶ćć“ć ļ¼
iPadēć®ćæćć¬ćććPCćććē“ę„ē»é¢ć«ęåćęøćč¾¼ćć¾ćććŗć¼ć ļ¼ē§»åļ¼ćć³ļ¼ļ¼
å°ććē»é¢ć§ćę”大ćć¦ē“°ććē·“ēæćåÆč½ćPDF / ē»åļ¼PNGļ¼äæåļ¼
ęøćććć¼ćøćć¾ćØćć¦äæåļ¼
ććŖć³ćć¢ć¦ććć¦ćē“ć®ććŖć«ććØćć¦ä½æćććØćć§ćć¾ćš
ļ¼PCćiPadććæćć¬ćććŖć©ć§äæåććććē»åäæåä»ćļ¼
5. 使ćäŗŗć«åÆćę·»ć£ćć便å©ćŖåćęæćę©č½ć
ćć¼ćÆć¢ć¼ć対åæļ¼
å¤éć®å¦ēæćē®ć«é ę ®ććé»čęÆć¢ć¼ćć
ęøććē·ć®č²ćčŖåć§ē½é»å転ćć¾ććę„č±ćć¤ćŖć³ć¬ć«åƾåæļ¼
ććæć³äøć¤ć§ć”ćć„ć¼ććę„ę¬čŖāč±čŖćć«åćęæćåÆč½ļ¼
ę„ę¬čŖćåå¼·äøć®å¤å½äŗŗć®ę¹ć«ććć®ć¾ć¾ä½æćć¾ćć
šĀ ć¢ććŖć®ē»é¢ćØä½æćę¹ļ¼WEBć»HTMLļ¼

iPadć§åćå¤ę©č½WEBć¢ććŖ
āGemini - Google AI ć«ē“ę„ć¢ćÆć»ć¹
ćć®ć¢ććŖć®ę大ć®ćć ćććÆć
äø”å©ć対åæćć¤ććøćæć«WEBć¢ććŖć§ć
ä½åŗ¦ć§ćē¹°ćčæććć¬ć¼ćć³ć°ć«ä½æćć¾ćļ¼ļ¼
ććć«ććć ććććććé ć«ć
äŗåé³é ć«ęåćäø¦ć¹ćć®ć§ćÆćŖćć
ćęøććććęåććå°ććć¤é£ęåŗ¦ćäøćć¦ććć
ē¬čŖć®ć¹ćććć¢ććę¹å¼ćę”ēØćć¦ććē¹ć§ćć
šćććć¼ć®č§£čŖ¬

ćććć¼ć®č§£čŖ¬
ćć®ćććć¼ćÆćē»åć»č³ęć®é²č¦§ćē·ØéćäæåćŖć©ćč”ćććć®ć”ćć„ć¼ć§ćć
Ā ć²ćććŖļ¼ć«ćæć«ćåćęæćĀ
ć²ćććŖćØć«ćæć«ććēøäŗć«åćęæćć¾ććĀ ćć¼ćåć»ć¢ć«ćć”ćććļ¼ć»ććļ¼
ćć¼ćå蔨čØćØć¢ć«ćć”ććć蔨čØćć»ććć§č”Øē¤ŗćć¾ććĀ ę°åć»ę¼¢ę°åļ¼ć»ććļ¼
ę°åćØę¼¢ę°åćć»ććć§č”Øē¤ŗćć¾ććĀ å©ćęåćęæć
å·¦ęēØļ¼ććć©ć«ćļ¼ććå³ęēØćøć®åćęæććć§ćć¾ćĀ ę¶å»
éøęććéØåćęøćč¾¼ćæćę¶å»ćć¾ććĀ å Øę¶å»
ē»é¢äøć®ćć¹ć¦ć®ęøćč¾¼ćæćå „åå 容ćę¶å»ćć¾ććĀ äæåå½¢å¼ļ¼PNG / PDFļ¼
PNG ć¾ć㯠PDF å½¢å¼ć§ćć¦ć³ćć¼ćć»äæåć§ćć¾ććĀ äæåļ¼ē¾åØć®ćć¼ćøļ¼
ē¾åØć®ćć¼ćøćē»åćć”ć¤ć«ćØćć¦äæåćć¾ććĀ å Øäæåļ¼ćć¹ć¦ć®ćć¼ćøļ¼
ćć¹ć¦ć®ćć¼ćøćć¾ćØćć¦ē»åćć”ć¤ć«ćØćć¦äæåćć¾ććĀ ęē»ćć¼ć«
ćć³ć»ę¶ćć“ć ć»ęć®ć²ćļ¼ē§»åļ¼ćéøęć§ćć¾ććĀ ćć¼ćøē§»å
ćć¼ć ļ¼ęåć®ćć¼ćøć«ę»ćļ¼ć»åćøć»ę¬”ćøć§ćć¼ćøććććć¾ćć 蔨示ć»ćŗć¼ć
ćŗć¼ć ć¢ć¦ćć»å Øē»é¢č”Øē¤ŗć»ćŗć¼ć ć¤ć³ćå©ēØć§ćć¾ććĀ é³å£°åē
é³å£°ć®åēļ¼åę¢ćč”ćć¾ććĀ čØčŖåćęæć
ę„ę¬čŖćØč±čŖćåćęæćć§ćć¾ćć 蔨示ę¹ååćęæć
ēø¦ęøććØęØŖęøćć®äøč¦§č”Øē¤ŗćåćęæćć§ćć¾ććĀ ćć¼ćåćęæć
ć©ć¤ćć¢ć¼ććØćć¼ćÆć¢ć¼ććåćęæćć§ćć¾ćć
ćć¤ć³ć
ēØéćå¦ēæć¹ćæć¤ć«ć«åććć¦ć蔨示ć¢ć¼ćććć¼ć«ćåćęæććććØć§ććć使ćććććŖćć¾ćć
šø ć²ćććŖļ¼å Ø8ćć¼ćøļ¼
ćććŖćé£ććęåććęøććć¾ććļ¼
ēéć³ć®é£ęåŗ¦é ć«ć¹ć¢ć¼ć«ć¹ćććć§é²ćæć¾ćć

Step 1ļ¼č¶ å „éļ¼ļ¼
1ē»ć§ć¹ććØęøććć·ć³ćć«ćŖē·ć
ććć»ć¤ć»ćć»ćøćStep 2ļ¼åē“ļ¼ļ¼
åē“ćŖć«ć¼ććØ2ē»ć®ęåć
ćć®ć»ć¦ć»ćØć»ćć»ćć»ćć»ć²ćStep 3ļ¼äøē“ļ¼ļ¼
ęćčæććę³¢ęć”ć®åććē·“ēæć
ććć»ćć»ćć»ćć»ćć»ćć»ćć»ćć»ććStep 4ļ¼äøē“ļ¼ļ¼
ē·ć®äŗ¤å·®ćå Øä½ć®ćć©ć³ć¹ćę±ććććęåć
ććć»ćć»ćć»ćć»ćć»ćć»ć”ć»ć«ć»ćµć»ćć»ćć»ćć»ććStep 5ļ¼ęé£é¢ļ¼ļ¼
ćć³åć§ęćć¤ć¾ć„ćććććććć£ćØåććēµć³ććććęåć«ęę¦ļ¼
ććć»ćć»ćć»ćŖć»ć¬ć»ćć»ćÆć»ć»ć»ć¾ć»ćæć»ćć»ćć»ććStep 6ć»7ļ¼
ē·ä»äøććØćć¦ććēø¦ęøććć横ęøćć
ććććć®äŗåé³č”Øćå®åćć¦ćć¾ćć
⨠ć«ćæć«ćļ¼å Ø8ćć¼ćøļ¼
ć«ćæć«ćē¹ęć®ćē“ē·ććØćč§åŗ¦ććęčććć«ćŖćć„ć©ć ć§ćć

Step 1ļ¼č¶ å „éļ¼ļ¼
ć·ć³ćć«ćŖē“ē·ćØęćć
ćć¤ć»ćć»ćć»ćć»ćć»ćć»ććStep 2ļ¼åē“ļ¼ļ¼
åććØč§åŗ¦ćå½ć«ćŖććééććććęåććć¹ćæć¼ć
ćć·ć»ćć»ć½ć»ć³ć»ć ć»ć¤ć»ćÆćStep 3ļ¼äøē“ļ¼ļ¼
ęććć«ććå¦ć¶ęåć
ćć¢ć»ć«ć»ć³ć»ćµć»ć»ć»ćæć»ćć»ćć»ćć»ć©ć»ććStep 4ļ¼äøē“ļ¼ļ¼
交差ććē·ć®ćć©ć³ć¹ćå¦ć³ć¾ćć
ććć»ć”ć»ćć»ć±ć»ćć»ćć»ć¹ć»ćć»ć¢ć»ćØć»ć«ć»ćÆćStep 5ļ¼ęé£é¢ļ¼ļ¼
空éć®åćę¹ćé£ććęåć§ä»äøćć¾ćć
ćć¦ć»ćØć»ćŖć»ćć»ćć»ć¦ć»ćŖć»ć¬ć»ć²ćStep 6ć»7ļ¼
ć²ćććŖåę§ćēø¦ęøćć»ęØŖęøćć®äŗåé³č”Øćå®åć
š¤ ćć¼ćåć»ć¢ć«ćć”ćććļ¼å Ø10ćć¼ćøļ¼
åŗē¤ććå®č·µēćŖč”ØčØć«ć¼ć«ć®éćć¾ć§ć
ććäøć¤ć§å®ē§ć«ē¶²ē¾
ć§ćć¾ćļ¼

Step 1ć2ļ¼
ć¾ććÆč±čŖć®åŗę¬ć大ęå(AćZ)ććå°ęå(aćz)ććē·“ēæćStep 3ć4ļ¼
ććč”ććč”ćć®åŗę¬ćć¼ćåćStep 5ļ¼
ćććć±č”ćć®ęæé³ć»åęæé³ćStep 6ć8ļ¼
ćććć»ć²ćć»ććććŖć©ć®ęé³ļ¼ććććļ¼ćå¾¹åŗē·“ēæćStep 9ć10ļ¼č¶ å®č·µē·Øļ¼ļ¼ļ¼
ćććę大ć®ćć¤ć³ćļ¼
ćććć³å¼ććØćčØä»¤å¼ćć®éććęÆč¼ć»č§£čŖ¬ććŖććē·“ēæć§ćć¾ććććć³å¼ļ¼SHI, CHI, TSU, FUļ¼ļ¼
č±čŖč©±č ćēŗé³ććććć
ćć¹ćć¼ććé§ åć»éč·ÆęØčć§ä½æćććå®ēØēćŖęøćę¹ćčØä»¤å¼ļ¼SI, TI, TU, HUļ¼ļ¼
äŗåé³č”Øć®č¦åć«ę²æć£ć¦ććć
ę„ę¬ć®å°å¦ę ”ć®ęę„ć§ēæćććØćå¤ćęøćę¹ćććµćććććććć”ćććŖć©ć
ē¹ć«ééćććć注ęē¹ć
赤č²ćØéč²ć§ćććććććć¤ć©ć¤ć解説ćć¦ćć¾ćć
š¢ ę°åć»ę¼¢ę°åļ¼å Ø2ćć¼ćøļ¼

Step 1ļ¼
ę„åøøć§ęć使ććē®ēØę°åļ¼0ć9ļ¼ćStep 2ļ¼
ćę¼¢ę°åļ¼äøćäøćććåļ¼ć
ę¼¢ę°åć®ćć¹ć«ćÆććć”ćć1000ććŖć©ć®
čŖćæę¹ļ¼ć«ćļ¼ćØć¢ć©ćć¢ę°åćč£å©ćØćć¦č”Øē¤ŗćććććć
ę„ę¬čŖćåå¼·äøć®å¤å½äŗŗć®ę¹ć«ććØć¦ć親åćŖčØčØć«ćŖć£ć¦ćć¾ćć
ā»å ćæć«ććć¹ć¦ć®ć¢ć¼ćć§ćč±čŖć«čØčŖćåćęæćć§ćć¾ćļ¼
čØčŖåćęæćććæć³ćÆćé³å£°ććæć³ć®å³ć§ćļ¼


šć¹ć©ć¤ć解説

AI Handwriting Practice App for iPad, Phone & PC āļø

ćććä¼ćććććØćććć®ć«ä¼ććććŖćäŗŗććććØćććć
ćććå¦ć³ććę°ęć”ćććć®ć«ććć®ę©ä¼ćååć«å¾ćććŖćäŗŗććććØćććć
ē§ćć”ćÆććć®ē¾å®ć«åćåćć¾ććć
ä»ę„ć話ćććć®ćÆćåćŖćć¢ććŖć®č©±ć§ćÆććć¾ććć
äøäŗŗć²ćØćć®åÆč½ę§ććéćć«ćć£ć¦éććććŖćęŖę„ć®č©±ć§ćć
å¤ę©č½ććøćæć«ęåē·“ēæåø³ć¢ććŖćÆććć®ęŖę„ćøåććå°ććŖäøę©ćØćć¦ēć¾ćć¾ććć

äøć®äøć«ćÆćč¦ćć«ććå°é£ćę°å¤ćååØćć¦ćć¾ćć
å·¦å©ćć§ććććØć
čØčćęåć®éććććććØć
čŗ«ä½ę©č½ćå¦ēæē°å¢ć«éććććććØć
ćć®ć©ććććę¬ę„ć®č½åćØćÆē”é¢äæć§ćć
ćććŖć®ć«ćå¤ćć®äŗŗćęę¦ććåć«å£ć«ć¶ć¤ćć£ć¦ćć¾ćć
ē§ćć”ćÆćåćććć¾ććć
ę¬å½ć«å¤ććć¹ććŖć®ćÆćäŗŗć§ćÆćŖćē°å¢ć§ćÆćŖćć§ććććć

ćć®åććøć®ēććØćć¦ēć¾ććć®ććInclusive AI Writing Canvasć§ćć
AIć®åć使ćććć®ćć¼ć«ć§ćÆććć¾ććć
äŗŗć®åćå¼ćåŗćććć«ćAIć使ććć¼ć«ć§ćć
äøäŗŗć²ćØćć®éććå¼±ćæćØćć¦ę±ćć®ć§ćÆćŖććåę§ćØćć¦åćå
„ććć
ćććŖå¦ć³ć®å “ęććććøćæć«ć®åć§å®ē¾ććććØčćć¾ććć
ē§ćć”ćē®ęćć®ćÆćčŖ°ćåćę®ćććŖćå¦ēæä½éØć§ćć

ććÆćććøć¼ćé²åććć»ć©ćē§ćć”ćÆäŗŗéćććć大åć«ććććØčćć¦ćć¾ćć
ć ćććć®ć¢ććŖćÆćē“ć«ęøćęø©ćććę®ćć¾ććć
ęćć¤ććććØćčŖē±ć«ęøćć
試ćć¦ćæćć
ééććć
ććć¦ć¾ćęę¦ććć
ćć®čŖē¶ćŖå¦ć³ć®ęµććå£ććŖćććØććä½ććć大åć«ćć¦ćć¾ćć

å¤ćć®äŗŗć«ćØć£ć¦å½ććåć®ęä½ććčŖ°ćć«ćØć£ć¦ćÆä½æćć„ććć®åå ć«ćŖćć¾ćć
ä¾ćć°ćå·¦å©ćć®ę¹ć§ćć
äøć®äøć®å¤ćć®ćµć¼ćć¹ćÆćē”ęčć®ćć”ć«å³å©ććåęćØćć¦čØčØććć¦ćć¾ćć
ććććē§ćć”ćÆčćć¾ććć
čŖ°ććęę
¢ććåęć®ćć¶ć¤ć³ćÆćę¬å½ć«ę£ććć®ć§ććććć
ććććŖé
ę
®ććććć¾ććć
ććć©ććć®é
ę
®ćčŖ°ćć®ęę¦ćęÆććåć«ćŖćć®ć§ćć

å¦ć³ćÆććć®ē¬éć ćć®ćć®ć§ćÆććć¾ććć
ä»ę„ć§ććŖćć£ćććØććęę„ć§ććććć«ćŖćć
ćć®ęé·ćč¦å±ććććć«ćÆćčØé²ćåæ
č¦ć§ćć
ćć®ć¢ććŖć§ćÆćęęćäæåććęÆćčæćććØćć§ćć¾ćć
éå»ć®čŖåćč¦ćććØć§ćäŗŗćÆåćøé²ćåę°ćå¾ććć¾ćć
čØé²ćÆćåćŖććć¼ćæć§ćÆććć¾ććć
ęŖę„ćøć®åøęć§ćć

ę°ććććØćå§ćććØććäŗŗćÆäøå®ćęćć¾ćć
ć ććē§ćć”ćÆćęä½ćć§ććéćć·ć³ćć«ć«ćć¾ććć
ęøćć
ē“ćć
äæåććć
ććć ćć§ćć
é£ććęä½ćč¦ććććć§ćÆćŖććčŖåććć蔨ē¾ććććć«ęéć使ć£ć¦ć»ććć
ćć®ę³ććććć®čØčØć«č¾¼ćć¦ćć¾ćć

čØčćÆćäŗŗćØäŗŗćć¤ćŖćåć§ćć
ćććććØććØćć¦čØčćÆå£ć«ććŖćć¾ćć
ēŗé³ćåćććŖćć
čŖćæę¹ćåćććŖćć
ć ćććčŖäæ”ć失ć£ć¦ćć¾ćć
ćććŖē¬éććå°ćć§ćęøććććć
ęåćØé³ćēµć³ä»ććēč§£ćęÆććććØć§ćē§ćć”ćÆå¦ć¶åć³ćå±ććććØčćć¦ćć¾ćć

å¦ć³ć®äøć«ćÆćęćē¾ććē¬éćććć¾ćć
ćććÆćåćććŖćć£ćććØćåćć£ćē¬éć§ćć
ę°åć®č”Øē¾äøć¤ćåć£ć¦ććäŗŗć«ćć£ć¦ēč§£ććććå½¢ćÆē°ćŖćć¾ćć
ć ććē§ćć”ćÆćå¤ę§ćŖč”Øē¾ćč¦ęÆć¹ćŖććå¦ć¹ćē°å¢ćēØęćć¾ććć
ēč§£ć§ćććØććęåä½éØććäŗŗćꬔć®ęę¦ćøå°ćć¦ćććććć§ćć

äŗŗćććććå¾ęćŖććØćÆéćć¾ćć
ęåćęøćććØćå¾ęćŖäŗŗć
話ćć¦ä¼ććććØćå¾ęćŖäŗŗć
ēµµćå³ć§čććććØćå¾ęćŖäŗŗć
ć ćććå
„åę¹ę³ćäøć¤ć«éå®ćć¾ććć§ććć
äŗŗććć¼ć«ć«åćććć®ć§ćÆćŖćććć¼ć«ćäŗŗć«åćććć
ććććććę¬å½ć®ć¤ć³ćÆć«ć¼ć·ććć¶ć¤ć³ć ćØčćć¦ćć¾ćć

ć©ćć»ć©åŖććä½éØćććććęÆććęč”ććŖććć°å®ē¾ć§ćć¾ććć
ćć®ć·ć¹ćć ć«ćÆćå¤ćć®å·„夫ćØęč”ćč©°ćč¾¼ć¾ćć¦ćć¾ćć
ććććå©ēØč
ć«č¤éććęćććć¦ćÆććć¾ććć
ē§ćć”ćē®ęććć®ćÆćé«åŗ¦ćŖęč”ćéćć«äŗŗćęÆććäøēć§ćć
主役ćÆć·ć¹ćć ć§ćÆććć¾ććć
å©ēØććäøäŗŗć²ćØćć§ćć

čŖ°ććäøēęøå½ęøććęåć
ē¹°ćčæćē·“ēæććčØé²ć
å°ććć¤äøéććč»č·”ć
ććććÆćć¹ć¦ććć®äŗŗć ćć®č²”ē£ć§ćć
ć ććē§ćć”ćÆććć®åŖåććć£ććę®ććä»ēµćæćä½ćć¾ććć
äŗŗć®ęé·ćÆč¦ćć«ćććć®ć§ćć
ć ćććććę®ććęÆćčæććå®ęć§ććććØć大åćŖć®ć§ćć

使ćććććØćÆćę©č½ć®å¤ćć§ćÆććć¾ććć
å®åæćć¦ä½æććććØć§ćć
é·ęé使ć£ć¦ćē²ććŖćććØć§ćć
čŖåććć使ććććØć§ćć
ć©ć¤ćć¢ć¼ćććć¼ćÆć¢ć¼ććććć®ććć«ååØćć¦ćć¾ćć
ć©ććŖē°å¢ć§ććć©ććŖäŗŗć§ćć
åæ«é©ć«ä½æććä½éØćē®ęćć¾ććć

ē§ćć”ćč¦ć¦ććć®ćÆćē»é¢ć®åććć«ććäŗŗć§ćć
ćŖćććŖćé å¼µćę¹ć
ę°ććčØčćå¦ć¶ę¹ć
åćć¦ęåćęøćåć©ććć”ć
å·¦å©ćć ććäøä¾æćęćć¦ććę¹ć
ćć®äøäŗŗć²ćØćć«åÆćę·»ćććć
ććÆćććøć¼ć®ä¾”å¤ćÆćć©ćć ćå¤ćć®äŗŗćęÆćććććć§ę±ŗć¾ććØäæ”ćć¦ćć¾ćć

čŖ°ćććä½ććä¼ććććØęć£ć¦ćć¾ćć
čŖ°ćććå¦ć³ćććØęć£ć¦ćć¾ćć
čŖ°ćććęé·ććććØęć£ć¦ćć¾ćć
ćć®é”ćć«ćéććéå£ććć£ć¦ćÆćŖććŖćć
ē§ćć”ćä½ć£ćć®ćÆćć¢ććŖć§ćÆććć¾ććć
åÆč½ę§ććććććŖćć¦ćććØććć”ćć»ć¼ćøć§ćć
äøäŗŗć§ćå¤ćć®äŗŗććčŖåćććå¦ć³ćčŖåććć蔨ē¾ć§ćć社ä¼ćøć
å¤ę©č½ććøćæć«ęåē·“ēæåø³ć¢ććŖćÆććć®ęŖę„ćäæ”ćć¦ę©ćæē¶ćć¾ćć
ććććØććććć¾ććć
šå®éć®ć³ć¼ćļ¼ä½æćę¹ļ¼ćÆćć”ć

仄äøć«čØč¼ććHTMLć³ć¼ććć³ćć¼ćć¦
ć”ć¢åø³ć«č²¼ćä»ćć¦ćę”å¼µåć.htmlć§äæåćć¦éćć¦é ććć
Geminić®Canvasć§ä½ć£ććŖć³ćÆććæćććććØć使ēØć§ćć¾ćš
äøčØć®ćŖć³ćÆćę¼ćć¦ćWEBćć©ć¦ć¶ć§éććØć
仄äøć®ē»é¢ć蔨示ććććē¶ćććć®ććæć³ććÆćŖććÆļ¼
ā»Geminić«ćć°ć¤ć³ććŖćć¦ććć¢ććŖć使ēØć§ćć¾ćļ¼ļ¼

ä¾ļ¼ćć”ć¤ć«åļ¼japanese_practice_workbook.html
ć²ćććŖļ¼ć«ćæć«ćļ¼ę°åļ¼ćć¼ćå ē·“ēæåø³ļ¼äø”å©ćć»é³å£°čŖćæäøć対åæļ¼.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>ć²ćććŖļ¼ć«ćæć«ćļ¼ę°åļ¼ćć¼ćå ē·“ēæåø³ļ¼äø”å©ćć»é³å£°čŖćæäøć対åæļ¼</title>
<link href="https://fonts.googleapis.com/css2?family=Klee+One:wght@400;600&family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
padding-top: calc(130px + env(safe-area-inset-top, 0px));
font-family: 'Noto Sans JP', sans-serif;
background-color: var(--body-bg); color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
overflow: auto; touch-action: pan-x pan-y;
}
/* ć©ć¤ćć¢ć¼ć */
:root {
--body-bg: #e9ecef; --bg-color: #ffffff; --text-color: #212529;
--border-color: #adb5bd; --grid-color: rgba(0,0,0,0.15);
--trace-color: rgba(0,0,0,0.2);
--ctrl-bg: #ffffff; --block-bg: #f8f9fa; --cell-bg: #ffffff;
--btn-hover: #f1f3f5;
}
/* å·¦ę: 赤系 / å³ę: éē³» */
body.left-hand { --accent-color: #b52a2a; }
body.right-hand { --accent-color: #2a5eb5; }
/* ćć¼ćÆć¢ć¼ć */
body.dark-mode {
--body-bg: #121212; --bg-color: #1e1e1e; --text-color: #f8f9fa;
--border-color: #495057; --grid-color: rgba(255,255,255,0.15);
--trace-color: rgba(255,255,255,0.25);
--ctrl-bg: #212529; --block-bg: #2b2b2b; --cell-bg: #1e1e1e;
--btn-hover: #343a40;
}
body.dark-mode.left-hand { --accent-color: #ff6b6b; }
body.dark-mode.right-hand { --accent-color: #4a90e2; }
body.move-mode { touch-action: none; }
/* å·¦ęēØć®å “åćÆćć¹ē®ć®äø¦ć³é ćå転 */
body.left-hand .char-block { flex-direction: row-reverse; }
/* ć³ć³ććć¼ć«ćć¼ļ¼ć»ć¼ććØćŖć¢åƾåæļ¼ */
#controls {
position: fixed; top: 0; left: 0; width: 100%;
background-color: var(--ctrl-bg);
padding: calc(12px + env(safe-area-inset-top, 0px)) 15px 12px;
display: flex; flex-direction: column; gap: 12px;
align-items: center;
z-index: 1000; box-shadow: 0 2px 10px rgba(0,0,0,0.15);
border-bottom: 1px solid var(--border-color);
transition: background-color 0.3s;
}
.control-row {
display: flex; justify-content: flex-start; align-items: center;
flex-wrap: nowrap; gap: 12px;
width: fit-content; max-width: 100%;
overflow-x: auto;
-ms-overflow-style: none; scrollbar-width: none;
padding-bottom: 2px;
}
.control-row::-webkit-scrollbar { display: none; }
.tool-group {
display: flex; align-items: center; background: var(--block-bg);
padding: 4px; border-radius: 25px; border: 1px solid var(--border-color);
gap: 2px; white-space: nowrap;
}
.btn {
padding: 8px 14px; border: none; border-radius: 20px;
background-color: transparent; color: var(--text-color); cursor: pointer;
font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 6px;
transition: all 0.2s; font-family: inherit; white-space: nowrap;
}
.btn:hover:not(:disabled) { background-color: var(--btn-hover); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn.active { background-color: var(--accent-color); color: #fff; }
.btn-primary { background-color: var(--accent-color); color: #fff; }
.btn-primary:hover:not(:disabled) { filter: brightness(0.9); }
.icon-btn { width: 40px; height: 40px; justify-content: center; border-radius: 50%; padding: 0; font-size: 16px; }
.text-danger { color: #dc3545; } body.dark-mode .text-danger { color: #ff8787; }
.format-select { padding: 8px 12px; border-radius: 20px; border: none; background-color: transparent; color: var(--text-color); font-weight: bold; font-family: inherit; outline: none; cursor: pointer; }
.format-select option { color: #000; }
.page-indicator { font-weight: bold; font-size: 16px; min-width: 60px; text-align: center; background: var(--block-bg); padding: 6px 16px; border-radius: 20px; border: 1px solid var(--border-color); white-space: nowrap; }
#pages-container { display: flex; justify-content: center; padding: 20px; transform-origin: top center; transition: transform 0.1s ease-out; }
/* ćć¼ćøćµć¤ćŗ (A4横) */
.page {
display: none; width: 1120px; height: 792px; min-width: 1120px;
background-color: var(--bg-color); padding: 15px 30px;
border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);
margin: 0 auto; position: relative; flex-direction: column;
}
.page.active { display: flex; }
.main-title { font-size: 22px; margin: 0 0 6px; border-bottom: 2px solid var(--border-color); padding-bottom: 6px; display: flex; align-items: center; gap: 10px; }
.step-title-small { font-size: 14px; font-weight: bold; margin: 2px 0; display: flex; align-items: center; gap: 8px; }
.step-desc { font-size: 13px; line-height: 1.4; margin-bottom: 8px; opacity: 0.9; white-space: pre-wrap; }
.step-badge { background-color: var(--accent-color); color: #fff; padding: 2px 10px; border-radius: 12px; font-size: 13px; font-weight: bold; }
.step-badge.large { font-size: 16px; padding: 4px 12px; }
.char-block { display: flex; background-color: var(--block-bg); border-radius: 8px; border: 1px solid var(--border-color); }
.cell { position: relative; border: 1px solid var(--border-color); background-color: var(--cell-bg); display: flex; align-items: center; justify-content: center; font-family: 'Klee One', cursive; line-height: 1; flex-shrink: 0; }
.cell.sample span.main-char { color: var(--text-color); font-weight: 600; }
.cell.trace span.main-char { color: var(--trace-color); }
.cell::before, .cell::after { content: ''; position: absolute; pointer-events: none; }
.cell::before { top: 50%; left: 0; right: 0; border-top: 1px dashed var(--grid-color); }
.cell::after { left: 50%; top: 0; bottom: 0; border-left: 1px dashed var(--grid-color); }
canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; touch-action: none; }
.cell.sample { cursor: pointer; transition: opacity 0.2s; }
.cell.sample:active { opacity: 0.5; }
.summary-wrapper { margin: 0 auto; width: max-content; display: flex; flex-direction: column; align-items: flex-start; }
.blocks-container.compact { display: grid; grid-template-columns: repeat(7, max-content); justify-content: flex-start; gap: 6px 8px; margin-bottom: 6px; width: 100%; }
.char-block.compact { gap: 2px; padding: 3px; }
.cell.compact { width: 43px; height: 43px; font-size: 32px; }
.blocks-container.step-layout { display: grid; grid-template-columns: repeat(3, max-content); justify-content: center; gap: 20px 30px; margin-top: 20px; width: 100%; }
.char-block.step { gap: 4px; padding: 6px; }
.cell.step { width: 105px; height: 105px; font-size: 80px; }
.vertical-layout { display: flex; flex-direction: row-reverse; justify-content: center; gap: 14px; margin-top: 10px; }
.column-group { display: flex; flex-direction: column; gap: 4px; }
.horizontal-layout { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 4px; margin-top: 10px; }
.row-group { display: flex; flex-direction: row; gap: 10px; }
.char-block.aiueo { gap: 2px; padding: 3px; }
.cell.aiueo { width: 54px; height: 54px; font-size: 40px; }
.blocks-container.numbers-layout, .blocks-container.kanji-layout { display: grid; grid-template-columns: repeat(3, max-content); justify-content: center; gap: 20px 20px; margin-top: 30px; width: 100%; }
.char-block.numbers-block, .char-block.kanji { gap: 6px; padding: 6px; }
.cell.numbers-cell, .cell.kanji { width: 105px; height: 105px; font-size: 80px; }
.cell.kanji { font-size: 64px; font-family: 'Klee One', cursive; }
.kanji-ruby { position: absolute; top: 6px; left: 0; right: 0; text-align: center; font-size: 18px; font-family: 'Klee One', cursive; font-weight: bold; color: var(--accent-color); opacity: 0.85; pointer-events: none; line-height: 1; }
body.dark-mode .kanji-ruby { color: var(--text-color); opacity: 0.7; }
.kanji-num { position: absolute; bottom: 6px; left: 0; right: 0; text-align: center; font-size: 18px; font-family: 'Noto Sans JP', sans-serif; font-weight: bold; color: var(--accent-color); opacity: 0.85; pointer-events: none; line-height: 1; }
body.dark-mode .kanji-num { color: var(--text-color); opacity: 0.7; }
.cell.kanji span.main-char { margin-top: 0; }
.blocks-container.alphabet-layout { display: flex; flex-direction: column; gap: 14px; align-items: center; margin-top: 20px; width: 100%; }
.blocks-container.alphabet-layout .row-group { display: flex; justify-content: center; align-items: center; gap: 10px; width: 100%; }
.char-block.alphabet { gap: 3px; padding: 3px; }
.cell.alphabet { width: 44px; height: 44px; font-size: 32px; font-family: 'Noto Sans JP', sans-serif; }
.char-block.romaji { gap: 4px; padding: 4px; }
.cell.romaji { width: 64px; height: 64px; font-size: 24px; font-family: 'Noto Sans JP', sans-serif; letter-spacing: 0; }
.romaji-kana { position: absolute; top: 3px; left: 0; right: 0; display: flex; justify-content: space-between; padding: 0 4px; font-size: 14px; font-family: 'Klee One', cursive; font-weight: bold; color: var(--accent-color); opacity: 0.85; pointer-events: none; line-height: 1; }
body.dark-mode .romaji-kana { color: var(--text-color); opacity: 0.7; }
.cell.alphabet span.main-char { font-family: 'Noto Sans JP', sans-serif; }
.cell.romaji span.main-char { font-family: 'Noto Sans JP', sans-serif; margin-top: 14px; }
.char-block.romaji-yoon { gap: 6px; padding: 6px; }
.cell.romaji-yoon { width: 96px; height: 96px; font-size: 40px; font-family: 'Noto Sans JP', sans-serif; letter-spacing: 0; }
.cell.romaji-yoon .romaji-kana { font-size: 16px; top: 6px; padding: 0 8px; }
.cell.romaji-yoon span.main-char { margin-top: 16px; }
/* ==== ććć³å¼ć»čØä»¤å¼ć®ęÆč¼ćć¼ćøēØ CSS ==== */
.diff-page-container { display: flex; gap: 20px; margin-top: 15px; height: calc(100% - 40px); align-items: stretch; }
.diff-text-area { width: 380px; display: flex; flex-direction: column; gap: 12px; }
.diff-card { background: var(--block-bg); border: 1px solid var(--border-color); border-radius: 8px; padding: 12px 16px; font-size: 14.5px; line-height: 1.5; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
.diff-card p { margin: 0; }
.diff-card h4 { margin: 0 0 8px; font-size: 16px; color: var(--accent-color); border-bottom: 1px dashed var(--border-color); padding-bottom: 6px; display: flex; align-items: center; gap: 6px; }
.hebon-card h4 { color: #d35400; } .kunrei-card h4 { color: #2980b9; }
body.dark-mode .hebon-card h4 { color: #e67e22; } body.dark-mode .kunrei-card h4 { color: #3498db; }
/* ććć³å¼ćć¤ć©ć¤ćļ¼čµ¤ē³»ļ¼ */
.diff-highlight { color: #e74c3c; font-weight: bold; }
body.dark-mode .diff-highlight { color: #ff7675; }
.cell.trace span.main-char .diff-highlight { color: inherit; }
/* čØä»¤å¼ćć¤ć©ć¤ćļ¼éē³»ļ¼ */
.kunrei-highlight { color: #2980b9; font-weight: bold; }
body.dark-mode .kunrei-highlight { color: #3498db; }
.cell.trace span.main-char .kunrei-highlight { color: inherit; }
.diff-note-list { margin: 0; padding-left: 20px; } .diff-note-list li { margin-bottom: 6px; }
.diff-practice-area { flex: 1; background: var(--block-bg); border: 1px solid var(--border-color); border-radius: 8px; padding: 12px 16px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
.diff-table-header { display: flex; align-items: center; border-bottom: 2px solid var(--border-color); padding-bottom: 6px; margin-bottom: 8px; font-weight: bold; font-size: 14px; text-align: center; color: var(--text-color); }
.diff-table-header.small { font-size: 12px; }
.diff-col-kana { width: 45px; text-align: center; font-size: 18px; font-family: 'Klee One', cursive; font-weight: bold; }
.diff-col-hebon, .diff-col-kunrei { flex: 1; display: flex; justify-content: center; align-items: center; position: relative; }
.diff-row { display: flex; align-items: center; margin-bottom: 6px; background: var(--cell-bg); padding: 6px; border-radius: 6px; border: 1px solid var(--border-color); }
.char-block.diff-block { gap: 4px; padding: 0; border: none; background: transparent; }
.cell.diff-cell { width: 52px; height: 52px; font-size: 20px; border-radius: 4px; font-family: 'Noto Sans JP', sans-serif; }
.cell.diff-cell.small { width: 46px; height: 46px; font-size: 16px; }
.cell.diff-cell span.main-char { margin-top: 0; }
/* ćć¼ćēØćŖć¼ćć¼ć¬ć¤ */
#loading-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); z-index: 9999; flex-direction: column; justify-content: center; align-items: center; color: white; text-align: center; }
/* ē»åäæåļ¼åēć¢ććŖēØļ¼ć¢ć¼ćć« */
#image-modal {
display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.95); z-index: 10000; flex-direction: column;
}
.modal-header {
display: flex; justify-content: space-between; align-items: center;
padding: calc(15px + env(safe-area-inset-top, 0px)) 15px 15px;
background: #222; color: #fff; font-weight: bold;
}
.modal-content {
flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 20px; align-items: center;
}
.modal-content img {
max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.5);
border: 2px solid #555;
}
</style>
</head>
<body class="dark-mode left-hand" data-tool="pen">
<div id="loading-overlay">
<i class="fa-solid fa-spinner fa-spin fa-4x"></i>
<h2 style="margin-top: 20px;" data-i18n="common.loading_title">äæåå¦ēäø...</h2>
<p data-i18n="common.loading_desc"></p>
</div>
<!-- åēć¢ććŖäæåēØć¢ć¼ćć« -->
<div id="image-modal">
<div class="modal-header">
<span data-i18n="common.modal_guide">ē»åćé·ę¼ććć¦ćåēć«čæ½å ćć¾ććÆćäæåććéøćć§ćć ććć</span>
<button class="btn icon-btn text-danger" onclick="closeModal()"><i class="fa-solid fa-xmark"></i></button>
</div>
<div class="modal-content" id="modal-images"></div>
</div>
<!-- ć³ć³ććć¼ć«ćć¼ -->
<div id="controls">
<div class="control-row">
<div class="tool-group">
<button class="btn btn-primary" onclick="setScriptMode('kana')" title="ć²ćććŖ/ć«ćæć«ć" style="min-width: 90px; justify-content: center; background-color: #2b908f;">
<span id="kana-btn-text">ć²ćććŖ</span>
</button>
<button class="btn btn-primary" onclick="setScriptMode('alphabet')" title="ćć¼ćå" style="min-width: 90px; justify-content: center; background-color: #8e44ad;">
<span data-i18n="common.script_alphabet">ćć¼ćå</span>
</button>
<button class="btn btn-primary" onclick="setScriptMode('numbers')" title="ę°å" style="min-width: 90px; justify-content: center; background-color: #d35400;">
<span data-i18n="common.script_numbers">ę°å</span>
</button>
<button class="btn btn-primary" id="hand-toggle-btn" onclick="toggleHand()" title="å©ćęåćęæć" style="min-width: 100px; justify-content: center;">
<i class="fa-solid fa-hand"></i> <span>å·¦ęēØ</span>
</button>
</div>
<div class="tool-group">
<button class="btn" onclick="clearCurrentPage()"><i class="fa-regular fa-trash-can"></i> <span data-i18n="common.btn_clear_page">ćć¼ćøę¶å»</span></button>
<button class="btn text-danger" onclick="clearAllPages()"><i class="fa-solid fa-trash-can"></i> <span data-i18n="common.btn_clear_all">å
Øę¶å»</span></button>
</div>
<div class="tool-group">
<select id="save-format" class="format-select">
<option value="png">PNG</option>
<option value="pdf">PDF</option>
</select>
<button class="btn btn-primary" onclick="saveCurrentPage()"><i class="fa-solid fa-download"></i> <span data-i18n="common.btn_save_page">äæå</span></button>
<button class="btn btn-primary" onclick="saveAllPages()"><i class="fa-solid fa-file-zipper"></i> <span data-i18n="common.btn_save_all">å
Øäæå</span></button>
</div>
</div>
<div class="control-row">
<div class="tool-group">
<button class="btn active icon-btn" id="btn-pen" onclick="setTool('pen')" title="Pen"><i class="fa-solid fa-pen"></i></button>
<button class="btn icon-btn" id="btn-eraser" onclick="setTool('eraser')" title="Eraser"><i class="fa-solid fa-eraser"></i></button>
<button class="btn icon-btn" id="btn-move" onclick="setTool('move')" title="Move/Pan"><i class="fa-solid fa-hand"></i></button>
</div>
<div class="tool-group nav-group" style="padding: 4px;">
<button class="btn" onclick="goToTop()" title="Home">
<i class="fa-solid fa-house"></i>
</button>
<button class="btn" id="prev-btn" onclick="changePage(-1)">
<i class="fa-solid fa-chevron-left"></i> <span data-i18n="common.btn_prev">åćø</span>
</button>
<span class="page-indicator" id="page-indicator" style="border: none; background: transparent;">1 / 8</span>
<button class="btn" id="next-btn" onclick="changePage(1)">
<span data-i18n="common.btn_next">ꬔćø</span> <i class="fa-solid fa-chevron-right"></i>
</button>
</div>
<!-- ćŗć¼ć ęä½ć³ć³ććć¼ć«ćčæ½å -->
<div class="tool-group">
<button class="btn icon-btn" onclick="changeZoom(-0.1)" title="ēø®å°"><i class="fa-solid fa-magnifying-glass-minus"></i></button>
<button class="btn icon-btn" onclick="resetZoom()" title="100%" style="font-size: 14px;"><i class="fa-solid fa-expand"></i></button>
<button class="btn icon-btn" onclick="changeZoom(0.1)" title="ę”大"><i class="fa-solid fa-magnifying-glass-plus"></i></button>
</div>
<div class="tool-group">
<button class="btn icon-btn" onclick="speakCurrentPage()" title="ćć¼ćøćčŖćæäøćć"><i class="fa-solid fa-volume-high"></i></button>
<button class="btn icon-btn text-danger" onclick="stopSpeaking()" title="čŖćæäøććåę¢ćć"><i class="fa-solid fa-stop"></i></button>
</div>
<div class="tool-group">
<button class="btn icon-btn" onclick="toggleLang()" title="Language Switch"><i class="fa-solid fa-language"></i></button>
<button class="btn icon-btn jump-btn" onclick="jumpToAiueoV()" title="ēø¦ęøć" style="gap: 2px;">
<i class="fa-solid fa-arrow-down" style="font-size: 12px;"></i>
<span id="jump-v-text" style="font-size: 12px; font-weight: bold; font-family: 'Noto Sans JP';">ć</span>
</button>
<button class="btn icon-btn jump-btn" onclick="jumpToAiueoH()" title="横ęøć" style="gap: 2px;">
<i class="fa-solid fa-arrow-right" style="font-size: 12px;"></i>
<span id="jump-h-text" style="font-size: 12px; font-weight: bold; font-family: 'Noto Sans JP';">ć</span>
</button>
<button class="btn icon-btn" onclick="toggleTheme()" title="Theme"><i id="theme-icon" class="fa-solid fa-sun"></i></button>
</div>
</div>
</div>
<div id="pages-container"></div>
<script>
// iOS/iPadOSå¤å®ļ¼é·ę¼ćäæåć¢ć¼ćēØļ¼
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
let currentLang = 'jp';
let currentHand = 'left';
let currentScript = 'hiragana';
let lastKana = 'hiragana';
let lastKanaPage = 1;
let currentPage = 1;
let currentTool = 'pen';
let penSize = 3;
let eraserSize = 50;
let currentZoom = 1.0;
let lastSpokenChar = '';
let lastSpokenTime = 0;
const i18n = {
jp: {
common: {
btn_clear_page: "ę¶å»", btn_clear_all: "å
Øę¶å»", btn_save_page: "äæå", btn_save_all: "å
Øäæå", btn_prev: "åćø", btn_next: "ꬔćø",
loading_title: "äæåå¦ēäø...", loading_desc: "ā»å
Øćć¼ćøäæåć«ćÆå°ćęéććććć¾ćć<br>ćć©ć¦ć¶ćć<b>ćč¤ę°ćć”ć¤ć«ć®ćć¦ć³ćć¼ćć許åÆćć¾ććļ¼ć</b>ćØčćććå “åćÆć<b>ć許åÆć</b>ćę¼ćć¦ćć ććć",
script_hiragana: "ć²ćććŖ", script_katakana: "ć«ćæć«ć", script_alphabet: "ćć¼ćå", script_numbers: "ę°å", hand_left: "å·¦ęēØ", hand_right: "å³ęēØ",
kana: "ććŖ", hebon: "ććć³å¼", kunrei: "čØä»¤å¼",
modal_guide: "ē»åćé·ę¼ććć¦ćåēć«čæ½å ćć¾ććÆćē»åćäæåććéøćć§ćć ććć"
},
hiragana: {
title_main: { left: "å·¦ęēØ ć²ćććŖē·“ēæåø³", right: "å³ęēØ ć²ćććŖē·“ēæåø³" }, title_aiueo_v: "ēø¦ęøć äŗåé³é ļ¼ćććč”ļ¼", title_aiueo_h: "横ęøć äŗåé³é ļ¼ćććč”ļ¼",
step1_title: "1ē»ć®ć·ć³ćć«ćŖē·ļ¼č¶
å
„éļ¼", step2_title: "åē“ćŖć«ć¼ććØ2ē»ć®ęåļ¼åē“ļ¼", step3_title: "ęćčæććØę³¢ęć”ļ¼äøē“ļ¼", step4_title: "ē·ć®äŗ¤å·®ćØćć©ć³ć¹ļ¼äøē“ļ¼", step5_title: "ććć£ćØåććēµć³ćļ¼ęé£é¢ļ¼"
},
katakana: {
title_main: { left: "å·¦ęēØ ć«ćæć«ćē·“ēæåø³", right: "å³ęēØ ć«ćæć«ćē·“ēæåø³" }, title_aiueo_v: "ēø¦ęøć äŗåé³é ļ¼ć¢ććÆč”ļ¼", title_aiueo_h: "横ęøć äŗåé³é ļ¼ć¢ććÆč”ļ¼",
step1_title: "ć·ć³ćć«ćŖē“ē·ćØęćļ¼č¶
å
„éļ¼", step2_title: "åććØč§åŗ¦ćå½ļ¼åē“ļ¼", step3_title: "ęććØć«ćļ¼äøē“ļ¼", step4_title: "交差ćØćć©ć³ć¹ļ¼äøē“ļ¼", step5_title: "空éć®åćę¹ćØå½¢ļ¼ęé£é¢ļ¼"
},
alphabet: {
title_main: { left: "å·¦ęēØ ć¢ć«ćć”ćććć»ćć¼ćå", right: "å³ęēØ ć¢ć«ćć”ćććć»ćć¼ćå" },
step1_title: "ć¢ć«ćć”ććć 大ęå (AćZ)", step2_title: "ć¢ć«ćć”ććć å°ęå (aćz)", step3_title: "ćć¼ćå (ćč”ććŖč”)", step4_title: "ćć¼ćå (ćÆč”ććč”)", step5_title: "ćć¼ćå ęæé³ć»åęæé³ (ććć±č”)", step6_title: "ćć¼ćå ęé³1 (ćććć«ć)", step7_title: "ćć¼ćå ęé³2 (ć²ćććć)", step8_title: "ćć¼ćå ęé³3 (ćććć“ć)",
step9_title: "ććć³å¼ćØčØä»¤å¼ć®éćļ¼åé³ļ¼", step10_title: "ććć³å¼ćØčØä»¤å¼ć®éćļ¼ęé³ļ¼",
diff_hebon_title: "<i class=\"fa-solid fa-passport\"></i> ććć³å¼",
diff_hebon_desc: "č±čŖč©±č
ćēŗé³ććććęøćę¹ć<br>SI ā <span class=\"diff-highlight\">SHI</span> / TI ā <span class=\"diff-highlight\">CHI</span><br>TU ā <span class=\"diff-highlight\">TSU</span> / HU ā <span class=\"diff-highlight\">FU</span><br>š <strong>ćć¹ćć¼ććé§
åć»éč·ÆęØč</strong>ćŖć©ć§äøč¬ēć",
diff_kunrei_title: "<i class=\"fa-solid fa-school\"></i> čØä»¤å¼",
diff_kunrei_desc: "äŗåé³č”Øć®č¦åććć®ć¾ć¾č”Øćęøćę¹ć<br>ćč”: SA <span class=\"kunrei-highlight\">SI</span> SU SE SO<br>ćč”: TA <span class=\"kunrei-highlight\">TI</span> <span class=\"kunrei-highlight\">TU</span> TE TO<br>š <strong>å¦ę ”ć®ćć¼ćåęč²</strong>ć§ēæćććØćå¤ćć",
diff_note_title: "ā ļø ē¹ć«ééćććć注ęē¹",
diff_note_1: "<strong>ććµć</strong> <span class=\"diff-highlight\">ććć³å¼: <b>FU</b></span> / <span class=\"kunrei-highlight\">čØä»¤å¼: <b>HU</b></span><br><strong>ććć</strong> <span class=\"diff-highlight\">ććć³å¼: <b>SHI</b></span> / <span class=\"kunrei-highlight\">čØä»¤å¼: <b>SI</b></span><br><strong>ćć”ć</strong> <span class=\"diff-highlight\">ććć³å¼: <b>CHI</b></span> / <span class=\"kunrei-highlight\">čØä»¤å¼: <b>TI</b></span><br><strong>ćć¤ć</strong> <span class=\"diff-highlight\">ććć³å¼: <b>TSU</b></span> / <span class=\"kunrei-highlight\">čØä»¤å¼: <b>TU</b></span>",
diff_note_2: "<strong>ćććć»ćć
ć»ććć</strong><br><span class=\"diff-highlight\">ććć³å¼: <b>SHAć»SHUć»SHO</b></span><br><span class=\"kunrei-highlight\">čØä»¤å¼: <b>SYAć»SYUć»SYO</b></span><br><br><strong>ćć”ćć»ć”ć
ć»ć”ćć</strong><br><span class=\"diff-highlight\">ććć³å¼: <b>CHAć»CHUć»CHO</b></span><br><span class=\"kunrei-highlight\">čØä»¤å¼: <b>TYAć»TYUć»TYO</b></span><br><br><strong>ćććć»ćć
ć»ććć</strong><br><span class=\"diff-highlight\">ććć³å¼: <b>JAć»JUć»JO</b></span><br><span class=\"kunrei-highlight\">čØä»¤å¼: <b>ZYAć»ZYUć»ZYO</b></span>"
},
numbers: { title_main: { left: "å·¦ęēØ ę°åć»ę¼¢ę°å ē·“ēæåø³", right: "å³ęēØ ę°åć»ę¼¢ę°å ē·“ēæåø³" }, step1_title: "ē®ēØę°å (0ć9)", step2_title: "ę¼¢ę°å (äøćäøćććå)" }
},
en: {
common: {
btn_clear_page: "Clear", btn_clear_all: "Clear All", btn_save_page: "Save", btn_save_all: "Save All", btn_prev: "Prev", btn_next: "Next",
loading_title: "Saving...", loading_desc: "Saving all pages may take a while.<br>If asked 'Allow downloading multiple files?', please select <b>Allow</b>.",
script_hiragana: "Hiragana", script_katakana: "Katakana", script_alphabet: "Romaji", script_numbers: "Numbers", hand_left: "Left", hand_right: "Right",
kana: "Kana", hebon: "Hepburn", kunrei: "Kunrei",
modal_guide: "Long press the image to save it to your Photos."
},
hiragana: { title_main: { left: "Left-Handed Hiragana", right: "Right-Handed Hiragana" }, title_aiueo_v: "Vertical Gojuon", title_aiueo_h: "Horizontal Gojuon", step1_title: "1-Stroke Lines", step2_title: "Simple Curves", step3_title: "Fold Back & Wave", step4_title: "Intersections", step5_title: "Looping 'Knots'" },
katakana: { title_main: { left: "Left-Handed Katakana", right: "Right-Handed Katakana" }, title_aiueo_v: "Vertical Gojuon", title_aiueo_h: "Horizontal Gojuon", step1_title: "Simple Lines", step2_title: "Direction & Angle", step3_title: "Folds & Corners", step4_title: "Intersections", step5_title: "Spacing" },
alphabet: {
title_main: { left: "Left-Handed Alphabet & Romaji", right: "Right-Handed Alphabet & Romaji" },
step1_title: "Alphabet Uppercase (A-Z)", step2_title: "Alphabet Lowercase (a-z)", step3_title: "Romaji (A - No)", step4_title: "Romaji (Ha - Wa)", step5_title: "Romaji Dakuon (Ga - Pa)", step6_title: "Romaji Yoon 1 (Kya - Nya)", step7_title: "Romaji Yoon 2 (Hya - Rya)", step8_title: "Romaji Yoon 3 (Gya - Pya)",
step9_title: "Differences: Hepburn vs Kunrei (Consonants)", step10_title: "Differences: Hepburn vs Kunrei (Yoon)",
diff_hebon_title: "<i class=\"fa-solid fa-passport\"></i> Hepburn",
diff_hebon_desc: "Pronunciation-based spelling.<br>SI ā <span class=\"diff-highlight\">SHI</span> / TI ā <span class=\"diff-highlight\">CHI</span><br>TU ā <span class=\"diff-highlight\">TSU</span> / HU ā <span class=\"diff-highlight\">FU</span><br>š Used in <strong>passports and signs</strong>.",
diff_kunrei_title: "<i class=\"fa-solid fa-school\"></i> Kunrei",
diff_kunrei_desc: "Follows the Gojuon rules.<br>Sa row: SA <span class=\"kunrei-highlight\">SI</span> SU SE SO<br>Ta row: TA <span class=\"kunrei-highlight\">TI</span> <span class=\"kunrei-highlight\">TU</span> TE TO<br>š Taught in <strong>Japanese schools</strong>.",
diff_note_title: "ā ļø Common Mistakes",
diff_note_1: "<strong>ććµ(fu/hu)ć</strong> <span class=\"diff-highlight\">Hepburn: <b>FU</b></span> / <span class=\"kunrei-highlight\">Kunrei: <b>HU</b></span><br><strong>ćć(shi/si)ć</strong> <span class=\"diff-highlight\">Hepburn: <b>SHI</b></span> / <span class=\"kunrei-highlight\">Kunrei: <b>SI</b></span><br><strong>ćć”(chi/ti)ć</strong> <span class=\"diff-highlight\">Hepburn: <b>CHI</b></span> / <span class=\"kunrei-highlight\">Kunrei: <b>TI</b></span><br><strong>ćć¤(tsu/tu)ć</strong> <span class=\"diff-highlight\">Hepburn: <b>TSU</b></span> / <span class=\"kunrei-highlight\">Kunrei: <b>TU</b></span>",
diff_note_2: "<strong>ćććć»ćć
ć»ććć</strong><br><span class=\"diff-highlight\">Hepburn: <b>SHAć»SHUć»SHO</b></span><br><span class=\"kunrei-highlight\">Kunrei: <b>SYAć»SYUć»SYO</b></span><br><br><strong>ćć”ćć»ć”ć
ć»ć”ćć</strong><br><span class=\"diff-highlight\">Hepburn: <b>CHAć»CHUć»CHO</b></span><br><span class=\"kunrei-highlight\">Kunrei: <b>TYAć»TYUć»TYO</b></span><br><br><strong>ćććć»ćć
ć»ććć</strong><br><span class=\"diff-highlight\">Hepburn: <b>JAć»JUć»JO</b></span><br><span class=\"kunrei-highlight\">Kunrei: <b>ZYAć»ZYUć»ZYO</b></span>"
},
numbers: { title_main: { left: "Left-Handed Numbers", right: "Right-Handed Numbers" }, step1_title: "Arabic Numerals (0-9)", step2_title: "Kanji Numerals" }
}
};
const charsData = {
hiragana: {
steps: [
{ id: 1, chars: ['ć','ć¤','ć','ćø'] },
{ id: 2, chars: ['ć®','ć¦','ćØ','ć','ć','ć','ć²'] },
{ id: 3, chars: ['ć','ć','ć','ć','ć','ć','ć','ć','ć'] },
{ id: 4, chars: ['ć','ć','ć','ć','ć','ć','ć”','ć«','ćµ','ć','ć','ć','ć'] },
{ id: 5, chars: ['ć','ć','ć','ćŖ','ć¬','ć','ćÆ','ć»','ć¾','ćæ','ć','ć','ć'] }
],
aiueo: [
['ć','ć','ć','ć','ć'], ['ć','ć','ć','ć','ć'], ['ć','ć','ć','ć','ć'],
['ć','ć”','ć¤','ć¦','ćØ'], ['ćŖ','ć«','ć¬','ć','ć®'], ['ćÆ','ć²','ćµ','ćø','ć»'],
['ć¾','ćæ','ć','ć','ć'], ['ć','','ć','','ć'], ['ć','ć','ć','ć','ć'],
['ć','','ć','','ć']
]
},
katakana: {
steps: [
{ id: 1, chars: ['ć¤','ć','ć','ć','ć','ć','ć'] },
{ id: 2, chars: ['ć·','ć','ć½','ć³','ć ','ć¤','ćÆ'] },
{ id: 3, chars: ['ć¢','ć«','ć³','ćµ','ć»','ćæ','ć','ć','ć','ć©','ć'] },
{ id: 4, chars: ['ć','ć”','ć','ć±','ć','ć','ć¹','ć','ć¢','ćØ','ć«','ćÆ'] },
{ id: 5, chars: ['ć¦','ćØ','ćŖ','ć','ć','ć¦','ćŖ','ć¬','ć²'] }
],
aiueo: [
['ć¢','ć¤','ć¦','ćØ','ćŖ'], ['ć«','ć','ćÆ','ć±','ć³'], ['ćµ','ć·','ć¹','ć»','ć½'],
['ćæ','ć','ć','ć','ć'], ['ć','ć','ć','ć','ć'], ['ć','ć','ć','ć','ć'],
['ć','ć','ć ','ć”','ć¢'], ['ć¤','','ć¦','','ćØ'], ['ć©','ćŖ','ć«','ć¬','ć'],
['ćÆ','','ć²','','ć³']
]
},
alphabet: {
uppercase: [
['A','B','C','D','E','F','G'], ['H','I','J','K','L','M','N'],
['O','P','Q','R','S','T','U'], ['V','W','X','Y','Z']
],
lowercase: [
['a','b','c','d','e','f','g'], ['h','i','j','k','l','m','n'],
['o','p','q','r','s','t','u'], ['v','w','x','y','z']
],
romaji1: [
[{c:'a',h:'ć',k:'ć¢'},{c:'i',h:'ć',k:'ć¤'},{c:'u',h:'ć',k:'ć¦'},{c:'e',h:'ć',k:'ćØ'},{c:'o',h:'ć',k:'ćŖ'}],
[{c:'ka',h:'ć',k:'ć«'},{c:'ki',h:'ć',k:'ć'},{c:'ku',h:'ć',k:'ćÆ'},{c:'ke',h:'ć',k:'ć±'},{c:'ko',h:'ć',k:'ć³'}],
[{c:'sa',h:'ć',k:'ćµ'},{c:'s<span class="diff-highlight">h</span>i',h:'ć',k:'ć·'},{c:'su',h:'ć',k:'ć¹'},{c:'se',h:'ć',k:'ć»'},{c:'so',h:'ć',k:'ć½'}],
[{c:'ta',h:'ć',k:'ćæ'},{c:'<span class="diff-highlight">c</span>hi',h:'ć”',k:'ć'},{c:'t<span class="diff-highlight">s</span>u',h:'ć¤',k:'ć'},{c:'te',h:'ć¦',k:'ć'},{c:'to',h:'ćØ',k:'ć'}],
[{c:'na',h:'ćŖ',k:'ć'},{c:'ni',h:'ć«',k:'ć'},{c:'nu',h:'ć¬',k:'ć'},{c:'ne',h:'ć',k:'ć'},{c:'no',h:'ć®',k:'ć'}]
],
romaji2: [
[{c:'ha',h:'ćÆ',k:'ć'},{c:'hi',h:'ć²',k:'ć'},{c:'<span class="diff-highlight">f</span>u',h:'ćµ',k:'ć'},{c:'he',h:'ćø',k:'ć'},{c:'ho',h:'ć»',k:'ć'}],
[{c:'ma',h:'ć¾',k:'ć'},{c:'mi',h:'ćæ',k:'ć'},{c:'mu',h:'ć',k:'ć '},{c:'me',h:'ć',k:'ć”'},{c:'mo',h:'ć',k:'ć¢'}],
[{c:'ya',h:'ć',k:'ć¤'},null,{c:'yu',h:'ć',k:'ć¦'},null,{c:'yo',h:'ć',k:'ćØ'}],
[{c:'ra',h:'ć',k:'ć©'},{c:'ri',h:'ć',k:'ćŖ'},{c:'ru',h:'ć',k:'ć«'},{c:'re',h:'ć',k:'ć¬'},{c:'ro',h:'ć',k:'ć'}],
[{c:'wa',h:'ć',k:'ćÆ'},null,{c:'o',h:'ć',k:'ć²'},null,{c:'n',h:'ć',k:'ć³'}]
],
romaji3: [
[{c:'ga',h:'ć',k:'ć¬'},{c:'gi',h:'ć',k:'ć®'},{c:'gu',h:'ć',k:'ć°'},{c:'ge',h:'ć',k:'ć²'},{c:'go',h:'ć',k:'ć“'}],
[{c:'za',h:'ć',k:'ć¶'},{c:'<span class="diff-highlight">j</span>i',h:'ć',k:'ćø'},{c:'zu',h:'ć',k:'ćŗ'},{c:'ze',h:'ć',k:'ć¼'},{c:'zo',h:'ć',k:'ć¾'}],
[{c:'da',h:'ć ',k:'ć'},{c:'<span class="diff-highlight">j</span>i',h:'ć¢',k:'ć'},{c:'zu',h:'ć„',k:'ć
'},{c:'de',h:'ć§',k:'ć'},{c:'do',h:'ć©',k:'ć'}],
[{c:'ba',h:'ć°',k:'ć'},{c:'bi',h:'ć³',k:'ć'},{c:'bu',h:'ć¶',k:'ć'},{c:'be',h:'ć¹',k:'ć'},{c:'bo',h:'ć¼',k:'ć'}],
[{c:'pa',h:'ć±',k:'ć'},{c:'pi',h:'ć“',k:'ć'},{c:'pu',h:'ć·',k:'ć'},{c:'pe',h:'ćŗ',k:'ć'},{c:'po',h:'ć½',k:'ć'}]
],
romaji4: [
[{c:'kya',h:'ćć',k:'ćć£'},{c:'kyu',h:'ćć
',k:'ćć„'},{c:'kyo',h:'ćć',k:'ćć§'}],
[{c:'s<span class="diff-highlight">h</span>a',h:'ćć',k:'ć·ć£'},{c:'s<span class="diff-highlight">h</span>u',h:'ćć
',k:'ć·ć„'},{c:'s<span class="diff-highlight">h</span>o',h:'ćć',k:'ć·ć§'}],
[{c:'<span class="diff-highlight">ch</span>a',h:'ć”ć',k:'ćć£'},{c:'<span class="diff-highlight">ch</span>u',h:'ć”ć
',k:'ćć„'},{c:'<span class="diff-highlight">ch</span>o',h:'ć”ć',k:'ćć§'}],
[{c:'nya',h:'ć«ć',k:'ćć£'},{c:'nyu',h:'ć«ć
',k:'ćć„'},{c:'nyo',h:'ć«ć',k:'ćć§'}]
],
romaji5: [
[{c:'hya',h:'ć²ć',k:'ćć£'},{c:'hyu',h:'ć²ć
',k:'ćć„'},{c:'hyo',h:'ć²ć',k:'ćć§'}],
[{c:'mya',h:'ćæć',k:'ćć£'},{c:'myu',h:'ćæć
',k:'ćć„'},{c:'myo',h:'ćæć',k:'ćć§'}],
[{c:'rya',h:'ćć',k:'ćŖć£'},{c:'ryu',h:'ćć
',k:'ćŖć„'},{c:'ryo',h:'ćć',k:'ćŖć§'}]
],
romaji6: [
[{c:'gya',h:'ćć',k:'ć®ć£'},{c:'gyu',h:'ćć
',k:'ć®ć„'},{c:'gyo',h:'ćć',k:'ć®ć§'}],
[{c:'<span class="diff-highlight">j</span>a',h:'ćć',k:'ćøć£'},{c:'<span class="diff-highlight">j</span>u',h:'ćć
',k:'ćøć„'},{c:'<span class="diff-highlight">j</span>o',h:'ćć',k:'ćøć§'}],
[{c:'bya',h:'ć³ć',k:'ćć£'},{c:'byu',h:'ć³ć
',k:'ćć„'},{c:'byo',h:'ć³ć',k:'ćć§'}],
[{c:'pya',h:'ć“ć',k:'ćć£'},{c:'pyu',h:'ć“ć
',k:'ćć„'},{c:'pyo',h:'ć“ć',k:'ćć§'}]
],
romajiDiff1: [
{ h: 'ć', hebon: 'S<span class="diff-highlight">H</span>I', kunrei: '<span class="kunrei-highlight">S</span>I' },
{ h: 'ć”', hebon: '<span class="diff-highlight">C</span>HI', kunrei: '<span class="kunrei-highlight">T</span>I' },
{ h: 'ć¤', hebon: 'T<span class="diff-highlight">S</span>U', kunrei: '<span class="kunrei-highlight">TU</span>' },
{ h: 'ćµ', hebon: '<span class="diff-highlight">F</span>U', kunrei: '<span class="kunrei-highlight">H</span>U' },
{ h: 'ć', hebon: '<span class="diff-highlight">J</span>I', kunrei: '<span class="kunrei-highlight">Z</span>I' },
{ h: 'ć¢', hebon: '<span class="diff-highlight">J</span>I', kunrei: '<span class="kunrei-highlight">Z</span>I' },
{ h: 'ć„', hebon: 'ZU', kunrei: 'ZU' }
],
romajiDiff2_col1: [
{ h: 'ćć', hebon: 'S<span class="diff-highlight">H</span>A', kunrei: 'S<span class="kunrei-highlight">Y</span>A' },
{ h: 'ćć
', hebon: 'S<span class="diff-highlight">H</span>U', kunrei: 'S<span class="kunrei-highlight">Y</span>U' },
{ h: 'ćć', hebon: 'S<span class="diff-highlight">H</span>O', kunrei: 'S<span class="kunrei-highlight">Y</span>O' },
{ h: 'ćć', hebon: '<span class="diff-highlight">J</span>A', kunrei: '<span class="kunrei-highlight">ZY</span>A' },
{ h: 'ćć
', hebon: '<span class="diff-highlight">J</span>U', kunrei: '<span class="kunrei-highlight">ZY</span>U' },
{ h: 'ćć', hebon: '<span class="diff-highlight">J</span>O', kunrei: '<span class="kunrei-highlight">ZY</span>O' }
],
romajiDiff2_col2: [
{ h: 'ć”ć', hebon: '<span class="diff-highlight">CH</span>A', kunrei: '<span class="kunrei-highlight">TY</span>A' },
{ h: 'ć”ć
', hebon: '<span class="diff-highlight">CH</span>U', kunrei: '<span class="kunrei-highlight">TY</span>U' },
{ h: 'ć”ć', hebon: '<span class="diff-highlight">CH</span>O', kunrei: '<span class="kunrei-highlight">TY</span>O' },
{ h: 'ć¢ć', hebon: '<span class="diff-highlight">J</span>A', kunrei: '<span class="kunrei-highlight">ZY</span>A' },
{ h: 'ć¢ć
', hebon: '<span class="diff-highlight">J</span>U', kunrei: '<span class="kunrei-highlight">ZY</span>U' },
{ h: 'ć¢ć', hebon: '<span class="diff-highlight">J</span>O', kunrei: '<span class="kunrei-highlight">ZY</span>O' }
]
},
numbers: {
arabic: ['1','2','3','4','5','6','7','8','9','0'],
kanji: [
[{c:'äø',r:'ćć”',n:'1'},{c:'äŗ',r:'ć«',n:'2'},{c:'äø',r:'ćć',n:'3'}],
[{c:'å',r:'ćć',n:'4'},{c:'äŗ',r:'ć',n:'5'},{c:'å
',r:'ćć',n:'6'}],
[{c:'äø',r:'ćŖćŖ',n:'7'},{c:'å
«',r:'ćÆć”',n:'8'},{c:'ä¹',r:'ćć
ć',n:'9'}],
[{c:'å',r:'ćć
ć',n:'10'},{c:'ē¾',r:'ć²ćć',n:'100'},{c:'å',r:'ćć',n:'1000'}],
[{c:'äø',r:'ć¾ć',n:'10000'},{c:'ć',r:'ćć',n:'0'},{c:'å',r:'ćć',n:'Ā„'}]
]
}
};
window.onload = () => {
renderPages();
applyLanguage();
updatePageDisplay();
initDrawing();
updateNavigation();
updateCursor();
initPanAndZoom();
};
function stopSpeaking() {
if ('speechSynthesis' in window) speechSynthesis.cancel();
}
function speakText(text, forceLang = null) {
if (!('speechSynthesis' in window)) return;
// čØå·ćē¹å®ć®åčŖćčŖē¶ć«čŖććććć«ē½®ćęćļ¼ćåø³ćåé”ćŖć©ć®åéæļ¼
let cleanText = text.replace(/[ā»\*]/g, '')
.replace(/Ć/g, '')
.replace(/ā/g, 'ć') // ćć¤ćć«ćć§ćÆćŖćčŖē¹ć«ćć¦äøå¼åøē½®ć
.replace(/ć/g, 'ćć')
.replace(/č”/g, 'ććć')
.replace(/åęæé³/g, 'ćÆćć ććć')
.replace(/ęæé³/g, 'ć ććć')
.replace(/ęé³/g, 'ćććć')
.replace(/č±čŖč©±č
/g, 'ćććććć')
.replace(/ē·“ēæåø³/g, 'ćććć
ćć”ćć')
.replace(/åø³/g, 'ć”ćć')
.replace(/\//g, 'ć')
.replace(/:/g, 'ćÆć')
.replace(/š/g, '');
// ćć¼ćøå
Øä½ć®čŖ¬ęęćŖć©ć§ćć¢ć«ćć”ćććć®é£ē¶ćåčŖčŖćæć§ćÆćŖć1ęåćć¤ļ¼ć¹ćć«ļ¼čŖć¾ćć
cleanText = cleanText.replace(/[A-Z]{2,}/g, match => {
return match.split('').join(', ').toLowerCase();
});
const msg = new SpeechSynthesisUtterance(cleanText);
msg.lang = forceLang ? forceLang : (currentLang === 'en' ? 'en-US' : 'ja-JP');
msg.rate = 0.85;
speechSynthesis.speak(msg);
}
function speakChar(char, forceLang = null) {
const now = Date.now();
if (char === lastSpokenChar && (now - lastSpokenTime) < 1500) return;
lastSpokenChar = char;
lastSpokenTime = now;
stopSpeaking();
let textToSpeak = char;
// ę„ę¬čŖć®å©č©ć®čŖćæę¹ć®čŖæę“
if (char === 'ćÆ') textToSpeak = 'ć';
if (char === 'ćø') textToSpeak = 'ć';
// ć¢ć«ćć”ććć1ęåć®ćæććęć«ććć£ććæć«ćććØčŖć¾ććć®ćåéæćććććå°ęåć«ćć¦ććęø”ć
if (forceLang === 'en-US' && textToSpeak.length === 1 && /[A-Z]/.test(textToSpeak)) {
textToSpeak = textToSpeak.toLowerCase();
}
speakText(textToSpeak, forceLang || 'ja-JP');
}
function speakCurrentPage() {
const page = document.querySelector('.page.active');
if (page) speakPageInfo(page);
}
function speakPageInfo(page) {
if (!page) return;
stopSpeaking();
let speechQueue = [];
const mainLang = currentLang === 'en' ? 'en-US' : 'ja-JP';
const mainTitle = page.querySelector('.main-title');
if (mainTitle) {
const badge = mainTitle.querySelector('.step-badge');
const textSpan = mainTitle.querySelector('span[data-i18n]');
if (badge) speechQueue.push({ text: badge.innerText, lang: mainLang });
if (textSpan) speechQueue.push({ text: textSpan.innerText, lang: mainLang });
}
if (currentScript === 'alphabet' && currentPage >= 9) {
// Step 9, 10 ēØć®čŖ¬ęęćØęåćŖć¹ćć®čŖćæäøć
const diffCards = page.querySelectorAll('.diff-card');
diffCards.forEach(card => {
const h4 = card.querySelector('h4');
const p = card.querySelector('p');
if (h4) speechQueue.push({ text: h4.innerText, lang: mainLang });
if (p) speechQueue.push({ text: p.innerText, lang: mainLang });
});
const prefix = currentLang === 'en' ? "Characters are: " : "ćć®ćć¼ćøć®ęåćÆć";
speechQueue.push({ text: prefix, lang: mainLang });
let chars = [];
page.querySelectorAll('.diff-row .diff-col-kana').forEach(cell => {
const char = cell.innerText.trim();
if(char && char !== 'ććŖ' && char !== 'Kana' && !chars.includes(char)) {
chars.push(char);
}
});
speechQueue.push({ text: chars.join('ć'), lang: 'ja-JP' });
} else if (currentScript === 'alphabet') {
const prefix = currentLang === 'en' ? "Characters are: " : "ćć®ćć¼ćøć®ęåćÆć";
speechQueue.push({ text: prefix, lang: mainLang });
let chars = [];
page.querySelectorAll('.char-block[data-char]').forEach(block => {
const char = block.getAttribute('data-char');
if(char && !chars.includes(char)) chars.push(char);
});
if (currentPage === 1 || currentPage === 2) {
// ć¢ć«ćć”ćććåä½ć®čŖ¤čŖćé²ććććć«ć³ććØć¹ćć¼ć¹ć§å®å
Øć«åŗåć
// 大ęåć®ćć¼ćøļ¼currentPage === 1ļ¼ć®å “åćććć£ććæć«ćåéæć®ććå°ęåć«ćć
if (currentPage === 1) {
chars = chars.map(c => c.toLowerCase());
}
speechQueue.push({ text: chars.join(', '), lang: 'en-US' });
} else {
speechQueue.push({ text: chars.join('ć'), lang: 'ja-JP' });
}
} else if ((currentScript === 'hiragana' || currentScript === 'katakana') && (currentPage === 7 || currentPage === 8)) {
const prefix = currentLang === 'en' ? "Characters are: " : "ćć®ćć¼ćøć®ęåćÆć";
speechQueue.push({ text: prefix, lang: mainLang });
let chars = [];
charsData[currentScript].aiueo.forEach(group => {
group.forEach(char => { if (char !== '') chars.push(char); });
});
speechQueue.push({ text: chars.join('ć'), lang: 'ja-JP' });
} else if (currentPage >= 2 && currentScript !== 'numbers') {
const charBlocks = page.querySelectorAll('.char-block[data-char]');
if (charBlocks.length > 0) {
const prefix = currentLang === 'en' ? "Characters are: " : "ćć®ćć¼ćøć®ęåćÆć";
speechQueue.push({ text: prefix, lang: mainLang });
let chars = [];
charBlocks.forEach(block => {
const char = block.getAttribute('data-ruby') || block.getAttribute('data-char');
if(char) chars.push(char);
});
speechQueue.push({ text: chars.join('ć'), lang: 'ja-JP' });
}
} else if (currentPage === 1 && currentScript !== 'numbers') {
const stepTitles = page.querySelectorAll('.step-title-small');
const containers = page.querySelectorAll('.blocks-container');
stepTitles.forEach((stepTitle, idx) => {
const badge = stepTitle.querySelector('.step-badge');
const textSpan = stepTitle.querySelector('span[data-i18n]');
if (badge) speechQueue.push({ text: badge.innerText, lang: mainLang });
if (textSpan) speechQueue.push({ text: textSpan.innerText, lang: mainLang });
const container = containers[idx];
if (container) {
let chars = [];
container.querySelectorAll('.char-block[data-char]').forEach(block => {
const char = block.getAttribute('data-char');
if(char) chars.push(char);
});
if (chars.length > 0) speechQueue.push({ text: chars.join('ć'), lang: 'ja-JP' });
}
});
}
speechQueue.forEach(item => {
const punctuation = item.lang === 'en-US' ? '.' : 'ć';
let textToSpeak = item.text;
if (!textToSpeak.endsWith(punctuation)) textToSpeak += punctuation;
speakText(textToSpeak, item.lang);
});
}
function getTotalPages() {
if (currentScript === 'numbers') return 2;
if (currentScript === 'alphabet') return 10;
return 8;
}
function setScriptMode(mode) {
if (mode === 'kana') {
if (currentScript === 'hiragana' || currentScript === 'katakana') {
currentScript = currentScript === 'hiragana' ? 'katakana' : 'hiragana';
lastKana = currentScript;
} else {
currentScript = lastKana;
currentPage = lastKanaPage;
}
}
else if (mode === 'alphabet') {
if (currentScript !== 'alphabet') {
if (currentScript === 'hiragana' || currentScript === 'katakana') lastKanaPage = currentPage;
currentScript = 'alphabet';
currentPage = 1;
}
}
else if (mode === 'numbers') {
if (currentScript !== 'numbers') {
if (currentScript === 'hiragana' || currentScript === 'katakana') lastKanaPage = currentPage;
currentScript = 'numbers';
currentPage = 1;
}
}
updatePageDisplay();
applyLanguage();
}
function toggleHand() {
currentHand = currentHand === 'left' ? 'right' : 'left';
document.body.classList.toggle('left-hand', currentHand === 'left');
document.body.classList.toggle('right-hand', currentHand === 'right');
applyLanguage();
}
function toggleLang() {
currentLang = currentLang === 'jp' ? 'en' : 'jp';
applyLanguage();
}
function applyLanguage() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const keys = el.getAttribute('data-i18n').split('.');
let val = i18n[currentLang];
for (const k of keys) { if (val) val = val[k]; }
if (val) {
if (typeof val === 'object') el.innerHTML = val[currentHand] || val;
else el.innerHTML = val;
}
});
const handBtn = document.getElementById('hand-toggle-btn');
if (handBtn) {
const text = i18n[currentLang].common[`hand_${currentHand}`];
if (currentHand === 'left') {
handBtn.innerHTML = `<i class="fa-solid fa-hand"></i> <span>${text}</span>`;
} else {
handBtn.innerHTML = `<span>${text}</span> <i class="fa-solid fa-hand"></i>`;
}
}
const kanaBtnText = document.getElementById('kana-btn-text');
if (kanaBtnText) {
const targetScript = (currentScript === 'hiragana' || currentScript === 'katakana') ? currentScript : lastKana;
kanaBtnText.innerText = i18n[currentLang].common[`script_${targetScript}`];
}
document.getElementById('jump-v-text').innerText = currentScript === 'katakana' ? 'ć¢' : 'ć';
document.getElementById('jump-h-text').innerText = currentScript === 'katakana' ? 'ć¢' : 'ć';
}
function renderPages() {
const container = document.getElementById('pages-container');
container.innerHTML = '';
// --- ć²ćććŖć»ć«ćæć«ć ---
['hiragana', 'katakana'].forEach(script => {
const sData = charsData[script].steps;
const aData = charsData[script].aiueo;
let page1 = `<div id="page-${script}-1" class="page"><h1 class="main-title"><span data-i18n="${script}.title_main"></span></h1><div class="summary-wrapper">`;
sData.forEach((data, i) => {
let mt = i > 0 ? 'margin-top: 18px;' : 'margin-top: 16px;';
page1 += `<div class="step-title-small" style="${mt}"><span class="step-badge">Step ${i+1}</span> <span data-i18n="${script}.step${i+1}_title"></span></div><div class="blocks-container compact">`;
data.chars.forEach(char => {
page1 += `<div class="char-block compact" data-char="${char}">
<div class="cell compact sample" onclick="speakChar('${char}')" title="čŖćæäøćć"><span class="main-char">${char}</span></div>
<div class="cell compact trace"><span class="main-char">${char}</span><canvas data-size="43"></canvas></div>
<div class="cell compact"><canvas data-size="43"></canvas></div>
</div>`;
});
page1 += `</div>`;
});
page1 += `</div></div>`;
container.insertAdjacentHTML('beforeend', page1);
sData.forEach((data, index) => {
let pageHtml = `<div id="page-${script}-${index+2}" class="page"><h1 class="main-title"><span class="step-badge large">Step ${index+1}</span> <span data-i18n="${script}.step${index+1}_title"></span></h1><div class="blocks-container step-layout">`;
data.chars.forEach(char => {
pageHtml += `<div class="char-block step" data-char="${char}">
<div class="cell step sample" onclick="speakChar('${char}')" title="čŖćæäøćć"><span class="main-char">${char}</span></div>
<div class="cell step trace"><span class="main-char">${char}</span><canvas data-size="105"></canvas></div>
<div class="cell step"><canvas data-size="105"></canvas></div>
</div>`;
});
pageHtml += `</div></div>`;
container.insertAdjacentHTML('beforeend', pageHtml);
});
let page7 = `<div id="page-${script}-7" class="page"><h1 class="main-title"><span data-i18n="${script}.title_aiueo_v"></span></h1><div class="vertical-layout">`;
for(let i=0; i<5; i++){
page7 += `<div class="column-group">`;
aData[i].forEach(char => {
if(char === '') {
page7 += `<div class="char-block aiueo" style="visibility: hidden;"><div class="cell aiueo"></div><div class="cell aiueo"></div><div class="cell aiueo"></div></div>`;
} else {
page7 += `<div class="char-block aiueo" data-char="${char}">
<div class="cell aiueo sample" onclick="speakChar('${char}')" title="čŖćæäøćć"><span class="main-char">${char}</span></div>
<div class="cell aiueo trace"><span class="main-char">${char}</span><canvas data-size="54"></canvas></div>
<div class="cell aiueo"><canvas data-size="54"></canvas></div>
</div>`;
}
});
page7 += `</div>`;
}
page7 += `</div><div class="vertical-layout" style="margin-top: 40px;">`;
for(let i=5; i<10; i++){
page7 += `<div class="column-group">`;
aData[i].forEach(char => {
if(char === '') {
page7 += `<div class="char-block aiueo" style="visibility: hidden;"><div class="cell aiueo"></div><div class="cell aiueo"></div><div class="cell aiueo"></div></div>`;
} else {
page7 += `<div class="char-block aiueo" data-char="${char}">
<div class="cell aiueo sample" onclick="speakChar('${char}')" title="čŖćæäøćć"><span class="main-char">${char}</span></div>
<div class="cell aiueo trace"><span class="main-char">${char}</span><canvas data-size="54"></canvas></div>
<div class="cell aiueo"><canvas data-size="54"></canvas></div>
</div>`;
}
});
page7 += `</div>`;
}
page7 += `</div></div>`;
container.insertAdjacentHTML('beforeend', page7);
let page8 = `<div id="page-${script}-8" class="page"><h1 class="main-title"><span data-i18n="${script}.title_aiueo_h"></span></h1><div class="horizontal-layout">`;
aData.forEach((group, idx) => {
let mt = (idx === 5) ? 'margin-top: 24px;' : '';
page8 += `<div class="row-group" style="${mt}">`;
group.forEach(char => {
if(char === '') {
page8 += `<div class="char-block aiueo" style="visibility: hidden;"><div class="cell aiueo"></div><div class="cell aiueo"></div><div class="cell aiueo"></div></div>`;
} else {
page8 += `<div class="char-block aiueo" data-char="${char}">
<div class="cell aiueo sample" onclick="speakChar('${char}')" title="čŖćæäøćć"><span class="main-char">${char}</span></div>
<div class="cell aiueo trace"><span class="main-char">${char}</span><canvas data-size="54"></canvas></div>
<div class="cell aiueo"><canvas data-size="54"></canvas></div>
</div>`;
}
});
page8 += `</div>`;
});
page8 += `</div></div>`;
container.insertAdjacentHTML('beforeend', page8);
});
// --- ć¢ć«ćć”ććć ---
let pageAlphabet1 = `<div id="page-alphabet-1" class="page"><h1 class="main-title"><span class="step-badge large">Step 1</span> <span data-i18n="alphabet.step1_title"></span></h1><div class="blocks-container alphabet-layout">`;
charsData.alphabet.uppercase.forEach(row => {
pageAlphabet1 += `<div class="row-group">`;
row.forEach(char => {
pageAlphabet1 += `<div class="char-block alphabet" data-char="${char}">
<div class="cell alphabet sample" onclick="speakChar('${char}', 'en-US')" title="čŖćæäøćć"><span class="main-char">${char}</span></div>
<div class="cell alphabet trace"><span class="main-char">${char}</span><canvas data-size="44"></canvas></div>
<div class="cell alphabet"><canvas data-size="44"></canvas></div>
</div>`;
});
pageAlphabet1 += `</div>`;
});
pageAlphabet1 += `</div></div>`;
container.insertAdjacentHTML('beforeend', pageAlphabet1);
let pageAlphabet2 = `<div id="page-alphabet-2" class="page"><h1 class="main-title"><span class="step-badge large">Step 2</span> <span data-i18n="alphabet.step2_title"></span></h1><div class="blocks-container alphabet-layout">`;
charsData.alphabet.lowercase.forEach(row => {
pageAlphabet2 += `<div class="row-group">`;
row.forEach(char => {
pageAlphabet2 += `<div class="char-block alphabet" data-char="${char}">
<div class="cell alphabet sample" onclick="speakChar('${char}', 'en-US')" title="čŖćæäøćć"><span class="main-char">${char}</span></div>
<div class="cell alphabet trace"><span class="main-char">${char}</span><canvas data-size="44"></canvas></div>
<div class="cell alphabet"><canvas data-size="44"></canvas></div>
</div>`;
});
pageAlphabet2 += `</div>`;
});
pageAlphabet2 += `</div></div>`;
container.insertAdjacentHTML('beforeend', pageAlphabet2);
const romajiPages = [
{ id: 3, data: charsData.alphabet.romaji1 }, { id: 4, data: charsData.alphabet.romaji2 },
{ id: 5, data: charsData.alphabet.romaji3 }, { id: 6, data: charsData.alphabet.romaji4 },
{ id: 7, data: charsData.alphabet.romaji5 }, { id: 8, data: charsData.alphabet.romaji6 }
];
romajiPages.forEach(pageInfo => {
const isYoon = (pageInfo.id >= 6);
const blockClass = isYoon ? "char-block romaji-yoon" : "char-block romaji";
const cellClass = isYoon ? "cell romaji-yoon" : "cell romaji";
const canvasSize = isYoon ? 96 : 64;
let html = `<div id="page-alphabet-${pageInfo.id}" class="page"><h1 class="main-title"><span class="step-badge large">Step ${pageInfo.id}</span> <span data-i18n="alphabet.step${pageInfo.id}_title"></span></h1><div class="blocks-container alphabet-layout">`;
pageInfo.data.forEach(row => {
let rowStyle = "";
if (!isYoon && row.length === 3) rowStyle = 'style="gap: 50px;"';
html += `<div class="row-group" ${rowStyle}>`;
row.forEach(item => {
if (!item) {
html += `<div class="${blockClass}" style="visibility: hidden;"><div class="${cellClass}"></div><div class="${cellClass}"></div><div class="${cellClass}"></div></div>`;
} else {
let rawC = item.c.replace(/<[^>]*>?/gm, ''); // ćŖććęøćēØćÆćæć°ćé¤å¤ććęåå
html += `<div class="${blockClass}" data-char="${item.h}">
<div class="${cellClass} sample" onclick="speakChar('${item.h}', 'ja-JP')" title="čŖćæäøćć">
<div class="romaji-kana"><span>${item.h}</span><span>${item.k}</span></div>
<span class="main-char">${item.c}</span>
</div>
<div class="${cellClass} trace"><span class="main-char">${rawC}</span><canvas data-size="${canvasSize}"></canvas></div>
<div class="${cellClass}"><canvas data-size="${canvasSize}"></canvas></div>
</div>`;
}
});
html += `</div>`;
});
html += `</div></div>`;
container.insertAdjacentHTML('beforeend', html);
});
// --- Step 9 (ććć³å¼ vs čØä»¤å¼ļ¼åé³) ---
let pageAlphabet9 = `<div id="page-alphabet-9" class="page">
<h1 class="main-title"><span class="step-badge large">Step 9</span> <span data-i18n="alphabet.step9_title"></span></h1>
<div class="diff-page-container">
<div class="diff-text-area">
<div class="diff-card hebon-card"><h4 data-i18n="alphabet.diff_hebon_title"></h4><p data-i18n="alphabet.diff_hebon_desc"></p></div>
<div class="diff-card kunrei-card"><h4 data-i18n="alphabet.diff_kunrei_title"></h4><p data-i18n="alphabet.diff_kunrei_desc"></p></div>
<div class="diff-card note-card"><h4 data-i18n="alphabet.diff_note_title"></h4><p data-i18n="alphabet.diff_note_1"></p></div>
</div>
<div class="diff-practice-area">
<div class="diff-table-header"><div class="diff-col-kana" data-i18n="common.kana">ććŖ</div><div class="diff-col-hebon" data-i18n="common.hebon">ććć³å¼</div><div class="diff-col-kunrei" data-i18n="common.kunrei">čØä»¤å¼</div></div>`;
charsData.alphabet.romajiDiff1.forEach(item => {
let rawHebon = item.hebon.replace(/<[^>]*>?/gm, '');
let rawKunrei = item.kunrei.replace(/<[^>]*>?/gm, '');
pageAlphabet9 += `<div class="diff-row"><div class="diff-col-kana">${item.h}</div>
<div class="diff-col-hebon"><div class="char-block romaji diff-block" data-char="${item.h}">
<div class="cell romaji sample diff-cell" onclick="speakChar('${item.h}', 'ja-JP')" title="čŖćæäøćć"><span class="main-char">${item.hebon}</span></div>
<div class="cell romaji trace diff-cell"><span class="main-char">${rawHebon}</span><canvas data-size="52"></canvas></div><div class="cell romaji diff-cell"><canvas data-size="52"></canvas></div></div></div>
<div class="diff-col-kunrei"><div class="char-block romaji diff-block" data-char="${item.h}">
<div class="cell romaji sample diff-cell" onclick="speakChar('${item.h}', 'ja-JP')" title="čŖćæäøćć"><span class="main-char">${item.kunrei}</span></div>
<div class="cell romaji trace diff-cell"><span class="main-char">${rawKunrei}</span><canvas data-size="52"></canvas></div><div class="cell romaji diff-cell"><canvas data-size="52"></canvas></div></div></div></div>`;
});
pageAlphabet9 += `</div></div></div>`;
container.insertAdjacentHTML('beforeend', pageAlphabet9);
// --- Step 10 (ććć³å¼ vs čØä»¤å¼ļ¼ęé³) ---
let pageAlphabet10 = `<div id="page-alphabet-10" class="page">
<h1 class="main-title"><span class="step-badge large">Step 10</span> <span data-i18n="alphabet.step10_title"></span></h1>
<div class="diff-page-container">
<div class="diff-text-area" style="width: 330px;"><div class="diff-card note-card" style="height: 100%;"><h4 data-i18n="alphabet.diff_note_title"></h4><p data-i18n="alphabet.diff_note_2" style="margin-top: 10px;"></p></div></div>
<div class="diff-practice-area" style="display: flex; gap: 15px; padding: 12px 14px;">
<div style="flex: 1;"><div class="diff-table-header small"><div class="diff-col-kana" data-i18n="common.kana">ććŖ</div><div class="diff-col-hebon" data-i18n="common.hebon">ććć³å¼</div><div class="diff-col-kunrei" data-i18n="common.kunrei">čØä»¤å¼</div></div>`;
charsData.alphabet.romajiDiff2_col1.forEach(item => {
let rawHebon = item.hebon.replace(/<[^>]*>?/gm, '');
let rawKunrei = item.kunrei.replace(/<[^>]*>?/gm, '');
pageAlphabet10 += `<div class="diff-row"><div class="diff-col-kana">${item.h}</div>
<div class="diff-col-hebon"><div class="char-block romaji diff-block" data-char="${item.h}">
<div class="cell romaji sample diff-cell small" onclick="speakChar('${item.h}', 'ja-JP')" title="čŖćæäøćć"><span class="main-char">${item.hebon}</span></div>
<div class="cell romaji trace diff-cell small"><span class="main-char">${rawHebon}</span><canvas data-size="46"></canvas></div><div class="cell romaji diff-cell small"><canvas data-size="46"></canvas></div></div></div>
<div class="diff-col-kunrei"><div class="char-block romaji diff-block" data-char="${item.h}">
<div class="cell romaji sample diff-cell small" onclick="speakChar('${item.h}', 'ja-JP')" title="čŖćæäøćć"><span class="main-char">${item.kunrei}</span></div>
<div class="cell romaji trace diff-cell small"><span class="main-char">${rawKunrei}</span><canvas data-size="46"></canvas></div><div class="cell romaji diff-cell small"><canvas data-size="46"></canvas></div></div></div></div>`;
});
pageAlphabet10 += `</div><div style="flex: 1;"><div class="diff-table-header small"><div class="diff-col-kana" data-i18n="common.kana">ććŖ</div><div class="diff-col-hebon" data-i18n="common.hebon">ććć³å¼</div><div class="diff-col-kunrei" data-i18n="common.kunrei">čØä»¤å¼</div></div>`;
charsData.alphabet.romajiDiff2_col2.forEach(item => {
let rawHebon = item.hebon.replace(/<[^>]*>?/gm, '');
let rawKunrei = item.kunrei.replace(/<[^>]*>?/gm, '');
pageAlphabet10 += `<div class="diff-row"><div class="diff-col-kana">${item.h}</div>
<div class="diff-col-hebon"><div class="char-block romaji diff-block" data-char="${item.h}">
<div class="cell romaji sample diff-cell small" onclick="speakChar('${item.h}', 'ja-JP')" title="čŖćæäøćć"><span class="main-char">${item.hebon}</span></div>
<div class="cell romaji trace diff-cell small"><span class="main-char">${rawHebon}</span><canvas data-size="46"></canvas></div><div class="cell romaji diff-cell small"><canvas data-size="46"></canvas></div></div></div>
<div class="diff-col-kunrei"><div class="char-block romaji diff-block" data-char="${item.h}">
<div class="cell romaji sample diff-cell small" onclick="speakChar('${item.h}', 'ja-JP')" title="čŖćæäøćć"><span class="main-char">${item.kunrei}</span></div>
<div class="cell romaji trace diff-cell small"><span class="main-char">${rawKunrei}</span><canvas data-size="46"></canvas></div><div class="cell romaji diff-cell small"><canvas data-size="46"></canvas></div></div></div></div>`;
});
pageAlphabet10 += `</div></div></div></div>`;
container.insertAdjacentHTML('beforeend', pageAlphabet10);
// --- ę°åć»ę¼¢ę°å ---
let pageNum1 = `<div id="page-numbers-1" class="page"><h1 class="main-title"><span class="step-badge large">Step 1</span> <span data-i18n="numbers.step1_title"></span></h1><div class="blocks-container numbers-layout">`;
charsData.numbers.arabic.forEach(num => {
pageNum1 += `<div class="char-block numbers-block" data-char="${num}">
<div class="cell numbers-cell sample" onclick="speakChar('${num}')" title="čŖćæäøćć"><span class="main-char">${num}</span></div>
<div class="cell numbers-cell trace"><span class="main-char">${num}</span><canvas data-size="105"></canvas></div>
<div class="cell numbers-cell"><canvas data-size="105"></canvas></div>
</div>`;
});
pageNum1 += `</div></div>`;
container.insertAdjacentHTML('beforeend', pageNum1);
let pageNum2 = `<div id="page-numbers-2" class="page"><h1 class="main-title"><span class="step-badge large">Step 2</span> <span data-i18n="numbers.step2_title"></span></h1><div class="blocks-container kanji-layout">`;
charsData.numbers.kanji.forEach(row => {
row.forEach(item => {
pageNum2 += `<div class="char-block kanji" data-char="${item.c}" data-ruby="${item.r}">
<div class="cell kanji sample" onclick="speakChar('${item.r}', 'ja-JP')" title="čŖćæäøćć"><div class="kanji-ruby">${item.r}</div><span class="main-char">${item.c}</span><div class="kanji-num">${item.n}</div></div>
<div class="cell kanji trace"><span class="main-char">${item.c}</span><canvas data-size="105"></canvas></div>
<div class="cell kanji"><canvas data-size="105"></canvas></div>
</div>`;
});
});
pageNum2 += `</div></div>`;
container.insertAdjacentHTML('beforeend', pageNum2);
}
function updatePageDisplay() {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(`page-${currentScript}-${currentPage}`).classList.add('active');
updateNavigation();
applyZoom();
}
// ćŗć¼ć ęä½é¢ę°ē¾¤
function changeZoom(delta) {
currentZoom += delta;
if(currentZoom < 0.5) currentZoom = 0.5;
if(currentZoom > 2.5) currentZoom = 2.5;
applyZoom();
}
function resetZoom() {
currentZoom = 1.0;
applyZoom();
}
function applyZoom() {
const container = document.getElementById('pages-container');
container.style.transform = `scale(${currentZoom})`;
const activePage = document.querySelector('.page.active');
if(activePage) {
document.body.style.minHeight = `${(activePage.offsetHeight * currentZoom) + 180}px`;
document.body.style.minWidth = `${activePage.offsetWidth * currentZoom}px`;
}
}
function changePage(delta) {
const total = getTotalPages();
currentPage += delta;
if (currentPage < 1) currentPage = 1;
if (currentPage > total) currentPage = total;
updatePageDisplay();
}
function updateNavigation() {
const total = getTotalPages();
const isKana = (currentScript === 'hiragana' || currentScript === 'katakana');
document.getElementById('page-indicator').textContent = `${currentPage} / ${total}`;
document.getElementById('prev-btn').disabled = currentPage === 1;
document.getElementById('next-btn').disabled = currentPage === total;
document.querySelectorAll('.jump-btn').forEach(btn => { btn.style.display = isKana ? 'flex' : 'none'; });
}
function goToTop() { changePage(1 - currentPage); }
function jumpToAiueoV() { changePage(7 - currentPage); }
function jumpToAiueoH() { changePage(8 - currentPage); }
function toggleTheme() {
document.body.classList.toggle('dark-mode');
const isDark = document.body.classList.contains('dark-mode');
const icon = document.getElementById('theme-icon');
icon.className = isDark ? 'fa-solid fa-sun' : 'fa-solid fa-moon';
// ćć§ć«ęććććć£ć³ćć¹ć®ē·ć®č²ćå転ćććå¦ēļ¼é»āē½ļ¼
document.querySelectorAll('canvas').forEach(canvas => {
const w = canvas.width;
const h = canvas.height;
if(w === 0 || h === 0) return;
const ctx = canvas.getContext('2d');
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
if (data[i+3] > 0) { // éęć§ćŖćęē»ććÆć»ć«ćå転
data[i] = 255 - data[i];
data[i+1] = 255 - data[i+1];
data[i+2] = 255 - data[i+2];
}
}
ctx.putImageData(imgData, 0, 0);
});
updateCursor();
}
function setTool(tool) {
currentTool = tool;
document.getElementById('btn-pen').classList.toggle('active', tool === 'pen');
document.getElementById('btn-eraser').classList.toggle('active', tool === 'eraser');
document.getElementById('btn-move').classList.toggle('active', tool === 'move');
const canvases = document.querySelectorAll('canvas');
if (tool === 'move') {
document.body.classList.add('move-mode');
canvases.forEach(c => c.style.pointerEvents = 'none');
document.body.style.cursor = 'grab';
} else {
document.body.classList.remove('move-mode');
canvases.forEach(c => c.style.pointerEvents = 'auto');
document.body.style.cursor = 'default';
updateCursor();
}
}
function updateCursor() {
if(currentTool === 'move') return;
const canvases = document.querySelectorAll('canvas');
if (currentTool === 'eraser') {
const size = eraserSize;
const strokeColor = document.body.classList.contains('dark-mode') ? '#fff' : '#000';
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}"><circle cx="${size/2}" cy="${size/2}" r="${Math.max(1, size/2 - 1)}" fill="rgba(150,150,150,0.3)" stroke="${strokeColor}" stroke-width="1"/></svg>`;
const dataUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
const offset = Math.floor(size / 2);
canvases.forEach(c => { c.style.cursor = `url('${dataUrl}') ${offset} ${offset}, cell`; });
} else {
canvases.forEach(c => { c.style.cursor = 'crosshair'; });
}
}
let isDrawing = false;
let currentCtx = null;
function initDrawing() {
document.querySelectorAll('canvas').forEach(canvas => {
const size = parseInt(canvas.getAttribute('data-size'));
canvas.width = size * 2;
canvas.height = size * 2;
const ctx = canvas.getContext('2d');
ctx.scale(2, 2);
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
canvas.addEventListener('touchstart', startDrawing, {passive: false});
canvas.addEventListener('touchmove', draw, {passive: false});
canvas.addEventListener('touchend', stopDrawing, {passive: false});
});
}
function getEventPos(e, canvas) {
const rect = canvas.getBoundingClientRect();
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
return {
x: (clientX - rect.left) / currentZoom,
y: (clientY - rect.top) / currentZoom
};
}
function startDrawing(e) {
if(currentTool === 'move') return;
e.preventDefault();
const charBlock = this.closest('.char-block');
if (charBlock) {
const char = charBlock.getAttribute('data-char');
const ruby = charBlock.getAttribute('data-ruby');
if (char && char.trim() !== '') {
let lang = 'ja-JP';
let textToSpeak = ruby ? ruby : char;
// ć¢ć«ćć”ćććåä½ć®ćć¼ćø(Step1, 2)ć®ćæč±čŖćØć³ćøć³ć§ēŗé³
if (currentScript === 'alphabet' && [1, 2].includes(currentPage)) lang = 'en-US';
speakChar(textToSpeak, lang);
}
}
isDrawing = true;
currentCtx = this.getContext('2d');
currentCtx.beginPath();
currentCtx.moveTo(getEventPos(e, this).x, getEventPos(e, this).y);
}
function draw(e) {
if (!isDrawing || !currentCtx || currentTool === 'move') return;
e.preventDefault();
const pos = getEventPos(e, this);
currentCtx.lineCap = 'round';
currentCtx.lineJoin = 'round';
if (currentTool === 'eraser') {
currentCtx.globalCompositeOperation = 'destination-out';
currentCtx.lineWidth = eraserSize;
currentCtx.strokeStyle = 'rgba(0,0,0,1)';
} else {
currentCtx.globalCompositeOperation = 'source-over';
currentCtx.lineWidth = penSize;
const isDark = document.body.classList.contains('dark-mode');
currentCtx.strokeStyle = isDark ? '#ffffff' : '#111111'; // ćć¼ćÆć¢ć¼ćęćÆåøøć«ē½ć§ęē»
}
currentCtx.lineTo(pos.x, pos.y);
currentCtx.stroke();
}
function stopDrawing(e) {
if(e) e.preventDefault();
isDrawing = false;
currentCtx = null;
}
function initPanAndZoom() {
let isPanning = false;
let startX, startY, scrollLeft, scrollTop;
let initialPinchDistance = null;
let initialZoomScale = 1.0;
document.addEventListener('mousedown', (e) => {
if (currentTool !== 'move') return;
isPanning = true;
startX = e.pageX - document.documentElement.offsetLeft;
startY = e.pageY - document.documentElement.offsetTop;
scrollLeft = window.scrollX;
scrollTop = window.scrollY;
document.body.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isPanning || currentTool !== 'move') return;
e.preventDefault();
const x = e.pageX - document.documentElement.offsetLeft;
const y = e.pageY - document.documentElement.offsetTop;
window.scrollTo(scrollLeft - (x - startX), scrollTop - (y - startY));
});
document.addEventListener('mouseup', () => {
isPanning = false;
if(currentTool === 'move') document.body.style.cursor = 'grab';
});
document.addEventListener('touchstart', (e) => {
if (currentTool !== 'move') return;
if (e.touches.length === 1) {
isPanning = true;
startX = e.touches[0].pageX - document.documentElement.offsetLeft;
startY = e.touches[0].pageY - document.documentElement.offsetTop;
scrollLeft = window.scrollX;
scrollTop = window.scrollY;
} else if (e.touches.length === 2) {
isPanning = false;
initialPinchDistance = Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY);
initialZoomScale = currentZoom;
}
}, {passive: false});
document.addEventListener('touchmove', (e) => {
if (currentTool !== 'move') return;
e.preventDefault();
if (e.touches.length === 1 && isPanning) {
const x = e.touches[0].pageX - document.documentElement.offsetLeft;
const y = e.touches[0].pageY - document.documentElement.offsetTop;
window.scrollTo(scrollLeft - (x - startX), scrollTop - (y - startY));
} else if (e.touches.length === 2 && initialPinchDistance) {
const currentDistance = Math.hypot(e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY);
let newZoom = initialZoomScale * (currentDistance / initialPinchDistance);
if(newZoom < 0.5) newZoom = 0.5;
if(newZoom > 2.5) newZoom = 2.5;
currentZoom = newZoom;
applyZoom();
}
}, {passive: false});
document.addEventListener('touchend', (e) => {
if (e.touches.length < 2) initialPinchDistance = null;
if (e.touches.length === 0) isPanning = false;
});
}
function clearCurrentPage() {
clearCanvasesInElement(document.querySelector('.page.active'));
}
function clearAllPages() {
document.querySelectorAll(`.page[id^="page-${currentScript}-"]`).forEach(page => clearCanvasesInElement(page));
}
function clearCanvasesInElement(el) {
el.querySelectorAll('canvas').forEach(canvas => {
const ctx = canvas.getContext('2d');
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
});
}
function getDateString() {
const d = new Date();
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
}
function getBaseName() {
const handStr = currentHand === 'left' ? (currentLang === 'en' ? 'LeftHanded' : 'å·¦ęēØ') : (currentLang === 'en' ? 'RightHanded' : 'å³ęēØ');
let scriptStr = '';
if (currentScript === 'hiragana') scriptStr = currentLang === 'en' ? 'Hiragana' : 'ć²ćććŖ';
else if (currentScript === 'katakana') scriptStr = currentLang === 'en' ? 'Katakana' : 'ć«ćæć«ć';
else if (currentScript === 'alphabet') scriptStr = currentLang === 'en' ? 'Romaji' : 'ćć¼ćå';
else scriptStr = currentLang === 'en' ? 'Numbers' : 'ę°å';
return currentLang === 'en' ? `${handStr}_${scriptStr}` : `${handStr}_${scriptStr}ē·“ēæ`;
}
async function capturePage(pageEl) {
const bgColor = getComputedStyle(document.body).getPropertyValue('--bg-color').trim();
const origRadius = pageEl.style.borderRadius;
const origShadow = pageEl.style.boxShadow;
pageEl.style.borderRadius = '0';
pageEl.style.boxShadow = 'none';
await new Promise(r => setTimeout(r, 50));
const canvas = await html2canvas(pageEl, { scale: 2, backgroundColor: bgColor, useCORS: true });
pageEl.style.borderRadius = origRadius;
pageEl.style.boxShadow = origShadow;
return canvas;
}
function closeModal() {
document.getElementById('image-modal').style.display = 'none';
document.getElementById('modal-images').innerHTML = '';
}
async function saveCurrentPage() {
document.getElementById('loading-overlay').style.display = 'flex';
try {
const tempZoom = currentZoom;
resetZoom();
const format = document.getElementById('save-format').value;
const canvas = await capturePage(document.querySelector('.page.active'));
const fName = `${getBaseName()}_Page${currentPage}_${getDateString()}`;
if (format === 'png') {
if (isIOS) {
// iOS/iPadOSćÆć¢ć¼ćć«ć§ē»åć蔨示ććé·ę¼ćäæåććć
const modalImages = document.getElementById('modal-images');
modalImages.innerHTML = '';
const img = document.createElement('img');
img.src = canvas.toDataURL('image/png');
modalImages.appendChild(img);
document.getElementById('image-modal').style.display = 'flex';
} else {
// PCćAndroidēćÆćć®ć¾ć¾ćć¦ć³ćć¼ć
const link = document.createElement('a');
link.download = `${fName}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
}
} else if (format === 'pdf') {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'landscape', format: 'a4' });
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, 0, 297, 210);
pdf.save(`${fName}.pdf`);
}
currentZoom = tempZoom;
applyZoom();
} catch (err) {
console.error(err);
alert("Error while saving.");
}
finally {
document.getElementById('loading-overlay').style.display = 'none';
}
}
async function saveAllPages() {
document.getElementById('loading-overlay').style.display = 'flex';
const format = document.getElementById('save-format').value;
const originalPage = currentPage;
const tempZoom = currentZoom;
resetZoom();
const base = getBaseName();
const allStr = currentLang === 'en' ? 'AllPages' : 'å
Øćć¼ćø';
const dateStr = getDateString();
const total = getTotalPages();
try {
let capturedCanvases = [];
for (let i = 1; i <= total; i++) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
const pageEl = document.getElementById(`page-${currentScript}-${i}`);
pageEl.classList.add('active');
capturedCanvases.push(await capturePage(pageEl));
}
if (format === 'png') {
if (isIOS) {
// iOS/iPadOSćÆć¢ć¼ćć«ć«å
Øćć¼ćøć®ē»åćäø¦ć¹ć¦č”Øē¤ŗćć
const modalImages = document.getElementById('modal-images');
modalImages.innerHTML = '';
for (let idx = 0; idx < capturedCanvases.length; idx++) {
const img = document.createElement('img');
img.src = capturedCanvases[idx].toDataURL('image/png');
modalImages.appendChild(img);
}
document.getElementById('image-modal').style.display = 'flex';
} else {
// PCćAndroidēćÆćć®ć¾ć¾é£ē¶ćć¦ć³ćć¼ć
for (let idx = 0; idx < capturedCanvases.length; idx++) {
const link = document.createElement('a');
link.download = `${base}_Page${idx + 1}_${dateStr}.png`;
link.href = capturedCanvases[idx].toDataURL('image/png');
link.click();
await new Promise(r => setTimeout(r, 600)); // é£ē¶DLć®ććŖć¼ćŗé²ę¢
}
}
} else if (format === 'pdf') {
const { jsPDF } = window.jspdf;
let pdf = null;
capturedCanvases.forEach((cvs, idx) => {
if (idx === 0) pdf = new jsPDF({ orientation: 'landscape', format: 'a4' });
else pdf.addPage('a4', 'landscape');
pdf.addImage(cvs.toDataURL('image/png'), 'PNG', 0, 0, 297, 210);
});
pdf.save(`${base}_${allStr}_${dateStr}.pdf`);
}
} catch (err) {
console.error(err);
alert("Error while saving.");
}
finally {
currentPage = originalPage;
updatePageDisplay();
currentZoom = tempZoom;
applyZoom();
document.getElementById('loading-overlay').style.display = 'none';
}
}
</script>
</body>
</html>šć³ć¼ćć®č©³ē“°č§£čŖ¬
ććć©ć¦ć¶äøć§åććć²ćććŖć»ć«ćæć«ćć»ćć¼ćåć»ę°åć®ęøćåćē·“ēæć¢ććŖļ¼SPAļ¼ć·ć³ć°ć«ćć¼ćøć¢ććŖć±ć¼ć·ć§ć³ļ¼ćć§ćć
1ć¤ć®HTMLćć”ć¤ć«ć®äøć«ćHTML/CSS/JavaScriptććć¹ć¦ć¾ćØć¾ć£ć¦ććććµć¼ćć¼ćŖćļ¼ćŖćć©ć¤ć³ļ¼ć§ćåä½ććä½ćć«ćŖć£ć¦ćć¾ćć
ćć®ć³ć¼ćć®ććććØććććć©ć®ćććŖä»ēµćæć§åćć¦ććć®ććåććććć解説ćć¾ćļ¼
šć¢ććŖć®äø»ćŖē¹å¾“ļ¼å®č£ ććć¦ććę©č½ļ¼
4ć¤ć®å¦ēæć¢ć¼ć:
ć²ćććŖćć«ćæć«ćććć¼ćåćę°åäø”å©ć対åæ:
å·¦ęēØć»å³ęēØć§ęåć®äø¦ć³é
ļ¼ćęę¬ćØęøćč¾¼ćæćć¹ć®ä½ē½®ļ¼ćå転ćććēµµććę©č½:
ćć³ć»ę¶ćć“ć ć§ć®ęøćč¾¼ćæåƾåæ
ļ¼ćæćć¬ććć®ćæććęä½ć»PCć®ćć¦ć¹ęä½äø”対åæļ¼é³å£°čŖćæäøć:
ćæććććęåćććć¼ćøå Øä½ć
ćć©ć¦ć¶ć®ę©č½ļ¼Web Speech APIļ¼ć§čŖćæäøćē»åć»PDFäæåę©č½:
ęøććå 容ćPNGē»åćPDFćØćć¦ćć¦ć³ćć¼ćåÆč½
ļ¼iOSē¹ęć®äæåå¶éć«ć対åæļ¼å¤čØčŖļ¼ćć¼ć対åæ:
ę„ę¬čŖ/č±čŖć®åćęæććć©ć¤ć/ćć¼ćÆć¢ć¼ćć®åćęæććŗć¼ć ļ¼ē§»å:
ćć³ćć¤ć³ć»ć¢ć¦ćļ¼ę”大縮å°ļ¼ćć¹ćÆć¤ćć§ć®ē»é¢ē§»åć«åƾåæ
š ļø ć³ć¼ćć®ę§é 解説
ć³ć¼ććÆå¤§ććåćć¦ć
ā čØå®ć»ć©ć¤ćć©ćŖčŖćæč¾¼ćæ
ā”CSSļ¼ćć¶ć¤ć³ļ¼
ā¢HTMLļ¼UIę ēµćæļ¼
ā£JavaScriptļ¼åä½ććøććÆļ¼
ć®4ććććÆć§ę§ęććć¦ćć¾ćć
1. <head>ļ¼å¤éØć©ć¤ćć©ćŖćØćć©ć³ćć®čŖćæč¾¼ćæ
Google Fonts:
Klee Oneļ¼ęē§ęøä½ć®ćććŖęęøć風ćć©ć³ćļ¼ćØ
Noto Sans JPļ¼UIēØļ¼ćčŖćæč¾¼ćć§ćć¾ććFontAwesome:
ććæć³ć®ć¢ć¤ć³ć³ļ¼ćć³ćę¶ćć“ć ćć“ćē®±ćŖć©ļ¼ć
蔨示ććććć®ć©ć¤ćć©ćŖć§ććhtml2canvas / jsPDF:
HTMLć®ē»é¢ććć®ć¾ć¾ćē»åććØćć¦ćć£ććć£ćć
ććććPDFćć«å¤ęćć¦äæåććććć®
å¼·åćŖJavaScriptć©ć¤ćć©ćŖć§ćć
2. <style>ļ¼CSSćć¶ć¤ć³ćØć¬ć¹ćć³ć·ć対åæ
CSSå¤ę°ļ¼:rootļ¼:
č²ć®čØå®ćå¤ę°åćć¦ććć
.dark-mode ćÆć©ć¹ćä»äøććććØćć«
å¤ę°ć®č²ćäøęć«ęćč²ć«åćęæććććØć§ć
ćć¼ćÆć¢ć¼ććē°”åć«å®ē¾ćć¦ćć¾ććå·¦å©ć対åæć®å·„夫:
css
body.left-hand .char-block { flex-direction: row-reverse; }
å·¦ęć¢ć¼ćć®ęćÆć
Flexboxć®äø¦ć³é ćå転ļ¼row-reverseļ¼ććć¦ćć¾ćć
ććć«ćććęøćć¦ććčŖåć®ęć§
ććęę¬ććé ććŖćććć«é ę ®ććć¦ćć¾ćććć£ć³ćć¹ć®éćåćć:
ęåć®ćć¹ē®ļ¼.cellļ¼ć®äøć«
<canvas> ćæć°ć絶対é ē½®ļ¼position: absoluteļ¼ć§éćć¦ććć
ćęę¬ććŖćććććć«ćŖć£ć¦ćć¾ćć
3. <body>ļ¼HTMLć®éŖØēµćæ
ē»é¢ć«ęåććååØćć¦ććć®ćÆć仄äøć®3ć¤ć®ćć¼ćć ćć§ćć
#loading-overlay:
äæåäøćŖć©ć«ē»é¢ćęććć¦ćå¦ēäøććåŗćē»é¢ć#image-modal:
iPhone/iPadļ¼iOSļ¼ć®å “åć
ē“ę„ćć¦ć³ćć¼ććå¶éćććććØćććććć
ćē»åćé·ę¼ććć¦äæåćć¦ćććØč”Øē¤ŗććå°ēØć®ē»é¢ć#controls:
ē»é¢äøéØć«åŗå®ććć¦ććć”ćć„ć¼ćć¼ć#pages-container:
ē·“ēæåø³ć®ćć¼ćøćå „ćć空ć®ē®±ćć§ćć
äøčŗ«ćÆćć¹ć¦JavaScriptć§ēęććć¾ćć
4. <script>ļ¼JavaScriptć®åæčéØ
éåøøć«é«åŗ¦ćŖå¦ēćč”ććć¦ćć¾ććäø»č¦ćŖę©č½ććććÆć¢ćććć¾ćć
ćć¼ćæē®”ēļ¼i18n, charsDataļ¼:
ę„ę¬čŖ/č±čŖć®ēæ»čسćć¼ćæćØć
ē·“ēæććęåć®ćć¼ćæļ¼äŗåé³ćć¢ć«ćć”ćććć
ććć³å¼ć»čØä»¤å¼ć®éććŖć©ļ¼ććŖććøć§ćÆććØćć¦ę“ēććć¦ćć¾ćććć¼ćøć®åēēęļ¼renderPages()ļ¼:
charsData ć®ćć¼ćæćå ć«ć
JavaScriptć® for ć«ć¼ćć使ć£ć¦
大éć®HTMLļ¼ē·“ēæćć¹ćCanvasļ¼ćčŖåēęćć
#pages-container ć«ęµćč¾¼ćć§ćć¾ćććēµµććę©č½ļ¼initDrawing(), startDrawing(), draw() ćŖć©ļ¼:
<canvas> APIćå©ēØćć¦ćć¾ćć
ćć¦ć¹ććæććć®åćļ¼mousemove, touchmoveļ¼ćę¤ē„ćć¦ć
Canvasäøć«ē·ćå¼ćć¾ćć
ę¶ćć“ć ć¢ć¼ćć®ę㯠destination-out ćØććć¢ć¼ćć«ćć¦
ē·ćéęåļ¼ę¶å»ļ¼ćć¦ćć¾ććé³å£°čŖćæäøćļ¼speakText(), speakChar()ļ¼:
ćć©ć¦ć¶ęØęŗć® SpeechSynthesisUtterance ć使ēØć
åć«ęåćęø”ćć ćć§ćŖćć
ććÆććććććØčŖć¾ćććčØå·ćē”č¦ćććŖć©ć
čŖē¶ć«ēŗé³ćććććć®ē½®ęå¦ēćē“°ććå®č£ ććć¦ćć¾ćććć³ććŗć¼ć ć»ē§»åļ¼initPanAndZoom()ļ¼:
ę2ę¬ć§ć®ćæććļ¼e.touches.length === 2ļ¼ć®č·é¢ć
ććæć“ć©ć¹ć®å®ēļ¼Math.hypotļ¼ć§čØē®ćć
ē“ęēćŖę”大縮å°ļ¼ćŗć¼ć ļ¼ćå®ē¾ćć¦ćć¾ććäæåę©č½ļ¼saveCurrentPage(), saveAllPages()ļ¼:
html2canvas ć§ē»é¢ćē»ååćć¾ććććć§ć®ē“ ę“ććć巄夫ćÆć
ć¦ć¼ć¶ć¼ć®ē«Æę«ćiOSćć©ćććå¤å®ļ¼isIOSļ¼ćć¦ććē¹ć§ćć
iOSć®ćć©ć¦ć¶ļ¼SafarićŖć©ļ¼ćÆ
JavaScriptććć®å¼·å¶ćć¦ć³ćć¼ććććććÆććććØćå¤ćććć
iOSć®ęć ććē»åćē»é¢ć«č”Øē¤ŗćć¦ć
ć¦ć¼ć¶ć¼čŖčŗ«ć«é·ę¼ćäæåćććććØćć
å®å ØćŖćć©ć¼ć«ćććÆļ¼ä»£ęæå¦ēļ¼ćč”ć£ć¦ćć¾ćć
šē·č©

ćć®ć³ć¼ććÆćåćŖćWebćć¼ćøć§ćÆćŖć
ćå®ęćććęč²ē³»Webć¢ććŖćć§ćć
UXļ¼ć¦ć¼ć¶ć¼ä½éØļ¼ćøć®é ę ®:
å·¦å©ć対åæććć¼ćÆć¢ć¼ććē»é¢ćŗć¼ć ćčŖćæäøćęć®čŖ¤čŖé²ę¢
ļ¼ććÆćć®čŖćæę¹ćŖć©ļ¼ćććć³å¼ć»čØä»¤å¼ć®č§£čŖ¬ćć¼ćøć¾ć§
å«ć¾ćć¦ććē¹ćŖć©ćå¦ēæč ćøć®ęććććéęć«č¦ććć¾ććć¢ćć³ćŖęč”ć®ę“»ēØ:
ćć¬ć¼ć ćÆć¼ćÆļ¼ReactćVuećŖć©ļ¼ć使ććć«ć
ē“ ć®JavaScriptļ¼ććć©JSļ¼ć ćć§ē¶ę ē®”ēć
DOMēęćCanvasęē»ć端ę«å¤å®ć¾ć§ćē¾ććå®č£ ćć¦ćć¾ćć
ćć®HTMLćć”ć¤ć«ć
ćć®ć¾ć¾ćć©ć¦ć¶ļ¼ChromećSafarićŖć©ļ¼ć§éćć ćć§
ććć«ćć«ę©č½ć§éć¹ććéåøøć«č³Ŗć®é«ćć³ć¼ćć§ćļ¼
š ćććŖäŗŗć«ćŖć¹ć¹ć”
ć²ćććŖćć«ćæć«ćććć¼ćåćč¦ćå§ćć®å°ććŖćåę§š¶
åøč²©ć®ććŖć«ć ćØęćę±ććććęåćé ćć¦ćć¾ćå·¦å©ćć®ę¹āļø
ē“ćę¶č²»ććććæćć¬ććć§ćć¼ćć¼ć¬ć¹å¦ēæććććć親徔ććš±
ę£ććęøćé ćēŗé³ćå¦ć³ćććę„ę¬čŖå¦ēæäøć®å¤å½äŗŗć®ę¹š
å³č
ćåććŖććØććēµ¶ęć®ę·µććć
AIćØććå¼·åćŖēøę£ćØćØćć«ēćæåŗćććć®ć¢ććŖć
ęč²ēé
ę
®ćØććøćæć«ćŖćć§ćÆć®ę©č½ćčåćććčŖäæ”ä½ć§ćć
ćć®ć¢ććŖććęåćå¦ć¶čŖ°ćć®
ćäøä¾æććć愽ćććć«å¤ćććć£ććć«ćŖćććć
ććć»ć©å¬ććććØćÆććć¾ććć
ćć²ćiPadćPCćć触ć£ć¦ćæć¦ćć ćććļ¼
ć¢ććŖćÆć³ć橚
ā ć³ć³ćć¹ććæć°
#åµä½å¤§č³2026 #ćŖć¼ć«ć«ćć“ćŖéØé
ā å·¦å©ćć»ćØćć»ć¤ć»äŗŗē
#å·¦å©ć #ćØćć»ć¤ #ćŖćććŖ #å„åŗ· #éē #é害č #é害 #ććŖć¢ććŖć¼ #ć¢ćÆć»ć·ććŖć㣠#ćć¤ćć¼ć·ć㣠#ćć¤ććŖć㣠#ć©ć¤ćć¹ćæć¤ć« #ēćę¹ #äŗŗē #ęę¦ #ä½éØč« #ć¢ććć¼ć·ć§ć³
ā ęč²ć»åč²ć¦ć»ē„č²
#ē„č² #åč²ć¦ #č²å #ęč² #å¦ēæ #åå¼· #å¹¼å ęč² #å°å¦ē #äæč²å #å¹¼ēØå #ICTęč² #EdTech #ē„č²ć¢ććŖ #ććŖć« #å®æé” #ćććŗ #åå¼·å¢ #ę„čØ
ā ęåć»ęøåć»čŖå¦
#ę„ę¬čŖå¦ēæ #ć²ćććŖ #ć«ćæć«ć #ćć¼ćå #ę°å #ćć³å #ē¾ęå #ęøéĀ #ęęøć #ęęøćęå #ēæå #ę„ę¬čŖ #Japanese #ę„ę¬čŖęč² #čŖå¦ #č±čŖå¦ēæ #č±čŖ
ā iPadć»ććøćæć«ę“»ēØ
#iPadę“»ēØ #iPad #ćæćć¬ćć #ApplePencil #ććøćæć« #ćć¼ćć¼ć¬ć¹ #ć©ć¤ććć㯠#便å©ćć¼ć« #ććććć¢ććŖ #ē”ęć¢ććŖ
ā AIć»ććć°ć©ćć³ć°ć»åäŗŗéēŗ
#ć¢ććŖéēŗ #AIćØćć£ć¦ćæć #ććć°ć©ćć³ć° #Webć¢ććŖ #HTML #JavaScript #AI #ēęAI #ChatGPT #Gemini #ćØć³ćøć㢠#ććÆćććøć¼ #IT #ććć°ć©ćć³ć°ååæč #UIUX #Webćć¶ć¤ć³
ā åµä½ć»å°čŖ¬ć»ćØć³ćæć”
#åµä½ #å°čŖ¬ #ćć”ć³ćæćøć¼ #ćÆćŖćØć¤ćæć¼ #ćć”ć³ć¢ć¼ć #ć¤ć©ć¹ć #ć¢ćć” #ę åå¶ä½ #ę ē» #ćØć³ćæć”
ā noteå®ēŖć»ę„åøø
#ęÆę„note #note #ę„čØ #ć³ć©ć #ę„åøø #ć¹ććć¦ćæć¦ #čŖęø
#AIć§éć¼ć #åć©ćć®ęé·ćęćććØć
#AIćØå§ćć¦ćæć #ć¹ć©ć³ćć®ä¹ćč¶ćę¹
ćć®čØäŗćåå ćć¦ććåé
- #ę„čØ
- #ćØćć»ć¤
- #ęÆę„note
- #å°čŖ¬
- #note
- #ć¤ć©ć¹ć
- #äŗŗē
- #AI
- #åč²ć¦
- #ę„åøø
- #ć³ć©ć
- #čŖęø
- #ēęAI
- #ChatGPT
- #ę ē»
- #åµä½
- #č²å
- #å„åŗ·
- #ēćę¹
- #č±čŖ
- #åµä½å¤§č³2026
- #ęč²
- #åå¼·
- #č±čŖå¦ēæ
- #ęę¦
- #ć¢ćć”
- #ć©ć¤ćć¹ćæć¤ć«
- #Gemini
- #ęē«
- #Claude
- #ćØć³ćøćć¢
- #ćŖć¼ć«ć«ćć“ćŖéØé
- #ä½éØč«
- #ććć°ć©ćć³ć°
- #ćć”ć³ćæćøć¼
- #IT
- #ć¢ććŖ
- #ć©ć¤ććććÆ
- #ęå¦
- #åå¼·ę³
- #ćŖćććŖ
- #ććÆćććøć¼
- #ć¢ććć¼ć·ć§ć³
- #ClaudeCode
- #ćÆćŖćØć¤ćæć¼
- #å¦ēæ
- #ę„ę¬čŖ
- #åå¼·čØé²
- #Webćć¶ć¤ć³
- #ćØć³ćæć”
- #ććøćæć«
- #é害č
- #čŖå¦
- #iPad
- #ććć°ć©ćć³ć°ååæč
- #é害
- #english
- #ć¢ććŖéēŗ
- #ęå
- #å¹¼å ęč²
- #ęøé
- #čŖå¦å¦ēæ
- #AIćØć§ććććØ
- #ę„ę¬čŖęč²
- #ę åå¶ä½
- #ćć¤ćć¼ć·ćć£
- #ęęøć
- #HTML
- #čŖē±ē ē©¶
- #ćæćć¬ćć
- #ICTęč²
- #ććććć¢ććŖ
- #ćć¤ććŖćć£
- #ćć”ć³ć¢ć¼ć
- #css
- #ććŖć¢ććŖć¼
- #åå¼·å¢
- #Japanese
- #UIUX
- #ēæå
- #ć¢ćÆć»ć·ććŖćć£
- #Webć¢ććŖ
- #ē¾ęå
- #ć²ćććŖ
- #ćµć¤ćććć
- #ę„ę¬čŖå¦ēæ
- #Fable5
- #ć«ćæć«ć
- #edtech
- #ęęøćęå
- #ćć¼ćć¼ć¬ć¹
- #ćć³å
- #applepencil
- #iPadę“»ēØ
- #canvas
- #ē”ęć¢ććŖ
- #ćć³åē·“ēæ