User Tools

Site Tools


font:keyboard

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
font:keyboard [2026/03/22 19:44] – created wikaraifont:keyboard [2026/03/25 01:11] (current) – external edit A User Not Logged in
Line 1: Line 1:
-===== Keyboard Layout Populator =====+===== 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.
 ---- ----
-<html><center><b>These 64 core glyphs form the foundation of the writing system</b><br/><br/>Select a key on the keyboard, then click a glyph on the grid to assign it<br/><b>Drag keys</bto rearrange them (Center Swap | Edges Insert).</center>+<html> 
 +<link rel="preload" href="https://atigerseye.com/src/font/YzWr-Bold.woff2" as="font" type="font/woff2" crossorigin> 
 +<link rel="preload" href="https://crow.work/src/font/YzWr-Regular.woff2" as="font" type="font/woff2" crossorigin> 
 +<link rel="preload" href="https://atigerseye.com/src/font/YzWr-Italic.woff2" as="font" type="font/woff2" crossorigin>
  
 <div id="yiv-global-wrapper" class="yiv-container"> <div id="yiv-global-wrapper" class="yiv-container">
Line 7: Line 11:
   <div class="yiv-controls-panel">   <div class="yiv-controls-panel">
     <div class="yiv-control-group">     <div class="yiv-control-group">
-      <strong>Font Style:</strong> +      <label style="cursor:pointer; color:#4a9eff; font-weight:bold;"> 
-      <label><input type="radio" name="fontStyle" value="regular" checked onclick="updateYivDisplay()"> Modern</label+        <input type="checkboxid="editModeToggleonchange="toggleEditMode()"> &#x1F6E0;&#xFE0F; Edit Mode 
-      <label><input type="radioname="fontStylevalue="italic" onclick="updateYivDisplay()"> Ancient</label> +      </label>
-      <label><input type="radio" name="fontStyle" value="bold" onclick="updateYivDisplay()"> Future</label>+
     </div>     </div>
-     
     <div class="yiv-control-group">     <div class="yiv-control-group">
-      <strong>Casing:</strong> +      <strong>Font:</strong> 
-      <label><input type="radio" name="textCase" value="defaultchecked onclick="updateYivDisplay()"> Yz</label> +      <label><input type="radio" name="fontStyle" value="italic" onclick="updateYivDisplay()"> Ancient</label> 
-      <label><input type="radio" name="textCase" value="upper" onclick="updateYivDisplay()"> YZ</label> +      <label><input type="radio" name="fontStyle" value="regular" onclick="updateYivDisplay()"> Present</label> 
-      <label><input type="radio" name="textCase" value="lower" onclick="updateYivDisplay()"> yz</label>+      <label><input type="radio" name="fontStyle" value="boldchecked onclick="updateYivDisplay()"> Modern</label
 +    </div> 
 +  </div> 
 + 
 +  <div style="text-align:center; margin-bottom:8px;"> 
 +    <span id="editInstructions" style="display:none; font-size:13px; color:#aaa;"> 
 +      Select a key on the keyboard, then click a glyph on the grid to assign it. 
 +      <br/><b>Drag keys</b> to rearrange them (Centre = Swap | Edges = Insert). 
 +    </span> 
 +  </div> 
 + 
 +  <div id="typingSection" class="typing-wrapper"> 
 +    <div class="type-area-container"> 
 +      <button id="copyBtn" onclick="copyTypingArea()">[copy]</button> 
 +      <div id="typeArea" contenteditable="true" spellcheck="false"></div>
     </div>     </div>
   </div>   </div>
  
-  <div class="populator-wrapper">+  <div id="editGridSection" class="populator-wrapper" style="display:none;">
     <div class="pop-grid-section">     <div class="pop-grid-section">
       <table class="yiv-table pop-table" id="popGlyphTable">       <table class="yiv-table pop-table" id="popGlyphTable">
Line 34: Line 50:
         <tr><th>N</th><td><span class="yiv-font">Nb</span></td><td><span class="yiv-font">Nd</span></td><td><span class="yiv-font">Ng</span></td><td><span class="yiv-font">Nl</span></td><td><span class="yiv-font">Nw</span></td><td><span class="yiv-font">Ny</span></td><td><span class="yiv-font">Nx</span></td><td><span class="yiv-font">Nn</span></td></tr>         <tr><th>N</th><td><span class="yiv-font">Nb</span></td><td><span class="yiv-font">Nd</span></td><td><span class="yiv-font">Ng</span></td><td><span class="yiv-font">Nl</span></td><td><span class="yiv-font">Nw</span></td><td><span class="yiv-font">Ny</span></td><td><span class="yiv-font">Nx</span></td><td><span class="yiv-font">Nn</span></td></tr>
       </table>       </table>
-    </div> 
- 
-    <div class="pop-kb-section"> 
-      <div class="kb-container" id="kbContainer"> 
-        </div> 
     </div>     </div>
   </div>   </div>
  
-  <div class="pop-output-section"> +  <div class="pop-kb-wrapper"> 
-    <h3>ASCII Output</h3> +    <div class="kb-containerid="kbContainer"></div>
-    <pre id="asciiOutput">Keyboard empty...</pre> +
-    <button onclick="clearKeyboard()" style="padding: 10px 20px; cursor: pointer; background: #444; color: white; border: none; border-radius: 4px;">Clear Keyboard</button>+
   </div>   </div>
  
-<style> +  <div id="diac-popupclass="diac-popup"></div>
-/* 1. Font Setup */ +
-@font-face { font-family: "YzWr-Italic"; src: url("https://crow.work/den/yivalkes/YzWr-Italic.woff2"); }+
  
-/* 2. Global Toggles */ +  <div class="yiv-case-opts"> 
-.italic-mode .yiv-font, .italic-mode .key-side { font-family: "YzWr-Italic", sans-serif !important; } +    <label><input type="checkbox" id="caseAutoReturnChk" onchange="caseAutoReturn=this.checkedchecked> Auto-return to middle case</label> 
-.bold-mode .yiv-font, .bold-mode .key-side { font-family: "YzWrBoldFont", sans-serif !important; } +    <label><input type="checkboxid="forceCaseTyping" checked> Manually type in selected case only</label> 
-.upper-mode .yiv-font, .upper-mode .key-side { text-transform: uppercase; } +  </div>
-.lower-mode .yiv-font, .lower-mode .key-side { text-transform: lowercase; }+
  
-/* 3. Layout */ +  <div class="yiv-tips-section"> 
-.yiv-container { width: 100%margin: 0 auto; color: inherit; } +    <p class="yiv-desc">Type Yivalese by clicking the keys above. Each key shows two glyphs &ndashunshifted (left) and shifted (right)The font selector switches between writing periods.</p> 
-.yiv-controls-panel { display: flex; justify-content: center; gap: 30px; margin-bottom: 20px; } +    <ul class="yiv-tips-list"> 
-.yiv-control-group { display: flex; gap: 10px; align-items: center; font-family: sans-seriffont-size: 14px+      <li>Double-click <span class="yiv-font yiv-tip-glyph">XY</span> to hold shift on (caps lock).</li> 
-.populator-wrapper { display: flex; flex-direction: row; flex-wrap: wrapgap: 40pxjustify-content: center; align-items: flex-start; }+      <li>Click the left / middle / right third of <span class="yiv-font yiv-tip-glyph">BB</span>&thinsp;|&thinsp;<span class="yiv-font yiv-tip-glyph">Bb</span>&thinsp;|&thinsp;<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> 
 +    </ul> 
 +  </div>
  
-/* 4. Grid Table *+  <details id="asciiDetails" class="yiv-details" open> 
-.pop-table { border-collapsecollapse; background: #2a2a2a; color: #eee+    <summary class="yiv-summary">&#x1F4CB; Import Export as ASCII</summary> 
-.pop-table td, .pop-table th { border: 1px solid #444; padding10px; text-align: centercursorpointer+    <div class="yiv-details-body"> 
-.pop-table td:hover { background: #4a9effcolor: white} +      <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 applyFormat: <code>[LeftGlyph RightGlyph]</code>. Special-key markers (<code>S_*</code>) are ignored on import.</p> 
-.pop-table .yiv-font { font-size24px; color: #4a9effpointer-events: none}+      <textarea id="asciiOutput" spellcheck="false" style="width:100%; max-width:100%; height:170px; background:#111; color:#4a9efffont-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:10pxdisplay:flex; gap:10px; flex-wrap:wrap; justify-content:center;"> 
 +        <button class="yiv-btn" onclick="importAscii()">&#x2B06;&#xFE0FImport ASCII</button> 
 +        <button class="yiv-btn" onclick="copyAscii()">&#x1F4CB; Copy ASCII</button> 
 +        <button class="yiv-btn" onclick="clearKeyboard()" style="background:#383838border-color:#555;">&#x1F504Reset Layout</button> 
 +      </div> 
 +    </div> 
 +  </details>
  
-/* 5Keyboard Geometry *+  <details id="exportDetails" class="yiv-details" open> 
-.kb-container { background: #b8a598padding: 25px; border-radius: 12px; display: flexflex-directioncolumngap8pxborder4px solid #8c7365} +    <summary class="yiv-summary">&#x1F4BE; Export to HTML</summary> 
-.kb-row { display: flexgap8pxalign-itemscenter}+    <div class="yiv-details-body"> 
 +      <div style="display:flex; gap:10px; margin-bottom:16px; flex-wrap:wrap;"> 
 +        <label class="yiv-export-opt"> 
 +          <input type="radio" name="exportMode" value="barebones" onchange="generateExport()"> 
 +          <div><strong>Barebones</strong><br><small>Compact standalone HTML fileBalanced sizing, easy to embed anywhere.</small></div> 
 +        </label> 
 +        <label class="yiv-export-opt"> 
 +          <input type="radio" name="exportMode" value="minimalist" onchange="generateExport()"> 
 +          <div><strong>Minimalist</strong><br><small>Ultra-compact PDA-style keyboardMicro sizing for tiny embeds.</small></div> 
 +        </label> 
 +        <label class="yiv-export-opt"> 
 +          <input type="radio" name="exportMode" value="popup" checked onchange="generateExport()"> 
 +          <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> 
 +      </div> 
 +      <div class="yiv-theme-row"> 
 +        <span style="color:#aaa;font-size:12px;">Theme:</span> 
 +        <button class="yiv-theme-btn active" data-theme="leather" onclick="setTheme('leather')">Leather</button> 
 +        <button class="yiv-theme-btn" data-theme="clean" onclick="setTheme('clean')">Clean</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 &#x25BE;</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">&#x1F441Live 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">&lt;/&gt; 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()">&#x1F4CBCopy 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> 
 +  </details>
  
-/* Staircase Indentation */ +</div>
-.kb-row-0 { padding-left: 20px; } +
-.kb-row-1 { padding-left: 40px; } +
-.kb-row-2 { padding-left: 60px; } +
-.kb-row-3 { padding-left: 40px; } +
-.kb-row-4 { padding-left: 20px; }+
  
-/* Keys */+<style> 
 +@font-face { 
 +  font-family: "YzWrFont"; 
 +  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.ttf") format("truetype"); 
 +  font-weight: normal; font-style: normal; font-display: swap; 
 +
 +@font-face { 
 +  font-family: "YzWr-Italic"; 
 +  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.ttf") format("truetype"); 
 +  font-weight: normal; font-style: normal; font-display: swap; 
 +
 +@font-face { 
 +  font-family: "YzWrBoldFont"; 
 +  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.ttf") format("truetype"); 
 +  font-weight: normal; font-style: normal; font-display: swap; 
 +
 +.yiv-font, .key-side { font-family: "YzWrFont", sans-serif; } 
 +.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; } 
 +.upper-mode .kb-container .key-side { text-transform: uppercase; } 
 +.lower-mode .kb-container .key-side { text-transform: lowercase; } 
 +.yiv-container { width: 100%; margin: 0 auto; color: inherit; font-family: sans-serif; position: relative; overflow: visible; } 
 +.yiv-controls-panel { 
 +  display: flex; justify-content: center; gap: 30px; margin-bottom: 18px; 
 +  background: #1a1a1a; padding: 12px 16px; border-radius: 8px; border: 1px solid #333; flex-wrap: wrap; 
 +
 +.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; } 
 +.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-wrapper { width: 100%; max-width: 700px; margin: 0 auto 22px auto; position: relative; z-index: 1; } 
 +.type-area-container { 
 +  position: relative; background: #181818; border: 2px solid #3a2012; 
 +  border-radius: 10px; padding: 28px 28px 22px; box-shadow: inset 0 5px 15px rgba(0,0,0,0.8); 
 +
 +#typeArea { 
 +  min-height: 70px; font-size: 34px; color: #e5cd9e; text-align: center; 
 +  display: block; outline: none; white-space: pre-wrap; line-height: 1.4; word-break: break-word; 
 +
 +#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.success { color: #4ade80 !important; } 
 +.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:hover { background: #4a9eff; color: white; } 
 +.pop-table .yiv-font { font-size: 20px; color: #4a9eff; pointer-events: none; } 
 +.kb-container { 
 +  background: #b8a598; padding: 19px; border-radius: 10px; 
 +  display: inline-flex; flex-direction: column; gap: 6px; 
 +  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);
 +.kb-row { display: flex; gap: 6px; align-items: center; justify-content: flex-start; } 
 +.kb-row-0 { margin-left: 0;    } 
 +.kb-row-1 { margin-left: 25px; } 
 +.kb-row-2 { margin-left: 50px; } 
 +.kb-row-3 { margin-left: 25px; } 
 +.kb-row-4 { margin-left: 0;    }
 .pop-key { .pop-key {
   background: #3a2012; color: #e5cd9e; border: 2px solid #241107;   background: #3a2012; color: #e5cd9e; border: 2px solid #241107;
-  border-radius: 8px; height: 60px; width: 60px;+  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;
-  font-size: 22px; cursor: grab; user-select: none; position: relative;+  cursor: pointer; user-select: none; position: relative; flex-shrink: 0;
   box-shadow: inset 0 2px 0 rgba(255,255,255,0.1), 0 3px 5px rgba(0,0,0,0.5);   box-shadow: inset 0 2px 0 rgba(255,255,255,0.1), 0 3px 5px rgba(0,0,0,0.5);
-  transition: transform 0.1s, border-color 0.2s;+  transition: transform 0.05s, border-color 0.2s, background 0.2s;
 } }
 +.pop-key:active { transform: translateY(2px); }
 +.edit-active .pop-key { cursor: grab; }
 .pop-key.home-marker { background: #5c3a26; border-color: #8c7365; } .pop-key.home-marker { background: #5c3a26; border-color: #8c7365; }
 .pop-key.gap { visibility: hidden; pointer-events: none; } .pop-key.gap { visibility: hidden; pointer-events: none; }
-.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-w2 { width: 94px; } 
-/* Key Sizes */ +.key-w3 { width: 144px; } 
-.key-w2 { width: 120px; } +.key-side { width: 45%; text-align: center; pointer-events: none; font-size: 22px; line-height: 1; } 
-.key-w3 { width: 180px; } +.key-side.empty { opacity: 0.15; font-family: sans-serif !important; font-size: 11px; } 
- +.key-shift { justify-content: flex-end; padding-right: 12px; } 
-/* Drag Indicators */+.key-shift .yiv-font { font-size: 24px; color: #888; } 
 +.shift-active .yiv-font  { color: #4a9eff !important; } 
 +.shift-locked            { background: #4a9eff !important; border-color: #2b7bc4 !important; } 
 +.shift-locked .yiv-font  { color: white !important; } 
 +.key-newline { font-size: 26px; color: #999; justify-content: center; } 
 +.key-newline span { font-family: "Times New Roman", serif; font-style: italic; } 
 +.key-space  { font-size: 18px; color: #777; justify-content: center; } 
 +.key-case   { width: 94px; background: #3a2012; border-color: #241107; gap: 3px; justify-content: center !important; padding: 0 3px; } 
 +.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.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; } 
 +.diac-popup-head { grid-column:-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: -6px; top: 0; bottom: 0; width: 4px; 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: -6px; top: 0; bottom: 0; width: 4px; 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; } 
- +.yiv-case-opts { 
-.key-side width45%; text-align: center; pointer-eventsnone; font-family"YzWrFont", sans-serif; line-height: 1; } +  display: flex; align-items: center; gap: 20px; flex-wrap: wrap; 
-.key-side.empty opacity: 0.15; font-familysans-serif; font-size: 11px; } +  justify-content: center; font-size: 12px; color: #888; padding: 6px 4px 12px; 
- +} 
-.pop-output-section { margin-top: 30px; width: 100%; background: #1e1e1e; color: #d4d4d4padding20px; border-radius: 8px; } +.yiv-case-opts label displayflex; align-items: center; gap: 5px; cursor: pointer; } 
-#asciiOutput { font-familymonospace; white-space: pre; font-size: 13px; line-height: 1.4; }+.yiv-tips-section { margin-top: 4px; padding: 14px 16px; background: #1a1a1a; border-radius: 8px; border: 1px solid #2a2a2a; } 
 +.yiv-desc { color: #999; font-size: 12px; margin: 0 0 10px 0; text-align: center; line-height1.5; } 
 +.yiv-tips-list { color: #aaa; font-size12px; margin: 0; padding-left: 18px; line-height: 1.8; } 
 +.yiv-tip-glyph { font-size: 18px; vertical-align: middle; color: #c8b07e; } 
 +.yiv-details margin-top10px; 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:hover { background: #5c3a26; } 
 +.yiv-btn-wide { width: 100%; max-width: 760px; display: inline-flex; justify-content: center; font-size15px; padding: 10px 16px; } 
 +.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 input   { margin-top: 3px; flex-shrink: 0; } 
 +.yiv-export-opt strong  { color: #ddd; font-size: 13px; } 
 +.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-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: #cccfont-size: 12px; line-height: 1.4; box-sizing: border-box; 
 +
 +.yiv-more-item:hover { background: #252525; } 
 +.yiv-more-item strong { color: #e5cd9edisplayblock; 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-size11px; 
 +  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>
  
 <script> <script>
-const layoutSchema = [+var YIV_FONT_URL = 'https://crow.work/src/font/'; 
 + 
 +var initStrings = [ 
 +  "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", 
 +  "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" 
 +]; 
 + 
 +var layoutSchema = [
   ['n','n','n','x','n','n','n','x','n','n','n'],   ['n','n','n','x','n','n','n','x','n','n','n'],
   ['n','n','n','x','n','y','n','x','n','n','n'],   ['n','n','n','x','n','y','n','x','n','n','n'],
   ['n','n','n','x','n','n','n','x','n','n','n'],   ['n','n','n','x','n','n','n','x','n','n','n'],
   ['n','n','n','x','n','n','n','x','n','n','n'],   ['n','n','n','x','n','n','n','x','n','n','n'],
-  [{w:2},{w:2},{w:3},{w:2},{w:2}]+  [{w:2,type:'s_dot'},{w:2,type:'s_shift'},{w:3,type:'s_space'},{w:2,type:'s_newline'},{w:2,type:'s_case'}]
 ]; ];
  
-let kbState = JSON.parse(sessionStorage.getItem('yivTypewriter')) || []; +var kbState        = JSON.parse(sessionStorage.getItem('yivTypewriter')) || []; 
-let activeKeyId = null; +var isEditMode     = false; 
-let activeSide = 'left';+var activeKeyId    = null; 
 +var activeSide     = 'left'; 
 +var shiftMode      = 0; 
 +var lastShiftTap   = 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 (kbState.length === 0) resetState();+  if (yivInitialized) return; 
 +  yivInitialized = true; 
 +  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;
   buildKeyboard();   buildKeyboard();
   updateAsciiOutput();   updateAsciiOutput();
   updateYivDisplay();   updateYivDisplay();
 +  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 resetState() { +function normalizeHomeMarkers() { 
-  kbState layoutSchema.map((row, r) => row.map((key, k) =({ +  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 }; 
-    id`r${r}k${k}`, +  kbState.forEach(function(row, r) 
-    type: key.type || key +    row.forEach(function(ki) { 
-    width: key.w || 1, +      if (!|| typeof k !== 'object'return; 
-    left: '..', +      if (k.type === 'y' || keyTypeIsSpecial(k)) 
-    right: '..' +        k.home = false; 
-  })));+      } else { 
 +        k.home = !!homeMap[r+':'+i]; 
 +      } 
 +    }); 
 +  }); 
 +
 + 
 +function parseDefaultStrings() { 
 +  kbState = layoutSchema.map(function(rowSchema, r{ 
 +    var rowData = < 4 ? initStrings[r].split('; ') : []; 
 +    return rowSchema.map(function(item, k{ 
 +      var key = { id:'r'+r+'k'+k, type: typeof item==='string'?item:item.type, width:item.w||1, left:'..', right:'..', home:false }; 
 +      if (r < 4) { 
 +        var raw = rowData[k] || '.. ..'; 
 +        if (raw.indexOf('[') !== -1) { key.home = true; raw = raw.replace(/[\[\]]/g, '');
 +        if (raw === 'BLANK') { key.type = 'y';
 +        else { var pts = raw.split(' '); key.left = pts[0]||'..'; key.right = pts[1]||'..';
 +      } else { 
 +        if (key.type === 's_dot'    key.left  = '.'; 
 +        if (key.type === 's_shift'  key.right = 'XY'; 
 +        if (key.type === 's_newline') key.left  = '\u2014'; 
 +      } 
 +      return key; 
 +    })
 +  })
 +
 + 
 +function toggleEditMode() { 
 +  isEditMode = document.getElementById('editModeToggle').checked; 
 +  var wrapper = document.getElementById('yiv-global-wrapper'); 
 +  if (isEditMode) { 
 +    wrapper.classList.add('edit-active'); 
 +    document.getElementById('editGridSection').style.display  = 'flex'; 
 +    document.getElementById('typingSection').style.display    = 'none'; 
 +    document.getElementById('editInstructions').style.display = 'inline'; 
 +  } else { 
 +    wrapper.classList.remove('edit-active'); 
 +    document.getElementById('editGridSection').style.display  = 'none'; 
 +    document.getElementById('typingSection').style.display    = 'block'; 
 +    document.getElementById('editInstructions').style.display = 'none'; 
 +    activeKeyId = null; 
 +  } 
 +  closeDiacPopup(); 
 +  buildKeyboard(); 
 +  fitKeyboard();
 } }
  
 function buildKeyboard() { function buildKeyboard() {
-  const container = document.getElementById('kbContainer');+  var container = document.getElementById('kbContainer');
   container.innerHTML = '';   container.innerHTML = '';
-   +  kbState.forEach(function(row, r) { 
-  kbState.forEach((row, r) => +    var rowDiv = document.createElement('div'); 
-    const rowDiv = document.createElement('div'); +    rowDiv.className = 'kb-row kb-row-' + r; 
-    rowDiv.className = `kb-row kb-row-${r}`; +    row.forEach(function(k) { 
-     +      var kd = document.createElement('div'); 
-    row.forEach((keyData, k) => +      kd.className = 'pop-key'; 
-      const keyDiv = document.createElement('div'); +      if (k.home)         kd.classList.add('home-marker'); 
-      keyDiv.className = 'pop-key'; +      if (k.type === 'y'kd.classList.add('gap'); 
-       +      if (k.width === 2)  kd.classList.add('key-w2'); 
-      // STATIC MARKER LOGIC: Columns 3 and 7 (4th and 8th keysare markers +      if (k.width === 3 kd.classList.add('key-w3'); 
-      if (k === 3 || k === 7keyDiv.classList.add('home-marker'); +      kd.id = k.id; 
-       +      if (k.type === 's_dot'
-      if (keyData.type === 'y'keyDiv.classList.add('gap'); +        kd.classList.add('key-dot')
-      if (keyData.width === 2keyDiv.classList.add('key-w2'); +        diacAnchorId = kd.id; 
-      if (keyData.width === 3keyDiv.classList.add('key-w3'); +        kd.innerHTML = '<span class="yiv-font" style="font-size:24px;color:#aaa;">'+diacMode+'</span>'
-       +                       '<span class="diac-hotzone"></span>'
-      keyDiv.id keyData.id+                       '<span class="diac-triangle"></span>'
-      keyDiv.draggable true+      } else if (k.type === 's_shift'
-       +        kd.classList.add('key-shift')
-      keyDiv.innerHTML = ` +        if (shiftMode===1) kd.classList.add('shift-active'); 
-        <div class="key-side left ${keyData.left === '..' ? 'empty' : ''}">${keyData.left}</div> +        if (shiftMode===2) kd.classList.add('shift-locked'); 
-        <div class="key-side right ${keyData.right === '..' ? 'empty' : ''}">${keyData.right}</div> +        kd.innerHTML = '<span class="yiv-font">'+k.right+'</span>'
-      `+      } else if (k.type === 's_newline'
-       +        kd.classList.add('key-newline'); 
-      keyDiv.onclick = () => selectKey(keyDiv); +        kd.innerHTML = '<span>'+k.left+'</span>'; 
-      addDragListeners(keyDiv); +      } else if (k.type === 's_space') { 
-      rowDiv.appendChild(keyDiv);+        kd.classList.add('key-space')
 +        kd.innerHTML '<span style="font-family:sans-serif;">\u23b5</span>'
 +      } else if (k.type === 's_case') { 
 +        kd.classList.add('key-case'); 
 +        kd.innerHTML = ['BB','Bb','bb'].map(function(c,i){ 
 +          var cls = 'case-lbl yiv-font'; 
 +          if (i===caseMode) cls += ' active'; 
 +          if (caseLocked && i===caseMode) cls += ' locked-case'; 
 +          return '<span class="'+cls+'">'+c+'</span>'; 
 +        }).join('<span class="case-sep">|</span>'); 
 +      } else { 
 +        kd.innerHTML = 
 +          '<div class="key-side yiv-font'+(k.left==='..'?' empty':'')+'">'+k.left+'</div>'+ 
 +          '<div class="key-side yiv-font'+(k.right==='..'?' empty':'')+'">'+k.right+'</div>'
 +      } 
 +      kd.onclick = (function(kdata,el){ return function(e){ handleKeyPress(kdata,el,e); }; })(k,kd); 
 +      if (isEditMode) addDragListeners(kd); 
 +      rowDiv.appendChild(kd);
     });     });
     container.appendChild(rowDiv);     container.appendChild(rowDiv);
   });   });
-   +  if (isEditMode && activeKeyId) { 
-  if (activeKeyId) { +    var ae = document.getElementById(activeKeyId); 
-     const activeEl = document.getElementById(activeKeyId); +    if (aeae.classList.add('active-target');
-     if (activeElactiveEl.classList.add('active-target');+
   }   }
 +  positionDiacPopup();
 } }
  
-function selectKey(el) { +function handleKeyPress(k, el, e) { 
-  document.querySelectorAll('.pop-key').forEach(k => k.classList.remove('active-target')); +  if (isEditMode) { 
-  activeKeyId = el.id; +    if (keyTypeIsSpecial(k)) return; 
-  activeSide = 'left'; +    document.querySelectorAll('.pop-key').forEach(function(x){ x.classList.remove('active-target'); }); 
-  el.classList.add('active-target');+    activeKeyId = el.id; activeSide = 'left'; 
 +    el.classList.add('active-target'); 
 +    return; 
 +  } 
 +  if (k.type === 's_shift') { 
 +    var nowS = Date.now(); 
 +    if (nowS - lastShiftTap < 300) { shiftMode = 2; } 
 +    else { shiftMode = shiftMode===0 ? 1 : 0; } 
 +    lastShiftTap = nowS; buildKeyboard(); return; 
 +  } 
 +  if (k.type === 's_case') { 
 +    var nowC = Date.now(); 
 +    var rect = el.getBoundingClientRect(); 
 +    var cx = e.clientX - rect.left; 
 +    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 = ''; 
 +  if      (k.type==='s_space'  charToType = ' '; 
 +  else if (k.type==='s_newline') charToType = '\n'; 
 +  else if (k.type==='s_dot') { 
 +    var popup = document.getElementById('diac-popup'); 
 +    if (e && e.target && (e.target.classList.contains('diac-triangle')||e.target.classList.contains('diac-hotzone')||e.target===popup)) { 
 +      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, 'button'); 
 +  if (shiftMode===1) { shiftMode=0; buildKeyboard(); }
 } }
  
-// Glyph Grid Interaction +function toggleDiacPopup(e) { 
-document.querySelectorAll('#popGlyphTable td').forEach(td =+  if (e) e.stopPropagation(); 
-  td.onclick = function() { +  var popup = document.getElementById('diac-popup'); 
-    if (!activeKeyId) return; +  if (!popup) return; 
-    const glyph this.querySelector('span').innerText+  var isOpen = popup.classList.contains('open'); 
-    const [rk] activeKeyId.match(/\d+/g).map(Number); +  closeDiacPopup(); 
-     +  if (!isOpen) { 
-    kbState[r][k][activeSide] glyph+    popup.innerHTML = ''; 
-     +    var head = document.createElement('div'); 
-    if (activeSide === 'left') { +    head.className = 'diac-popup-head'; head.textContent = 'Diacritics'; popup.appendChild(head); 
-      activeSide = 'right'; +    var note1 = document.createElement('div'); 
-    } else { +    note1.className = 'diac-popup-note'; 
-      // Auto-Jump Logic +    note1.textContent = 'Dot: meaningful/logographic/phrase-end\nDouble: redup./longer-end/pause\nTriple: tone/abrupt-end/long-pause'; 
-      findNextAvailableKey(rk);+    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() { 
 +  var w = document.getElementById('yiv-global-wrapper'); 
 +  w.classList.remove('upper-mode','lower-mode'); 
 +  if      (caseMode===0) w.classList.add('upper-mode'); 
 +  else if (caseMode===2) w.classList.add('lower-mode'); 
 +
 + 
 +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'); 
 +  if (c==='\n') { insertAtCursor(area, document.createElement('br')); return; } 
 +  if (c===' '||c==='\u00a0') { insertAtCursor(area, document.createTextNode('\u00a0')); return; } 
 +  var fc = c; 
 +  if      (caseMode===0) fc = c.toUpperCase(); 
 +  else if (caseMode===1) fc = c.charAt(0).toUpperCase()+c.slice(1).toLowerCase(); 
 +  else                   fc = c.toLowerCase(); 
 +  var span = document.createElement('span'); span.className='yiv-font'; span.innerText=fc; 
 +  insertAtCursor(areaspan)
 +  if (caseAutoReturn && !caseLocked && caseMode!==1) { 
 +    if (casePendingChars > 0) { 
 +      if (source === 'manual') casePendingChars--; 
 +      else casePendingChars = 0;
     }     }
-    +    if (casePendingChars > 0) return; 
 +    caseMode=1; updateCaseDisplay(); buildKeyboard(); 
 +  } 
 +
 + 
 +function copyTypingArea() { 
 +  navigator.clipboard.writeText(document.getElementById('typeArea').innerText).then(function() { 
 +    var btn=document.getElementById('copyBtn'); 
 +    btn.innerText='[copied!]'; btn.classList.add('success'); 
 +    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) { 
 +  td.onclick = function() { 
 +    if (!activeKeyId||!isEditMode) return; 
 +    var glyph = this.querySelector('span').innerText; 
 +    var coords = activeKeyId.match(/\d+/g).map(Number); 
 +    var r=coords[0], ki=coords[1]; 
 +    kbState[r][ki][activeSide] = glyph; 
 +    if (activeSide==='left') { activeSide='right'; } else { findNextAvailableKey(r,ki); }
     saveAndRefresh();     saveAndRefresh();
   };   };
Line 208: Line 947:
  
 function findNextAvailableKey(currR, currK) { function findNextAvailableKey(currR, currK) {
-    let flattened = kbState.flat()+  var flat = kbState.reduce(function(a,b){return a.concat(b);},[]); 
-    let currIdx = kbState[currR].indexOf(kbState[currR][currK]); +  var gi=0; 
-    let globalIdx = 0; +  for (var r=0;r<currR;r++) gi+=kbState[r].length; 
-    for(let r=0; r<currR; r++) globalIdx += kbState[r].length; +  gi+=currK; activeKeyId=null; 
-    globalIdx += currIdx; +  for (var i=gi+1;i<flat.length;i++) { 
- +    if (flat[i].type!=='y' && !keyTypeIsSpecial(flat[i]) && (flat[i].left==='..'||flat[i].right==='..')) { 
-    activeKeyId = null;  +      activeKeyId=flat[i].id; activeSide=flat[i].left==='..'?'left':'right'; return;
-     +
-    // Search forward from current +
-    for (let i = globalIdx + 1; i < flattened.length; i++) { +
-        if (flattened[i].type !== 'y' && (flattened[i].left === '..' || flattened[i].right === '..')) { +
-            activeKeyId = flattened[i].id; +
-            activeSide = (flattened[i].left === '..') ? 'left' : 'right'; +
-            return; +
-        } +
-    } +
-    // If none found forward, search from start +
-    for (let i = 0; i < globalIdx; i++) { +
-        if (flattened[i].type !== 'y' && (flattened[i].left === '..' || flattened[i].right === '..')) { +
-            activeKeyId = flattened[i].id; +
-            activeSide = (flattened[i].left === '..'? 'left' : 'right'; +
-            return; +
-        }+
     }     }
 +  }
 } }
  
-// Drag & Drop Logic +var draggedId=null;
-let draggedId = null; +
 function addDragListeners(el) { function addDragListeners(el) {
-  el.ondragstart = (e) => { draggedId = e.target.id; e.dataTransfer.setData('text', e.target.id); }; +  el.draggable=true; 
-  el.ondragover = (e) => {+  el.ondragstart=function(e){draggedId=e.target.id;e.dataTransfer.setData('text',e.target.id);}; 
 +  el.ondragover=function(e){
     e.preventDefault();     e.preventDefault();
-    const rect = el.getBoundingClientRect()+    var rect=el.getBoundingClientRect(),x=e.clientX-rect.left; 
-    const 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 = () => 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 = (e) => {+  el.ondrop=function(e){
     e.preventDefault();     e.preventDefault();
-    const targetId = el.id; +    var tId=el.id; if(draggedId===tId)return; 
-    if (draggedId === targetId) return; +    var rect=el.getBoundingClientRect(),x=e.clientX-rect.left; 
-    const rect = el.getBoundingClientRect()+    var src=draggedId.match(/\d+/g).map(Number); 
-    const x = e.clientX - rect.left; +    var tgt=tId.match(/\d+/g).map(Number); 
-    const sourceCoords = draggedId.match(/\d+/g).map(Number); +    var srcKey=kbState[src[0]].splice(src[1],1)[0]; 
-    const targetCoords targetId.match(/\d+/g).map(Number); +    var ins=tgt[1]; 
-     +    if(src[0]===tgt[0]&&src[1]<tgt[1])ins=Math.max(0,ins); 
-    const sourceKey = kbState[sourceCoords[0]].splice(sourceCoords[1], 1)[0]; +    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);} 
-    // Recalculate target index if same row +    else{ 
-    let insertIdx targetCoords[1]; +      var tk=kbState[tgt[0]][ins],tc={left:tk.left,right:tk.right}; 
-    if (sourceCoords[0] === targetCoords[0] && sourceCoords[1] < targetCoords[1]) +      tk.left=srcKey.left;tk.right=srcKey.right; 
-        insertIdx = Math.max(0, insertIdx);  +      kbState[src[0]].splice(src[1],0,srcKey); 
-    } +      kbState[src[0]][src[1]].left=tc.left;kbState[src[0]][src[1]].right=tc.right;
- +
-    if (x < rect.width * 0.25) {  +
-       kbState[targetCoords[0]].splice(insertIdx, 0, sourceKey); +
-    else if (x > rect.width * 0.75) { +
-       kbState[targetCoords[0]].splice(insertIdx + 1, 0, sourceKey); +
-    else {  +
-       const targetKeyRef = kbState[targetCoords[0]][insertIdx]+
-       const targetContent = { left: targetKeyRef.left, right: targetKeyRef.right }; +
-       targetKeyRef.left = sourceKey.left; +
-       targetKeyRef.right = sourceKey.right; +
-       kbState[sourceCoords[0]].splice(sourceCoords[1], 0, sourceKey); +
-       kbState[sourceCoords[0]][sourceCoords[1]].left = targetContent.left; +
-       kbState[sourceCoords[0]][sourceCoords[1]].right = targetContent.right;+
     }     }
     saveAndRefresh();     saveAndRefresh();
Line 284: Line 993:
  
 function saveAndRefresh() { function saveAndRefresh() {
-  kbState.forEach((row, r) => row.forEach((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=['','  ','    ','  ',''];
 function updateAsciiOutput() { function updateAsciiOutput() {
-  let out = ""+  var out=''
-  kbState.forEach((row, r) => +  kbState.forEach(function(row,r){ 
-    let line =  ".repeat(r)+    var line=ASCII_INDENTS[r]||''
-    row.forEach(k => +    row.forEach(function(k)
-      if (k.type === 'y') line +=      "+      if(k.type==='y')line+=     '
-      else if (k.width === 3) line += " SPACE  "+      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 + "\n";+    out+=line.replace(/\s+$/,'')+'\n';
   });   });
-  document.getElementById('asciiOutput').innerText = out;+  document.getElementById('asciiOutput').value=out
 +  fitAsciiBox(); 
 +
 + 
 +function importAscii() { 
 +  var text=document.getElementById('asciiOutput').value; 
 +  var blocks=[],re=/\[(.*?)\]/g,m; 
 +  while((m=re.exec(text))!==null)blocks.push(m[1].trim()); 
 +  var bi=0; 
 +  kbState.forEach(function(row){row.forEach(function(k){ 
 +    if(k.type==='y'||keyTypeIsSpecial(k))return; 
 +    if(bi>=blocks.length)return; 
 +    var block=blocks[bi++]; 
 +    if(block.indexOf('S_')===0)return; 
 +    var pts=block.split(/\s+/);k.left=pts[0]||'..';k.right=pts[1]||'..'; 
 +  });}); 
 +  saveAndRefresh(); alert('Layout imported!'); 
 +
 + 
 +function copyAscii() { 
 +  navigator.clipboard.writeText(document.getElementById('asciiOutput').value).then(function(){alert('ASCII copied!');});
 } }
  
 function clearKeyboard() { function clearKeyboard() {
-  if (confirm("Reset layout to default?")) { resetState(); saveAndRefresh(); }+  if(confirm('Reset layout to default?')){sessionStorage.removeItem('yivTypewriter');parseDefaultStrings();saveAndRefresh();}
 } }
  
-function updateYivDisplay() { +function updateYivDisplay(keepEraChoice) { 
-  const wrapper = document.getElementById("yiv-global-wrapper"); +  var w=document.getElementById('yiv-global-wrapper'); 
-  const fontStyle = document.querySelector('input[name="fontStyle"]:checked').value; +  var val=document.querySelector('input[name="fontStyle"]:checked').value; 
-  const textCase = document.querySelector('input[name="textCase"]:checked').value; +  w.classList.remove('italic-mode','bold-mode'); 
-  wrapper.className = "yiv-container"+  if(val==='italic')w.classList.add('italic-mode'); 
-  if (fontStyle === "italic"wrapper.classList.add("italic-mode"); +  else if(val==='bold')w.classList.add('bold-mode'); 
-  else if (fontStyle === "bold"wrapper.classList.add("bold-mode"); +  if (!(keepEraChoice && currentEra==='choice')) { 
-  if (textCase === "upper"wrapper.classList.add("upper-mode"); +    if (val==='italic'currentEra='ancient'
-  else if (textCase === "lower"wrapper.classList.add("lower-mode");+    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();
 } }
  
-window.onload = init;+function copySnippet() { 
 +  var txt=document.getElementById('htmlSnippet').value; 
 +  var minToggle=document.getElementById('minifyCodeToggle'); 
 +  var btn=document.getElementById('copySnippetBtn'); 
 +  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='&#x2705; Copied'; 
 +    btn.classList.add('is-copied'); 
 +    setTimeout(function(){ 
 +      btn.innerHTML='&#x1F4CB; Copy Code'; 
 +      btn.classList.remove('is-copied'); 
 +    }, 1800); 
 +  }); 
 +
 + 
 +function escapeHtml(str) { 
 +  return str 
 +    .replace(/&/g, '&amp;'
 +    .replace(/</g, '&lt;'
 +    .replace(/>/g, '&gt;'); 
 +
 + 
 +function escapeHtmlAttr(str) { 
 +  return String(str) 
 +    .replace(/&/g, '&amp;'
 +    .replace(/"/g, '&quot;'
 +    .replace(/</g, '&lt;'
 +    .replace(/>/g, '&gt;'); 
 +
 + 
 +function prettyIndent(level) { 
 +  return '  '.repeat(Math.max(0, level || 0)); 
 +
 + 
 +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(/(&lt;\/?)([a-zA-Z0-9-]+)([^&]*?)(\/?&gt;)/g, function(_, a, tag, attrs, z) { 
 +    var hiAttrs = attrs.replace(/([a-zA-Z-:]+)=(&quot;.*?&quot;|'[^']*')/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() { 
 +  if(!kbState.length)return; 
 +  var modeEl=document.querySelector('input[name="exportMode"]:checked'); 
 +  var mode=modeEl?modeEl.value:'popup'; 
 +  var sj=JSON.stringify(kbState); 
 +  var out; 
 +  if(mode==='barebones')out=buildBarebonesHtml(sj,currentTheme,currentEra); 
 +  else if(mode==='minimalist')out=buildMinimalistHtml(sj,currentTheme,currentEra); 
 +  else out=buildPopupFragment(sj,currentTheme,currentEra); 
 +  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,fontVar) { 
 +  var fvar = fontVar || '--yiv-font'; 
 +  return [ 
 +    'var st='+stateExpr+',shift=0,cm=1,lastTap=0,lastCaseTap=0,lastCaseThird=-1,caseLocked=false,pend=0;', 
 +    'function applyCase(c){if(cm===0)return c.toUpperCase();if(cm===1)return c.charAt(0).toUpperCase()+c.slice(1).toLowerCase();return c.toLowerCase();}', 
 +    '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;}', 
 +    'function typeC(c,src){var a=document.getElementById("'+areaId+'");if(!a)return;', 
 +    'if(c==="\\n"){a.appendChild(document.createElement("br"));if(consumeCase(src||"button"))render();return;}', 
 +    'if(c===" "){a.appendChild(document.createTextNode(" "));if(consumeCase(src||"button"))render();return;}', 
 +    '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(){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;', 
 +    'row.forEach(function(k){var kd=document.createElement("div");kd.className="yz-key";', 
 +    'if(k.type==="y")kd.classList.add("yz-gap");', 
 +    'if(k.width===2)kd.classList.add("yz-w2");if(k.width===3)kd.classList.add("yz-w3");', 
 +    'if(k.type.indexOf("s_")===0){kd.classList.add("yz-sp");', 
 +    '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");}', 
 +    '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");}', 
 +    'else if(k.type==="s_newline"){kd.innerText="\u21b5";}', 
 +    'else if(k.type==="s_space"){kd.innerText="\u23b5";}', 
 +    'else{kd.innerHTML=\'<span style="font-family:var('+JSON.stringify(fvar)+',\\"YzWrBoldFont\\",\\"YzWrFont\\"),sans-serif">\'+(k.left||".")+\'</span>\';}', 
 +    '}else{kd.innerHTML="<span>"+k.left+"</span><span>"+k.right+"</span>";}', 
 +    'if(k.home)kd.classList.add("yz-home");', 
 +    'kd.onclick=(function(kk){return function(ev){', 
 +    '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 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{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,"button");if(shift===1){shift=0;render();}}', 
 +    '};})(k);rd.appendChild(kd);});kb.appendChild(rd);});bindTyping();}', 
 +    'render();' 
 +  ].join('\n'); 
 +
 + 
 +function buildBarebonesHtml(sj,themeId,era) { 
 +  var fu=YIV_FONT_URL, t=getThemeForExport(themeId); 
 +  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 [ 
 +    '<!DOCTYPE html><html lang="en"><head>', 
 +    '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">', 
 +    '<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"),url("'+fu+'YzWr-Regular.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', 
 +    '@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;}', 
 +    '@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;}', 
 +    ':root{--yz-font:"'+ff+'";}', 
 +    '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;}', 
 +    '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-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: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{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: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>', 
 +    '<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>', 
 +    '<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>', 
 +    '<script>(function(){'+sc+'})();<\/script>', 
 +    '</'+'body></'+'html>' 
 +  ].join('\n'); 
 +
 + 
 +function buildMinimalistHtml(sj,themeId,era) { 
 +  var fu=YIV_FONT_URL, t=getThemeForExport(themeId); 
 +  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 [ 
 +    '<!DOCTYPE html><html lang="en"><head>', 
 +    '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">', 
 +    '<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"),url("'+fu+'YzWr-Regular.ttf")format("truetype");font-weight:normal;font-style:normal;font-display:swap;}', 
 +    '@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;}', 
 +    '@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;}', 
 +    ':root{--yz-font:"'+ff+'";}', 
 +    '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;}', 
 +    'body.font-ancient{--yz-font:"YzWr-Italic";}body.font-present{--yz-font:"YzWrFont";}body.font-modern{--yz-font:"YzWrBoldFont";}', 
 +    '#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-font-wrap{width:100%;max-width:450px;display:flex;align-items:center;gap:8px;margin-bottom:9px;}', 
 +    '#yz-font-wrap label{font-size:11px;color:'+t.headFg+';}', 
 +    '#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-ch{font-family:var(--yz-font),"YzWrBoldFont","YzWrFont",sans-serif;}', 
 +    '#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>', 
 +    (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>', 
 +    '<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>', 
 +    '<script>(function(){'+sc+'})();<\/script>', 
 +    '</'+'body></'+'html>' 
 +  ].join('\n'); 
 +
 + 
 +function buildPopupFragment(sj,themeId,era) { 
 +  var fu=YIV_FONT_URL, t=getThemeForExport(themeId); 
 +  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 [ 
 +    '<!DOCTYPE html><html lang="en"><head>', 
 +    '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">', 
 +    '<title>Yivalese Popup Demo</title><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:"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;}', 
 +    '@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;}', 
 +    ':root{--yiv-font:"YzWrBoldFont";}.yz-ch{font-family:var(--yiv-font,"YzWrBoldFont","YzWrFont"),sans-serif;}', 
 +    '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.open{display:flex;}', 
 +    '#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:6px;right:10px;background:none;border:none;font-size:28px;cursor:pointer;color:'+t.specFg+';}', 
 +    '#yzp-kbd{display:inline-flex;flex-direction:column;gap:6px;}', 
 +    '.yz-row{display:flex;gap:6px;}', 
 +    '.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: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{filter:brightness(1.15);}.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(--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>', 
 +    (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\')">&#9000;&#65039; Open Keyboard</button>', 
 +    '<div id="yiv-popup"><div id="yiv-popup-inner">', 
 +    '  <button id="yiv-close-btn" onclick="document.getElementById(\'yiv-popup\').classList.remove(\'open\')">&#10005;</button>', 
 +    '  <div id="yzp-kbd"></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>', 
 +    '<script>(function(){'+sc+'})();<\/script>', 
 +    '</'+'body></'+'html>' 
 +  ].join('\n'); 
 +
 + 
 +if (document.readyState === 'loading') { 
 +  document.addEventListener('DOMContentLoaded', init); 
 +} else { 
 +  init(); 
 +}
 </script> </script>
-</div> 
 </html> </html>
 +
font/keyboard.1774233899.txt.gz · Last modified: (external edit)