update v2.0 layout, fix keyboard viewer dead key cycling, clean up repo

- remove duplicate compositions from v2.0: § from Navigators, ± from
  Option+Shift §, ± from Mathematicians on -, terminator dupes on 5
- change Mathematicians terminator from space to 𝕄
- fix keyboard viewer: support multiple dead keys per key with click
  cycling, show active dead key's own layer character, hide others
- add feature card SVG icons (dead keys, ISO enter, versions, install)
- delete unused draft SVGs, remove leftover docs/ PDFs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 12:31:57 +01:00
parent c5cd3850c3
commit 7c7e54754e
12 changed files with 74 additions and 57 deletions

View File

@@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<!-- Draft C: Single key cap with EU star — minimal, reads well at any size -->
<rect width="1024" height="1024" rx="180" fill="#003399"/>
<!-- Large key cap shape, centered -->
<rect x="172" y="172" width="680" height="680" rx="80" fill="rgba(255,255,255,0.92)"
stroke="rgba(255,255,255,0.3)" stroke-width="8"/>
<!-- Key shadow/depth -->
<rect x="172" y="180" width="680" height="680" rx="80" fill="none"
stroke="rgba(0,0,0,0.08)" stroke-width="8"/>
<!-- Single EU gold star centered on key -->
<polygon points="512,220 568,398 756,398 604,506 646,680 512,580 378,680 420,506 268,398 456,398"
fill="#FFCC00"/>
</svg>

Before

Width:  |  Height:  |  Size: 699 B

View File

@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<!-- Template icon: "EU" — transparent bg, black text
macOS TISIconIsTemplate renders this adaptively:
light mode: dark text, light pill background
dark mode: light text, dark pill background -->
<text x="512" y="700" text-anchor="middle" font-family="'SF Pro Text', '.AppleSystemUIFont', 'Helvetica Neue', sans-serif" font-size="620" font-weight="600" fill="#000" letter-spacing="-10">EU</text>
</svg>

Before

Width:  |  Height:  |  Size: 490 B

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<!-- Template icon: "EUR" text — macOS renders black in light mode, white in dark mode -->
<text x="512" y="640" text-anchor="middle" font-family="-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif" font-size="480" font-weight="700" fill="#000" letter-spacing="-20">EUR</text>
</svg>

Before

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- Key cap with accent mark -->
<rect x="8" y="12" width="32" height="28" rx="4" stroke="#003399" stroke-width="2.5" fill="none"/>
<!-- Letter a on key -->
<text x="24" y="35" text-anchor="middle" font-family="-apple-system, system-ui, sans-serif" font-size="16" font-weight="700" fill="#003399">a</text>
<!-- Accent mark floating above key -->
<path d="M20 8 L24 4 L28 8" stroke="#003399" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 558 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
<!-- ISO Enter key shape (inverted L) -->
<path d="M18 6 H38 Q42 6 42 10 V38 Q42 42 38 42 H26 Q22 42 22 38 V22 H10 Q6 22 6 18 V10 Q6 6 10 6 Z" stroke="#003399" stroke-width="2.5" fill="none"/>
<!-- Enter arrow -->
<path d="M36 18 V30 H28" stroke="#003399" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31 27 L28 30 L31 33" stroke="#003399" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 521 B

View File

@@ -54,22 +54,22 @@
<h2>Features</h2>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon"></div>
<div class="feature-icon"><img src="img/icon-deadkeys.svg" alt="" width="36" height="36"></div>
<h3>Dead Keys for Diacritics</h3>
<p>Type accented characters (ä, é, ñ, č, …) using intuitive Option-key combinations. No character palette needed.</p>
</div>
<div class="feature-card">
<div class="feature-icon"></div>
<div class="feature-icon"><img src="img/icon-iso.svg" alt="" width="36" height="36"></div>
<h3>ISO International Layout</h3>
<p>Built for the physical English International keyboard found on European MacBooks — with the extra § key and big Enter.</p>
</div>
<div class="feature-card">
<div class="feature-icon"></div>
<div class="feature-icon"><img src="img/icon-versions.svg" alt="" width="36" height="36"></div>
<h3>Multiple Versions</h3>
<p>Ships v1.2, v1.3, v1.4, and v2.0 in a single bundle. Pick the one that fits your workflow.</p>
</div>
<div class="feature-card">
<div class="feature-icon"></div>
<div class="feature-icon"><img src="img/icon-install.svg" alt="" width="36" height="36"></div>
<h3>Easy Install</h3>
<p>Download the DMG, drag the bundle to Keyboard Layouts, log out. Done in under a minute.</p>
</div>

View File

@@ -63,7 +63,7 @@ const DEAD_KEY_NAMES = {
"\u00af": "The Macrons", "\u02da": "The Rings & Dots",
"\u03b1": "The Greeks", "\u221a": "The Mathematicians",
"\u00ac": "The Navigators", "\u00a9": "The Navigators",
" ": "The Mathematicians",
"\uD835\uDD44": "The Mathematicians",
};
const cache = new Map();
@@ -186,8 +186,7 @@ function renderKeyboard(data) {
} else {
keyElements.set(keyCode, keyEl);
let hasDead = false;
let deadState = null;
const deadStates = [];
for (const layer of LAYERS) {
const info = charForKey(data, layer.mod, keyCode);
@@ -196,18 +195,19 @@ function renderKeyboard(data) {
if (info) {
span.textContent = displayChar(info.char);
if (info.deadKey) {
hasDead = true;
deadState = info.deadKey;
if (!deadStates.includes(info.deadKey)) deadStates.push(info.deadKey);
span.classList.add("key-char--is-dead");
}
}
keyEl.appendChild(span);
}
if (hasDead) {
if (deadStates.length > 0) {
deadStates.reverse();
keyEl.classList.add("key--dead");
keyEl.dataset.deadKey = deadState;
keyEl.addEventListener("click", () => toggleDeadKeyMode(deadState));
keyEl.dataset.deadKey = deadStates[0];
keyEl.dataset.deadKeys = JSON.stringify(deadStates);
keyEl.addEventListener("click", () => cycleDeadKeyMode(keyEl));
}
}
@@ -228,6 +228,23 @@ function toggleDeadKeyMode(deadState) {
}
}
function cycleDeadKeyMode(keyEl) {
const deadStates = JSON.parse(keyEl.dataset.deadKeys || "[]");
if (deadStates.length === 0) return;
if (!currentDeadKey || !deadStates.includes(currentDeadKey)) {
enterDeadKeyMode(deadStates[0]);
} else {
const idx = deadStates.indexOf(currentDeadKey);
const next = idx + 1;
if (next < deadStates.length) {
enterDeadKeyMode(deadStates[next]);
} else {
exitDeadKeyMode();
}
}
}
function enterDeadKeyMode(deadState) {
if (!currentData) return;
const charMap = buildCompositionMap(currentData, deadState);
@@ -260,21 +277,35 @@ function enterDeadKeyMode(deadState) {
const baseComposed = charMap[baseChar] || "";
const shiftComposed = charMap[shiftChar] || "";
const spans = keyEl.querySelectorAll(".key-char");
// order: shift, option-shift, base, option
if (spans[0]) spans[0].textContent = displayChar(shiftComposed);
if (spans[1]) spans[1].textContent = "";
if (spans[2]) spans[2].textContent = displayChar(baseComposed);
if (spans[3]) spans[3].textContent = "";
if (keyEl.dataset.deadKey === deadState) {
const allDead = JSON.parse(keyEl.dataset.deadKeys || "[]");
if (allDead.includes(deadState)) {
keyEl.classList.add("key--dead-active");
} else if (baseComposed || shiftComposed) {
keyEl.classList.add("key--has-composition");
keyEl.classList.remove("key--no-composition");
// only show the span for the layer that owns this dead key
const spans = keyEl.querySelectorAll(".key-char");
const layerOrder = [MOD_SHIFT, MOD_OPTION_SHIFT, MOD_BASE, MOD_OPTION];
for (let i = 0; i < spans.length; i++) {
const info = charForKey(currentData, layerOrder[i], keyCode);
if (info?.deadKey === deadState) {
spans[i].style.visibility = "visible";
} else {
spans[i].style.visibility = "hidden";
}
}
} else {
keyEl.classList.add("key--no-composition");
keyEl.classList.remove("key--has-composition");
const spans = keyEl.querySelectorAll(".key-char");
// order: shift, option-shift, base, option
if (spans[0]) spans[0].textContent = displayChar(shiftComposed);
if (spans[1]) spans[1].textContent = "";
if (spans[2]) spans[2].textContent = displayChar(baseComposed);
if (spans[3]) spans[3].textContent = "";
if (baseComposed || shiftComposed) {
keyEl.classList.add("key--has-composition");
keyEl.classList.remove("key--no-composition");
} else {
keyEl.classList.add("key--no-composition");
keyEl.classList.remove("key--has-composition");
}
}
}
}
@@ -302,6 +333,7 @@ function exitDeadKeyMode() {
for (let i = 0; i < spans.length; i++) {
const info = charForKey(currentData, layerOrder[i], keyCode);
spans[i].textContent = info ? displayChar(info.char) : "";
spans[i].style.visibility = "";
}
}
}

View File

@@ -508,7 +508,7 @@
<key code="7" output="Á"/>
<key code="8" output="Ç"/>
<key code="9" output="Ì"/>
<key code="10" output="±"/>
<key code="10" output=""/>
<key code="11" output="Í"/>
<key code="12" action="Æ"/>
<key code="13" output="Å"/>
@@ -1093,7 +1093,6 @@
<action id="-">
<when state="none" output="-"/>
<when state="⌥m" output="⁻"/>
<when state="⌥⇧m" output="±"/>
</action>
<action id=".">
<when state="none" output="."/>
@@ -1132,10 +1131,6 @@
<action id="5">
<when state="none" output="5"/>
<when state="⌥\" output="⅔"/>
<when state="⌥⇧7" output="¯"/>
<when state="⌥'" output="´"/>
<when state="⌥⇧6" output="ˇ"/>
<when state="⌥7" output="˚"/>
<when state="⌥m" output="₅"/>
</action>
<action id="6">
@@ -1584,7 +1579,6 @@
<action id="s">
<when state="none" output="s"/>
<when state="⌥6" output="ŝ"/>
<when state="⌥\" output="§"/>
<when state="⌥'" output="ś"/>
<when state="⌥⇧6" output="š"/>
<when state="⌥m" output="σ"/>
@@ -1775,6 +1769,6 @@
<when state="⌥⇧6" output="ˇ"/>
<when state="⌥7" output="˚"/>
<when state="⌥m" output="α"/>
<when state="⌥⇧m" output=" "/>
<when state="⌥⇧m" output="𝕄"/>
</terminators>
</keyboard>