Skip to content

Commit 8ecece0

Browse files
authored
Add files via upload
1 parent 9abd2e5 commit 8ecece0

1 file changed

Lines changed: 148 additions & 30 deletions

File tree

3code_mobile.html

Lines changed: 148 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
127127
margin-bottom: 5px;
128128
padding: 8px;
129-
height: 40vh;
129+
height: 20vh; /* Reduced from 40vh to 20vh */
130130
font-size: 1.5rem;
131131
line-height: 1.6;
132132
white-space: pre-wrap;
@@ -364,9 +364,23 @@
364364

365365
#keyboard-overlay.emoji button {
366366
box-sizing: border-box;
367-
float: left;
367+
/* 由於 flexbox 佈局,float: left 被移除 */
368368
text-align: center;
369369
font-size: 1.8rem;
370+
/* ADDED: 使其看起來像獨立的按鍵 */
371+
background-color: var(--key-bg); /* 使用鍵盤背景色 #f9f9f9 */
372+
border: 1px solid var(--border-color); /* 添加邊框 */
373+
border-radius: 6px; /* 圓角 */
374+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* 陰影效果 */
375+
margin: 2px; /* 增加微小間隔,讓按鍵更明顯 */
376+
}
377+
378+
/* ADDED: 點擊時的反白效果 */
379+
#keyboard-overlay.emoji button:active {
380+
background-color: var(--key-active-bg); /* 使用點擊時的顏色 #a0a0a0 */
381+
color: white; /* 讓表情符號更突出 */
382+
box-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
383+
transform: translateY(1px); /* 輕微下壓效果 */
370384
}
371385

372386
#keyboard-overlay.editor {
@@ -470,13 +484,19 @@
470484
const KEYCODE_DOWN=0xff54;
471485
const KEYCODE_DEL_F=0xff;
472486

473-
// --- 鍵盤配置 (略) ---
487+
// --- 鍵盤配置 ---
474488
var Keyboards = {
475489
english: {
476490
shifted : false,
477491
alt : {
492+
// 頂部兩排
478493
'q':'Q', 'w':'W', 'e':'E', 'r':'R', 't':'T', 'y':'Y', 'u':'U', 'i':'I', 'o':'O', 'p':'P',
479494
'a':'A', 's':'S', 'd':'D', 'f':'F', 'g':'G', 'h':'H', 'j':'J', 'k':'K', 'l':'L',
495+
496+
// **修正點:新增底部一排 (zxcvbnm) 的全形字元**
497+
'z':'Z', 'x':'X', 'c':'C', 'v':'V', 'b':'B', 'n':'N', 'm':'M',
498+
499+
// 標點符號
480500
';':';', // 全形
481501
'\'':'〃', // 全形
482502
',':',', // 全形
@@ -516,7 +536,7 @@
516536
[{v:'1'},{v:'2'},{v:'3'},{v:'4'},{v:'5'},
517537
{v:'6'},{v:'7'},{v:'8'},{v:'9'},{v:'0'}],
518538
[{v:'@'},{v:'#'},{v:'$'},{v:'%'},{v:'&'},
519-
{v:'*'},{v:'-'},{v:'+'},{v:'('},{v:')'}],
539+
{v:'*'},{v:'-'},{v:'+'},{v:'('},{v:'('}],
520540
[{v:'⇪',c:KEYCODE_SHIFT,r:1.5,s:true},
521541
{v:'!'},{v:'"'},{v:'\''},{v:':'},{v:';'},{v:'/'},{v:'?'},
522542
{v:'⌫',c:KEYCODE_DEL,r:1.5,s:true}],
@@ -730,30 +750,43 @@
730750

731751
if (show === false) return;
732752

733-
// 取得尺寸
734-
const cand = document.getElementById('candidate-display');
753+
// --- 修正點: 取得鍵盤的實際位置和尺寸,使其覆蓋整個按鍵區 (#keyboard) ---
735754
const keyboard = document.getElementById('keyboard');
736-
const width = keyboard.clientWidth;
737-
// 浮層高度 = 鍵盤總高度 - 候選字列高度
738-
const height = keyboard.clientHeight - cand.clientHeight;
755+
const rect = keyboard.getBoundingClientRect(); // 取得鍵盤元素在視窗中的位置和尺寸
756+
757+
const width = rect.width;
758+
const height = rect.height; // 取得按鍵區的實際高度
739759

740760
// 配置浮層 div
741761
div.dataset.show = show;
742762
div.style.display = 'block';
743-
div.style.position = 'absolute'; // 確保能定位
744-
div.style.left = '0px';
745-
div.style.top = cand.clientHeight + 'px';
763+
div.className = type; // 應用 class for styling
764+
765+
// 確保浮層與鍵盤位置重合,使用 fixed 定位來精確覆蓋鍵盤 (#keyboard)
766+
div.style.position = 'fixed';
767+
div.style.left = rect.left + 'px';
768+
div.style.top = rect.top + 'px'; // 從鍵盤頂部開始
746769
div.style.width = width + 'px';
747770
div.style.height = height + 'px';
748771

749772
const div2 = document.createElement('DIV');
750773
div2.style.width = width + 'px';
751774
div2.style.height = height + 'px';
752775
div2.style.overflowY = 'auto'; // 讓內容可滾動
776+
777+
// --- FIX: Add Flexbox setup for 10-column layout ---
778+
if (type === "emoji") {
779+
div2.style.display = 'flex';
780+
div2.style.flexWrap = 'wrap';
781+
div2.style.alignContent = 'flex-start';
782+
// 調整 padding 以容納按鍵間距
783+
div2.style.padding = '3px';
784+
}
785+
// --- END FIX ---
753786

754787
div.innerHTML = ''; // 清空內容
755788
div.appendChild(div2);
756-
div.className = type; // 應用 class for styling
789+
// --- 結束修正點 ---
757790

758791
if (type === "emoji") {
759792
const emojis = [[
@@ -775,37 +808,48 @@
775808
"🎁","🎂","🎄","🎅","🎈","🎓","👌","👍","👎","👏",
776809
"🎨","🎯","🎲","🏀","🏃","🏅","🏆","🏐","🏓","🏟",
777810
"🏧","🐀","🐁","🐂","🐃","🐄","🐅","🐆","🐇","🐈",
778-
"🐉","🐊","🐋","🐌","🐍","🐎","🐏","🐐","🐑","🐒",
811+
"🐉","🐊","🐋","🐌","","🐎","🐏","🐐","🐑","🐒",
779812
"🐓","🐔","🐕","🐖","🐗","🐘","🐙","🐚","🐛","🐜",
780813
"🐝","🐞","🐟","🐠","🐢","🐧","👣","👫","💊","💋"
781814
]];
782815

783816
const emojiSet = emojis[show] || emojis[0];
784817
const rowLength = 10;
785-
const keyWidth = width / rowLength;
818+
const displayRows = 4; // <--- 固定顯示 4 行
819+
820+
// 由於 CSS 樣式新增了 2px margin,這裡需要調整寬度計算
821+
// 為了簡單起見並使用 flexbox,我們設定按鈕寬度為 10%
786822

787-
// 計算每行有多少按鈕,然後估算每個按鈕的高度
788-
const numRows = Math.ceil(emojiSet.length / rowLength);
789-
const keyHeight = height / (numRows > 0 ? numRows : 1);
823+
const keyHeight = height / displayRows; // <--- 按鈕高度為浮層高度的 1/4
790824

791825
for (let i = 0; i < emojiSet.length; i++) {
792826
const one = document.createElement('button');
793827
one.textContent = emojiSet[i];
794-
one.style.width = keyWidth + "px";
795-
one.style.height = keyHeight + "px";
828+
829+
// --- FIX: 使用百分比寬度和計算高度確保 10 欄精確排列 ---
830+
one.style.width = `calc(${100 / rowLength}% - 4px)`; /* 10% 減去左右 margin (2px*2) */
831+
one.style.height = keyHeight - 4 + "px"; /* 高度減去上下 margin (2px*2) */
832+
796833
one.style.fontSize = '1.8rem';
797-
one.style.border = 'none';
798-
one.style.background = 'none';
799-
one.style.margin = '0';
800-
one.style.padding = '0';
834+
// 移除內聯樣式,讓 CSS 來控制按鍵外觀
835+
// one.style.border = 'none';
836+
// one.style.background = 'none';
837+
// one.style.margin = '0';
838+
// one.style.padding = '0';
801839
div2.appendChild(one);
802840
}
803841

804842
div2.addEventListener('click', function(e) {
843+
// 確保點擊到的是 button
844+
if (e.target.tagName !== 'BUTTON') return;
845+
805846
const text = e.target.textContent;
806847
// 過濾掉可能存在的空白佔位符
807848
if (text && text.length > 0 && text !== ' ')
808849
commitText(text);
850+
851+
// **修正 1:點擊後不再隱藏浮層,以滿足連續輸入的需求**
852+
// overlay("emoji", false); // <--- 已移除此行
809853
});
810854

811855
} else if (type === "editor") {
@@ -844,6 +888,75 @@
844888
overlay("editor", 0);
845889
}
846890

891+
// --- ADDED: 語音輸入邏輯 ---
892+
893+
/**
894+
* 啟動 Web Speech API 語音辨識
895+
*/
896+
function startVoiceInput() {
897+
// 檢查瀏覽器是否支援 Web Speech API
898+
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
899+
900+
if (!SpeechRecognition) {
901+
alert('抱歉,您的瀏覽器不支援 Web 語音輸入功能 (Speech Recognition API)。建議使用 Chrome 瀏覽器。');
902+
return;
903+
}
904+
905+
// 建立 Recognition 物件
906+
const recognition = new SpeechRecognition();
907+
908+
// 設定:
909+
recognition.lang = 'cmn-Hant-TW'; // 繁體中文
910+
recognition.continuous = false; // 每次只錄製一次
911+
recognition.interimResults = false; // 只傳回最終結果
912+
913+
// 儲存原始 UI 狀態
914+
const originalMainInput = mainInput.textContent;
915+
916+
// 處理事件
917+
recognition.onstart = function() {
918+
mainInput.textContent = '🔊 正在聆聽... 請開始說話。';
919+
candidateDisplay.innerHTML = '<span style="color: red; font-weight: bold;">[語音辨識中...]</span>';
920+
};
921+
922+
recognition.onresult = function(event) {
923+
// 取得最終結果
924+
const transcript = event.results[event.results.length - 1][0].transcript;
925+
926+
// 將結果提交到輸出區
927+
if (transcript) {
928+
commitText(transcript);
929+
}
930+
931+
// 恢復 UI 狀態
932+
recognition.onend();
933+
};
934+
935+
recognition.onerror = function(event) {
936+
alert('語音辨識發生錯誤: ' + event.error + ' (請檢查麥克風權限)');
937+
console.error('Speech Recognition Error:', event.error);
938+
// 恢復 UI 狀態
939+
recognition.onend();
940+
};
941+
942+
recognition.onend = function() {
943+
// 恢復 UI 狀態
944+
mainInput.textContent = originalMainInput;
945+
// 重新繪製工具列
946+
showCandidates();
947+
};
948+
949+
try {
950+
recognition.start();
951+
} catch (e) {
952+
alert('啟動語音輸入失敗,請檢查瀏覽器麥克風權限設定。');
953+
console.error('Recognition start failed:', e);
954+
// 恢復 UI 狀態
955+
mainInput.textContent = originalMainInput;
956+
showCandidates();
957+
}
958+
}
959+
847960
// --- ADDED: 工具列邏輯 ---
848961

849962
function showToolbar() {
@@ -874,7 +987,7 @@
874987
function handleToolbarClick(target) {
875988
const tool = target.dataset.tool;
876989
if (tool === "1") {
877-
console.log('Voice input triggered (Not implemented).');
990+
startVoiceInput(); // <-- 呼叫新的語音輸入函式
878991
} else if (tool === "2") {
879992
overlay("emoji", 0);
880993
} else if (tool === "3") {
@@ -942,8 +1055,11 @@
9421055
*/
9431056
function handleFunctionKey(code, keyChar) {
9441057
// 確保在處理功能鍵時隱藏浮層
945-
overlayDiv.style.display = 'none';
946-
delete overlayDiv.dataset.show;
1058+
// **注意:如果用戶在 emoji 浮層上點擊了功能鍵(例如切換到 QWERTY),則需要關閉浮層。**
1059+
if (code === KEYCODE_SWITCH_english || code === KEYCODE_SWITCH_number || code === KEYCODE_STATE || code === KEYCODE_DEL || code === KEYCODE_ENTER || code === KEYCODE_SPACE) {
1060+
overlayDiv.style.display = 'none';
1061+
delete overlayDiv.dataset.show;
1062+
}
9471063

9481064
switch(code) {
9491065
case KEYCODE_SHIFT:
@@ -1358,14 +1474,16 @@
13581474
}
13591475

13601476
/**
1361-
* 顯示候選字或工具列 (修正:確保無編碼時顯示工具列,而不是模式訊息)
1477+
* 顯示候選字或工具列
1478+
* **修正 2:確保浮層開啟時,工具列(表情符號切換/關閉鍵)依然顯示。**
13621479
*/
13631480
function showCandidates() {
13641481
candidateDisplay.innerHTML = '';
13651482

1366-
// 1. 如果浮層開啟,優先處理浮層,清空顯示區
1483+
// 1. 如果浮層開啟 (Emoji/Editor)
13671484
if (overlayDiv.dataset.show) {
1368-
candidateDisplay.textContent = '';
1485+
// 修正:浮層開啟時,必須顯示工具列,讓用戶可以切換頁面或關閉浮層。
1486+
showToolbar();
13691487
return;
13701488
}
13711489

0 commit comments

Comments
 (0)