| Both sides previous revisionPrevious revision | |
| font:keyboard [2026/03/22 23:33] – wikarai | font:keyboard [2026/03/25 01:11] (current) – external edit A User Not Logged in |
|---|
| ===== Yivalese Keyboard ===== | ===== Yivalese Keyboard ===== |
| An interactive Yivalese typing keyboard. Click keys to compose text in the typing area, switch font styles to preview different writing periods, and use the export tools to embed the keyboard on any webpage. Enable //Edit Mode// to reassign or rearrange keys. | An interactive Yivalese typing keyboard. Click keys to compose text in the typing area, switch font styles to preview different writing periods, and use the export tools to embed the keyboard on any webpage. |
| ---- | ---- |
| <html> | <html> |
| <label><input type="radio" name="fontStyle" value="italic" onclick="updateYivDisplay()"> Ancient</label> | <label><input type="radio" name="fontStyle" value="italic" onclick="updateYivDisplay()"> Ancient</label> |
| <label><input type="radio" name="fontStyle" value="regular" onclick="updateYivDisplay()"> Present</label> | <label><input type="radio" name="fontStyle" value="regular" onclick="updateYivDisplay()"> Present</label> |
| <label><input type="radio" name="fontStyle" value="bold" checked onclick="updateYivDisplay()"> Future</label> | <label><input type="radio" name="fontStyle" value="bold" checked onclick="updateYivDisplay()"> Modern</label> |
| </div> | </div> |
| </div> | </div> |
| </div> | </div> |
| |
| <div class="yiv-tips-section"> | <div id="diac-popup" class="diac-popup"></div> |
| <p class="yiv-desc">Type Yivalese by clicking the keys above. Each key shows two glyphs — unshifted (left) and shifted (right). The font selector switches between writing periods. Enable Edit Mode to reassign or rearrange keys via drag-and-drop.</p> | |
| <div class="yiv-tips-grid"> | <div class="yiv-case-opts"> |
| <div class="yiv-tip">💡 Double-click <span class="yiv-font yiv-tip-glyph">XY</span> to hold shift on (Caps Lock).</div> | <label><input type="checkbox" id="caseAutoReturnChk" onchange="caseAutoReturn=this.checked" checked> Auto-return to middle case</label> |
| <div class="yiv-tip">🔡 Toggle <span class="yiv-font yiv-tip-glyph">BB</span> / <span class="yiv-font yiv-tip-glyph">Bb</span> / <span class="yiv-font yiv-tip-glyph">bb</span> to preview the whole keyboard and type in upper, title, or lower case.</div> | <label><input type="checkbox" id="forceCaseTyping" checked> Manually type in selected case only</label> |
| <div class="yiv-tip">🛠️ In <b>Edit Mode</b>: click a key then a glyph from the grid to reassign. Drag keys to rearrange (centre = swap, edge = insert).</div> | |
| <div class="yiv-tip">📋 Use <b>View / Edit ASCII</b> below to save, share, or import a custom layout as a portable text snippet.</div> | |
| </div> | |
| <div class="yiv-action-bar"> | |
| <button class="yiv-btn" onclick="toggleAsciiPanel()">📋 View / Edit ASCII</button> | |
| <button class="yiv-btn yiv-btn-export" onclick="openExportDialog()">💾 Export to HTML</button> | |
| </div> | |
| </div> | </div> |
| |
| <div id="asciiPanel" class="pop-output-section" style="display:none;"> | <div class="yiv-tips-section"> |
| <h3 style="color:#e5cd9e; margin:0 0 6px 0;">ASCII Layout <span style="font-size:12px; color:#888; font-weight:normal;">— editable snapshot of the current keyboard</span></h3> | <p class="yiv-desc">Type Yivalese by clicking the keys above. Each key shows two glyphs – unshifted (left) and shifted (right). The font selector switches between writing periods.</p> |
| <p style="font-size:12px; color:#888; margin:0 0 10px 0;">Edit glyph codes in brackets, then click <b>Import</b> to apply. Format: <code>[LeftGlyph RightGlyph]</code>. Special-key markers (<code>S_*</code>) are ignored on import.</p> | <ul class="yiv-tips-list"> |
| <textarea id="asciiOutput" spellcheck="false" style="width:100%; height:170px; background:#111; color:#4a9eff; font-family:monospace; font-size:12px; padding:10px; border:1px solid #444; box-sizing:border-box; resize:vertical; border-radius:4px;"></textarea> | <li>Double-click <span class="yiv-font yiv-tip-glyph">XY</span> to hold shift on (caps lock).</li> |
| <div style="margin-top:10px; display:flex; gap:10px; flex-wrap:wrap;"> | <li>Click the left / middle / right third of <span class="yiv-font yiv-tip-glyph">BB</span> | <span class="yiv-font yiv-tip-glyph">Bb</span> | <span class="yiv-font yiv-tip-glyph">bb</span> to select upper, title, or lower case. Double-click a third to lock it (shown in blue).</li> |
| <button class="yiv-btn" onclick="importAscii()">⬆️ Import ASCII</button> | </ul> |
| <button class="yiv-btn" onclick="copyAscii()">📋 Copy ASCII</button> | |
| <button class="yiv-btn" onclick="clearKeyboard()" style="background:#383838; border-color:#555;">🔄 Reset Layout</button> | |
| </div> | |
| </div> | </div> |
| |
| <div id="exportDialog" class="yiv-modal-overlay" style="display:none;" onclick="if(event.target===this)closeExportDialog()"> | <details id="asciiDetails" class="yiv-details" open> |
| <div class="yiv-modal-box"> | <summary class="yiv-summary">📋 Import / Export as ASCII</summary> |
| <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;"> | <div class="yiv-details-body"> |
| <h3 style="margin:0; color:#e5cd9e;">Export Keyboard to HTML</h3> | <p style="font-size:12px; color:#888; margin:0 0 10px 0; text-align:center;">Edit glyph codes in brackets, then click <b>Import</b> to apply. Format: <code>[LeftGlyph RightGlyph]</code>. Special-key markers (<code>S_*</code>) are ignored on import.</p> |
| <button onclick="closeExportDialog()" style="background:none; border:none; color:#aaa; font-size:22px; cursor:pointer; line-height:1; padding:0;">✕</button> | <textarea id="asciiOutput" spellcheck="false" style="width:100%; max-width:100%; height:170px; background:#111; color:#4a9eff; font-family:monospace; font-size:12px; padding:10px; border:1px solid #444; box-sizing:border-box; resize:vertical; border-radius:4px; white-space:pre; overflow-x:auto; display:block; margin:0 auto; text-align:left;"></textarea> |
| | <div style="margin-top:10px; display:flex; gap:10px; flex-wrap:wrap; justify-content:center;"> |
| | <button class="yiv-btn" onclick="importAscii()">⬆️ Import ASCII</button> |
| | <button class="yiv-btn" onclick="copyAscii()">📋 Copy ASCII</button> |
| | <button class="yiv-btn" onclick="clearKeyboard()" style="background:#383838; border-color:#555;">🔄 Reset Layout</button> |
| </div> | </div> |
| | </div> |
| | </details> |
| | |
| | <details id="exportDetails" class="yiv-details" open> |
| | <summary class="yiv-summary">💾 Export to HTML</summary> |
| | <div class="yiv-details-body"> |
| <div style="display:flex; gap:10px; margin-bottom:16px; flex-wrap:wrap;"> | <div style="display:flex; gap:10px; margin-bottom:16px; flex-wrap:wrap;"> |
| <label class="yiv-export-opt"> | <label class="yiv-export-opt"> |
| <input type="radio" name="exportMode" value="barebones" checked> | <input type="radio" name="exportMode" value="barebones" onchange="generateExport()"> |
| <div><strong>Barebones</strong><br><small>Clean B&W standalone HTML file. Simple typing demo, easy to embed anywhere.</small></div> | <div><strong>Barebones</strong><br><small>Compact standalone HTML file. Balanced sizing, easy to embed anywhere.</small></div> |
| </label> | </label> |
| <label class="yiv-export-opt"> | <label class="yiv-export-opt"> |
| <input type="radio" name="exportMode" value="minimalist"> | <input type="radio" name="exportMode" value="minimalist" onchange="generateExport()"> |
| <div><strong>Minimalist</strong><br><small>Compact dark PDA-style keyboard. Ultra-light, same layout, tiny keys.</small></div> | <div><strong>Minimalist</strong><br><small>Ultra-compact PDA-style keyboard. Micro sizing for tiny embeds.</small></div> |
| </label> | </label> |
| <label class="yiv-export-opt"> | <label class="yiv-export-opt"> |
| <input type="radio" name="exportMode" value="popup"> | <input type="radio" name="exportMode" value="popup" checked onchange="generateExport()"> |
| <div><strong>Popup Keyboard</strong><br><small>HTML fragment — paste into any page. Keyboard appears as an overlay popup. Change target by editing <code>yivFontEntry</code> in the script.</small></div> | <div><strong>Popup Keyboard</strong><br><small>Paste into any page. Keyboard appears as an overlay popup. Change target by editing <code>yivFontEntry</code> in the script.</small></div> |
| </label> | </label> |
| </div> | </div> |
| <button class="yiv-btn" style="margin-bottom:14px;" onclick="generateExport()">⚙️ Generate Code</button> | <div class="yiv-theme-row"> |
| <textarea id="htmlSnippet" readonly spellcheck="false" style="width:100%; height:220px; background:#111; color:#8cd98c; font-family:monospace; font-size:11px; padding:10px; border:1px solid #444; box-sizing:border-box; resize:vertical; border-radius:4px;"></textarea> | <span style="color:#aaa;font-size:12px;">Theme:</span> |
| <div style="margin-top:10px; display:flex; gap:10px; flex-wrap:wrap;"> | <button class="yiv-theme-btn active" data-theme="leather" onclick="setTheme('leather')">Leather</button> |
| <button class="yiv-btn" onclick="copySnippet()">📋 Copy Code</button> | <button class="yiv-theme-btn" data-theme="clean" onclick="setTheme('clean')">Clean</button> |
| <button class="yiv-btn" onclick="closeExportDialog()" style="background:#383838; border-color:#555;">Close</button> | <button class="yiv-theme-btn" data-theme="noir" onclick="setTheme('noir')">Noir</button> |
| | <button class="yiv-theme-btn" data-theme="bone" onclick="setTheme('bone')">Bone</button> |
| | <div class="yiv-more-wrap"> |
| | <button class="yiv-theme-btn" id="yiv-more-btn" onclick="toggleMoreMenu(event)">More ▾</button> |
| | <div id="yiv-more-menu" class="yiv-more-menu" style="display:none;"></div> |
| | </div> |
| | <span style="color:#aaa;font-size:12px;margin-left:10px;">Era:</span> |
| | <button class="yiv-era-btn" data-era="ancient" onclick="setEra('ancient')">Ancient</button> |
| | <button class="yiv-era-btn" data-era="present" onclick="setEra('present')">Present</button> |
| | <button class="yiv-era-btn active" data-era="modern" onclick="setEra('modern')">Modern</button> |
| | <button class="yiv-era-btn" data-era="choice" onclick="setEra('choice')">Choice</button> |
| | </div> |
| | <div class="yiv-preview-wrap"> |
| | <p class="yiv-preview-label">👁 Live Preview:</p> |
| | <iframe id="yiv-preview-frame" class="yiv-preview-frame" srcdoc=""></iframe> |
| | </div> |
| | <div class="yiv-code-wrap"> |
| | <div class="yiv-code-head"> |
| | <p class="yiv-preview-label"></> Generated Code:</p> |
| | <div class="yiv-code-head-actions"> |
| | <label class="yiv-code-toggle"><input id="minifyCodeToggle" type="checkbox" checked onchange="renderCodeView()"> minified</label> |
| | <button id="copySnippetBtn" class="yiv-btn yiv-copy-btn" type="button" onclick="copySnippet()">📋 Copy Code</button> |
| | </div> |
| | </div> |
| | <pre id="htmlSnippetPretty" class="yiv-code-block"><code id="htmlSnippetCode"></code></pre> |
| | <textarea id="htmlSnippet" readonly spellcheck="false" style="display:none;"></textarea> |
| </div> | </div> |
| </div> | </div> |
| </div> | </details> |
| |
| </div> | </div> |
| |
| <style> | <style> |
| /* Font Declarations */ | |
| @font-face { | @font-face { |
| font-family: "YzWrFont"; | font-family: "YzWrFont"; |
| src: url("https://crow.work/src/font/YzWr-Regular.woff2") format("woff2"), | src: url("https://crow.work/src/font/YzWr-Regular.woff2") format("woff2"), |
| url("https://crow.work/src/font/YzWr-Regular.woff") format("woff"); | url("https://crow.work/src/font/YzWr-Regular.woff") format("woff"), |
| | url("https://crow.work/src/font/YzWr-Regular.ttf") format("truetype"); |
| font-weight: normal; font-style: normal; font-display: swap; | font-weight: normal; font-style: normal; font-display: swap; |
| } | } |
| font-family: "YzWr-Italic"; | font-family: "YzWr-Italic"; |
| src: url("https://crow.work/src/font/YzWr-Italic.woff2") format("woff2"), | src: url("https://crow.work/src/font/YzWr-Italic.woff2") format("woff2"), |
| url("https://crow.work/src/font/YzWr-Italic.woff") format("woff"); | url("https://crow.work/src/font/YzWr-Italic.woff") format("woff"), |
| | url("https://crow.work/src/font/YzWr-Italic.ttf") format("truetype"); |
| font-weight: normal; font-style: normal; font-display: swap; | font-weight: normal; font-style: normal; font-display: swap; |
| } | } |
| font-family: "YzWrBoldFont"; | font-family: "YzWrBoldFont"; |
| src: url("https://crow.work/src/font/YzWr-Bold.woff2") format("woff2"), | src: url("https://crow.work/src/font/YzWr-Bold.woff2") format("woff2"), |
| url("https://crow.work/src/font/YzWr-Bold.woff") format("woff"); | url("https://crow.work/src/font/YzWr-Bold.woff") format("woff"), |
| font-weight: bold; font-style: normal; font-display: swap; | url("https://crow.work/src/font/YzWr-Bold.ttf") format("truetype"); |
| | font-weight: normal; font-style: normal; font-display: swap; |
| } | } |
| /* Base Yiv font */ | |
| .yiv-font, .key-side { font-family: "YzWrFont", sans-serif; } | .yiv-font, .key-side { font-family: "YzWrFont", sans-serif; } |
| /* Font mode toggles */ | |
| .italic-mode .yiv-font, .italic-mode .key-side { font-family: "YzWr-Italic", sans-serif !important; font-style: normal; } | .italic-mode .yiv-font, .italic-mode .key-side { font-family: "YzWr-Italic", sans-serif !important; font-style: normal; } |
| .bold-mode .yiv-font, .bold-mode .key-side { font-family: "YzWrBoldFont", sans-serif !important; font-weight: bold; } | .bold-mode .yiv-font, .bold-mode .key-side { font-family: "YzWrBoldFont", sans-serif !important; font-weight: bold; } |
| /* Case transforms */ | .upper-mode .kb-container .key-side { text-transform: uppercase; } |
| .upper-mode .yiv-font, .upper-mode .key-side { text-transform: uppercase; } | .lower-mode .kb-container .key-side { text-transform: lowercase; } |
| .lower-mode .yiv-font, .lower-mode .key-side { text-transform: lowercase; } | .yiv-container { width: 100%; margin: 0 auto; color: inherit; font-family: sans-serif; position: relative; overflow: visible; } |
| /* Global layout */ | |
| .yiv-container { width: 100%; margin: 0 auto; color: inherit; font-family: sans-serif; } | |
| .yiv-controls-panel { | .yiv-controls-panel { |
| display: flex; justify-content: center; gap: 30px; margin-bottom: 18px; | display: flex; justify-content: center; gap: 30px; margin-bottom: 18px; |
| .yiv-control-group { display: flex; gap: 10px; align-items: center; font-size: 13px; color: #ddd; } | .yiv-control-group { display: flex; gap: 10px; align-items: center; font-size: 13px; color: #ddd; } |
| .populator-wrapper { display: flex; flex-direction: row; flex-wrap: wrap; gap: 40px; justify-content: center; margin-bottom: 20px; } | .populator-wrapper { display: flex; flex-direction: row; flex-wrap: wrap; gap: 40px; justify-content: center; margin-bottom: 20px; } |
| .pop-kb-wrapper { display: flex; justify-content: center; } | .pop-kb-wrapper { display: flex; justify-content: center; overflow-x: auto; overflow-y: visible; -webkit-overflow-scrolling: touch; padding: 4px 0 8px; width: 100%; position: relative; z-index: 2; } |
| /* Typing area */ | .typing-wrapper { width: 100%; max-width: 700px; margin: 0 auto 22px auto; position: relative; z-index: 1; } |
| .typing-wrapper { width: 100%; max-width: 700px; margin: 0 auto 22px auto; } | |
| .type-area-container { | .type-area-container { |
| position: relative; background: #181818; border: 2px solid #3a2012; | position: relative; background: #181818; border: 2px solid #3a2012; |
| #typeArea { | #typeArea { |
| min-height: 70px; font-size: 34px; color: #e5cd9e; text-align: center; | min-height: 70px; font-size: 34px; color: #e5cd9e; text-align: center; |
| display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; | display: block; outline: none; white-space: pre-wrap; line-height: 1.4; word-break: break-word; |
| align-items: center; outline: none; white-space: pre-wrap; line-height: 1.3; | |
| } | } |
| #copyBtn { position: absolute; top: 8px; right: 12px; background: none; border: none; color: #666; font-family: monospace; cursor: pointer; font-size: 13px; transition: color 0.2s; } | #copyBtn { position: absolute; top: 8px; right: 12px; background: none; border: none; color: #666; font-family: monospace; cursor: pointer; font-size: 13px; transition: color 0.2s; } |
| #copyBtn:hover { color: #aaa; } | #copyBtn:hover { color: #aaa; } |
| #copyBtn.success { color: #4ade80 !important; } | #copyBtn.success { color: #4ade80 !important; } |
| /* Glyph grid */ | |
| .pop-table { border-collapse: collapse; background: #2a2a2a; color: #eee; } | .pop-table { border-collapse: collapse; background: #2a2a2a; color: #eee; } |
| .pop-table td, .pop-table th { border: 1px solid #444; padding: 7px 9px; text-align: center; cursor: pointer; } | .pop-table td, .pop-table th { border: 1px solid #444; padding: 7px 9px; text-align: center; cursor: pointer; } |
| .pop-table td:hover { background: #4a9eff; color: white; } | .pop-table td:hover { background: #4a9eff; color: white; } |
| .pop-table .yiv-font { font-size: 20px; color: #4a9eff; pointer-events: none; } | .pop-table .yiv-font { font-size: 20px; color: #4a9eff; pointer-events: none; } |
| /* Keyboard container */ | |
| .kb-container { | .kb-container { |
| background: #b8a598; padding: 18px; border-radius: 10px; | background: #b8a598; padding: 19px; border-radius: 10px; |
| display: inline-flex; flex-direction: column; gap: 6px; | display: inline-flex; flex-direction: column; gap: 6px; |
| border: 3px solid #8c7365; transition: border-color 0.3s; | border: 3px solid #8c7365; transition: border-color 0.3s; transform-origin: top center; |
| } | } |
| .edit-active .kb-container { border-color: #4a9eff; box-shadow: 0 0 20px rgba(74,158,255,0.2); } | .edit-active .kb-container { border-color: #4a9eff; box-shadow: 0 0 20px rgba(74,158,255,0.2); } |
| /* Diamond staircase: key=42px gap=6px unit=48px half=24px. Peak at row 2. */ | |
| .kb-row { display: flex; gap: 6px; align-items: center; justify-content: flex-start; } | .kb-row { display: flex; gap: 6px; align-items: center; justify-content: flex-start; } |
| .kb-row-0 { margin-left: 0; } | .kb-row-0 { margin-left: 0; } |
| .kb-row-1 { margin-left: 24px; } | .kb-row-1 { margin-left: 25px; } |
| .kb-row-2 { margin-left: 48px; } | .kb-row-2 { margin-left: 50px; } |
| .kb-row-3 { margin-left: 24px; } | .kb-row-3 { margin-left: 25px; } |
| .kb-row-4 { margin-left: 0; } | .kb-row-4 { margin-left: 0; } |
| /* Keys (70% of 60px = 42px) */ | |
| .pop-key { | .pop-key { |
| background: #3a2012; color: #e5cd9e; border: 2px solid #241107; | background: #3a2012; color: #e5cd9e; border: 2px solid #241107; |
| border-radius: 6px; height: 42px; width: 42px; | border-radius: 6px; height: 44px; width: 44px; |
| display: flex; align-items: center; justify-content: space-around; | display: flex; align-items: center; justify-content: space-around; |
| cursor: pointer; user-select: none; position: relative; flex-shrink: 0; | cursor: pointer; user-select: none; position: relative; flex-shrink: 0; |
| .pop-key.gap { visibility: hidden; pointer-events: none; } | .pop-key.gap { visibility: hidden; pointer-events: none; } |
| .edit-active .pop-key.active-target { border-color: #4a9eff; box-shadow: 0 0 15px #4a9eff, inset 0 0 5px rgba(74,158,255,0.5); } | .edit-active .pop-key.active-target { border-color: #4a9eff; box-shadow: 0 0 15px #4a9eff, inset 0 0 5px rgba(74,158,255,0.5); } |
| /* Key size variants: w2 = 2*42+6 = 90px; w3 = 3*42+12 = 138px */ | .key-w2 { width: 94px; } |
| .key-w2 { width: 90px; } | .key-w3 { width: 144px; } |
| .key-w3 { width: 138px; } | .key-side { width: 45%; text-align: center; pointer-events: none; font-size: 22px; line-height: 1; } |
| /* Key content */ | .key-side.empty { opacity: 0.15; font-family: sans-serif !important; font-size: 11px; } |
| .key-side { width: 45%; text-align: center; pointer-events: none; font-size: 15px; line-height: 1; } | .key-shift { justify-content: flex-end; padding-right: 12px; } |
| .key-side.empty { opacity: 0.15; font-family: sans-serif !important; font-size: 9px; } | .key-shift .yiv-font { font-size: 24px; color: #888; } |
| /* Special keys */ | |
| .key-shift { justify-content: flex-end; padding-right: 10px; } | |
| .key-shift .yiv-font { font-size: 13px; color: #888; } | |
| .shift-active .yiv-font { color: #4a9eff !important; } | .shift-active .yiv-font { color: #4a9eff !important; } |
| .shift-locked { background: #4a9eff !important; border-color: #2b7bc4 !important; } | .shift-locked { background: #4a9eff !important; border-color: #2b7bc4 !important; } |
| .shift-locked .yiv-font { color: white !important; } | .shift-locked .yiv-font { color: white !important; } |
| .key-newline { font-size: 28px; color: #999; justify-content: center; } | .key-newline { font-size: 26px; color: #999; justify-content: center; } |
| .key-newline span { font-family: "Times New Roman", serif; font-style: italic; } | .key-newline span { font-family: "Times New Roman", serif; font-style: italic; } |
| .key-space { font-size: 16px; color: #777; justify-content: center; } | .key-space { font-size: 18px; color: #777; justify-content: center; } |
| .key-case { background: #222; border-color: #111; gap: 3px; justify-content: center !important; padding: 0 3px; } | .key-case { width: 94px; background: #3a2012; border-color: #241107; gap: 3px; justify-content: center !important; padding: 0 3px; } |
| .case-lbl { font-size: 10px; padding: 1px 3px; border-radius: 3px; color: #555; line-height: 1.2; } | .case-lbl { font-size: 20px; padding: 1px 3px; border-radius: 3px; color: #c8b07e; line-height: 1.1; } |
| .case-lbl.active { color: #e5cd9e; border: 1px solid #666; background: rgba(255,255,255,0.1); } | .case-lbl.active { color: #e5cd9e; border: 1px solid #666; background: rgba(255,255,255,0.1); } |
| /* Drag indicators */ | .case-lbl.locked-case { color: #4a9eff !important; border: 1px solid #4a9eff !important; background: rgba(74,158,255,0.18) !important; } |
| | .case-sep { color: #8c7365; font-size: 12px; font-family: sans-serif !important; line-height: 1; } |
| | .key-dot { position: relative; overflow: visible; } |
| | .diac-hotzone { |
| | position: absolute; top: 0; right: 0; bottom: 0; width: 22%; |
| | cursor: pointer; z-index: 5; |
| | } |
| | .diac-triangle { |
| | position: absolute; top: 3px; right: 3px; width: 0; height: 0; |
| | border-left: 5px solid transparent; border-right: 5px solid transparent; |
| | border-bottom: 7px solid #8c7365; |
| | pointer-events: none; |
| | } |
| | .diac-popup { |
| | display: none; position: fixed; top: 0; left: 0; |
| | background: #2a1a0e; border: 1px solid #8c7365; border-radius: 8px; padding: 6px; |
| | z-index: 10000; box-shadow: 0 8px 24px rgba(0,0,0,0.7); white-space: normal; |
| | transform: translate3d(-9999px,-9999px,0); |
| | } |
| | .diac-popup.open { |
| | display: grid; grid-template-columns: minmax(165px, 1fr) minmax(165px, 1fr); |
| | gap: 4px 6px; min-width: 340px; max-width: 420px; max-height: min(70vh, 520px); overflow: auto; |
| | } |
| | .diac-popup-head { color: #8c7365; font-size: 9px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.06em; padding: 2px 4px 4px; font-family: sans-serif !important; } |
| | .diac-popup-sep { border: none; border-top: 1px solid #3a2a1e; margin: 3px 0; grid-column: 1 / -1; } |
| | .diac-popup-head { grid-column: 1 / -1; } |
| | .diac-popup-note { grid-column: 1 / -1; color: #9b8f84; font-size: 9px; line-height: 1.35; padding: 1px 4px 2px; white-space: pre-line; } |
| | .diac-btn { |
| | background: #3a2012; color: #e5cd9e; border: 1px solid #5c3a26; |
| | border-radius: 4px; padding: 4px 8px; cursor: pointer; |
| | font-family: sans-serif !important; white-space: normal; line-height: 1.4; |
| | display: flex; align-items: center; gap: 8px; text-align: left; position: relative; |
| | min-width: 0; |
| | } |
| | .diac-btn:hover { background: #5c3a26; } |
| | .diac-btn.active-diac { border-color: #4a9eff; background: #1a2a3a; } |
| | .diac-lbl { min-width: 28px; text-align: center; font-size: 20px; font-weight: normal; color: #c8b07e; font-family: "YzWrFont", sans-serif !important; line-height: 1; display: inline-flex; align-items: center; justify-content: center; gap: 0; } |
| | .diac-lbl-char { font: inherit; } |
| | .diac-lbl-sep { font-family: sans-serif !important; font-size: 16px; line-height: 1; color: #8c7365; padding: 0 1px; } |
| | .italic-mode .diac-lbl { font-family: "YzWr-Italic", sans-serif !important; font-style: normal; } |
| | .bold-mode .diac-lbl { font-family: "YzWrBoldFont", sans-serif !important; font-weight: bold; } |
| | .diac-copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; } |
| | .diac-short { font-size: 10px; color: #8c7365; } |
| | .diac-desc { display: none; font-size: 10px; color: #b7a694; line-height: 1.3; } |
| | .diac-btn[data-tip]:hover::after { |
| | content: attr(data-tip); position: absolute; top: 0; |
| | background: #1a0a04; border: 1px solid #5c3a26; border-radius: 4px; |
| | padding: 4px 8px; color: #e5cd9e; font-size: 10px; white-space: normal; max-width: 240px; |
| | z-index: 9999; pointer-events: none; box-shadow: 0 2px 8px rgba(0,0,0,0.5); |
| | } |
| | .diac-btn.diac-tip-left[data-tip]:hover::after { left: calc(100% + 6px); right: auto; } |
| | .diac-btn.diac-tip-right[data-tip]:hover::after { right: calc(100% + 6px); left: auto; } |
| .drag-over-center { background: #4a9eff !important; color: white !important; } | .drag-over-center { background: #4a9eff !important; color: white !important; } |
| .drag-over-left::before { content: ''; position: absolute; left: -5px; top: 0; bottom: 0; width: 3px; background: #4a9eff; border-radius: 2px; } | .drag-over-left::before { content: ''; position: absolute; left: -5px; top: 0; bottom: 0; width: 3px; background: #4a9eff; border-radius: 2px; } |
| .drag-over-right::after { content: ''; position: absolute; right: -5px; top: 0; bottom: 0; width: 3px; background: #4a9eff; border-radius: 2px; } | .drag-over-right::after { content: ''; position: absolute; right: -5px; top: 0; bottom: 0; width: 3px; background: #4a9eff; border-radius: 2px; } |
| /* Output panel */ | .yiv-case-opts { |
| .pop-output-section { margin-top: 16px; width: 100%; background: #1e1e1e; padding: 18px 20px; border-radius: 8px; box-sizing: border-box; } | display: flex; align-items: center; gap: 20px; flex-wrap: wrap; |
| /* Tips section */ | justify-content: center; font-size: 12px; color: #888; padding: 6px 4px 12px; |
| .yiv-tips-section { margin-top: 18px; padding: 14px 16px; background: #1a1a1a; border-radius: 8px; border: 1px solid #2a2a2a; } | } |
| .yiv-desc { color: #999; font-size: 12px; margin: 0 0 12px 0; text-align: center; line-height: 1.5; } | .yiv-case-opts label { display: flex; align-items: center; gap: 5px; cursor: pointer; } |
| .yiv-tips-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 14px; } | .yiv-tips-section { margin-top: 4px; padding: 14px 16px; background: #1a1a1a; border-radius: 8px; border: 1px solid #2a2a2a; } |
| @media (max-width: 600px) { .yiv-tips-grid { grid-template-columns: 1fr; } } | .yiv-desc { color: #999; font-size: 12px; margin: 0 0 10px 0; text-align: center; line-height: 1.5; } |
| .yiv-tip { font-size: 12px; color: #aaa; padding: 7px 10px; background: #222; border-radius: 6px; line-height: 1.5; } | .yiv-tips-list { color: #aaa; font-size: 12px; margin: 0; padding-left: 18px; line-height: 1.8; } |
| .yiv-tip-glyph { font-size: 11px; vertical-align: middle; color: #c8b07e; } | .yiv-tip-glyph { font-size: 18px; vertical-align: middle; color: #c8b07e; } |
| .yiv-action-bar { display: flex; gap: 10px; flex-wrap: wrap; } | .yiv-details { margin-top: 10px; background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px; overflow: visible; } |
| | .yiv-summary { cursor: pointer; padding: 11px 16px; color: #c8b07e; font-size: 13px; font-weight: bold; list-style: none; user-select: none; } |
| | .yiv-summary::-webkit-details-marker { display: none; } |
| | .yiv-summary::marker { display: none; } |
| | .yiv-summary:hover { background: #222; } |
| | .yiv-details[open] .yiv-summary { border-bottom: 1px solid #2a2a2a; } |
| | .yiv-details-body { padding: 16px; overflow: visible; } |
| .yiv-btn { padding: 7px 14px; background: #3a2012; color: #e5cd9e; border: 1px solid #5c3a26; cursor: pointer; border-radius: 5px; font-size: 13px; transition: background 0.2s; } | .yiv-btn { padding: 7px 14px; background: #3a2012; color: #e5cd9e; border: 1px solid #5c3a26; cursor: pointer; border-radius: 5px; font-size: 13px; transition: background 0.2s; } |
| .yiv-btn:hover { background: #5c3a26; } | .yiv-btn:hover { background: #5c3a26; } |
| .yiv-btn-export { background: #1a3a1a; border-color: #2a6a2a; color: #8cd98c; } | .yiv-btn-wide { width: 100%; max-width: 760px; display: inline-flex; justify-content: center; font-size: 15px; padding: 10px 16px; } |
| .yiv-btn-export:hover { background: #265a26; } | |
| /* Export modal */ | |
| .yiv-modal-overlay { | |
| position: fixed; top: 0; left: 0; width: 100%; height: 100%; | |
| background: rgba(0,0,0,0.82); z-index: 9999; | |
| display: flex; justify-content: center; align-items: center; padding: 20px; box-sizing: border-box; | |
| } | |
| .yiv-modal-box { background: #1e1e1e; border: 1px solid #444; border-radius: 10px; padding: 22px; max-width: 680px; width: 100%; max-height: 90vh; overflow-y: auto; } | |
| .yiv-export-opt { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; padding: 10px; background: #2a2a2a; border-radius: 6px; border: 2px solid #333; flex: 1; min-width: 150px; transition: border-color 0.2s; } | .yiv-export-opt { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; padding: 10px; background: #2a2a2a; border-radius: 6px; border: 2px solid #333; flex: 1; min-width: 150px; transition: border-color 0.2s; } |
| .yiv-export-opt:hover { border-color: #555; } | .yiv-export-opt:hover { border-color: #555; } |
| .yiv-export-opt small { color: #888; font-size: 11px; display: block; margin-top: 2px; } | .yiv-export-opt small { color: #888; font-size: 11px; display: block; margin-top: 2px; } |
| .yiv-export-opt code { background: #1a1a1a; padding: 1px 4px; border-radius: 3px; font-size: 10px; color: #8cd98c; } | .yiv-export-opt code { background: #1a1a1a; padding: 1px 4px; border-radius: 3px; font-size: 10px; color: #8cd98c; } |
| | .yiv-theme-row { display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; text-align: center; } |
| | .yiv-theme-btn { padding: 4px 11px; background: #2a2a2a; color: #aaa; border: 1px solid #444; cursor: pointer; border-radius: 4px; font-size: 12px; transition: background 0.15s; } |
| | .yiv-theme-btn:hover { background: #333; color: #ddd; } |
| | .yiv-theme-btn.active { background: #3a2012; color: #e5cd9e; border-color: #8c7365; } |
| | .yiv-era-btn { padding: 4px 11px; background: #23262d; color: #aab4c8; border: 1px solid #3e4758; cursor: pointer; border-radius: 4px; font-size: 12px; transition: background 0.15s; } |
| | .yiv-era-btn:hover { background: #2d3340; color: #d6deef; } |
| | .yiv-era-btn.active { background: #1f2f48; color: #c7dcff; border-color: #4d79b8; } |
| | .yiv-more-wrap { position: relative; display: inline-block; } |
| | .yiv-more-menu { |
| | position: absolute; top: calc(100% + 4px); left: 0; |
| | background: #1a1a1a; border: 1px solid #333; border-radius: 6px; |
| | padding: 4px 0; z-index: 1200; min-width: 270px; max-height: 290px; |
| | overflow-y: auto; box-shadow: 0 4px 16px rgba(0,0,0,0.7); |
| | } |
| | .yiv-more-item { |
| | display: block; width: 100%; text-align: left; |
| | padding: 7px 13px; background: none; border: none; cursor: pointer; |
| | color: #ccc; font-size: 12px; line-height: 1.4; box-sizing: border-box; |
| | } |
| | .yiv-more-item:hover { background: #252525; } |
| | .yiv-more-item strong { color: #e5cd9e; display: block; font-size: 12px; } |
| | .yiv-more-item span { color: #666; font-size: 10px; } |
| | .yiv-more-item.active { background: #2a1a0e; } |
| | .yiv-more-item.active strong { color: #c8a870; } |
| | .yiv-preview-wrap { margin-top: 14px; } |
| | .yiv-preview-label { color: #888; font-size: 11px; margin: 0 0 6px; } |
| | .yiv-preview-frame { width: 100%; height: 320px; border: 1px solid #333; border-radius: 6px; display: block; } |
| | .yiv-code-wrap { margin-top: 14px; } |
| | .yiv-code-head { |
| | display: flex; |
| | align-items: center; |
| | justify-content: space-between; |
| | gap: 10px; |
| | flex-wrap: wrap; |
| | margin-bottom: 6px; |
| | } |
| | .yiv-code-head .yiv-preview-label { margin-bottom: 0; } |
| | .yiv-code-head-actions { |
| | display: flex; |
| | align-items: center; |
| | justify-content: flex-end; |
| | gap: 10px; |
| | flex-wrap: wrap; |
| | } |
| | .yiv-code-toggle { |
| | display: inline-flex; |
| | align-items: center; |
| | gap: 6px; |
| | color: #9aa8bb; |
| | font-size: 11px; |
| | cursor: pointer; |
| | } |
| | .yiv-copy-btn { |
| | min-width: 154px; |
| | justify-content: center; |
| | padding-inline: 18px; |
| | white-space: nowrap; |
| | } |
| | .yiv-copy-btn.is-copied { |
| | background: #1f2f48; |
| | border-color: #4a9eff; |
| | color: #d9e7ff; |
| | } |
| | .yiv-code-block { |
| | margin: 0; |
| | max-height: 340px; |
| | overflow: auto; |
| | background: #11161d; |
| | border: 1px solid #253041; |
| | border-radius: 6px; |
| | padding: 12px; |
| | font-size: 12px; |
| | line-height: 1.55; |
| | color: #c5d3e5; |
| | } |
| | .yiv-code-block code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; white-space: pre; } |
| | .yiv-code-block span { box-shadow: none !important; } |
| | .tok-tag { color: #8ec07c; } |
| | .tok-attr { color: #7fb2ff; } |
| | .tok-str { color: #9ad4ff; } |
| | .tok-js-key { color: #f2a87f; } |
| | .tok-js-cmt { color: #6f8198; } |
| | @media (max-width: 760px) { |
| | .yiv-btn-wide { max-width: 100%; } |
| | } |
| | @media (max-width: 640px) { |
| | .diac-popup.open { grid-template-columns: 1fr; min-width: 0; max-width: min(320px, calc(100vw - 16px)); max-height: min(72vh, 460px); } |
| | .diac-btn { |
| | align-items: flex-start; |
| | padding: 7px 9px; |
| | } |
| | .diac-desc { display: block; } |
| | .diac-btn[data-tip]:hover::after { display: none; } |
| | .yiv-code-head { align-items: flex-start; } |
| | .yiv-code-head-actions { width: 100%; justify-content: space-between; } |
| | .yiv-copy-btn { min-width: 0; flex: 1 1 180px; } |
| | } |
| </style> | </style> |
| |
| "BB BY; BD GN; WY WN; [XX XB]; 4 14; 3 13; 2 12; [XD XN]; BX LG; GL WG; BG BN", | "BB BY; BD GN; WY WN; [XX XB]; 4 14; 3 13; 2 12; [XD XN]; BX LG; GL WG; BG BN", |
| "DY ND; DN NG; WX WB; [DW DD]; 5 15; BLANK; 1 11; [NY YN]; NW BW; LL BL; DX GX", | "DY ND; DN NG; WX WB; [DW DD]; 5 15; BLANK; 1 11; [NY YN]; NW BW; LL BL; DX GX", |
| "GW YD; YL YG; [XY XW]; 6 16; 7 17; 0 10; [XL NN]; LW LY; GD GG; NB LN", | "GW YD; YL YG; LX WL; [XY XW]; 6 16; 7 17; 0 10; [XL NN]; LW LY; GD GG; NB LN", |
| "WD DL; XG NL; DG NX; [YY YB]; @ 20; ? 40; # 60; [YW YX]; WW LD; DB LB; GB GY" | "WD DL; XG NL; DG NX; [YY YB]; @ 20; ? 40; # 60; [YW YX]; WW LD; DB LB; GB GY" |
| ]; | ]; |
| ]; | ]; |
| |
| var kbState = JSON.parse(sessionStorage.getItem('yivTypewriter')) || []; | var kbState = JSON.parse(sessionStorage.getItem('yivTypewriter')) || []; |
| var isEditMode = false; | var isEditMode = false; |
| var activeKeyId = null; | var activeKeyId = null; |
| var activeSide = 'left'; | var activeSide = 'left'; |
| var shiftMode = 0; | var shiftMode = 0; |
| var lastShiftTap= 0; | var lastShiftTap = 0; |
| var caseMode = 0; | var caseMode = 0; |
| | var caseLocked = false; |
| | var caseAutoReturn = true; |
| | var lastCaseTap = 0; |
| | var lastCaseThird = -1; |
| | var diacMode = '.'; |
| | var currentTheme = 'leather'; |
| | var currentEra = 'modern'; |
| | var yivInitialized = false; |
| | var casePendingChars = 0; |
| | var diacAnchorId = null; |
| | |
| | function keyTypeIsSpecial(k) { |
| | return !!(k && typeof k.type === 'string' && k.type.indexOf('s_') === 0); |
| | } |
| | |
| | var DIACRITICS = [ |
| | {label:'.', val:'.', short:'signi./logo./-end', desc:'Meaningful character, logographic character, or phrase end.'}, |
| | {label:',', val:',', short:'dot (alt key)', desc:'Alternate key for the meaningful/logographic/phrase-end mark.'}, |
| | {label:'..', val:'..', short:'redup./--end/pause', desc:'Reduplicated character, longer phrase end, or pause.'}, |
| | {label:':', val:':', short:'dbl-dot (alt key)', desc:'Alternate key for the reduplication/pause mark.'}, |
| | {label:'...', val:';', short:'int. tone/x-end/--pause', desc:'Intentional tone mark, abrupt end, long pause, and the like.'}, |
| | {label:'a', val:'a', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'e', val:'e', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'i', val:'i', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'o', val:'o', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'u', val:'u', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'m', val:'m', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'q/r', val:'q', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'f/s/c', val:'f', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'p/t/k', val:'p', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'v/z/j', val:'v', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'}, |
| | {label:'h', val:'h', short:'sound helper', desc:'Especially useful in obscure or old-fashioned written words.'} |
| | ]; |
| | |
| | function formatDiacLabel(label) { |
| | var parts = String(label).split('/'); |
| | if (parts.length === 1) return '<span class="diac-lbl-char">'+escapeHtml(label)+'</span>'; |
| | return parts.map(function(part, idx) { |
| | var seg = '<span class="diac-lbl-char">'+escapeHtml(part)+'</span>'; |
| | if (idx < parts.length - 1) seg += '<span class="diac-lbl-sep">/</span>'; |
| | return seg; |
| | }).join(''); |
| | } |
| | |
| | var THEMES = { |
| | leather: {name:'Leather', desc:'Classic warm tooled leather', bodyBg:'#2a1a0e',kbBg:'#b8a598',kbBdr:'#8c7365',keyBg:'#3a2012',keyBdr:'#241107',keyFg:'#e5cd9e',specFg:'#8c7365',areaBg:'#1a0e06',areaFg:'#e5cd9e',areaBdr:'#3a2012',headFg:'#c8a870'}, |
| | clean: {name:'Clean', desc:'Light minimalist white', bodyBg:'#f7f6f2',kbBg:'#e0ddd8',kbBdr:'#c0b8a8',keyBg:'#fafaf8',keyBdr:'#ccc', keyFg:'#333', specFg:'#888', areaBg:'#fff', areaFg:'#333', areaBdr:'#ddd', headFg:'#333'}, |
| | noir: {name:'Noir', desc:'Dark sleek monochrome', bodyBg:'#111', kbBg:'#222', kbBdr:'#444', keyBg:'#1a1a1a',keyBdr:'#333', keyFg:'#e0e0e0',specFg:'#666', areaBg:'#0a0a0a',areaFg:'#e0e0e0',areaBdr:'#333', headFg:'#ddd'}, |
| | bone: {name:'Bone', desc:'Ivory cream antiquarian', bodyBg:'#f5efe0',kbBg:'#e8dfc8',kbBdr:'#c8b89a',keyBg:'#f0e8d8',keyBdr:'#c0a880',keyFg:'#4a3020',specFg:'#9a8060',areaBg:'#faf6ee',areaFg:'#4a3020',areaBdr:'#c0a880',headFg:'#5a3a20'}, |
| | first_alni:{name:'The First Alni', desc:'Reed and Clay – earliest sun-baked tablets', bodyBg:'#A65E44',kbBg:'#D4B886',kbBdr:'#8B4513',keyBg:'#8B4513',keyBdr:'#6a340f',keyFg:'#F5F0E8',specFg:'#6a340f',areaBg:'#6a340f',areaFg:'#F5F0E8',areaBdr:'#8B4513',headFg:'#3B2F2F'}, |
| | quarry: {name:'Nalmiktokh Quarry', desc:'Limestone – foundation of their architecture', bodyBg:'#E3E0D5',kbBg:'#8C9296',kbBdr:'#6a6e72',keyBg:'#E3E0D5',keyBdr:'#9a9e9f',keyFg:'#1A1A1A',specFg:'#666', areaBg:'#f0eee8',areaFg:'#1A1A1A',areaBdr:'#9a9e9f',headFg:'#1A1A1A'}, |
| | gearwork: {name:'Luuspar Gearwork', desc:'Brass and Wood – early industrialization', bodyBg:'#5C4033',kbBg:'#8B6914',kbBdr:'#5c4510',keyBg:'#3a2a1a',keyBdr:'#2a1a0a',keyFg:'#d4be78',specFg:'#2E473B',areaBg:'#2a1a0a',areaFg:'#d4be78',areaBdr:'#5c4510',headFg:'#d4be78'}, |
| | harvest: {name:"Tuskekhad's Sieve", desc:'Wicker and roasted bean – harvest life', bodyBg:'#C29B62',kbBg:'#8B4513',kbBdr:'#5c2e0c',keyBg:'#5c2e0c',keyBdr:'#3a1c08',keyFg:'#F5DEB3',specFg:'#C29B62',areaBg:'#3a1c08',areaFg:'#F5DEB3',areaBdr:'#8B4513',headFg:'#3a1c08'}, |
| | tide: {name:'Iikshani Tide', desc:'Nautical – port town sea glass and drift-wood', bodyBg:'#4A5D6E',kbBg:'#2a3d4e',kbBdr:'#1a2d3e',keyBg:'#1a2d3e',keyBdr:'#0a1d2e',keyFg:'#E0F7FA',specFg:'#6a8a9a',areaBg:'#0B253A',areaFg:'#E0F7FA',areaBdr:'#2a3d4e',headFg:'#E0F7FA'}, |
| | loom: {name:"Moyil's Loom", desc:'Textile and Wool – woven in wine-dyed thread', bodyBg:'#D8CFC4',kbBg:'#6B4226',kbBdr:'#4a2a16',keyBg:'#4a2a16',keyBdr:'#2a1a0a',keyFg:'#D8CFC4',specFg:'#9a6040',areaBg:'#f5f0e8',areaFg:'#722F37',areaBdr:'#6B4226',headFg:'#722F37'}, |
| | hearth: {name:'Skurol Hearth', desc:'Ceramic and Ash – domestic fireside', bodyBg:'#3a4c18',kbBg:'#4a5c28',kbBdr:'#556B2F',keyBg:'#CD853F',keyBdr:'#a06028',keyFg:'#2C2C2C',specFg:'#3a4c18',areaBg:'#2C2C2C',areaFg:'#CD853F',areaBdr:'#3a4c18',headFg:'#CD853F'}, |
| | anvil: {name:"The Smith's Anvil", desc:'Tin and Iron – heavy forge work', bodyBg:'#737A80',kbBg:'#4A4A4A',kbBdr:'#2a2a2a',keyBg:'#2a2a2a',keyBdr:'#1a1a1a',keyFg:'#B0B8C0',specFg:'#737A80',areaBg:'#1a1a1a',areaFg:'#B85D19',areaBdr:'#4A4A4A',headFg:'#B85D19'}, |
| | embers: {name:'Aaskirakoh', desc:'Campfire Embers – charred log and spark yellow', bodyBg:'#1E1E1E',kbBg:'#2a1a1a',kbBdr:'#8A3324',keyBg:'#8A3324',keyBdr:'#6a2314',keyFg:'#FFD700',specFg:'#8A3324',areaBg:'#0a0a0a',areaFg:'#FFD700',areaBdr:'#8A3324',headFg:'#FFD700'}, |
| | night_sky: {name:'Nanoyar Watcher', desc:'Night Sky – constellation-like lettering', bodyBg:'#0F172A',kbBg:'#1e2a3a',kbBdr:'#334155',keyBg:'#334155',keyBdr:'#1e2a3a',keyFg:'#E2E8F0',specFg:'#94a3b8',areaBg:'#0F172A',areaFg:'#E2E8F0',areaBdr:'#334155',headFg:'#E2E8F0'}, |
| | ancestor: {name:"The Ancestor's Stylus", desc:'Bone and Obsidian – ritual recording of the dead', bodyBg:'#EAE6D7',kbBg:'#222', kbBdr:'#111', keyBg:'#111', keyBdr:'#000', keyFg:'#EAE6D7',specFg:'#555', areaBg:'#EAE6D7',areaFg:'#8B0000',areaBdr:'#333', headFg:'#8B0000'}, |
| | gold: {name:"Desim's Decree", desc:"Ivory and Gold – the Queen's hyper-luxurious machine", bodyBg:'#FFFFF0',kbBg:'#D4AF37',kbBdr:'#a08020',keyBg:'#a08020',keyBdr:'#806010',keyFg:'#FFFFF0',specFg:'#4B0082',areaBg:'#FFFFF0',areaFg:'#4B0082',areaBdr:'#D4AF37',headFg:'#4B0082'}, |
| | eclipse: {name:'Delnotur Eclipse', desc:'Blood Moon – lunar mysticism and Yivalkes magic', bodyBg:'#2B0000',kbBg:'#500000',kbBdr:'#800000',keyBg:'#800000',keyBdr:'#600000',keyFg:'#FFB6C1',specFg:'#400000',areaBg:'#1a0000',areaFg:'#FFB6C1',areaBdr:'#800000',headFg:'#FFB6C1'}, |
| | cavern: {name:'Nemfisa Cavern', desc:'Bioluminescence – underground fireflies and mushrooms',bodyBg:'#0A0A0A',kbBg:'#111', kbBdr:'#1a2a1a',keyBg:'#0A1A0A',keyBdr:'#0a2a0a',keyFg:'#00FF7F',specFg:'#007a3a',areaBg:'#050505',areaFg:'#00FF7F',areaBdr:'#0a2a0a',headFg:'#00FF7F'}, |
| | frost: {name:'Ushikakh Winter', desc:'Frost and Glass – frozen wasteland', bodyBg:'#B0E0E6',kbBg:'#E6E6FA',kbBdr:'#c0c0d8',keyBg:'#f0f0ff',keyBdr:'#c0c0d8',keyFg:'#000080',specFg:'#a0a0c0',areaBg:'#E6E6FA',areaFg:'#000080',areaBdr:'#c0c0d8',headFg:'#000080'} |
| | }; |
| | |
| | var MORE_THEME_IDS = ['first_alni','quarry','gearwork','harvest','tide','loom','hearth','anvil','embers','night_sky','ancestor','gold','eclipse','cavern','frost']; |
| | |
| | function clampByte(n) { |
| | return Math.max(0, Math.min(255, n | 0)); |
| | } |
| | |
| | function parseHexColor(hex) { |
| | if (!hex || typeof hex !== 'string') return null; |
| | var v = hex.trim().replace('#', ''); |
| | if (v.length === 3) v = v.split('').map(function(c){ return c + c; }).join(''); |
| | if (!/^[0-9a-fA-F]{6}$/.test(v)) return null; |
| | return { |
| | r: parseInt(v.slice(0, 2), 16), |
| | g: parseInt(v.slice(2, 4), 16), |
| | b: parseInt(v.slice(4, 6), 16) |
| | }; |
| | } |
| | |
| | function rgbToHex(rgb) { |
| | return '#' + [rgb.r, rgb.g, rgb.b].map(function(v){ |
| | return clampByte(v).toString(16).padStart(2, '0'); |
| | }).join(''); |
| | } |
| | |
| | function shiftHex(hex, pct) { |
| | var rgb = parseHexColor(hex); |
| | if (!rgb) return hex; |
| | var delta = Math.round(255 * (pct / 100)); |
| | return rgbToHex({ r: rgb.r + delta, g: rgb.g + delta, b: rgb.b + delta }); |
| | } |
| | |
| | function luminance(hex) { |
| | var rgb = parseHexColor(hex); |
| | if (!rgb) return 0.5; |
| | function ch(v) { |
| | var c = v / 255; |
| | return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); |
| | } |
| | return (0.2126 * ch(rgb.r)) + (0.7152 * ch(rgb.g)) + (0.0722 * ch(rgb.b)); |
| | } |
| | |
| | function contrastRatio(fg, bg) { |
| | var l1 = luminance(fg), l2 = luminance(bg); |
| | var high = Math.max(l1, l2), low = Math.min(l1, l2); |
| | return (high + 0.05) / (low + 0.05); |
| | } |
| | |
| | function readableFg(bg, preferred, fallback) { |
| | if (contrastRatio(preferred, bg) >= 4.2) return preferred; |
| | if (contrastRatio(fallback, bg) >= 4.2) return fallback; |
| | return contrastRatio('#f5f5f5', bg) >= contrastRatio('#111111', bg) ? '#f5f5f5' : '#111111'; |
| | } |
| | |
| | function getThemeForExport(id) { |
| | var base = THEMES[id] || THEMES.leather; |
| | var t = Object.assign({}, base); |
| | var isLight = luminance(base.keyBg) > 0.58 || luminance(base.kbBg) > 0.64; |
| | if (id === 'leather') { |
| | t.homeBg = shiftHex(base.keyBg, 10); |
| | t.homeBdr = shiftHex(base.keyBdr, 8); |
| | t.specBg = base.keyBg; |
| | t.specBdr = base.keyBdr; |
| | } else { |
| | t.homeBg = shiftHex(base.keyBg, isLight ? -12 : 10); |
| | t.homeBdr = shiftHex(base.keyBdr, isLight ? -14 : 8); |
| | t.specBg = shiftHex(base.keyBg, isLight ? -9 : -6); |
| | t.specBdr = shiftHex(base.keyBdr, isLight ? -12 : -8); |
| | } |
| | if (id === 'gearwork') { |
| | t.specBg = shiftHex(base.keyBg, -3); |
| | t.specBdr = shiftHex(base.keyBdr, 4); |
| | } |
| | t.specFg = readableFg(t.specBg, base.specFg || base.keyFg, base.keyFg); |
| | return t; |
| | } |
| |
| function init() { | function init() { |
| | if (yivInitialized) return; |
| | yivInitialized = true; |
| if (kbState.length === 0) parseDefaultStrings(); | if (kbState.length === 0) parseDefaultStrings(); |
| | normalizeHomeMarkers(); |
| | caseMode = 1; |
| if (kbState[4] && kbState[4][0] && kbState[4][0].type === 's_dot') kbState[4][0].width = 2; | if (kbState[4] && kbState[4][0] && kbState[4][0].type === 's_dot') kbState[4][0].width = 2; |
| buildKeyboard(); | buildKeyboard(); |
| updateYivDisplay(); | updateYivDisplay(); |
| updateCaseDisplay(); | updateCaseDisplay(); |
| | populateMoreMenu(); |
| | generateExport(); |
| | fitKeyboard(); |
| | fitAsciiBox(); |
| | window.addEventListener('resize', function() { |
| | fitKeyboard(); |
| | fitAsciiBox(); |
| | positionDiacPopup(); |
| | }); |
| | window.addEventListener('scroll', positionDiacPopup, true); |
| | var asciiDetails = document.getElementById('asciiDetails'); |
| | if (asciiDetails) { |
| | asciiDetails.addEventListener('toggle', function() { |
| | if (this.open) updateAsciiOutput(); |
| | }); |
| | } |
| | var exportDetails = document.getElementById('exportDetails'); |
| | if (exportDetails) { |
| | exportDetails.addEventListener('toggle', function() { |
| | if (this.open) generateExport(); |
| | }); |
| | } |
| | setEra('modern'); |
| | document.addEventListener('click', function() { closeDiacPopup(); closeMoreMenu(); }); |
| | document.getElementById('typeArea').addEventListener('keydown', function(e) { |
| | var fc = document.getElementById('forceCaseTyping'); |
| | if (!fc || !fc.checked) return; |
| | if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) { |
| | e.preventDefault(); |
| | typeCharacter(e.key, 'manual'); |
| | } |
| | }); |
| | } |
| | |
| | function normalizeHomeMarkers() { |
| | var homeMap = { '0:3':1, '0:7':1, '1:3':1, '1:7':1, '2:3':1, '2:7':1, '3:3':1, '3:7':1 }; |
| | kbState.forEach(function(row, r) { |
| | row.forEach(function(k, i) { |
| | if (!k || typeof k !== 'object') return; |
| | if (k.type === 'y' || keyTypeIsSpecial(k)) { |
| | k.home = false; |
| | } else { |
| | k.home = !!homeMap[r+':'+i]; |
| | } |
| | }); |
| | }); |
| } | } |
| |
| var rowData = r < 4 ? initStrings[r].split('; ') : []; | var rowData = r < 4 ? initStrings[r].split('; ') : []; |
| return rowSchema.map(function(item, k) { | return rowSchema.map(function(item, k) { |
| var key = { | var key = { id:'r'+r+'k'+k, type: typeof item==='string'?item:item.type, width:item.w||1, left:'..', right:'..', home:false }; |
| id: 'r' + r + 'k' + k, | |
| type: typeof item === 'string' ? item : item.type, | |
| width: item.w || 1, | |
| left: '..', right: '..', home: false | |
| }; | |
| if (r < 4) { | if (r < 4) { |
| var raw = rowData[k] || '.. ..'; | var raw = rowData[k] || '.. ..'; |
| if (raw.indexOf('[') !== -1) { key.home = true; raw = raw.replace(/[\[\]]/g, ''); } | if (raw.indexOf('[') !== -1) { key.home = true; raw = raw.replace(/[\[\]]/g, ''); } |
| if (raw === 'BLANK') { key.type = 'y'; } | if (raw === 'BLANK') { key.type = 'y'; } |
| else { var pts = raw.split(' '); key.left = pts[0] || '..'; key.right = pts[1] || '..'; } | else { var pts = raw.split(' '); key.left = pts[0]||'..'; key.right = pts[1]||'..'; } |
| } else { | } else { |
| if (key.type === 's_dot') key.left = '.'; | if (key.type === 's_dot') key.left = '.'; |
| activeKeyId = null; | activeKeyId = null; |
| } | } |
| | closeDiacPopup(); |
| buildKeyboard(); | buildKeyboard(); |
| | fitKeyboard(); |
| } | } |
| |
| rowDiv.className = 'kb-row kb-row-' + r; | rowDiv.className = 'kb-row kb-row-' + r; |
| row.forEach(function(k) { | row.forEach(function(k) { |
| var keyDiv = document.createElement('div'); | var kd = document.createElement('div'); |
| keyDiv.className = 'pop-key'; | kd.className = 'pop-key'; |
| if (k.home) keyDiv.classList.add('home-marker'); | if (k.home) kd.classList.add('home-marker'); |
| if (k.type === 'y') keyDiv.classList.add('gap'); | if (k.type === 'y') kd.classList.add('gap'); |
| if (k.width === 2) keyDiv.classList.add('key-w2'); | if (k.width === 2) kd.classList.add('key-w2'); |
| if (k.width === 3) keyDiv.classList.add('key-w3'); | if (k.width === 3) kd.classList.add('key-w3'); |
| keyDiv.id = k.id; | kd.id = k.id; |
| if (k.type === 's_dot') { | if (k.type === 's_dot') { |
| keyDiv.innerHTML = '<span class="yiv-font" style="font-size:16px;color:#aaa;">' + k.left + '</span>'; | kd.classList.add('key-dot'); |
| | diacAnchorId = kd.id; |
| | kd.innerHTML = '<span class="yiv-font" style="font-size:24px;color:#aaa;">'+diacMode+'</span>'+ |
| | '<span class="diac-hotzone"></span>'+ |
| | '<span class="diac-triangle"></span>'; |
| } else if (k.type === 's_shift') { | } else if (k.type === 's_shift') { |
| keyDiv.classList.add('key-shift'); | kd.classList.add('key-shift'); |
| if (shiftMode === 1) keyDiv.classList.add('shift-active'); | if (shiftMode===1) kd.classList.add('shift-active'); |
| if (shiftMode === 2) keyDiv.classList.add('shift-locked'); | if (shiftMode===2) kd.classList.add('shift-locked'); |
| keyDiv.innerHTML = '<span class="yiv-font">' + k.right + '</span>'; | kd.innerHTML = '<span class="yiv-font">'+k.right+'</span>'; |
| } else if (k.type === 's_newline') { | } else if (k.type === 's_newline') { |
| keyDiv.classList.add('key-newline'); | kd.classList.add('key-newline'); |
| keyDiv.innerHTML = '<span>' + k.left + '</span>'; | kd.innerHTML = '<span>'+k.left+'</span>'; |
| } else if (k.type === 's_space') { | } else if (k.type === 's_space') { |
| keyDiv.classList.add('key-space'); | kd.classList.add('key-space'); |
| keyDiv.innerHTML = '<span style="font-family:sans-serif;">\u23b5</span>'; | kd.innerHTML = '<span style="font-family:sans-serif;">\u23b5</span>'; |
| } else if (k.type === 's_case') { | } else if (k.type === 's_case') { |
| keyDiv.classList.add('key-case'); | kd.classList.add('key-case'); |
| keyDiv.innerHTML = | kd.innerHTML = ['BB','Bb','bb'].map(function(c,i){ |
| '<div class="case-lbl yiv-font' + (caseMode===0?' active':'') + '">Bb</div>' + | var cls = 'case-lbl yiv-font'; |
| '<div class="case-lbl yiv-font' + (caseMode===1?' active':'') + '">BB</div>' + | if (i===caseMode) cls += ' active'; |
| '<div class="case-lbl yiv-font' + (caseMode===2?' active':'') + '">bb</div>'; | if (caseLocked && i===caseMode) cls += ' locked-case'; |
| | return '<span class="'+cls+'">'+c+'</span>'; |
| | }).join('<span class="case-sep">|</span>'); |
| } else { | } else { |
| keyDiv.innerHTML = | kd.innerHTML = |
| '<div class="key-side yiv-font' + (k.left === '..' ? ' empty' : '') + '">' + k.left + '</div>' + | '<div class="key-side yiv-font'+(k.left==='..'?' empty':'')+'">'+k.left+'</div>'+ |
| '<div class="key-side yiv-font' + (k.right === '..' ? ' empty' : '') + '">' + k.right + '</div>'; | '<div class="key-side yiv-font'+(k.right==='..'?' empty':'')+'">'+k.right+'</div>'; |
| } | } |
| keyDiv.onclick = (function(kdata, el) { return function() { handleKeyPress(kdata, el); }; })(k, keyDiv); | kd.onclick = (function(kdata,el){ return function(e){ handleKeyPress(kdata,el,e); }; })(k,kd); |
| if (isEditMode) addDragListeners(keyDiv); | if (isEditMode) addDragListeners(kd); |
| rowDiv.appendChild(keyDiv); | rowDiv.appendChild(kd); |
| }); | }); |
| container.appendChild(rowDiv); | container.appendChild(rowDiv); |
| }); | }); |
| if (isEditMode && activeKeyId) { | if (isEditMode && activeKeyId) { |
| var activeEl = document.getElementById(activeKeyId); | var ae = document.getElementById(activeKeyId); |
| if (activeEl) activeEl.classList.add('active-target'); | if (ae) ae.classList.add('active-target'); |
| } | } |
| | positionDiacPopup(); |
| } | } |
| |
| function handleKeyPress(k, el) { | function handleKeyPress(k, el, e) { |
| if (isEditMode) { | if (isEditMode) { |
| if (k.type.indexOf('s_') === 0) return; | if (keyTypeIsSpecial(k)) return; |
| document.querySelectorAll('.pop-key').forEach(function(x){ x.classList.remove('active-target'); }); | document.querySelectorAll('.pop-key').forEach(function(x){ x.classList.remove('active-target'); }); |
| activeKeyId = el.id; activeSide = 'left'; | activeKeyId = el.id; activeSide = 'left'; |
| } | } |
| if (k.type === 's_shift') { | if (k.type === 's_shift') { |
| var now = Date.now(); | var nowS = Date.now(); |
| if (now - lastShiftTap < 300) { shiftMode = 2; } | if (nowS - lastShiftTap < 300) { shiftMode = 2; } |
| else { shiftMode = shiftMode === 0 ? 1 : 0; } | else { shiftMode = shiftMode===0 ? 1 : 0; } |
| lastShiftTap = now; | lastShiftTap = nowS; buildKeyboard(); return; |
| buildKeyboard(); | |
| return; | |
| } | } |
| if (k.type === 's_case') { | if (k.type === 's_case') { |
| caseMode = (caseMode + 1) % 3; | var nowC = Date.now(); |
| updateCaseDisplay(); | var rect = el.getBoundingClientRect(); |
| buildKeyboard(); | var cx = e.clientX - rect.left; |
| return; | var third = cx < rect.width/3 ? 0 : cx < 2*rect.width/3 ? 1 : 2; |
| | if (caseLocked && third===caseMode) { |
| | caseLocked = false; caseMode = 1; |
| | casePendingChars = 0; |
| | } else if (nowC - lastCaseTap < 300 && lastCaseThird===third) { |
| | caseMode = third; caseLocked = true; |
| | casePendingChars = 0; |
| | } else { |
| | caseLocked = false; caseMode = third; |
| | casePendingChars = caseMode===1 ? 0 : 2; |
| | } |
| | lastCaseTap = nowC; lastCaseThird = third; |
| | updateCaseDisplay(); buildKeyboard(); return; |
| } | } |
| var charToType = ''; | var charToType = ''; |
| if (k.type === 's_space') charToType = ' '; | if (k.type==='s_space') charToType = ' '; |
| else if (k.type === 's_newline') charToType = '\n'; | else if (k.type==='s_newline') charToType = '\n'; |
| else if (k.type === 's_dot') charToType = k.left; | else if (k.type==='s_dot') { |
| else { | var popup = document.getElementById('diac-popup'); |
| charToType = (shiftMode > 0 && k.right !== '..') ? k.right : k.left; | if (e && e.target && (e.target.classList.contains('diac-triangle')||e.target.classList.contains('diac-hotzone')||e.target===popup)) { |
| if (charToType === '..') return; | if (popup && popup.classList.contains('open')) { closeDiacPopup(); return; } |
| | toggleDiacPopup(e); return; |
| | } |
| | if (popup && popup.classList.contains('open')) { closeDiacPopup(); return; } |
| | charToType = diacMode; |
| | } else { |
| | charToType = (shiftMode>0 && k.right!=='..') ? k.right : k.left; |
| | if (charToType==='..') return; |
| } | } |
| typeCharacter(charToType); | typeCharacter(charToType, 'button'); |
| if (shiftMode === 1) { shiftMode = 0; buildKeyboard(); } | if (shiftMode===1) { shiftMode=0; buildKeyboard(); } |
| | } |
| | |
| | function toggleDiacPopup(e) { |
| | if (e) e.stopPropagation(); |
| | var popup = document.getElementById('diac-popup'); |
| | if (!popup) return; |
| | var isOpen = popup.classList.contains('open'); |
| | closeDiacPopup(); |
| | if (!isOpen) { |
| | popup.innerHTML = ''; |
| | var head = document.createElement('div'); |
| | head.className = 'diac-popup-head'; head.textContent = 'Diacritics'; popup.appendChild(head); |
| | var note1 = document.createElement('div'); |
| | note1.className = 'diac-popup-note'; |
| | note1.textContent = 'Dot: meaningful/logographic/phrase-end\nDouble: redup./longer-end/pause\nTriple: tone/abrupt-end/long-pause'; |
| | popup.appendChild(note1); |
| | var note2 = document.createElement('div'); |
| | note2.className = 'diac-popup-note'; |
| | note2.textContent = 'Sound helpers are especially useful in obscure or old-fashioned written words.'; |
| | popup.appendChild(note2); |
| | var s0 = document.createElement('hr'); s0.className = 'diac-popup-sep'; popup.appendChild(s0); |
| | var diacBtnOnLeft = true; |
| | DIACRITICS.forEach(function(d,idx) { |
| | if (idx===5 || idx===11) { var s=document.createElement('hr'); s.className='diac-popup-sep'; popup.appendChild(s); diacBtnOnLeft = true; } |
| | var btn = document.createElement('button'); |
| | var mobilePopup = (window.innerWidth || document.documentElement.clientWidth || 0) <= 640; |
| | btn.className = 'diac-btn'+(d.val===diacMode?' active-diac':'')+(diacBtnOnLeft?' diac-tip-left':' diac-tip-right'); |
| | if (!mobilePopup) btn.setAttribute('data-tip', d.desc); |
| | btn.innerHTML = '<span class="diac-lbl">'+formatDiacLabel(d.label)+'</span><span class="diac-copy"><span class="diac-short">'+d.short+'</span>'+(mobilePopup?'<span class="diac-desc">'+escapeHtml(d.desc)+'</span>':'')+'</span>'; |
| | btn.onclick = function(ev) { ev.stopPropagation(); diacMode=d.val; closeDiacPopup(); buildKeyboard(); }; |
| | popup.appendChild(btn); |
| | diacBtnOnLeft = !diacBtnOnLeft; |
| | }); |
| | popup.classList.add('open'); |
| | positionDiacPopup(); |
| | } |
| | } |
| | |
| | function closeDiacPopup() { |
| | var p = document.getElementById('diac-popup'); |
| | if (!p) return; |
| | p.classList.remove('open'); |
| | p.style.transform = 'translate3d(-9999px,-9999px,0)'; |
| | } |
| | |
| | function positionDiacPopup() { |
| | var popup = document.getElementById('diac-popup'); |
| | if (!popup || !popup.classList.contains('open')) return; |
| | var anchor = diacAnchorId ? document.getElementById(diacAnchorId) : null; |
| | if (!anchor) return; |
| | popup.style.transform = 'translate3d(-9999px,-9999px,0)'; |
| | var rect = anchor.getBoundingClientRect(); |
| | var typeArea = document.getElementById('typeArea'); |
| | var typeRect = typeArea ? typeArea.getBoundingClientRect() : null; |
| | var vw = window.innerWidth || document.documentElement.clientWidth; |
| | var vh = window.innerHeight || document.documentElement.clientHeight; |
| | if (vw <= 640) popup.style.width = Math.min(vw - 16, 320) + 'px'; |
| | else popup.style.width = ''; |
| | var width = popup.offsetWidth || 360; |
| | var height = popup.offsetHeight || 180; |
| | var left = rect.left; |
| | var topAbove = rect.top - height - 8; |
| | var topBelow = rect.bottom + 8; |
| | var top = topAbove; |
| | if (vw <= 640) { |
| | left = Math.max(8, Math.round((vw - width) / 2)); |
| | top = topBelow; |
| | } |
| | if (left + width + 10 > vw) left = Math.max(8, vw - width - 10); |
| | if (left < 8) left = 8; |
| | if (top < 8) top = topBelow; |
| | if (typeRect && top < typeRect.bottom + 8) top = topBelow; |
| | if (top < 8) top = 8; |
| | if (top + height + 8 > vh) top = Math.max(8, vh - height - 8); |
| | popup.style.transform = 'translate3d('+Math.round(left)+'px,'+Math.round(top)+'px,0)'; |
| } | } |
| |
| function updateCaseDisplay() { | function updateCaseDisplay() { |
| var w = document.getElementById('yiv-global-wrapper'); | var w = document.getElementById('yiv-global-wrapper'); |
| w.classList.remove('upper-mode', 'lower-mode'); | w.classList.remove('upper-mode','lower-mode'); |
| if (caseMode === 1) w.classList.add('upper-mode'); | if (caseMode===0) w.classList.add('upper-mode'); |
| else if (caseMode === 2) w.classList.add('lower-mode'); | else if (caseMode===2) w.classList.add('lower-mode'); |
| } | } |
| |
| function typeCharacter(c) { | function insertAtCursor(area, node) { |
| | area.focus(); |
| | var sel = window.getSelection(); |
| | if (sel && sel.rangeCount>0 && area.contains(sel.anchorNode)) { |
| | var range = sel.getRangeAt(0); |
| | range.deleteContents(); |
| | range.insertNode(node); |
| | range.setStartAfter(node); range.collapse(true); |
| | sel.removeAllRanges(); sel.addRange(range); |
| | } else { area.appendChild(node); } |
| | } |
| | |
| | function typeCharacter(c, source) { |
| var area = document.getElementById('typeArea'); | var area = document.getElementById('typeArea'); |
| if (c === ' ' || c === '\n') { area.appendChild(document.createTextNode(c)); return; } | if (c==='\n') { insertAtCursor(area, document.createElement('br')); return; } |
| | if (c===' '||c==='\u00a0') { insertAtCursor(area, document.createTextNode('\u00a0')); return; } |
| var fc = c; | var fc = c; |
| if (caseMode === 0) fc = c.charAt(0).toUpperCase() + c.slice(1).toLowerCase(); | if (caseMode===0) fc = c.toUpperCase(); |
| else if (caseMode === 1) fc = c.toUpperCase(); | else if (caseMode===1) fc = c.charAt(0).toUpperCase()+c.slice(1).toLowerCase(); |
| else fc = c.toLowerCase(); | else fc = c.toLowerCase(); |
| var span = document.createElement('span'); | var span = document.createElement('span'); span.className='yiv-font'; span.innerText=fc; |
| span.className = 'yiv-font'; | insertAtCursor(area, span); |
| span.innerText = fc; | if (caseAutoReturn && !caseLocked && caseMode!==1) { |
| area.appendChild(span); | if (casePendingChars > 0) { |
| | if (source === 'manual') casePendingChars--; |
| | else casePendingChars = 0; |
| | } |
| | if (casePendingChars > 0) return; |
| | caseMode=1; updateCaseDisplay(); buildKeyboard(); |
| | } |
| } | } |
| |
| function copyTypingArea() { | function copyTypingArea() { |
| navigator.clipboard.writeText(document.getElementById('typeArea').innerText).then(function() { | navigator.clipboard.writeText(document.getElementById('typeArea').innerText).then(function() { |
| var btn = document.getElementById('copyBtn'); | var btn=document.getElementById('copyBtn'); |
| btn.innerText = '[copied!]'; btn.classList.add('success'); | btn.innerText='[copied!]'; btn.classList.add('success'); |
| setTimeout(function(){ btn.innerText = '[copy]'; btn.classList.remove('success'); }, 2000); | setTimeout(function(){ btn.innerText='[copy]'; btn.classList.remove('success'); },2000); |
| }); | }); |
| | } |
| | |
| | function fitKeyboard() { |
| | var wrapper = document.querySelector('.pop-kb-wrapper'); |
| | var kb = document.getElementById('kbContainer'); |
| | if (!wrapper||!kb) return; |
| | kb.style.transform=''; |
| | var wW = wrapper.offsetWidth - 16; |
| | var kW = kb.offsetWidth; |
| | if (kW>wW && wW>100) { |
| | var scale = Math.max(0.35, wW/kW); |
| | kb.style.transform = 'scale('+scale+')'; |
| | kb.style.transformOrigin = 'top center'; |
| | wrapper.style.minHeight = Math.round(kb.offsetHeight*scale+20)+'px'; |
| | } else { wrapper.style.minHeight=''; } |
| | } |
| | |
| | function fitAsciiBox() { |
| | var box = document.getElementById('asciiOutput'); |
| | if (!box) return; |
| | var linesArr = (box.value || '').split('\n'); |
| | var lines = linesArr.length; |
| | var longest = linesArr.reduce(function(max, line){ return Math.max(max, line.length); }, 0); |
| | var rows = Math.max(4, Math.min(14, lines + 1)); |
| | box.style.height = (rows * 20) + 'px'; |
| | var parent = box.parentElement; |
| | var maxW = parent ? Math.max(320, parent.clientWidth - 4) : 800; |
| | var ideal = Math.min(maxW, Math.max(320, (longest * 8) + 36)); |
| | box.style.width = ideal + 'px'; |
| | box.style.maxWidth = '100%'; |
| } | } |
| |
| document.querySelectorAll('#popGlyphTable td').forEach(function(td) { | document.querySelectorAll('#popGlyphTable td').forEach(function(td) { |
| td.onclick = function() { | td.onclick = function() { |
| if (!activeKeyId || !isEditMode) return; | if (!activeKeyId||!isEditMode) return; |
| var glyph = this.querySelector('span').innerText; | var glyph = this.querySelector('span').innerText; |
| var coords = activeKeyId.match(/\d+/g).map(Number); | var coords = activeKeyId.match(/\d+/g).map(Number); |
| var r = coords[0], ki = coords[1]; | var r=coords[0], ki=coords[1]; |
| kbState[r][ki][activeSide] = glyph; | kbState[r][ki][activeSide] = glyph; |
| if (activeSide === 'left') { activeSide = 'right'; } | if (activeSide==='left') { activeSide='right'; } else { findNextAvailableKey(r,ki); } |
| else { findNextAvailableKey(r, ki); } | |
| saveAndRefresh(); | saveAndRefresh(); |
| }; | }; |
| |
| function findNextAvailableKey(currR, currK) { | function findNextAvailableKey(currR, currK) { |
| var flat = kbState.reduce(function(a,b){ return a.concat(b); }, []); | var flat = kbState.reduce(function(a,b){return a.concat(b);},[]); |
| var globalIdx = 0; | var gi=0; |
| for (var r = 0; r < currR; r++) globalIdx += kbState[r].length; | for (var r=0;r<currR;r++) gi+=kbState[r].length; |
| globalIdx += currK; | gi+=currK; activeKeyId=null; |
| activeKeyId = null; | for (var i=gi+1;i<flat.length;i++) { |
| for (var i = globalIdx + 1; i < flat.length; i++) { | if (flat[i].type!=='y' && !keyTypeIsSpecial(flat[i]) && (flat[i].left==='..'||flat[i].right==='..')) { |
| if (flat[i].type !== 'y' && flat[i].type.indexOf('s_') !== 0 && | activeKeyId=flat[i].id; activeSide=flat[i].left==='..'?'left':'right'; return; |
| (flat[i].left === '..' || flat[i].right === '..')) { | |
| activeKeyId = flat[i].id; | |
| activeSide = flat[i].left === '..' ? 'left' : 'right'; | |
| return; | |
| } | } |
| } | } |
| } | } |
| |
| var draggedId = null; | var draggedId=null; |
| function addDragListeners(el) { | function addDragListeners(el) { |
| el.draggable = true; | el.draggable=true; |
| el.ondragstart = function(e) { draggedId = e.target.id; e.dataTransfer.setData('text', e.target.id); }; | el.ondragstart=function(e){draggedId=e.target.id;e.dataTransfer.setData('text',e.target.id);}; |
| el.ondragover = function(e) { | el.ondragover=function(e){ |
| e.preventDefault(); | e.preventDefault(); |
| var rect = el.getBoundingClientRect(), x = e.clientX - rect.left; | var rect=el.getBoundingClientRect(),x=e.clientX-rect.left; |
| el.classList.remove('drag-over-left','drag-over-right','drag-over-center'); | el.classList.remove('drag-over-left','drag-over-right','drag-over-center'); |
| if (x < rect.width * 0.25) el.classList.add('drag-over-left'); | if(x<rect.width*0.25)el.classList.add('drag-over-left'); |
| else if (x > rect.width * 0.75) el.classList.add('drag-over-right'); | else if(x>rect.width*0.75)el.classList.add('drag-over-right'); |
| else el.classList.add('drag-over-center'); | else el.classList.add('drag-over-center'); |
| }; | }; |
| el.ondragleave = function() { el.classList.remove('drag-over-left','drag-over-right','drag-over-center'); }; | el.ondragleave=function(){el.classList.remove('drag-over-left','drag-over-right','drag-over-center');}; |
| el.ondrop = function(e) { | el.ondrop=function(e){ |
| e.preventDefault(); | e.preventDefault(); |
| var tId = el.id; | var tId=el.id; if(draggedId===tId)return; |
| if (draggedId === tId) return; | var rect=el.getBoundingClientRect(),x=e.clientX-rect.left; |
| var rect = el.getBoundingClientRect(), x = e.clientX - rect.left; | var src=draggedId.match(/\d+/g).map(Number); |
| var src = draggedId.match(/\d+/g).map(Number); | var tgt=tId.match(/\d+/g).map(Number); |
| var tgt = tId.match(/\d+/g).map(Number); | var srcKey=kbState[src[0]].splice(src[1],1)[0]; |
| var srcKey = kbState[src[0]].splice(src[1], 1)[0]; | var ins=tgt[1]; |
| var ins = tgt[1]; | if(src[0]===tgt[0]&&src[1]<tgt[1])ins=Math.max(0,ins); |
| if (src[0] === tgt[0] && src[1] < tgt[1]) ins = Math.max(0, ins); | if(x<rect.width*0.25){kbState[tgt[0]].splice(ins,0,srcKey);} |
| if (x < rect.width * 0.25) { kbState[tgt[0]].splice(ins, 0, srcKey); } | else if(x>rect.width*0.75){kbState[tgt[0]].splice(ins+1,0,srcKey);} |
| else if (x > rect.width * 0.75) { kbState[tgt[0]].splice(ins + 1, 0, srcKey); } | else{ |
| else { | var tk=kbState[tgt[0]][ins],tc={left:tk.left,right:tk.right}; |
| var tk = kbState[tgt[0]][ins]; | tk.left=srcKey.left;tk.right=srcKey.right; |
| var tc = { left: tk.left, right: tk.right }; | kbState[src[0]].splice(src[1],0,srcKey); |
| tk.left = srcKey.left; tk.right = srcKey.right; | kbState[src[0]][src[1]].left=tc.left;kbState[src[0]][src[1]].right=tc.right; |
| kbState[src[0]].splice(src[1], 0, srcKey); | |
| kbState[src[0]][src[1]].left = tc.left; | |
| kbState[src[0]][src[1]].right = tc.right; | |
| } | } |
| saveAndRefresh(); | saveAndRefresh(); |
| |
| function saveAndRefresh() { | function saveAndRefresh() { |
| kbState.forEach(function(row, r) { row.forEach(function(key, k) { key.id = 'r' + r + 'k' + k; }); }); | kbState.forEach(function(row,r){row.forEach(function(key,k){key.id='r'+r+'k'+k;});}); |
| sessionStorage.setItem('yivTypewriter', JSON.stringify(kbState)); | sessionStorage.setItem('yivTypewriter',JSON.stringify(kbState)); |
| buildKeyboard(); | buildKeyboard(); updateAsciiOutput(); |
| updateAsciiOutput(); | |
| } | } |
| |
| var ASCII_INDENTS = ['', ' ', ' ', ' ', '']; | var ASCII_INDENTS=['',' ',' ',' ','']; |
| function updateAsciiOutput() { | function updateAsciiOutput() { |
| var out = ''; | var out=''; |
| kbState.forEach(function(row, r) { | kbState.forEach(function(row,r){ |
| var line = ASCII_INDENTS[r] || ''; | var line=ASCII_INDENTS[r]||''; |
| row.forEach(function(k) { | row.forEach(function(k){ |
| if (k.type === 'y') line += ' '; | if(k.type==='y')line+=' '; |
| else if (k.type.indexOf('s_') === 0) line += '[S_' + k.type.replace('s_','').toUpperCase() + '] '; | else if(keyTypeIsSpecial(k))line+='[S_'+k.type.replace('s_','').toUpperCase()+'] '; |
| else line += '[' + k.left + ' ' + k.right + '] '; | else line+='['+k.left+' '+k.right+'] '; |
| }); | }); |
| out += line.replace(/\s+$/, '') + '\n'; | out+=line.replace(/\s+$/,'')+'\n'; |
| }); | }); |
| document.getElementById('asciiOutput').value = out; | document.getElementById('asciiOutput').value=out; |
| | fitAsciiBox(); |
| } | } |
| |
| function importAscii() { | function importAscii() { |
| var text = document.getElementById('asciiOutput').value; | var text=document.getElementById('asciiOutput').value; |
| var blocks = [], re = /\[(.*?)\]/g, m; | var blocks=[],re=/\[(.*?)\]/g,m; |
| while ((m = re.exec(text)) !== null) blocks.push(m[1].trim()); | while((m=re.exec(text))!==null)blocks.push(m[1].trim()); |
| var bIdx = 0; | var bi=0; |
| kbState.forEach(function(row) { | kbState.forEach(function(row){row.forEach(function(k){ |
| row.forEach(function(k) { | if(k.type==='y'||keyTypeIsSpecial(k))return; |
| if (k.type === 'y' || k.type.indexOf('s_') === 0) return; | if(bi>=blocks.length)return; |
| if (bIdx >= blocks.length) return; | var block=blocks[bi++]; |
| var block = blocks[bIdx++]; | if(block.indexOf('S_')===0)return; |
| if (block.indexOf('S_') === 0) return; | var pts=block.split(/\s+/);k.left=pts[0]||'..';k.right=pts[1]||'..'; |
| var pts = block.split(/\s+/); | });}); |
| k.left = pts[0] || '..'; k.right = pts[1] || '..'; | saveAndRefresh(); alert('Layout imported!'); |
| }); | |
| }); | |
| saveAndRefresh(); | |
| alert('Layout imported!'); | |
| } | } |
| |
| function copyAscii() { | function copyAscii() { |
| navigator.clipboard.writeText(document.getElementById('asciiOutput').value) | navigator.clipboard.writeText(document.getElementById('asciiOutput').value).then(function(){alert('ASCII copied!');}); |
| .then(function(){ alert('ASCII copied!'); }); | |
| } | } |
| |
| function clearKeyboard() { | function clearKeyboard() { |
| if (confirm('Reset layout to default?')) { | if(confirm('Reset layout to default?')){sessionStorage.removeItem('yivTypewriter');parseDefaultStrings();saveAndRefresh();} |
| sessionStorage.removeItem('yivTypewriter'); | } |
| parseDefaultStrings(); | |
| saveAndRefresh(); | function updateYivDisplay(keepEraChoice) { |
| | var w=document.getElementById('yiv-global-wrapper'); |
| | var val=document.querySelector('input[name="fontStyle"]:checked').value; |
| | w.classList.remove('italic-mode','bold-mode'); |
| | if(val==='italic')w.classList.add('italic-mode'); |
| | else if(val==='bold')w.classList.add('bold-mode'); |
| | if (!(keepEraChoice && currentEra==='choice')) { |
| | if (val==='italic') currentEra='ancient'; |
| | else if (val==='regular') currentEra='present'; |
| | else currentEra='modern'; |
| } | } |
| | document.querySelectorAll('.yiv-era-btn').forEach(function(btn){ |
| | btn.classList.toggle('active', btn.getAttribute('data-era')===currentEra); |
| | }); |
| | generateExport(); |
| } | } |
| |
| function toggleAsciiPanel() { | function copySnippet() { |
| var p = document.getElementById('asciiPanel'); | var txt=document.getElementById('htmlSnippet').value; |
| var show = p.style.display === 'none'; | var minToggle=document.getElementById('minifyCodeToggle'); |
| p.style.display = show ? 'block' : 'none'; | var btn=document.getElementById('copySnippetBtn'); |
| if (show) updateAsciiOutput(); | if(minToggle && minToggle.checked) txt = minifyHtml(txt); |
| | else if(minToggle) txt = prettyPrintHtml(txt); |
| | if(!txt.trim())return; |
| | navigator.clipboard.writeText(txt).then(function(){ |
| | if(!btn)return; |
| | btn.innerHTML='✅ Copied'; |
| | btn.classList.add('is-copied'); |
| | setTimeout(function(){ |
| | btn.innerHTML='📋 Copy Code'; |
| | btn.classList.remove('is-copied'); |
| | }, 1800); |
| | }); |
| } | } |
| |
| function updateYivDisplay() { | function escapeHtml(str) { |
| var w = document.getElementById('yiv-global-wrapper'); | return str |
| var val = document.querySelector('input[name="fontStyle"]:checked').value; | .replace(/&/g, '&') |
| w.classList.remove('italic-mode', 'bold-mode'); | .replace(/</g, '<') |
| if (val === 'italic') w.classList.add('italic-mode'); | .replace(/>/g, '>'); |
| else if (val === 'bold') w.classList.add('bold-mode'); | |
| } | } |
| |
| function openExportDialog() { document.getElementById('exportDialog').style.display = 'flex'; } | function escapeHtmlAttr(str) { |
| function closeExportDialog() { document.getElementById('exportDialog').style.display = 'none'; } | return String(str) |
| | .replace(/&/g, '&') |
| | .replace(/"/g, '"') |
| | .replace(/</g, '<') |
| | .replace(/>/g, '>'); |
| | } |
| |
| function copySnippet() { | function prettyIndent(level) { |
| var txt = document.getElementById('htmlSnippet').value; | return ' '.repeat(Math.max(0, level || 0)); |
| if (!txt.trim()) { alert('Click Generate Code first.'); return; } | } |
| navigator.clipboard.writeText(txt).then(function(){ alert('Copied!'); }); | |
| | function prettyPrintCodeBlock(code, indentLevel, allowLineComments) { |
| | var text = String(code || '').replace(/\r\n/g, '\n').trim(); |
| | if (!text) return ''; |
| | var out = []; |
| | var line = ''; |
| | var indent = indentLevel || 0; |
| | var quote = null; |
| | var blockComment = false; |
| | var lineComment = false; |
| | |
| | function flush(force) { |
| | var t = line.trim(); |
| | if (t || force) out.push(prettyIndent(indent) + t); |
| | line = ''; |
| | } |
| | |
| | for (var i = 0; i < text.length; i += 1) { |
| | var ch = text[i]; |
| | var next = text[i + 1]; |
| | |
| | if (lineComment) { |
| | line += ch; |
| | if (ch === '\n') { |
| | flush(true); |
| | lineComment = false; |
| | } |
| | continue; |
| | } |
| | |
| | if (blockComment) { |
| | line += ch; |
| | if (ch === '*' && next === '/') { |
| | line += '/'; |
| | i += 1; |
| | blockComment = false; |
| | } |
| | continue; |
| | } |
| | |
| | if (quote) { |
| | line += ch; |
| | if (ch === '\\') { |
| | if (next) { |
| | line += next; |
| | i += 1; |
| | } |
| | continue; |
| | } |
| | if (ch === quote) quote = null; |
| | continue; |
| | } |
| | |
| | if (allowLineComments && ch === '/' && next === '/') { |
| | flush(false); |
| | line = '//'; |
| | lineComment = true; |
| | i += 1; |
| | continue; |
| | } |
| | |
| | if (ch === '/' && next === '*') { |
| | flush(false); |
| | line = '/*'; |
| | blockComment = true; |
| | i += 1; |
| | continue; |
| | } |
| | |
| | if (ch === '"' || ch === "'" || ch === '`') { |
| | quote = ch; |
| | line += ch; |
| | continue; |
| | } |
| | |
| | if (ch === '{') { |
| | if (line.trim()) { |
| | line += ' {'; |
| | flush(true); |
| | } else { |
| | out.push(prettyIndent(indent) + '{'); |
| | } |
| | indent += 1; |
| | continue; |
| | } |
| | |
| | if (ch === '}') { |
| | flush(false); |
| | indent = Math.max(0, indent - 1); |
| | out.push(prettyIndent(indent) + '}'); |
| | continue; |
| | } |
| | |
| | if (ch === ';') { |
| | line += ';'; |
| | flush(true); |
| | continue; |
| | } |
| | |
| | if (ch === '\n' || ch === '\r') { |
| | flush(false); |
| | continue; |
| | } |
| | |
| | if (/\s/.test(ch)) { |
| | if (line && !/\s$/.test(line)) line += ' '; |
| | continue; |
| | } |
| | |
| | line += ch; |
| | } |
| | |
| | flush(false); |
| | return out.join('\n'); |
| | } |
| | |
| | function prettyPrintHtmlNode(node, indent, out) { |
| | if (!node) return; |
| | if (node.nodeType === 3) { |
| | var text = (node.textContent || '').replace(/\s+/g, ' ').trim(); |
| | if (text) out.push(prettyIndent(indent) + escapeHtml(text)); |
| | return; |
| | } |
| | if (node.nodeType === 8) { |
| | var comment = (node.textContent || '').trim(); |
| | if (comment) out.push(prettyIndent(indent) + '<!-- ' + comment + ' -->'); |
| | return; |
| | } |
| | if (node.nodeType !== 1) return; |
| | |
| | var tag = node.tagName.toLowerCase(); |
| | var attrs = Array.from(node.attributes || []).map(function(attr) { |
| | return attr.name + '="' + escapeHtmlAttr(attr.value) + '"'; |
| | }).join(' '); |
| | var open = '<' + tag + (attrs ? ' ' + attrs : '') + '>'; |
| | var voidTags = { meta:1, link:1, br:1, hr:1, img:1, input:1 }; |
| | if (voidTags[tag]) { |
| | out.push(prettyIndent(indent) + open); |
| | return; |
| | } |
| | |
| | if (tag === 'style') { |
| | out.push(prettyIndent(indent) + open); |
| | var css = prettyPrintCodeBlock(node.textContent || '', indent + 1, false); |
| | if (css) out.push(css); |
| | out.push(prettyIndent(indent) + '</style>'); |
| | return; |
| | } |
| | |
| | if (tag === 'script') { |
| | out.push(prettyIndent(indent) + open); |
| | var js = prettyPrintCodeBlock(node.textContent || '', indent + 1, true); |
| | if (js) out.push(js); |
| | out.push(prettyIndent(indent) + '<\/script>'); |
| | return; |
| | } |
| | |
| | var children = Array.from(node.childNodes || []).filter(function(child) { |
| | return !(child.nodeType === 3 && !(child.textContent || '').trim()); |
| | }); |
| | if (children.length === 0) { |
| | out.push(prettyIndent(indent) + open + '</' + tag + '>'); |
| | return; |
| | } |
| | |
| | if (children.length === 1 && children[0].nodeType === 3) { |
| | var inlineText = (children[0].textContent || '').replace(/\s+/g, ' ').trim(); |
| | if (inlineText) { |
| | out.push(prettyIndent(indent) + open + escapeHtml(inlineText) + '</' + tag + '>'); |
| | return; |
| | } |
| | } |
| | |
| | out.push(prettyIndent(indent) + open); |
| | children.forEach(function(child) { |
| | prettyPrintHtmlNode(child, indent + 1, out); |
| | }); |
| | out.push(prettyIndent(indent) + '</' + tag + '>'); |
| | } |
| | |
| | function prettyPrintHtml(code) { |
| | var html = String(code || '').replace(/\r\n/g, '\n').trim(); |
| | if (!html) return ''; |
| | var doc = new DOMParser().parseFromString(html, 'text/html'); |
| | var out = []; |
| | if (doc.doctype) out.push('<!DOCTYPE html>'); |
| | if (doc.documentElement) { |
| | prettyPrintHtmlNode(doc.documentElement, 0, out); |
| | } else { |
| | Array.from(doc.childNodes || []).forEach(function(node) { |
| | prettyPrintHtmlNode(node, 0, out); |
| | }); |
| | } |
| | return out.join('\n'); |
| | } |
| | |
| | function minifyHtml(code) { |
| | return code |
| | .replace(/>\s+</g, '><') |
| | .replace(/\n+/g, '') |
| | .replace(/\s{2,}/g, ' ') |
| | .trim(); |
| | } |
| | |
| | function cleanCodePreviewNoise(code) { |
| | return code.replace(/\/\*\s*background-color[\s\S]*?var\(--pre_background,\s*#fbfaf9\);\s*/gi, ''); |
| | } |
| | |
| | function syntaxHighlightSnippet(code) { |
| | var esc = escapeHtml(code); |
| | esc = esc.replace(/(<\/?)([a-zA-Z0-9-]+)([^&]*?)(\/?>)/g, function(_, a, tag, attrs, z) { |
| | var hiAttrs = attrs.replace(/([a-zA-Z-:]+)=(".*?"|'[^']*')/g, '<span class="tok-attr">$1</span>=<span class="tok-str">$2</span>'); |
| | return '<span class="tok-tag">'+a+tag+'</span>'+hiAttrs+'<span class="tok-tag">'+z+'</span>'; |
| | }); |
| | esc = esc.replace(/\b(function|var|let|const|return|if|else|for|while|new)\b/g, '<span class="tok-js-key">$1</span>'); |
| | esc = esc.replace(/(\/\/[^\n]*)/g, '<span class="tok-js-cmt">$1</span>'); |
| | return esc; |
| | } |
| | |
| | function renderCodeView() { |
| | var raw = (document.getElementById('htmlSnippet').value || ''); |
| | var minToggle = document.getElementById('minifyCodeToggle'); |
| | var show = (!minToggle || minToggle.checked) ? minifyHtml(raw) : prettyPrintHtml(raw); |
| | show = cleanCodePreviewNoise(show); |
| | var codeEl = document.getElementById('htmlSnippetCode'); |
| | if(codeEl) codeEl.innerHTML = syntaxHighlightSnippet(show); |
| | } |
| | |
| | function setEra(era) { |
| | currentEra = era; |
| | var radioMap = { ancient: 'italic', present: 'regular', modern: 'bold', choice: 'bold' }; |
| | var radioVal = radioMap[era] || 'bold'; |
| | var radio = document.querySelector('input[name="fontStyle"][value="'+radioVal+'"]'); |
| | if (radio) radio.checked = true; |
| | document.querySelectorAll('.yiv-era-btn').forEach(function(btn){ |
| | btn.classList.toggle('active', btn.getAttribute('data-era')===era); |
| | }); |
| | updateYivDisplay(era === 'choice'); |
| | } |
| | |
| | function setTheme(id) { |
| | currentTheme=id; |
| | document.querySelectorAll('.yiv-theme-btn[data-theme]').forEach(function(b){b.classList.toggle('active',b.getAttribute('data-theme')===id);}); |
| | document.querySelectorAll('.yiv-more-item').forEach(function(b){b.classList.toggle('active',b.getAttribute('data-theme')===id);}); |
| | generateExport(); |
| | } |
| | |
| | function populateMoreMenu() { |
| | var menu=document.getElementById('yiv-more-menu'); if(!menu)return; |
| | menu.innerHTML=''; |
| | MORE_THEME_IDS.forEach(function(id){ |
| | var t=THEMES[id]; if(!t)return; |
| | var btn=document.createElement('button'); |
| | btn.className='yiv-more-item'+(id===currentTheme?' active':''); |
| | btn.setAttribute('data-theme',id); |
| | btn.innerHTML='<strong>'+t.name+'</strong><span>'+t.desc+'</span>'; |
| | btn.onclick=function(ev){ev.stopPropagation();setTheme(id);closeMoreMenu();}; |
| | menu.appendChild(btn); |
| | }); |
| | } |
| | |
| | function toggleMoreMenu(e) { |
| | e.stopPropagation(); |
| | var menu=document.getElementById('yiv-more-menu'); if(!menu)return; |
| | menu.style.display=menu.style.display==='none'?'block':'none'; |
| | } |
| | |
| | function closeMoreMenu() { |
| | var m=document.getElementById('yiv-more-menu'); if(m)m.style.display='none'; |
| } | } |
| |
| function generateExport() { | function generateExport() { |
| var mode = document.querySelector('input[name="exportMode"]:checked').value; | if(!kbState.length)return; |
| var stateJson = JSON.stringify(kbState); | var modeEl=document.querySelector('input[name="exportMode"]:checked'); |
| | var mode=modeEl?modeEl.value:'popup'; |
| | var sj=JSON.stringify(kbState); |
| var out; | var out; |
| if (mode === 'barebones') out = buildBarebonesHtml(stateJson); | if(mode==='barebones')out=buildBarebonesHtml(sj,currentTheme,currentEra); |
| else if (mode === 'minimalist') out = buildMinimalistHtml(stateJson); | else if(mode==='minimalist')out=buildMinimalistHtml(sj,currentTheme,currentEra); |
| else out = buildPopupFragment(stateJson); | else out=buildPopupFragment(sj,currentTheme,currentEra); |
| document.getElementById('htmlSnippet').value = out; | var snip=document.getElementById('htmlSnippet'); if(snip)snip.value=out; |
| | renderCodeView(); |
| | var iframe=document.getElementById('yiv-preview-frame'); |
| | if(iframe) { |
| | iframe.srcdoc=out; |
| | setTimeout(function(){ |
| | try { |
| | var doc = iframe.contentDocument; |
| | if(!doc) return; |
| | var d = doc.documentElement; |
| | var b = doc.body; |
| | var h = Math.max(d.scrollHeight, b ? b.scrollHeight : 0, 320); |
| | iframe.style.height = (h + 6) + 'px'; |
| | } catch (_e) {} |
| | }, 70); |
| | } |
| } | } |
| |
| function buildKbRenderer(stateExpr, areaId, kbId) { | function buildKbRenderer(stateExpr,areaId,kbId,fontVar) { |
| | var fvar = fontVar || '--yiv-font'; |
| return [ | return [ |
| 'var st=' + stateExpr + ',shift=0,cm=0,lastTap=0;', | 'var st='+stateExpr+',shift=0,cm=1,lastTap=0,lastCaseTap=0,lastCaseThird=-1,caseLocked=false,pend=0;', |
| 'function typeC(c){', | 'function applyCase(c){if(cm===0)return c.toUpperCase();if(cm===1)return c.charAt(0).toUpperCase()+c.slice(1).toLowerCase();return c.toLowerCase();}', |
| ' var a=document.getElementById("' + areaId + '");if(!a)return;', | 'function consumeCase(src){if(cm===1||caseLocked||pend<=0)return false;if(src==="manual")pend=Math.max(0,pend-1);else pend=0;if(pend>0)return false;cm=1;return true;}', |
| ' if(c===" "||c==="\\n"){a.appendChild(document.createTextNode(c));return;}', | 'function typeC(c,src){var a=document.getElementById("'+areaId+'");if(!a)return;', |
| ' var f=c;if(cm===0)f=c.charAt(0).toUpperCase()+c.slice(1).toLowerCase();', | 'if(c==="\\n"){a.appendChild(document.createElement("br"));if(consumeCase(src||"button"))render();return;}', |
| ' else if(cm===1)f=c.toUpperCase();else f=c.toLowerCase();', | 'if(c===" "){a.appendChild(document.createTextNode(" "));if(consumeCase(src||"button"))render();return;}', |
| ' var sp=document.createElement("span");sp.className="yz-ch";sp.innerText=f;a.appendChild(sp);', | 'var sp=document.createElement("span");sp.className="yz-ch";sp.innerText=applyCase(c);a.appendChild(sp);if(consumeCase(src||"button"))render();}', |
| '}', | 'function bindTyping(){var a=document.getElementById("'+areaId+'");if(!a||a.__yzBound)return;a.__yzBound=1;a.addEventListener("keydown",function(e){if(e.key==="Enter"){e.preventDefault();typeC("\\n","manual");return;}if(e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey){e.preventDefault();typeC(e.key,"manual");}});}', |
| 'function render(){', | 'function render(){var kb=document.getElementById("'+kbId+'");if(!kb)return;kb.innerHTML="";', |
| ' var kb=document.getElementById("' + kbId + '");if(!kb)return;kb.innerHTML="";', | 'st.forEach(function(row,ri){var rd=document.createElement("div");rd.className="yz-row yz-r"+ri;', |
| ' st.forEach(function(row,ri){', | 'row.forEach(function(k){var kd=document.createElement("div");kd.className="yz-key";', |
| ' var rd=document.createElement("div");rd.className="yz-row yz-r"+ri;', | 'if(k.type==="y")kd.classList.add("yz-gap");', |
| ' row.forEach(function(k){', | 'if(k.width===2)kd.classList.add("yz-w2");if(k.width===3)kd.classList.add("yz-w3");', |
| ' var kd=document.createElement("div");kd.className="yz-key";', | 'if(k.type.indexOf("s_")===0){kd.classList.add("yz-sp");', |
| ' if(k.type==="y")kd.classList.add("yz-gap");', | 'if(k.type==="s_shift"){kd.innerHTML=\'<span style="font-family:var('+JSON.stringify(fvar)+',\\"YzWrBoldFont\\",\\"YzWrFont\\"),sans-serif;font-size:17px">\'+(k.right||"\u21e7")+\'</span>\';if(shift>0)kd.classList.add("yz-sa");}', |
| ' if(k.width===2)kd.classList.add("yz-w2");', | 'else if(k.type==="s_case"){kd.innerHTML=\'<span style="font-family:var('+JSON.stringify(fvar)+',\\"YzWrBoldFont\\",\\"YzWrFont\\"),sans-serif;font-size:15px">BB|Bb|bb</span>\';if(caseLocked)kd.classList.add("yz-sa");}', |
| ' if(k.width===3)kd.classList.add("yz-w3");', | 'else if(k.type==="s_newline"){kd.innerText="\u21b5";}', |
| ' if(k.type.indexOf("s_")===0){', | 'else if(k.type==="s_space"){kd.innerText="\u23b5";}', |
| ' kd.classList.add("yz-sp");', | 'else{kd.innerHTML=\'<span style="font-family:var('+JSON.stringify(fvar)+',\\"YzWrBoldFont\\",\\"YzWrFont\\"),sans-serif">\'+(k.left||".")+\'</span>\';}', |
| ' if(k.type==="s_shift"){kd.innerText=k.right||"\u21e7";if(shift>0)kd.classList.add("yz-sa");}', | '}else{kd.innerHTML="<span>"+k.left+"</span><span>"+k.right+"</span>";}', |
| ' else if(k.type==="s_case"){kd.innerText=["Bb","BB","bb"][cm];}', | 'if(k.home)kd.classList.add("yz-home");', |
| ' else if(k.type==="s_newline"){kd.innerText="\u21b5";}', | 'kd.onclick=(function(kk){return function(ev){', |
| ' else if(k.type==="s_space"){kd.innerText="\u23b5";}', | 'if(kk.type==="s_shift"){var n=Date.now();if(n-lastTap<300)shift=2;else shift=shift===0?1:0;lastTap=n;render();}', |
| ' else{kd.innerText=k.left||".";}', | 'else if(kk.type==="s_case"){var n=Date.now(),r=this.getBoundingClientRect(),x=(ev&&typeof ev.clientX==="number")?ev.clientX-r.left:r.width/2,third=x<r.width/3?0:(x<2*r.width/3?1:2);if(caseLocked&&third===cm){caseLocked=false;cm=1;pend=0;}else if(n-lastCaseTap<300&&lastCaseThird===third){cm=third;caseLocked=true;pend=0;}else{caseLocked=false;cm=third;pend=cm===1?0:2;}lastCaseTap=n;lastCaseThird=third;render();}', |
| ' }else{', | 'else{var c=(shift>0&&kk.right!=="..")?kk.right:kk.left;', |
| ' kd.innerHTML="<span>"+k.left+"</span><span>"+k.right+"</span>";', | 'if(kk.type==="s_space")c=" ";if(kk.type==="s_newline")c="\\n";', |
| ' }', | 'if(kk.type==="s_dot")c=kk.left;if(c==="..")return;', |
| ' kd.onclick=(function(kk){return function(){', | 'typeC(c,"button");if(shift===1){shift=0;render();}}', |
| ' if(kk.type==="s_shift"){var n=Date.now();if(n-lastTap<300)shift=2;else shift=shift===0?1:0;lastTap=n;render();}', | '};})(k);rd.appendChild(kd);});kb.appendChild(rd);});bindTyping();}', |
| ' else if(kk.type==="s_case"){cm=(cm+1)%3;render();}', | |
| ' else{var c=(shift>0&&kk.right!=="..")?kk.right:kk.left;', | |
| ' if(kk.type==="s_space")c=" ";if(kk.type==="s_newline")c="\\n";', | |
| ' if(kk.type==="s_dot")c=kk.left;if(c==="..")return;', | |
| ' typeC(c);if(shift===1){shift=0;render();}}', | |
| ' };})(k);', | |
| ' rd.appendChild(kd);', | |
| ' });', | |
| ' kb.appendChild(rd);', | |
| ' });', | |
| '}', | |
| 'render();' | 'render();' |
| ].join('\n'); | ].join('\n'); |
| } | } |
| |
| function buildBarebonesHtml(stateJson) { | function buildBarebonesHtml(sj,themeId,era) { |
| var fu = YIV_FONT_URL; | var fu=YIV_FONT_URL, t=getThemeForExport(themeId); |
| var sc = buildKbRenderer('(' + stateJson + ')', 'yz-area', 'yz-kbd'); | var sc=buildKbRenderer('('+sj+')','yz-area','yz-kbd','--yz-font'); |
| | var eraMap = { ancient:'YzWr-Italic', present:'YzWrFont', modern:'YzWrBoldFont', choice:'YzWrBoldFont' }; |
| | var eraClassMap = { ancient:'ancient', present:'present', modern:'modern', choice:'modern' }; |
| | var ff = eraMap[era] || 'YzWrBoldFont'; |
| | var eraClass = eraClassMap[era] || 'modern'; |
| | var ks=51,kw2=ks*2+6,kw3=ks*3+10,step=Math.round(ks/2); |
| return [ | return [ |
| '<!DOCTYPE html>', | '<!DOCTYPE html><html lang="en"><head>', |
| '<html lang="en"><head>', | |
| '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">', | '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">', |
| '<title>Yivalese Keyboard</title><style>', | '<title>Yivalese Keyboard</title><style>', |
| '@font-face{font-family:"YzWrFont";src:url("' + fu + 'YzWr-Regular.woff2")format("woff2"),url("' + fu + 'YzWr-Regular.woff")format("woff");}', | '@font-face{font-family:"YzWrFont";src:url("'+fu+'YzWr-Regular.woff2")format("woff2"),url("'+fu+'YzWr-Regular.woff")format("woff"),url("'+fu+'YzWr-Regular.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| 'body{font-family:sans-serif;background:#fff;color:#222;padding:20px;max-width:900px;margin:0 auto;}', | '@font-face{font-family:"YzWr-Italic";src:url("'+fu+'YzWr-Italic.woff2")format("woff2"),url("'+fu+'YzWr-Italic.woff")format("woff"),url("'+fu+'YzWr-Italic.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| 'h2{margin:0 0 10px 0;font-size:18px;}', | '@font-face{font-family:"YzWrBoldFont";src:url("'+fu+'YzWr-Bold.woff2")format("woff2"),url("'+fu+'YzWr-Bold.woff")format("woff"),url("'+fu+'YzWr-Bold.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| '#yz-area{min-height:60px;font-family:"YzWrFont",sans-serif;font-size:28px;border:1px solid #ccc;border-radius:6px;padding:10px;margin-bottom:12px;outline:none;display:flex;flex-wrap:wrap;align-items:center;gap:2px;}', | ':root{--yz-font:"'+ff+'";}', |
| '.yz-ch{font-family:"YzWrFont",sans-serif;}', | 'body{font-family:sans-serif;background:'+t.bodyBg+';color:'+t.keyFg+';padding:12px;max-width:740px;margin:0 auto;display:flex;flex-direction:column;align-items:center;}', |
| '#yz-kbd{display:inline-flex;flex-direction:column;gap:4px;}', | 'body.font-ancient{--yz-font:"YzWr-Italic";}body.font-present{--yz-font:"YzWrFont";}body.font-modern{--yz-font:"YzWrBoldFont";}', |
| | 'h2{margin:0 0 14px;font-size:27px;color:'+t.headFg+';}', |
| | '#yz-area{min-height:75px;width:100%;max-width:705px;font-family:var(--yz-font), "YzWrBoldFont","YzWrFont",sans-serif;font-size:30px;color:'+t.areaFg+';border:2px solid '+t.areaBdr+';border-radius:8px;padding:12px;margin-bottom:14px;outline:none;background:'+t.areaBg+';display:flex;flex-wrap:wrap;align-items:center;gap:3px;}', |
| | '#yz-font-wrap{width:100%;max-width:705px;display:flex;align-items:center;gap:10px;margin-bottom:14px;}', |
| | '#yz-font-wrap label{font-size:12px;color:'+t.headFg+';}', |
| | '#yz-font-select{flex:1;min-width:0;padding:11px 14px;border:1px solid '+t.kbBdr+';background:'+t.areaBg+';color:'+t.areaFg+';border-radius:6px;font:inherit;font-size:16px;}', |
| | '.yz-ch{font-family:var(--yz-font),"YzWrBoldFont","YzWrFont",sans-serif;}', |
| | '#yz-kbd{display:inline-flex;flex-direction:column;gap:4px;align-self:center;}', |
| '.yz-row{display:flex;gap:4px;}', | '.yz-row{display:flex;gap:4px;}', |
| '.yz-r0{margin-left:0}.yz-r1{margin-left:18px}.yz-r2{margin-left:36px}.yz-r3{margin-left:18px}.yz-r4{margin-left:0}', | '.yz-r0{margin-left:0}.yz-r1{margin-left:'+step+'px}.yz-r2{margin-left:'+(step*2)+'px}.yz-r3{margin-left:'+step+'px}.yz-r4{margin-left:0}', |
| '.yz-key{width:34px;height:34px;background:#f0f0f0;border:1px solid #bbb;border-radius:4px;cursor:pointer;font-family:"YzWrFont",sans-serif;font-size:11px;display:flex;align-items:center;justify-content:space-around;user-select:none;box-sizing:border-box;flex-shrink:0;}', | '.yz-key{width:'+ks+'px;height:'+ks+'px;background:'+t.keyBg+';border:2px solid '+t.keyBdr+';border-radius:5px;cursor:pointer;font-family:var(--yz-font),"YzWrBoldFont","YzWrFont",sans-serif;font-size:29px;color:'+t.keyFg+';display:flex;align-items:center;justify-content:space-around;user-select:none;box-sizing:border-box;flex-shrink:0;}', |
| '.yz-key:hover{background:#ddd;}.yz-key:active{transform:translateY(1px);}', | '.yz-key:hover{filter:brightness(1.18);}.yz-key:active{transform:translateY(1px);}', |
| '.yz-gap{visibility:hidden;pointer-events:none;}.yz-w2{width:72px}.yz-w3{width:110px}', | '.yz-gap{visibility:hidden;pointer-events:none;}.yz-w2{width:'+kw2+'px}.yz-w3{width:'+kw3+'px}', |
| '.yz-sp{font-family:sans-serif;font-size:10px;color:#888;justify-content:center;}.yz-sa{color:navy;border-color:navy;}', | '.yz-home{background:'+t.homeBg+';border-color:'+t.homeBdr+';}', |
| '</style></head><body>', | '.yz-sp{font-family:var(--yz-font),sans-serif;font-size:18px;color:'+t.specFg+';justify-content:center;background:'+t.specBg+';border-color:'+t.specBdr+';}.yz-sa{color:#4a9eff;border-color:#4a9eff;}', |
| | '</style></head><body class="font-'+eraClass+'">', |
| '<h2>Yivalese Keyboard</h2>', | '<h2>Yivalese Keyboard</h2>', |
| '<div id="yz-area" contenteditable="true" spellcheck="false"></div>', | '<div id="yz-area" contenteditable="true" spellcheck="false"></div>', |
| | (era==='choice' |
| | ? '<div id="yz-font-wrap"><label for="yz-font-select">Era</label><select id="yz-font-select" onchange="setYzFont(this.value)"><option value="ancient">Ancient</option><option value="present">Present</option><option value="modern" selected>Modern</option></select></div>' |
| | : ''), |
| '<div id="yz-kbd"></div>', | '<div id="yz-kbd"></div>', |
| '<script>(function(){', sc, '})();<\/script>', | '<script>function setYzFont(v){var b=document.body;b.classList.remove("font-ancient","font-present","font-modern");b.classList.add("font-"+v);}setYzFont("'+eraClass+'");<\/script>', |
| '</body></html>' | '<script>(function(){'+sc+'})();<\/script>', |
| | '</'+'body></'+'html>' |
| ].join('\n'); | ].join('\n'); |
| } | } |
| |
| function buildMinimalistHtml(stateJson) { | function buildMinimalistHtml(sj,themeId,era) { |
| var fu = YIV_FONT_URL; | var fu=YIV_FONT_URL, t=getThemeForExport(themeId); |
| var sc = buildKbRenderer('(' + stateJson + ')', 'yz-area', 'yz-kbd'); | var sc=buildKbRenderer('('+sj+')','yz-area','yz-kbd','--yz-font'); |
| | var eraMap = { ancient:'YzWr-Italic', present:'YzWrFont', modern:'YzWrBoldFont', choice:'YzWrBoldFont' }; |
| | var eraClassMap = { ancient:'ancient', present:'present', modern:'modern', choice:'modern' }; |
| | var ff = eraMap[era] || 'YzWrBoldFont'; |
| | var eraClass = eraClassMap[era] || 'modern'; |
| | var ks=42,kw2=ks*2+4,kw3=ks*3+8,step=Math.round(ks/2); |
| return [ | return [ |
| '<!DOCTYPE html>', | '<!DOCTYPE html><html lang="en"><head>', |
| '<html lang="en"><head>', | |
| '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">', | '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">', |
| '<title>Yivalese Mini</title><style>', | '<title>Yivalese Mini</title><style>', |
| '@font-face{font-family:"YzWrFont";src:url("' + fu + 'YzWr-Regular.woff2")format("woff2"),url("' + fu + 'YzWr-Regular.woff")format("woff");}', | '@font-face{font-family:"YzWrFont";src:url("'+fu+'YzWr-Regular.woff2")format("woff2"),url("'+fu+'YzWr-Regular.woff")format("woff"),url("'+fu+'YzWr-Regular.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| 'body{font-family:sans-serif;background:#1a1a1a;color:#ccc;padding:10px;max-width:560px;margin:0 auto;}', | '@font-face{font-family:"YzWr-Italic";src:url("'+fu+'YzWr-Italic.woff2")format("woff2"),url("'+fu+'YzWr-Italic.woff")format("woff"),url("'+fu+'YzWr-Italic.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| '#yz-area{min-height:44px;font-family:"YzWrFont",sans-serif;font-size:22px;color:#e5cd9e;border:1px solid #444;border-radius:4px;padding:8px;margin-bottom:8px;outline:none;background:#111;display:flex;flex-wrap:wrap;align-items:center;gap:1px;}', | '@font-face{font-family:"YzWrBoldFont";src:url("'+fu+'YzWr-Bold.woff2")format("woff2"),url("'+fu+'YzWr-Bold.woff")format("woff"),url("'+fu+'YzWr-Bold.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| '.yz-ch{font-family:"YzWrFont",sans-serif;}', | ':root{--yz-font:"'+ff+'";}', |
| '#yz-kbd{display:inline-flex;flex-direction:column;gap:3px;background:#8a7b6e;padding:10px;border-radius:7px;border:2px solid #6a5b4e;}', | 'body{font-family:sans-serif;background:'+t.bodyBg+';color:'+t.keyFg+';padding:10px;max-width:630px;margin:0 auto;display:flex;flex-direction:column;align-items:center;}', |
| '.yz-row{display:flex;gap:3px;}', | 'body.font-ancient{--yz-font:"YzWr-Italic";}body.font-present{--yz-font:"YzWrFont";}body.font-modern{--yz-font:"YzWrBoldFont";}', |
| '.yz-r0{margin-left:0}.yz-r1{margin-left:14px}.yz-r2{margin-left:28px}.yz-r3{margin-left:14px}.yz-r4{margin-left:0}', | '#yz-area{min-height:51px;width:100%;max-width:450px;font-family:var(--yz-font),"YzWrBoldFont","YzWrFont",sans-serif;font-size:26px;color:'+t.areaFg+';border:1px solid '+t.areaBdr+';border-radius:4px;padding:8px;margin-bottom:9px;outline:none;background:'+t.areaBg+';display:flex;flex-wrap:wrap;align-items:center;gap:2px;}', |
| '.yz-key{width:27px;height:27px;background:#2a1808;border:1px solid #1a0f00;border-radius:3px;cursor:pointer;font-family:"YzWrFont",sans-serif;font-size:8px;color:#d4b878;display:flex;align-items:center;justify-content:space-around;user-select:none;box-sizing:border-box;flex-shrink:0;}', | '#yz-font-wrap{width:100%;max-width:450px;display:flex;align-items:center;gap:8px;margin-bottom:9px;}', |
| '.yz-key:hover{background:#3a2010;}.yz-key:active{transform:translateY(1px);}', | '#yz-font-wrap label{font-size:11px;color:'+t.headFg+';}', |
| '.yz-gap{visibility:hidden;pointer-events:none;}.yz-w2{width:57px}.yz-w3{width:87px}', | '#yz-font-select{flex:1;min-width:0;padding:8px 12px;border:1px solid '+t.kbBdr+';background:'+t.areaBg+';color:'+t.areaFg+';border-radius:5px;font:inherit;font-size:14px;}', |
| '.yz-sp{font-family:sans-serif;font-size:7px;color:#888;justify-content:center;}.yz-sa{color:#4a9eff;border-color:#4a9eff;}', | '.yz-ch{font-family:var(--yz-font),"YzWrBoldFont","YzWrFont",sans-serif;}', |
| '</style></head><body>', | '#yz-kbd{display:inline-flex;flex-direction:column;gap:3px;background:'+t.kbBg+';padding:12px;border-radius:9px;border:2px solid '+t.kbBdr+';align-self:center;}', |
| | '.yz-row{display:flex;gap:4px;}', |
| | '.yz-r0{margin-left:0}.yz-r1{margin-left:'+step+'px}.yz-r2{margin-left:'+(step*2)+'px}.yz-r3{margin-left:'+step+'px}.yz-r4{margin-left:0}', |
| | '.yz-key{width:'+ks+'px;height:'+ks+'px;background:'+t.keyBg+';border:1px solid '+t.keyBdr+';border-radius:3px;cursor:pointer;font-family:var(--yz-font),"YzWrBoldFont","YzWrFont",sans-serif;font-size:21px;color:'+t.keyFg+';display:flex;align-items:center;justify-content:space-around;user-select:none;box-sizing:border-box;flex-shrink:0;}', |
| | '.yz-key:hover{filter:brightness(1.18);}.yz-key:active{transform:translateY(1px);}', |
| | '.yz-gap{visibility:hidden;pointer-events:none;}.yz-w2{width:'+kw2+'px}.yz-w3{width:'+kw3+'px}', |
| | '.yz-home{background:'+t.homeBg+';border-color:'+t.homeBdr+';}', |
| | '.yz-sp{font-family:var(--yz-font),sans-serif;font-size:15px;color:'+t.specFg+';justify-content:center;background:'+t.specBg+';border-color:'+t.specBdr+';}.yz-sa{color:#4a9eff;border-color:#4a9eff;}', |
| | '</style></head><body class="font-'+eraClass+'">', |
| '<div id="yz-area" contenteditable="true" spellcheck="false"></div>', | '<div id="yz-area" contenteditable="true" spellcheck="false"></div>', |
| | (era==='choice' |
| | ? '<div id="yz-font-wrap"><label for="yz-font-select">Era</label><select id="yz-font-select" onchange="setYzFont(this.value)"><option value="ancient">Ancient</option><option value="present">Present</option><option value="modern" selected>Modern</option></select></div>' |
| | : ''), |
| '<div id="yz-kbd"></div>', | '<div id="yz-kbd"></div>', |
| '<script>(function(){', sc, '})();<\/script>', | '<script>function setYzFont(v){var b=document.body;b.classList.remove("font-ancient","font-present","font-modern");b.classList.add("font-"+v);}setYzFont("'+eraClass+'");<\/script>', |
| '</body></html>' | '<script>(function(){'+sc+'})();<\/script>', |
| | '</'+'body></'+'html>' |
| ].join('\n'); | ].join('\n'); |
| } | } |
| |
| function buildPopupFragment(stateJson) { | function buildPopupFragment(sj,themeId,era) { |
| var fu = YIV_FONT_URL; | var fu=YIV_FONT_URL, t=getThemeForExport(themeId); |
| var sc = buildKbRenderer('(' + stateJson + ')', 'yivFontEntry', 'yzp-kbd'); | var sc=buildKbRenderer('('+sj+')','yivFontEntry','yzp-kbd','--yiv-font'); |
| | var eraMap = { ancient:'ancient', present:'present', modern:'modern', choice:'modern' }; |
| | var popEra = eraMap[era] || 'modern'; |
| | var ks=54,kw2=ks*2+6,kw3=ks*3+10,step=Math.round(ks/2); |
| return [ | return [ |
| '<!-- Yivalese Popup Keyboard — paste this entire block into your HTML page.', | '<!DOCTYPE html><html lang="en"><head>', |
| ' To retarget typing: change id="yivFontEntry" on the div AND', | '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">', |
| ' the matching getElementById("yivFontEntry") in the script. -->', | '<title>Yivalese Popup Demo</title><style>', |
| '<style>', | '@font-face{font-family:"YzWrFont";src:url("'+fu+'YzWr-Regular.woff2")format("woff2"),url("'+fu+'YzWr-Regular.woff")format("woff"),url("'+fu+'YzWr-Regular.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| '@font-face{font-family:"YzWrFont";src:url("' + fu + 'YzWr-Regular.woff2")format("woff2"),url("' + fu + 'YzWr-Regular.woff")format("woff");}', | '@font-face{font-family:"YzWr-Italic";src:url("'+fu+'YzWr-Italic.woff2")format("woff2"),url("'+fu+'YzWr-Italic.woff")format("woff"),url("'+fu+'YzWr-Italic.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| '.yz-ch{font-family:"YzWrFont",sans-serif;}', | '@font-face{font-family:"YzWrBoldFont";src:url("'+fu+'YzWr-Bold.woff2")format("woff2"),url("'+fu+'YzWr-Bold.woff")format("woff"),url("'+fu+'YzWr-Bold.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', |
| '#yivFontEntry{min-height:50px;font-family:"YzWrFont",sans-serif;font-size:26px;color:#e5cd9e;border:1px solid #555;border-radius:6px;padding:10px;outline:none;display:flex;flex-wrap:wrap;align-items:center;gap:2px;background:#111;}', | ':root{--yiv-font:"YzWrBoldFont";}.yz-ch{font-family:var(--yiv-font,"YzWrBoldFont","YzWrFont"),sans-serif;}', |
| '#yiv-open-btn{padding:6px 13px;background:#3a2012;color:#e5cd9e;border:1px solid #5c3a26;border-radius:4px;cursor:pointer;font-size:13px;margin-top:6px;}', | 'body{font-family:sans-serif;background:'+t.bodyBg+';margin:0;padding:24px 16px;display:flex;flex-direction:column;align-items:center;min-height:100vh;box-sizing:border-box;}', |
| | 'body.font-ancient{--yiv-font:"YzWr-Italic";}body.font-present{--yiv-font:"YzWrFont";}body.font-modern{--yiv-font:"YzWrBoldFont";}', |
| | 'h2{margin:0 0 22px;color:'+t.headFg+';font-size:30px;}', |
| | '#yivFontEntry{min-height:78px;width:100%;max-width:645px;font-family:var(--yiv-font,"YzWrBoldFont","YzWrFont"),sans-serif;font-size:36px;color:'+t.areaFg+';border:2px solid '+t.areaBdr+';background:'+t.areaBg+';border-radius:8px;padding:15px;outline:none;white-space:pre-wrap;word-break:break-word;display:block;}', |
| | '#yivFontSelectWrap{width:100%;max-width:645px;display:flex;align-items:center;gap:8px;margin-top:10px;}', |
| | '#yivFontSelectLabel{font-size:13px;color:'+t.headFg+';white-space:nowrap;}', |
| | '#yiv-font-select{flex:1;min-width:0;padding:12px 14px;border:1px solid '+t.kbBdr+';background:'+t.areaBg+';color:'+t.areaFg+';border-radius:6px;font:inherit;font-size:16px;}', |
| | '#yiv-open-btn{margin-top:16px;padding:12px 28px;background:'+t.keyBg+';color:'+t.keyFg+';border:1px solid '+t.kbBdr+';border-radius:5px;cursor:pointer;font-size:21px;}', |
| | '#yiv-open-btn:hover{filter:brightness(1.15);}', |
| '#yiv-popup{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.82);z-index:9999;justify-content:center;align-items:center;}', | '#yiv-popup{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.82);z-index:9999;justify-content:center;align-items:center;}', |
| '#yiv-popup.open{display:flex;}', | '#yiv-popup.open{display:flex;}', |
| '#yiv-popup-inner{background:#b8a598;padding:14px;border-radius:10px;border:3px solid #8c7365;position:relative;}', | '#yiv-popup-inner{background:'+t.kbBg+';padding:14px 18px 14px;border-radius:12px;border:3px solid '+t.kbBdr+';position:relative;box-shadow:0 12px 40px rgba(0,0,0,0.4);}', |
| '#yiv-close-btn{position:absolute;top:4px;right:7px;background:none;border:none;font-size:18px;cursor:pointer;color:#555;}', | '#yiv-close-btn{position:absolute;top:6px;right:10px;background:none;border:none;font-size:28px;cursor:pointer;color:'+t.specFg+';}', |
| '#yzp-kbd{display:inline-flex;flex-direction:column;gap:4px;}', | '#yzp-kbd{display:inline-flex;flex-direction:column;gap:6px;}', |
| '.yz-row{display:flex;gap:4px;}', | '.yz-row{display:flex;gap:6px;}', |
| '.yz-r0{margin-left:0}.yz-r1{margin-left:18px}.yz-r2{margin-left:36px}.yz-r3{margin-left:18px}.yz-r4{margin-left:0}', | '.yz-r0{margin-left:0}.yz-r1{margin-left:'+step+'px}.yz-r2{margin-left:'+(step*2)+'px}.yz-r3{margin-left:'+step+'px}.yz-r4{margin-left:0}', |
| '.yz-key{width:34px;height:34px;background:#3a2012;border:2px solid #241107;border-radius:5px;cursor:pointer;font-family:"YzWrFont",sans-serif;font-size:11px;color:#e5cd9e;display:flex;align-items:center;justify-content:space-around;user-select:none;box-sizing:border-box;flex-shrink:0;}', | '.yz-key{width:'+ks+'px;height:'+ks+'px;background:'+t.keyBg+';border:2px solid '+t.keyBdr+';border-radius:5px;cursor:pointer;font-family:var(--yiv-font,"YzWrBoldFont","YzWrFont"),sans-serif;font-size:29px;color:'+t.keyFg+';display:flex;align-items:center;justify-content:space-around;user-select:none;box-sizing:border-box;flex-shrink:0;}', |
| '.yz-key:hover{background:#4a3020;}.yz-key:active{transform:translateY(1px);}', | '.yz-key:hover{filter:brightness(1.15);}.yz-key:active{transform:translateY(1px);}', |
| '.yz-gap{visibility:hidden;pointer-events:none;}.yz-w2{width:72px}.yz-w3{width:110px}', | '.yz-gap{visibility:hidden;pointer-events:none;}.yz-w2{width:'+kw2+'px}.yz-w3{width:'+kw3+'px}', |
| '.yz-sp{font-family:sans-serif;font-size:10px;color:#999;justify-content:center;}.yz-sa{color:#4a9eff;border-color:#4a9eff;}', | '.yz-home{background:'+t.homeBg+';border-color:'+t.homeBdr+';}', |
| '</style>', | '.yz-sp{font-family:var(--yiv-font,"YzWrBoldFont","YzWrFont"),sans-serif;font-size:18px;color:'+t.specFg+';justify-content:center;background:'+t.specBg+';border-color:'+t.specBdr+';}.yz-sa{color:#4a9eff;border-color:#4a9eff;}', |
| | '</style></head><body class="font-'+popEra+'">', |
| | '<h2>Yivalese</h2>', |
| '<div id="yivFontEntry" contenteditable="true" spellcheck="false"></div>', | '<div id="yivFontEntry" contenteditable="true" spellcheck="false"></div>', |
| | (era==='choice' |
| | ? '<div id="yivFontSelectWrap"><label id="yivFontSelectLabel" for="yiv-font-select">Font</label><select id="yiv-font-select" onchange="setDemoFont(this.value)"><option value="ancient">Ancient</option><option value="present">Present</option><option value="modern" selected>Modern</option></select></div>' |
| | : ''), |
| '<button id="yiv-open-btn" onclick="document.getElementById(\'yiv-popup\').classList.add(\'open\')">⌨️ Open Keyboard</button>', | '<button id="yiv-open-btn" onclick="document.getElementById(\'yiv-popup\').classList.add(\'open\')">⌨️ Open Keyboard</button>', |
| '<div id="yiv-popup">', | '<div id="yiv-popup"><div id="yiv-popup-inner">', |
| ' <div id="yiv-popup-inner">', | ' <button id="yiv-close-btn" onclick="document.getElementById(\'yiv-popup\').classList.remove(\'open\')">✕</button>', |
| ' <button id="yiv-close-btn" onclick="document.getElementById(\'yiv-popup\').classList.remove(\'open\')">✕</button>', | ' <div id="yzp-kbd"></div>', |
| ' <div id="yzp-kbd"></div>', | '</div></div>', |
| ' </div>', | '<script>function setDemoFont(v){var b=document.body;b.classList.remove("font-ancient","font-present","font-modern");b.classList.add("font-"+v);}setDemoFont("'+popEra+'");<\/script>', |
| '</div>', | '<script>(function(){'+sc+'})();<\/script>', |
| '<script>(function(){', sc, '})();<\/script>' | '</'+'body></'+'html>' |
| ].join('\n'); | ].join('\n'); |
| } | } |
| |
| window.onload = init; | if (document.readyState === 'loading') { |
| | document.addEventListener('DOMContentLoaded', init); |
| | } else { |
| | init(); |
| | } |
| </script> | </script> |
| </html> | </html> |
| | |