Multi-action keys with tap/long-press/double-tap/hold, gesture timing, display output colors, global font, and display output JSON/color parsing

- Multi-action system: each key supports multiple actions with different triggers
  (tap, long_press, double_tap, hold_start, hold_end)
- GestureEngine: timing-based gesture detection with configurable thresholds
- New config types: KeyAction, TimingConfig, DisplayOutput
- Display output now supports JSON format (text/background/text_color) and
  first-line-as-hex-color format for dynamic key backgrounds
- Display background colors are reflected in the web UI on the grid
- renderUnicodeText/renderUnicodeTextOnImage accept custom text color (fg)
- Global font setting (medium/regular) in Settings UI
- PageManager.defaultFont used as fallback for per-key font
- Alpine.js bundled locally (no CDN dependency)
- Web UI: action tabs (Tap/Long Press/Double Tap/Hold), unified More section
  with appearance + display settings, gesture timing sliders in Settings
- Grid: subtle action type icons in top-left corner matching display indicator style
- Backward compatible: old config 'action' field auto-migrated to 'actions' array
- M+ 1m font files in assets/ for reference
- Updated config.example.json and README with all new features
This commit is contained in:
totmin
2026-06-17 00:09:48 +07:00
parent 35eb27738d
commit f6f3950d28
14 changed files with 1037 additions and 284 deletions
+87 -1
View File
@@ -596,7 +596,7 @@ main {
color: var(--accent);
}
/* ── Display indicator on grid keys ── */
/* ── Indicators on grid keys ── */
.display-indicator {
position: absolute;
bottom: 4px;
@@ -607,6 +607,23 @@ main {
pointer-events: none;
}
.action-icons {
position: absolute;
top: 4px;
left: 4px;
display: flex;
gap: 3px;
pointer-events: none;
flex-wrap: wrap;
max-width: calc(100% - 6px);
}
.action-icon {
font-size: 7px;
color: var(--accent);
opacity: 0.6;
line-height: 1;
}
/* ── Tab buttons ── */
.tab-row {
display: flex;
@@ -721,3 +738,72 @@ main {
font-size: 11px;
margin: 0 2px;
}
/* ── Action tabs ── */
.action-tabs {
display: flex;
gap: 4px;
margin-bottom: 10px;
}
.action-tab {
flex: 1;
padding: 5px 2px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-xs);
color: var(--text-muted);
font-size: 9px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
text-align: center;
transition: all var(--transition);
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
line-height: 1.3;
}
.action-tab:hover { color: var(--text); border-color: var(--border-light); }
.action-tab.active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.action-tab.configured {
border-color: var(--success);
color: var(--success);
}
.action-tab.active.configured {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.tab-check { font-size: 8px; }
/* ── Hold sub-tabs ── */
.hold-subtabs {
display: flex;
gap: 4px;
margin-bottom: 10px;
}
.hold-stab {
flex: 1;
padding: 4px 8px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-xs);
color: var(--text-muted);
font-size: 10px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
text-align: center;
transition: all var(--transition);
}
.hold-stab:hover { color: var(--text); border-color: var(--border-light); }
.hold-stab.active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}