スマホの入力を簡単にする?「Two Same Input」
【更新履歴】
・2026/1/30 バージョン1.0公開。
・2026/1/30 バージョン1.1公開。
・2026/1/31 バージョン1.3公開。
・2026/1/31 バージョン1.4公開。(検索、AI、変換強化)
《このツールの概要》
・スマホ画面のテキスト入力方式といえば、
フリック入力が一般的ですが、
このツールでは、ツーサム方式で入力します。
・入力したテキストは、
コピ(コピー)ボタンを押すことで
他のwebページなどで使用できる他、
検索ボタンやAIボタンを押すことで
Googleの検索結果を表示したり、
ChatGptからの解答を表示することができます。
《 PCでスマホの画面を試す方法 》
・htmlファイルをクリックして、Chromeで開く。
・Chromeのメニューバーから、
「設定」→「その他のツール」→「デベロッパーツール」
をクリックして、スマホのマークをクリックすると、
スマホの画面で操作することができます。
・ダウンロードされる方はこちら。↓
・ソースコードはこちら。↓
<!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">
<title>Two Same Input 1.4</title>
<style>
:root {
--bg-color: #121212;
--text-color: #fff;
/* 行カラー */
--c-a: #e60012; --c-k: #004098; --c-s: #40e0d0; --c-t: #ffd900;
--c-n: #009944; --c-h: #ff69b4; --c-m: #800080; --c-y: #f39800;
--c-r: #b08d55; --c-w: #6cbb5a;
--c-sym: #555555;
/* タブカラー */
--tab-hira: #6f4b3e; --tab-kata: #1d2088; --tab-lower: #ff69b4;
--tab-upper: #c71585; --tab-num: #40e0d0; --tab-sym: #009944;
--tab-emoji: #ffd900;
/* ボタン色設定 (今回変更箇所) */
--col-ent: #ffd900; /* 改行: 黄色 */
--col-ctl: #40e0d0; /* 操作系: 水色 */
--col-ai: #ff1493; /* AI: ピンク */
--col-sch: #6b8e23; /* 検索: 濃い黄緑 */
--col-cpy: #00bfff; /* コピー: 水色(DeepSkyBlue) */
--col-pst: #ff8c00; /* 貼付: オレンジ(DarkOrange) */
--col-silver: #c0c0c0; /* 銀色 */
--cursor-color: #00ffff;
--bg-inactive-btn: #555555;
--bg-candidate: #444444;
--bg-chip: #666666;
}
@keyframes border-blink {
0% { border-color: var(--cursor-color); box-shadow: 0 0 5px var(--cursor-color); }
50% { border-color: transparent; box-shadow: none; }
100% { border-color: var(--cursor-color); box-shadow: 0 0 5px var(--cursor-color); }
}
body {
margin: 0; padding: 0;
background-color: var(--bg-color);
color: var(--text-color);
font-family: "Hiragino Kaku Gothic ProN", sans-serif;
height: 100vh; height: 100dvh; width: 100vw;
display: flex; flex-direction: column;
overflow: hidden; user-select: none; touch-action: none;
}
/* --- 上部エリア --- */
#top-area {
padding-top: max(env(safe-area-inset-top), 20px);
padding-bottom: 5px; padding-left: 5px; padding-right: 5px;
flex-shrink: 0; z-index: 100; box-sizing: border-box;
background: #000; border-bottom: 2px solid #333;
display: flex; flex-direction: column; gap: 5px;
}
#top-area.fullscreen {
height: 92%; position: absolute; top: 0; left: 0; right: 0;
background: #000;
}
#display-wrapper {
display: flex; align-items: stretch;
height: 120px; gap: 5px;
}
/* 行番号 */
#line-indicator {
width: 45px;
display: flex; flex-direction: column;
align-items: center; justify-content: flex-start;
padding-top: 8px;
background: #111; border-radius: 4px; flex-shrink: 0;
line-height: 1.0;
}
.ln-num { font-size: 22px; font-weight: bold; color: #fff; }
.ln-label { font-size: 11px; color: #fff; margin-top: 2px; }
/* 入力欄 */
#input-line {
font-size: 18px; line-height: 1.5;
white-space: pre-wrap; overflow-y: auto;
flex: 1;
border: 2px solid #555; background: #ffffff; color: #000000;
padding: 8px; border-radius: 6px;
word-break: break-all;
outline: none; caret-color: #000;
}
[contenteditable] { -webkit-user-select: text; user-select: text; }
.composing-span { background: #d0ebff; border-bottom: 2px solid #00b4d8; }
/* サイドコントロール */
.side-controls {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 3px; width: 90px; flex-shrink: 0;
}
.ctrl-btn {
border: 2px solid transparent; border-radius: 4px;
color: #000; font-size: 16px; font-weight: bold;
cursor: pointer; display: flex; align-items: center; justify-content: center;
background: var(--col-ctl); box-sizing: border-box;
}
.btn-ent { background: var(--col-ent); font-size: 22px; color: #000; }
.btn-full { background: var(--col-ctl); color: #000; font-size: 18px; border: 1px solid #333; }
/* ホバーエフェクト */
.ctrl-btn:hover, .ctrl-btn:active,
.sub-btn:hover, .sub-btn:active,
.key:hover, .key:active,
.cand-chip:hover, .cand-chip:active {
border: 2px solid var(--cursor-color);
animation: border-blink 0.8s infinite;
z-index: 10; position: relative;
}
#sub-bar {
height: 70px; display: flex; gap: 5px; flex-shrink: 0; align-items: stretch;
}
#candidate-bar {
flex: 1; display: flex; flex-wrap: wrap; align-content: flex-start; overflow-y: auto;
background: var(--bg-candidate); padding: 4px; border-radius: 4px; gap: 4px;
}
.cand-chip {
background: var(--bg-chip); padding: 6px 12px;
border-radius: 12px; font-size: 14px; border: 2px solid transparent;
height: 28px; box-sizing: border-box; display: flex; align-items: center; color: #fff;
cursor: pointer;
}
.extended-tools {
display: flex; gap: 3px;
}
.sub-tools {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 3px; width: 90px; flex-shrink: 0;
}
.link-tools {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
gap: 3px; width: 45px; flex-shrink: 0;
}
.sub-btn {
border: 2px solid transparent; border-radius: 4px;
font-size: 12px; font-weight: bold;
display: flex; align-items: center; justify-content: center;
cursor: pointer; box-sizing: border-box;
}
/* ボタン色適用 */
.btn-mov { background: var(--col-ctl); color: #000; font-size: 14px; }
.btn-cpy { background: var(--col-cpy); color: #fff; }
.btn-pst { background: var(--col-pst); color: #fff; }
.btn-sch { background: var(--col-sch); color: #fff; font-size: 11px; }
.btn-ai { background: var(--col-ai); color: #fff; font-size: 14px; }
/* --- メインエリア --- */
#main-stage {
flex: 1; position: relative; overflow: hidden;
background: #111; padding: 4px;
}
#twothumb-board {
display: grid;
grid-template-columns: 1fr 2.8fr;
gap: 16px; height: 100%; width: 100%; box-sizing: border-box;
}
#left-panel { display: grid; grid-template-columns: 1fr; grid-auto-rows: 1fr; gap: 4px; }
#left-panel.jp-layout { grid-template-columns: 1fr 1fr; grid-template-rows: repeat(6, 1fr); }
#right-panel { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(4, 1fr); gap: 4px; }
#right-panel.jp-layout { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(5, 1fr); }
#right-panel.sym-layout { grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(4, 1fr); }
.key {
display: flex; align-items: center; justify-content: center;
font-weight: bold; border-radius: 6px; font-size: 22px;
cursor: pointer; color: #fff;
box-shadow: 0 3px 0 rgba(0,0,0,0.3);
border: 3px solid transparent; box-sizing: border-box;
text-align: center; line-height: 1.1;
}
.key-left.active {
border-color: var(--cursor-color);
animation: border-blink 1s infinite;
filter: brightness(1.2);
z-index: 5;
}
.font-emoji { font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif; }
.row-a { background: var(--c-a); } .row-k { background: var(--c-k); }
.row-s { background: var(--c-s); color: #000; text-shadow: none; }
.row-t { background: var(--c-t); color: #000; text-shadow: none; }
.row-n { background: var(--c-n); } .row-h { background: var(--c-h); }
.row-m { background: var(--c-m); } .row-y { background: var(--c-y); color: #000; text-shadow: none; }
.row-r { background: var(--c-r); } .row-w { background: var(--c-w); }
.row-sym { background: var(--c-sym); }
.key-del { background: #d62828; font-size: 16px; }
.key-cat { background: var(--bg-inactive-btn); border: 1px solid #777; font-size: 16px; color: #fff; }
.key-cat.active { background: #eee; color: #000; border: 3px solid #fff; }
/* 絵文字ページャー:シルバー */
.key-pager { background: var(--col-silver); font-size: 24px; color: #000; border: 1px solid #999; }
.key-char { background: #333; font-size: 24px; }
.key-jp-char { font-size: 20px; }
/* --- 下部タブ --- */
#tab-bar {
height: 60px; background: #111; border-top: 1px solid #333;
display: flex; flex-shrink: 0; z-index: 10;
padding-bottom: max(env(safe-area-inset-bottom), 0px);
}
.tab {
flex: 1; display: flex; align-items: center; justify-content: center;
font-size: 14px; font-weight: bold; color: #888;
border-right: 1px solid #222; flex-direction: column;
cursor: pointer; background: #222; transition: all 0.2s;
line-height: 1.1;
}
.tab-lbl-main { font-size: 14px; }
.tab-lbl-sub { font-size: 10px; opacity: 0.8; }
.t-hira.active { background: var(--tab-hira); color: #fff; }
.t-kata.active { background: var(--tab-kata); color: #fff; }
.t-lower.active { background: var(--tab-lower); color: #fff; }
.t-upper.active { background: var(--tab-upper); color: #fff; }
.t-num.active { background: var(--tab-num); color: #000; }
.t-sym.active { background: var(--tab-sym); color: #fff; }
.t-emoji.active { background: var(--tab-emoji); color: #000; }
</style>
</head>
<body>
<div id="top-area">
<div id="display-wrapper">
<div id="line-indicator">
<div class="ln-num" id="ln-num-text">1</div>
<div class="ln-label">行目</div>
</div>
<div id="input-line" contenteditable="true" inputmode="none" spellcheck="false"
oninput="handleInput()" onclick="updateLineNum()"></div>
<div class="side-controls">
<button class="ctrl-btn btn-ent no-focus-steal" onmousedown="event.preventDefault()" onclick="insertDirect('\n')">↵</button>
<button class="ctrl-btn no-focus-steal" onmousedown="event.preventDefault()" onclick="moveLine(-1)">▲</button>
<button class="ctrl-btn btn-full no-focus-steal" onmousedown="event.preventDefault()" onclick="toggleFull()">□</button>
<button class="ctrl-btn no-focus-steal" onmousedown="event.preventDefault()" onclick="moveLine(1)">▼</button>
</div>
</div>
<div id="sub-bar">
<div id="candidate-bar"></div>
<div class="extended-tools">
<div class="sub-tools">
<button class="sub-btn btn-mov no-focus-steal" onmousedown="event.preventDefault()" onclick="moveLine(-1)">▲</button>
<button class="sub-btn btn-cpy no-focus-steal" onmousedown="event.preventDefault()" onclick="copyAll()">コピ</button>
<button class="sub-btn btn-mov no-focus-steal" onmousedown="event.preventDefault()" onclick="moveLine(1)">▼</button>
<button class="sub-btn btn-pst no-focus-steal" onmousedown="event.preventDefault()" onclick="pasteFromClipboard()">貼付</button>
</div>
<div class="link-tools">
<button class="sub-btn btn-sch no-focus-steal" onmousedown="event.preventDefault()" onclick="searchWeb()">検索</button>
<button class="sub-btn btn-ai no-focus-steal" onmousedown="event.preventDefault()" onclick="askAI()">AI</button>
</div>
</div>
</div>
</div>
<div id="main-stage">
<div id="twothumb-board">
<div id="left-panel"></div>
<div id="right-panel"></div>
</div>
</div>
<div id="tab-bar">
<div class="tab t-hira active" onclick="switchMode('hira', this)"><span class="tab-lbl-main">あ</span><span class="tab-lbl-sub">平仮名</span></div>
<div class="tab t-kata" onclick="switchMode('kata', this)"><span class="tab-lbl-main">ア</span><span class="tab-lbl-sub">カタカナ</span></div>
<div class="tab t-lower" onclick="switchMode('lower', this)"><span class="tab-lbl-main">abc</span><span class="tab-lbl-sub">英小</span></div>
<div class="tab t-upper" onclick="switchMode('upper', this)"><span class="tab-lbl-main">ABC</span><span class="tab-lbl-sub">英大</span></div>
<div class="tab t-num" onclick="switchMode('num', this)"><span class="tab-lbl-main">数記</span><span class="tab-lbl-sub">半角</span></div>
<div class="tab t-sym" onclick="switchMode('sym', this)"><span class="tab-lbl-main">数記</span><span class="tab-lbl-sub">全角</span></div>
<div class="tab t-emoji" onclick="switchMode('emoji', this)"><span class="tab-lbl-main">😀</span><span class="tab-lbl-sub">絵文字</span></div>
</div>
<script>
const rainbowColors = ['#e60012','#f39800','#ffd900','#99cc00','#009944','#00a0e9','#004098','#4b0082','#800080','#e60033'];
const jpRows = [{id:'a',l:'あ',c:'row-a'},{id:'k',l:'か',c:'row-k'},{id:'s',l:'さ',c:'row-s'},{id:'t',l:'た',c:'row-t'},{id:'n',l:'な',c:'row-n'},{id:'h',l:'は',c:'row-h'},{id:'m',l:'ま',c:'row-m'},{id:'y',l:'や',c:'row-y'},{id:'r',l:'ら',c:'row-r'},{id:'w',l:'わ',c:'row-w'}];
const jpMap = {
'hira': {'a':['あ','ぁ','', 'い','ぃ','', 'う','ぅ','', 'え','ぇ','', 'お','ぉ',''], 'k':['か','が','', 'き','ぎ','', 'く','ぐ','', 'け','げ','', 'こ','ご',''], 's':['さ','ざ','', 'し','じ','', 'す','ず','', 'せ','ぜ','', 'そ','ぞ',''], 't':['た','だ','', 'ち','ぢ','', 'つ','づ','っ', 'て','で','', 'と','ど',''], 'n':['な','','', 'に','','', 'ぬ','','', 'ね','','', 'の','',''], 'h':['は','ば','ぱ', 'ひ','び','ぴ', 'ふ','ぶ','ぷ', 'へ','べ','ぺ', 'ほ','ぼ','ぽ'], 'm':['ま','','', 'み','','', 'む','','', 'め','','', 'も','',''], 'y':['や','ゃ','', '','','', 'ゆ','ゅ','', '','','', 'よ','ょ',''], 'r':['ら','','', 'り','','', 'る','','', 'れ','','', 'ろ','',''], 'w':['わ','ゎ','', 'を','','', 'ん','','', '','','', '','',''], 'sym':[' ', '\n', '、', '。', '「', '」', '(', ')']},
'kata': {'a':['ア','ァ','', 'イ','ィ','', 'ウ','ゥ','', 'エ','ェ','', 'オ','ォ',''], 'k':['カ','ガ','', 'キ','ギ','', 'ク','グ','', 'ケ','ゲ','', 'コ','ゴ',''], 's':['サ','ザ','', 'シ','ジ','', 'ス','ズ','', 'セ','ゼ','', 'ソ','ゾ',''], 't':['タ','ダ','', 'チ','ヂ','', 'ツ','ヅ','ッ', 'テ','デ','', 'ト','ド',''], 'n':['ナ','','', 'ニ','','', 'ヌ','','', 'ネ','','', 'ノ','',''], 'h':['ハ','バ','パ', 'ヒ','ビ','ピ', 'フ','ブ','プ', 'ヘ','ベ','ペ', 'ホ','ボ','ポ'], 'm':['マ','','', 'ミ','','', 'ム','','', 'メ','','', 'モ','',''], 'y':['ヤ','ャ','', '','','', 'ユ','ュ','', '','','', 'ヨ','ョ',''], 'r':['ラ','','', 'リ','','', 'ル','','', 'レ','','', 'ロ','',''], 'w':['ワ','ヮ','', 'ヲ','','', 'ン','','', '','','', '','',''], 'sym':[' ', '\n', '、', '。', '「', '」', '(', ')']}
};
const numCategories = [{id:'digits',l:'数字',src:"1234567890".split("")},{id:'math',l:'数式',src:"+-*/=<>".split("")},{id:'point',l:'点',src:".,:;".split("")},{id:'bra',l:'括弧',src:"()[]{}''\"\"".split("")},{id:'other',l:'その他',src:"%$#&@^|~`".split("")}];
const rawPages = {
'lower': [ "abcdefghi".split(""), "jklmnopqr".split(""), "stuvwxyz".split(""), ".,!?@_".split("") ],
'upper': [ "ABCDEFGHI".split(""), "JKLMNOPQR".split(""), "STUVWXYZ".split(""), ".,!?@_".split("") ]
};
const emojiData = [..."😀😁😂🤣😃😄😅😆😉😊😋😎😍😘🥰😗😙😚🙂🤗🤩🤔🤨😐😑😶🙄😏😣😥😮🤐😯😪😫😴😌😛😜😝🤤😒😓😔😕🙃🤑😲☹️🙁😖😞😟😤😢😭😦😧😨😩🤯😬😰😱🥵🥶😳🤪😵😡😠🤬😷🤒"];
const basicDictionary = [{k:'あ',v:['有る']},{k:'あい',v:['愛','AI']},{k:'か',v:['可','蚊']},{k:'かい',v:['貝','回','会']},{k:'きょう',v:['今日']},{k:'わたし',v:['私']},{k:'あした',v:['明日']}];
let userDictionary = {};
let currentMode = 'hira';
let currentSelection = 'a';
let composingText = "";
const inputEl = document.getElementById('input-line');
const candBar = document.getElementById('candidate-bar');
switchMode('hira', document.querySelector('.t-hira'));
inputEl.focus();
function switchMode(mode, tabEl) {
currentMode = mode;
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
if(tabEl) tabEl.classList.add('active');
const lp = document.getElementById('left-panel');
const rp = document.getElementById('right-panel');
lp.className = ""; rp.className = "";
if (mode !== 'hira' && mode !== 'kata') confirmComposition();
if (mode === 'hira' || mode === 'kata') {
lp.classList.add("jp-layout"); rp.classList.add("jp-layout"); currentSelection = 'a'; renderLeftJP(); renderRightJP();
} else if (mode === 'num' || mode === 'sym') {
currentSelection = 0; renderLeftCategories(); renderRightCategory();
} else if (mode === 'emoji') {
currentSelection = 0; renderLeftEmojiNav(); renderRightEmoji();
} else {
currentSelection = 0; renderLeftPages(mode); renderRightPage(mode);
}
}
function createKey(cls, text, onClick) {
const div = document.createElement('div');
div.className = cls + " no-focus-steal"; div.innerText = text;
div.onmousedown = (e) => e.preventDefault(); div.onclick = onClick; return div;
}
function renderLeftJP() {
const el = document.getElementById('left-panel'); el.innerHTML = "";
jpRows.forEach(row => {
const btn = createKey(`key key-left ${row.c}`, row.l, () => { currentSelection = row.id; renderLeftJP(); renderRightJP(); });
if(row.id === currentSelection) btn.classList.add('active'); el.appendChild(btn);
});
const symBtn = createKey("key key-left row-sym", "記", () => { currentSelection = 'sym'; renderLeftJP(); renderRightJP(); });
if(currentSelection === 'sym') symBtn.classList.add('active'); el.appendChild(symBtn);
el.appendChild(createKey("key key-left key-del", "削除", deleteChar));
}
function renderRightJP() {
const el = document.getElementById('right-panel'); el.innerHTML = "";
if(currentSelection === 'sym') el.className = 'sym-layout'; else el.className = 'jp-layout';
const map = (currentMode === 'kata') ? jpMap['kata'] : jpMap['hira'];
const chars = map[currentSelection] || [];
const activeL = document.querySelector('.key-left.active');
const bg = activeL ? window.getComputedStyle(activeL).backgroundColor : '#333';
const fg = activeL ? window.getComputedStyle(activeL).color : '#fff';
chars.forEach(char => {
let label = char; if(char === ' ') label = '空白'; if(char === '\n') label = '改行';
const btn = createKey("key key-right key-jp-char", label, () => {
if (currentSelection === 'sym') insertDirect(char); else insertComposing(char);
});
if(char || char === ' ' || char === '\n') {
btn.style.backgroundColor = bg; btn.style.color = fg; btn.style.filter = "brightness(0.9)";
} else {
btn.style.background = "transparent"; btn.style.boxShadow = "none"; btn.onclick = null; btn.classList.remove('key');
}
el.appendChild(btn);
});
}
function renderLeftPages(mode) {
const el = document.getElementById('left-panel'); el.innerHTML = "";
rawPages[mode].forEach((_, idx) => {
const btn = createKey("key key-cat", `p${idx+1}`, () => { currentSelection = idx; renderLeftPages(mode); renderRightPage(mode); });
if(idx === currentSelection) btn.classList.add('active'); el.appendChild(btn);
});
}
function renderRightPage(mode) {
const el = document.getElementById('right-panel'); el.innerHTML = "";
const pages = rawPages[mode]; const items = pages[currentSelection] || [];
let alphaStart = 0; for(let i=0; i<currentSelection; i++) alphaStart += pages[i].length;
let alphaCounter = alphaStart;
items.forEach(char => {
const btn = createKey("key key-char", char, () => insertDirect(char));
// 英字モードの最終ページ(p4)は記号なのでシルバー、それ以外はレインボー
if ((mode === 'lower' || mode === 'upper') && currentSelection === 3) {
btn.style.backgroundColor = "var(--col-silver)";
btn.style.color = "#000";
} else {
const cIdx = Math.floor(alphaCounter / 3) % rainbowColors.length;
const color = rainbowColors[cIdx];
btn.style.backgroundColor = color;
if(['#ffd900', '#99cc00', '#f39800', '#40e0d0'].includes(color)) btn.style.color = "#000";
alphaCounter++;
}
el.appendChild(btn);
});
// 最終ページの末尾に空白・削除追加
if (currentSelection === pages.length - 1) {
const spc = createKey("key key-char", "空白", () => insertDirect(" "));
spc.style.backgroundColor = "#777"; spc.style.fontSize="16px";
el.appendChild(spc);
const del = createKey("key key-char", "削除", deleteChar);
del.style.backgroundColor = "#d62828"; del.style.fontSize="16px";
el.appendChild(del);
}
}
function renderLeftCategories() {
const el = document.getElementById('left-panel'); el.innerHTML = "";
numCategories.forEach((cat, idx) => {
const btn = createKey("key key-cat", cat.l, () => { currentSelection = idx; renderLeftCategories(); renderRightCategory(); });
if(idx === currentSelection) btn.classList.add('active'); el.appendChild(btn);
});
}
function renderRightCategory() {
const el = document.getElementById('right-panel'); el.innerHTML = "";
const cat = numCategories[currentSelection];
cat.src.forEach(char => {
let val = char; let display = char;
if (currentMode === 'sym') { val = toFullWidth(char); display = val; }
const btn = createKey("key key-char", display, () => insertDirect(val));
btn.style.backgroundColor = "#d1f5fd"; btn.style.color = "#000"; btn.style.textShadow = "none";
el.appendChild(btn);
});
// その他のカテゴリの末尾に空白・削除
if (cat.id === 'other') {
const spc = createKey("key key-char", "空白", () => insertDirect(currentMode==='sym'?'\u3000':' '));
spc.style.backgroundColor = "#777"; spc.style.color="#fff"; spc.style.fontSize="16px"; spc.style.textShadow="none";
el.appendChild(spc);
const del = createKey("key key-char", "削除", deleteChar);
del.style.backgroundColor = "#d62828"; del.style.color="#fff"; del.style.fontSize="16px"; del.style.textShadow="none";
el.appendChild(del);
}
}
function renderLeftEmojiNav() {
const el = document.getElementById('left-panel'); el.innerHTML = "";
el.appendChild(createKey("key key-pager", "▲", () => { if(currentSelection > 0) { currentSelection--; renderRightEmoji(); } }));
el.appendChild(createKey("key key-pager", "▼", () => { const maxPage = Math.ceil(emojiData.length / 12) - 1; if(currentSelection < maxPage) { currentSelection++; renderRightEmoji(); } }));
}
function renderRightEmoji() {
const el = document.getElementById('right-panel'); el.innerHTML = "";
const items = emojiData.slice(currentSelection * 12, currentSelection * 12 + 12);
items.forEach(char => {
const btn = createKey("key key-char font-emoji", char, () => insertDirect(char));
btn.style.fontSize = "24px"; btn.style.backgroundColor = "#ffffff"; btn.style.color = "#000"; btn.style.textShadow = "none";
el.appendChild(btn);
});
}
// Logic
function insertComposing(char) { composingText += char; updateComposingDisplay(); updateCandidates(); }
function updateComposingDisplay() {
let span = inputEl.querySelector('.composing-span');
if (!span) { span = document.createElement('span'); span.className = 'composing-span'; insertNodeAtCursor(span); }
span.innerText = composingText; placeCaretAfter(span); updateLineNum();
}
function confirmComposition(textToCommit) {
if (textToCommit !== undefined) { composingText = textToCommit; if(userDictionary[textToCommit]) userDictionary[textToCommit]++; else userDictionary[textToCommit] = 1; }
if (!composingText) return;
let span = inputEl.querySelector('.composing-span');
if (span) { const textNode = document.createTextNode(composingText); span.parentNode.replaceChild(textNode, span); placeCaretAfter(textNode); } else { insertDirect(composingText); }
if (!textToCommit && composingText) { if(userDictionary[composingText]) userDictionary[composingText]++; else userDictionary[composingText] = 1; }
composingText = ""; candBar.innerHTML = ''; updateLineNum();
}
function insertDirect(text) { if (composingText) confirmComposition(); document.execCommand('insertText', false, text); updateLineNum(); }
function deleteChar() {
if (composingText) { composingText = composingText.slice(0, -1); if (!composingText) { let span = inputEl.querySelector('.composing-span'); if(span) span.remove(); candBar.innerHTML = ''; } else { updateComposingDisplay(); updateCandidates(); } } else { document.execCommand('delete'); } updateLineNum();
}
function insertNodeAtCursor(node) {
inputEl.focus(); const sel = window.getSelection();
if (sel.rangeCount > 0) { const range = sel.getRangeAt(0); range.deleteContents(); range.insertNode(node); } else { inputEl.appendChild(node); }
}
function placeCaretAfter(node) { const range = document.createRange(); range.setStartAfter(node); range.collapse(true); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); }
function toFullWidth(str) { return str.replace(/[!-~]/g, s => String.fromCharCode(s.charCodeAt(0) + 0xFEE0)).replace(/ /g, "\u3000"); }
function updateLineNum() { const text = inputEl.innerText; const lines = text.split(/\n/).length; document.getElementById('ln-num-text').innerText = lines; }
function handleInput() { updateLineNum(); }
function copyAll() { navigator.clipboard.writeText(inputEl.innerText).then(()=>alert("コピーしました")); }
async function pasteFromClipboard() { try { const text = await navigator.clipboard.readText(); insertDirect(text); } catch(e) {} }
function toggleFull() { document.getElementById('top-area').classList.toggle('fullscreen'); }
function moveLine(d) { inputEl.scrollTop += d * 30; }
function updateCandidates() {
candBar.innerHTML = ""; if (!composingText) return;
let candidates = [];
basicDictionary.filter(e => e.k.startsWith(composingText)).forEach(h => candidates = candidates.concat(h.v));
candidates.push(composingText);
if (currentMode === 'hira') candidates.push(composingText.replace(/[\u3041-\u3096]/g, c => String.fromCharCode(c.charCodeAt(0) + 0x60)));
candidates = [...new Set(candidates)];
candidates.sort((a, b) => (userDictionary[b]||0) - (userDictionary[a]||0));
candidates.forEach(c => {
const chip = document.createElement('div'); chip.className = 'cand-chip no-focus-steal'; chip.innerText = c;
chip.onmousedown = (e) => e.preventDefault(); chip.onclick = () => confirmComposition(c); candBar.appendChild(chip);
});
}
function searchWeb() { const t = inputEl.innerText; if(t) window.open(`https://www.google.com/search?q=${encodeURIComponent(t)}`, '_blank'); }
function askAI() { const t = inputEl.innerText; if(t) window.open(`https://chatgpt.com/?q=${encodeURIComponent(t)}`, '_blank'); }
</script>
</body>
</html>

コメント