diff options
Diffstat (limited to 'scripts/theme-studio/theme-studio.html')
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 3809 |
1 files changed, 3511 insertions, 298 deletions
diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index e25e05aa1..4896a2387 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -6,46 +6,165 @@ .wrap{display:flex;flex-wrap:nowrap;overflow-x:auto;gap:14px;padding-bottom:10px} .col{flex:0 0 auto;width:460px} pre{background:#0d0b0a;border:1px solid #252321;border-radius:8px;padding:14px 16px;font-size:12pt;overflow:auto;white-space:pre} - table.leg{border-collapse:collapse} table.leg td{padding:4px 12px;vertical-align:middle} - table.leg th{cursor:pointer;color:#b4b1a2;text-align:left;padding:4px 12px;user-select:none;font-weight:normal} + table.leg{border-collapse:collapse} table.leg td{padding:4px 8px;vertical-align:middle} + table.leg th{cursor:pointer;color:#b4b1a2;text-align:left;padding:4px 8px;user-select:none;font-weight:normal} + #legtable th:nth-child(3),#legtable th:nth-child(4), + #legtable td:nth-child(3),#legtable td:nth-child(4), + #uitable th:nth-child(3),#uitable th:nth-child(4), + #uitable td:nth-child(3),#uitable td:nth-child(4), + #pkgtable th:nth-child(3),#pkgtable th:nth-child(4), + #pkgtable td:nth-child(3),#pkgtable td:nth-child(4){width:78px;min-width:78px;max-width:78px;padding-left:4px;padding-right:4px;text-align:center} + #legtable th:nth-child(6),#legtable td:nth-child(6), + #uitable th:nth-child(8),#uitable td:nth-child(8), + #pkgtable th:nth-child(9),#pkgtable td:nth-child(9){width:76px;min-width:76px;max-width:76px;padding-left:6px;padding-right:6px;text-align:left} + .boxcluster{display:grid;grid-template-columns:repeat(2,1fr);gap:2px} + .boxbtn{width:17px;height:15px;padding:0;border:1px solid #3a3a3a;border-radius:3px;background:#1f1c19;color:#cdced1;font:11px monospace;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center} + .boxbtn.on{background:#3a3320;border-color:#e8bd30;color:#e8bd30} + .boxbtn:disabled{opacity:.3;cursor:default} + .stylecluster{display:flex;flex-wrap:wrap;align-items:center;gap:4px;width:max-content;max-width:210px} + .exptoggle{width:18px;height:18px;padding:0;border:1px solid #3a3a3a;border-radius:3px;background:#1f1c19;color:#8a9496;font:12px monospace;line-height:1;cursor:pointer;vertical-align:middle} + .exptoggle.on{background:#3a3320;border-color:#e8bd30;color:#e8bd30} + .exptoggle.exp-nd{border-color:#e8bd30;color:#e8bd30} + .exptoggle:disabled{opacity:.3;cursor:default} + tr.detailrow>td{background:#15120f;border-top:1px solid #2a2a2a;padding:8px 14px} + .detailedit{display:flex;flex-wrap:wrap;align-items:center;gap:14px} + .detailfield{display:flex;align-items:center;gap:5px;font:11px monospace;color:#b4b1a2} + .detailfield>span{white-space:nowrap} + input.detailinput{width:120px;padding:3px 5px;font:11px monospace;background:#1f1c19;color:#cdced1;border:1px solid #3a3a3a;border-radius:4px} + select.detailsel{width:130px;font:10pt monospace} + input.detailcheck{width:15px;height:15px;cursor:pointer} table.leg th:hover{color:#e8bd30} select.chip{appearance:none;border:1px solid #00000060;border-radius:5px;padding:5px 10px;font:bold 14px monospace;width:160px;cursor:pointer} + /* Prev/next arrows flanking the view dropdown: step the selection without reopening it. + Scoped under .pkgbar to outweigh the generic `.pkgbar button` rule above. */ + .pkgbar .viewnav{appearance:none;border:1px solid #00000060;border-radius:5px;background:#1f1c19;color:#e8bd30;font:bold 16px monospace;width:26px;height:30px;padding:0;margin:0;cursor:pointer;vertical-align:middle} + .pkgbar .viewnav:hover{border-color:#e8bd30} + /* Non-default marker: a small gold corner flag on a per-face setting cell whose + value differs from the face's default. The size box looks identical default + or not, so the flag is the only at-a-glance cue that a value was changed. */ + td.nd{position:relative} + td.nd::after{content:'';position:absolute;top:0;right:0;width:0;height:0;border-top:8px solid #e8bd30;border-left:8px solid transparent;pointer-events:none} + .cstep{display:inline-flex;align-items:center;gap:4px} + .cstepbtn{width:22px;height:28px;padding:0;border:1px solid #3a3a3a;border-radius:4px;background:#1f1c19;color:#e8bd30;font:bold 14px monospace;cursor:pointer} + .cstepbtn:disabled{opacity:.28;cursor:default;color:#8f8977} + .cstep.locked .cstepbtn{opacity:.28;cursor:default} + .cdd{display:inline-flex;align-items:center;gap:7px;border:1px solid #00000060;border-radius:5px;padding:5px 10px;font:bold 13px monospace;width:150px;cursor:pointer;box-sizing:border-box;overflow:hidden;white-space:nowrap} + .cdd.compact{width:28px;height:28px;padding:0;justify-content:center;gap:0} + .cdd.compact.is-default{border-color:#8f7810;box-shadow:inset 0 0 0 1px #8f7810} + .cddsw{display:inline-block;width:13px;height:13px;border-radius:3px;border:1px solid #0007;flex:none} + .cdd.compact .cddsw{width:18px;height:18px} + .cdd.enumdd{width:auto;min-width:60px;max-width:96px;justify-content:center;background:#161412;color:#cdced1;font:13px monospace;padding:5px 8px} + .cdd.enumdd.is-default{color:#8a8a82} + .cdd.enumdd.locked{cursor:default;opacity:.85;box-shadow:inset 0 0 0 2px #e8bd3088} + .enumpop{display:flex;flex-direction:column;gap:2px;min-width:96px} + .enumopt{text-align:left;font:13px monospace;color:#cdced1;background:#161412;border:1px solid #3a3a3a;border-radius:4px;padding:4px 10px;cursor:pointer} + .enumopt:hover{background:#252321} + .enumopt.sel{outline:1px solid #e8bd30;outline-offset:1px} + .enumdef{color:#9a9a92} + .cddpop{position:fixed;z-index:200;background:#161412;border:1px solid #3a3a3a;border-radius:6px;box-shadow:0 12px 34px #000c;max-height:70vh;overflow:auto;padding:8px} + .cddghead{display:flex;align-items:center;gap:8px;margin-bottom:7px} + .cddgdef{font:bold 11px monospace;color:#cdced1;background:#161412;border:1px solid #3a3a3a;border-radius:4px;padding:3px 10px;cursor:pointer} + .cddgdef:hover{background:#252321} + .cddgdef.sel{outline:1px solid #e8bd30;outline-offset:1px} + .cddglbl{font:11px monospace;color:#c66} + .cddgrow{display:flex;gap:3px;margin-bottom:3px} + .cddgc{display:inline-block;width:22px;height:22px;border-radius:3px;border:1px solid #0007;padding:0;cursor:pointer;flex:none} + .cddgc:hover{outline:1px solid #fff8;outline-offset:1px} + .cddgc.sel{outline:2px solid #e8bd30;outline-offset:1px} + .cddgc.gone{border:2px solid #c66} + .cstep.locked .cdd{cursor:default;opacity:.85;box-shadow:inset 0 0 0 2px #e8bd3088} + .lockbtn{background:none;border:none;cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;opacity:.5;filter:grayscale(1)} + .lockbtn.on{opacity:1;filter:none} + .boxctl{display:flex;align-items:center;gap:5px} + .boxctl .cstepbtn{width:18px} + .legctl{margin:0 0 8px;display:flex;gap:8px;align-items:center} .cat{color:#b4b1a2} .ex{font-size:17px} - .sbtn{width:26px;height:24px;border:1px solid #3a3a3a;border-radius:3px;background:#eaeaea;color:#111;cursor:pointer;font-size:15px;margin-right:2px;padding:0} + .paltoggle{align-self:flex-start;width:22px;height:22px;padding:0;border:1px solid #3a3a3a;border-radius:4px;background:#1f1c19;color:#e8bd30;font:12px monospace;line-height:1;cursor:pointer;margin-right:6px} + /* Barber-pole flag: a ring of two alternating contrasting colors, drawn with a + masked repeating gradient so it overlays without shifting layout. Distinct + color pairs keep "unused" (gold/black) and "gone" (red/white) tellable apart. */ + .pchip.unused::after,.fstrip.unused-col::after,.cdd.gone::after{content:"";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(45deg,var(--barberA) 0 6px,var(--barberB) 6px 12px);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude} + .pchip.unused{--barberA:#e8bd30;--barberB:#0d0b0a} + .pchip.unused::after{padding:1.5px;border-radius:6px} + .fstrip.unused-col{position:relative;--barberA:#e8bd30;--barberB:#0d0b0a} + .fstrip.unused-col::after{padding:1.5px;border-radius:8px} + .cdd.gone{position:relative;--barberA:#e0533f;--barberB:#f5f5f5} + .cdd.gone::after{padding:1px;border-radius:5px} + .sbtn{width:17px;height:15px;border:1px solid #3a3a3a;border-radius:3px;background:#eaeaea;color:#111;cursor:pointer;font-size:13px;line-height:1;margin-right:2px;padding:0} .sbtn.on{background:#0d0b0a;color:#cdced1;border-color:#8a9496} - .pals{display:flex;gap:8px;flex-wrap:wrap} - .palwarn{display:none;margin-top:8px;font:10pt monospace;color:#cb6b4d} - .palwarn .pwh{font-weight:bold;margin-bottom:2px} - .palwarn .pwl{opacity:.92} + .pals{display:flex;flex-direction:row;flex-wrap:wrap;gap:10px;align-items:flex-start} + .fstrip{display:flex;flex-direction:column;gap:6px;padding:5px;border-radius:7px;border:1px solid transparent} + .fstrip.ground{border-color:#252321;background:#161412} + .fhead{min-height:17px;width:128px;display:flex;align-items:center;justify-content:center;gap:3px;color:#b4b1a2;font:9pt monospace;text-align:center} + .fhead .ctitle{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:none;border:none;color:#b4b1a2;font:9pt monospace;text-align:center;cursor:pointer;padding:0} + .fhead .ctitle:hover{color:#e8bd30} + .fhead .cmove,.fhead .cdel{width:18px;height:17px;background:#161412;border:1px solid #252321;border-radius:3px;color:#8a9496;font:10pt monospace;line-height:13px;padding:0;cursor:pointer} + .fhead .cmove:hover:not(:disabled),.fhead .cdel:hover{color:#e8bd30;border-color:#3a3a3a} + .fhead .cmove:disabled{opacity:.28;cursor:default} + .fhead .cdel{margin-left:5px;color:#b36a5e} + .fhead .cdel:hover{color:#ff9078;border-color:#6d342c;background:#211512} + .fcount{margin-top:3px;font:9pt monospace;color:#8a9496;text-align:center} + .fcount input{width:40px;background:#0d0b0a;border:1px solid #252321;color:#cdced1;border-radius:4px;padding:2px 4px;font:9pt monospace;text-align:center} .pchip{width:128px;height:58px;border-radius:6px;border:1px solid #555;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:grab} - .pchip.drag{opacity:.4} .pchip.sel{outline:3px solid #e8bd30;outline-offset:2px} .pchip.over{outline:2px dashed #e8bd30;outline-offset:1px} .pchip input.nm{background:transparent;border:none;text-align:center;font:bold 10pt monospace;width:108px;outline:none} - .pchip .mv{position:absolute;bottom:-1px;background:none;border:none;cursor:pointer;font-size:22px;line-height:1;font-weight:bold;opacity:.5;padding:0 5px} .pchip .mv:hover{opacity:1} .pchip .mv.l{left:0} .pchip .mv.r{right:0} + .pchip.sel{outline:3px solid #e8bd30;outline-offset:2px} .pchip input.nm{background:transparent;border:none;text-align:center;font:bold 10pt monospace;width:108px;outline:none;cursor:pointer} + .pchip input.nm.editing{cursor:text;text-align:left} .pchip .hx{font-size:10pt;opacity:.8} .pchip .rm{position:absolute;top:2px;right:5px;background:none;border:none;cursor:pointer;font-size:14px;font-weight:bold;opacity:.7} .pchip .lock{position:absolute;top:3px;right:5px;font-size:10px;opacity:.6} - .palctl{margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap} + .palctl{margin:0 0 12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap} .palctl input[type=text]{background:#161412;border:1px solid #252321;color:#cdced1;border-radius:4px;padding:5px 8px;font:10pt monospace} .palctl input[type=text]::placeholder{color:#b4b1a2;opacity:1} .palctl{position:relative} .swatch{width:128px;height:58px;border:1px solid #555;border-radius:6px;cursor:pointer;background:#888} - .picker{display:none;position:absolute;top:66px;left:0;z-index:60;background:#161412;border:1px solid #3a3a3a;border-radius:8px;padding:12px;box-shadow:0 10px 30px #000b;width:470px} + .picker{display:none;position:absolute;top:66px;left:0;z-index:60;background:#1f1c19;border:1px solid #e8bd30;border-radius:8px;padding:12px;box-shadow:0 10px 30px #000b;width:470px} .picker .prow{display:flex;gap:10px} .sv{position:relative;width:400px;height:320px;border-radius:4px;cursor:crosshair} .svmask{position:absolute;inset:0;pointer-events:none;border-radius:4px} .pmode{margin:2px 2px 8px;font:10pt monospace;color:#b4b1a2;display:flex;gap:6px;align-items:center} .pmode button{background:#252321;color:#cdced1;border:1px solid #3a3a3a;border-radius:4px;padding:2px 9px;font:10pt monospace;cursor:pointer} .pmode button.on{background:#e8bd30;color:#000;border-color:#e8bd30} - .svcur{position:absolute;width:16px;height:16px;border:2px solid #fff;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 1px #0008;pointer-events:none} + .pmodel{margin:8px 2px 4px;font:10pt monospace;color:#b4b1a2;display:flex;gap:6px;align-items:center} + .pmodel button{background:#252321;color:#cdced1;border:1px solid #3a3a3a;border-radius:4px;padding:2px 9px;font:10pt monospace;cursor:pointer} + .pmodel button.on{background:#67809c;color:#000;border-color:#67809c} + .oklchctl{display:none;margin:0 2px 6px;font:10pt monospace;color:#9aa3ad} + .oklchctl.show{display:block} + .oklchctl .ocrow{display:flex;align-items:center;gap:6px;margin:3px 0} + .oklchctl .ocrow label{width:12px;color:#cdced1} + .oklchctl .ocrow input[type=range]{flex:1} + .oklchctl .ocrow input[type=number]{width:62px;background:#252321;color:#cdced1;border:1px solid #3a3a3a;border-radius:3px;font:10pt monospace;padding:1px 3px} + .oklchctl .pclamp{display:none;color:#cb6b4d;margin-top:3px} + .oklchctl .pclamp.show{display:block} + .svcur{position:absolute;width:16px;height:16px;border:2px solid #fff;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 1px #0008;pointer-events:none;z-index:3} .hue{position:relative;width:34px;height:320px;border-radius:4px;cursor:ns-resize;background:linear-gradient(to bottom,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)} .huecur{position:absolute;left:-2px;right:-2px;height:4px;background:#fff;border:1px solid #0008;transform:translateY(-50%);pointer-events:none} .pinfo{display:flex;justify-content:space-between;margin:10px 2px 4px;font:12pt monospace;color:#cdced1} .pinfo2{display:flex;justify-content:space-between;margin:0 2px 9px;font:10pt monospace;color:#9aa3ad} .pinfo2 span{cursor:default} .pkchips{display:flex;flex-wrap:wrap;gap:5px} .pkchips .pc{width:28px;height:28px;border-radius:3px;border:1px solid #555;cursor:pointer} + .svsafe{position:absolute;left:0;width:100%;background:rgba(203,107,77,0.30);border-bottom:2px solid #cb6b4d;pointer-events:none;z-index:2} .palctl button,.filebar button,.fbtn{background:#252321;color:#e8bd30;border:1px solid #3a3a3a;border-radius:4px;padding:6px 12px;font:10pt monospace;cursor:pointer} #palmsg{font:10pt monospace;opacity:0;transition:opacity .35s;margin-left:6px} + .genctl{margin:0 0 14px;padding:8px 10px;border:1px solid #252321;border-radius:7px;background:#11100e} + .genrow{display:flex;gap:8px;align-items:center;flex-wrap:wrap;color:#b4b1a2;font:10pt monospace} + .genrow label{display:flex;gap:5px;align-items:center} + .genrow label.disabled{opacity:.45} + .genrow select,.genrow input{background:#161412;border:1px solid #252321;color:#cdced1;border-radius:4px;padding:4px 6px;font:10pt monospace} + .genrow input:disabled{opacity:.55;cursor:default} + .genrow input{width:48px} + .genrow button{background:#252321;color:#e8bd30;border:1px solid #3a3a3a;border-radius:4px;padding:5px 10px;font:10pt monospace;cursor:pointer} + #genmsg{color:#8a9496} + .genpreview{display:flex;gap:10px;align-items:flex-start;flex-wrap:wrap;margin-top:8px} + .gencol{display:flex;flex-direction:column;gap:6px;padding:5px;border:1px dashed #3a3a3a;border-radius:7px} + .genhead{width:128px;display:flex;gap:4px;align-items:center;justify-content:center;color:#b4b1a2;font:9pt monospace} + .genhead span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;text-align:center;white-space:nowrap} + .genappend{width:22px;height:22px;padding:0;border:1px solid #3a3a3a;border-radius:4px;background:#252321;color:#e8bd30;font:bold 12px monospace;cursor:pointer} + .genchip{width:128px;height:58px;border-radius:6px;border:1px solid #555;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer;font:10pt monospace;line-height:1.15;text-align:center;box-sizing:border-box;padding:3px 5px;overflow:hidden} + .genchip.sel{outline:2px solid #e8bd30;outline-offset:2px} + .genchip .gn{font-weight:bold;white-space:normal;overflow-wrap:normal;max-width:116px} + .genchip .gh{opacity:.8;white-space:nowrap} #export{width:100%;height:180px;margin-top:10px;background:#0d0b0a;color:#a4ac64;border:1px solid #252321;border-radius:6px;font:10pt monospace;padding:10px} .filebar{margin:6px 0 0;display:flex;gap:8px;align-items:center} - #pagetitle{font-size:30px;color:#cdced1;font-weight:normal;border:none;margin:4px 0 18px;padding:0} + #pagetitle{font-size:30px;color:#cdced1;font-weight:normal;border:none;margin:0;padding:0} + .topbar{display:flex;gap:24px;align-items:flex-start;justify-content:space-between;margin:4px 0 18px} .cols{display:flex;gap:28px;align-items:flex-start} .cols.stretch{align-items:stretch} .pane{min-width:0} .pane.grow{flex:1} .pane.saveload{flex:0 0 auto;margin-left:auto} .pane h1{margin-top:0} @@ -56,7 +175,10 @@ #pkgbody td{padding:3px 8px} #codepre{width:100%;box-sizing:border-box} .mock{border:1px solid #252321;border-radius:8px;overflow:hidden;font:12pt/1.7 monospace;display:flex;flex-direction:column} - .mock .mbuf{flex:1} .mock .ln{display:flex;align-items:stretch;white-space:pre} + .mock .mbuf{flex:1;display:flex;align-items:stretch;overflow:auto} + .mock .mbuftext{flex:0 1 auto;width:max-content;max-width:calc(100% - 4ch - 3px);padding-right:4ch;box-sizing:border-box;overflow:hidden} + .mock .vborder{width:3px;flex:0 0 auto} + .mock .ln{display:flex;align-items:stretch;white-space:pre} .mock .fr{width:14px;flex:0 0 auto;border-right:1px solid #ffffff14} .mock .num{width:36px;flex:0 0 auto;text-align:right;padding-right:10px} .mock .cd{flex:1;padding-left:8px} .mock .bar,.mock .echo{padding:4px 10px;white-space:pre} #codepre [data-k],.mock [data-k],.mock [data-face]{cursor:pointer} @@ -65,94 +187,131 @@ @keyframes flashtok{0%,55%{background:#e8bd30aa;color:#000}100%{background:transparent}} .flashtok{animation:flashtok 1.1s ease-out;border-radius:2px} </style> -<h1 id="pagetitle">Untitled: theme</h1> -<div class="cols"> +<div class="topbar"> + <h1 id="pagetitle">Untitled: theme</h1> + <div class="saveload"> + <div class="filebar end"> + <label style="color:#b4b1a2">theme name</label><input type="text" id="themename" value="" placeholder="untitled" oninput="updateTitle()" style="background:#161412;border:1px solid #252321;color:#cdced1;border-radius:4px;padding:5px 8px;font:10pt monospace;width:200px"> + <button onclick="exportTheme()">⬇ export</button> + <button class="fbtn" onclick="importTheme()">⬆ import</button><input type="file" id="fileinput" accept=".json" onchange="importFile(event)" style="display:none"> + <button id="jsonbtn" onclick="toggleJSON()">show</button> + </div> + <textarea id="export" style="display:none" readonly></textarea> + </div> +</div> <section class="pane grow"> <h1>palette</h1> - <div class="pals" id="pals"></div> - <div class="palwarn" id="palwarn"></div> <div class="palctl"> <div id="swatch" class="swatch" title="open color picker"></div> - <input type="text" id="newhexstr" placeholder="#rrggbb" value="#888888" oninput="syncHex()" onkeydown="if(event.key==='Enter')applyEdit()" style="width:110px"> + <input type="text" id="newhexstr" placeholder="#rrggbb" value="#67809c" oninput="syncHex()" onkeydown="if(event.key==='Enter')applyEdit()" style="width:110px"> <input type="text" id="newname" placeholder="name" onkeydown="if(event.key==='Enter')applyEdit()"> <button onclick="addColor()">+ add color</button> <button onclick="updateColor()">↻ update selected</button> + <button onclick="clearPalette()" title="remove every palette color except the bg and fg tiles">clear palette</button> <span id="palmsg"></span> <div id="picker" class="picker"> <div class="prow"> - <div id="sv" class="sv"><canvas id="svmask" class="svmask"></canvas><div id="svcur" class="svcur"></div></div> + <div id="sv" class="sv"><canvas id="svmask" class="svmask"></canvas><div id="svsafe" class="svsafe" style="display:none"></div><div id="svcur" class="svcur"></div></div> <div id="hue" class="hue"><div id="huecur" class="huecur"></div></div> </div> - <div class="pinfo"><span id="pkhex">#888888</span><span id="pkcon"></span></div> + <div class="pmodel">edit <button data-pm="hsv" class="on">HSV</button><button data-pm="oklch">OKLCH</button></div> + <div class="pmodel" title="in OKLCH mode, shade the lightness too light to keep this overlay face readable over its foreground set">safe for <select id="safefor" onchange="setSafeFace(this.value)"><option value="">none</option></select></div> + <div class="oklchctl" id="oklchctl"> + <div class="ocrow"><label title="perceptual lightness">L</label><input type="range" id="okL" min="0" max="1" step="0.001"><input type="number" id="okLn" min="0" max="1" step="0.001"></div> + <div class="ocrow"><label title="chroma (colorfulness)">C</label><input type="range" id="okC" min="0" max="0.4" step="0.001"><input type="number" id="okCn" min="0" max="0.4" step="0.001"></div> + <div class="ocrow"><label title="hue angle, degrees">H</label><input type="range" id="okH" min="0" max="360" step="1"><input type="number" id="okHn" min="0" max="360" step="1"></div> + <div class="pclamp" id="pkclamp"></div> + </div> + <div class="pinfo"><span id="pkhex">#67809c</span><span id="pkcon"></span></div> <div class="pinfo2"><span id="pkoklch" title="OKLCH perceptual coordinates: lightness L (0..1), chroma C, hue H in degrees"></span><span id="pkapca"></span></div> <div class="pmode">limit <button data-m="any" class="on">any</button><button data-m="aa">AA+</button><button data-m="aaa">AAA</button></div> <div id="pkchips" class="pkchips"></div> </div> </div> - </section> - <section class="pane saveload"> - <h1>export, import, and save</h1> - <div class="filebar end"> - <label style="color:#b4b1a2">theme name</label><input type="text" id="themename" value="" placeholder="untitled" oninput="updateTitle()" style="background:#161412;border:1px solid #252321;color:#cdced1;border-radius:4px;padding:5px 8px;font:10pt monospace;width:200px"> - </div> - <div class="filebar end"> - <button id="savebtn" onclick="saveTheme()" style="display:none">💾 save</button> - <button onclick="exportTheme()">⬇ export</button> - <button class="fbtn" onclick="importTheme()">⬆ import</button><input type="file" id="fileinput" accept=".json" onchange="importFile(event)" style="display:none"> - <button id="jsonbtn" onclick="toggleJSON()">show</button> + <div class="genctl" id="genctl"> + <div class="genrow"> + <label>intent <select id="genintent"></select></label> + <label>vibe <select id="genvibe"></select></label> + <label>source <select id="gensource"></select></label> + <label title="how many base columns to propose">accent count <input id="genaccents" type="number" min="1" max="12" step="1" value="5"></label> + <label>contrast <select id="gencontrast"></select></label> + <button onclick="previewGenerator()">preview</button> + <button onclick="clearGeneratorPreview()">clear preview</button> + <span id="genmsg"></span> + </div> + <div id="genpreview" class="genpreview"></div> </div> - <textarea id="export" style="display:none" readonly></textarea> + <div class="pals" id="pals"></div> </section> -</div> -<h1>code/color assignments</h1> +<h1>assignment</h1> +<div class="pkgbar"><label style="color:#b4b1a2">view</label><button id="viewprev" class="viewnav" title="previous in the list" onclick="stepView(-1)">‹</button><select id="viewsel" class="chip" style="width:auto;font:bold 10pt monospace" onchange="onViewChange()"></select><button id="viewnext" class="viewnav" title="next in the list" onclick="stepView(1)">›</button></div> +<div id="view-code" class="viewblock"> <div class="cols"> <section class="pane"> - <table class="leg" id="legtable"><thead><tr><th onclick="srt(1)">elements △</th><th onclick="srt(0)">color △</th><th>style</th><th>example</th><th title="WCAG contrast of this color on the background">contrast</th></tr></thead><tbody id="legbody"></tbody></table> + <div class="legctl"><button id="syntaxlocktoggle" class="fbtn" onclick="toggleAllLocks('syntax')" title="lock or unlock every syntax row">lock all</button><button id="syntaxexpandall" class="fbtn" onclick="toggleAllExpanded('syntaxexpandall')" title="expand or collapse every row's detail">▶ expand all</button><button class="fbtn" onclick="resetUnlocked()" title="reset to captured defaults, preserving locked rows">↻ reset</button><button class="fbtn" onclick="clearUnlocked()" title="erase, preserving locked rows">erase</button></div> + <table class="leg" id="legtable"><thead><tr><th title="lock a decided element↔color association"></th><th onclick="srtTable('legbody',1)">elements △</th><th onclick="srtTable('legbody',2)">fg △</th><th onclick="srtTable('legbody',3)">bg △</th><th>style</th><th title="face :box (border)">box</th><th title="WCAG contrast of this color on the background">contrast</th><th>example</th></tr></thead><tbody id="legbody"></tbody></table> </section> <section class="pane grow"> - <div class="langbar"><label style="color:#b4b1a2">language</label><select id="langsel" class="chip" style="width:auto;font:bold 10pt monospace" onchange="renderCode()"></select></div> + <div class="langbar"><label style="color:#b4b1a2">language</label><button id="langprev" class="viewnav" title="previous in the list" onclick="stepLang(-1)">‹</button><select id="langsel" class="chip" style="width:auto;font:bold 10pt monospace" onchange="renderCode();buildPkgPreview()"></select><button id="langnext" class="viewnav" title="next in the list" onclick="stepLang(1)">›</button></div> <pre id="codepre"></pre> </section> </div> -<h1>ui faces</h1> +</div> +<div id="view-ui" class="viewblock" style="display:none"> <div class="cols stretch"> <section class="pane"> - <table class="leg" id="uitable"><thead><tr><th onclick="srtTable('uibody',0)">face △</th><th onclick="srtTable('uibody',1)">foreground △</th><th onclick="srtTable('uibody',2)">background △</th><th>style</th><th>preview</th></tr></thead><tbody id="uibody"></tbody></table> + <div class="legctl"><button id="uilocktoggle" class="fbtn" onclick="toggleAllLocks('ui')" title="lock or unlock every UI face row">lock all</button><button id="uiexpandall" class="fbtn" onclick="toggleAllExpanded('uiexpandall')" title="expand or collapse every row's detail">▶ expand all</button><button class="fbtn" onclick="resetUnlockedUI()" title="reset to captured defaults, preserving locked rows">↻ reset</button><button class="fbtn" onclick="clearUnlockedUI()" title="erase, preserving locked rows">erase</button></div> + <table class="leg" id="uitable"><thead><tr><th title="lock a decided face"></th><th onclick="srtTable('uibody',1)">face △</th><th onclick="srtTable('uibody',2)" title="foreground">fg △</th><th onclick="srtTable('uibody',3)" title="background">bg △</th><th>style</th><th title="face :box (border)">box</th><th onclick="srtTable('uibody',6)" title="WCAG contrast: this face's foreground on its background (or the ground)">contrast △</th><th>preview</th></tr></thead><tbody id="uibody"></tbody></table> </section> <section class="pane grow" style="display:flex;flex-direction:column"> <div class="langbar"><label style="color:#b4b1a2">live buffer preview</label></div> <div id="mockframe" class="mock"></div> </section> </div> -<h1>package faces</h1> +</div> +<div id="view-pkg" class="viewblock" style="display:none"> <div class="pkgbar"> - <label style="color:#b4b1a2">application</label><select id="appsel" class="chip" style="width:auto;font:bold 10pt monospace"></select> <label style="color:#b4b1a2">filter</label><input id="pkgfilter" type="text" placeholder="face name" oninput="buildPkgTable()" style="background:#161412;border:1px solid #252321;color:#cdced1;border-radius:4px;padding:5px 8px;font:10pt monospace;width:160px"> - <button onclick="resetApp()">↻ reset all</button> + <button onclick="resetApp()" title="reset to captured defaults, preserving locked rows">↻ reset</button> + <button id="pkglocktoggle" class="fbtn" onclick="toggleAllLocks('pkg')" title="lock or unlock every face row in the current package">lock all</button><button id="pkgexpandall" class="fbtn" onclick="toggleAllExpanded('pkgexpandall')" title="expand or collapse every row's detail">▶ expand all</button> + <button class="fbtn" onclick="clearUnlockedPkg()" title="erase, preserving locked rows">erase</button> </div> <div class="cols stretch"> <section class="pane"> - <table class="leg" id="pkgtable"><thead><tr><th onclick="srtTable('pkgbody',0)">face △</th><th onclick="srtTable('pkgbody',1)">fg △</th><th onclick="srtTable('pkgbody',2)">bg △</th><th>style</th><th onclick="srtTable('pkgbody',4)">inherit △</th><th onclick="srtTable('pkgbody',5)">size △</th><th onclick="srtTable('pkgbody',6)">contrast △</th><th></th></tr></thead><tbody id="pkgbody"></tbody></table> + <table class="leg" id="pkgtable"><thead><tr><th title="lock a decided face"></th><th onclick="srtTable('pkgbody',1)">face △</th><th onclick="srtTable('pkgbody',2)">fg △</th><th onclick="srtTable('pkgbody',3)">bg △</th><th>style</th><th title="face :box (border)">box</th><th onclick="srtTable('pkgbody',6)">contrast △</th></tr></thead><tbody id="pkgbody"></tbody></table> </section> <section class="pane grow" style="display:flex;flex-direction:column"> <div class="langbar"><label id="pkgprevlabel" style="color:#b4b1a2">preview</label></div> - <div id="pkgpreview" class="mock" style="overflow:auto"></div> + <div id="pkgpreview" class="mock" style="overflow:auto;min-height:60vh"></div> </section> </div> +</div> <script> -const SAMPLES={"Elisp": [[["cmd", ";;"], ["cm", " cache.el"]], [["punc", "("], ["kw", "require"], ["p", " "], ["con", "'cl-lib"], ["punc", ")"]], [], [["punc", "("], ["kw", "defvar"], ["p", " "], ["var", "cache--tbl"], ["p", " "], ["punc", "("], ["fnc", "make-hash-table"], ["p", " "], ["con", ":test"], ["p", " "], ["con", "'equal"], ["punc", "))"]], [["p", " "], ["doc", "\"Memo table.\")"]], [], [["punc", "("], ["kw", "defun"], ["p", " "], ["fnd", "cache-get"], ["p", " "], ["punc", "("], ["var", "key"], ["punc", ")"]], [["p", " "], ["doc", "\"Return cached value for KEY.\""]], [["p", " "], ["punc", "("], ["kw", "or"], ["p", " "], ["punc", "("], ["bi", "gethash"], ["p", " "], ["var", "key"], ["p", " "], ["var", "cache--tbl"], ["punc", ")"]], [["p", " "], ["punc", "("], ["kw", "let"], ["p", " "], ["punc", "(("], ["var", "v"], ["p", " "], ["punc", "("], ["fnc", "compute"], ["p", " "], ["var", "key"], ["p", " "], ["num", "42"], ["punc", "))) "]], [["p", " "], ["punc", "("], ["fnc", "puthash"], ["p", " "], ["var", "key"], ["p", " "], ["var", "v"], ["p", " "], ["var", "cache--tbl"], ["punc", ") "], ["var", "v"], ["punc", "))))"]], [], [["punc", "("], ["kw", "defun"], ["p", " "], ["fnd", "cache-clear"], ["p", " "], ["punc", "()"]], [["p", " "], ["doc", "\"Empty the memo table.\""]], [["p", " "], ["punc", "("], ["kw", "interactive"], ["punc", ")"]], [["p", " "], ["punc", "("], ["fnc", "clrhash"], ["p", " "], ["var", "cache--tbl"], ["punc", ")"]], [["p", " "], ["punc", "("], ["fnc", "message"], ["p", " "], ["str", "\"cleared"], ["esc", "\\n"], ["str", "\""], ["punc", "))"]], [], [["punc", "("], ["kw", "defun"], ["p", " "], ["fnd", "cache-keys"], ["p", " "], ["punc", "()"]], [["p", " "], ["doc", "\"Return all keys.\""]], [["p", " "], ["punc", "("], ["kw", "let"], ["p", " "], ["punc", "(("], ["var", "acc"], ["p", " "], ["con", "nil"], ["punc", "))"]], [["p", " "], ["punc", "("], ["fnc", "maphash"], ["p", " "], ["punc", "("], ["kw", "lambda"], ["p", " "], ["punc", "("], ["var", "k"], ["p", " "], ["var", "_v"], ["punc", ")"], ["p", " "], ["punc", "("], ["fnc", "push"], ["p", " "], ["var", "k"], ["p", " "], ["var", "acc"], ["punc", "))"]], [["p", " "], ["var", "cache--tbl"], ["punc", ")"], ["p", " "], ["var", "acc"], ["punc", "))"]], [], [["punc", "("], ["kw", "provide"], ["p", " "], ["con", "'cache"], ["punc", ")"]]], "Go": [[["cmd", "//"], ["cm", " queue.go"]], [["kw", "package"], ["p", " "], ["var", "main"]], [], [["kw", "import"], ["p", " "], ["str", "\"fmt\""]], [], [["kw", "const"], ["p", " "], ["con", "MaxItems"], ["p", " "], ["op", "="], ["p", " "], ["num", "100"]], [], [["kw", "type"], ["p", " "], ["ty", "Order"], ["p", " "], ["kw", "struct"], ["p", " "], ["punc", "{"]], [["p", " "], ["prop", "ID"], ["p", " "], ["ty", "int"]], [["p", " "], ["prop", "Name"], ["p", " "], ["ty", "string"]], [["punc", "}"]], [], [["kw", "func"], ["p", " "], ["punc", "("], ["var", "q"], ["p", " "], ["op", "*"], ["ty", "Queue"], ["punc", ")"], ["p", " "], ["fnd", "Push"], ["punc", "("], ["var", "o"], ["p", " "], ["op", "*"], ["ty", "Order"], ["punc", ")"], ["p", " "], ["ty", "error"], ["p", " "], ["punc", "{"]], [["p", " "], ["cmd", "//"], ["cm", " reject nil"]], [["p", " "], ["kw", "if"], ["p", " "], ["var", "o"], ["p", " "], ["op", "=="], ["p", " "], ["con", "nil"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "return"], ["p", " "], ["fnc", "fmt.Errorf"], ["punc", "("], ["str", "\"nil"], ["esc", "\\n"], ["str", "\""], ["punc", ")"]], [["p", " "], ["punc", "}"]], [["p", " "], ["var", "q"], ["op", "."], ["prop", "items"], ["p", " "], ["op", "="], ["p", " "], ["bi", "append"], ["punc", "("], ["var", "q"], ["op", "."], ["prop", "items"], ["punc", ","], ["p", " "], ["var", "o"], ["punc", ")"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "nil"]], [["punc", "}"]], [], [["kw", "func"], ["p", " "], ["fnd", "main"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["fnc", "fmt.Println"], ["punc", "("], ["op", "&"], ["ty", "Queue"], ["punc", "{}"], ["punc", ")"]], [["punc", "}"]]], "Python": [[["cmd", "#"], ["cm", " theme.py"]], [["kw", "from"], ["p", " "], ["var", "dataclasses"], ["p", " "], ["kw", "import"], ["p", " "], ["var", "dataclass"], ["punc", ","], ["p", " "], ["var", "field"]], [], [["con", "DEFAULT_PORT"], ["op", ":"], ["p", " "], ["ty", "int"], ["p", " "], ["op", "="], ["p", " "], ["num", "8080"]], [["con", "HEX"], ["p", " "], ["op", "="], ["p", " "], ["var", "re"], ["op", "."], ["fnc", "compile"], ["punc", "("], ["re", "r\"#[0-9a-f]{6}\""], ["punc", ")"]], [], [["dec", "@dataclass"]], [["kw", "class"], ["p", " "], ["ty", "Theme"], ["op", ":"]], [["p", " "], ["doc", "\"\"\"A color theme.\"\"\""]], [["p", " "], ["prop", "name"], ["op", ":"], ["p", " "], ["ty", "str"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""]], [["p", " "], ["prop", "colors"], ["op", ":"], ["p", " "], ["ty", "dict"], ["p", " "], ["op", "="], ["p", " "], ["fnc", "field"], ["punc", "("], ["prop", "default_factory"], ["op", "="], ["ty", "dict"], ["punc", ")"]], [], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["var", "self"], ["punc", ","], ["p", " "], ["var", "key"], ["op", ":"], ["p", " "], ["ty", "str"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "str"], ["p", " "], ["op", "|"], ["p", " "], ["con", "None"], ["op", ":"]], [["p", " "], ["cmd", "#"], ["cm", " fallback to none"]], [["p", " "], ["var", "v"], ["p", " "], ["op", "="], ["p", " "], ["var", "self"], ["op", "."], ["prop", "colors"], ["op", "."], ["fnc", "get"], ["punc", "("], ["var", "key"], ["punc", ","], ["p", " "], ["str", "\""], ["esc", "\\t"], ["str", "none\""], ["punc", ")"]], [["p", " "], ["kw", "if"], ["p", " "], ["bi", "len"], ["punc", "("], ["var", "v"], ["punc", ")"], ["p", " "], ["op", "=="], ["p", " "], ["num", "0"], ["op", ":"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "None"]], [["p", " "], ["kw", "return"], ["p", " "], ["var", "v"]], [], [["p", " "], ["dec", "@property"]], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "size"], ["punc", "("], ["var", "self"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "int"], ["op", ":"]], [["p", " "], ["kw", "return"], ["p", " "], ["bi", "len"], ["punc", "("], ["var", "self"], ["op", "."], ["prop", "colors"], ["punc", ")"]], [], [["var", "theme"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Theme"], ["punc", "("], ["str", "\"dupre\""], ["punc", ")"]], [["fnc", "print"], ["punc", "("], ["var", "theme"], ["op", "."], ["fnc", "resolve"], ["punc", "("], ["str", "\"bg\""], ["punc", "))"]]], "TypeScript": [[["cmd", "//"], ["cm", " orders.ts"]], [["kw", "import"], ["p", " "], ["punc", "{"], ["p", " "], ["ty", "Order"], ["p", " "], ["punc", "}"], ["p", " "], ["kw", "from"], ["p", " "], ["str", "\"./types\""]], [], [["kw", "export"], ["p", " "], ["kw", "interface"], ["p", " "], ["ty", "Queue"], ["p", " "], ["punc", "{"]], [["p", " "], ["prop", "max"], ["op", ":"], ["p", " "], ["ty", "number"], ["punc", ";"]], [["p", " "], ["prop", "items"], ["op", ":"], ["p", " "], ["ty", "Order"], ["punc", "[];"]], [["punc", "}"]], [], [["dec", "@Injectable"], ["punc", "()"]], [["kw", "export"], ["p", " "], ["kw", "class"], ["p", " "], ["ty", "OrderQueue"], ["p", " "], ["kw", "implements"], ["p", " "], ["ty", "Queue"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "private"], ["p", " "], ["prop", "re"], ["p", " "], ["op", "="], ["p", " "], ["re", "/^#[0-9a-f]{6}$/i"], ["punc", ";"]], [], [["p", " "], ["fnd", "push"], ["punc", "("], ["var", "o"], ["op", ":"], ["p", " "], ["ty", "Order"], ["punc", ")"], ["op", ":"], ["p", " "], ["ty", "boolean"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "o"], ["p", " "], ["op", "==="], ["p", " "], ["con", "null"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "false"], ["punc", ";"]], [["p", " "], ["var", "console"], ["op", "."], ["fnc", "log"], ["punc", "("], ["str", "`id "], ["punc", "${"], ["var", "o"], ["op", "."], ["prop", "id"], ["punc", "}"], ["esc", "\\n"], ["str", "`"], ["punc", ");"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "true"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["punc", "}"]], [], [["kw", "const"], ["p", " "], ["con", "LIMIT"], ["op", ":"], ["p", " "], ["ty", "number"], ["p", " "], ["op", "="], ["p", " "], ["num", "50"], ["punc", ";"]], [["kw", "const"], ["p", " "], ["var", "q"], ["p", " "], ["op", "="], ["p", " "], ["kw", "new"], ["p", " "], ["ty", "OrderQueue"], ["punc", "()"], ["punc", ";"]], [["var", "q"], ["op", "."], ["fnd", "push"], ["punc", "("], ["punc", "{"], ["p", " "], ["prop", "id"], ["op", ":"], ["p", " "], ["num", "1"], ["p", " "], ["punc", "}"], ["p", " "], ["kw", "as"], ["p", " "], ["ty", "Order"], ["punc", ")"], ["punc", ";"]], [["var", "console"], ["op", "."], ["fnc", "log"], ["punc", "("], ["var", "q"], ["op", "."], ["prop", "max"], ["punc", ")"], ["punc", ";"]], [["kw", "const"], ["p", " "], ["var", "cap"], ["p", " "], ["op", "="], ["p", " "], ["var", "Math"], ["op", "."], ["bi", "max"], ["punc", "("], ["con", "LIMIT"], ["punc", ","], ["p", " "], ["num", "0"], ["punc", ")"], ["punc", ";"]]], "Java": [[["cmd", "/**"], ["doc", " A color theme. */"]], [["kw", "package"], ["p", " "], ["var", "com"], ["op", "."], ["var", "dupre"], ["punc", ";"]], [["kw", "import"], ["p", " "], ["var", "java"], ["op", "."], ["var", "util"], ["op", "."], ["var", "regex"], ["op", "."], ["ty", "Pattern"], ["punc", ";"]], [], [["dec", "@Deprecated"]], [["kw", "public"], ["p", " "], ["kw", "final"], ["p", " "], ["kw", "class"], ["p", " "], ["ty", "Theme"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "private"], ["p", " "], ["kw", "static"], ["p", " "], ["kw", "final"], ["p", " "], ["ty", "int"], ["p", " "], ["con", "MAX_PORT"], ["p", " "], ["op", "="], ["p", " "], ["num", "8080"], ["punc", ";"]], [["p", " "], ["kw", "private"], ["p", " "], ["kw", "final"], ["p", " "], ["ty", "String"], ["p", " "], ["prop", "name"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""], ["punc", ";"]], [["p", " "], ["kw", "private"], ["p", " "], ["kw", "static"], ["p", " "], ["kw", "final"], ["p", " "], ["ty", "Pattern"], ["p", " "], ["con", "HEX"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Pattern"], ["op", "."], ["fnc", "compile"], ["punc", "("], ["re", "\"#[0-9a-f]{6}\""], ["punc", ")"], ["punc", ";"]], [], [["p", " "], ["dec", "@Override"]], [["p", " "], ["kw", "public"], ["p", " "], ["ty", "String"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["ty", "String"], ["p", " "], ["var", "key"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["cmd", "//"], ["cm", " fall back to null"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "key"], ["op", "."], ["fnc", "isEmpty"], ["punc", "()"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "null"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["var", "key"], ["op", "."], ["fnc", "strip"], ["punc", "("], ["punc", ")"], ["op", "+"], ["str", "\""], ["esc", "\\t"], ["str", "\""], ["punc", ";"]], [["p", " "], ["punc", "}"]], [], [["p", " "], ["kw", "public"], ["p", " "], ["kw", "static"], ["p", " "], ["ty", "void"], ["p", " "], ["fnd", "main"], ["punc", "("], ["ty", "String"], ["punc", "[]"], ["p", " "], ["var", "args"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["ty", "var"], ["p", " "], ["var", "t"], ["p", " "], ["op", "="], ["p", " "], ["kw", "new"], ["p", " "], ["ty", "Theme"], ["punc", "()"], ["punc", ";"]], [["p", " "], ["ty", "System"], ["op", "."], ["prop", "out"], ["op", "."], ["fnc", "println"], ["punc", "("], ["var", "t"], ["op", "."], ["fnc", "resolve"], ["punc", "("], ["str", "\"bg\""], ["punc", "))"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["punc", "}"]]], "C": [[["cmd", "/**"], ["doc", " Order queue. */"]], [["pp", "#include"], ["p", " "], ["str", "<stdio.h>"]], [["pp", "#include"], ["p", " "], ["str", "<stdlib.h>"]], [["pp", "#define"], ["p", " "], ["con", "MAX_PORT"], ["p", " "], ["num", "8080"]], [], [["kw", "typedef"], ["p", " "], ["kw", "struct"], ["p", " "], ["punc", "{"]], [["p", " "], ["ty", "int"], ["p", " "], ["prop", "id"], ["punc", ";"]], [["p", " "], ["kw", "const"], ["p", " "], ["ty", "char"], ["p", " "], ["op", "*"], ["prop", "name"], ["punc", ";"]], [["punc", "}"], ["p", " "], ["ty", "Order"], ["punc", ";"]], [], [["cmd", "//"], ["cm", " returns -1 on null input"]], [["ty", "int"], ["p", " "], ["fnd", "push"], ["punc", "("], ["ty", "Order"], ["p", " "], ["op", "*"], ["var", "o"], ["punc", ")"], ["p", " "], ["dec", "__attribute__"], ["punc", "(("], ["dec", "nonnull"], ["punc", "))"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "o"], ["p", " "], ["op", "=="], ["p", " "], ["con", "NULL"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["num", "-1"], ["punc", ";"]], [["p", " "], ["fnc", "printf"], ["punc", "("], ["str", "\"id=%d"], ["esc", "\\n"], ["str", "\""], ["punc", ","], ["p", " "], ["var", "o"], ["op", "->"], ["prop", "id"], ["punc", ");"]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "0"], ["punc", ";"]], [["punc", "}"]], [], [["ty", "int"], ["p", " "], ["fnd", "main"], ["punc", "("], ["ty", "void"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["ty", "Order"], ["p", " "], ["var", "o"], ["p", " "], ["op", "="], ["p", " "], ["punc", "{"], ["p", " "], ["op", "."], ["prop", "id"], ["p", " "], ["op", "="], ["p", " "], ["num", "1"], ["punc", ","], ["p", " "], ["op", "."], ["prop", "name"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""], ["p", " "], ["punc", "}"], ["punc", ";"]], [["p", " "], ["ty", "Order"], ["p", " "], ["op", "*"], ["var", "p2"], ["p", " "], ["op", "="], ["p", " "], ["bi", "malloc"], ["punc", "("], ["bi", "sizeof"], ["punc", "("], ["ty", "Order"], ["punc", "))"], ["punc", ";"]], [["p", " "], ["fnc", "push"], ["punc", "("], ["op", "&"], ["var", "o"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["bi", "free"], ["punc", "("], ["var", "p2"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "0"], ["punc", ";"]], [["punc", "}"]]], "C++": [[["cmd", "/**"], ["doc", " A color theme. */"]], [["pp", "#include"], ["p", " "], ["str", "<string>"]], [["pp", "#include"], ["p", " "], ["str", "<regex>"]], [["pp", "#pragma"], ["p", " "], ["pp", "once"]], [], [["kw", "namespace"], ["p", " "], ["var", "dupre"], ["p", " "], ["punc", "{"]], [], [["kw", "template"], ["op", "<"], ["kw", "typename"], ["p", " "], ["ty", "T"], ["op", ">"], ["p", " "], ["kw", "class"], ["p", " "], ["ty", "Theme"], ["p", " "], ["punc", "{"]], [["kw", "public"], ["op", ":"]], [["p", " "], ["kw", "static"], ["p", " "], ["kw", "constexpr"], ["p", " "], ["ty", "int"], ["p", " "], ["con", "MAX"], ["p", " "], ["op", "="], ["p", " "], ["num", "0x20"], ["punc", ";"]], [["p", " "], ["ty", "std"], ["op", "::"], ["ty", "string"], ["p", " "], ["prop", "name_"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""], ["punc", ";"]], [], [["p", " "], ["dec", "[[nodiscard]]"], ["p", " "], ["ty", "T"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["kw", "const"], ["p", " "], ["ty", "std"], ["op", "::"], ["ty", "string"], ["op", "&"], ["p", " "], ["var", "key"], ["punc", ")"], ["p", " "], ["kw", "const"], ["p", " "], ["punc", "{"]], [["p", " "], ["cmd", "//"], ["cm", " validate against a hex pattern"]], [["p", " "], ["kw", "static"], ["p", " "], ["ty", "std"], ["op", "::"], ["ty", "regex"], ["p", " "], ["var", "re"], ["punc", "("], ["re", "R\"(#[0-9a-f]{6})\""], ["punc", ")"], ["punc", ";"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "key"], ["op", "."], ["fnc", "empty"], ["punc", "()"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "nullptr"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["ty", "T"], ["punc", "{"], ["var", "key"], ["punc", "}"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["punc", "}"], ["punc", ";"]], [], [["ty", "int"], ["p", " "], ["fnd", "main"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "auto"], ["p", " "], ["var", "t"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Theme"], ["op", "<"], ["ty", "int"], ["op", ">"], ["punc", "{}"], ["punc", ";"]], [["p", " "], ["bi", "static_cast"], ["op", "<"], ["ty", "int"], ["op", ">"], ["punc", "("], ["var", "t"], ["op", "."], ["prop", "name_"], ["op", "."], ["fnc", "size"], ["punc", "())"], ["punc", ";"]], [["p", " "], ["ty", "std"], ["op", "::"], ["fnc", "printf"], ["punc", "("], ["str", "\"%s"], ["esc", "\\n"], ["str", "\""], ["punc", ","], ["p", " "], ["var", "t"], ["op", "."], ["prop", "name_"], ["op", "."], ["fnc", "c_str"], ["punc", "())"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "0"], ["punc", ";"]], [["punc", "}"]]], "Shell": [[["cmd", "#!"], ["cm", "/bin/bash"]], [["cmd", "#"], ["cm", " deploy.sh"]], [["bi", "set"], ["p", " "], ["op", "-"], ["var", "euo"], ["p", " "], ["var", "pipefail"]], [], [["var", "PORT"], ["op", "="], ["num", "8080"]], [["var", "NAME"], ["op", "="], ["str", "\"dupre\""]], [], [["fnd", "deploy"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "local"], ["p", " "], ["var", "target"], ["op", "="], ["str", "\"$1\""]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "[["], ["p", " "], ["op", "-z"], ["p", " "], ["str", "\"$target\""], ["p", " "], ["punc", "]]"], ["punc", ";"], ["p", " "], ["kw", "then"]], [["p", " "], ["bi", "echo"], ["p", " "], ["str", "\"no target\""]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "1"]], [["p", " "], ["kw", "fi"]], [["p", " "], ["fnc", "rsync"], ["p", " "], ["op", "-az"], ["p", " "], ["str", "\"$NAME\""], ["p", " "], ["str", "\"$target\""]], [["punc", "}"]], [], [["fnd", "main"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "for"], ["p", " "], ["var", "host"], ["p", " "], ["kw", "in"], ["p", " "], ["str", "\"$@\""], ["punc", ";"], ["p", " "], ["kw", "do"]], [["p", " "], ["fnc", "deploy"], ["p", " "], ["str", "\"$host\""], ["p", " "], ["op", "||"], ["p", " "], ["bi", "exit"], ["p", " "], ["num", "1"]], [["p", " "], ["kw", "done"]], [["p", " "], ["bi", "echo"], ["p", " "], ["op", "-e"], ["p", " "], ["str", "\"all done"], ["esc", "\\n"], ["str", "\""]], [["punc", "}"]], [], [["fnc", "main"], ["p", " "], ["str", "\"$@\""]]]}, CATS=[["bg", "background (ground)", "Aa Bb 123"], ["p", "fg \u00b7 default text", "other / whitespace"], ["kw", "keyword", "class def if return"], ["bi", "builtin", "len echo printf"], ["pp", "preprocessor", "#include #define"], ["fnd", "function \u00b7 def", "resolve push"], ["fnc", "function \u00b7 call", "printf rsync get"], ["dec", "decorator", "@dataclass"], ["ty", "type / class", "int str Order Queue"], ["prop", "property / field", "id name items"], ["con", "constant", "None nil NULL true"], ["num", "number", "8080 100 -1"], ["str", "string", "\"dupre\" \"fmt\""], ["esc", "escape", "\\n \\t"], ["re", "regexp", "/^#[0-9a-f]+/"], ["doc", "docstring", "\"\"\"...\"\"\""], ["cm", "comment", "# reject nil"], ["cmd", "comment delim", "# // ;;"], ["var", "variable / use", "value key self"], ["op", "operator", ": = -> =="], ["punc", "punctuation", "{ } ( ) ;"]], UI_FACES=[["cursor", "cursor", "Aa|"], ["region", "region (selection)", "selected text"], ["hl-line", "hl-line (current line)", "current line"], ["highlight", "highlight", "hover"], ["mode-line", "mode-line", "status active"], ["mode-line-inactive", "mode-line-inactive", "status idle"], ["fringe", "fringe", "| |"], ["line-number", "line-number", " 42"], ["line-number-current-line", "line-number-current-line", "> 42"], ["minibuffer-prompt", "minibuffer-prompt", "M-x "], ["isearch", "isearch (match)", "match"], ["lazy-highlight", "lazy-highlight", "other match"], ["isearch-fail", "isearch-fail", "no match"], ["show-paren-match", "show-paren-match", "( )"], ["show-paren-mismatch", "show-paren-mismatch", ") ("], ["link", "link", "https://"], ["error", "error", "error!"], ["warning", "warning", "warning"], ["success", "success", "ok"], ["vertical-border", "vertical-border", "|"]], APPS={"org-mode": {"label": "org-mode", "preview": "org", "faces": [["org-document-title", "document title", {"fg": "gold", "bold": true, "height": 1.5}], ["org-document-info", "document info", {"fg": "steel"}], ["org-document-info-keyword", "document info keyword", {"fg": "pewter", "inherit": "fixed-pitch"}], ["org-level-1", "level 1", {"fg": "blue", "bold": true, "height": 1.3}], ["org-level-2", "level 2", {"fg": "gold", "height": 1.2}], ["org-level-3", "level 3", {"fg": "regal", "height": 1.15}], ["org-level-4", "level 4", {"fg": "emerald", "height": 1.1}], ["org-level-5", "level 5", {"fg": "terracotta"}], ["org-level-6", "level 6", {"fg": "tan"}], ["org-level-7", "level 7", {"fg": "sage"}], ["org-level-8", "level 8", {"fg": "steel"}], ["org-headline-todo", "headline todo", {}], ["org-headline-done", "headline done", {"fg": "pewter"}], ["org-todo", "todo", {"fg": "terracotta", "bold": true}], ["org-done", "done", {"fg": "sage", "bold": true}], ["org-priority", "priority", {"fg": "gold", "bold": true}], ["org-tag", "tag", {"fg": "tan"}], ["org-tag-group", "tag group", {"fg": "tan"}], ["org-special-keyword", "special keyword", {"fg": "pewter"}], ["org-drawer", "drawer", {"fg": "pewter"}], ["org-property-value", "property value", {"fg": "steel"}], ["org-checkbox", "checkbox", {"fg": "gold", "inherit": "fixed-pitch"}], ["org-checkbox-statistics-todo", "checkbox statistics todo", {"fg": "terracotta"}], ["org-checkbox-statistics-done", "checkbox statistics done", {"fg": "sage"}], ["org-warning", "warning", {"fg": "terracotta", "bold": true}], ["org-link", "link", {"fg": "blue"}], ["org-footnote", "footnote", {"fg": "blue"}], ["org-date", "date", {"fg": "steel", "inherit": "fixed-pitch"}], ["org-sexp-date", "sexp date", {"fg": "steel"}], ["org-date-selected", "date selected", {"fg": "ground", "bg": "gold"}], ["org-target", "target", {"fg": "regal"}], ["org-macro", "macro", {"fg": "regal"}], ["org-cite", "cite", {"fg": "blue"}], ["org-cite-key", "cite key", {"fg": "blue"}], ["org-block", "block", {"fg": "white", "bg": "bg-dim", "inherit": "fixed-pitch"}], ["org-block-begin-line", "block begin line", {"fg": "pewter", "bg": "bg-dim", "inherit": "fixed-pitch"}], ["org-block-end-line", "block end line", {"fg": "pewter", "bg": "bg-dim", "inherit": "fixed-pitch"}], ["org-code", "code", {"fg": "terracotta", "inherit": "fixed-pitch"}], ["org-verbatim", "verbatim", {"fg": "steel", "inherit": "fixed-pitch"}], ["org-inline-src-block", "inline src block", {"fg": "terracotta", "inherit": "fixed-pitch"}], ["org-quote", "quote", {"fg": "silver", "italic": true}], ["org-verse", "verse", {"fg": "silver", "italic": true}], ["org-latex-and-related", "latex and related", {"fg": "gold"}], ["org-table", "table", {"fg": "steel", "inherit": "fixed-pitch"}], ["org-table-header", "table header", {"fg": "white", "bold": true, "bg": "gunmetal"}], ["org-table-row", "table row", {}], ["org-formula", "formula", {"fg": "terracotta"}], ["org-column", "column", {"bg": "gunmetal"}], ["org-column-title", "column title", {"fg": "white", "bold": true, "bg": "gunmetal"}], ["org-list-dt", "list dt", {"fg": "gold", "bold": true}], ["org-meta-line", "meta line", {"fg": "pewter", "inherit": "fixed-pitch"}], ["org-ellipsis", "ellipsis", {"fg": "pewter"}], ["org-hide", "hide", {"fg": "ground"}], ["org-indent", "indent", {"fg": "ground"}], ["org-archived", "archived", {"fg": "pewter"}], ["org-default", "default", {}], ["org-dispatcher-highlight", "dispatcher highlight", {"fg": "gold", "bold": true, "bg": "navy"}], ["org-agenda-structure", "agenda structure", {"fg": "blue", "bold": true, "height": 1.1}], ["org-agenda-structure-secondary", "agenda structure secondary", {"fg": "blue"}], ["org-agenda-structure-filter", "agenda structure filter", {"fg": "terracotta", "bold": true}], ["org-agenda-date", "agenda date", {"fg": "steel", "height": 1.05}], ["org-agenda-date-today", "agenda date today", {"fg": "gold", "bold": true, "height": 1.05}], ["org-agenda-date-weekend", "agenda date weekend", {"fg": "steel", "bold": true}], ["org-agenda-date-weekend-today", "agenda date weekend today", {"fg": "gold", "bold": true}], ["org-agenda-current-time", "agenda current time", {"fg": "gold"}], ["org-agenda-done", "agenda done", {"fg": "sage"}], ["org-agenda-dimmed-todo-face", "agenda dimmed todo", {"fg": "pewter"}], ["org-agenda-calendar-event", "agenda calendar event", {"fg": "white"}], ["org-agenda-calendar-sexp", "agenda calendar sexp", {"fg": "steel"}], ["org-agenda-calendar-daterange", "agenda calendar daterange", {"fg": "steel"}], ["org-agenda-diary", "agenda diary", {"fg": "sage"}], ["org-agenda-clocking", "agenda clocking", {"bg": "navy"}], ["org-agenda-column-dateline", "agenda column dateline", {"bg": "gunmetal"}], ["org-agenda-restriction-lock", "agenda restriction lock", {"bg": "terracotta"}], ["org-agenda-filter-category", "agenda filter category", {"fg": "gold", "bold": true}], ["org-agenda-filter-effort", "agenda filter effort", {"fg": "gold", "bold": true}], ["org-agenda-filter-regexp", "agenda filter regexp", {"fg": "gold", "bold": true}], ["org-agenda-filter-tags", "agenda filter tags", {"fg": "gold", "bold": true}], ["org-scheduled", "scheduled", {"fg": "sage"}], ["org-scheduled-today", "scheduled today", {"fg": "sage", "bold": true}], ["org-scheduled-previously", "scheduled previously", {"fg": "terracotta"}], ["org-upcoming-deadline", "upcoming deadline", {"fg": "gold"}], ["org-upcoming-distant-deadline", "upcoming distant deadline", {"fg": "tan"}], ["org-imminent-deadline", "imminent deadline", {"fg": "terracotta", "bold": true}], ["org-time-grid", "time grid", {"fg": "tan"}], ["org-clock-overlay", "clock overlay", {"bg": "navy"}], ["org-mode-line-clock", "mode line clock", {"fg": "steel"}], ["org-mode-line-clock-overrun", "mode line clock overrun", {"fg": "terracotta", "bold": true}]]}, "magit": {"label": "magit", "preview": "magit", "faces": [["magit-section-heading", "section heading", {"fg": "gold", "bold": true}], ["magit-section-secondary-heading", "section secondary heading", {"fg": "tan", "bold": true}], ["magit-section-heading-selection", "section heading selection", {"fg": "gold", "bg": "navy"}], ["magit-section-highlight", "section highlight", {"bg": "bg-dim"}], ["magit-section-child-count", "section child count", {"fg": "pewter"}], ["magit-diff-added", "diff added", {"fg": "sage"}], ["magit-diff-added-highlight", "diff added highlight", {"fg": "sage", "bg": "bg-dim"}], ["magit-diff-removed", "diff removed", {"fg": "terracotta"}], ["magit-diff-removed-highlight", "diff removed highlight", {"fg": "terracotta", "bg": "bg-dim"}], ["magit-diff-context", "diff context", {"fg": "pewter"}], ["magit-diff-context-highlight", "diff context highlight", {"fg": "silver", "bg": "bg-dim"}], ["magit-diff-file-heading", "diff file heading", {"fg": "white", "bold": true}], ["magit-diff-file-heading-highlight", "diff file heading highlight", {"fg": "white", "bold": true, "bg": "bg-dim"}], ["magit-diff-file-heading-selection", "diff file heading selection", {}], ["magit-diff-hunk-heading", "diff hunk heading", {"fg": "steel", "bg": "gunmetal"}], ["magit-diff-hunk-heading-highlight", "diff hunk heading highlight", {"fg": "white", "bg": "gunmetal"}], ["magit-diff-hunk-heading-selection", "diff hunk heading selection", {}], ["magit-diff-hunk-region", "diff hunk region", {}], ["magit-diff-lines-heading", "diff lines heading", {}], ["magit-diff-lines-boundary", "diff lines boundary", {}], ["magit-diff-base", "diff base", {}], ["magit-diff-base-highlight", "diff base highlight", {}], ["magit-diff-our", "diff our", {}], ["magit-diff-our-highlight", "diff our highlight", {}], ["magit-diff-their", "diff their", {}], ["magit-diff-their-highlight", "diff their highlight", {}], ["magit-diff-conflict-heading", "diff conflict heading", {}], ["magit-diff-conflict-heading-highlight", "diff conflict heading highlight", {}], ["magit-diff-revision-summary", "diff revision summary", {}], ["magit-diff-revision-summary-highlight", "diff revision summary highlight", {}], ["magit-diff-whitespace-warning", "diff whitespace warning", {"bg": "terracotta"}], ["magit-diffstat-added", "diffstat added", {"fg": "sage"}], ["magit-diffstat-removed", "diffstat removed", {"fg": "terracotta"}], ["magit-branch-current", "branch current", {"fg": "blue", "bold": true}], ["magit-branch-local", "branch local", {"fg": "blue"}], ["magit-branch-remote", "branch remote", {"fg": "sage"}], ["magit-branch-remote-head", "branch remote head", {"fg": "sage", "bold": true}], ["magit-branch-upstream", "branch upstream", {}], ["magit-branch-warning", "branch warning", {}], ["magit-head", "head", {"fg": "blue", "bold": true}], ["magit-tag", "tag", {"fg": "gold"}], ["magit-hash", "hash", {"fg": "pewter"}], ["magit-filename", "filename", {"fg": "steel"}], ["magit-dimmed", "dimmed", {"fg": "pewter"}], ["magit-keyword", "keyword", {"fg": "regal"}], ["magit-keyword-squash", "keyword squash", {"fg": "terracotta"}], ["magit-refname", "refname", {"fg": "pewter"}], ["magit-refname-stash", "refname stash", {}], ["magit-refname-wip", "refname wip", {}], ["magit-refname-pullreq", "refname pullreq", {}], ["magit-log-author", "log author", {"fg": "tan"}], ["magit-log-date", "log date", {"fg": "steel"}], ["magit-log-graph", "log graph", {"fg": "pewter"}], ["magit-header-line", "header line", {"fg": "white", "bold": true, "bg": "gunmetal"}], ["magit-header-line-key", "header line key", {}], ["magit-header-line-log-select", "header line log select", {}], ["magit-process-ok", "process ok", {"fg": "sage", "bold": true}], ["magit-process-ng", "process ng", {"fg": "terracotta", "bold": true}], ["magit-mode-line-process", "mode line process", {"fg": "sage"}], ["magit-mode-line-process-error", "mode line process error", {"fg": "terracotta"}], ["magit-bisect-good", "bisect good", {"fg": "sage"}], ["magit-bisect-bad", "bisect bad", {"fg": "terracotta"}], ["magit-bisect-skip", "bisect skip", {"fg": "gold"}], ["magit-blame-heading", "blame heading", {"fg": "steel", "bg": "gunmetal"}], ["magit-blame-highlight", "blame highlight", {}], ["magit-blame-hash", "blame hash", {"fg": "pewter"}], ["magit-blame-name", "blame name", {"fg": "tan"}], ["magit-blame-date", "blame date", {"fg": "steel"}], ["magit-blame-summary", "blame summary", {}], ["magit-blame-dimmed", "blame dimmed", {}], ["magit-blame-margin", "blame margin", {}], ["magit-cherry-equivalent", "cherry equivalent", {"fg": "regal"}], ["magit-cherry-unmatched", "cherry unmatched", {"fg": "sage"}], ["magit-signature-good", "signature good", {"fg": "sage"}], ["magit-signature-bad", "signature bad", {"fg": "terracotta", "bold": true}], ["magit-signature-untrusted", "signature untrusted", {"fg": "gold"}], ["magit-signature-expired", "signature expired", {"fg": "tan"}], ["magit-signature-expired-key", "signature expired key", {}], ["magit-signature-revoked", "signature revoked", {}], ["magit-signature-error", "signature error", {}], ["magit-reflog-commit", "reflog commit", {"fg": "sage"}], ["magit-reflog-amend", "reflog amend", {"fg": "regal"}], ["magit-reflog-merge", "reflog merge", {"fg": "sage"}], ["magit-reflog-checkout", "reflog checkout", {"fg": "blue"}], ["magit-reflog-reset", "reflog reset", {"fg": "terracotta"}], ["magit-reflog-rebase", "reflog rebase", {"fg": "regal"}], ["magit-reflog-cherry-pick", "reflog cherry pick", {"fg": "sage"}], ["magit-reflog-remote", "reflog remote", {"fg": "steel"}], ["magit-reflog-other", "reflog other", {"fg": "steel"}], ["magit-sequence-pick", "sequence pick", {"fg": "white"}], ["magit-sequence-stop", "sequence stop", {"fg": "terracotta"}], ["magit-sequence-part", "sequence part", {}], ["magit-sequence-head", "sequence head", {"fg": "blue"}], ["magit-sequence-drop", "sequence drop", {}], ["magit-sequence-done", "sequence done", {"fg": "pewter"}], ["magit-sequence-onto", "sequence onto", {}], ["magit-sequence-exec", "sequence exec", {}], ["magit-left-margin", "left margin", {}]]}, "elfeed": {"label": "elfeed", "preview": "elfeed", "faces": [["elfeed-search-date-face", "search date", {"fg": "steel"}], ["elfeed-search-title-face", "search title", {"fg": "silver"}], ["elfeed-search-unread-title-face", "search unread title", {"fg": "white", "bold": true}], ["elfeed-search-feed-face", "search feed", {"fg": "sage"}], ["elfeed-search-tag-face", "search tag", {"fg": "tan"}], ["elfeed-search-unread-count-face", "search unread count", {"fg": "gold"}], ["elfeed-search-filter-face", "search filter", {"fg": "blue", "bold": true}], ["elfeed-search-last-update-face", "search last update", {"fg": "pewter"}], ["elfeed-log-date-face", "log date", {"fg": "steel"}], ["elfeed-log-error-level-face", "log error level", {"fg": "terracotta", "bold": true}], ["elfeed-log-warn-level-face", "log warn level", {"fg": "gold"}], ["elfeed-log-info-level-face", "log info level", {"fg": "sage"}], ["elfeed-log-debug-level-face", "log debug level", {"fg": "pewter"}]]}, "mu4e": {"label": "mu4e", "preview": "mu4e", "faces": [["mu4e-title-face", "title", {"fg": "blue", "bold": true}], ["mu4e-context-face", "context", {"fg": "blue", "bold": true}], ["mu4e-modeline-face", "modeline", {"fg": "silver"}], ["mu4e-ok-face", "ok", {"fg": "sage", "bold": true}], ["mu4e-warning-face", "warning", {"fg": "gold", "bold": true}], ["mu4e-header-title-face", "header title", {"fg": "blue", "bold": true}], ["mu4e-header-key-face", "header key", {"fg": "blue", "bold": true}], ["mu4e-header-value-face", "header value", {"fg": "silver"}], ["mu4e-header-face", "header", {"fg": "#cdced1"}], ["mu4e-header-highlight-face", "header highlight", {"bg": "gunmetal"}], ["mu4e-header-marks-face", "header marks", {"fg": "gold"}], ["mu4e-unread-face", "unread", {"fg": "white", "bold": true}], ["mu4e-flagged-face", "flagged", {"fg": "gold"}], ["mu4e-replied-face", "replied", {"fg": "silver"}], ["mu4e-forwarded-face", "forwarded", {"fg": "silver"}], ["mu4e-draft-face", "draft", {"fg": "steel", "italic": true}], ["mu4e-trashed-face", "trashed", {"fg": "pewter", "strike": true}], ["mu4e-moved-face", "moved", {"fg": "steel", "italic": true}], ["mu4e-related-face", "related", {"fg": "steel", "italic": true}], ["mu4e-contact-face", "contact", {"fg": "#cdced1"}], ["mu4e-special-header-value-face", "special header value", {"fg": "silver"}], ["mu4e-attach-number-face", "attach number", {"fg": "blue", "bold": true}], ["mu4e-url-number-face", "url number", {"fg": "blue", "bold": true}], ["mu4e-link-face", "link", {"fg": "blue", "underline": true}], ["mu4e-cited-1-face", "cited 1", {"fg": "silver"}], ["mu4e-cited-2-face", "cited 2", {"fg": "steel"}], ["mu4e-cited-3-face", "cited 3", {"fg": "sage"}], ["mu4e-cited-4-face", "cited 4", {"fg": "pewter"}], ["mu4e-cited-5-face", "cited 5", {"fg": "tan"}], ["mu4e-cited-6-face", "cited 6", {"fg": "terracotta"}], ["mu4e-cited-7-face", "cited 7", {"fg": "regal"}], ["mu4e-footer-face", "footer", {"fg": "pewter"}], ["mu4e-region-code", "region code", {"bg": "bg-dim"}], ["mu4e-system-face", "system", {"fg": "pewter", "italic": true}], ["mu4e-highlight-face", "highlight", {"fg": "gold", "bold": true}], ["mu4e-compose-header-face", "compose header", {"fg": "blue", "bold": true}], ["mu4e-compose-separator-face", "compose separator", {"fg": "pewter"}]]}, "ghostel": {"label": "ghostel", "preview": "ghostel", "faces": [["ghostel-default", "default", {"fg": "#cdced1"}], ["ghostel-fake-cursor", "fake cursor", {"fg": "#000000", "bg": "silver"}], ["ghostel-fake-cursor-box", "fake cursor box", {"fg": "silver"}], ["ghostel-color-black", "color black", {"fg": "pewter"}], ["ghostel-color-red", "color red", {"fg": "terracotta"}], ["ghostel-color-green", "color green", {"fg": "emerald"}], ["ghostel-color-yellow", "color yellow", {"fg": "gold"}], ["ghostel-color-blue", "color blue", {"fg": "blue"}], ["ghostel-color-magenta", "color magenta", {"fg": "regal"}], ["ghostel-color-cyan", "color cyan", {"fg": "sage"}], ["ghostel-color-white", "color white", {"fg": "silver"}], ["ghostel-color-bright-black", "color bright black", {"fg": "steel"}], ["ghostel-color-bright-red", "color bright red", {"fg": "#de4949"}], ["ghostel-color-bright-green", "color bright green", {"fg": "#84b068"}], ["ghostel-color-bright-yellow", "color bright yellow", {"fg": "#eed376"}], ["ghostel-color-bright-blue", "color bright blue", {"fg": "#7a9abe"}], ["ghostel-color-bright-magenta", "color bright magenta", {"fg": "#b07fd0"}], ["ghostel-color-bright-cyan", "color bright cyan", {"fg": "#7fc0a8"}], ["ghostel-color-bright-white", "color bright white", {"fg": "white"}]]}, "dashboard": {"label": "dashboard", "preview": "dashboard", "faces": [["dashboard-banner-logo-title", "banner logo title", {"fg": "gold", "bold": true}], ["dashboard-text-banner", "text banner", {"fg": "steel"}], ["dashboard-heading", "heading", {"fg": "blue", "bold": true}], ["dashboard-items-face", "items", {"fg": "#cdced1"}], ["dashboard-navigator", "navigator", {"fg": "blue"}], ["dashboard-no-items-face", "no items", {"fg": "pewter"}], ["dashboard-footer-face", "footer", {"fg": "tan"}], ["dashboard-footer-icon-face", "footer icon", {"fg": "gold"}]]}, "lsp-mode": {"label": "lsp-mode", "preview": "lsp", "faces": [["lsp-signature-face", "signature", {"fg": "silver"}], ["lsp-signature-highlight-function-argument", "signature highlight function argument", {"fg": "gold", "bold": true}], ["lsp-signature-posframe", "signature posframe", {"bg": "bg-dim"}], ["lsp-face-highlight-read", "face highlight read", {"bg": "navy"}], ["lsp-face-highlight-write", "face highlight write", {"bg": "#3d2f4a"}], ["lsp-face-highlight-textual", "face highlight textual", {"bg": "gunmetal"}], ["lsp-face-rename", "face rename", {"bg": "gunmetal", "bold": true}], ["lsp-rename-placeholder-face", "rename placeholder", {"fg": "gold", "bold": true}], ["lsp-inlay-hint-face", "inlay hint", {"fg": "pewter", "italic": true}], ["lsp-inlay-hint-parameter-face", "inlay hint parameter", {"fg": "steel", "italic": true}], ["lsp-inlay-hint-type-face", "inlay hint type", {"fg": "sage", "italic": true}], ["lsp-details-face", "details", {"fg": "pewter", "italic": true}], ["lsp-installation-buffer-face", "installation buffer", {"fg": "blue"}], ["lsp-installation-finished-buffer-face", "installation finished buffer", {"fg": "sage"}]]}, "git-gutter": {"label": "git-gutter", "preview": "gitgutter", "faces": [["git-gutter:added", "added", {"fg": "emerald"}], ["git-gutter:modified", "modified", {"fg": "gold"}], ["git-gutter:deleted", "deleted", {"fg": "terracotta"}], ["git-gutter:unchanged", "unchanged", {"fg": "pewter"}], ["git-gutter:separator", "separator", {"fg": "steel"}]]}, "flycheck": {"label": "flycheck", "preview": "flycheck", "faces": [["flycheck-error", "error", {"fg": "terracotta"}], ["flycheck-warning", "warning", {"fg": "gold"}], ["flycheck-info", "info", {"fg": "blue"}], ["flycheck-fringe-error", "fringe error", {"fg": "terracotta"}], ["flycheck-fringe-warning", "fringe warning", {"fg": "gold"}], ["flycheck-fringe-info", "fringe info", {"fg": "blue"}], ["flycheck-delimited-error", "delimited error", {"fg": "terracotta"}], ["flycheck-error-delimiter", "error delimiter", {"fg": "terracotta"}], ["flycheck-error-list-error", "error list error", {"fg": "terracotta"}], ["flycheck-error-list-warning", "error list warning", {"fg": "gold"}], ["flycheck-error-list-info", "error list info", {"fg": "blue"}], ["flycheck-error-list-error-message", "error list error message", {"fg": "#cdced1"}], ["flycheck-error-list-checker-name", "error list checker name", {"fg": "steel"}], ["flycheck-error-list-column-number", "error list column number", {"fg": "pewter"}], ["flycheck-error-list-line-number", "error list line number", {"fg": "pewter"}], ["flycheck-error-list-filename", "error list filename", {"fg": "blue"}], ["flycheck-error-list-id", "error list id", {"fg": "steel"}], ["flycheck-error-list-id-with-explainer", "error list id with explainer", {"fg": "steel", "bold": true}], ["flycheck-error-list-highlight", "error list highlight", {"bg": "gunmetal"}], ["flycheck-verify-select-checker", "verify select checker", {"fg": "gold"}]]}, "dired": {"label": "dired", "preview": "dired", "faces": [["dired-header", "header", {"fg": "blue", "bold": true}], ["dired-directory", "directory", {"fg": "blue", "bold": true}], ["dired-symlink", "symlink", {"fg": "sage"}], ["dired-broken-symlink", "broken symlink", {"fg": "#de4949", "bold": true}], ["dired-special", "special", {"fg": "regal"}], ["dired-set-id", "set id", {"fg": "terracotta"}], ["dired-perm-write", "perm write", {"fg": "silver"}], ["dired-mark", "mark", {"fg": "gold"}], ["dired-marked", "marked", {"fg": "gold", "bold": true}], ["dired-flagged", "flagged", {"fg": "terracotta", "bold": true}], ["dired-ignored", "ignored", {"fg": "pewter"}], ["dired-warning", "warning", {"fg": "gold", "bold": true}]]}, "dirvish": {"label": "dirvish", "preview": "dirvish", "faces": [["dirvish-inactive", "inactive", {"fg": "pewter"}], ["dirvish-free-space", "free space", {"fg": "sage"}], ["dirvish-hl-line", "hl line", {"bg": "gunmetal"}], ["dirvish-hl-line-inactive", "hl line inactive", {"bg": "bg-dim"}], ["dirvish-file-modes", "file modes", {"fg": "steel"}], ["dirvish-file-link-number", "file link number", {"fg": "pewter"}], ["dirvish-file-user-id", "file user id", {"fg": "blue"}], ["dirvish-file-group-id", "file group id", {"fg": "steel"}], ["dirvish-file-size", "file size", {"fg": "sage"}], ["dirvish-file-time", "file time", {"fg": "pewter"}], ["dirvish-file-inode-number", "file inode number", {"fg": "pewter"}], ["dirvish-file-device-number", "file device number", {"fg": "pewter"}], ["dirvish-subtree-guide", "subtree guide", {"fg": "pewter"}], ["dirvish-subtree-state", "subtree state", {"fg": "steel"}], ["dirvish-collapse-dir-face", "collapse dir", {"fg": "blue"}], ["dirvish-collapse-empty-dir-face", "collapse empty dir", {"fg": "pewter"}], ["dirvish-collapse-file-face", "collapse file", {"fg": "silver"}], ["dirvish-emerge-group-title", "emerge group title", {"fg": "gold", "bold": true}], ["dirvish-media-info-heading", "media info heading", {"fg": "blue", "bold": true}], ["dirvish-media-info-property-key", "media info property key", {"fg": "steel"}], ["dirvish-narrow-match-face-0", "narrow match 0", {"fg": "gold", "bold": true}], ["dirvish-narrow-match-face-1", "narrow match 1", {"fg": "blue", "bold": true}], ["dirvish-narrow-match-face-2", "narrow match 2", {"fg": "emerald", "bold": true}], ["dirvish-narrow-match-face-3", "narrow match 3", {"fg": "regal", "bold": true}], ["dirvish-narrow-split", "narrow split", {"fg": "pewter"}], ["dirvish-proc-running", "proc running", {"fg": "gold"}], ["dirvish-proc-finished", "proc finished", {"fg": "sage"}], ["dirvish-proc-failed", "proc failed", {"fg": "terracotta"}], ["dirvish-git-commit-message-face", "git commit message", {"fg": "tan", "italic": true}], ["dirvish-vc-added-state", "vc added state", {"fg": "sage"}], ["dirvish-vc-edited-state", "vc edited state", {"fg": "gold"}], ["dirvish-vc-removed-state", "vc removed state", {"fg": "terracotta"}], ["dirvish-vc-conflict-state", "vc conflict state", {"fg": "terracotta", "bold": true}], ["dirvish-vc-locked-state", "vc locked state", {"fg": "blue"}], ["dirvish-vc-missing-state", "vc missing state", {"fg": "terracotta"}], ["dirvish-vc-needs-merge-face", "vc needs merge", {"fg": "gold"}], ["dirvish-vc-needs-update-state", "vc needs update state", {"fg": "gold"}], ["dirvish-vc-unregistered-face", "vc unregistered", {"fg": "pewter"}]]}, "calibredb": {"label": "calibredb", "preview": "calibredb", "faces": [["calibredb-search-header-library-name-face", "search header library name", {"fg": "blue", "bold": true}], ["calibredb-search-header-library-path-face", "search header library path", {"fg": "pewter"}], ["calibredb-search-header-total-face", "search header total", {"fg": "sage"}], ["calibredb-search-header-filter-face", "search header filter", {"fg": "gold"}], ["calibredb-search-header-sort-face", "search header sort", {"fg": "steel"}], ["calibredb-search-header-highlight-face", "search header highlight", {"fg": "gold", "bold": true}], ["calibredb-id-face", "id", {"fg": "pewter"}], ["calibredb-title-face", "title", {"fg": "blue", "bold": true}], ["calibredb-author-face", "author", {"fg": "sage"}], ["calibredb-format-face", "format", {"fg": "steel"}], ["calibredb-size-face", "size", {"fg": "pewter"}], ["calibredb-tag-face", "tag", {"fg": "tan"}], ["calibredb-date-face", "date", {"fg": "pewter"}], ["calibredb-mark-face", "mark", {"fg": "gold", "bold": true}], ["calibredb-series-face", "series", {"fg": "regal"}], ["calibredb-publisher-face", "publisher", {"fg": "steel"}], ["calibredb-pubdate-face", "pubdate", {"fg": "pewter"}], ["calibredb-language-face", "language", {"fg": "steel"}], ["calibredb-comment-face", "comment", {"fg": "silver", "italic": true}], ["calibredb-archive-face", "archive", {"fg": "pewter"}], ["calibredb-favorite-face", "favorite", {"fg": "gold"}], ["calibredb-file-face", "file", {"fg": "blue"}], ["calibredb-ids-face", "ids", {"fg": "pewter"}], ["calibredb-highlight-face", "highlight", {"fg": "gold", "bold": true}], ["calibredb-current-page-button-face", "current page button", {"fg": "blue", "bold": true}], ["calibredb-mouse-face", "mouse", {"bg": "gunmetal"}], ["calibredb-title-detailed-view-face", "title detailed view", {"fg": "gold", "bold": true}], ["calibredb-edit-annotation-header-title-face", "edit annotation header title", {"fg": "blue", "bold": true}]]}, "erc": {"label": "erc", "preview": "erc", "faces": [["erc-header-line", "header line", {"fg": "white", "bg": "gunmetal", "bold": true}], ["erc-timestamp-face", "timestamp", {"fg": "pewter"}], ["erc-notice-face", "notice", {"fg": "steel"}], ["erc-default-face", "default", {"fg": "#cdced1"}], ["erc-current-nick-face", "current nick", {"fg": "gold", "bold": true}], ["erc-my-nick-face", "my nick", {"fg": "gold", "bold": true}], ["erc-my-nick-prefix-face", "my nick prefix", {"fg": "gold"}], ["erc-nick-default-face", "nick default", {"fg": "blue"}], ["erc-nick-prefix-face", "nick prefix", {"fg": "sage"}], ["erc-button-nick-default-face", "button nick default", {"fg": "blue"}], ["erc-nick-msg-face", "nick msg", {"fg": "regal"}], ["erc-direct-msg-face", "direct msg", {"fg": "regal"}], ["erc-action-face", "action", {"fg": "sage", "italic": true}], ["erc-keyword-face", "keyword", {"fg": "gold", "bold": true}], ["erc-pal-face", "pal", {"fg": "emerald"}], ["erc-fool-face", "fool", {"fg": "pewter"}], ["erc-dangerous-host-face", "dangerous host", {"fg": "terracotta", "bold": true}], ["erc-error-face", "error", {"fg": "terracotta", "bold": true}], ["erc-input-face", "input", {"fg": "silver"}], ["erc-prompt-face", "prompt", {"fg": "blue", "bold": true}], ["erc-command-indicator-face", "command indicator", {"fg": "steel", "bold": true}], ["erc-information", "information", {"fg": "steel"}], ["erc-button", "button", {"fg": "blue"}], ["erc-bold-face", "bold", {"bold": true}], ["erc-italic-face", "italic", {"italic": true}], ["erc-underline-face", "underline", {"fg": "silver", "underline": true}], ["erc-inverse-face", "inverse", {"fg": "#000000", "bg": "silver"}], ["erc-spoiler-face", "spoiler", {"fg": "#000000", "bg": "gunmetal"}], ["erc-fill-wrap-merge-indicator-face", "fill wrap merge indicator", {"fg": "pewter"}], ["erc-keep-place-indicator-arrow", "keep place indicator arrow", {"fg": "gold"}], ["erc-keep-place-indicator-line", "keep place indicator line", {"bg": "bg-dim"}]]}, "org-drill": {"label": "org-drill", "preview": "orgdrill", "faces": [["org-drill-hidden-cloze-face", "hidden cloze", {"fg": "#000000", "bg": "steel"}], ["org-drill-visible-cloze-face", "visible cloze", {"fg": "gold", "bold": true}], ["org-drill-visible-cloze-hint-face", "visible cloze hint", {"fg": "pewter", "italic": true}]]}, "org-noter": {"label": "org-noter", "preview": "orgnoter", "faces": [["org-noter-notes-exist-face", "notes exist", {"fg": "sage"}], ["org-noter-no-notes-exist-face", "no notes exist", {"fg": "pewter"}]]}, "signel": {"label": "signel", "preview": "signel", "faces": [["signel-timestamp-face", "timestamp", {"fg": "pewter"}], ["signel-my-msg-face", "my msg", {"fg": "blue"}], ["signel-other-msg-face", "other msg", {"fg": "silver"}], ["signel-error-face", "error", {"fg": "terracotta", "bold": true}]]}, "pearl": {"label": "pearl", "preview": "pearl", "faces": [["pearl-preamble-summary", "preamble summary", {"fg": "blue", "bold": true}], ["pearl-editable-comment", "editable comment", {"fg": "silver"}], ["pearl-readonly-comment", "readonly comment", {"fg": "pewter", "italic": true}], ["pearl-modified-highlight", "modified highlight", {"bg": "navy"}], ["pearl-modified-local", "modified local", {"fg": "gold"}], ["pearl-modified-unknown", "modified unknown", {"fg": "pewter"}]]}, "slack": {"label": "slack", "preview": "slack", "faces": [["slack-room-info-title-face", "room info title", {"fg": "blue", "bold": true}], ["slack-room-info-title-room-name-face", "room info title room name", {"fg": "gold", "bold": true}], ["slack-room-info-section-title-face", "room info section title", {"fg": "blue", "bold": true}], ["slack-room-info-section-label-face", "room info section label", {"fg": "steel"}], ["slack-room-unread-face", "room unread", {"fg": "white", "bold": true}], ["slack-message-output-header", "message output header", {"fg": "blue", "bold": true}], ["slack-message-output-text", "message output text", {"fg": "#cdced1"}], ["slack-message-output-reaction", "message output reaction", {"fg": "steel"}], ["slack-message-output-reaction-pressed", "message output reaction pressed", {"fg": "gold", "bold": true}], ["slack-message-deleted-face", "message deleted", {"fg": "pewter", "italic": true}], ["slack-new-message-marker-face", "new message marker", {"fg": "terracotta", "bold": true}], ["slack-all-thread-buffer-thread-header-face", "all thread buffer thread header", {"fg": "blue", "bold": true}], ["slack-message-mention-face", "message mention", {"fg": "blue"}], ["slack-message-mention-me-face", "message mention me", {"fg": "gold", "bg": "navy", "bold": true}], ["slack-message-mention-keyword-face", "message mention keyword", {"fg": "gold", "bold": true}], ["slack-channel-button-face", "channel button", {"fg": "blue"}], ["slack-mrkdwn-bold-face", "mrkdwn bold", {"bold": true}], ["slack-mrkdwn-italic-face", "mrkdwn italic", {"italic": true}], ["slack-mrkdwn-code-face", "mrkdwn code", {"fg": "terracotta"}], ["slack-mrkdwn-code-block-face", "mrkdwn code block", {"fg": "terracotta", "bg": "bg-dim"}], ["slack-mrkdwn-strike-face", "mrkdwn strike", {"fg": "pewter", "strike": true}], ["slack-mrkdwn-blockquote-face", "mrkdwn blockquote", {"fg": "silver", "italic": true}], ["slack-mrkdwn-list-face", "mrkdwn list", {"fg": "silver"}], ["slack-attachment-header", "attachment header", {"fg": "blue", "bold": true}], ["slack-attachment-footer", "attachment footer", {"fg": "pewter"}], ["slack-attachment-pad", "attachment pad", {"fg": "pewter"}], ["slack-attachment-field-title", "attachment field title", {"fg": "steel", "bold": true}], ["slack-message-attachment-preview-header-face", "message attachment preview header", {"fg": "blue"}], ["slack-preview-face", "preview", {"fg": "silver"}], ["slack-block-highlight-source-overlay-face", "block highlight source overlay", {"bg": "bg-dim"}], ["slack-message-action-face", "message action", {"fg": "blue"}], ["slack-message-action-primary-face", "message action primary", {"fg": "sage"}], ["slack-message-action-danger-face", "message action danger", {"fg": "terracotta"}], ["slack-button-block-element-face", "button block element", {"fg": "silver"}], ["slack-button-primary-block-element-face", "button primary block element", {"fg": "sage", "bold": true}], ["slack-button-danger-block-element-face", "button danger block element", {"fg": "terracotta", "bold": true}], ["slack-select-block-element-face", "select block element", {"fg": "blue"}], ["slack-overflow-block-element-face", "overflow block element", {"fg": "steel"}], ["slack-date-picker-block-element-face", "date picker block element", {"fg": "blue"}], ["slack-dialog-title-face", "dialog title", {"fg": "blue", "bold": true}], ["slack-dialog-element-label-face", "dialog element label", {"fg": "steel"}], ["slack-dialog-element-hint-face", "dialog element hint", {"fg": "pewter", "italic": true}], ["slack-dialog-element-placeholder-face", "dialog element placeholder", {"fg": "pewter"}], ["slack-dialog-element-error-face", "dialog element error", {"fg": "terracotta"}], ["slack-dialog-submit-button-face", "dialog submit button", {"fg": "sage", "bold": true}], ["slack-dialog-cancel-button-face", "dialog cancel button", {"fg": "silver"}], ["slack-dialog-select-element-input-face", "dialog select element input", {"fg": "silver"}], ["slack-user-active-face", "user active", {"fg": "sage"}], ["slack-user-dnd-face", "user dnd", {"fg": "terracotta"}], ["slack-user-profile-header-face", "user profile header", {"fg": "blue", "bold": true}], ["slack-user-profile-property-name-face", "user profile property name", {"fg": "steel"}], ["slack-profile-image-face", "profile image", {"fg": "pewter"}], ["slack-search-result-message-header-face", "search result message header", {"fg": "blue"}], ["slack-search-result-message-username-face", "search result message username", {"fg": "gold", "bold": true}], ["slack-modeline-has-unreads-face", "modeline has unreads", {"fg": "gold"}], ["slack-modeline-channel-has-unreads-face", "modeline channel has unreads", {"fg": "gold", "bold": true}], ["slack-modeline-thread-has-unreads-face", "modeline thread has unreads", {"fg": "gold"}]]}, "telega": {"label": "telega", "preview": "telega", "faces": [["telega-root-heading", "root heading", {"fg": "blue", "bold": true}], ["telega-tracking", "tracking", {"fg": "gold"}], ["telega-unread-unmuted-modeline", "unread unmuted modeline", {"fg": "gold", "bold": true}], ["telega-username", "username", {"fg": "blue"}], ["telega-user-online-status", "user online status", {"fg": "sage"}], ["telega-user-non-online-status", "user non online status", {"fg": "pewter"}], ["telega-secret-title", "secret title", {"fg": "sage"}], ["telega-contact-birthdays-today", "contact birthdays today", {"fg": "gold"}], ["telega-muted-count", "muted count", {"fg": "pewter"}], ["telega-unmuted-count", "unmuted count", {"fg": "gold", "bold": true}], ["telega-mention-count", "mention count", {"fg": "gold", "bold": true}], ["telega-has-chatbuf-brackets", "has chatbuf brackets", {"fg": "steel"}], ["telega-delim-face", "delim", {"fg": "pewter"}], ["telega-shadow", "shadow", {"fg": "pewter"}], ["telega-link", "link", {"fg": "blue"}], ["telega-blue", "blue", {"fg": "blue"}], ["telega-red", "red", {"fg": "terracotta"}], ["telega-msg-heading", "msg heading", {"fg": "steel"}], ["telega-msg-user-title", "msg user title", {"fg": "blue", "bold": true}], ["telega-msg-self-title", "msg self title", {"fg": "gold", "bold": true}], ["telega-msg-deleted", "msg deleted", {"fg": "pewter", "italic": true}], ["telega-msg-sponsored", "msg sponsored", {"fg": "pewter", "italic": true}], ["telega-msg-inline-reply", "msg inline reply", {"fg": "steel"}], ["telega-msg-inline-forward", "msg inline forward", {"fg": "sage"}], ["telega-msg-inline-other", "msg inline other", {"fg": "pewter"}], ["telega-entity-type-bold", "entity type bold", {"bold": true}], ["telega-entity-type-italic", "entity type italic", {"italic": true}], ["telega-entity-type-underline", "entity type underline", {"fg": "silver", "underline": true}], ["telega-entity-type-strikethrough", "entity type strikethrough", {"fg": "pewter", "strike": true}], ["telega-entity-type-code", "entity type code", {"fg": "terracotta"}], ["telega-entity-type-pre", "entity type pre", {"fg": "terracotta", "bg": "bg-dim"}], ["telega-entity-type-blockquote", "entity type blockquote", {"fg": "silver", "italic": true}], ["telega-entity-type-mention", "entity type mention", {"fg": "blue"}], ["telega-entity-type-hashtag", "entity type hashtag", {"fg": "blue"}], ["telega-entity-type-cashtag", "entity type cashtag", {"fg": "sage"}], ["telega-entity-type-botcommand", "entity type botcommand", {"fg": "sage"}], ["telega-entity-type-texturl", "entity type texturl", {"fg": "blue"}], ["telega-entity-type-spoiler", "entity type spoiler", {"fg": "gunmetal", "bg": "gunmetal"}], ["telega-reaction", "reaction", {"fg": "steel"}], ["telega-reaction-chosen", "reaction chosen", {"fg": "gold", "bold": true}], ["telega-reaction-paid", "reaction paid", {"fg": "gold"}], ["telega-reaction-paid-chosen", "reaction paid chosen", {"fg": "gold", "bold": true}], ["telega-highlight-text-face", "highlight text", {"fg": "#000000", "bg": "gold"}], ["telega-button-highlight", "button highlight", {"fg": "gold", "bold": true}], ["telega-chat-prompt", "chat prompt", {"fg": "blue", "bold": true}], ["telega-chat-prompt-aux", "chat prompt aux", {"fg": "steel"}], ["telega-chat-input-attachment", "chat input attachment", {"fg": "sage"}], ["telega-topic-button", "topic button", {"fg": "blue"}], ["telega-filter-active", "filter active", {"fg": "gold", "bold": true}], ["telega-filter-button-active", "filter button active", {"fg": "#000000", "bg": "gold"}], ["telega-filter-button-inactive", "filter button inactive", {"fg": "steel"}], ["telega-checklist-stats-done", "checklist stats done", {"fg": "sage"}], ["telega-checklist-stats-todo", "checklist stats todo", {"fg": "steel"}], ["telega-box-button", "box button", {"fg": "blue"}], ["telega-box-button-active", "box button active", {"fg": "#000000", "bg": "blue"}], ["telega-box-button-default-active", "box button default active", {"fg": "#000000", "bg": "silver"}], ["telega-box-button-default-passive", "box button default passive", {"fg": "steel"}], ["telega-box-button-primary-active", "box button primary active", {"fg": "#000000", "bg": "blue"}], ["telega-box-button-primary-passive", "box button primary passive", {"fg": "blue"}], ["telega-box-button-success-active", "box button success active", {"fg": "#000000", "bg": "emerald"}], ["telega-box-button-success-passive", "box button success passive", {"fg": "sage"}], ["telega-box-button-danger-active", "box button danger active", {"fg": "#000000", "bg": "terracotta"}], ["telega-box-button-danger-passive", "box button danger passive", {"fg": "terracotta"}], ["telega-box-button-ui-active", "box button ui active", {"fg": "#000000", "bg": "gold"}], ["telega-box-button-ui-passive", "box button ui passive", {"fg": "gold"}], ["telega-box-button2-active", "box button2 active", {"fg": "#000000", "bg": "blue"}], ["telega-box-button2-passive", "box button2 passive", {"fg": "steel"}], ["telega-box-button2-white-foreground", "box button2 white foreground", {"fg": "white"}], ["telega-describe-item-title", "describe item title", {"fg": "steel", "bold": true}], ["telega-describe-section-title", "describe section title", {"fg": "blue", "bold": true}], ["telega-describe-subsection-title", "describe subsection title", {"fg": "blue"}], ["telega-enckey-00", "enckey 00", {"fg": "pewter"}], ["telega-enckey-01", "enckey 01", {"fg": "sage"}], ["telega-enckey-10", "enckey 10", {"fg": "gold"}], ["telega-enckey-11", "enckey 11", {"fg": "blue"}], ["telega-palette-builtin-blue", "palette builtin blue", {"fg": "blue"}], ["telega-palette-builtin-green", "palette builtin green", {"fg": "emerald"}], ["telega-palette-builtin-orange", "palette builtin orange", {"fg": "terracotta"}], ["telega-palette-builtin-purple", "palette builtin purple", {"fg": "regal"}], ["telega-webpage-title", "webpage title", {"fg": "blue", "bold": true}], ["telega-webpage-subtitle", "webpage subtitle", {"fg": "steel"}], ["telega-webpage-header", "webpage header", {"fg": "gold", "bold": true}], ["telega-webpage-subheader", "webpage subheader", {"fg": "gold"}], ["telega-webpage-outline", "webpage outline", {"fg": "pewter"}], ["telega-webpage-fixed", "webpage fixed", {"fg": "terracotta"}], ["telega-webpage-preformatted", "webpage preformatted", {"fg": "terracotta", "bg": "bg-dim"}], ["telega-webpage-marked", "webpage marked", {"fg": "#000000", "bg": "gold"}], ["telega-webpage-strike-through", "webpage strike through", {"fg": "pewter", "strike": true}], ["telega-webpage-chat-link", "webpage chat link", {"fg": "blue"}], ["telega-link-preview-sitename", "link preview sitename", {"fg": "steel"}], ["telega-link-preview-title", "link preview title", {"fg": "blue", "bold": true}]]}, "shr": {"label": "shr (HTML: nov/eww/mail)", "preview": "shr", "faces": [["shr-h1", "h1", {"fg": "gold", "bold": true, "height": 1.4}], ["shr-h2", "h2", {"fg": "blue", "bold": true, "height": 1.2}], ["shr-h3", "h3", {"fg": "blue", "bold": true}], ["shr-h4", "h4", {"fg": "silver", "bold": true}], ["shr-h5", "h5", {"fg": "steel", "bold": true}], ["shr-h6", "h6", {"fg": "pewter", "bold": true}], ["shr-text", "text", {"fg": "#cdced1"}], ["shr-link", "link", {"fg": "blue", "underline": true}], ["shr-selected-link", "selected link", {"fg": "gold", "bold": true, "underline": true}], ["shr-code", "code", {"fg": "terracotta", "bg": "bg-dim"}], ["shr-mark", "mark", {"fg": "#000000", "bg": "gold"}], ["shr-strike-through", "strike through", {"fg": "pewter", "strike": true}], ["shr-sup", "sup", {"fg": "steel"}], ["shr-abbreviation", "abbreviation", {"fg": "steel", "italic": true}], ["shr-sliced-image", "sliced image", {"fg": "pewter"}]]}, "2048-game": {"label": "2048-game", "preview": "generic", "faces": [["twentyfortyeight-face-1024", "twentyfortyeight 1024", {}], ["twentyfortyeight-face-128", "twentyfortyeight 128", {}], ["twentyfortyeight-face-16", "twentyfortyeight 16", {}], ["twentyfortyeight-face-2", "twentyfortyeight 2", {}], ["twentyfortyeight-face-2048", "twentyfortyeight 2048", {}], ["twentyfortyeight-face-256", "twentyfortyeight 256", {}], ["twentyfortyeight-face-32", "twentyfortyeight 32", {}], ["twentyfortyeight-face-4", "twentyfortyeight 4", {}], ["twentyfortyeight-face-512", "twentyfortyeight 512", {}], ["twentyfortyeight-face-64", "twentyfortyeight 64", {}], ["twentyfortyeight-face-8", "twentyfortyeight 8", {}]]}, "alert": {"label": "alert", "preview": "generic", "faces": [["alert-high-face", "high", {}], ["alert-low-face", "low", {}], ["alert-moderate-face", "moderate", {}], ["alert-normal-face", "normal", {}], ["alert-trivial-face", "trivial", {}], ["alert-urgent-face", "urgent", {}]]}, "all-the-icons": {"label": "all-the-icons", "preview": "generic", "faces": [["all-the-icons-blue", "blue", {}], ["all-the-icons-blue-alt", "blue alt", {}], ["all-the-icons-cyan", "cyan", {}], ["all-the-icons-cyan-alt", "cyan alt", {}], ["all-the-icons-dblue", "dblue", {}], ["all-the-icons-dcyan", "dcyan", {}], ["all-the-icons-dgreen", "dgreen", {}], ["all-the-icons-dmaroon", "dmaroon", {}], ["all-the-icons-dorange", "dorange", {}], ["all-the-icons-dpink", "dpink", {}], ["all-the-icons-dpurple", "dpurple", {}], ["all-the-icons-dred", "dred", {}], ["all-the-icons-dsilver", "dsilver", {}], ["all-the-icons-dyellow", "dyellow", {}], ["all-the-icons-green", "green", {}], ["all-the-icons-lblue", "lblue", {}], ["all-the-icons-lcyan", "lcyan", {}], ["all-the-icons-lgreen", "lgreen", {}], ["all-the-icons-lmaroon", "lmaroon", {}], ["all-the-icons-lorange", "lorange", {}], ["all-the-icons-lpink", "lpink", {}], ["all-the-icons-lpurple", "lpurple", {}], ["all-the-icons-lred", "lred", {}], ["all-the-icons-lsilver", "lsilver", {}], ["all-the-icons-lyellow", "lyellow", {}], ["all-the-icons-maroon", "maroon", {}], ["all-the-icons-orange", "orange", {}], ["all-the-icons-pink", "pink", {}], ["all-the-icons-purple", "purple", {}], ["all-the-icons-purple-alt", "purple alt", {}], ["all-the-icons-red", "red", {}], ["all-the-icons-red-alt", "red alt", {}], ["all-the-icons-silver", "silver", {}], ["all-the-icons-yellow", "yellow", {}]]}, "company": {"label": "company", "preview": "generic", "faces": [["company-echo", "echo", {}], ["company-echo-common", "echo common", {}], ["company-preview", "preview", {}], ["company-preview-common", "preview common", {}], ["company-preview-search", "preview search", {}], ["company-tooltip", "tooltip", {}], ["company-tooltip-annotation", "tooltip annotation", {}], ["company-tooltip-annotation-selection", "tooltip annotation selection", {}], ["company-tooltip-common", "tooltip common", {}], ["company-tooltip-common-selection", "tooltip common selection", {}], ["company-tooltip-deprecated", "tooltip deprecated", {}], ["company-tooltip-mouse", "tooltip mouse", {}], ["company-tooltip-quick-access", "tooltip quick access", {}], ["company-tooltip-quick-access-selection", "tooltip quick access selection", {}], ["company-tooltip-scrollbar-thumb", "tooltip scrollbar thumb", {}], ["company-tooltip-scrollbar-track", "tooltip scrollbar track", {}], ["company-tooltip-search", "tooltip search", {}], ["company-tooltip-search-selection", "tooltip search selection", {}], ["company-tooltip-selection", "tooltip selection", {}]]}, "company-box": {"label": "company-box", "preview": "generic", "faces": [["company-box-annotation", "annotation", {}], ["company-box-background", "background", {}], ["company-box-candidate", "candidate", {}], ["company-box-numbers", "numbers", {}], ["company-box-scrollbar", "scrollbar", {}], ["company-box-selection", "selection", {}]]}, "consult": {"label": "consult", "preview": "generic", "faces": [["consult-async-failed", "async failed", {}], ["consult-async-finished", "async finished", {}], ["consult-async-running", "async running", {}], ["consult-async-split", "async split", {}], ["consult-bookmark", "bookmark", {}], ["consult-buffer", "buffer", {}], ["consult-file", "file", {}], ["consult-grep-context", "grep context", {}], ["consult-help", "help", {}], ["consult-highlight-mark", "highlight mark", {}], ["consult-highlight-match", "highlight match", {}], ["consult-key", "key", {}], ["consult-line-number", "line number", {}], ["consult-line-number-prefix", "line number prefix", {}], ["consult-line-number-wrapped", "line number wrapped", {}], ["consult-narrow-indicator", "narrow indicator", {}], ["consult-preview-insertion", "preview insertion", {}], ["consult-preview-line", "preview line", {}], ["consult-preview-match", "preview match", {}], ["consult-separator", "separator", {}]]}, "embark": {"label": "embark", "preview": "generic", "faces": [["embark-collect-annotation", "collect annotation", {}], ["embark-collect-candidate", "collect candidate", {}], ["embark-collect-group-separator", "collect group separator", {}], ["embark-collect-group-title", "collect group title", {}], ["embark-keybinding", "keybinding", {}], ["embark-keybinding-repeat", "keybinding repeat", {}], ["embark-keymap", "keymap", {}], ["embark-selected", "selected", {}], ["embark-target", "target", {}], ["embark-verbose-indicator-documentation", "verbose indicator documentation", {}], ["embark-verbose-indicator-shadowed", "verbose indicator shadowed", {}], ["embark-verbose-indicator-title", "verbose indicator title", {}]]}, "emms": {"label": "emms", "preview": "generic", "faces": [["emms-browser-album-face", "browser album", {}], ["emms-browser-albumartist-face", "browser albumartist", {}], ["emms-browser-artist-face", "browser artist", {}], ["emms-browser-composer-face", "browser composer", {}], ["emms-browser-performer-face", "browser performer", {}], ["emms-browser-track-face", "browser track", {}], ["emms-browser-year/genre-face", "browser year/genre", {}], ["emms-metaplaylist-mode-current-face", "metaplaylist mode current", {}], ["emms-metaplaylist-mode-face", "metaplaylist mode", {}], ["emms-playlist-selected-face", "playlist selected", {}], ["emms-playlist-track-face", "playlist track", {}]]}, "flyspell-correct": {"label": "flyspell-correct", "preview": "generic", "faces": [["flyspell-correct-highlight-face", "highlight", {}]]}, "highlight-indent-guides": {"label": "highlight-indent-guides", "preview": "generic", "faces": [["highlight-indent-guides-character-face", "character", {}], ["highlight-indent-guides-even-face", "even", {}], ["highlight-indent-guides-odd-face", "odd", {}], ["highlight-indent-guides-stack-character-face", "stack character", {}], ["highlight-indent-guides-stack-even-face", "stack even", {}], ["highlight-indent-guides-stack-odd-face", "stack odd", {}], ["highlight-indent-guides-top-character-face", "top character", {}], ["highlight-indent-guides-top-even-face", "top even", {}], ["highlight-indent-guides-top-odd-face", "top odd", {}]]}, "hl-todo": {"label": "hl-todo", "preview": "generic", "faces": [["hl-todo", "hl todo", {}], ["hl-todo-flymake-type", "flymake type", {}]]}, "json-mode": {"label": "json-mode", "preview": "generic", "faces": [["json-mode-object-name-face", "object name", {}]]}, "llama": {"label": "llama", "preview": "generic", "faces": [["llama-##-macro", "## macro", {}], ["llama-deleted-argument", "deleted argument", {}], ["llama-llama-macro", "llama macro", {}], ["llama-mandatory-argument", "mandatory argument", {}], ["llama-optional-argument", "optional argument", {}]]}, "lv": {"label": "lv", "preview": "generic", "faces": [["lv-separator", "separator", {}]]}, "magit-section": {"label": "magit-section", "preview": "generic", "faces": [["magit-left-margin", "magit left margin", {}], ["magit-section-child-count", "child count", {}], ["magit-section-heading", "heading", {}], ["magit-section-heading-selection", "heading selection", {}], ["magit-section-highlight", "highlight", {}], ["magit-section-secondary-heading", "secondary heading", {}]]}, "malyon": {"label": "malyon", "preview": "generic", "faces": [["malyon-face-bold", "face bold", {}], ["malyon-face-error", "face error", {}], ["malyon-face-italic", "face italic", {}], ["malyon-face-plain", "face plain", {}], ["malyon-face-reverse", "face reverse", {}]]}, "marginalia": {"label": "marginalia", "preview": "generic", "faces": [["marginalia-archive", "archive", {}], ["marginalia-char", "char", {}], ["marginalia-date", "date", {}], ["marginalia-documentation", "documentation", {}], ["marginalia-file-name", "file name", {}], ["marginalia-file-owner", "file owner", {}], ["marginalia-file-priv-dir", "file priv dir", {}], ["marginalia-file-priv-exec", "file priv exec", {}], ["marginalia-file-priv-link", "file priv link", {}], ["marginalia-file-priv-no", "file priv no", {}], ["marginalia-file-priv-other", "file priv other", {}], ["marginalia-file-priv-rare", "file priv rare", {}], ["marginalia-file-priv-read", "file priv read", {}], ["marginalia-file-priv-write", "file priv write", {}], ["marginalia-function", "function", {}], ["marginalia-installed", "installed", {}], ["marginalia-key", "key", {}], ["marginalia-lighter", "lighter", {}], ["marginalia-list", "list", {}], ["marginalia-mode", "mode", {}], ["marginalia-modified", "modified", {}], ["marginalia-null", "null", {}], ["marginalia-number", "number", {}], ["marginalia-off", "off", {}], ["marginalia-on", "on", {}], ["marginalia-size", "size", {}], ["marginalia-string", "string", {}], ["marginalia-symbol", "symbol", {}], ["marginalia-true", "true", {}], ["marginalia-type", "type", {}], ["marginalia-value", "value", {}], ["marginalia-version", "version", {}]]}, "markdown-mode": {"label": "markdown-mode", "preview": "generic", "faces": [["markdown-blockquote-face", "markdown blockquote", {}], ["markdown-bold-face", "markdown bold", {}], ["markdown-code-face", "markdown code", {}], ["markdown-comment-face", "markdown comment", {}], ["markdown-footnote-marker-face", "markdown footnote marker", {}], ["markdown-footnote-text-face", "markdown footnote text", {}], ["markdown-gfm-checkbox-face", "markdown gfm checkbox", {}], ["markdown-header-delimiter-face", "markdown header delimiter", {}], ["markdown-header-face", "markdown header", {}], ["markdown-header-face-1", "markdown header 1", {}], ["markdown-header-face-2", "markdown header 2", {}], ["markdown-header-face-3", "markdown header 3", {}], ["markdown-header-face-4", "markdown header 4", {}], ["markdown-header-face-5", "markdown header 5", {}], ["markdown-header-face-6", "markdown header 6", {}], ["markdown-header-rule-face", "markdown header rule", {}], ["markdown-highlight-face", "markdown highlight", {}], ["markdown-highlighting-face", "markdown highlighting", {}], ["markdown-hr-face", "markdown hr", {}], ["markdown-html-attr-name-face", "markdown html attr name", {}], ["markdown-html-attr-value-face", "markdown html attr value", {}], ["markdown-html-entity-face", "markdown html entity", {}], ["markdown-html-tag-delimiter-face", "markdown html tag delimiter", {}], ["markdown-html-tag-name-face", "markdown html tag name", {}], ["markdown-inline-code-face", "markdown inline code", {}], ["markdown-italic-face", "markdown italic", {}], ["markdown-language-info-face", "markdown language info", {}], ["markdown-language-keyword-face", "markdown language keyword", {}], ["markdown-line-break-face", "markdown line break", {}], ["markdown-link-face", "markdown link", {}], ["markdown-link-title-face", "markdown link title", {}], ["markdown-list-face", "markdown list", {}], ["markdown-markup-face", "markdown markup", {}], ["markdown-math-face", "markdown math", {}], ["markdown-metadata-key-face", "markdown metadata key", {}], ["markdown-metadata-value-face", "markdown metadata value", {}], ["markdown-missing-link-face", "markdown missing link", {}], ["markdown-plain-url-face", "markdown plain url", {}], ["markdown-pre-face", "markdown pre", {}], ["markdown-reference-face", "markdown reference", {}], ["markdown-strike-through-face", "markdown strike through", {}], ["markdown-table-face", "markdown table", {}], ["markdown-url-face", "markdown url", {}]]}, "nerd-icons": {"label": "nerd-icons", "preview": "generic", "faces": [["nerd-icons-blue", "blue", {}], ["nerd-icons-blue-alt", "blue alt", {}], ["nerd-icons-cyan", "cyan", {}], ["nerd-icons-cyan-alt", "cyan alt", {}], ["nerd-icons-dblue", "dblue", {}], ["nerd-icons-dcyan", "dcyan", {}], ["nerd-icons-dgreen", "dgreen", {}], ["nerd-icons-dmaroon", "dmaroon", {}], ["nerd-icons-dorange", "dorange", {}], ["nerd-icons-dpink", "dpink", {}], ["nerd-icons-dpurple", "dpurple", {}], ["nerd-icons-dred", "dred", {}], ["nerd-icons-dsilver", "dsilver", {}], ["nerd-icons-dyellow", "dyellow", {}], ["nerd-icons-green", "green", {}], ["nerd-icons-lblue", "lblue", {}], ["nerd-icons-lcyan", "lcyan", {}], ["nerd-icons-lgreen", "lgreen", {}], ["nerd-icons-lmaroon", "lmaroon", {}], ["nerd-icons-lorange", "lorange", {}], ["nerd-icons-lpink", "lpink", {}], ["nerd-icons-lpurple", "lpurple", {}], ["nerd-icons-lred", "lred", {}], ["nerd-icons-lsilver", "lsilver", {}], ["nerd-icons-lyellow", "lyellow", {}], ["nerd-icons-maroon", "maroon", {}], ["nerd-icons-orange", "orange", {}], ["nerd-icons-pink", "pink", {}], ["nerd-icons-purple", "purple", {}], ["nerd-icons-purple-alt", "purple alt", {}], ["nerd-icons-red", "red", {}], ["nerd-icons-red-alt", "red alt", {}], ["nerd-icons-silver", "silver", {}], ["nerd-icons-yellow", "yellow", {}]]}, "nerd-icons-completion": {"label": "nerd-icons-completion", "preview": "generic", "faces": [["nerd-icons-completion-dir-face", "dir", {}]]}, "orderless": {"label": "orderless", "preview": "generic", "faces": [["orderless-match-face-0", "match 0", {}], ["orderless-match-face-1", "match 1", {}], ["orderless-match-face-2", "match 2", {}], ["orderless-match-face-3", "match 3", {}]]}, "org-roam": {"label": "org-roam", "preview": "generic", "faces": [["org-roam-dailies-calendar-note", "dailies calendar note", {}], ["org-roam-dim", "dim", {}], ["org-roam-header-line", "header line", {}], ["org-roam-olp", "olp", {}], ["org-roam-preview-heading", "preview heading", {}], ["org-roam-preview-heading-highlight", "preview heading highlight", {}], ["org-roam-preview-heading-selection", "preview heading selection", {}], ["org-roam-preview-region", "preview region", {}], ["org-roam-title", "title", {}]]}, "org-superstar": {"label": "org-superstar", "preview": "generic", "faces": [["org-superstar-first", "first", {}], ["org-superstar-header-bullet", "header bullet", {}], ["org-superstar-item", "item", {}], ["org-superstar-leading", "leading", {}]]}, "prescient": {"label": "prescient", "preview": "generic", "faces": [["prescient-primary-highlight", "primary highlight", {}], ["prescient-secondary-highlight", "secondary highlight", {}]]}, "rainbow-delimiters": {"label": "rainbow-delimiters", "preview": "generic", "faces": [["rainbow-delimiters-base-error-face", "base error", {}], ["rainbow-delimiters-base-face", "base", {}], ["rainbow-delimiters-depth-1-face", "depth 1", {}], ["rainbow-delimiters-depth-2-face", "depth 2", {}], ["rainbow-delimiters-depth-3-face", "depth 3", {}], ["rainbow-delimiters-depth-4-face", "depth 4", {}], ["rainbow-delimiters-depth-5-face", "depth 5", {}], ["rainbow-delimiters-depth-6-face", "depth 6", {}], ["rainbow-delimiters-depth-7-face", "depth 7", {}], ["rainbow-delimiters-depth-8-face", "depth 8", {}], ["rainbow-delimiters-depth-9-face", "depth 9", {}], ["rainbow-delimiters-mismatched-face", "mismatched", {}], ["rainbow-delimiters-unmatched-face", "unmatched", {}]]}, "symbol-overlay": {"label": "symbol-overlay", "preview": "generic", "faces": [["symbol-overlay-default-face", "default", {}], ["symbol-overlay-face-1", "face 1", {}], ["symbol-overlay-face-2", "face 2", {}], ["symbol-overlay-face-3", "face 3", {}], ["symbol-overlay-face-4", "face 4", {}], ["symbol-overlay-face-5", "face 5", {}], ["symbol-overlay-face-6", "face 6", {}], ["symbol-overlay-face-7", "face 7", {}], ["symbol-overlay-face-8", "face 8", {}]]}, "tmr": {"label": "tmr", "preview": "generic", "faces": [["tmr-description", "description", {}], ["tmr-duration", "duration", {}], ["tmr-end-time", "end time", {}], ["tmr-finished", "finished", {}], ["tmr-is-acknowledged", "is acknowledged", {}], ["tmr-must-be-acknowledged", "must be acknowledged", {}], ["tmr-start-time", "start time", {}], ["tmr-tabulated-acknowledgement", "tabulated acknowledgement", {}], ["tmr-tabulated-description", "tabulated description", {}], ["tmr-tabulated-end-time", "tabulated end time", {}], ["tmr-tabulated-remaining-time", "tabulated remaining time", {}], ["tmr-tabulated-start-time", "tabulated start time", {}]]}, "transient": {"label": "transient", "preview": "generic", "faces": [["transient-active-infix", "active infix", {}], ["transient-argument", "argument", {}], ["transient-delimiter", "delimiter", {}], ["transient-disabled-suffix", "disabled suffix", {}], ["transient-enabled-suffix", "enabled suffix", {}], ["transient-heading", "heading", {}], ["transient-higher-level", "higher level", {}], ["transient-inactive-argument", "inactive argument", {}], ["transient-inactive-value", "inactive value", {}], ["transient-inapt-argument", "inapt argument", {}], ["transient-inapt-suffix", "inapt suffix", {}], ["transient-key", "key", {}], ["transient-key-exit", "key exit", {}], ["transient-key-noop", "key noop", {}], ["transient-key-recurse", "key recurse", {}], ["transient-key-return", "key return", {}], ["transient-key-stack", "key stack", {}], ["transient-key-stay", "key stay", {}], ["transient-mismatched-key", "mismatched key", {}], ["transient-nonstandard-key", "nonstandard key", {}], ["transient-unreachable", "unreachable", {}], ["transient-unreachable-key", "unreachable key", {}], ["transient-value", "value", {}]]}, "vertico": {"label": "vertico", "preview": "generic", "faces": [["vertico-current", "current", {}], ["vertico-group-separator", "group separator", {}], ["vertico-group-title", "group title", {}], ["vertico-multiline", "multiline", {}]]}, "web-mode": {"label": "web-mode", "preview": "generic", "faces": [["web-mode-annotation-face", "annotation", {}], ["web-mode-annotation-html-face", "annotation html", {}], ["web-mode-annotation-tag-face", "annotation tag", {}], ["web-mode-annotation-type-face", "annotation type", {}], ["web-mode-annotation-value-face", "annotation value", {}], ["web-mode-block-attr-name-face", "block attr name", {}], ["web-mode-block-attr-value-face", "block attr value", {}], ["web-mode-block-comment-face", "block comment", {}], ["web-mode-block-control-face", "block control", {}], ["web-mode-block-delimiter-face", "block delimiter", {}], ["web-mode-block-face", "block", {}], ["web-mode-block-string-face", "block string", {}], ["web-mode-bold-face", "bold", {}], ["web-mode-builtin-face", "builtin", {}], ["web-mode-comment-face", "comment", {}], ["web-mode-comment-keyword-face", "comment keyword", {}], ["web-mode-constant-face", "constant", {}], ["web-mode-css-at-rule-face", "css at rule", {}], ["web-mode-css-color-face", "css color", {}], ["web-mode-css-comment-face", "css comment", {}], ["web-mode-css-function-face", "css function", {}], ["web-mode-css-priority-face", "css priority", {}], ["web-mode-css-property-name-face", "css property name", {}], ["web-mode-css-pseudo-class-face", "css pseudo class", {}], ["web-mode-css-selector-class-face", "css selector class", {}], ["web-mode-css-selector-face", "css selector", {}], ["web-mode-css-selector-tag-face", "css selector tag", {}], ["web-mode-css-string-face", "css string", {}], ["web-mode-css-variable-face", "css variable", {}], ["web-mode-current-column-highlight-face", "current column highlight", {}], ["web-mode-current-element-highlight-face", "current element highlight", {}], ["web-mode-doctype-face", "doctype", {}], ["web-mode-error-face", "error", {}], ["web-mode-filter-face", "filter", {}], ["web-mode-folded-face", "folded", {}], ["web-mode-function-call-face", "function call", {}], ["web-mode-function-name-face", "function name", {}], ["web-mode-html-attr-custom-face", "html attr custom", {}], ["web-mode-html-attr-engine-face", "html attr engine", {}], ["web-mode-html-attr-equal-face", "html attr equal", {}], ["web-mode-html-attr-name-face", "html attr name", {}], ["web-mode-html-attr-value-face", "html attr value", {}], ["web-mode-html-entity-face", "html entity", {}], ["web-mode-html-tag-bracket-face", "html tag bracket", {}], ["web-mode-html-tag-custom-face", "html tag custom", {}], ["web-mode-html-tag-face", "html tag", {}], ["web-mode-html-tag-namespaced-face", "html tag namespaced", {}], ["web-mode-html-tag-unclosed-face", "html tag unclosed", {}], ["web-mode-inlay-face", "inlay", {}], ["web-mode-interpolate-color1-face", "interpolate color1", {}], ["web-mode-interpolate-color2-face", "interpolate color2", {}], ["web-mode-interpolate-color3-face", "interpolate color3", {}], ["web-mode-interpolate-color4-face", "interpolate color4", {}], ["web-mode-italic-face", "italic", {}], ["web-mode-javascript-comment-face", "javascript comment", {}], ["web-mode-javascript-string-face", "javascript string", {}], ["web-mode-json-comment-face", "json comment", {}], ["web-mode-json-context-face", "json context", {}], ["web-mode-json-key-face", "json key", {}], ["web-mode-json-string-face", "json string", {}], ["web-mode-jsx-depth-1-face", "jsx depth 1", {}], ["web-mode-jsx-depth-2-face", "jsx depth 2", {}], ["web-mode-jsx-depth-3-face", "jsx depth 3", {}], ["web-mode-jsx-depth-4-face", "jsx depth 4", {}], ["web-mode-jsx-depth-5-face", "jsx depth 5", {}], ["web-mode-keyword-face", "keyword", {}], ["web-mode-param-name-face", "param name", {}], ["web-mode-part-comment-face", "part comment", {}], ["web-mode-part-face", "part", {}], ["web-mode-part-string-face", "part string", {}], ["web-mode-preprocessor-face", "preprocessor", {}], ["web-mode-script-face", "script", {}], ["web-mode-sql-keyword-face", "sql keyword", {}], ["web-mode-string-face", "string", {}], ["web-mode-style-face", "style", {}], ["web-mode-symbol-face", "symbol", {}], ["web-mode-type-face", "type", {}], ["web-mode-underline-face", "underline", {}], ["web-mode-variable-name-face", "variable name", {}], ["web-mode-warning-face", "warning", {}], ["web-mode-whitespace-face", "whitespace", {}]]}, "yasnippet": {"label": "yasnippet", "preview": "generic", "faces": [["yas--field-debug-face", "yas field debug", {}], ["yas-field-highlight-face", "yas field highlight", {}]]}}; -let MAP={"kw": "#67809c", "bi": "#67809c", "pp": "#67809c", "fnd": "#a9b2bb", "fnc": "#a9b2bb", "dec": "#e8bd30", "ty": "#9b5fd0", "prop": "#838d97", "con": "#cb6b4d", "num": "#cb6b4d", "esc": "#cb6b4d", "str": "#5d9b86", "re": "#5d9b86", "doc": "#5d9b86", "cm": "#be9e74", "cmd": "#a9b2bb", "var": "#e8bd30", "op": "#a9b2bb", "punc": "#a9b2bb", "p": "#ffffff", "bg": "#000000"}, PALETTE=[["#67809c", "blue"], ["#e8bd30", "gold"], ["#9b5fd0", "regal"], ["#2ba178", "emerald"], ["#5d9b86", "sage"], ["#cb6b4d", "terracotta"], ["#be9e74", "tan"], ["#ffffff", "white"], ["#a9b2bb", "silver"], ["#838d97", "steel"], ["#5e6770", "pewter"], ["#2f343a", "gunmetal"], ["#264364", "navy"], ["#000000", "ground"], ["#1a1714", "bg-dim"]], BOLD={"kw": true, "bi": false, "pp": false, "fnd": true, "fnc": false, "dec": false, "ty": false, "prop": false, "con": false, "num": false, "esc": false, "str": false, "re": false, "doc": false, "cm": false, "cmd": false, "var": false, "op": false, "punc": false, "p": false}, ITALIC={}, UIMAP={"cursor": {"fg": null, "bg": "#a9b2bb"}, "region": {"fg": null, "bg": "#264364"}, "hl-line": {"fg": null, "bg": "#1a1714"}, "highlight": {"fg": null, "bg": "#2f343a"}, "mode-line": {"fg": "#cdced1", "bg": "#2f343a"}, "mode-line-inactive": {"fg": "#838d97", "bg": "#1a1714"}, "fringe": {"fg": null, "bg": "#0d0b0a"}, "line-number": {"fg": "#5e6770", "bg": null}, "line-number-current-line": {"fg": "#e8bd30", "bg": "#1a1714"}, "minibuffer-prompt": {"fg": "#67809c", "bg": null}, "isearch": {"fg": "#0d0b0a", "bg": "#e8bd30"}, "lazy-highlight": {"fg": "#0d0b0a", "bg": "#838d97"}, "isearch-fail": {"fg": "#cb6b4d", "bg": null}, "show-paren-match": {"fg": null, "bg": "#264364"}, "show-paren-mismatch": {"fg": "#0d0b0a", "bg": "#cb6b4d"}, "link": {"fg": "#67809c", "bg": null, "underline": true}, "error": {"fg": "#cb6b4d", "bg": null}, "warning": {"fg": "#e8bd30", "bg": null}, "success": {"fg": "#5d9b86", "bg": null}, "vertical-border": {"fg": "#2f343a", "bg": null}}; +const SAMPLES={"Elisp": [[["cmd", ";;"], ["cm", " cache.el"]], [["punc", "("], ["kw", "require"], ["p", " "], ["con", "'cl-lib"], ["punc", ")"]], [], [["punc", "("], ["kw", "defvar"], ["p", " "], ["var", "cache--tbl"], ["p", " "], ["punc", "("], ["fnc", "make-hash-table"], ["p", " "], ["con", ":test"], ["p", " "], ["con", "'equal"], ["punc", "))"]], [["p", " "], ["doc", "\"Memo table.\")"]], [], [["punc", "("], ["kw", "defun"], ["p", " "], ["fnd", "cache-get"], ["p", " "], ["punc", "("], ["var", "key"], ["punc", ")"]], [["p", " "], ["doc", "\"Return cached value for KEY.\""]], [["p", " "], ["punc", "("], ["kw", "or"], ["p", " "], ["punc", "("], ["bi", "gethash"], ["p", " "], ["var", "key"], ["p", " "], ["var", "cache--tbl"], ["punc", ")"]], [["p", " "], ["punc", "("], ["kw", "let"], ["p", " "], ["punc", "(("], ["var", "v"], ["p", " "], ["punc", "("], ["fnc", "compute"], ["p", " "], ["var", "key"], ["p", " "], ["num", "42"], ["punc", "))) "]], [["p", " "], ["punc", "("], ["fnc", "puthash"], ["p", " "], ["var", "key"], ["p", " "], ["var", "v"], ["p", " "], ["var", "cache--tbl"], ["punc", ") "], ["var", "v"], ["punc", "))))"]], [], [["punc", "("], ["kw", "defun"], ["p", " "], ["fnd", "cache-clear"], ["p", " "], ["punc", "()"]], [["p", " "], ["doc", "\"Empty the memo table.\""]], [["p", " "], ["punc", "("], ["kw", "interactive"], ["punc", ")"]], [["p", " "], ["punc", "("], ["fnc", "clrhash"], ["p", " "], ["var", "cache--tbl"], ["punc", ")"]], [["p", " "], ["punc", "("], ["fnc", "message"], ["p", " "], ["str", "\"cleared"], ["esc", "\\n"], ["str", "\""], ["punc", "))"]], [], [["punc", "("], ["kw", "defun"], ["p", " "], ["fnd", "cache-keys"], ["p", " "], ["punc", "()"]], [["p", " "], ["doc", "\"Return all keys.\""]], [["p", " "], ["punc", "("], ["kw", "let"], ["p", " "], ["punc", "(("], ["var", "acc"], ["p", " "], ["con", "nil"], ["punc", "))"]], [["p", " "], ["punc", "("], ["fnc", "maphash"], ["p", " "], ["punc", "("], ["kw", "lambda"], ["p", " "], ["punc", "("], ["var", "k"], ["p", " "], ["var", "_v"], ["punc", ")"], ["p", " "], ["punc", "("], ["fnc", "push"], ["p", " "], ["var", "k"], ["p", " "], ["var", "acc"], ["punc", "))"]], [["p", " "], ["var", "cache--tbl"], ["punc", ")"], ["p", " "], ["var", "acc"], ["punc", "))"]], [], [["punc", "("], ["kw", "provide"], ["p", " "], ["con", "'cache"], ["punc", ")"]]], "Go": [[["cmd", "//"], ["cm", " queue.go"]], [["kw", "package"], ["p", " "], ["var", "main"]], [], [["kw", "import"], ["p", " "], ["str", "\"fmt\""]], [], [["kw", "const"], ["p", " "], ["con", "MaxItems"], ["p", " "], ["op", "="], ["p", " "], ["num", "100"]], [], [["kw", "type"], ["p", " "], ["ty", "Order"], ["p", " "], ["kw", "struct"], ["p", " "], ["punc", "{"]], [["p", " "], ["prop", "ID"], ["p", " "], ["ty", "int"]], [["p", " "], ["prop", "Name"], ["p", " "], ["ty", "string"]], [["punc", "}"]], [], [["kw", "func"], ["p", " "], ["punc", "("], ["var", "q"], ["p", " "], ["op", "*"], ["ty", "Queue"], ["punc", ")"], ["p", " "], ["fnd", "Push"], ["punc", "("], ["var", "o"], ["p", " "], ["op", "*"], ["ty", "Order"], ["punc", ")"], ["p", " "], ["ty", "error"], ["p", " "], ["punc", "{"]], [["p", " "], ["cmd", "//"], ["cm", " reject nil"]], [["p", " "], ["kw", "if"], ["p", " "], ["var", "o"], ["p", " "], ["op", "=="], ["p", " "], ["con", "nil"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "return"], ["p", " "], ["fnc", "fmt.Errorf"], ["punc", "("], ["str", "\"nil"], ["esc", "\\n"], ["str", "\""], ["punc", ")"]], [["p", " "], ["punc", "}"]], [["p", " "], ["var", "q"], ["op", "."], ["prop", "items"], ["p", " "], ["op", "="], ["p", " "], ["bi", "append"], ["punc", "("], ["var", "q"], ["op", "."], ["prop", "items"], ["punc", ","], ["p", " "], ["var", "o"], ["punc", ")"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "nil"]], [["punc", "}"]], [], [["kw", "func"], ["p", " "], ["fnd", "main"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["fnc", "fmt.Println"], ["punc", "("], ["op", "&"], ["ty", "Queue"], ["punc", "{}"], ["punc", ")"]], [["punc", "}"]]], "Python": [[["cmd", "#"], ["cm", " theme.py"]], [["kw", "from"], ["p", " "], ["var", "dataclasses"], ["p", " "], ["kw", "import"], ["p", " "], ["var", "dataclass"], ["punc", ","], ["p", " "], ["var", "field"]], [], [["con", "DEFAULT_PORT"], ["op", ":"], ["p", " "], ["ty", "int"], ["p", " "], ["op", "="], ["p", " "], ["num", "8080"]], [["con", "HEX"], ["p", " "], ["op", "="], ["p", " "], ["var", "re"], ["op", "."], ["fnc", "compile"], ["punc", "("], ["re", "r\"#[0-9a-f]{6}\""], ["punc", ")"]], [], [["dec", "@dataclass"]], [["kw", "class"], ["p", " "], ["ty", "Theme"], ["op", ":"]], [["p", " "], ["doc", "\"\"\"A color theme.\"\"\""]], [["p", " "], ["prop", "name"], ["op", ":"], ["p", " "], ["ty", "str"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""]], [["p", " "], ["prop", "colors"], ["op", ":"], ["p", " "], ["ty", "dict"], ["p", " "], ["op", "="], ["p", " "], ["fnc", "field"], ["punc", "("], ["prop", "default_factory"], ["op", "="], ["ty", "dict"], ["punc", ")"]], [], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["var", "self"], ["punc", ","], ["p", " "], ["var", "key"], ["op", ":"], ["p", " "], ["ty", "str"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "str"], ["p", " "], ["op", "|"], ["p", " "], ["con", "None"], ["op", ":"]], [["p", " "], ["cmd", "#"], ["cm", " fallback to none"]], [["p", " "], ["var", "v"], ["p", " "], ["op", "="], ["p", " "], ["var", "self"], ["op", "."], ["prop", "colors"], ["op", "."], ["fnc", "get"], ["punc", "("], ["var", "key"], ["punc", ","], ["p", " "], ["str", "\""], ["esc", "\\t"], ["str", "none\""], ["punc", ")"]], [["p", " "], ["kw", "if"], ["p", " "], ["bi", "len"], ["punc", "("], ["var", "v"], ["punc", ")"], ["p", " "], ["op", "=="], ["p", " "], ["num", "0"], ["op", ":"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "None"]], [["p", " "], ["kw", "return"], ["p", " "], ["var", "v"]], [], [["p", " "], ["dec", "@property"]], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "size"], ["punc", "("], ["var", "self"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "int"], ["op", ":"]], [["p", " "], ["kw", "return"], ["p", " "], ["bi", "len"], ["punc", "("], ["var", "self"], ["op", "."], ["prop", "colors"], ["punc", ")"]], [], [["var", "theme"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Theme"], ["punc", "("], ["str", "\"dupre\""], ["punc", ")"]], [["fnc", "print"], ["punc", "("], ["var", "theme"], ["op", "."], ["fnc", "resolve"], ["punc", "("], ["str", "\"bg\""], ["punc", "))"]]], "TypeScript": [[["cmd", "//"], ["cm", " orders.ts"]], [["kw", "import"], ["p", " "], ["punc", "{"], ["p", " "], ["ty", "Order"], ["p", " "], ["punc", "}"], ["p", " "], ["kw", "from"], ["p", " "], ["str", "\"./types\""]], [], [["kw", "export"], ["p", " "], ["kw", "interface"], ["p", " "], ["ty", "Queue"], ["p", " "], ["punc", "{"]], [["p", " "], ["prop", "max"], ["op", ":"], ["p", " "], ["ty", "number"], ["punc", ";"]], [["p", " "], ["prop", "items"], ["op", ":"], ["p", " "], ["ty", "Order"], ["punc", "[];"]], [["punc", "}"]], [], [["dec", "@Injectable"], ["punc", "()"]], [["kw", "export"], ["p", " "], ["kw", "class"], ["p", " "], ["ty", "OrderQueue"], ["p", " "], ["kw", "implements"], ["p", " "], ["ty", "Queue"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "private"], ["p", " "], ["prop", "re"], ["p", " "], ["op", "="], ["p", " "], ["re", "/^#[0-9a-f]{6}$/i"], ["punc", ";"]], [], [["p", " "], ["fnd", "push"], ["punc", "("], ["var", "o"], ["op", ":"], ["p", " "], ["ty", "Order"], ["punc", ")"], ["op", ":"], ["p", " "], ["ty", "boolean"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "o"], ["p", " "], ["op", "==="], ["p", " "], ["con", "null"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "false"], ["punc", ";"]], [["p", " "], ["var", "console"], ["op", "."], ["fnc", "log"], ["punc", "("], ["str", "`id "], ["punc", "${"], ["var", "o"], ["op", "."], ["prop", "id"], ["punc", "}"], ["esc", "\\n"], ["str", "`"], ["punc", ");"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "true"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["punc", "}"]], [], [["kw", "const"], ["p", " "], ["con", "LIMIT"], ["op", ":"], ["p", " "], ["ty", "number"], ["p", " "], ["op", "="], ["p", " "], ["num", "50"], ["punc", ";"]], [["kw", "const"], ["p", " "], ["var", "q"], ["p", " "], ["op", "="], ["p", " "], ["kw", "new"], ["p", " "], ["ty", "OrderQueue"], ["punc", "()"], ["punc", ";"]], [["var", "q"], ["op", "."], ["fnd", "push"], ["punc", "("], ["punc", "{"], ["p", " "], ["prop", "id"], ["op", ":"], ["p", " "], ["num", "1"], ["p", " "], ["punc", "}"], ["p", " "], ["kw", "as"], ["p", " "], ["ty", "Order"], ["punc", ")"], ["punc", ";"]], [["var", "console"], ["op", "."], ["fnc", "log"], ["punc", "("], ["var", "q"], ["op", "."], ["prop", "max"], ["punc", ")"], ["punc", ";"]], [["kw", "const"], ["p", " "], ["var", "cap"], ["p", " "], ["op", "="], ["p", " "], ["var", "Math"], ["op", "."], ["bi", "max"], ["punc", "("], ["con", "LIMIT"], ["punc", ","], ["p", " "], ["num", "0"], ["punc", ")"], ["punc", ";"]]], "Java": [[["cmd", "/**"], ["doc", " A color theme. */"]], [["kw", "package"], ["p", " "], ["var", "com"], ["op", "."], ["var", "dupre"], ["punc", ";"]], [["kw", "import"], ["p", " "], ["var", "java"], ["op", "."], ["var", "util"], ["op", "."], ["var", "regex"], ["op", "."], ["ty", "Pattern"], ["punc", ";"]], [], [["dec", "@Deprecated"]], [["kw", "public"], ["p", " "], ["kw", "final"], ["p", " "], ["kw", "class"], ["p", " "], ["ty", "Theme"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "private"], ["p", " "], ["kw", "static"], ["p", " "], ["kw", "final"], ["p", " "], ["ty", "int"], ["p", " "], ["con", "MAX_PORT"], ["p", " "], ["op", "="], ["p", " "], ["num", "8080"], ["punc", ";"]], [["p", " "], ["kw", "private"], ["p", " "], ["kw", "final"], ["p", " "], ["ty", "String"], ["p", " "], ["prop", "name"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""], ["punc", ";"]], [["p", " "], ["kw", "private"], ["p", " "], ["kw", "static"], ["p", " "], ["kw", "final"], ["p", " "], ["ty", "Pattern"], ["p", " "], ["con", "HEX"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Pattern"], ["op", "."], ["fnc", "compile"], ["punc", "("], ["re", "\"#[0-9a-f]{6}\""], ["punc", ")"], ["punc", ";"]], [], [["p", " "], ["dec", "@Override"]], [["p", " "], ["kw", "public"], ["p", " "], ["ty", "String"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["ty", "String"], ["p", " "], ["var", "key"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["cmd", "//"], ["cm", " fall back to null"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "key"], ["op", "."], ["fnc", "isEmpty"], ["punc", "()"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "null"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["var", "key"], ["op", "."], ["fnc", "strip"], ["punc", "("], ["punc", ")"], ["op", "+"], ["str", "\""], ["esc", "\\t"], ["str", "\""], ["punc", ";"]], [["p", " "], ["punc", "}"]], [], [["p", " "], ["kw", "public"], ["p", " "], ["kw", "static"], ["p", " "], ["ty", "void"], ["p", " "], ["fnd", "main"], ["punc", "("], ["ty", "String"], ["punc", "[]"], ["p", " "], ["var", "args"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["ty", "var"], ["p", " "], ["var", "t"], ["p", " "], ["op", "="], ["p", " "], ["kw", "new"], ["p", " "], ["ty", "Theme"], ["punc", "()"], ["punc", ";"]], [["p", " "], ["ty", "System"], ["op", "."], ["prop", "out"], ["op", "."], ["fnc", "println"], ["punc", "("], ["var", "t"], ["op", "."], ["fnc", "resolve"], ["punc", "("], ["str", "\"bg\""], ["punc", "))"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["punc", "}"]]], "C": [[["cmd", "/**"], ["doc", " Order queue. */"]], [["pp", "#include"], ["p", " "], ["str", "<stdio.h>"]], [["pp", "#include"], ["p", " "], ["str", "<stdlib.h>"]], [["pp", "#define"], ["p", " "], ["con", "MAX_PORT"], ["p", " "], ["num", "8080"]], [], [["kw", "typedef"], ["p", " "], ["kw", "struct"], ["p", " "], ["punc", "{"]], [["p", " "], ["ty", "int"], ["p", " "], ["prop", "id"], ["punc", ";"]], [["p", " "], ["kw", "const"], ["p", " "], ["ty", "char"], ["p", " "], ["op", "*"], ["prop", "name"], ["punc", ";"]], [["punc", "}"], ["p", " "], ["ty", "Order"], ["punc", ";"]], [], [["cmd", "//"], ["cm", " returns -1 on null input"]], [["ty", "int"], ["p", " "], ["fnd", "push"], ["punc", "("], ["ty", "Order"], ["p", " "], ["op", "*"], ["var", "o"], ["punc", ")"], ["p", " "], ["dec", "__attribute__"], ["punc", "(("], ["dec", "nonnull"], ["punc", "))"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "o"], ["p", " "], ["op", "=="], ["p", " "], ["con", "NULL"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["num", "-1"], ["punc", ";"]], [["p", " "], ["fnc", "printf"], ["punc", "("], ["str", "\"id=%d"], ["esc", "\\n"], ["str", "\""], ["punc", ","], ["p", " "], ["var", "o"], ["op", "->"], ["prop", "id"], ["punc", ");"]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "0"], ["punc", ";"]], [["punc", "}"]], [], [["ty", "int"], ["p", " "], ["fnd", "main"], ["punc", "("], ["ty", "void"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["ty", "Order"], ["p", " "], ["var", "o"], ["p", " "], ["op", "="], ["p", " "], ["punc", "{"], ["p", " "], ["op", "."], ["prop", "id"], ["p", " "], ["op", "="], ["p", " "], ["num", "1"], ["punc", ","], ["p", " "], ["op", "."], ["prop", "name"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""], ["p", " "], ["punc", "}"], ["punc", ";"]], [["p", " "], ["ty", "Order"], ["p", " "], ["op", "*"], ["var", "p2"], ["p", " "], ["op", "="], ["p", " "], ["bi", "malloc"], ["punc", "("], ["bi", "sizeof"], ["punc", "("], ["ty", "Order"], ["punc", "))"], ["punc", ";"]], [["p", " "], ["fnc", "push"], ["punc", "("], ["op", "&"], ["var", "o"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["bi", "free"], ["punc", "("], ["var", "p2"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "0"], ["punc", ";"]], [["punc", "}"]]], "C++": [[["cmd", "/**"], ["doc", " A color theme. */"]], [["pp", "#include"], ["p", " "], ["str", "<string>"]], [["pp", "#include"], ["p", " "], ["str", "<regex>"]], [["pp", "#pragma"], ["p", " "], ["pp", "once"]], [], [["kw", "namespace"], ["p", " "], ["var", "dupre"], ["p", " "], ["punc", "{"]], [], [["kw", "template"], ["op", "<"], ["kw", "typename"], ["p", " "], ["ty", "T"], ["op", ">"], ["p", " "], ["kw", "class"], ["p", " "], ["ty", "Theme"], ["p", " "], ["punc", "{"]], [["kw", "public"], ["op", ":"]], [["p", " "], ["kw", "static"], ["p", " "], ["kw", "constexpr"], ["p", " "], ["ty", "int"], ["p", " "], ["con", "MAX"], ["p", " "], ["op", "="], ["p", " "], ["num", "0x20"], ["punc", ";"]], [["p", " "], ["ty", "std"], ["op", "::"], ["ty", "string"], ["p", " "], ["prop", "name_"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""], ["punc", ";"]], [], [["p", " "], ["dec", "[[nodiscard]]"], ["p", " "], ["ty", "T"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["kw", "const"], ["p", " "], ["ty", "std"], ["op", "::"], ["ty", "string"], ["op", "&"], ["p", " "], ["var", "key"], ["punc", ")"], ["p", " "], ["kw", "const"], ["p", " "], ["punc", "{"]], [["p", " "], ["cmd", "//"], ["cm", " validate against a hex pattern"]], [["p", " "], ["kw", "static"], ["p", " "], ["ty", "std"], ["op", "::"], ["ty", "regex"], ["p", " "], ["var", "re"], ["punc", "("], ["re", "R\"(#[0-9a-f]{6})\""], ["punc", ")"], ["punc", ";"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "key"], ["op", "."], ["fnc", "empty"], ["punc", "()"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "nullptr"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["ty", "T"], ["punc", "{"], ["var", "key"], ["punc", "}"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["punc", "}"], ["punc", ";"]], [], [["ty", "int"], ["p", " "], ["fnd", "main"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "auto"], ["p", " "], ["var", "t"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Theme"], ["op", "<"], ["ty", "int"], ["op", ">"], ["punc", "{}"], ["punc", ";"]], [["p", " "], ["bi", "static_cast"], ["op", "<"], ["ty", "int"], ["op", ">"], ["punc", "("], ["var", "t"], ["op", "."], ["prop", "name_"], ["op", "."], ["fnc", "size"], ["punc", "())"], ["punc", ";"]], [["p", " "], ["ty", "std"], ["op", "::"], ["fnc", "printf"], ["punc", "("], ["str", "\"%s"], ["esc", "\\n"], ["str", "\""], ["punc", ","], ["p", " "], ["var", "t"], ["op", "."], ["prop", "name_"], ["op", "."], ["fnc", "c_str"], ["punc", "())"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "0"], ["punc", ";"]], [["punc", "}"]]], "Rust": [[["cmd", "//"], ["cm", " theme.rs"]], [["dec", "#![allow(dead_code)]"]], [["kw", "use"], ["p", " "], ["var", "std"], ["op", "::"], ["var", "fmt"], ["punc", ";"]], [], [["dec", "#[derive"], ["punc", "("], ["dec", "Debug"], ["punc", ","], ["p", " "], ["dec", "Clone"], ["punc", ")]"]], [["kw", "pub"], ["p", " "], ["kw", "trait"], ["p", " "], ["ty", "Theme"], ["op", "<"], ["var", "'a"], ["op", ">"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "const"], ["p", " "], ["con", "NAME"], ["op", ":"], ["p", " "], ["op", "&"], ["var", "'static"], ["p", " "], ["ty", "str"], ["punc", ";"]], [["p", " "], ["kw", "fn"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["op", "&"], ["var", "'a"], ["p", " "], ["var", "self"], ["punc", ","], ["p", " "], ["var", "key"], ["op", ":"], ["p", " "], ["op", "&"], ["var", "'a"], ["p", " "], ["ty", "str"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "Option"], ["op", "<"], ["op", "&"], ["var", "'a"], ["p", " "], ["ty", "str"], ["op", ">"], ["punc", ";"]], [["punc", "}"]], [], [["kw", "pub"], ["p", " "], ["kw", "struct"], ["p", " "], ["ty", "Palette"], ["op", "<"], ["var", "'a"], ["op", ">"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "pub"], ["p", " "], ["prop", "name"], ["op", ":"], ["p", " "], ["op", "&"], ["var", "'a"], ["p", " "], ["ty", "str"], ["punc", ","]], [["p", " "], ["kw", "pub"], ["p", " "], ["prop", "colors"], ["op", ":"], ["p", " "], ["ty", "Vec"], ["op", "<"], ["punc", "("], ["op", "&"], ["var", "'a"], ["p", " "], ["ty", "str"], ["punc", ","], ["p", " "], ["op", "&"], ["var", "'a"], ["p", " "], ["ty", "str"], ["punc", ")"], ["op", ">"], ["punc", ","]], [["punc", "}"]], [], [["kw", "impl"], ["op", "<"], ["var", "'a"], ["op", ">"], ["p", " "], ["ty", "Theme"], ["op", "<"], ["var", "'a"], ["op", ">"], ["p", " "], ["kw", "for"], ["p", " "], ["ty", "Palette"], ["op", "<"], ["var", "'a"], ["op", ">"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "const"], ["p", " "], ["con", "NAME"], ["op", ":"], ["p", " "], ["op", "&"], ["var", "'static"], ["p", " "], ["ty", "str"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""], ["punc", ";"]], [["p", " "], ["kw", "fn"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["op", "&"], ["var", "'a"], ["p", " "], ["var", "self"], ["punc", ","], ["p", " "], ["var", "key"], ["op", ":"], ["p", " "], ["op", "&"], ["var", "'a"], ["p", " "], ["ty", "str"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "Option"], ["op", "<"], ["op", "&"], ["var", "'a"], ["p", " "], ["ty", "str"], ["op", ">"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "if"], ["p", " "], ["var", "key"], ["op", "."], ["fnc", "is_empty"], ["punc", "()"], ["p", " "], ["punc", "{"], ["p", " "], ["kw", "return"], ["p", " "], ["con", "None"], ["punc", ";"], ["p", " "], ["punc", "}"]], [["p", " "], ["var", "self"], ["op", "."], ["prop", "colors"], ["op", "."], ["fnc", "iter"], ["punc", "()"], ["op", "."], ["fnc", "find"], ["punc", "("], ["op", "|"], ["punc", "("], ["var", "k"], ["punc", ","], ["p", " "], ["var", "_"], ["punc", ")"], ["op", "|"], ["p", " "], ["op", "*"], ["var", "k"], ["p", " "], ["op", "=="], ["p", " "], ["var", "key"], ["punc", ")"], ["op", "."], ["fnc", "map"], ["punc", "("], ["op", "|"], ["punc", "("], ["var", "_"], ["punc", ","], ["p", " "], ["var", "v"], ["punc", ")"], ["op", "|"], ["p", " "], ["op", "*"], ["var", "v"], ["punc", ")"]], [["p", " "], ["punc", "}"]], [["punc", "}"]], [], [["kw", "fn"], ["p", " "], ["fnd", "main"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "let"], ["p", " "], ["var", "palette"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Palette"], ["p", " "], ["punc", "{"], ["p", " "], ["prop", "name"], ["op", ":"], ["p", " "], ["str", "\"dupre\""], ["punc", ","], ["p", " "], ["prop", "colors"], ["op", ":"], ["p", " "], ["bi", "vec!"], ["punc", "["], ["punc", "("], ["str", "\"bg\""], ["punc", ","], ["p", " "], ["str", "\"#0d0b0a\""], ["punc", ")"], ["punc", "]"], ["p", " "], ["punc", "}"], ["punc", ";"]], [["p", " "], ["bi", "println!"], ["punc", "("], ["str", "\"{:?}\""], ["punc", ","], ["p", " "], ["var", "palette"], ["op", "."], ["fnc", "resolve"], ["punc", "("], ["str", "\"bg\""], ["punc", "))"], ["punc", ";"]], [["punc", "}"]]], "Zig": [[["cmd", "//"], ["cm", " theme.zig"]], [["kw", "const"], ["p", " "], ["var", "std"], ["p", " "], ["op", "="], ["p", " "], ["bi", "@import"], ["punc", "("], ["str", "\"std\""], ["punc", ")"], ["punc", ";"]], [["kw", "const"], ["p", " "], ["ty", "Allocator"], ["p", " "], ["op", "="], ["p", " "], ["var", "std"], ["op", "."], ["var", "mem"], ["op", "."], ["ty", "Allocator"], ["punc", ";"]], [], [["kw", "pub"], ["p", " "], ["kw", "const"], ["p", " "], ["ty", "Theme"], ["p", " "], ["op", "="], ["p", " "], ["kw", "struct"], ["p", " "], ["punc", "{"]], [["p", " "], ["prop", "name"], ["op", ":"], ["p", " "], ["punc", "["], ["punc", "]"], ["kw", "const"], ["p", " "], ["ty", "u8"], ["punc", ","]], [["p", " "], ["prop", "colors"], ["op", ":"], ["p", " "], ["punc", "["], ["punc", "]"], ["kw", "const"], ["p", " "], ["ty", "Color"], ["punc", ","]], [], [["p", " "], ["kw", "pub"], ["p", " "], ["kw", "fn"], ["p", " "], ["fnd", "init"], ["punc", "("], ["var", "alloc"], ["op", ":"], ["p", " "], ["op", "*"], ["ty", "Allocator"], ["punc", ")"], ["p", " "], ["op", "!"], ["bi", "@This"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "const"], ["p", " "], ["var", "colors"], ["p", " "], ["op", "="], ["p", " "], ["kw", "try"], ["p", " "], ["var", "alloc"], ["op", "."], ["fnc", "alloc"], ["punc", "("], ["ty", "Color"], ["punc", ","], ["p", " "], ["num", "2"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["var", "colors"], ["punc", "["], ["num", "0"], ["punc", "]"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Color"], ["punc", "{"], ["p", " "], ["prop", ".name"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"bg\""], ["punc", ","], ["p", " "], ["prop", ".hex"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"#0d0b0a\""], ["p", " "], ["punc", "}"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["bi", "@This"], ["punc", "()"], ["punc", "{"], ["p", " "], ["prop", ".name"], ["p", " "], ["op", "="], ["p", " "], ["str", "\"dupre\""], ["punc", ","], ["p", " "], ["prop", ".colors"], ["p", " "], ["op", "="], ["p", " "], ["var", "colors"], ["p", " "], ["punc", "}"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["punc", "}"], ["punc", ";"]], [], [["kw", "const"], ["p", " "], ["ty", "Color"], ["p", " "], ["op", "="], ["p", " "], ["kw", "struct"], ["p", " "], ["punc", "{"], ["p", " "], ["prop", "name"], ["op", ":"], ["p", " "], ["punc", "["], ["punc", "]"], ["kw", "const"], ["p", " "], ["ty", "u8"], ["punc", ","], ["p", " "], ["prop", "hex"], ["op", ":"], ["p", " "], ["punc", "["], ["punc", "]"], ["kw", "const"], ["p", " "], ["ty", "u8"], ["p", " "], ["punc", "}"], ["punc", ";"]], [], [["kw", "fn"], ["p", " "], ["fnd", "resolve"], ["punc", "("], ["var", "theme"], ["op", ":"], ["p", " "], ["ty", "Theme"], ["punc", ","], ["p", " "], ["kw", "comptime"], ["p", " "], ["var", "key"], ["op", ":"], ["p", " "], ["punc", "["], ["punc", ":"], ["num", "0"], ["punc", "]"], ["kw", "const"], ["p", " "], ["ty", "u8"], ["punc", ")"], ["p", " "], ["op", "!"], ["punc", "["], ["punc", "]"], ["kw", "const"], ["p", " "], ["ty", "u8"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "inline"], ["p", " "], ["kw", "for"], ["p", " "], ["punc", "("], ["var", "theme"], ["op", "."], ["prop", "colors"], ["punc", ")"], ["p", " "], ["op", "|"], ["var", "color"], ["op", "|"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "std"], ["op", "."], ["var", "mem"], ["op", "."], ["fnc", "eql"], ["punc", "("], ["ty", "u8"], ["punc", ","], ["p", " "], ["var", "color"], ["op", "."], ["prop", "name"], ["punc", ","], ["p", " "], ["var", "key"], ["punc", ")"], ["punc", ")"], ["p", " "], ["kw", "return"], ["p", " "], ["var", "color"], ["op", "."], ["prop", "hex"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "error.MissingColor"], ["punc", ";"]], [["punc", "}"]], [], [["kw", "test"], ["p", " "], ["str", "\"resolve bg\""], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "var"], ["p", " "], ["var", "arena"], ["p", " "], ["op", "="], ["p", " "], ["var", "std"], ["op", "."], ["var", "heap"], ["op", "."], ["ty", "ArenaAllocator"], ["op", "."], ["fnc", "init"], ["punc", "("], ["var", "std"], ["op", "."], ["var", "testing"], ["op", "."], ["prop", "allocator"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["kw", "defer"], ["p", " "], ["var", "arena"], ["op", "."], ["fnc", "deinit"], ["punc", "()"], ["punc", ";"]], [["p", " "], ["kw", "try"], ["p", " "], ["var", "std"], ["op", "."], ["var", "testing"], ["op", "."], ["fnc", "expectEqualStrings"], ["punc", "("], ["str", "\"#0d0b0a\""], ["punc", ","], ["p", " "], ["kw", "try"], ["p", " "], ["fnc", "resolve"], ["punc", "("], ["kw", "try"], ["p", " "], ["ty", "Theme"], ["op", "."], ["fnc", "init"], ["punc", "("], ["op", "&"], ["var", "arena"], ["op", "."], ["prop", "allocator"], ["punc", ")"], ["punc", ","], ["p", " "], ["str", "\"bg\""], ["punc", "))"], ["punc", ";"]], [["punc", "}"]]], "Shell": [[["cmd", "#!"], ["cm", "/bin/bash"]], [["cmd", "#"], ["cm", " deploy.sh"]], [["bi", "set"], ["p", " "], ["op", "-"], ["var", "euo"], ["p", " "], ["var", "pipefail"]], [], [["var", "PORT"], ["op", "="], ["num", "8080"]], [["var", "NAME"], ["op", "="], ["str", "\"dupre\""]], [], [["fnd", "deploy"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "local"], ["p", " "], ["var", "target"], ["op", "="], ["str", "\"$1\""]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "[["], ["p", " "], ["op", "-z"], ["p", " "], ["str", "\"$target\""], ["p", " "], ["punc", "]]"], ["punc", ";"], ["p", " "], ["kw", "then"]], [["p", " "], ["bi", "echo"], ["p", " "], ["str", "\"no target\""]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "1"]], [["p", " "], ["kw", "fi"]], [["p", " "], ["fnc", "rsync"], ["p", " "], ["op", "-az"], ["p", " "], ["str", "\"$NAME\""], ["p", " "], ["str", "\"$target\""]], [["punc", "}"]], [], [["fnd", "main"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "for"], ["p", " "], ["var", "host"], ["p", " "], ["kw", "in"], ["p", " "], ["str", "\"$@\""], ["punc", ";"], ["p", " "], ["kw", "do"]], [["p", " "], ["fnc", "deploy"], ["p", " "], ["str", "\"$host\""], ["p", " "], ["op", "||"], ["p", " "], ["bi", "exit"], ["p", " "], ["num", "1"]], [["p", " "], ["kw", "done"]], [["p", " "], ["bi", "echo"], ["p", " "], ["op", "-e"], ["p", " "], ["str", "\"all done"], ["esc", "\\n"], ["str", "\""]], [["punc", "}"]], [], [["fnc", "main"], ["p", " "], ["str", "\"$@\""]]], "Racket": [[["pp", "#lang"], ["p", " "], ["pp", "racket"]], [], [["cmd", ";;"], ["p", " "], ["cm", "Compute Fibonacci numbers with memoization"]], [["punc", "("], ["kw", "require"], ["p", " "], ["var", "racket/list"], ["punc", ")"]], [], [["punc", "("], ["kw", "define"], ["p", " "], ["punc", "("], ["fnd", "fib"], ["p", " "], ["var", "n"], ["punc", ")"]], [["p", " "], ["punc", "("], ["kw", "cond"], ["p", " "]], [["p", " "], ["punc", "[("], ["bi", "<"], ["p", " "], ["var", "n"], ["p", " "], ["num", "2"], ["punc", ")"], ["p", " "], ["var", "n"], ["punc", "]"]], [["p", " "], ["punc", "["], ["con", "else"], ["p", " "]], [["p", " "], ["punc", "("], ["bi", "+"], ["p", " "], ["punc", "("], ["fnc", "fib"], ["p", " "], ["punc", "("], ["bi", "-"], ["p", " "], ["var", "n"], ["p", " "], ["num", "1"], ["punc", "))"], ["p", " "]], [["p", " "], ["punc", "("], ["fnc", "fib"], ["p", " "], ["punc", "("], ["bi", "-"], ["p", " "], ["var", "n"], ["p", " "], ["num", "2"], ["punc", ")))])]"]], [], [["cmd", ";;"], ["p", " "], ["cm", "A point struct with two fields"]], [["punc", "("], ["kw", "struct"], ["p", " "], ["ty", "point"], ["p", " "], ["punc", "("], ["prop", "x"], ["p", " "], ["prop", "y"], ["punc", ")"], ["p", " "], ["con", "#:transparent"], ["punc", ")"]], [], [["punc", "("], ["kw", "define"], ["p", " "], ["var", "origin"], ["p", " "], ["punc", "("], ["fnc", "point"], ["p", " "], ["num", "0"], ["p", " "], ["num", "0"], ["punc", "))"]], [], [["punc", "("], ["kw", "define"], ["p", " "], ["var", "nums"], ["p", " "], ["punc", "("], ["kw", "quote"], ["p", " "], ["punc", "("], ["num", "1"], ["p", " "], ["num", "2"], ["p", " "], ["num", "3"], ["p", " "], ["num", "4"], ["p", " "], ["num", "5"], ["punc", "))"]], [], [["punc", "("], ["kw", "define"], ["p", " "], ["var", "squared"], ["p", " "]], [["p", " "], ["punc", "("], ["bi", "map"], ["p", " "], ["punc", "("], ["kw", "lambda"], ["p", " "], ["punc", "("], ["var", "x"], ["punc", ")"], ["p", " "], ["punc", "("], ["bi", "*"], ["p", " "], ["var", "x"], ["p", " "], ["var", "x"], ["punc", "))"], ["p", " "], ["var", "nums"], ["punc", "))"]], [], [["punc", "("], ["bi", "printf"], ["p", " "], ["str", "\"squares: ~a\\n\""], ["p", " "], ["var", "squared"], ["punc", ")"]], [["punc", "("], ["bi", "displayln"], ["p", " "], ["punc", "("], ["fnc", "first"], ["p", " "], ["var", "squared"], ["punc", "))"]]], "Scheme": [[["cmd", ";;"], ["p", " "], ["cm", "Tail-recursive factorial in Scheme"]], [], [["punc", "("], ["kw", "define"], ["p", " "], ["punc", "("], ["fnd", "factorial"], ["p", " "], ["var", "n"], ["punc", ")"]], [["p", " "], ["punc", "("], ["kw", "let"], ["p", " "], ["fnd", "loop"], ["p", " "], ["punc", "(["], ["var", "acc"], ["p", " "], ["num", "1"], ["punc", "]"], ["p", " "], ["punc", "["], ["var", "i"], ["p", " "], ["var", "n"], ["punc", "])"]], [["p", " "], ["punc", "("], ["kw", "if"], ["p", " "], ["punc", "("], ["bi", "="], ["p", " "], ["var", "i"], ["p", " "], ["num", "0"], ["punc", ")"]], [["p", " "], ["var", "acc"], ["p", " "]], [["p", " "], ["punc", "("], ["fnc", "loop"], ["p", " "], ["punc", "("], ["bi", "*"], ["p", " "], ["var", "acc"], ["p", " "], ["var", "i"], ["punc", ")"], ["p", " "], ["punc", "("], ["bi", "-"], ["p", " "], ["var", "i"], ["p", " "], ["num", "1"], ["punc", "))))"]], [], [["cmd", ";;"], ["p", " "], ["cm", "Higher-order map over a quoted list"]], [["punc", "("], ["kw", "define"], ["p", " "], ["var", "primes"], ["p", " "], ["punc", "("], ["kw", "quote"], ["p", " "], ["punc", "("], ["num", "2"], ["p", " "], ["num", "3"], ["p", " "], ["num", "5"], ["p", " "], ["num", "7"], ["p", " "], ["num", "11"], ["punc", "))"]], [], [["punc", "("], ["kw", "define"], ["p", " "], ["punc", "("], ["fnd", "double"], ["p", " "], ["var", "x"], ["punc", ")"]], [["p", " "], ["punc", "("], ["bi", "*"], ["p", " "], ["var", "x"], ["p", " "], ["num", "2"], ["punc", ")"]], [], [["punc", "("], ["kw", "define"], ["p", " "], ["var", "doubled"], ["p", " "], ["punc", "("], ["bi", "map"], ["p", " "], ["var", "double"], ["p", " "], ["var", "primes"], ["punc", "))"]], [], [["cmd", ";;"], ["p", " "], ["cm", "Predicate using cond and recursion"]], [["punc", "("], ["kw", "define"], ["p", " "], ["punc", "("], ["fnd", "member?"], ["p", " "], ["var", "x"], ["p", " "], ["var", "lst"], ["punc", ")"]], [["p", " "], ["punc", "("], ["kw", "cond"], ["p", " "]], [["p", " "], ["punc", "[("], ["bi", "null?"], ["p", " "], ["var", "lst"], ["punc", ")"], ["p", " "], ["con", "#f"], ["punc", "]"]], [["p", " "], ["punc", "[("], ["bi", "equal?"], ["p", " "], ["punc", "("], ["bi", "car"], ["p", " "], ["var", "lst"], ["punc", ")"], ["p", " "], ["var", "x"], ["punc", ")"], ["p", " "], ["con", "#t"], ["punc", "]"]], [["p", " "], ["punc", "["], ["con", "else"], ["p", " "], ["punc", "("], ["fnc", "member?"], ["p", " "], ["var", "x"], ["p", " "], ["punc", "("], ["bi", "cdr"], ["p", " "], ["var", "lst"], ["punc", "))]"], ["punc", ")"]], [], [["punc", "("], ["bi", "display"], ["p", " "], ["punc", "("], ["fnc", "member?"], ["p", " "], ["num", "5"], ["p", " "], ["var", "primes"], ["punc", "))"]], [["punc", "("], ["bi", "newline"], ["punc", ")"]]], "Haskell": [[["cmd", "-- |"], ["cm", " Compute statistics over a stream of samples."]], [["pp", "{-# LANGUAGE ScopedTypeVariables #-}"]], [["kw", "module"], ["p", " "], ["ty", "Stats"], ["p", " "], ["punc", "("], ["var", "mean"], ["punc", ","], ["p", " "], ["var", "variance"], ["punc", ")"], ["p", " "], ["kw", "where"]], [], [["kw", "import"], ["p", " "], ["kw", "qualified"], ["p", " "], ["ty", "Data.List"], ["p", " "], ["kw", "as"], ["p", " "], ["ty", "L"]], [], [["cmd", "-- |"], ["cm", " A labelled measurement."]], [["kw", "data"], ["p", " "], ["ty", "Sample"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Sample"]], [["p", " "], ["p", " "], ["punc", "{"], ["p", " "], ["prop", "label"], ["p", " "], ["op", "::"], ["p", " "], ["ty", "String"]], [["p", " "], ["p", " "], ["punc", ","], ["p", " "], ["prop", "value"], ["p", " "], ["op", "::"], ["p", " "], ["ty", "Double"]], [["p", " "], ["p", " "], ["punc", "}"], ["p", " "], ["kw", "deriving"], ["p", " "], ["punc", "("], ["ty", "Show"], ["punc", ","], ["p", " "], ["ty", "Eq"], ["punc", ")"]], [], [["cmd", "-- |"], ["cm", " Arithmetic mean; returns 0 for an empty list."]], [["fnd", "mean"], ["p", " "], ["op", "::"], ["p", " "], ["punc", "["], ["ty", "Double"], ["punc", "]"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "Double"]], [["fnd", "mean"], ["p", " "], ["con", "[]"], ["p", " "], ["op", "="], ["p", " "], ["num", "0"]], [["fnd", "mean"], ["p", " "], ["var", "xs"], ["p", " "], ["op", "="], ["p", " "], ["fnc", "sum"], ["p", " "], ["var", "xs"], ["p", " "], ["op", "/"], ["p", " "], ["fnc", "fromIntegral"], ["p", " "], ["punc", "("], ["fnc", "length"], ["p", " "], ["var", "xs"], ["punc", ")"]], [], [["fnd", "variance"], ["p", " "], ["op", "::"], ["p", " "], ["punc", "["], ["ty", "Double"], ["punc", "]"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "Double"]], [["fnd", "variance"], ["p", " "], ["var", "xs"], ["p", " "], ["op", "="], ["p", " "], ["kw", "let"], ["p", " "], ["var", "m"], ["p", " "], ["op", "="], ["p", " "], ["fnc", "mean"], ["p", " "], ["var", "xs"]], [["p", " "], ["kw", "in"], ["p", " "], ["fnc", "mean"], ["p", " "], ["punc", "["], ["p", " "], ["punc", "("], ["var", "x"], ["p", " "], ["op", "-"], ["p", " "], ["var", "m"], ["punc", ")"], ["p", " "], ["op", "^"], ["p", " "], ["num", "2"], ["p", " "], ["op", "|"], ["p", " "], ["var", "x"], ["p", " "], ["op", "<-"], ["p", " "], ["var", "xs"], ["p", " "], ["punc", "]"]], [], [["cmd", "-- |"], ["cm", " Demo entry point."]], [["fnd", "main"], ["p", " "], ["op", "::"], ["p", " "], ["ty", "IO"], ["p", " "], ["punc", "("], ["punc", ")"]], [["fnd", "main"], ["p", " "], ["op", "="], ["p", " "], ["kw", "do"]], [["p", " "], ["kw", "let"], ["p", " "], ["var", "samples"], ["p", " "], ["op", "="], ["p", " "], ["punc", "["], ["num", "1.0"], ["punc", ","], ["p", " "], ["num", "2.5"], ["punc", ","], ["p", " "], ["num", "3.5"], ["punc", "]"]], [["p", " "], ["fnc", "putStrLn"], ["p", " "], ["punc", "("], ["str", "\"mean = \""], ["p", " "], ["op", "++"], ["p", " "], ["fnc", "show"], ["p", " "], ["punc", "("], ["fnc", "mean"], ["p", " "], ["var", "samples"], ["punc", "))"]]], "OCaml": [[["cmd", "(*"], ["cm", " Simple expression evaluator with variant types. "], ["cmd", "*)"]], [], [["kw", "type"], ["p", " "], ["ty", "expr"], ["p", " "], ["op", "="]], [["p", " "], ["p", " "], ["op", "|"], ["p", " "], ["ty", "Num"], ["p", " "], ["kw", "of"], ["p", " "], ["ty", "float"]], [["p", " "], ["p", " "], ["op", "|"], ["p", " "], ["ty", "Var"], ["p", " "], ["kw", "of"], ["p", " "], ["ty", "string"]], [["p", " "], ["p", " "], ["op", "|"], ["p", " "], ["ty", "Add"], ["p", " "], ["kw", "of"], ["p", " "], ["ty", "expr"], ["p", " "], ["op", "*"], ["p", " "], ["ty", "expr"]], [["p", " "], ["p", " "], ["op", "|"], ["p", " "], ["ty", "Mul"], ["p", " "], ["kw", "of"], ["p", " "], ["ty", "expr"], ["p", " "], ["op", "*"], ["p", " "], ["ty", "expr"]], [], [["cmd", "(**"], ["cm", " Evaluate [e] under environment [env]. "], ["cmd", "*)"]], [["kw", "let"], ["p", " "], ["kw", "rec"], ["p", " "], ["fnd", "eval"], ["p", " "], ["var", "env"], ["p", " "], ["var", "e"], ["p", " "], ["op", "="]], [["p", " "], ["kw", "match"], ["p", " "], ["var", "e"], ["p", " "], ["kw", "with"]], [["p", " "], ["op", "|"], ["p", " "], ["ty", "Num"], ["p", " "], ["var", "n"], ["p", " "], ["op", "->"], ["p", " "], ["var", "n"]], [["p", " "], ["op", "|"], ["p", " "], ["ty", "Var"], ["p", " "], ["var", "x"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "List"], ["punc", "."], ["fnc", "assoc"], ["p", " "], ["var", "x"], ["p", " "], ["var", "env"]], [["p", " "], ["op", "|"], ["p", " "], ["ty", "Add"], ["p", " "], ["punc", "("], ["var", "a"], ["punc", ","], ["p", " "], ["var", "b"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["fnc", "eval"], ["p", " "], ["var", "env"], ["p", " "], ["var", "a"], ["p", " "], ["op", "+."], ["p", " "], ["fnc", "eval"], ["p", " "], ["var", "env"], ["p", " "], ["var", "b"]], [["p", " "], ["op", "|"], ["p", " "], ["ty", "Mul"], ["p", " "], ["punc", "("], ["var", "a"], ["punc", ","], ["p", " "], ["var", "b"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["fnc", "eval"], ["p", " "], ["var", "env"], ["p", " "], ["var", "a"], ["p", " "], ["op", "*."], ["p", " "], ["fnc", "eval"], ["p", " "], ["var", "env"], ["p", " "], ["var", "b"]], [], [["kw", "let"], ["p", " "], ["punc", "()"], ["p", " "], ["op", "="], ["p", " "], ["kw", "let"], ["p", " "], ["var", "env"], ["p", " "], ["op", "="], ["p", " "], ["punc", "["], ["p", " "], ["punc", "("], ["str", "\"x\""], ["punc", ","], ["p", " "], ["num", "3.0"], ["punc", ")"], ["p", " "], ["punc", "]"], ["p", " "], ["kw", "in"]], [["p", " "], ["kw", "let"], ["p", " "], ["var", "e"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Add"], ["p", " "], ["punc", "("], ["ty", "Var"], ["p", " "], ["str", "\"x\""], ["punc", ","], ["p", " "], ["ty", "Num"], ["p", " "], ["num", "4.0"], ["punc", ")"], ["p", " "], ["kw", "in"]], [["p", " "], ["ty", "Printf"], ["punc", "."], ["fnc", "printf"], ["p", " "], ["str", "\"result = %g\\n\""], ["p", " "], ["punc", "("], ["fnc", "eval"], ["p", " "], ["var", "env"], ["p", " "], ["var", "e"], ["punc", ")"]]], "Scala": [[["cmd", "//"], ["cm", " Geometry helpers for 2D shapes"]], [["kw", "package"], ["p", " "], ["var", "geometry"]], [], [["kw", "import"], ["p", " "], ["var", "scala"], ["op", "."], ["var", "math"], ["op", "."], ["fnc", "sqrt"]], [], [["dec", "@inline"], ["p", " "], ["kw", "final"], ["p", " "], ["kw", "case"], ["p", " "], ["kw", "class"], ["p", " "], ["ty", "Point"], ["punc", "("], ["kw", "val"], ["p", " "], ["prop", "x"], ["op", ":"], ["p", " "], ["ty", "Double"], ["punc", ","], ["p", " "], ["kw", "val"], ["p", " "], ["prop", "y"], ["op", ":"], ["p", " "], ["ty", "Double"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "distanceTo"], ["punc", "("], ["var", "that"], ["op", ":"], ["p", " "], ["ty", "Point"], ["punc", ")"], ["op", ":"], ["p", " "], ["ty", "Double"], ["p", " "], ["op", "="], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "val"], ["p", " "], ["var", "dx"], ["p", " "], ["op", "="], ["p", " "], ["var", "x"], ["p", " "], ["op", "-"], ["p", " "], ["var", "that"], ["op", "."], ["prop", "x"]], [["p", " "], ["kw", "val"], ["p", " "], ["var", "dy"], ["p", " "], ["op", "="], ["p", " "], ["var", "y"], ["p", " "], ["op", "-"], ["p", " "], ["var", "that"], ["op", "."], ["prop", "y"]], [["p", " "], ["fnc", "sqrt"], ["punc", "("], ["var", "dx"], ["p", " "], ["op", "*"], ["p", " "], ["var", "dx"], ["p", " "], ["op", "+"], ["p", " "], ["var", "dy"], ["p", " "], ["op", "*"], ["p", " "], ["var", "dy"], ["punc", ")"]], [["p", " "], ["punc", "}"]], [["punc", "}"]], [], [["kw", "object"], ["p", " "], ["ty", "Geometry"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "val"], ["p", " "], ["var", "origin"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Point"], ["punc", "("], ["num", "0.0"], ["punc", ","], ["p", " "], ["num", "0.0"], ["punc", ")"]], [["p", " "], ["kw", "val"], ["p", " "], ["var", "pts"], ["p", " "], ["op", "="], ["p", " "], ["ty", "List"], ["punc", "("], ["ty", "Point"], ["punc", "("], ["num", "3.0"], ["punc", ","], ["p", " "], ["num", "4.0"], ["punc", "),"], ["p", " "], ["ty", "Point"], ["punc", "("], ["num", "1.0"], ["punc", ","], ["p", " "], ["num", "2.0"], ["punc", "))"]], [["p", " "], ["kw", "val"], ["p", " "], ["var", "dists"], ["p", " "], ["op", "="], ["p", " "], ["kw", "for"], ["p", " "], ["punc", "("], ["var", "p"], ["p", " "], ["op", "<-"], ["p", " "], ["var", "pts"], ["punc", ")"], ["p", " "], ["kw", "yield"], ["p", " "], ["var", "origin"], ["op", "."], ["fnc", "distanceTo"], ["punc", "("], ["var", "p"], ["punc", ")"]], [], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "main"], ["punc", "("], ["var", "args"], ["op", ":"], ["p", " "], ["ty", "Array"], ["punc", "["], ["ty", "String"], ["punc", "]"], ["punc", ")"], ["op", ":"], ["p", " "], ["ty", "Unit"], ["p", " "], ["op", "="], ["p", " "], ["punc", "{"]], [["p", " "], ["var", "dists"], ["op", "."], ["fnc", "foreach"], ["punc", "("], ["var", "d"], ["p", " "], ["op", "=>"], ["p", " "], ["fnc", "println"], ["punc", "("], ["str", "s\"dist = $d\""], ["punc", ")"], ["punc", ")"]], [["p", " "], ["kw", "val"], ["p", " "], ["var", "ok"], ["p", " "], ["op", "="], ["p", " "], ["var", "dists"], ["op", "."], ["fnc", "nonEmpty"], ["p", " "], ["op", "&&"], ["p", " "], ["con", "true"]], [["p", " "], ["punc", "}"]], [["punc", "}"]]], "Kotlin": [[["cmd", "//"], ["cm", " User repository with a simple cache"]], [["kw", "package"], ["p", " "], ["var", "com"], ["op", "."], ["var", "example"], ["op", "."], ["var", "data"]], [], [["kw", "import"], ["p", " "], ["var", "kotlin"], ["op", "."], ["var", "collections"], ["op", "."], ["var", "mutableMapOf"]], [], [["kw", "data"], ["p", " "], ["kw", "class"], ["p", " "], ["ty", "User"], ["punc", "("], ["kw", "val"], ["p", " "], ["prop", "id"], ["op", ":"], ["p", " "], ["ty", "Int"], ["punc", ","], ["p", " "], ["kw", "val"], ["p", " "], ["prop", "name"], ["op", ":"], ["p", " "], ["ty", "String"], ["punc", ")"]], [], [["kw", "class"], ["p", " "], ["ty", "UserRepo"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "private"], ["p", " "], ["kw", "val"], ["p", " "], ["var", "cache"], ["p", " "], ["op", "="], ["p", " "], ["bi", "mutableMapOf"], ["punc", "<"], ["ty", "Int"], ["punc", ","], ["p", " "], ["ty", "User"], ["punc", ">"], ["punc", "()"]], [], [["p", " "], ["dec", "@JvmStatic"], ["p", " "]], [["p", " "], ["kw", "fun"], ["p", " "], ["fnd", "findById"], ["punc", "("], ["var", "id"], ["op", ":"], ["p", " "], ["ty", "Int"], ["punc", ")"], ["op", ":"], ["p", " "], ["ty", "User"], ["op", "?"], ["p", " "], ["op", "="], ["p", " "], ["var", "cache"], ["punc", "["], ["var", "id"], ["punc", "]"]], [], [["p", " "], ["kw", "fun"], ["p", " "], ["fnd", "save"], ["punc", "("], ["var", "user"], ["op", ":"], ["p", " "], ["ty", "User"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["var", "cache"], ["punc", "["], ["var", "user"], ["op", "."], ["prop", "id"], ["punc", "]"], ["p", " "], ["op", "="], ["p", " "], ["var", "user"]], [["p", " "], ["bi", "println"], ["punc", "("], ["str", "\"saved "], ["esc", "\\n"], ["str", "\""], ["p", " "], ["op", "+"], ["p", " "], ["var", "user"], ["op", "."], ["prop", "name"], ["punc", ")"]], [["p", " "], ["punc", "}"]], [["punc", "}"]], [], [["kw", "fun"], ["p", " "], ["fnd", "main"], ["punc", "()"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "val"], ["p", " "], ["var", "repo"], ["p", " "], ["op", "="], ["p", " "], ["ty", "UserRepo"], ["punc", "()"]], [["p", " "], ["var", "repo"], ["op", "."], ["fnc", "save"], ["punc", "("], ["ty", "User"], ["punc", "("], ["num", "1"], ["punc", ","], ["p", " "], ["str", "\"Ada\""], ["punc", "))"]], [["p", " "], ["kw", "val"], ["p", " "], ["var", "found"], ["p", " "], ["op", "="], ["p", " "], ["var", "repo"], ["op", "."], ["fnc", "findById"], ["punc", "("], ["num", "1"], ["punc", ")"], ["p", " "], ["op", "?:"], ["p", " "], ["kw", "return"]], [["p", " "], ["bi", "println"], ["punc", "("], ["var", "found"], ["punc", ")"]], [["punc", "}"]]], "Swift": [[["cmd", "//"], ["cm", " Account model with balance guard"]], [["kw", "import"], ["p", " "], ["ty", "Foundation"]], [], [["dec", "@frozen"], ["p", " "]], [["kw", "struct"], ["p", " "], ["ty", "Account"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "let"], ["p", " "], ["prop", "id"], ["op", ":"], ["p", " "], ["ty", "Int"]], [["p", " "], ["kw", "var"], ["p", " "], ["prop", "balance"], ["op", ":"], ["p", " "], ["ty", "Double"], ["p", " "], ["op", "="], ["p", " "], ["num", "0.0"]], [], [["p", " "], ["kw", "func"], ["p", " "], ["fnd", "withdraw"], ["punc", "("], ["var", "amount"], ["op", ":"], ["p", " "], ["ty", "Double"], ["punc", ")"], ["p", " "], ["op", "->"], ["p", " "], ["ty", "Bool"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "guard"], ["p", " "], ["var", "amount"], ["p", " "], ["op", "<="], ["p", " "], ["prop", "balance"], ["p", " "], ["kw", "else"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "false"]], [["p", " "], ["punc", "}"]], [["p", " "], ["prop", "balance"], ["p", " "], ["op", "-="], ["p", " "], ["var", "amount"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "true"]], [["p", " "], ["punc", "}"]], [["punc", "}"]], [], [["kw", "let"], ["p", " "], ["var", "acct"], ["p", " "], ["op", "="], ["p", " "], ["ty", "Account"], ["punc", "("], ["var", "id"], ["op", ":"], ["p", " "], ["num", "7"], ["punc", ","], ["p", " "], ["var", "balance"], ["op", ":"], ["p", " "], ["num", "100.0"], ["punc", ")"]], [["kw", "var"], ["p", " "], ["var", "copy"], ["p", " "], ["op", "="], ["p", " "], ["var", "acct"]], [["kw", "let"], ["p", " "], ["var", "ok"], ["p", " "], ["op", "="], ["p", " "], ["var", "copy"], ["op", "."], ["fnc", "withdraw"], ["punc", "("], ["var", "amount"], ["op", ":"], ["p", " "], ["num", "30.0"], ["punc", ")"]], [["bi", "print"], ["punc", "("], ["str", "\"acct ok=\""], ["punc", ","], ["p", " "], ["var", "ok"], ["punc", ")"]]], "Lua": [[["cmd", "--"], ["cm", " Account module: balances and transfers"]], [["kw", "local"], ["p", " "], ["ty", "Account"], ["op", "="], ["punc", "{}"]], [["ty", "Account"], ["punc", "."], ["prop", "__index"], ["op", "="], ["ty", "Account"]], [], [["kw", "local"], ["p", " "], ["var", "rates"], ["op", "="], ["p", " "], ["punc", "{"], ["str", "\"usd\""], ["op", "="], ["num", "1.0"], ["punc", ","], ["p", " "], ["str", "\"eur\""], ["op", "="], ["num", "0.92"], ["punc", "}"]], [], [["kw", "function"], ["p", " "], ["ty", "Account"], ["op", "."], ["fnd", "new"], ["punc", "("], ["var", "name"], ["punc", ","], ["p", " "], ["var", "balance"], ["punc", ")"]], [["p", " "], ["kw", "local"], ["p", " "], ["var", "self"], ["op", "="], ["p", " "], ["fnc", "setmetatable"], ["punc", "("], ["punc", "{}"], ["punc", ","], ["p", " "], ["ty", "Account"], ["punc", ")"]], [["p", " "], ["var", "self"], ["punc", "."], ["prop", "name"], ["op", "="], ["var", "name"]], [["p", " "], ["var", "self"], ["punc", "."], ["prop", "balance"], ["op", "="], ["p", " "], ["var", "balance"], ["p", " "], ["kw", "or"], ["p", " "], ["num", "0"]], [["p", " "], ["kw", "return"], ["p", " "], ["var", "self"]], [["kw", "end"]], [], [["kw", "function"], ["p", " "], ["ty", "Account"], ["op", ":"], ["fnd", "report"], ["punc", "()"]], [["p", " "], ["kw", "for"], ["p", " "], ["var", "code"], ["punc", ","], ["p", " "], ["var", "rate"], ["p", " "], ["kw", "in"], ["p", " "], ["bi", "pairs"], ["punc", "("], ["var", "rates"], ["punc", ")"], ["p", " "], ["kw", "do"]], [["p", " "], ["bi", "print"], ["punc", "("], ["var", "code"], ["punc", ","], ["p", " "], ["var", "self"], ["punc", "."], ["prop", "balance"], ["p", " "], ["op", "*"], ["p", " "], ["var", "rate"], ["punc", ")"]], [["p", " "], ["kw", "end"]], [["p", " "], ["kw", "if"], ["p", " "], ["var", "self"], ["punc", "."], ["prop", "balance"], ["p", " "], ["op", "=="], ["p", " "], ["num", "0"], ["p", " "], ["kw", "then"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "nil"]], [["p", " "], ["kw", "end"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "true"]], [["kw", "end"]]], "Ruby": [[["cmd", "#"], ["cm", " Inventory tracker with tagged items"]], [["kw", "class"], ["p", " "], ["ty", "Inventory"]], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "initialize"], ["punc", "("], ["var", "items"], ["p", " "], ["op", "="], ["p", " "], ["punc", "[]"], ["punc", ")"]], [["p", " "], ["var", "@items"], ["p", " "], ["op", "="], ["p", " "], ["var", "items"]], [["p", " "], ["var", "@tags"], ["p", " "], ["op", "="], ["p", " "], ["punc", "{"], ["prop", "sku:"], ["p", " "], ["con", "nil"], ["punc", "}"]], [["p", " "], ["kw", "end"]], [], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "add"], ["punc", "("], ["var", "name"], ["punc", ","], ["p", " "], ["var", "price"], ["punc", ")"]], [["p", " "], ["kw", "return"], ["p", " "], ["con", "false"], ["p", " "], ["kw", "unless"], ["p", " "], ["var", "name"], ["p", " "], ["op", "=~"], ["p", " "], ["re", "/\\A\\w+\\z/"]], [["p", " "], ["var", "@items"], ["p", " "], ["op", "<<"], ["p", " "], ["punc", "{"], ["p", " "], ["prop", "name:"], ["p", " "], ["var", "name"], ["punc", ","], ["p", " "], ["prop", "price:"], ["p", " "], ["var", "price"], ["p", " "], ["punc", "}"]], [["p", " "], ["kw", "end"]], [], [["p", " "], ["kw", "def"], ["p", " "], ["fnd", "total"], ["punc", "("], ["var", "tax"], ["p", " "], ["op", "="], ["p", " "], ["num", "0.08"], ["punc", ")"]], [["p", " "], ["var", "sum"], ["p", " "], ["op", "="], ["p", " "], ["num", "0"]], [["p", " "], ["var", "@items"], ["punc", "."], ["fnc", "each"], ["p", " "], ["kw", "do"], ["p", " "], ["punc", "|"], ["var", "item"], ["punc", "|"]], [["p", " "], ["var", "sum"], ["p", " "], ["op", "+="], ["p", " "], ["var", "item"], ["punc", "["], ["prop", ":price"], ["punc", "]"]], [["p", " "], ["kw", "end"]], [["p", " "], ["bi", "printf"], ["punc", "("], ["str", "\"total: %.2f\\n\""], ["punc", ","], ["p", " "], ["var", "sum"], ["p", " "], ["op", "*"], ["p", " "], ["punc", "("], ["num", "1"], ["p", " "], ["op", "+"], ["p", " "], ["var", "tax"], ["punc", "))"]], [["p", " "], ["kw", "end"]], [["kw", "end"]]], "Perl": [[["cmd", "#"], ["cm", "!/usr/bin/perl"]], [["kw", "use"], ["p", " "], ["pp", "strict"], ["punc", ";"]], [["kw", "use"], ["p", " "], ["pp", "warnings"], ["punc", ";"]], [], [["cmd", "#"], ["cm", " Parse a config line into a hash"]], [["kw", "sub"], ["p", " "], ["fnd", "parse_config"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "my"], ["p", " "], ["punc", "("], ["var", "$line"], ["punc", ")"], ["p", " "], ["op", "="], ["p", " "], ["var", "@_"], ["punc", ";"]], [["p", " "], ["kw", "my"], ["p", " "], ["var", "%conf"], ["p", " "], ["op", "="], ["p", " "], ["punc", "()"], ["punc", ";"]], [], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "$line"], ["p", " "], ["op", "=~"], ["p", " "], ["re", "/^(\\w+)\\s*=\\s*(.+)$/"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["var", "$conf"], ["punc", "{"], ["var", "$1"], ["punc", "}"], ["p", " "], ["op", "="], ["p", " "], ["var", "$2"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [], [["p", " "], ["kw", "return"], ["p", " "], ["op", "\\"], ["var", "%conf"], ["punc", ";"]], [["punc", "}"]], [], [["kw", "my"], ["p", " "], ["var", "$ref"], ["p", " "], ["op", "="], ["p", " "], ["fnc", "parse_config"], ["punc", "("], ["str", "\"host = localhost\""], ["punc", ")"], ["punc", ";"]], [["kw", "my"], ["p", " "], ["var", "@keys"], ["p", " "], ["op", "="], ["p", " "], ["bi", "keys"], ["p", " "], ["var", "%$ref"], ["punc", ";"]], [["bi", "print"], ["p", " "], ["var", "@keys"], ["punc", ";"]]], "R": [[["cmd", "#"], ["cm", " Summarize sales by region and fit a model"]], [["var", "library"], ["punc", "("], ["bi", "dplyr"], ["punc", ")"]], [], [["var", "sales"], ["p", " "], ["op", "<-"], ["p", " "], ["fnc", "read.csv"], ["punc", "("], ["str", "\"sales.csv\""], ["punc", ","], ["p", " "], ["prop", "stringsAsFactors"], ["p", " "], ["op", "="], ["p", " "], ["con", "FALSE"], ["punc", ")"]], [["var", "regions"], ["p", " "], ["op", "<-"], ["p", " "], ["bi", "c"], ["punc", "("], ["str", "\"North\""], ["punc", ","], ["p", " "], ["str", "\"South\""], ["punc", ","], ["p", " "], ["str", "\"East\""], ["punc", ","], ["p", " "], ["str", "\"West\""], ["punc", ")"]], [], [["cmd", "#"], ["cm", " Compute mean revenue per region"]], [["fnd", "summarize_region"], ["p", " "], ["op", "<-"], ["p", " "], ["kw", "function"], ["punc", "("], ["var", "df"], ["punc", ","], ["p", " "], ["var", "reg"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["var", "subset"], ["p", " "], ["op", "<-"], ["p", " "], ["var", "df"], ["punc", "["], ["var", "df"], ["op", "$"], ["prop", "region"], ["p", " "], ["op", "=="], ["p", " "], ["var", "reg"], ["punc", ","], ["p", " "], ["punc", "]"]], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["fnc", "nrow"], ["punc", "("], ["var", "subset"], ["punc", ")"], ["p", " "], ["op", "=="], ["p", " "], ["num", "0"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "return"], ["punc", "("], ["con", "NA"], ["punc", ")"]], [["p", " "], ["punc", "}"]], [["p", " "], ["fnc", "mean"], ["punc", "("], ["var", "subset"], ["op", "$"], ["prop", "revenue"], ["punc", ","], ["p", " "], ["prop", "na.rm"], ["p", " "], ["op", "="], ["p", " "], ["con", "TRUE"], ["punc", ")"]], [["punc", "}"]], [], [["var", "means"], ["p", " "], ["op", "<-"], ["p", " "], ["fnc", "sapply"], ["punc", "("], ["var", "regions"], ["punc", ","], ["p", " "], ["kw", "function"], ["punc", "("], ["var", "r"], ["punc", ")"], ["p", " "], ["fnc", "summarize_region"], ["punc", "("], ["var", "sales"], ["punc", ","], ["p", " "], ["var", "r"], ["punc", ")"], ["punc", ")"]], [["var", "sales"], ["p", " "], ["op", "%>%"], ["p", " "], ["fnc", "filter"], ["punc", "("], ["prop", "revenue"], ["p", " "], ["op", ">"], ["p", " "], ["num", "1000"], ["punc", ")"], ["p", " "], ["op", "%>%"], ["p", " "], ["fnc", "head"], ["punc", "("], ["num", "5"], ["punc", ")"]], [], [["var", "model"], ["p", " "], ["op", "<-"], ["p", " "], ["fnc", "lm"], ["punc", "("], ["prop", "revenue"], ["p", " "], ["op", "~"], ["p", " "], ["prop", "units"], ["p", " "], ["op", "+"], ["p", " "], ["prop", "region"], ["punc", ","], ["p", " "], ["prop", "data"], ["p", " "], ["op", "="], ["p", " "], ["var", "sales"], ["punc", ")"]], [["fnc", "print"], ["punc", "("], ["fnc", "summary"], ["punc", "("], ["var", "model"], ["punc", ")"], ["punc", ")"]]], "Erlang": [[["cmd", "%"], ["cm", " Bank account server with pattern matching"]], [["pp", "-module"], ["punc", "("], ["ty", "bank"], ["punc", ")."]], [["pp", "-export"], ["punc", "(["], ["fnc", "start"], ["op", "/"], ["num", "0"], ["punc", ","], ["p", " "], ["fnc", "balance"], ["op", "/"], ["num", "1"], ["punc", "])"], ["punc", "."]], [], [["fnd", "start"], ["punc", "()"], ["p", " "], ["op", "->"]], [["p", " "], ["fnc", "spawn"], ["punc", "("], ["kw", "fun"], ["punc", "()"], ["p", " "], ["op", "->"], ["p", " "], ["fnc", "loop"], ["punc", "("], ["num", "0"], ["punc", ")"], ["p", " "], ["kw", "end"], ["punc", ")."]], [], [["fnd", "loop"], ["punc", "("], ["var", "Balance"], ["punc", ")"], ["p", " "], ["op", "->"]], [["p", " "], ["kw", "receive"]], [["p", " "], ["punc", "{"], ["con", "deposit"], ["punc", ","], ["p", " "], ["var", "Amount"], ["punc", "}"], ["p", " "], ["kw", "when"], ["p", " "], ["var", "Amount"], ["p", " "], ["op", ">"], ["p", " "], ["num", "0"], ["p", " "], ["op", "->"]], [["p", " "], ["fnc", "loop"], ["punc", "("], ["var", "Balance"], ["p", " "], ["op", "+"], ["p", " "], ["var", "Amount"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["punc", "{"], ["con", "withdraw"], ["punc", ","], ["p", " "], ["var", "Amount"], ["punc", "}"], ["p", " "], ["op", "->"]], [["p", " "], ["fnc", "loop"], ["punc", "("], ["var", "Balance"], ["p", " "], ["op", "-"], ["p", " "], ["var", "Amount"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["punc", "{"], ["con", "balance"], ["punc", ","], ["p", " "], ["var", "From"], ["punc", "}"], ["p", " "], ["op", "->"]], [["p", " "], ["var", "From"], ["p", " "], ["op", "!"], ["p", " "], ["punc", "{"], ["con", "ok"], ["punc", ","], ["p", " "], ["var", "Balance"], ["punc", "}"], ["punc", ","], ["p", " "], ["fnc", "loop"], ["punc", "("], ["var", "Balance"], ["punc", ")"]], [["p", " "], ["kw", "end"], ["punc", "."]], [], [["fnd", "balance"], ["punc", "("], ["var", "Pid"], ["punc", ")"], ["p", " "], ["op", "->"]], [["p", " "], ["var", "Pid"], ["p", " "], ["op", "!"], ["p", " "], ["punc", "{"], ["con", "balance"], ["punc", ","], ["p", " "], ["fnc", "self"], ["punc", "()"], ["punc", "}"], ["punc", ","], ["p", " "], ["kw", "receive"], ["p", " "], ["punc", "{"], ["con", "ok"], ["punc", ","], ["p", " "], ["var", "B"], ["punc", "}"], ["p", " "], ["op", "->"], ["p", " "], ["var", "B"], ["p", " "], ["kw", "end"], ["punc", "."]]], "SQL": [[["cmd", "-- "], ["cm", "Monthly revenue by active customer"]], [["kw", "SELECT"], ["p", " "], ["prop", "c.id"], ["punc", ","], ["p", " "], ["prop", "c.name"], ["punc", ","]], [["p", " "], ["bi", "COUNT"], ["punc", "("], ["prop", "o.id"], ["punc", ")"], ["p", " "], ["kw", "AS"], ["p", " "], ["var", "order_count"], ["punc", ","]], [["p", " "], ["bi", "COALESCE"], ["punc", "("], ["bi", "SUM"], ["punc", "("], ["prop", "o.total"], ["punc", "),"], ["p", " "], ["num", "0"], ["punc", ")"], ["p", " "], ["kw", "AS"], ["p", " "], ["var", "revenue"]], [["kw", "FROM"], ["p", " "], ["prop", "customers"], ["p", " "], ["var", "c"]], [["kw", "JOIN"], ["p", " "], ["prop", "orders"], ["p", " "], ["var", "o"], ["p", " "], ["kw", "ON"], ["p", " "], ["prop", "o.customer_id"], ["p", " "], ["op", "="], ["p", " "], ["prop", "c.id"]], [["kw", "WHERE"], ["p", " "], ["prop", "c.active"], ["p", " "], ["op", "="], ["p", " "], ["con", "TRUE"]], [["p", " "], ["kw", "AND"], ["p", " "], ["prop", "o.created_at"], ["p", " "], ["op", ">="], ["p", " "], ["str", "'2024-01-01'"]], [["p", " "], ["kw", "AND"], ["p", " "], ["prop", "o.status"], ["p", " "], ["op", "<>"], ["p", " "], ["con", "NULL"]], [["kw", "GROUP BY"], ["p", " "], ["prop", "c.id"], ["punc", ","], ["p", " "], ["prop", "c.name"]], [["kw", "HAVING"], ["p", " "], ["bi", "COUNT"], ["punc", "("], ["prop", "o.id"], ["punc", ")"], ["p", " "], ["op", ">"], ["p", " "], ["num", "5"]], [["kw", "ORDER BY"], ["p", " "], ["var", "revenue"], ["p", " "], ["kw", "DESC"]], [["kw", "LIMIT"], ["p", " "], ["num", "25"], ["punc", ";"]], [], [["cmd", "-- "], ["cm", "Flag stale accounts for review"]], [["kw", "UPDATE"], ["p", " "], ["prop", "customers"]], [["kw", "SET"], ["p", " "], ["prop", "status"], ["p", " "], ["op", "="], ["p", " "], ["str", "'dormant'"]], [["kw", "WHERE"], ["p", " "], ["prop", "last_login"], ["p", " "], ["op", "<"], ["p", " "], ["bi", "CURRENT_DATE"], ["p", " "], ["op", "-"], ["p", " "], ["kw", "INTERVAL"], ["p", " "], ["str", "'90 days'"]], [["p", " "], ["kw", "AND"], ["p", " "], ["prop", "active"], ["p", " "], ["op", "="], ["p", " "], ["con", "FALSE"], ["punc", ";"]]], "PHP": [[["pp", "<?php"]], [["kw", "namespace"], ["p", " "], ["ty", "App\\Service"], ["punc", ";"]], [], [["cmd", "/** "], ["doc", "Computes invoice totals. */"]], [["dec", "#[Service]"]], [["kw", "class"], ["p", " "], ["ty", "InvoiceCalculator"]], [["punc", "{"]], [["p", " "], ["kw", "public"], ["p", " "], ["ty", "float"], ["p", " "], ["var", "$taxRate"], ["p", " "], ["op", "="], ["p", " "], ["num", "0.0825"], ["punc", ";"]], [], [["p", " "], ["kw", "public"], ["p", " "], ["kw", "function"], ["p", " "], ["fnd", "total"], ["punc", "("], ["kw", "array"], ["p", " "], ["var", "$items"], ["punc", ")"], ["op", ":"], ["p", " "], ["ty", "float"]], [["p", " "], ["punc", "{"]], [["p", " "], ["cmd", "// "], ["cm", "sum each line item"]], [["p", " "], ["var", "$prices"], ["p", " "], ["op", "="], ["p", " "], ["bi", "array_map"], ["punc", "("], ["kw", "fn"], ["punc", "("], ["var", "$i"], ["punc", ")"], ["p", " "], ["op", "=>"], ["p", " "], ["var", "$i"], ["op", "["], ["str", "'price'"], ["op", "]"], ["punc", ","], ["p", " "], ["var", "$items"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["var", "$subtotal"], ["p", " "], ["op", "="], ["p", " "], ["bi", "array_sum"], ["punc", "("], ["var", "$prices"], ["punc", ")"], ["punc", ";"]], [], [["p", " "], ["kw", "if"], ["p", " "], ["punc", "("], ["var", "$subtotal"], ["p", " "], ["op", "==="], ["p", " "], ["num", "0"], ["punc", ")"], ["p", " "], ["punc", "{"]], [["p", " "], ["kw", "return"], ["p", " "], ["num", "0.0"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [], [["p", " "], ["var", "$total"], ["p", " "], ["op", "="], ["p", " "], ["var", "$subtotal"], ["p", " "], ["op", "*"], ["p", " "], ["punc", "("], ["num", "1"], ["p", " "], ["op", "+"], ["p", " "], ["var", "$this"], ["op", "->"], ["prop", "taxRate"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["fnc", "printf"], ["punc", "("], ["str", "\"Total: %.2f\\n\""], ["punc", ","], ["p", " "], ["var", "$total"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["kw", "return"], ["p", " "], ["var", "$total"], ["punc", ";"]], [["p", " "], ["punc", "}"]], [["punc", "}"]]], "Ada": [[["cmd", "-- "], ["cm", "Compute factorial and print the result"]], [["pp", "with"], ["p", " "], ["var", "Ada.Text_IO"], ["punc", ";"]], [["pp", "use"], ["p", " "], ["var", "Ada.Text_IO"], ["punc", ";"]], [], [["kw", "procedure"], ["p", " "], ["fnd", "Factorial_Demo"], ["p", " "], ["kw", "is"]], [["p", " "], ["var", "N"], ["p", " "], ["punc", ":"], ["p", " "], ["ty", "Integer"], ["p", " "], ["op", ":="], ["p", " "], ["num", "5"], ["punc", ";"]], [["p", " "], ["var", "Result"], ["p", " "], ["punc", ":"], ["p", " "], ["ty", "Integer"], ["p", " "], ["op", ":="], ["p", " "], ["num", "1"], ["punc", ";"]], [["kw", "begin"]], [["p", " "], ["kw", "for"], ["p", " "], ["var", "I"], ["p", " "], ["kw", "in"], ["p", " "], ["num", "1"], ["p", " "], ["op", ".."], ["p", " "], ["var", "N"], ["p", " "], ["kw", "loop"]], [["p", " "], ["var", "Result"], ["p", " "], ["op", ":="], ["p", " "], ["var", "Result"], ["p", " "], ["op", "*"], ["p", " "], ["var", "I"], ["punc", ";"]], [["p", " "], ["kw", "end"], ["p", " "], ["kw", "loop"], ["punc", ";"]], [], [["p", " "], ["kw", "if"], ["p", " "], ["var", "Result"], ["p", " "], ["op", ">"], ["p", " "], ["num", "0"], ["p", " "], ["kw", "then"]], [["p", " "], ["bi", "Put_Line"], ["punc", "("], ["str", "\"Factorial = \""], ["p", " "], ["op", "&"], ["p", " "], ["var", "Integer"], ["punc", "'"], ["var", "Image"], ["punc", "("], ["var", "Result"], ["punc", "))"], ["punc", ";"]], [["p", " "], ["kw", "end"], ["p", " "], ["kw", "if"], ["punc", ";"]], [["kw", "end"], ["p", " "], ["fnd", "Factorial_Demo"], ["punc", ";"]]], "Fortran": [[["cmd", "! "], ["cm", "Sum the elements of an array"]], [["kw", "program"], ["p", " "], ["fnd", "array_sum"]], [["p", " "], ["kw", "implicit none"]], [["p", " "], ["ty", "integer"], ["p", " "], ["punc", "::"], ["p", " "], ["var", "i"], ["punc", ","], ["p", " "], ["var", "n"]], [["p", " "], ["ty", "real"], ["punc", "("], ["var", "kind"], ["op", "="], ["num", "8"], ["punc", ")"], ["p", " "], ["punc", "::"], ["p", " "], ["var", "total"]], [["p", " "], ["ty", "real"], ["punc", "("], ["var", "kind"], ["op", "="], ["num", "8"], ["punc", ")"], ["punc", ","], ["p", " "], ["kw", "dimension"], ["punc", "("], ["num", "5"], ["punc", ")"], ["p", " "], ["punc", "::"], ["p", " "], ["var", "a"]], [], [["p", " "], ["var", "n"], ["p", " "], ["op", "="], ["p", " "], ["num", "5"]], [["p", " "], ["var", "total"], ["p", " "], ["op", "="], ["p", " "], ["num", "0.0"]], [["p", " "], ["var", "a"], ["p", " "], ["op", "="], ["p", " "], ["punc", "["], ["num", "1.0"], ["punc", ","], ["p", " "], ["num", "2.0"], ["punc", ","], ["p", " "], ["num", "3.0"], ["punc", ","], ["p", " "], ["num", "4.0"], ["punc", ","], ["p", " "], ["num", "5.0"], ["punc", "]"]], [], [["p", " "], ["kw", "do"], ["p", " "], ["var", "i"], ["p", " "], ["op", "="], ["p", " "], ["num", "1"], ["punc", ","], ["p", " "], ["var", "n"]], [["p", " "], ["var", "total"], ["p", " "], ["op", "="], ["p", " "], ["var", "total"], ["p", " "], ["op", "+"], ["p", " "], ["var", "a"], ["punc", "("], ["var", "i"], ["punc", ")"]], [["p", " "], ["kw", "end do"]], [], [["p", " "], ["bi", "print"], ["p", " "], ["op", "*"], ["punc", ","], ["p", " "], ["str", "\"Sum = \""], ["punc", ","], ["p", " "], ["var", "total"]], [["kw", "end program"], ["p", " "], ["fnd", "array_sum"]]], "MATLAB": [[["cmd", "% "], ["cm", "Normalize a vector and report its length"]], [["kw", "function"], ["p", " "], ["var", "out"], ["p", " "], ["op", "="], ["p", " "], ["fnd", "normalize_vec"], ["punc", "("], ["var", "v"], ["punc", ")"]], [["p", " "], ["var", "n"], ["p", " "], ["op", "="], ["p", " "], ["bi", "length"], ["punc", "("], ["var", "v"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["var", "acc"], ["p", " "], ["op", "="], ["p", " "], ["num", "0"], ["punc", ";"]], [], [["p", " "], ["kw", "for"], ["p", " "], ["var", "i"], ["p", " "], ["op", "="], ["p", " "], ["num", "1"], ["op", ":"], ["var", "n"]], [["p", " "], ["var", "acc"], ["p", " "], ["op", "="], ["p", " "], ["var", "acc"], ["p", " "], ["op", "+"], ["p", " "], ["var", "v"], ["punc", "("], ["var", "i"], ["punc", ")"], ["op", "^"], ["num", "2"], ["punc", ";"]], [["p", " "], ["kw", "end"]], [], [["p", " "], ["var", "mag"], ["p", " "], ["op", "="], ["p", " "], ["bi", "sqrt"], ["punc", "("], ["var", "acc"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["kw", "if"], ["p", " "], ["var", "mag"], ["p", " "], ["op", "=="], ["p", " "], ["num", "0"]], [["p", " "], ["var", "out"], ["p", " "], ["op", "="], ["p", " "], ["bi", "zeros"], ["punc", "("], ["bi", "size"], ["punc", "("], ["var", "v"], ["punc", ")"], ["punc", ")"], ["punc", ";"]], [["p", " "], ["kw", "else"]], [["p", " "], ["var", "out"], ["p", " "], ["op", "="], ["p", " "], ["var", "v"], ["p", " "], ["op", "/"], ["p", " "], ["var", "mag"], ["punc", ";"]], [["p", " "], ["kw", "end"]], [], [["p", " "], ["bi", "disp"], ["punc", "("], ["str", "\"vector length:\""], ["punc", ")"], ["punc", ";"]], [["p", " "], ["bi", "disp"], ["punc", "("], ["var", "n"], ["punc", ")"], ["punc", ";"]], [["kw", "end"]]], "Assembly": [[["cmd", ";"], ["cm", " print a greeting via the write syscall"]], [["pp", "section"], ["p", " "], ["pp", ".data"]], [["p", " "], ["var", "msg"], ["p", " "], ["pp", "db"], ["p", " "], ["str", "\"Hello, world!\""], ["punc", ","], ["p", " "], ["num", "0xA"]], [["p", " "], ["con", "msglen"], ["p", " "], ["pp", "equ"], ["p", " "], ["var", "$"], ["p", " "], ["op", "-"], ["p", " "], ["var", "msg"]], [], [["pp", "section"], ["p", " "], ["pp", ".text"]], [["p", " "], ["bi", "global"], ["p", " "], ["fnc", "_start"]], [], [["fnd", "_start"], ["punc", ":"]], [["p", " "], ["kw", "mov"], ["p", " "], ["var", "rax"], ["punc", ","], ["p", " "], ["num", "1"], ["p", " "], ["cmd", ";"], ["cm", " sys_write"]], [["p", " "], ["kw", "mov"], ["p", " "], ["var", "rdi"], ["punc", ","], ["p", " "], ["num", "1"], ["p", " "], ["cmd", ";"], ["cm", " stdout"]], [["p", " "], ["kw", "lea"], ["p", " "], ["var", "rsi"], ["punc", ","], ["p", " "], ["punc", "["], ["var", "rel"], ["p", " "], ["var", "msg"], ["punc", "]"]], [["p", " "], ["kw", "mov"], ["p", " "], ["var", "rdx"], ["punc", ","], ["p", " "], ["con", "msglen"]], [["p", " "], ["kw", "syscall"]], [], [["p", " "], ["kw", "mov"], ["p", " "], ["var", "rax"], ["punc", ","], ["p", " "], ["num", "60"], ["p", " "], ["cmd", ";"], ["cm", " sys_exit"]], [["p", " "], ["kw", "xor"], ["p", " "], ["var", "rdi"], ["punc", ","], ["p", " "], ["var", "rdi"], ["p", " "], ["cmd", ";"], ["cm", " status 0"]], [["p", " "], ["kw", "syscall"]]]}, CATS=[["bg", "bg (ground)", "Aa Bb 123"], ["p", "fg", "other / whitespace"], ["kw", "keyword", "class def if return"], ["bi", "builtin", "len echo printf"], ["pp", "preprocessor", "#include #define"], ["fnd", "function \u00b7 def", "resolve push"], ["fnc", "function \u00b7 call", "printf rsync get"], ["dec", "decorator \u2192 type", "@dataclass"], ["ty", "type / class", "int str Order Queue"], ["prop", "property / field", "id name items"], ["con", "constant", "None nil NULL true"], ["num", "number", "8080 100 -1"], ["str", "string", "\"dupre\" \"fmt\""], ["esc", "escape", "\\n \\t"], ["re", "regexp", "/^#[0-9a-f]+/"], ["doc", "docstring", "\"\"\"...\"\"\""], ["cm", "comment", "# reject nil"], ["cmd", "comment delim", "# // ;;"], ["var", "variable / use", "value key self"], ["op", "operator", ": = -> =="], ["punc", "punctuation", "{ } ( ) ;"]], UI_FACES=[["cursor", "cursor", "Aa|"], ["region", "region (selection)", "selected text"], ["hl-line", "hl-line (current line)", "current line"], ["highlight", "highlight", "hover"], ["mode-line", "mode-line", "status active"], ["mode-line-highlight", "mode-line-highlight (hover)", "git:main"], ["mode-line-inactive", "mode-line-inactive", "status idle"], ["fringe", "fringe", "| |"], ["line-number", "line-number", " 42"], ["line-number-current-line", "line-number-current-line", "> 42"], ["minibuffer-prompt", "minibuffer-prompt", "M-x "], ["isearch", "isearch (match)", "match"], ["lazy-highlight", "lazy-highlight", "other match"], ["isearch-fail", "isearch-fail", "no match"], ["show-paren-match", "show-paren-match", "( )"], ["show-paren-mismatch", "show-paren-mismatch", ") ("], ["link", "link", "https://"], ["error", "error", "error!"], ["warning", "warning", "warning"], ["success", "success", "ok"], ["vertical-border", "vertical-border", "|"]], APPS={"org-mode": {"label": "org-mode", "preview": "org", "faces": [["org-document-title", "document title", {}], ["org-document-info", "document info", {}], ["org-document-info-keyword", "document info keyword", {}], ["org-level-1", "level 1", {}], ["org-level-2", "level 2", {}], ["org-level-3", "level 3", {}], ["org-level-4", "level 4", {}], ["org-level-5", "level 5", {}], ["org-level-6", "level 6", {}], ["org-level-7", "level 7", {}], ["org-level-8", "level 8", {}], ["org-headline-todo", "headline todo", {}], ["org-headline-done", "headline done", {}], ["org-todo", "todo", {}], ["org-done", "done", {}], ["org-priority", "priority", {}], ["org-tag", "tag", {}], ["org-tag-group", "tag group", {}], ["org-special-keyword", "special keyword", {}], ["org-drawer", "drawer", {}], ["org-property-value", "property value", {}], ["org-checkbox", "checkbox", {}], ["org-checkbox-statistics-todo", "checkbox statistics todo", {}], ["org-checkbox-statistics-done", "checkbox statistics done", {}], ["org-warning", "warning", {}], ["org-link", "link", {}], ["org-footnote", "footnote", {}], ["org-date", "date", {}], ["org-sexp-date", "sexp date", {}], ["org-date-selected", "date selected", {}], ["org-target", "target", {}], ["org-macro", "macro", {}], ["org-cite", "cite", {}], ["org-cite-key", "cite key", {}], ["org-block", "block", {}], ["org-block-begin-line", "block begin line", {}], ["org-block-end-line", "block end line", {}], ["org-code", "code", {}], ["org-verbatim", "verbatim", {}], ["org-inline-src-block", "inline src block", {}], ["org-quote", "quote", {}], ["org-verse", "verse", {}], ["org-latex-and-related", "latex and related", {}], ["org-table", "table", {}], ["org-table-header", "table header", {}], ["org-table-row", "table row", {}], ["org-formula", "formula", {}], ["org-column", "column", {}], ["org-column-title", "column title", {}], ["org-list-dt", "list dt", {}], ["org-meta-line", "meta line", {}], ["org-ellipsis", "ellipsis", {}], ["org-hide", "hide", {}], ["org-indent", "indent", {}], ["org-archived", "archived", {}], ["org-default", "default", {}], ["org-dispatcher-highlight", "dispatcher highlight", {}], ["org-agenda-structure", "agenda structure", {}], ["org-agenda-structure-secondary", "agenda structure secondary", {}], ["org-agenda-structure-filter", "agenda structure filter", {}], ["org-agenda-date", "agenda date", {}], ["org-agenda-date-today", "agenda date today", {}], ["org-agenda-date-weekend", "agenda date weekend", {}], ["org-agenda-date-weekend-today", "agenda date weekend today", {}], ["org-agenda-current-time", "agenda current time", {}], ["org-agenda-done", "agenda done", {}], ["org-agenda-dimmed-todo-face", "agenda dimmed todo", {}], ["org-agenda-calendar-event", "agenda calendar event", {}], ["org-agenda-calendar-sexp", "agenda calendar sexp", {}], ["org-agenda-calendar-daterange", "agenda calendar daterange", {}], ["org-agenda-diary", "agenda diary", {}], ["org-agenda-clocking", "agenda clocking", {}], ["org-agenda-column-dateline", "agenda column dateline", {}], ["org-agenda-restriction-lock", "agenda restriction lock", {}], ["org-agenda-filter-category", "agenda filter category", {}], ["org-agenda-filter-effort", "agenda filter effort", {}], ["org-agenda-filter-regexp", "agenda filter regexp", {}], ["org-agenda-filter-tags", "agenda filter tags", {}], ["org-scheduled", "scheduled", {}], ["org-scheduled-today", "scheduled today", {}], ["org-scheduled-previously", "scheduled previously", {}], ["org-upcoming-deadline", "upcoming deadline", {}], ["org-upcoming-distant-deadline", "upcoming distant deadline", {}], ["org-imminent-deadline", "imminent deadline", {}], ["org-time-grid", "time grid", {}], ["org-clock-overlay", "clock overlay", {}], ["org-mode-line-clock", "mode line clock", {}], ["org-mode-line-clock-overrun", "mode line clock overrun", {}]]}, "magit": {"label": "magit", "preview": "magit", "faces": [["magit-section-heading", "section heading", {"fg": "#8b6508", "weight": "bold", "extend": true}], ["magit-section-secondary-heading", "section secondary heading", {"weight": "bold", "extend": true}], ["magit-section-heading-selection", "section heading selection", {"fg": "#8b4c39", "extend": true}], ["magit-section-highlight", "section highlight", {"bg": "#f2f2f2", "extend": true}], ["magit-section-child-count", "section child count", {}], ["magit-diff-added", "diff added", {"fg": "#22aa22", "bg": "#ddffdd", "extend": true}], ["magit-diff-added-highlight", "diff added highlight", {"fg": "#22aa22", "bg": "#cceecc", "extend": true}], ["magit-diff-removed", "diff removed", {"fg": "#aa2222", "bg": "#ffdddd", "extend": true}], ["magit-diff-removed-highlight", "diff removed highlight", {"fg": "#aa2222", "bg": "#eecccc", "extend": true}], ["magit-diff-context", "diff context", {"fg": "#7f7f7f", "extend": true}], ["magit-diff-context-highlight", "diff context highlight", {"fg": "#7f7f7f", "bg": "#f2f2f2", "extend": true}], ["magit-diff-file-heading", "diff file heading", {"weight": "bold", "extend": true}], ["magit-diff-file-heading-highlight", "diff file heading highlight", {"extend": true, "inherit": "magit-section-highlight"}], ["magit-diff-file-heading-selection", "diff file heading selection", {"fg": "#8b4c39", "extend": true, "inherit": "magit-diff-file-heading-highlight"}], ["magit-diff-hunk-heading", "diff hunk heading", {"fg": "#333333", "bg": "#e5e5e5", "extend": true}], ["magit-diff-hunk-heading-highlight", "diff hunk heading highlight", {"fg": "#333333", "bg": "#cccccc", "extend": true}], ["magit-diff-hunk-heading-selection", "diff hunk heading selection", {"fg": "#8b4c39", "extend": true, "inherit": "magit-diff-hunk-heading-highlight"}], ["magit-diff-hunk-region", "diff hunk region", {"inherit": "bold"}], ["magit-diff-lines-heading", "diff lines heading", {"bg": "#cd8162", "extend": true, "inherit": "magit-diff-hunk-heading-highlight"}], ["magit-diff-lines-boundary", "diff lines boundary", {"extend": true, "inherit": "magit-diff-lines-heading"}], ["magit-diff-base", "diff base", {"fg": "#aaaa11", "bg": "#ffffcc", "extend": true}], ["magit-diff-base-highlight", "diff base highlight", {"fg": "#aaaa11", "bg": "#eeeebb", "extend": true}], ["magit-diff-our", "diff our", {"inherit": "magit-diff-removed"}], ["magit-diff-our-highlight", "diff our highlight", {"inherit": "magit-diff-removed-highlight"}], ["magit-diff-their", "diff their", {"inherit": "magit-diff-added"}], ["magit-diff-their-highlight", "diff their highlight", {"inherit": "magit-diff-added-highlight"}], ["magit-diff-conflict-heading", "diff conflict heading", {"inherit": "magit-diff-hunk-heading"}], ["magit-diff-conflict-heading-highlight", "diff conflict heading highlight", {"inherit": "magit-diff-hunk-heading-highlight"}], ["magit-diff-revision-summary", "diff revision summary", {"inherit": "magit-diff-hunk-heading"}], ["magit-diff-revision-summary-highlight", "diff revision summary highlight", {"inherit": "magit-diff-hunk-heading-highlight"}], ["magit-diff-whitespace-warning", "diff whitespace warning", {"inherit": "trailing-whitespace"}], ["magit-diffstat-added", "diffstat added", {"fg": "#22aa22"}], ["magit-diffstat-removed", "diffstat removed", {"fg": "#aa2222"}], ["magit-branch-current", "branch current", {"inherit": "magit-branch-local"}], ["magit-branch-local", "branch local", {"fg": "#4a708b"}], ["magit-branch-remote", "branch remote", {"fg": "#6e8b3d"}], ["magit-branch-remote-head", "branch remote head", {"inherit": "magit-branch-remote"}], ["magit-branch-upstream", "branch upstream", {"slant": "italic"}], ["magit-branch-warning", "branch warning", {"inherit": "warning"}], ["magit-head", "head", {"inherit": "magit-branch-local"}], ["magit-tag", "tag", {"fg": "#8b6914"}], ["magit-hash", "hash", {"fg": "#999999"}], ["magit-filename", "filename", {}], ["magit-dimmed", "dimmed", {"fg": "#7f7f7f"}], ["magit-keyword", "keyword", {"inherit": "font-lock-string-face"}], ["magit-keyword-squash", "keyword squash", {"inherit": "font-lock-warning-face"}], ["magit-refname", "refname", {"fg": "#4d4d4d"}], ["magit-refname-stash", "refname stash", {"inherit": "magit-refname"}], ["magit-refname-wip", "refname wip", {"inherit": "magit-refname"}], ["magit-refname-pullreq", "refname pullreq", {"inherit": "magit-refname"}], ["magit-log-author", "log author", {"fg": "#b22222"}], ["magit-log-date", "log date", {"fg": "#4d4d4d"}], ["magit-log-graph", "log graph", {"fg": "#4d4d4d"}], ["magit-header-line", "header line", {"inherit": "magit-section-heading"}], ["magit-header-line-key", "header line key", {"inherit": "font-lock-builtin-face"}], ["magit-header-line-log-select", "header line log select", {"inherit": "bold"}], ["magit-process-ok", "process ok", {"fg": "#00ff00", "inherit": "magit-section-heading"}], ["magit-process-ng", "process ng", {"fg": "#ff0000", "inherit": "magit-section-heading"}], ["magit-mode-line-process", "mode line process", {"inherit": "mode-line-emphasis"}], ["magit-mode-line-process-error", "mode line process error", {"inherit": "error"}], ["magit-bisect-good", "bisect good", {"fg": "#556b2f"}], ["magit-bisect-bad", "bisect bad", {"fg": "#8b3a3a"}], ["magit-bisect-skip", "bisect skip", {"fg": "#b8860b"}], ["magit-blame-heading", "blame heading", {"extend": true, "inherit": "magit-blame-highlight"}], ["magit-blame-highlight", "blame highlight", {"fg": "#000000", "bg": "#cccccc", "extend": true}], ["magit-blame-hash", "blame hash", {}], ["magit-blame-name", "blame name", {}], ["magit-blame-date", "blame date", {}], ["magit-blame-summary", "blame summary", {}], ["magit-blame-dimmed", "blame dimmed", {"inherit": "magit-dimmed"}], ["magit-blame-margin", "blame margin", {"inherit": "magit-blame-highlight"}], ["magit-cherry-equivalent", "cherry equivalent", {"fg": "#ff00ff"}], ["magit-cherry-unmatched", "cherry unmatched", {"fg": "#00ffff"}], ["magit-signature-good", "signature good", {"fg": "#00ff00"}], ["magit-signature-bad", "signature bad", {"fg": "#ff0000", "weight": "bold"}], ["magit-signature-untrusted", "signature untrusted", {"fg": "#66cdaa"}], ["magit-signature-expired", "signature expired", {"fg": "#ffa500"}], ["magit-signature-expired-key", "signature expired key", {"inherit": "magit-signature-expired"}], ["magit-signature-revoked", "signature revoked", {"fg": "#d02090"}], ["magit-signature-error", "signature error", {"fg": "#add8e6"}], ["magit-reflog-commit", "reflog commit", {"fg": "#00ff00"}], ["magit-reflog-amend", "reflog amend", {"fg": "#ff00ff"}], ["magit-reflog-merge", "reflog merge", {"fg": "#00ff00"}], ["magit-reflog-checkout", "reflog checkout", {"fg": "#0000ff"}], ["magit-reflog-reset", "reflog reset", {"fg": "#ff0000"}], ["magit-reflog-rebase", "reflog rebase", {"fg": "#ff00ff"}], ["magit-reflog-cherry-pick", "reflog cherry pick", {"fg": "#00ff00"}], ["magit-reflog-remote", "reflog remote", {"fg": "#00ffff"}], ["magit-reflog-other", "reflog other", {"fg": "#00ffff"}], ["magit-sequence-pick", "sequence pick", {"inherit": "default"}], ["magit-sequence-stop", "sequence stop", {"fg": "#6e8b3d"}], ["magit-sequence-part", "sequence part", {"fg": "#8b6914"}], ["magit-sequence-head", "sequence head", {"fg": "#4a708b"}], ["magit-sequence-drop", "sequence drop", {"fg": "#cd5c5c"}], ["magit-sequence-done", "sequence done", {"inherit": "magit-hash"}], ["magit-sequence-onto", "sequence onto", {"inherit": "magit-sequence-done"}], ["magit-sequence-exec", "sequence exec", {"inherit": "magit-hash"}], ["magit-left-margin", "left margin", {"inherit": "default"}], ["git-commit-comment-action", "git commit comment action", {"inherit": "bold"}], ["git-commit-comment-branch-local", "git commit comment branch local", {"inherit": "magit-branch-local"}], ["git-commit-comment-branch-remote", "git commit comment branch remote", {"inherit": "magit-branch-remote"}], ["git-commit-comment-detached", "git commit comment detached", {"inherit": "git-commit-comment-branch-local"}], ["git-commit-comment-file", "git commit comment file", {"inherit": "git-commit-trailer-value"}], ["git-commit-comment-heading", "git commit comment heading", {"inherit": "git-commit-trailer-token"}], ["git-commit-keyword", "git commit keyword", {"inherit": "font-lock-string-face"}], ["git-commit-nonempty-second-line", "git commit nonempty second line", {"inherit": "font-lock-warning-face"}], ["git-commit-overlong-summary", "git commit overlong summary", {"inherit": "font-lock-warning-face"}], ["git-commit-summary", "git commit summary", {"inherit": "font-lock-type-face"}], ["git-commit-trailer-token", "git commit trailer token", {"inherit": "font-lock-keyword-face"}], ["git-commit-trailer-value", "git commit trailer value", {"inherit": "font-lock-string-face"}]]}, "elfeed": {"label": "elfeed", "preview": "elfeed", "faces": [["elfeed-search-date-face", "search date", {"fg": "#aaa"}], ["elfeed-search-title-face", "search title", {"fg": "#000"}], ["elfeed-search-unread-title-face", "search unread title", {"weight": "bold"}], ["elfeed-search-feed-face", "search feed", {"fg": "#aa0"}], ["elfeed-search-tag-face", "search tag", {"fg": "#070"}], ["elfeed-search-unread-count-face", "search unread count", {"fg": "#000"}], ["elfeed-search-filter-face", "search filter", {"inherit": "mode-line-buffer-id"}], ["elfeed-search-last-update-face", "search last update", {}], ["elfeed-log-date-face", "log date", {"inherit": "font-lock-type-face"}], ["elfeed-log-error-level-face", "log error level", {"fg": "#ff0000"}], ["elfeed-log-warn-level-face", "log warn level", {"fg": "#daa520"}], ["elfeed-log-info-level-face", "log info level", {"fg": "#00bfff"}], ["elfeed-log-debug-level-face", "log debug level", {"fg": "#ee00ee"}]]}, "mu4e": {"label": "mu4e", "preview": "mu4e", "faces": [["mu4e-title-face", "title", {}], ["mu4e-context-face", "context", {}], ["mu4e-modeline-face", "modeline", {}], ["mu4e-ok-face", "ok", {}], ["mu4e-warning-face", "warning", {}], ["mu4e-header-title-face", "header title", {}], ["mu4e-header-key-face", "header key", {}], ["mu4e-header-value-face", "header value", {}], ["mu4e-header-face", "header", {}], ["mu4e-header-highlight-face", "header highlight", {}], ["mu4e-header-marks-face", "header marks", {}], ["mu4e-unread-face", "unread", {}], ["mu4e-flagged-face", "flagged", {}], ["mu4e-replied-face", "replied", {}], ["mu4e-forwarded-face", "forwarded", {}], ["mu4e-draft-face", "draft", {}], ["mu4e-trashed-face", "trashed", {}], ["mu4e-related-face", "related", {}], ["mu4e-contact-face", "contact", {}], ["mu4e-special-header-value-face", "special header value", {}], ["mu4e-url-number-face", "url number", {}], ["mu4e-link-face", "link", {}], ["mu4e-footer-face", "footer", {}], ["mu4e-region-code", "region code", {}], ["mu4e-system-face", "system", {}], ["mu4e-highlight-face", "highlight", {}], ["mu4e-compose-separator-face", "compose separator", {}]]}, "gnus": {"label": "gnus (mu4e article view)", "preview": "gnus", "faces": [["gnus-header-name", "header name", {}], ["gnus-header-from", "header from", {}], ["gnus-header-subject", "header subject", {}], ["gnus-header-content", "header content", {}], ["gnus-header-newsgroups", "header newsgroups", {}], ["gnus-cite-1", "cite 1", {}], ["gnus-cite-2", "cite 2", {}], ["gnus-cite-3", "cite 3", {}], ["gnus-cite-4", "cite 4", {}], ["gnus-cite-5", "cite 5", {}], ["gnus-cite-6", "cite 6", {}], ["gnus-cite-7", "cite 7", {}], ["gnus-cite-8", "cite 8", {}], ["gnus-cite-9", "cite 9", {}], ["gnus-cite-10", "cite 10", {}], ["gnus-cite-11", "cite 11", {}], ["gnus-cite-attribution", "cite attribution", {}], ["gnus-signature", "signature", {}], ["gnus-button", "button", {}], ["gnus-emphasis-bold", "emphasis bold", {}], ["gnus-emphasis-italic", "emphasis italic", {}], ["gnus-emphasis-underline", "emphasis underline", {}], ["gnus-emphasis-strikethru", "emphasis strikethru", {}], ["gnus-emphasis-highlight-words", "emphasis highlight words", {}]]}, "org-faces": {"label": "org-faces", "preview": "orgfaces", "faces": [["org-faces-todo", "todo", {}], ["org-faces-project", "project", {}], ["org-faces-doing", "doing", {}], ["org-faces-waiting", "waiting", {}], ["org-faces-verify", "verify", {}], ["org-faces-stalled", "stalled", {}], ["org-faces-delegated", "delegated", {}], ["org-faces-failed", "failed", {}], ["org-faces-done", "done", {}], ["org-faces-cancelled", "cancelled", {}], ["org-faces-priority-a", "priority a", {}], ["org-faces-priority-b", "priority b", {}], ["org-faces-priority-c", "priority c", {}], ["org-faces-priority-d", "priority d", {}], ["org-faces-todo-dim", "todo dim", {}], ["org-faces-project-dim", "project dim", {}], ["org-faces-doing-dim", "doing dim", {}], ["org-faces-waiting-dim", "waiting dim", {}], ["org-faces-verify-dim", "verify dim", {}], ["org-faces-stalled-dim", "stalled dim", {}], ["org-faces-delegated-dim", "delegated dim", {}], ["org-faces-failed-dim", "failed dim", {}], ["org-faces-done-dim", "done dim", {}], ["org-faces-cancelled-dim", "cancelled dim", {}], ["org-faces-priority-a-dim", "priority a dim", {}], ["org-faces-priority-b-dim", "priority b dim", {}], ["org-faces-priority-c-dim", "priority c dim", {}], ["org-faces-priority-d-dim", "priority d dim", {}]]}, "ghostel": {"label": "ghostel", "preview": "ghostel", "faces": [["ghostel-default", "default", {"inherit": "default"}], ["ghostel-fake-cursor", "fake cursor", {"box": {"style": "line", "width": 1, "color": null}}], ["ghostel-fake-cursor-box", "fake cursor box", {"inherit": "cursor"}], ["ghostel-color-black", "color black", {"inherit": "ansi-color-black"}], ["ghostel-color-red", "color red", {"inherit": "ansi-color-red"}], ["ghostel-color-green", "color green", {"inherit": "ansi-color-green"}], ["ghostel-color-yellow", "color yellow", {"inherit": "ansi-color-yellow"}], ["ghostel-color-blue", "color blue", {"inherit": "ansi-color-blue"}], ["ghostel-color-magenta", "color magenta", {"inherit": "ansi-color-magenta"}], ["ghostel-color-cyan", "color cyan", {"inherit": "ansi-color-cyan"}], ["ghostel-color-white", "color white", {"inherit": "ansi-color-white"}], ["ghostel-color-bright-black", "color bright black", {"inherit": "ansi-color-bright-black"}], ["ghostel-color-bright-red", "color bright red", {"inherit": "ansi-color-bright-red"}], ["ghostel-color-bright-green", "color bright green", {"inherit": "ansi-color-bright-green"}], ["ghostel-color-bright-yellow", "color bright yellow", {"inherit": "ansi-color-bright-yellow"}], ["ghostel-color-bright-blue", "color bright blue", {"inherit": "ansi-color-bright-blue"}], ["ghostel-color-bright-magenta", "color bright magenta", {"inherit": "ansi-color-bright-magenta"}], ["ghostel-color-bright-cyan", "color bright cyan", {"inherit": "ansi-color-bright-cyan"}], ["ghostel-color-bright-white", "color bright white", {"inherit": "ansi-color-bright-white"}]]}, "ansi-color": {"label": "ansi-color (vterm/eshell/compilation/ghostel)", "preview": "ansicolor", "faces": [["ansi-color-black", "black", {}], ["ansi-color-red", "red", {}], ["ansi-color-green", "green", {}], ["ansi-color-yellow", "yellow", {}], ["ansi-color-blue", "blue", {}], ["ansi-color-magenta", "magenta", {}], ["ansi-color-cyan", "cyan", {}], ["ansi-color-white", "white", {}], ["ansi-color-bright-black", "bright black", {}], ["ansi-color-bright-red", "bright red", {}], ["ansi-color-bright-green", "bright green", {}], ["ansi-color-bright-yellow", "bright yellow", {}], ["ansi-color-bright-blue", "bright blue", {}], ["ansi-color-bright-magenta", "bright magenta", {}], ["ansi-color-bright-cyan", "bright cyan", {}], ["ansi-color-bright-white", "bright white", {}]]}, "auto-dim-other-buffers": {"label": "auto-dim", "preview": "autodim", "faces": [["auto-dim-other-buffers", "auto dim other buffers", {}], ["auto-dim-other-buffers-hide", "hide", {}]]}, "dashboard": {"label": "dashboard", "preview": "dashboard", "faces": [["dashboard-banner-logo-title", "banner logo title", {"inherit": "default"}], ["dashboard-text-banner", "text banner", {"inherit": "font-lock-keyword-face"}], ["dashboard-heading", "heading", {"inherit": "font-lock-keyword-face"}], ["dashboard-items-face", "items", {"inherit": "widget-button"}], ["dashboard-navigator", "navigator", {"inherit": "font-lock-keyword-face"}], ["dashboard-no-items-face", "no items", {"inherit": "widget-button"}], ["dashboard-footer-face", "footer", {"inherit": "font-lock-doc-face"}], ["dashboard-footer-icon-face", "footer icon", {"inherit": "dashboard-footer-face"}]]}, "lsp-mode": {"label": "lsp-mode", "preview": "lsp", "faces": [["lsp-signature-face", "signature", {"inherit": "lsp-details-face"}], ["lsp-signature-highlight-function-argument", "signature highlight function argument", {"inherit": "eldoc-highlight-function-argument"}], ["lsp-signature-posframe", "signature posframe", {"inherit": "tooltip"}], ["lsp-face-highlight-read", "face highlight read", {"underline": {"style": "line", "color": null}, "inherit": "highlight"}], ["lsp-face-highlight-write", "face highlight write", {"weight": "bold", "inherit": "highlight"}], ["lsp-face-highlight-textual", "face highlight textual", {"inherit": "highlight"}], ["lsp-face-rename", "face rename", {"underline": {"style": "line", "color": null}}], ["lsp-rename-placeholder-face", "rename placeholder", {"inherit": "font-lock-variable-name-face"}], ["lsp-inlay-hint-face", "inlay hint", {"inherit": "font-lock-comment-face"}], ["lsp-inlay-hint-parameter-face", "inlay hint parameter", {"inherit": "lsp-inlay-hint-face"}], ["lsp-inlay-hint-type-face", "inlay hint type", {"inherit": "lsp-inlay-hint-face"}], ["lsp-details-face", "details", {"inherit": "shadow", "height": 0.8}], ["lsp-installation-buffer-face", "installation buffer", {"fg": "#00ff00"}], ["lsp-installation-finished-buffer-face", "installation finished buffer", {"fg": "#ffa500"}]]}, "git-gutter": {"label": "git-gutter", "preview": "gitgutter", "faces": [["git-gutter:added", "added", {"fg": "#00ff00", "weight": "bold", "inherit": "default"}], ["git-gutter:modified", "modified", {"fg": "#ff00ff", "weight": "bold", "inherit": "default"}], ["git-gutter:deleted", "deleted", {"fg": "#ff0000", "weight": "bold", "inherit": "default"}], ["git-gutter:unchanged", "unchanged", {"bg": "#ffff00", "inherit": "default"}], ["git-gutter:separator", "separator", {"fg": "#00ffff", "weight": "bold", "inherit": "default"}]]}, "flycheck": {"label": "flycheck", "preview": "flycheck", "faces": [["flycheck-error", "error", {"underline": {"style": "line", "color": null}}], ["flycheck-warning", "warning", {"underline": {"style": "line", "color": null}}], ["flycheck-info", "info", {"underline": {"style": "line", "color": null}}], ["flycheck-fringe-error", "fringe error", {"inherit": "error"}], ["flycheck-fringe-warning", "fringe warning", {"inherit": "warning"}], ["flycheck-fringe-info", "fringe info", {"inherit": "success"}], ["flycheck-delimited-error", "delimited error", {}], ["flycheck-error-delimiter", "error delimiter", {}], ["flycheck-error-list-error", "error list error", {"inherit": "error"}], ["flycheck-error-list-warning", "error list warning", {"inherit": "warning"}], ["flycheck-error-list-info", "error list info", {"inherit": "success"}], ["flycheck-error-list-error-message", "error list error message", {}], ["flycheck-error-list-checker-name", "error list checker name", {"inherit": "font-lock-function-name-face"}], ["flycheck-error-list-column-number", "error list column number", {}], ["flycheck-error-list-line-number", "error list line number", {}], ["flycheck-error-list-filename", "error list filename", {"inherit": "mode-line-buffer-id"}], ["flycheck-error-list-id", "error list id", {"inherit": "font-lock-type-face"}], ["flycheck-error-list-id-with-explainer", "error list id with explainer", {"box": {"style": "released", "width": 1, "color": null}, "inherit": "flycheck-error-list-id"}], ["flycheck-error-list-highlight", "error list highlight", {"weight": "bold"}], ["flycheck-verify-select-checker", "verify select checker", {"box": {"style": "released", "width": 1, "color": null}}]]}, "dired": {"label": "dired", "preview": "dired", "faces": [["dired-header", "header", {}], ["dired-directory", "directory", {}], ["dired-symlink", "symlink", {}], ["dired-broken-symlink", "broken symlink", {}], ["dired-special", "special", {}], ["dired-set-id", "set id", {}], ["dired-perm-write", "perm write", {}], ["dired-mark", "mark", {}], ["dired-marked", "marked", {}], ["dired-flagged", "flagged", {}], ["dired-ignored", "ignored", {}], ["dired-warning", "warning", {}]]}, "dirvish": {"label": "dirvish", "preview": "dirvish", "faces": [["dirvish-inactive", "inactive", {"inherit": "shadow"}], ["dirvish-free-space", "free space", {"inherit": "font-lock-constant-face"}], ["dirvish-hl-line", "hl line", {"extend": true, "inherit": "highlight"}], ["dirvish-hl-line-inactive", "hl line inactive", {"extend": true, "inherit": "region"}], ["dirvish-file-modes", "file modes", {"fg": "#6b6b6b"}], ["dirvish-file-link-number", "file link number", {"inherit": "font-lock-constant-face"}], ["dirvish-file-user-id", "file user id", {"inherit": "font-lock-preprocessor-face"}], ["dirvish-file-group-id", "file group id", {"inherit": "dirvish-file-user-id"}], ["dirvish-file-size", "file size", {"underline": {"style": "line", "color": null}, "inherit": "completions-annotations"}], ["dirvish-file-time", "file time", {"fg": "#979797"}], ["dirvish-file-inode-number", "file inode number", {"inherit": "dirvish-file-link-number"}], ["dirvish-file-device-number", "file device number", {"inherit": "dirvish-file-link-number"}], ["dirvish-subtree-guide", "subtree guide", {"bg": "unspecified", "underline": {"style": "line", "color": null}, "inherit": "dired-ignored"}], ["dirvish-subtree-state", "subtree state", {"bg": "unspecified", "underline": {"style": "line", "color": null}, "inherit": "dired-ignored"}], ["dirvish-collapse-dir-face", "collapse dir", {"inherit": "dired-directory"}], ["dirvish-collapse-empty-dir-face", "collapse empty dir", {"inherit": "shadow"}], ["dirvish-collapse-file-face", "collapse file", {"inherit": "default"}], ["dirvish-emerge-group-title", "emerge group title", {"inherit": "dired-ignored"}], ["dirvish-media-info-heading", "media info heading", {"inherit": ["dired-header", "bold"]}], ["dirvish-media-info-property-key", "media info property key", {"inherit": ["italic"]}], ["dirvish-narrow-match-face-0", "narrow match 0", {"fg": "#223fbf", "weight": "bold"}], ["dirvish-narrow-match-face-1", "narrow match 1", {"fg": "#8f0075", "weight": "bold"}], ["dirvish-narrow-match-face-2", "narrow match 2", {"fg": "#145a00", "weight": "bold"}], ["dirvish-narrow-match-face-3", "narrow match 3", {"fg": "#804000", "weight": "bold"}], ["dirvish-narrow-split", "narrow split", {"inherit": "font-lock-negation-char-face"}], ["dirvish-proc-running", "proc running", {"inherit": "warning"}], ["dirvish-proc-finished", "proc finished", {"inherit": "success"}], ["dirvish-proc-failed", "proc failed", {"inherit": "error"}], ["dirvish-git-commit-message-face", "git commit message", {"bg": "unspecified", "underline": {"style": "line", "color": null}, "inherit": "dired-ignored"}], ["dirvish-vc-added-state", "vc added state", {"inherit": "vc-locally-added-state"}], ["dirvish-vc-edited-state", "vc edited state", {"inherit": "vc-edited-state"}], ["dirvish-vc-removed-state", "vc removed state", {"inherit": "vc-removed-state"}], ["dirvish-vc-conflict-state", "vc conflict state", {"inherit": "vc-conflict-state"}], ["dirvish-vc-locked-state", "vc locked state", {"inherit": "vc-locked-state"}], ["dirvish-vc-missing-state", "vc missing state", {"inherit": "vc-missing-state"}], ["dirvish-vc-needs-merge-face", "vc needs merge", {"bg": "#efcbcf"}], ["dirvish-vc-needs-update-state", "vc needs update state", {"inherit": "vc-needs-update-state"}], ["dirvish-vc-unregistered-face", "vc unregistered", {"inherit": "font-lock-constant-face"}]]}, "calibredb": {"label": "calibredb", "preview": "calibredb", "faces": [["calibredb-search-header-library-name-face", "search header library name", {}], ["calibredb-search-header-library-path-face", "search header library path", {}], ["calibredb-search-header-total-face", "search header total", {}], ["calibredb-search-header-filter-face", "search header filter", {}], ["calibredb-search-header-sort-face", "search header sort", {}], ["calibredb-search-header-highlight-face", "search header highlight", {}], ["calibredb-id-face", "id", {}], ["calibredb-title-face", "title", {}], ["calibredb-author-face", "author", {}], ["calibredb-format-face", "format", {}], ["calibredb-size-face", "size", {}], ["calibredb-tag-face", "tag", {}], ["calibredb-date-face", "date", {}], ["calibredb-mark-face", "mark", {}], ["calibredb-series-face", "series", {}], ["calibredb-publisher-face", "publisher", {}], ["calibredb-pubdate-face", "pubdate", {}], ["calibredb-language-face", "language", {}], ["calibredb-comment-face", "comment", {}], ["calibredb-archive-face", "archive", {}], ["calibredb-favorite-face", "favorite", {}], ["calibredb-file-face", "file", {}], ["calibredb-ids-face", "ids", {}], ["calibredb-highlight-face", "highlight", {}], ["calibredb-current-page-button-face", "current page button", {}], ["calibredb-mouse-face", "mouse", {}], ["calibredb-title-detailed-view-face", "title detailed view", {}], ["calibredb-edit-annotation-header-title-face", "edit annotation header title", {}]]}, "erc": {"label": "erc", "preview": "erc", "faces": [["erc-header-line", "header line", {}], ["erc-timestamp-face", "timestamp", {}], ["erc-notice-face", "notice", {}], ["erc-default-face", "default", {}], ["erc-current-nick-face", "current nick", {}], ["erc-my-nick-face", "my nick", {}], ["erc-my-nick-prefix-face", "my nick prefix", {}], ["erc-nick-default-face", "nick default", {}], ["erc-nick-prefix-face", "nick prefix", {}], ["erc-button-nick-default-face", "button nick default", {}], ["erc-nick-msg-face", "nick msg", {}], ["erc-direct-msg-face", "direct msg", {}], ["erc-action-face", "action", {}], ["erc-keyword-face", "keyword", {}], ["erc-pal-face", "pal", {}], ["erc-fool-face", "fool", {}], ["erc-dangerous-host-face", "dangerous host", {}], ["erc-error-face", "error", {}], ["erc-input-face", "input", {}], ["erc-prompt-face", "prompt", {}], ["erc-command-indicator-face", "command indicator", {}], ["erc-information", "information", {}], ["erc-button", "button", {}], ["erc-bold-face", "bold", {}], ["erc-italic-face", "italic", {}], ["erc-underline-face", "underline", {}], ["erc-inverse-face", "inverse", {}], ["erc-spoiler-face", "spoiler", {}], ["erc-fill-wrap-merge-indicator-face", "fill wrap merge indicator", {}], ["erc-keep-place-indicator-arrow", "keep place indicator arrow", {}], ["erc-keep-place-indicator-line", "keep place indicator line", {}]]}, "org-drill": {"label": "org-drill", "preview": "orgdrill", "faces": [["org-drill-hidden-cloze-face", "hidden cloze", {}], ["org-drill-visible-cloze-face", "visible cloze", {}], ["org-drill-visible-cloze-hint-face", "visible cloze hint", {}]]}, "org-noter": {"label": "org-noter", "preview": "orgnoter", "faces": [["org-noter-notes-exist-face", "notes exist", {}], ["org-noter-no-notes-exist-face", "no notes exist", {}]]}, "signel": {"label": "signel", "preview": "signel", "faces": [["signel-timestamp-face", "timestamp", {}], ["signel-my-msg-face", "my msg", {}], ["signel-other-msg-face", "other msg", {}], ["signel-error-face", "error", {}]]}, "pearl": {"label": "pearl", "preview": "pearl", "faces": [["pearl-preamble-summary", "preamble summary", {}], ["pearl-editable-comment", "editable comment", {}], ["pearl-readonly-comment", "readonly comment", {}], ["pearl-modified-highlight", "modified highlight", {}], ["pearl-modified-local", "modified local", {}], ["pearl-modified-unknown", "modified unknown", {}]]}, "slack": {"label": "slack", "preview": "slack", "faces": [["slack-room-info-title-face", "room info title", {}], ["slack-room-info-title-room-name-face", "room info title room name", {}], ["slack-room-info-section-title-face", "room info section title", {}], ["slack-room-info-section-label-face", "room info section label", {}], ["slack-room-unread-face", "room unread", {}], ["slack-message-output-header", "message output header", {}], ["slack-message-output-text", "message output text", {}], ["slack-message-output-reaction", "message output reaction", {}], ["slack-message-output-reaction-pressed", "message output reaction pressed", {}], ["slack-message-deleted-face", "message deleted", {}], ["slack-new-message-marker-face", "new message marker", {}], ["slack-all-thread-buffer-thread-header-face", "all thread buffer thread header", {}], ["slack-message-mention-face", "message mention", {}], ["slack-message-mention-me-face", "message mention me", {}], ["slack-message-mention-keyword-face", "message mention keyword", {}], ["slack-channel-button-face", "channel button", {}], ["slack-mrkdwn-bold-face", "mrkdwn bold", {}], ["slack-mrkdwn-italic-face", "mrkdwn italic", {}], ["slack-mrkdwn-code-face", "mrkdwn code", {}], ["slack-mrkdwn-code-block-face", "mrkdwn code block", {}], ["slack-mrkdwn-strike-face", "mrkdwn strike", {}], ["slack-mrkdwn-blockquote-face", "mrkdwn blockquote", {}], ["slack-mrkdwn-list-face", "mrkdwn list", {}], ["slack-attachment-header", "attachment header", {}], ["slack-attachment-footer", "attachment footer", {}], ["slack-attachment-pad", "attachment pad", {}], ["slack-attachment-field-title", "attachment field title", {}], ["slack-message-attachment-preview-header-face", "message attachment preview header", {}], ["slack-preview-face", "preview", {}], ["slack-block-highlight-source-overlay-face", "block highlight source overlay", {}], ["slack-message-action-face", "message action", {}], ["slack-message-action-primary-face", "message action primary", {}], ["slack-message-action-danger-face", "message action danger", {}], ["slack-button-block-element-face", "button block element", {}], ["slack-button-primary-block-element-face", "button primary block element", {}], ["slack-button-danger-block-element-face", "button danger block element", {}], ["slack-select-block-element-face", "select block element", {}], ["slack-overflow-block-element-face", "overflow block element", {}], ["slack-date-picker-block-element-face", "date picker block element", {}], ["slack-dialog-title-face", "dialog title", {}], ["slack-dialog-element-label-face", "dialog element label", {}], ["slack-dialog-element-hint-face", "dialog element hint", {}], ["slack-dialog-element-placeholder-face", "dialog element placeholder", {}], ["slack-dialog-element-error-face", "dialog element error", {}], ["slack-dialog-submit-button-face", "dialog submit button", {}], ["slack-dialog-cancel-button-face", "dialog cancel button", {}], ["slack-dialog-select-element-input-face", "dialog select element input", {}], ["slack-user-active-face", "user active", {}], ["slack-user-dnd-face", "user dnd", {}], ["slack-user-profile-header-face", "user profile header", {}], ["slack-user-profile-property-name-face", "user profile property name", {}], ["slack-profile-image-face", "profile image", {}], ["slack-search-result-message-header-face", "search result message header", {}], ["slack-search-result-message-username-face", "search result message username", {}], ["slack-modeline-has-unreads-face", "modeline has unreads", {}], ["slack-modeline-channel-has-unreads-face", "modeline channel has unreads", {}], ["slack-modeline-thread-has-unreads-face", "modeline thread has unreads", {}]]}, "telega": {"label": "telega", "preview": "telega", "faces": [["telega-root-heading", "root heading", {}], ["telega-tracking", "tracking", {}], ["telega-unread-unmuted-modeline", "unread unmuted modeline", {}], ["telega-username", "username", {}], ["telega-user-online-status", "user online status", {}], ["telega-user-non-online-status", "user non online status", {}], ["telega-secret-title", "secret title", {}], ["telega-contact-birthdays-today", "contact birthdays today", {}], ["telega-muted-count", "muted count", {}], ["telega-unmuted-count", "unmuted count", {}], ["telega-mention-count", "mention count", {}], ["telega-has-chatbuf-brackets", "has chatbuf brackets", {}], ["telega-delim-face", "delim", {}], ["telega-shadow", "shadow", {}], ["telega-link", "link", {}], ["telega-blue", "blue", {}], ["telega-red", "red", {}], ["telega-msg-heading", "msg heading", {}], ["telega-msg-user-title", "msg user title", {}], ["telega-msg-self-title", "msg self title", {}], ["telega-msg-deleted", "msg deleted", {}], ["telega-msg-sponsored", "msg sponsored", {}], ["telega-msg-inline-reply", "msg inline reply", {}], ["telega-msg-inline-forward", "msg inline forward", {}], ["telega-msg-inline-other", "msg inline other", {}], ["telega-entity-type-bold", "entity type bold", {}], ["telega-entity-type-italic", "entity type italic", {}], ["telega-entity-type-underline", "entity type underline", {}], ["telega-entity-type-strikethrough", "entity type strikethrough", {}], ["telega-entity-type-code", "entity type code", {}], ["telega-entity-type-pre", "entity type pre", {}], ["telega-entity-type-blockquote", "entity type blockquote", {}], ["telega-entity-type-mention", "entity type mention", {}], ["telega-entity-type-hashtag", "entity type hashtag", {}], ["telega-entity-type-cashtag", "entity type cashtag", {}], ["telega-entity-type-botcommand", "entity type botcommand", {}], ["telega-entity-type-texturl", "entity type texturl", {}], ["telega-entity-type-spoiler", "entity type spoiler", {}], ["telega-reaction", "reaction", {}], ["telega-reaction-chosen", "reaction chosen", {}], ["telega-reaction-paid", "reaction paid", {}], ["telega-reaction-paid-chosen", "reaction paid chosen", {}], ["telega-highlight-text-face", "highlight text", {}], ["telega-button-highlight", "button highlight", {}], ["telega-chat-prompt", "chat prompt", {}], ["telega-chat-prompt-aux", "chat prompt aux", {}], ["telega-chat-input-attachment", "chat input attachment", {}], ["telega-topic-button", "topic button", {}], ["telega-filter-active", "filter active", {}], ["telega-filter-button-active", "filter button active", {}], ["telega-filter-button-inactive", "filter button inactive", {}], ["telega-checklist-stats-done", "checklist stats done", {}], ["telega-checklist-stats-todo", "checklist stats todo", {}], ["telega-box-button", "box button", {}], ["telega-box-button-active", "box button active", {}], ["telega-box-button-default-active", "box button default active", {}], ["telega-box-button-default-passive", "box button default passive", {}], ["telega-box-button-primary-active", "box button primary active", {}], ["telega-box-button-primary-passive", "box button primary passive", {}], ["telega-box-button-success-active", "box button success active", {}], ["telega-box-button-success-passive", "box button success passive", {}], ["telega-box-button-danger-active", "box button danger active", {}], ["telega-box-button-danger-passive", "box button danger passive", {}], ["telega-box-button-ui-active", "box button ui active", {}], ["telega-box-button-ui-passive", "box button ui passive", {}], ["telega-box-button2-active", "box button2 active", {}], ["telega-box-button2-passive", "box button2 passive", {}], ["telega-box-button2-white-foreground", "box button2 white foreground", {}], ["telega-describe-item-title", "describe item title", {}], ["telega-describe-section-title", "describe section title", {}], ["telega-describe-subsection-title", "describe subsection title", {}], ["telega-enckey-00", "enckey 00", {}], ["telega-enckey-01", "enckey 01", {}], ["telega-enckey-10", "enckey 10", {}], ["telega-enckey-11", "enckey 11", {}], ["telega-palette-builtin-blue", "palette builtin blue", {}], ["telega-palette-builtin-green", "palette builtin green", {}], ["telega-palette-builtin-orange", "palette builtin orange", {}], ["telega-palette-builtin-purple", "palette builtin purple", {}], ["telega-webpage-title", "webpage title", {}], ["telega-webpage-subtitle", "webpage subtitle", {}], ["telega-webpage-header", "webpage header", {}], ["telega-webpage-subheader", "webpage subheader", {}], ["telega-webpage-outline", "webpage outline", {}], ["telega-webpage-fixed", "webpage fixed", {}], ["telega-webpage-preformatted", "webpage preformatted", {}], ["telega-webpage-marked", "webpage marked", {}], ["telega-webpage-strike-through", "webpage strike through", {}], ["telega-webpage-chat-link", "webpage chat link", {}], ["telega-link-preview-sitename", "link preview sitename", {}], ["telega-link-preview-title", "link preview title", {}]]}, "shr": {"label": "shr (HTML: nov/eww/mail)", "preview": "shr", "faces": [["shr-h1", "h1", {}], ["shr-h2", "h2", {}], ["shr-h3", "h3", {}], ["shr-h4", "h4", {}], ["shr-h5", "h5", {}], ["shr-h6", "h6", {}], ["shr-text", "text", {}], ["shr-link", "link", {}], ["shr-selected-link", "selected link", {}], ["shr-code", "code", {}], ["shr-mark", "mark", {}], ["shr-strike-through", "strike through", {}], ["shr-sup", "sup", {}], ["shr-abbreviation", "abbreviation", {}], ["shr-sliced-image", "sliced image", {}]]}, "2048-game": {"label": "2048-game", "preview": "generic", "faces": [["twentyfortyeight-face-1024", "twentyfortyeight 1024", {"fg": "#000000", "bg": "#ffd700"}], ["twentyfortyeight-face-128", "twentyfortyeight 128", {"fg": "#ffffff", "bg": "#8b0000"}], ["twentyfortyeight-face-16", "twentyfortyeight 16", {"fg": "#000000", "bg": "#ffa500"}], ["twentyfortyeight-face-2", "twentyfortyeight 2", {"fg": "#000000", "bg": "#f0e68c"}], ["twentyfortyeight-face-2048", "twentyfortyeight 2048", {"fg": "#000000", "bg": "#ffff00"}], ["twentyfortyeight-face-256", "twentyfortyeight 256", {"fg": "#ffffff", "bg": "#8b008b"}], ["twentyfortyeight-face-32", "twentyfortyeight 32", {"fg": "#000000", "bg": "#ff4500"}], ["twentyfortyeight-face-4", "twentyfortyeight 4", {"fg": "#000000", "bg": "#deb887"}], ["twentyfortyeight-face-512", "twentyfortyeight 512", {"fg": "#000000", "bg": "#ff00ff"}], ["twentyfortyeight-face-64", "twentyfortyeight 64", {"fg": "#ffffff", "bg": "#b22222"}], ["twentyfortyeight-face-8", "twentyfortyeight 8", {"fg": "#000000", "bg": "#cd8500"}]]}, "alert": {"label": "alert", "preview": "generic", "faces": [["alert-high-face", "high", {"fg": "#ff8c00", "weight": "bold"}], ["alert-low-face", "low", {"fg": "#00008b"}], ["alert-moderate-face", "moderate", {"fg": "#ffd700", "weight": "bold"}], ["alert-normal-face", "normal", {}], ["alert-trivial-face", "trivial", {"fg": "#9400d3"}], ["alert-urgent-face", "urgent", {"fg": "#ff0000", "weight": "bold"}]]}, "all-the-icons": {"label": "all-the-icons", "preview": "generic", "faces": [["all-the-icons-blue", "blue", {"fg": "#6a9fb5"}], ["all-the-icons-blue-alt", "blue alt", {"fg": "#2188b6"}], ["all-the-icons-cyan", "cyan", {"fg": "#75b5aa"}], ["all-the-icons-cyan-alt", "cyan alt", {"fg": "#0595bd"}], ["all-the-icons-dblue", "dblue", {"fg": "#446674"}], ["all-the-icons-dcyan", "dcyan", {"fg": "#48746d"}], ["all-the-icons-dgreen", "dgreen", {"fg": "#6d8143"}], ["all-the-icons-dmaroon", "dmaroon", {"fg": "#72584b"}], ["all-the-icons-dorange", "dorange", {"fg": "#915b2d"}], ["all-the-icons-dpink", "dpink", {"fg": "#7e5d5f"}], ["all-the-icons-dpurple", "dpurple", {"fg": "#694863"}], ["all-the-icons-dred", "dred", {"fg": "#843031"}], ["all-the-icons-dsilver", "dsilver", {"fg": "#838484"}], ["all-the-icons-dyellow", "dyellow", {"fg": "#b48d56"}], ["all-the-icons-green", "green", {"fg": "#90a959"}], ["all-the-icons-lblue", "lblue", {"fg": "#677174"}], ["all-the-icons-lcyan", "lcyan", {"fg": "#2c7d6e"}], ["all-the-icons-lgreen", "lgreen", {"fg": "#3d6837"}], ["all-the-icons-lmaroon", "lmaroon", {"fg": "#ce7a4e"}], ["all-the-icons-lorange", "lorange", {"fg": "#ffa500"}], ["all-the-icons-lpink", "lpink", {"fg": "#ff505b"}], ["all-the-icons-lpurple", "lpurple", {"fg": "#e69dd6"}], ["all-the-icons-lred", "lred", {"fg": "#eb595a"}], ["all-the-icons-lsilver", "lsilver", {"fg": "#7f7869"}], ["all-the-icons-lyellow", "lyellow", {"fg": "#ff9300"}], ["all-the-icons-maroon", "maroon", {"fg": "#8f5536"}], ["all-the-icons-orange", "orange", {"fg": "#d4843e"}], ["all-the-icons-pink", "pink", {"fg": "#fc505b"}], ["all-the-icons-purple", "purple", {"fg": "#68295b"}], ["all-the-icons-purple-alt", "purple alt", {"fg": "#5d54e1"}], ["all-the-icons-red", "red", {"fg": "#ac4142"}], ["all-the-icons-red-alt", "red alt", {"fg": "#843031"}], ["all-the-icons-silver", "silver", {"fg": "#716e68"}], ["all-the-icons-yellow", "yellow", {"fg": "#ffcc0e"}]]}, "company": {"label": "company", "preview": "generic", "faces": [["company-echo", "echo", {}], ["company-echo-common", "echo common", {"fg": "#8b1a1a"}], ["company-preview", "preview", {"inherit": ["company-tooltip-selection", "company-tooltip"]}], ["company-preview-common", "preview common", {"inherit": "company-tooltip-common-selection"}], ["company-preview-search", "preview search", {"inherit": "company-tooltip-common-selection"}], ["company-tooltip", "tooltip", {"fg": "#000000", "bg": "#fff8dc"}], ["company-tooltip-annotation", "tooltip annotation", {"fg": "#8b1a1a"}], ["company-tooltip-annotation-selection", "tooltip annotation selection", {"inherit": "company-tooltip-annotation"}], ["company-tooltip-common", "tooltip common", {"fg": "#8b0000"}], ["company-tooltip-common-selection", "tooltip common selection", {"inherit": "company-tooltip-common"}], ["company-tooltip-deprecated", "tooltip deprecated", {"strike": {"color": null}}], ["company-tooltip-mouse", "tooltip mouse", {"inherit": "highlight"}], ["company-tooltip-quick-access", "tooltip quick access", {"inherit": "company-tooltip-annotation"}], ["company-tooltip-quick-access-selection", "tooltip quick access selection", {"inherit": "company-tooltip-annotation-selection"}], ["company-tooltip-scrollbar-thumb", "tooltip scrollbar thumb", {"bg": "#cd5c5c"}], ["company-tooltip-scrollbar-track", "tooltip scrollbar track", {"bg": "#f5deb3"}], ["company-tooltip-search", "tooltip search", {"inherit": "highlight"}], ["company-tooltip-search-selection", "tooltip search selection", {"inherit": "highlight"}], ["company-tooltip-selection", "tooltip selection", {"bg": "#add8e6"}]]}, "company-box": {"label": "company-box", "preview": "generic", "faces": [["company-box-annotation", "annotation", {"inherit": "company-tooltip-annotation"}], ["company-box-background", "background", {"inherit": "company-tooltip"}], ["company-box-candidate", "candidate", {"fg": "#000000"}], ["company-box-numbers", "numbers", {"inherit": "company-box-candidate"}], ["company-box-scrollbar", "scrollbar", {"inherit": "company-tooltip-selection"}], ["company-box-selection", "selection", {"extend": true, "inherit": "company-tooltip-selection"}]]}, "consult": {"label": "consult", "preview": "generic", "faces": [["consult-async-failed", "async failed", {"inherit": "error"}], ["consult-async-finished", "async finished", {"inherit": "success"}], ["consult-async-running", "async running", {"inherit": "consult-narrow-indicator"}], ["consult-async-split", "async split", {"inherit": "font-lock-negation-char-face"}], ["consult-bookmark", "bookmark", {"inherit": "font-lock-constant-face"}], ["consult-buffer", "buffer", {}], ["consult-file", "file", {"inherit": "font-lock-function-name-face"}], ["consult-grep-context", "grep context", {"inherit": "shadow"}], ["consult-help", "help", {"inherit": "shadow"}], ["consult-highlight-mark", "highlight mark", {"inherit": "consult-highlight-match"}], ["consult-highlight-match", "highlight match", {"inherit": "match"}], ["consult-key", "key", {"inherit": "font-lock-keyword-face"}], ["consult-line-number", "line number", {"inherit": "consult-key"}], ["consult-line-number-prefix", "line number prefix", {"inherit": "line-number"}], ["consult-line-number-wrapped", "line number wrapped", {"inherit": "warning"}], ["consult-narrow-indicator", "narrow indicator", {"inherit": "warning"}], ["consult-preview-insertion", "preview insertion", {"inherit": "region"}], ["consult-preview-line", "preview line", {"extend": true, "inherit": "consult-preview-insertion"}], ["consult-preview-match", "preview match", {"inherit": "isearch"}], ["consult-separator", "separator", {}]]}, "embark": {"label": "embark", "preview": "generic", "faces": [["embark-collect-annotation", "collect annotation", {"inherit": "completions-annotations"}], ["embark-collect-candidate", "collect candidate", {"inherit": "default"}], ["embark-collect-group-separator", "collect group separator", {"slant": "italic", "strike": {"color": null}, "inherit": "shadow"}], ["embark-collect-group-title", "collect group title", {"slant": "italic", "inherit": "shadow"}], ["embark-keybinding", "keybinding", {"inherit": "success"}], ["embark-keybinding-repeat", "keybinding repeat", {"inherit": "font-lock-builtin-face"}], ["embark-keymap", "keymap", {"slant": "italic"}], ["embark-selected", "selected", {"inherit": "match"}], ["embark-target", "target", {"inherit": "highlight"}], ["embark-verbose-indicator-documentation", "verbose indicator documentation", {"inherit": "completions-annotations"}], ["embark-verbose-indicator-shadowed", "verbose indicator shadowed", {"inherit": "shadow"}], ["embark-verbose-indicator-title", "verbose indicator title", {"weight": "bold", "height": 1.1}]]}, "emms": {"label": "emms", "preview": "generic", "faces": [["emms-browser-album-face", "browser album", {}], ["emms-browser-albumartist-face", "browser albumartist", {}], ["emms-browser-artist-face", "browser artist", {}], ["emms-browser-composer-face", "browser composer", {}], ["emms-browser-performer-face", "browser performer", {}], ["emms-browser-track-face", "browser track", {}], ["emms-browser-year/genre-face", "browser year/genre", {}], ["emms-metaplaylist-mode-current-face", "metaplaylist mode current", {"fg": "#ffffff", "bg": "#cd0000"}], ["emms-metaplaylist-mode-face", "metaplaylist mode", {"fg": "#cd0000"}], ["emms-playlist-selected-face", "playlist selected", {"fg": "#ffffff", "bg": "#0000cd"}], ["emms-playlist-track-face", "playlist track", {"fg": "#0000ff"}]]}, "flyspell-correct": {"label": "flyspell-correct", "preview": "generic", "faces": [["flyspell-correct-highlight-face", "highlight", {"inherit": "isearch"}]]}, "highlight-indent-guides": {"label": "highlight-indent-guides", "preview": "generic", "faces": [["highlight-indent-guides-character-face", "character", {}], ["highlight-indent-guides-even-face", "even", {}], ["highlight-indent-guides-odd-face", "odd", {}], ["highlight-indent-guides-stack-character-face", "stack character", {}], ["highlight-indent-guides-stack-even-face", "stack even", {}], ["highlight-indent-guides-stack-odd-face", "stack odd", {}], ["highlight-indent-guides-top-character-face", "top character", {}], ["highlight-indent-guides-top-even-face", "top even", {}], ["highlight-indent-guides-top-odd-face", "top odd", {}]]}, "hl-todo": {"label": "hl-todo", "preview": "generic", "faces": [["hl-todo", "hl todo", {"fg": "#cc9393", "weight": "bold"}], ["hl-todo-flymake-type", "flymake type", {"inherit": "font-lock-keyword-face"}]]}, "json-mode": {"label": "json-mode", "preview": "generic", "faces": [["json-mode-object-name-face", "object name", {}]]}, "llama": {"label": "llama", "preview": "generic", "faces": [["llama-##-macro", "## macro", {"inherit": "font-lock-function-call-face"}], ["llama-deleted-argument", "deleted argument", {"box": {"style": "line", "width": 1, "color": "#ff0000"}}], ["llama-llama-macro", "llama macro", {"inherit": "font-lock-keyword-face"}], ["llama-mandatory-argument", "mandatory argument", {"inherit": "font-lock-variable-use-face"}], ["llama-optional-argument", "optional argument", {"inherit": "font-lock-type-face"}]]}, "lv": {"label": "lv", "preview": "generic", "faces": [["lv-separator", "separator", {"bg": "#cccccc"}]]}, "magit-section": {"label": "magit-section", "preview": "generic", "faces": [["magit-left-margin", "magit left margin", {"inherit": "default"}], ["magit-section-child-count", "child count", {}], ["magit-section-heading", "heading", {"fg": "#8b6508", "weight": "bold", "extend": true}], ["magit-section-heading-selection", "heading selection", {"fg": "#8b4c39", "extend": true}], ["magit-section-highlight", "highlight", {"bg": "#f2f2f2", "extend": true}], ["magit-section-secondary-heading", "secondary heading", {"weight": "bold", "extend": true}]]}, "malyon": {"label": "malyon", "preview": "generic", "faces": [["malyon-face-bold", "face bold", {"inherit": "bold"}], ["malyon-face-error", "face error", {"inherit": "error"}], ["malyon-face-italic", "face italic", {"inherit": "italic"}], ["malyon-face-plain", "face plain", {"inherit": "default"}], ["malyon-face-reverse", "face reverse", {"inverse": true, "inherit": "default"}]]}, "marginalia": {"label": "marginalia", "preview": "generic", "faces": [["marginalia-archive", "archive", {"inherit": "warning"}], ["marginalia-char", "char", {"inherit": "marginalia-key"}], ["marginalia-date", "date", {"inherit": "marginalia-key"}], ["marginalia-documentation", "documentation", {"inherit": "completions-annotations"}], ["marginalia-file-name", "file name", {"inherit": "marginalia-documentation"}], ["marginalia-file-owner", "file owner", {"inherit": "font-lock-preprocessor-face"}], ["marginalia-file-priv-dir", "file priv dir", {"inherit": "font-lock-keyword-face"}], ["marginalia-file-priv-exec", "file priv exec", {"inherit": "font-lock-function-name-face"}], ["marginalia-file-priv-link", "file priv link", {"inherit": "font-lock-keyword-face"}], ["marginalia-file-priv-no", "file priv no", {"inherit": "shadow"}], ["marginalia-file-priv-other", "file priv other", {"inherit": "font-lock-constant-face"}], ["marginalia-file-priv-rare", "file priv rare", {"inherit": "font-lock-variable-name-face"}], ["marginalia-file-priv-read", "file priv read", {"inherit": "font-lock-type-face"}], ["marginalia-file-priv-write", "file priv write", {"inherit": "font-lock-builtin-face"}], ["marginalia-function", "function", {"inherit": "font-lock-function-name-face"}], ["marginalia-installed", "installed", {"inherit": "success"}], ["marginalia-key", "key", {"inherit": "font-lock-keyword-face"}], ["marginalia-lighter", "lighter", {"inherit": "marginalia-size"}], ["marginalia-list", "list", {"inherit": "font-lock-constant-face"}], ["marginalia-mode", "mode", {"inherit": "marginalia-key"}], ["marginalia-modified", "modified", {"inherit": "font-lock-negation-char-face"}], ["marginalia-null", "null", {"inherit": "font-lock-comment-face"}], ["marginalia-number", "number", {"inherit": "font-lock-constant-face"}], ["marginalia-off", "off", {"inherit": "error"}], ["marginalia-on", "on", {"inherit": "success"}], ["marginalia-size", "size", {"inherit": "marginalia-number"}], ["marginalia-string", "string", {"inherit": "font-lock-string-face"}], ["marginalia-symbol", "symbol", {"inherit": "font-lock-type-face"}], ["marginalia-true", "true", {"inherit": "font-lock-builtin-face"}], ["marginalia-type", "type", {"inherit": "marginalia-key"}], ["marginalia-value", "value", {"inherit": "marginalia-key"}], ["marginalia-version", "version", {"inherit": "marginalia-number"}]]}, "markdown-mode": {"label": "markdown-mode", "preview": "markdown", "faces": [["markdown-blockquote-face", "markdown blockquote", {"inherit": "font-lock-doc-face"}], ["markdown-bold-face", "markdown bold", {"inherit": "bold"}], ["markdown-code-face", "markdown code", {"inherit": "fixed-pitch"}], ["markdown-comment-face", "markdown comment", {"inherit": "font-lock-comment-face"}], ["markdown-footnote-marker-face", "markdown footnote marker", {"inherit": "markdown-markup-face"}], ["markdown-footnote-text-face", "markdown footnote text", {"inherit": "font-lock-comment-face"}], ["markdown-gfm-checkbox-face", "markdown gfm checkbox", {"inherit": "font-lock-builtin-face"}], ["markdown-header-delimiter-face", "markdown header delimiter", {"inherit": "markdown-markup-face"}], ["markdown-header-face", "markdown header", {"weight": "bold", "inherit": ["font-lock-function-name-face"]}], ["markdown-header-face-1", "markdown header 1", {"inherit": "markdown-header-face"}], ["markdown-header-face-2", "markdown header 2", {"inherit": "markdown-header-face"}], ["markdown-header-face-3", "markdown header 3", {"inherit": "markdown-header-face"}], ["markdown-header-face-4", "markdown header 4", {"inherit": "markdown-header-face"}], ["markdown-header-face-5", "markdown header 5", {"inherit": "markdown-header-face"}], ["markdown-header-face-6", "markdown header 6", {"inherit": "markdown-header-face"}], ["markdown-header-rule-face", "markdown header rule", {"inherit": "markdown-markup-face"}], ["markdown-highlight-face", "markdown highlight", {"inherit": "highlight"}], ["markdown-highlighting-face", "markdown highlighting", {"fg": "#000000", "bg": "#ffff00"}], ["markdown-hr-face", "markdown hr", {"inherit": "markdown-markup-face"}], ["markdown-html-attr-name-face", "markdown html attr name", {"inherit": "font-lock-variable-name-face"}], ["markdown-html-attr-value-face", "markdown html attr value", {"inherit": "font-lock-string-face"}], ["markdown-html-entity-face", "markdown html entity", {"inherit": "font-lock-variable-name-face"}], ["markdown-html-tag-delimiter-face", "markdown html tag delimiter", {"inherit": "markdown-markup-face"}], ["markdown-html-tag-name-face", "markdown html tag name", {"inherit": "font-lock-type-face"}], ["markdown-inline-code-face", "markdown inline code", {"inherit": ["markdown-code-face", "font-lock-constant-face"]}], ["markdown-italic-face", "markdown italic", {"inherit": "italic"}], ["markdown-language-info-face", "markdown language info", {"inherit": "font-lock-string-face"}], ["markdown-language-keyword-face", "markdown language keyword", {"inherit": "font-lock-type-face"}], ["markdown-line-break-face", "markdown line break", {"underline": {"style": "line", "color": null}, "inherit": "font-lock-constant-face"}], ["markdown-link-face", "markdown link", {"inherit": "link"}], ["markdown-link-title-face", "markdown link title", {"inherit": "font-lock-comment-face"}], ["markdown-list-face", "markdown list", {"inherit": "markdown-markup-face"}], ["markdown-markup-face", "markdown markup", {"inherit": "shadow"}], ["markdown-math-face", "markdown math", {"inherit": "font-lock-string-face"}], ["markdown-metadata-key-face", "markdown metadata key", {"inherit": "font-lock-variable-name-face"}], ["markdown-metadata-value-face", "markdown metadata value", {"inherit": "font-lock-string-face"}], ["markdown-missing-link-face", "markdown missing link", {"inherit": "font-lock-warning-face"}], ["markdown-plain-url-face", "markdown plain url", {"inherit": "markdown-link-face"}], ["markdown-pre-face", "markdown pre", {"inherit": ["markdown-code-face", "font-lock-constant-face"]}], ["markdown-reference-face", "markdown reference", {"inherit": "markdown-markup-face"}], ["markdown-strike-through-face", "markdown strike through", {"strike": {"color": null}}], ["markdown-table-face", "markdown table", {"inherit": ["markdown-code-face"]}], ["markdown-url-face", "markdown url", {"inherit": "font-lock-string-face"}]]}, "nerd-icons": {"label": "nerd-icons", "preview": "generic", "faces": [["nerd-icons-blue", "blue", {"fg": "#6a9fb5"}], ["nerd-icons-blue-alt", "blue alt", {"fg": "#2188b6"}], ["nerd-icons-cyan", "cyan", {"fg": "#75b5aa"}], ["nerd-icons-cyan-alt", "cyan alt", {"fg": "#0595bd"}], ["nerd-icons-dblue", "dblue", {"fg": "#446674"}], ["nerd-icons-dcyan", "dcyan", {"fg": "#48746d"}], ["nerd-icons-dgreen", "dgreen", {"fg": "#6d8143"}], ["nerd-icons-dmaroon", "dmaroon", {"fg": "#72584b"}], ["nerd-icons-dorange", "dorange", {"fg": "#915b2d"}], ["nerd-icons-dpink", "dpink", {"fg": "#7e5d5f"}], ["nerd-icons-dpurple", "dpurple", {"fg": "#694863"}], ["nerd-icons-dred", "dred", {"fg": "#843031"}], ["nerd-icons-dsilver", "dsilver", {"fg": "#838484"}], ["nerd-icons-dyellow", "dyellow", {"fg": "#b48d56"}], ["nerd-icons-green", "green", {"fg": "#90a959"}], ["nerd-icons-lblue", "lblue", {"fg": "#677174"}], ["nerd-icons-lcyan", "lcyan", {"fg": "#2c7d6e"}], ["nerd-icons-lgreen", "lgreen", {"fg": "#3d6837"}], ["nerd-icons-lmaroon", "lmaroon", {"fg": "#ce7a4e"}], ["nerd-icons-lorange", "lorange", {"fg": "#ffa500"}], ["nerd-icons-lpink", "lpink", {"fg": "#ff505b"}], ["nerd-icons-lpurple", "lpurple", {"fg": "#e69dd6"}], ["nerd-icons-lred", "lred", {"fg": "#eb595a"}], ["nerd-icons-lsilver", "lsilver", {"fg": "#7f7869"}], ["nerd-icons-lyellow", "lyellow", {"fg": "#ff9300"}], ["nerd-icons-maroon", "maroon", {"fg": "#8f5536"}], ["nerd-icons-orange", "orange", {"fg": "#d4843e"}], ["nerd-icons-pink", "pink", {"fg": "#fc505b"}], ["nerd-icons-purple", "purple", {"fg": "#68295b"}], ["nerd-icons-purple-alt", "purple alt", {"fg": "#5d54e1"}], ["nerd-icons-red", "red", {"fg": "#ac4142"}], ["nerd-icons-red-alt", "red alt", {"fg": "#843031"}], ["nerd-icons-silver", "silver", {"fg": "#716e68"}], ["nerd-icons-yellow", "yellow", {"fg": "#ffcc0e"}]]}, "nerd-icons-completion": {"label": "nerd-icons-completion", "preview": "generic", "faces": [["nerd-icons-completion-dir-face", "dir", {}]]}, "orderless": {"label": "orderless", "preview": "generic", "faces": [["orderless-match-face-0", "match 0", {"fg": "#223fbf", "weight": "bold"}], ["orderless-match-face-1", "match 1", {"fg": "#8f0075", "weight": "bold"}], ["orderless-match-face-2", "match 2", {"fg": "#145a00", "weight": "bold"}], ["orderless-match-face-3", "match 3", {"fg": "#804000", "weight": "bold"}]]}, "org-roam": {"label": "org-roam", "preview": "generic", "faces": [["org-roam-dailies-calendar-note", "dailies calendar note", {"underline": {"style": "line", "color": null}, "inherit": ["org-link"]}], ["org-roam-dim", "dim", {"fg": "#999999"}], ["org-roam-header-line", "header line", {"fg": "#8b6508", "weight": "bold", "extend": true}], ["org-roam-olp", "olp", {"fg": "#999999"}], ["org-roam-preview-heading", "preview heading", {"fg": "#4d4d4d", "bg": "#cccccc", "extend": true}], ["org-roam-preview-heading-highlight", "preview heading highlight", {"fg": "#4d4d4d", "bg": "#bfbfbf", "extend": true}], ["org-roam-preview-heading-selection", "preview heading selection", {"fg": "#8b4c39", "extend": true, "inherit": "org-roam-preview-heading-highlight"}], ["org-roam-preview-region", "preview region", {"inherit": "bold"}], ["org-roam-title", "title", {"weight": "bold"}]]}, "org-superstar": {"label": "org-superstar", "preview": "generic", "faces": [["org-superstar-first", "first", {"inherit": "org-warning"}], ["org-superstar-header-bullet", "header bullet", {}], ["org-superstar-item", "item", {"inherit": "default"}], ["org-superstar-leading", "leading", {"fg": "#bebebe", "inherit": "default"}]]}, "prescient": {"label": "prescient", "preview": "generic", "faces": [["prescient-primary-highlight", "primary highlight", {"weight": "bold"}], ["prescient-secondary-highlight", "secondary highlight", {"underline": {"style": "line", "color": null}, "inherit": "prescient-primary-highlight"}]]}, "rainbow-delimiters": {"label": "rainbow-delimiters", "preview": "generic", "faces": [["rainbow-delimiters-base-error-face", "base error", {"fg": "#88090b", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-base-face", "base", {"inherit": "unspecified"}], ["rainbow-delimiters-depth-1-face", "depth 1", {"fg": "#707183", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-depth-2-face", "depth 2", {"fg": "#7388d6", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-depth-3-face", "depth 3", {"fg": "#909183", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-depth-4-face", "depth 4", {"fg": "#709870", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-depth-5-face", "depth 5", {"fg": "#907373", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-depth-6-face", "depth 6", {"fg": "#6276ba", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-depth-7-face", "depth 7", {"fg": "#858580", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-depth-8-face", "depth 8", {"fg": "#80a880", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-depth-9-face", "depth 9", {"fg": "#887070", "inherit": "rainbow-delimiters-base-face"}], ["rainbow-delimiters-mismatched-face", "mismatched", {"inherit": "rainbow-delimiters-unmatched-face"}], ["rainbow-delimiters-unmatched-face", "unmatched", {"inherit": "rainbow-delimiters-base-error-face"}]]}, "symbol-overlay": {"label": "symbol-overlay", "preview": "generic", "faces": [["symbol-overlay-default-face", "default", {"inherit": "highlight"}], ["symbol-overlay-face-1", "face 1", {"fg": "#000000", "bg": "#1e90ff"}], ["symbol-overlay-face-2", "face 2", {"fg": "#000000", "bg": "#ff69b4"}], ["symbol-overlay-face-3", "face 3", {"fg": "#000000", "bg": "#ffff00"}], ["symbol-overlay-face-4", "face 4", {"fg": "#000000", "bg": "#da70d6"}], ["symbol-overlay-face-5", "face 5", {"fg": "#000000", "bg": "#ff0000"}], ["symbol-overlay-face-6", "face 6", {"fg": "#000000", "bg": "#fa8072"}], ["symbol-overlay-face-7", "face 7", {"fg": "#000000", "bg": "#00ff7f"}], ["symbol-overlay-face-8", "face 8", {"fg": "#000000", "bg": "#40e0d0"}]]}, "tmr": {"label": "tmr", "preview": "generic", "faces": [["tmr-description", "description", {"inherit": "bold"}], ["tmr-duration", "duration", {}], ["tmr-end-time", "end time", {"inherit": "error"}], ["tmr-finished", "finished", {"inherit": "error"}], ["tmr-is-acknowledged", "is acknowledged", {"inherit": "success"}], ["tmr-must-be-acknowledged", "must be acknowledged", {"inherit": "warning"}], ["tmr-start-time", "start time", {"inherit": "success"}], ["tmr-tabulated-acknowledgement", "tabulated acknowledgement", {"inherit": "bold"}], ["tmr-tabulated-description", "tabulated description", {"inherit": "font-lock-doc-face"}], ["tmr-tabulated-end-time", "tabulated end time", {"fg": "#800040"}], ["tmr-tabulated-remaining-time", "tabulated remaining time", {"fg": "#603f00"}], ["tmr-tabulated-start-time", "tabulated start time", {"fg": "#004476"}]]}, "transient": {"label": "transient", "preview": "generic", "faces": [["transient-active-infix", "active infix", {"inherit": "highlight"}], ["transient-argument", "argument", {"weight": "bold", "inherit": "font-lock-string-face"}], ["transient-delimiter", "delimiter", {"inherit": "shadow"}], ["transient-disabled-suffix", "disabled suffix", {"fg": "#000000", "bg": "#ff0000", "weight": "bold"}], ["transient-enabled-suffix", "enabled suffix", {"fg": "#000000", "bg": "#00ff00", "weight": "bold"}], ["transient-heading", "heading", {"inherit": "font-lock-keyword-face"}], ["transient-higher-level", "higher level", {"box": {"style": "line", "width": 1, "color": "#999999"}}], ["transient-inactive-argument", "inactive argument", {"inherit": "shadow"}], ["transient-inactive-value", "inactive value", {"inherit": "shadow"}], ["transient-inapt-argument", "inapt argument", {"weight": "bold", "inherit": "shadow"}], ["transient-inapt-suffix", "inapt suffix", {"slant": "italic", "inherit": "shadow"}], ["transient-key", "key", {"inherit": "font-lock-builtin-face"}], ["transient-key-exit", "key exit", {"fg": "#aa2222", "inherit": "transient-key"}], ["transient-key-noop", "key noop", {"fg": "#cccccc", "inherit": "transient-key"}], ["transient-key-recurse", "key recurse", {"fg": "#2266ff", "inherit": "transient-key"}], ["transient-key-return", "key return", {"fg": "#aaaa11", "inherit": "transient-key"}], ["transient-key-stack", "key stack", {"fg": "#dd4488", "inherit": "transient-key"}], ["transient-key-stay", "key stay", {"fg": "#22aa22", "inherit": "transient-key"}], ["transient-mismatched-key", "mismatched key", {"box": {"style": "line", "width": 1, "color": "#ff00ff"}}], ["transient-nonstandard-key", "nonstandard key", {"box": {"style": "line", "width": 1, "color": "#00ffff"}}], ["transient-unreachable", "unreachable", {"inherit": "shadow"}], ["transient-unreachable-key", "unreachable key", {"inherit": ["shadow", "transient-key"]}], ["transient-value", "value", {"weight": "bold", "inherit": "font-lock-string-face"}]]}, "vertico": {"label": "vertico", "preview": "generic", "faces": [["vertico-current", "current", {"extend": true, "inherit": "highlight"}], ["vertico-group-separator", "group separator", {"strike": {"color": null}, "inherit": "vertico-group-title"}], ["vertico-group-title", "group title", {"slant": "italic", "inherit": "shadow"}], ["vertico-multiline", "multiline", {"inherit": "shadow"}]]}, "web-mode": {"label": "web-mode", "preview": "generic", "faces": [["web-mode-annotation-face", "annotation", {"inherit": "web-mode-comment-face"}], ["web-mode-annotation-html-face", "annotation html", {"slant": "italic", "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-tag-face", "annotation tag", {"underline": {"style": "line", "color": null}, "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-type-face", "annotation type", {"weight": "bold", "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-value-face", "annotation value", {"slant": "italic", "inherit": "web-mode-annotation-face"}], ["web-mode-block-attr-name-face", "block attr name", {"fg": "#8fbc8f"}], ["web-mode-block-attr-value-face", "block attr value", {"fg": "#5f9ea0"}], ["web-mode-block-comment-face", "block comment", {"inherit": "web-mode-comment-face"}], ["web-mode-block-control-face", "block control", {"inherit": "font-lock-preprocessor-face"}], ["web-mode-block-delimiter-face", "block delimiter", {"inherit": "font-lock-preprocessor-face"}], ["web-mode-block-face", "block", {"bg": "#ffffe0"}], ["web-mode-block-string-face", "block string", {"inherit": "web-mode-string-face"}], ["web-mode-bold-face", "bold", {"weight": "bold"}], ["web-mode-builtin-face", "builtin", {"inherit": "font-lock-builtin-face"}], ["web-mode-comment-face", "comment", {"inherit": "font-lock-comment-face"}], ["web-mode-comment-keyword-face", "comment keyword", {"weight": "bold"}], ["web-mode-constant-face", "constant", {"inherit": "font-lock-constant-face"}], ["web-mode-css-at-rule-face", "css at rule", {"inherit": "font-lock-constant-face"}], ["web-mode-css-color-face", "css color", {"inherit": "font-lock-builtin-face"}], ["web-mode-css-comment-face", "css comment", {"inherit": "web-mode-comment-face"}], ["web-mode-css-function-face", "css function", {"inherit": "font-lock-builtin-face"}], ["web-mode-css-priority-face", "css priority", {"inherit": "font-lock-builtin-face"}], ["web-mode-css-property-name-face", "css property name", {"inherit": "font-lock-variable-name-face"}], ["web-mode-css-pseudo-class-face", "css pseudo class", {"inherit": "font-lock-builtin-face"}], ["web-mode-css-selector-class-face", "css selector class", {"inherit": "font-lock-keyword-face"}], ["web-mode-css-selector-face", "css selector", {"inherit": "font-lock-keyword-face"}], ["web-mode-css-selector-tag-face", "css selector tag", {"inherit": "font-lock-keyword-face"}], ["web-mode-css-string-face", "css string", {"inherit": "web-mode-string-face"}], ["web-mode-css-variable-face", "css variable", {"slant": "italic", "inherit": "web-mode-variable-name-face"}], ["web-mode-current-column-highlight-face", "current column highlight", {"bg": "#3e3c36"}], ["web-mode-current-element-highlight-face", "current element highlight", {"fg": "#ffffff", "bg": "#000000"}], ["web-mode-doctype-face", "doctype", {"fg": "#bebebe"}], ["web-mode-error-face", "error", {"bg": "#ff0000"}], ["web-mode-filter-face", "filter", {"inherit": "font-lock-function-name-face"}], ["web-mode-folded-face", "folded", {"underline": {"style": "line", "color": null}}], ["web-mode-function-call-face", "function call", {"inherit": "font-lock-function-name-face"}], ["web-mode-function-name-face", "function name", {"inherit": "font-lock-function-name-face"}], ["web-mode-html-attr-custom-face", "html attr custom", {"inherit": "web-mode-html-attr-name-face"}], ["web-mode-html-attr-engine-face", "html attr engine", {"inherit": "web-mode-block-delimiter-face"}], ["web-mode-html-attr-equal-face", "html attr equal", {"inherit": "web-mode-html-attr-name-face"}], ["web-mode-html-attr-name-face", "html attr name", {"fg": "#8b8989"}], ["web-mode-html-attr-value-face", "html attr value", {"inherit": "font-lock-string-face"}], ["web-mode-html-entity-face", "html entity", {"slant": "italic"}], ["web-mode-html-tag-bracket-face", "html tag bracket", {"fg": "#242424"}], ["web-mode-html-tag-custom-face", "html tag custom", {"inherit": "web-mode-html-tag-face"}], ["web-mode-html-tag-face", "html tag", {"fg": "#8b8989"}], ["web-mode-html-tag-namespaced-face", "html tag namespaced", {"inherit": "web-mode-block-control-face"}], ["web-mode-html-tag-unclosed-face", "html tag unclosed", {"underline": {"style": "line", "color": null}, "inherit": "web-mode-html-tag-face"}], ["web-mode-inlay-face", "inlay", {"bg": "#ffffe0"}], ["web-mode-interpolate-color1-face", "interpolate color1", {"inherit": "web-mode-string-face"}], ["web-mode-interpolate-color2-face", "interpolate color2", {"inherit": "web-mode-string-face"}], ["web-mode-interpolate-color3-face", "interpolate color3", {"inherit": "web-mode-string-face"}], ["web-mode-interpolate-color4-face", "interpolate color4", {"inherit": "web-mode-string-face"}], ["web-mode-italic-face", "italic", {"slant": "italic"}], ["web-mode-javascript-comment-face", "javascript comment", {"inherit": "web-mode-comment-face"}], ["web-mode-javascript-string-face", "javascript string", {"inherit": "web-mode-string-face"}], ["web-mode-json-comment-face", "json comment", {"inherit": "web-mode-comment-face"}], ["web-mode-json-context-face", "json context", {"fg": "#cd69c9"}], ["web-mode-json-key-face", "json key", {"fg": "#dda0dd"}], ["web-mode-json-string-face", "json string", {"inherit": "web-mode-string-face"}], ["web-mode-jsx-depth-1-face", "jsx depth 1", {"bg": "#000053"}], ["web-mode-jsx-depth-2-face", "jsx depth 2", {"bg": "#001970"}], ["web-mode-jsx-depth-3-face", "jsx depth 3", {"bg": "#002984"}], ["web-mode-jsx-depth-4-face", "jsx depth 4", {"bg": "#49599a"}], ["web-mode-jsx-depth-5-face", "jsx depth 5", {"bg": "#9499b7"}], ["web-mode-keyword-face", "keyword", {"inherit": "font-lock-keyword-face"}], ["web-mode-param-name-face", "param name", {"fg": "#cdc9c9"}], ["web-mode-part-comment-face", "part comment", {"inherit": "web-mode-comment-face"}], ["web-mode-part-face", "part", {"inherit": "web-mode-block-face"}], ["web-mode-part-string-face", "part string", {"inherit": "web-mode-string-face"}], ["web-mode-preprocessor-face", "preprocessor", {"inherit": "font-lock-preprocessor-face"}], ["web-mode-script-face", "script", {"inherit": "web-mode-part-face"}], ["web-mode-sql-keyword-face", "sql keyword", {"weight": "bold", "slant": "italic"}], ["web-mode-string-face", "string", {"inherit": "font-lock-string-face"}], ["web-mode-style-face", "style", {"inherit": "web-mode-part-face"}], ["web-mode-symbol-face", "symbol", {"fg": "#eeb422"}], ["web-mode-type-face", "type", {"inherit": "font-lock-type-face"}], ["web-mode-underline-face", "underline", {"underline": {"style": "line", "color": null}}], ["web-mode-variable-name-face", "variable name", {"inherit": "font-lock-variable-name-face"}], ["web-mode-warning-face", "warning", {"inherit": "font-lock-warning-face"}], ["web-mode-whitespace-face", "whitespace", {"bg": "#68228b"}]]}, "yasnippet": {"label": "yasnippet", "preview": "generic", "faces": [["yas--field-debug-face", "yas field debug", {}], ["yas-field-highlight-face", "yas field highlight", {"inherit": "region"}]]}}; +const COLOR_NAMES=[["alice-blue", "#f0f8ff"], ["antique-white", "#faebd7"], ["aquamarine", "#7fffd4"], ["azure", "#f0ffff"], ["beige", "#f5f5dc"], ["bisque", "#ffe4c4"], ["black", "#000000"], ["blanched-almond", "#ffebcd"], ["blue", "#0000ff"], ["blue-violet", "#8a2be2"], ["brown", "#a52a2a"], ["burlywood", "#deb887"], ["cadet-blue", "#5f9ea0"], ["chartreuse", "#7fff00"], ["chocolate", "#d2691e"], ["coral", "#ff7f50"], ["cornflower-blue", "#6495ed"], ["cornsilk", "#fff8dc"], ["cyan", "#00ffff"], ["dark-blue", "#00008b"], ["dark-cyan", "#008b8b"], ["dark-goldenrod", "#b8860b"], ["dark-green", "#006400"], ["dark-grey", "#a9a9a9"], ["dark-khaki", "#bdb76b"], ["dark-magenta", "#8b008b"], ["dark-olive", "#556b2f"], ["dark-orange", "#ff8c00"], ["dark-orchid", "#9932cc"], ["dark-red", "#8b0000"], ["dark-salmon", "#e9967a"], ["dark-sea", "#8fbc8f"], ["dark-slate", "#2f4f4f"], ["dark-slate", "#483d8b"], ["dark-turquoise", "#00ced1"], ["dark-violet", "#9400d3"], ["deep-pink", "#ff1493"], ["deep-sky", "#00bfff"], ["dim-gray", "#696969"], ["dodger-blue", "#1e90ff"], ["firebrick", "#b22222"], ["floral-white", "#fffaf0"], ["forest-green", "#228b22"], ["gainsboro", "#dcdcdc"], ["ghost-white", "#f8f8ff"], ["gold", "#ffd700"], ["goldenrod", "#daa520"], ["gray", "#bebebe"], ["green", "#00ff00"], ["green-yellow", "#adff2f"], ["honeydew", "#f0fff0"], ["hot-pink", "#ff69b4"], ["indian-red", "#cd5c5c"], ["ivory", "#fffff0"], ["khaki", "#f0e68c"], ["lavender", "#e6e6fa"], ["lavender-blush", "#fff0f5"], ["lawn-green", "#7cfc00"], ["lemon-chiffon", "#fffacd"], ["light-blue", "#add8e6"], ["light-coral", "#f08080"], ["light-cyan", "#e0ffff"], ["light-goldenrod", "#eedd82"], ["light-goldenrod", "#fafad2"], ["light-green", "#90ee90"], ["light-grey", "#d3d3d3"], ["light-pink", "#ffb6c1"], ["light-salmon", "#ffa07a"], ["light-sea", "#20b2aa"], ["light-sky", "#87cefa"], ["light-slate", "#778899"], ["light-slate", "#8470ff"], ["light-steel", "#b0c4de"], ["light-yellow", "#ffffe0"], ["lime-green", "#32cd32"], ["linen", "#faf0e6"], ["magenta", "#ff00ff"], ["maroon", "#b03060"], ["medium-aquamarine", "#66cdaa"], ["medium-blue", "#0000cd"], ["medium-orchid", "#ba55d3"], ["medium-purple", "#9370db"], ["medium-sea", "#3cb371"], ["medium-slate", "#7b68ee"], ["medium-spring", "#00fa9a"], ["medium-turquoise", "#48d1cc"], ["medium-violet", "#c71585"], ["midnight-blue", "#191970"], ["mint-cream", "#f5fffa"], ["misty-rose", "#ffe4e1"], ["moccasin", "#ffe4b5"], ["navajo-white", "#ffdead"], ["navy", "#000080"], ["old-lace", "#fdf5e6"], ["olive-drab", "#6b8e23"], ["orange", "#ffa500"], ["orange-red", "#ff4500"], ["orchid", "#da70d6"], ["pale-goldenrod", "#eee8aa"], ["pale-green", "#98fb98"], ["pale-turquoise", "#afeeee"], ["pale-violet", "#db7093"], ["papaya-whip", "#ffefd5"], ["peach-puff", "#ffdab9"], ["peru", "#cd853f"], ["pink", "#ffc0cb"], ["plum", "#dda0dd"], ["powder-blue", "#b0e0e6"], ["purple", "#a020f0"], ["red", "#ff0000"], ["rosy-brown", "#bc8f8f"], ["royal-blue", "#4169e1"], ["saddle-brown", "#8b4513"], ["salmon", "#fa8072"], ["sandy-brown", "#f4a460"], ["sea-green", "#2e8b57"], ["seashell", "#fff5ee"], ["sienna", "#a0522d"], ["sky-blue", "#87ceeb"], ["slate-blue", "#6a5acd"], ["slate-gray", "#708090"], ["snow", "#fffafa"], ["spring-green", "#00ff7f"], ["steel-blue", "#4682b4"], ["tan", "#d2b48c"], ["thistle", "#d8bfd8"], ["tomato", "#ff6347"], ["turquoise", "#40e0d0"], ["violet", "#ee82ee"], ["violet-red", "#d02090"], ["wheat", "#f5deb3"], ["white", "#ffffff"], ["white-smoke", "#f5f5f5"], ["yellow", "#ffff00"], ["yellow-green", "#9acd32"]]; +const FACE_DOCS={"flyspell-duplicate": "Flyspell face for words that appear twice in a row.", "flyspell-incorrect": "Flyspell face for misspelled words.", "hl-line": "Default face for highlighting the current line in Hl-Line mode.", "ghostel-default": "Base face used to derive ghostel terminal default fg/bg colors.", "ghostel-fake-cursor-box": "Face for the solid hint cursor drawn for box-style cursors.", "ghostel-fake-cursor": "Face for the hollow hint cursor drawn in copy and Emacs modes.", "ghostel-color-bright-white": "Face used to render bright white color code.", "ghostel-color-bright-cyan": "Face used to render bright cyan color code.", "ghostel-color-bright-magenta": "Face used to render bright magenta color code.", "ghostel-color-bright-blue": "Face used to render bright blue color code.", "ghostel-color-bright-yellow": "Face used to render bright yellow color code.", "ghostel-color-bright-green": "Face used to render bright green color code.", "ghostel-color-bright-red": "Face used to render bright red color code.", "ghostel-color-bright-black": "Face used to render bright black color code.", "ghostel-color-white": "Face used to render white color code.", "ghostel-color-cyan": "Face used to render cyan color code.", "ghostel-color-magenta": "Face used to render magenta color code.", "ghostel-color-blue": "Face used to render blue color code.", "ghostel-color-yellow": "Face used to render yellow color code.", "ghostel-color-green": "Face used to render green color code.", "ghostel-color-red": "Face used to render red color code.", "ghostel-color-black": "Face used to render black color code.", "apropos-misc-button": "Button face indicating a miscellaneous object type in Apropos.", "apropos-user-option-button": "Button face indicating a user option in Apropos.", "apropos-variable-button": "Button face indicating a variable in Apropos.", "apropos-function-button": "Button face indicating a function, macro, or command in Apropos.", "apropos-button": "Face for buttons that indicate a face in Apropos.", "apropos-property": "Face for property name in Apropos output, or nil for none.", "apropos-keybinding": "Face for lists of keybinding in Apropos output.", "apropos-symbol": "Face for the symbol name in Apropos output.", "hl-todo-flymake-type": "Face used for the Flymake diagnostics type \u2018hl-todo-flymake\u2019.", "hl-todo": "Base face used to highlight TODO and similar keywords.", "org-roam-dailies-calendar-note": "Face for dates with a daily-note in the calendar.", "org-roam-dim": "Face for the dimmer part of the widgets.", "org-roam-preview-region": "Face used by \u2018org-roam-highlight-preview-region-using-face\u2019.", "org-roam-preview-heading-selection": "Face for selected preview headings.", "org-roam-preview-heading-highlight": "Face for current preview headings.", "org-roam-preview-heading": "Face for preview headings.", "org-roam-olp": "Face for the OLP of the node.", "org-roam-title": "Face for Org-roam titles.", "org-roam-header-line": "Face for the \u2018header-line\u2019 in some Org-roam modes.", "malyon-face-reverse": "Face for reverse-video text.", "malyon-face-italic": "Italic face for game text.", "malyon-face-error": "Face for game errors.", "malyon-face-bold": "Bold face for game text.", "malyon-face-plain": "Basic face for game text.", "twentyfortyeight-face-2048": "Face for the tile 2048.", "twentyfortyeight-face-1024": "Face for the tile 1024.", "twentyfortyeight-face-512": "Face for the tile 512.", "twentyfortyeight-face-256": "Face for the tile 256.", "twentyfortyeight-face-128": "Face for the tile 128.", "twentyfortyeight-face-64": "Face for the tile 64.", "twentyfortyeight-face-32": "Face for the tile 32.", "twentyfortyeight-face-16": "Face for the tile 16.", "twentyfortyeight-face-8": "Face for the tile 8.", "twentyfortyeight-face-4": "Face for the tile 4.", "twentyfortyeight-face-2": "Face for the tile 2.", "tmr-mode-line-urgent": "Face for timers that will expire in the next 30 seconds.", "tmr-mode-line-soon": "Face for timers that will expire in the next 2 minutes.", "tmr-mode-line-active": "Face for active timers in the mode-line.", "tmr-tabulated-description": "Description of timer in the \u2018tmr-tabulated-view\u2019.", "tmr-tabulated-acknowledgement": "Acknowledgement indicator in the \u2018tmr-tabulated-view\u2019.", "tmr-tabulated-paused": "Face for styling the description of a paused timer.", "tmr-tabulated-remaining-time": "Remaining time in the \u2018tmr-tabulated-view\u2019.", "tmr-tabulated-end-time": "End time in the \u2018tmr-tabulated-view\u2019.", "tmr-tabulated-start-time": "Start time in the \u2018tmr-tabulated-view\u2019.", "tmr-paused": "Face for styling the description of a paused timer.", "tmr-finished": "Face for styling the description of a finished timer.", "tmr-must-be-acknowledged": "Face for styling the acknowledgment confirmation.", "tmr-is-acknowledged": "Face for styling the acknowledgment confirmation.", "tmr-end-time": "Face for styling the start time of a timer.", "tmr-start-time": "Face for styling the start time of a timer.", "tmr-description": "Face for styling the description of a timer.", "tmr-duration": "Face for styling the duration of a timer.", "magit-blame-date": "Face used for dates when blaming.", "magit-blame-name": "Face used for author and committer names when blaming.", "magit-blame-hash": "Face used for commit hashes when blaming.", "magit-blame-summary": "Face used for commit summaries when blaming.", "magit-blame-heading": "Face used for blame headings by default when blaming.", "magit-blame-dimmed": "Face used for the blame margin in some cases when blaming.", "magit-blame-margin": "Face used for the blame margin by default when blaming.", "magit-blame-highlight": "Face used for highlighting when blaming.", "magit-reflog-other": "Face for other commands in reflogs.", "magit-reflog-remote": "Face for pull and clone commands in reflogs.", "magit-reflog-cherry-pick": "Face for cherry-pick commands in reflogs.", "magit-reflog-rebase": "Face for rebase commands in reflogs.", "magit-reflog-reset": "Face for reset commands in reflogs.", "magit-reflog-checkout": "Face for checkout commands in reflogs.", "magit-reflog-merge": "Face for merge, checkout and branch commands in reflogs.", "magit-reflog-amend": "Face for amend commands in reflogs.", "magit-reflog-commit": "Face for commit commands in reflogs.", "magit-bisect-bad": "Face for bad bisect revisions.", "magit-bisect-skip": "Face for skipped bisect revisions.", "magit-bisect-good": "Face for good bisect revisions.", "magit-sequence-exec": "Face used in sequence sections.", "magit-sequence-onto": "Face used in sequence sections.", "magit-sequence-done": "Face used in sequence sections.", "magit-sequence-drop": "Face used in sequence sections.", "magit-sequence-head": "Face used in sequence sections.", "magit-sequence-part": "Face used in sequence sections.", "magit-sequence-stop": "Face used in sequence sections.", "magit-sequence-pick": "Face used in sequence sections.", "magit-filename": "Face for filenames.", "magit-cherry-equivalent": "Face for equivalent cherry commits.", "magit-cherry-unmatched": "Face for unmatched cherry commits.", "magit-signature-error": "Face for signatures that cannot be checked (e.g., missing key).", "magit-signature-revoked": "Face for signatures made by a revoked key.", "magit-signature-expired-key": "Face for signatures made by an expired key.", "magit-signature-expired": "Face for signatures that have expired.", "magit-signature-untrusted": "Face for good untrusted signatures.", "magit-signature-bad": "Face for bad signatures.", "magit-signature-good": "Face for good signatures.", "magit-keyword-squash": "Face for squash! and similar keywords in commit messages.", "magit-keyword": "Face for parts of commit messages inside brackets.", "magit-refname-pullreq": "Face for pullreq refnames.", "magit-refname-wip": "Face for wip refnames.", "magit-refname-stash": "Face for stash refnames.", "magit-refname": "Face for refnames without a dedicated face.", "magit-head": "Face for the symbolic ref \u2018HEAD\u2019.", "magit-branch-warning": "Face for warning about (missing) branch.", "magit-branch-upstream": "Face for upstream branch.", "magit-branch-current": "Face for current branch.", "magit-branch-local": "Face for local branches.", "magit-branch-remote-head": "Face for current branch.", "magit-branch-remote": "Face for remote branch head labels shown in log buffer.", "magit-tag": "Face for tag labels shown in log buffer.", "magit-hash": "Face for the commit object name in the log output.", "magit-dimmed": "Face for text that shouldn\u2019t stand out.", "magit-header-line-key": "Face for keys in the \u2018header-line\u2019.", "magit-header-line": "Face for the \u2018header-line\u2019 in some Magit modes.", "magit-header-line-log-select": "Face for the \u2018header-line\u2019 in \u2018magit-log-select-mode\u2019.", "magit-log-date": "Face for the date part of the log output.", "magit-log-author": "Face for the author part of the log output.", "magit-log-graph": "Face for the graph part of the log output.", "magit-diffstat-removed": "Face for removal indicator in diffstat.", "magit-diffstat-added": "Face for addition indicator in diffstat.", "magit-diff-whitespace-warning": "Face for highlighting whitespace errors added lines.", "magit-diff-context-highlight": "Face for lines in the current context in a diff.", "magit-diff-their-highlight": "Face for lines in a diff for their side in a conflict.", "magit-diff-base-highlight": "Face for lines in a diff for the base side in a conflict.", "magit-diff-our-highlight": "Face for lines in a diff for our side in a conflict.", "magit-diff-removed-highlight": "Face for lines in a diff that have been removed.", "magit-diff-added-highlight": "Face for lines in a diff that have been added.", "magit-diff-context": "Face for lines in a diff that are unchanged.", "magit-diff-their": "Face for lines in a diff for their side in a conflict.", "magit-diff-base": "Face for lines in a diff for the base side in a conflict.", "magit-diff-our": "Face for lines in a diff for our side in a conflict.", "magit-diff-removed": "Face for lines in a diff that have been removed.", "magit-diff-added": "Face for lines in a diff that have been added.", "magit-diff-conflict-heading": "Face for conflict markers.", "magit-diff-lines-boundary": "Face for boundary of marked lines in diff hunk.", "magit-diff-lines-heading": "Face for diff hunk heading when lines are marked.", "magit-diff-revision-summary-highlight": "Face for highlighted commit message summaries.", "magit-diff-revision-summary": "Face for commit message summaries.", "magit-diff-conflict-heading-highlight": "Face for conflict markers.", "magit-diff-hunk-region": "Face used by \u2018magit-diff-highlight-hunk-region-using-face\u2019.", "magit-diff-hunk-heading-selection": "Face for selected diff hunk headings.", "magit-diff-hunk-heading-highlight": "Face for current diff hunk headings.", "magit-diff-hunk-heading": "Face for diff hunk headings.", "magit-diff-file-heading-selection": "Face for selected diff file headings.", "magit-diff-file-heading-highlight": "Face for current diff file headings.", "magit-diff-file-heading": "Face for diff file headings.", "smerge-refined-added": "Face used for added characters shown by \u2018smerge-refine\u2019.", "smerge-refined-removed": "Face used for removed characters shown by \u2018smerge-refine\u2019.", "smerge-refined-changed": "Face used for char-based changes shown by \u2018smerge-refine\u2019.", "smerge-markers": "Face for the conflict markers.", "smerge-base": "Face for the base code.", "smerge-lower": "Face for the \u2018lower\u2019 version of a conflict.", "smerge-upper": "Face for the \u2018upper\u2019 version of a conflict.", "git-commit-comment-action": "Face used for actions in commit message comments.", "git-commit-comment-file": "Face used for file names in commit message comments.", "git-commit-comment-heading": "Face used for headings in commit message comments.", "git-commit-comment-detached": "Face used for detached \u2018HEAD\u2019 in commit message comments.", "git-commit-comment-branch-remote": "Face used for names of remote branches in commit message comments.", "git-commit-comment-branch-local": "Face used for names of local branches in commit message comments.", "git-commit-trailer-value": "Face used for Git trailer values in commit messages.", "git-commit-trailer-token": "Face used for Git trailer tokens in commit messages.", "git-commit-keyword": "Face used for keywords in commit messages.", "git-commit-nonempty-second-line": "Face used for non-whitespace on the second line of commit messages.", "git-commit-overlong-summary": "Face used for the tail of overlong commit message summaries.", "git-commit-summary": "Face used for the summary in commit messages.", "log-edit-unknown-header": "Face for unknown headers in \u2018log-edit-mode\u2019 buffers.", "log-edit-header": "Face for the headers in \u2018log-edit-mode\u2019 buffers.", "log-edit-headers-separator": "Face for the separator line in \u2018log-edit-mode\u2019 buffers.", "log-edit-summary": "Face for the summary in \u2018log-edit-mode\u2019 buffers.", "change-log-acknowledgment": "Face for highlighting acknowledgments.", "change-log-function": "Face for highlighting items of the form \u2018<....>\u2019.", "change-log-conditionals": "Face for highlighting conditionals of the form \u2018[...]\u2019.", "change-log-list": "Face for highlighting parenthesized lists of functions or variables.", "change-log-file": "Face for highlighting file names.", "change-log-email": "Face for highlighting author email addresses.", "change-log-name": "Face for highlighting author names.", "change-log-date": "Face used to highlight dates in date lines.", "magit-mode-line-process-error": "Face for \u2018mode-line-process\u2019 error status.", "magit-mode-line-process": "Face for \u2018mode-line-process\u2019 status when Git is running for side-effects.", "magit-process-ng": "Face for non-zero exit-status.", "magit-process-ok": "Face for zero exit-status.", "which-func": "Face used to highlight mode line function names.", "magit-left-margin": "Face used for the left margin.", "magit-section-child-count": "Face used for child counts at the end of some section headings.", "magit-section-heading-selection": "Face for selected section headings.", "magit-section-secondary-heading": "Face for section headings of some secondary headings.", "magit-section-heading": "Face for section headings.", "magit-section-highlight": "Face for highlighting the current section.", "llama-deleted-argument": "Face used for deleted arguments \u2018_%1\u2019...\u2018_%9\u2019, \u2018_&1\u2019...\u2018_&9\u2019 and \u2018_&*\u2019.", "llama-optional-argument": "Face used for optional arguments \u2018&1\u2019 through \u2018&9\u2019, \u2018&\u2019 and \u2018&*\u2019.", "llama-mandatory-argument": "Face used for mandatory arguments \u2018%1\u2019 through \u2018%9\u2019 and \u2018%\u2019.", "llama-llama-macro": "Face used for the name of the \u2018llama\u2019 macro.", "llama-##-macro": "Face used for the name of the \u2018##\u2019 macro.", "table-cell": "Face used for table cell contents.", "which-key-docstring-face": "Face for docstrings.", "which-key-special-key-face": "Face for special keys (SPC, TAB, RET).", "which-key-group-description-face": "Face for the key description when it is a group or prefix.", "which-key-highlighted-command-face": "Default face for highlighted command descriptions.", "which-key-local-map-description-face": "Face for the key description when it is found in \u2018current-local-map\u2019.", "which-key-command-description-face": "Face for the key description when it is a command.", "which-key-note-face": "Face for notes or hints occasionally provided.", "which-key-separator-face": "Face for the separator (default separator is an arrow).", "which-key-key-face": "Face for which-key keys.", "org-superstar-first": "Face used to display the first bullet of an inline task.", "org-superstar-ordered-item": "Face used to display ordered list item bullets.", "org-superstar-item": "Face used to display prettified item bullets.", "org-superstar-header-bullet": "Face containing distinguishing features headline bullets.", "org-superstar-leading": "Face used to display prettified leading stars in a headline.", "org-indent": "Face for outline indentation.", "company-box-numbers": "company-box-numbers is an alias for the face `company-tooltip'.", "company-box-scrollbar": "Face used for the scrollbar.", "company-box-background": "company-box-background is an alias for the face `company-tooltip'.", "company-box-selection": "company-box-selection is an alias for the face `company-tooltip-selection'.", "company-box-annotation": "company-box-annotation is an alias for the face `company-tooltip-annotation'.", "company-box-candidate": "company-box-candidate is an alias for the face `company-tooltip'.", "makefile-makepp-perl": "Face to use for additionally highlighting Perl code in Font-Lock mode.", "makefile-shell": "Face to use for additionally highlighting Shell commands in Font-Lock mode.", "makefile-targets": "Face to use for additionally highlighting rule targets in Font-Lock mode.", "makefile-space": "Face to use for highlighting leading spaces in Font-Lock mode.", "grep-heading": "Face of headings when \u2018grep-use-headings\u2019 is non-nil.", "ibuffer-locked-buffer": "Face used for locked buffers in Ibuffer.", "org-drill-hidden-cloze-face": "The face used to hide the contents of cloze phrases.", "org-drill-visible-cloze-hint-face": "The face used to hide the contents of cloze phrases.", "org-drill-visible-cloze-face": "The face used to hide the contents of cloze phrases.", "alert-trivial-face": "Trivial alert face.", "alert-low-face": "Low alert face.", "alert-normal-face": "Normal alert face.", "alert-moderate-face": "Moderate alert face.", "alert-high-face": "High alert face.", "alert-urgent-face": "Urgent alert face.", "org-faces-priority-d-dim": "Dimmed [#D] priority cookie for non-selected windows.", "org-faces-priority-c-dim": "Dimmed [#C] priority cookie for non-selected windows.", "org-faces-priority-b-dim": "Dimmed [#B] priority cookie for non-selected windows.", "org-faces-priority-a-dim": "Dimmed [#A] priority cookie for non-selected windows.", "org-faces-cancelled-dim": "Dimmed CANCELLED keyword for non-selected windows.", "org-faces-done-dim": "Dimmed DONE keyword for non-selected windows.", "org-faces-failed-dim": "Dimmed FAILED keyword for non-selected windows.", "org-faces-delegated-dim": "Dimmed DELEGATED keyword for non-selected windows.", "org-faces-stalled-dim": "Dimmed STALLED keyword for non-selected windows.", "org-faces-verify-dim": "Dimmed VERIFY keyword for non-selected windows.", "org-faces-waiting-dim": "Dimmed WAITING keyword for non-selected windows.", "org-faces-doing-dim": "Dimmed DOING keyword for non-selected windows.", "org-faces-project-dim": "Dimmed PROJECT keyword for non-selected windows.", "org-faces-todo-dim": "Dimmed TODO keyword for non-selected windows.", "org-faces-priority-d": "Face for the [#D] priority cookie.", "org-faces-priority-c": "Face for the [#C] priority cookie.", "org-faces-priority-b": "Face for the [#B] priority cookie.", "org-faces-priority-a": "Face for the [#A] priority cookie.", "org-faces-cancelled": "Face for the CANCELLED keyword.", "org-faces-done": "Face for the DONE keyword.", "org-faces-failed": "Face for the FAILED keyword.", "org-faces-delegated": "Face for the DELEGATED keyword.", "org-faces-stalled": "Face for the STALLED keyword.", "org-faces-verify": "Face for the VERIFY keyword.", "org-faces-waiting": "Face for the WAITING keyword.", "org-faces-doing": "Face for the DOING keyword.", "org-faces-project": "Face for the PROJECT keyword.", "org-faces-todo": "Face for the TODO keyword.", "eww-valid-certificate": "Face for web pages with valid certificates.", "eww-invalid-certificate": "Face for web pages with invalid certificates.", "eww-form-textarea": "Face for eww textarea inputs.", "eww-form-text": "Face for eww text inputs.", "eww-form-select": "Face for eww buffer buttons.", "eww-form-checkbox": "Face for eww buffer buttons.", "eww-form-file": "Face for eww buffer buttons.", "eww-form-submit": "Face for eww buffer buttons.", "gnus-header-content": "Face used for displaying header content.", "gnus-header-name": "Face used for displaying header names.", "gnus-header-newsgroups": "Face used for displaying newsgroups headers.", "gnus-header-subject": "Face used for displaying subject headers.", "gnus-header-from": "Face used for displaying from headers.", "gnus-header": "Base face used for all Gnus header faces.", "gnus-signature": "Face used for highlighting a signature in the article buffer.", "gnus-button": "Face used for highlighting a button in the article buffer.", "gnus-emphasis-highlight-words": "Face used for displaying highlighted words.", "gnus-emphasis-strikethru": "Face used for displaying strike-through text (-word-).", "gnus-emphasis-underline-bold-italic": "Face used for displaying underlined bold italic emphasized text.", "gnus-emphasis-bold-italic": "Face used for displaying bold italic emphasized text (/*word*/).", "gnus-emphasis-underline-italic": "Face used for displaying underlined italic emphasized text (_/word/_).", "gnus-emphasis-underline-bold": "Face used for displaying underlined bold emphasized text (_*word*_).", "gnus-emphasis-underline": "Face used for displaying underlined emphasized text (_word_).", "gnus-emphasis-italic": "Face used for displaying italic emphasized text (/word/).", "gnus-emphasis-bold": "Face used for displaying strong emphasized text (*word*).", "mm-uu-extract": "Face for extracted buffers.", "shr-sliced-image": "Face used for sliced images.", "shr-mark": "Face used for <mark> elements.", "shr-code": "Face used for rendering <code> blocks.", "shr-h6": "Face for <h6> elements.", "shr-h5": "Face for <h5> elements.", "shr-h4": "Face for <h4> elements.", "shr-h3": "Face for <h3> elements.", "shr-h2": "Face for <h2> elements.", "shr-h1": "Face for <h1> elements.", "shr-sup": "Face for <sup> and <sub> elements.", "shr-abbreviation": "Face for <abbr> elements.", "shr-selected-link": "Temporary face for externally visited link elements.", "shr-link": "Face for link elements.", "shr-strike-through": "Face for <s> elements.", "shr-text": "Face used for rendering text.", "message-signature-separator": "Face used for displaying the signature separator.", "message-mml": "Face used for displaying MML.", "message-cited-text-4": "Face used for displaying 4th-level cited text.", "message-cited-text-3": "Face used for displaying 3rd-level cited text.", "message-cited-text-2": "Face used for displaying 2nd-level cited text.", "message-cited-text-1": "Face used for displaying 1st-level cited text.", "message-separator": "Face used for displaying the separator.", "message-header-xheader": "Face used for displaying X-Header headers.", "message-header-name": "Face used for displaying header names.", "message-header-other": "Face used for displaying other headers.", "message-header-newsgroups": "Face used for displaying Newsgroups headers.", "message-header-subject": "Face used for displaying Subject headers.", "message-header-cc": "Face used for displaying Cc headers.", "message-header-to": "Face used for displaying To headers.", "gnus-splash": "Face for the splash screen.", "gnus-summary-low-read": "Face used for low interest read articles.", "gnus-summary-high-read": "Face used for high interest read articles.", "gnus-summary-normal-read": "Face used for normal interest read articles.", "gnus-summary-low-unread": "Face used for low interest unread articles.", "gnus-summary-high-unread": "Face used for high interest unread articles.", "gnus-summary-normal-unread": "Face used for normal interest unread articles.", "gnus-summary-low-undownloaded": "Face used for low interest uncached articles.", "gnus-summary-high-undownloaded": "Face used for high interest uncached articles.", "gnus-summary-normal-undownloaded": "Face used for normal interest uncached articles.", "gnus-summary-low-ancient": "Face used for low interest ancient articles.", "gnus-summary-high-ancient": "Face used for high interest ancient articles.", "gnus-summary-normal-ancient": "Face used for normal interest ancient articles.", "gnus-summary-low-ticked": "Face used for low interest ticked articles.", "gnus-summary-high-ticked": "Face used for high interest ticked articles.", "gnus-summary-normal-ticked": "Face used for normal interest ticked articles.", "gnus-summary-cancelled": "Face used for canceled articles.", "gnus-summary-selected": "Face used for selected articles.", "gnus-group-mail-low": "Low level mailgroup face.", "gnus-group-mail-low-empty": "Low level empty mailgroup face.", "gnus-group-mail-3": "Level 3 mailgroup face.", "gnus-group-mail-3-empty": "Level 3 empty mailgroup face.", "gnus-group-mail-2": "Level 2 mailgroup face.", "gnus-group-mail-2-empty": "Level 2 empty mailgroup face.", "gnus-group-mail-1": "Level 1 mailgroup face.", "gnus-group-mail-1-empty": "Level 1 empty mailgroup face.", "gnus-group-news-low": "Low level newsgroup face.", "gnus-group-news-low-empty": "Low level empty newsgroup face.", "gnus-group-news-6": "Level 6 newsgroup face.", "gnus-group-news-6-empty": "Level 6 empty newsgroup face.", "gnus-group-news-5": "Level 5 newsgroup face.", "gnus-group-news-5-empty": "Level 5 empty newsgroup face.", "gnus-group-news-4": "Level 4 newsgroup face.", "gnus-group-news-4-empty": "Level 4 empty newsgroup face.", "gnus-group-news-3": "Level 3 newsgroup face.", "gnus-group-news-3-empty": "Level 3 empty newsgroup face.", "gnus-group-news-2": "Level 2 newsgroup face.", "gnus-group-news-2-empty": "Level 2 empty newsgroup face.", "gnus-group-news-1": "Level 1 newsgroup face.", "gnus-group-news-1-empty": "Level 1 empty newsgroup face.", "doc-view-svg-face": "Face used for SVG images.", "sh-escaped-newline": "Face used for (non-escaped) backslash at end of a line in Shell-script mode.", "sh-quoted-exec": "Face to show quoted execs like `blabla`.", "sh-heredoc": "Face to show a here-document.", "org-mode-line-clock-overrun": "Face used for clock display for overrun tasks in mode line.", "org-mode-line-clock": "Face used for clock display in mode line.", "org-tag-group": "Face for group tags.", "org-macro": "Face for macros.", "org-latex-and-related": "Face used to highlight LaTeX data, entities and sub/superscript.", "org-agenda-calendar-sexp": "Face used to show events computed from a S-expression.", "org-agenda-calendar-event": "Face used to show events and appointments in the agenda.", "org-agenda-calendar-daterange": "Face used to show entries with a date range in the agenda.", "org-agenda-diary": "Face used for agenda entries that come from the Emacs diary.", "org-agenda-current-time": "Face used to show the current time in the time grid.", "org-time-grid": "Face used for time grids.", "org-agenda-filter-regexp": "Face for regexp(s) in the mode-line when filtering the agenda.", "org-agenda-filter-effort": "Face for effort in the mode-line when filtering the agenda.", "org-agenda-filter-category": "Face for categories in the mode-line when filtering the agenda.", "org-agenda-filter-tags": "Face for tag(s) in the mode-line when filtering the agenda.", "org-agenda-restriction-lock": "Face for showing the agenda restriction lock.", "org-upcoming-distant-deadline": "Face for items scheduled previously, not done, and have a distant deadline.", "org-upcoming-deadline": "Face for items scheduled previously, and not yet done.", "org-imminent-deadline": "Face for current deadlines in the agenda.", "org-scheduled-previously": "Face for items scheduled previously, and not yet done.", "org-agenda-dimmed-todo-face": "Face used to dim blocked tasks in the agenda.", "org-scheduled-today": "Face for items scheduled for a certain day.", "org-scheduled": "Face for items scheduled for a certain day.", "org-agenda-date-weekend": "Face used in agenda for weekend days.", "org-agenda-clocking": "Face marking the current clock item in the agenda.", "org-agenda-date-weekend-today": "Face used in agenda for today during weekends.", "org-agenda-date-today": "Face used in agenda for today.", "org-agenda-date": "Face used in agenda for normal days.", "org-agenda-structure-filter": "Face used for the current type of task filter in the agenda.", "org-agenda-structure-secondary": "Face used for secondary information in agenda block headers.", "org-agenda-structure": "Face used in agenda for captions and dates.", "org-clock-overlay": "Basic face for displaying the secondary selection.", "org-verse": "Face for #+BEGIN_VERSE ... #+END_VERSE blocks.", "org-quote": "Face for #+BEGIN_QUOTE ... #+END_QUOTE blocks.", "org-verbatim": "Face for fixed-with text like code snippets.", "org-inline-src-block": "Face used for inline source blocks as a whole.", "org-block-end-line": "Face used for the line delimiting the end of source blocks.", "org-block-begin-line": "Face used for the line delimiting the begin of source blocks.", "org-block": "Face used for text inside various blocks.", "org-document-info-keyword": "Face for document information keywords.", "org-document-info": "Face for document information such as the author and date.", "org-document-title": "Face for document title, i.e. that which follows the #+TITLE: keyword.", "org-meta-line": "Face for meta lines starting with \"#+\".", "org-code": "Face for fixed-width text like code snippets.", "org-formula": "Face for formulas.", "org-table-header": "Face for table header.", "org-table-row": "Face used to fontify whole table rows (including newlines and indentation).", "org-table": "Face used for tables.", "org-checkbox-statistics-done": "Face used for finished checkbox statistics.", "org-checkbox-statistics-todo": "Face used for unfinished checkbox statistics.", "org-checkbox": "Face for checkboxes.", "org-priority": "Face used for priority cookies.", "org-headline-done": "Face used to indicate that a headline is DONE.", "org-headline-todo": "Face used to indicate that a headline is marked as TODO.", "org-agenda-done": "Face used in agenda, to indicate lines switched to DONE.", "org-done": "Face used for todo keywords that indicate DONE items.", "org-todo": "Face for TODO keywords.", "org-list-dt": "Default face for definition terms in lists.", "org-tag": "Default face for tags.", "org-sexp-date": "Face for diary-like sexp date specifications.", "org-date-selected": "Face for highlighting the calendar day when using \u2018org-read-date\u2019.", "org-date": "Face for date/time stamps.", "org-target": "Face for link targets.", "org-ellipsis": "Face for the ellipsis in folded text.", "org-footnote": "Face for footnotes.", "org-link": "Face for links.", "org-cite-key": "Face for citation keys.", "org-cite": "Face for citations.", "org-archived": "Face for headline with the ARCHIVE tag.", "org-warning": "Face for deadlines and TODO keywords.", "org-agenda-column-dateline": "Face used in agenda column view for datelines with summaries.", "org-column-title": "Face for column display of entry properties.", "org-column": "Face for column display of entry properties.", "org-property-value": "Face used for the value of a property.", "org-drawer": "Face used for drawers.", "org-special-keyword": "Face used for special keywords.", "org-level-8": "Face used for level 8 headlines.", "org-level-7": "Face used for level 7 headlines.", "org-level-6": "Face used for level 6 headlines.", "org-level-5": "Face used for level 5 headlines.", "org-level-4": "Face used for level 4 headlines.", "org-level-3": "Face used for level 3 headlines.", "org-level-2": "Face used for level 2 headlines.", "org-level-1": "Face used for level 1 headlines.", "org-dispatcher-highlight": "Face for highlighted keys in the dispatcher.", "org-hide": "Face used to hide leading stars in headlines.", "org-default": "Face used for default text.", "calendar-month-header": "Face used for month headers in the calendar.", "calendar-weekend-header": "Face used for weekend column headers in the calendar.", "calendar-weekday-header": "Face used for weekday column headers in the calendar.", "holiday": "Face for indicating in the calendar dates that have holidays.", "diary": "Face for highlighting diary entries.", "calendar-today": "Face for indicating today\u2019s date in the calendar.", "lsp-inlay-hint-parameter-face": "Face for inlay parameter hints (e.g. function parameter names at", "lsp-inlay-hint-type-face": "Face for inlay type hints (e.g. inferred variable types).", "lsp-inlay-hint-face": "The face to use for the JavaScript inlays.", "lsp-installation-buffer-face": "Face used for installation buffers still in progress.", "lsp-installation-finished-buffer-face": "Face used for finished installation buffers.", "lsp-signature-face": "Used to display signatures in \u2018imenu\u2019, ....", "lsp-details-face": "Used to display additional information throughout \u2018lsp\u2019.", "lsp-rename-placeholder-face": "Face used to display the rename placeholder in.", "lsp-face-rename": "Face used to highlight the identifier being renamed.", "lsp-signature-highlight-function-argument": "The face to use to highlight function arguments in signatures.", "lsp-signature-posframe": "Background and foreground for \u2018lsp-signature-posframe\u2019.", "lsp-face-highlight-write": "Face used for highlighting symbols being written to.", "lsp-face-highlight-read": "Face used for highlighting symbols being read.", "lsp-face-highlight-textual": "Face used for textual occurrences of symbols.", "diff-refine-added": "Face used for added characters shown by \u2018diff-refine-hunk\u2019.", "diff-refine-removed": "Face used for removed characters shown by \u2018diff-refine-hunk\u2019.", "diff-refine-changed": "Face used for char-based changes shown by \u2018diff-refine-hunk\u2019.", "diff-error": "\u2018diff-mode\u2019 face for error messages from diff.", "diff-nonexistent": "\u2018diff-mode\u2019 face used to highlight nonexistent files in recursive diffs.", "diff-context": "\u2018diff-mode\u2019 face used to highlight context and other side-information.", "diff-function": "\u2018diff-mode\u2019 face used to highlight function names produced by \"diff -p\".", "diff-indicator-changed": "\u2018diff-mode\u2019 face used to highlight indicator of changed lines.", "diff-indicator-added": "\u2018diff-mode\u2019 face used to highlight indicator of added lines (+, >).", "diff-indicator-removed": "\u2018diff-mode\u2019 face used to highlight indicator of removed lines (-, <).", "diff-changed": "\u2018diff-mode\u2019 face used to highlight changed lines.", "diff-changed-unspecified": "\u2018diff-mode\u2019 face used to highlight changed lines.", "diff-added": "\u2018diff-mode\u2019 face used to highlight added lines.", "diff-removed": "\u2018diff-mode\u2019 face used to highlight removed lines.", "diff-hunk-header": "\u2018diff-mode\u2019 face used to highlight hunk header lines.", "diff-index": "\u2018diff-mode\u2019 face used to highlight index header lines.", "diff-file-header": "\u2018diff-mode\u2019 face used to highlight file header lines.", "diff-header": "\u2018diff-mode\u2019 face inherited by hunk and index header faces.", "vc-git-log-edit-summary-max-warning": "Face for Git commit summary lines beyond the maximum length.", "vc-git-log-edit-summary-target-warning": "Face for Git commit summary lines beyond the target length.", "xref-match": "Face used to highlight matches in the xref buffer.", "xref-line-number": "Face for displaying line numbers in the xref buffer.", "xref-file-header": "Face used to highlight file header in the xref buffer.", "edit-indirect-edited-region": "Face used to highlight an indirectly edited region.", "markdown-header-face-6": "Face for level 6 headers.", "markdown-header-face-5": "Face for level 5 headers.", "markdown-header-face-4": "Face for level 4 headers.", "markdown-header-face-3": "Face for level 3 headers.", "markdown-header-face-2": "Face for level 2 headers.", "markdown-header-face-1": "Face for level 1 headers.", "markdown-header-face": "Base face for headers.", "markdown-highlighting-face": "Face for highlighting.", "markdown-html-entity-face": "Face for HTML entities.", "markdown-html-attr-value-face": "Face for HTML attribute values.", "markdown-html-attr-name-face": "Face for HTML attribute names.", "markdown-html-tag-delimiter-face": "Face for HTML tag delimiters.", "markdown-html-tag-name-face": "Face for HTML tag names.", "markdown-hr-face": "Face for horizontal rules.", "markdown-highlight-face": "Face for mouse highlighting.", "markdown-gfm-checkbox-face": "Face for GFM checkboxes.", "markdown-metadata-value-face": "Face for metadata values.", "markdown-metadata-key-face": "Face for metadata keys.", "markdown-math-face": "Face for LaTeX expressions.", "markdown-comment-face": "Face for HTML comments.", "markdown-line-break-face": "Face for hard line breaks.", "markdown-link-title-face": "Face for reference link titles.", "markdown-plain-url-face": "Face for URLs that are also links.", "markdown-url-face": "Face for URLs that are part of markup.", "markdown-footnote-text-face": "Face for footnote text.", "markdown-footnote-marker-face": "Face for footnote markers.", "markdown-reference-face": "Face for link references.", "markdown-missing-link-face": "Face for the link text if the link points to a missing file.", "markdown-link-face": "Face for link text, ie the alias portion of a link.", "markdown-language-info-face": "Face for programming language info strings.", "markdown-language-keyword-face": "Face for programming language identifiers.", "markdown-table-face": "Face for tables.", "markdown-pre-face": "Face for preformatted text.", "markdown-inline-code-face": "Face for inline code.", "markdown-code-face": "Face for inline code, pre blocks, and fenced code blocks.", "markdown-blockquote-face": "Face for blockquote sections.", "markdown-list-face": "Face for list item markers.", "markdown-header-delimiter-face": "Base face for headers hash delimiter.", "markdown-header-rule-face": "Base face for headers rules.", "markdown-markup-face": "Face for markup elements.", "markdown-strike-through-face": "Face for strike-through text.", "markdown-bold-face": "Face for bold text.", "markdown-italic-face": "Face for italic text.", "outline-8": "Level 8.", "outline-7": "Level 7.", "outline-6": "Level 6.", "outline-5": "Level 5.", "outline-4": "Level 4.", "outline-3": "Level 3.", "outline-2": "Level 2.", "outline-1": "Level 1.", "lv-separator": "Face used to draw line between the lv window and the echo area.", "compilation-column-number": "Face for displaying column numbers in compiler messages.", "compilation-line-number": "Face for displaying line numbers in compiler messages.", "compilation-mode-line-exit": "Face for Compilation mode\u2019s \"exit\" mode line indicator.", "compilation-mode-line-run": "Face for Compilation mode\u2019s \"running\" mode line indicator.", "compilation-mode-line-fail": "Face for Compilation mode\u2019s \"error\" mode line indicator.", "compilation-info": "Face used to highlight compiler information.", "compilation-warning": "Face used to highlight compiler warnings.", "compilation-error": "Face used to highlight compiler errors.", "breakpoint-disabled": "Face for disabled breakpoint icon in fringe.", "breakpoint-enabled": "Face for enabled breakpoint icon in fringe.", "gud-highlight-current-line-face": "Face for highlighting the source code line being executed.", "ert-test-result-unexpected": "Face used for unexpected results in the ERT results buffer.", "ert-test-result-expected": "Face used for expected results in the ERT results buffer.", "yas--field-debug-face": "The face used for debugging some overlays normally hidden", "yas-field-highlight-face": "The face used to highlight the currently active field of a snippet", "treesit-explorer-field-name": "Face for field names in tree-sitter explorer.", "treesit-explorer-anonymous-node": "Face for anonymous nodes in tree-sitter explorer.", "dirvish-vc-needs-update-state": "Face used for \u2018needs-update\u2019 vc state in the Dirvish buffer.", "dirvish-vc-locked-state": "Face used for \u2018locked\u2019 vc state in the Dirvish buffer.", "dirvish-vc-conflict-state": "Face used for \u2018conflict\u2019 vc state in the Dirvish buffer.", "dirvish-vc-missing-state": "Face used for \u2018missing\u2019 vc state in the Dirvish buffer.", "dirvish-vc-removed-state": "Face used for \u2018removed\u2019 vc state in the Dirvish buffer.", "dirvish-vc-added-state": "Face used for \u2018added\u2019 vc state in the Dirvish buffer.", "dirvish-vc-edited-state": "Face used for \u2018edited\u2019 vc state in the Dirvish buffer.", "dirvish-git-commit-message-face": "Face for commit message overlays.", "dirvish-vc-unregistered-face": "Face used for \u2018unregistered\u2019 vc state in the Dirvish buffer.", "dirvish-vc-needs-merge-face": "Face used for \u2018needs-merge\u2019 vc state in the Dirvish buffer.", "shell-highlight-undef-alias-face": "Face used for shell command aliases.", "shell-highlight-undef-undefined-face": "Face used for non-existent shell commands.", "shell-highlight-undef-defined-face": "Face used for existing shell commands.", "dirvish-collapse-file-face": "Face used for files in \u2018collapse\u2019 attribute.", "dirvish-collapse-empty-dir-face": "Face used for empty directories in \u2018collapse\u2019 attribute.", "dirvish-collapse-dir-face": "Face used for directories in \u2018collapse\u2019 attribute.", "dirvish-narrow-split": "Face used to highlight punctuation character.", "dirvish-narrow-match-face-3": "Face for matches of components numbered 3 mod 4.", "dirvish-narrow-match-face-2": "Face for matches of components numbered 2 mod 4.", "dirvish-narrow-match-face-1": "Face for matches of components numbered 1 mod 4.", "dirvish-narrow-match-face-0": "Face for matches of components numbered 0 mod 4.", "dirvish-subtree-guide": "Face used for \u2018expanded-state\u2019 attribute.", "dirvish-subtree-state": "Face used for \u2018expanded-state\u2019 attribute.", "dirvish-emerge-group-title": "Face used for emerge group title.", "dirvish-proc-failed": "Face used if asynchronous process has failed.", "dirvish-proc-finished": "Face used if asynchronous process has finished.", "dirvish-proc-running": "Face used if asynchronous process is running.", "dirvish-inactive": "Face used for mode-line segments in unfocused Dirvish windows.", "dirvish-hl-line-inactive": "Face used for Dirvish line highlighting in unfocused Dirvish windows.", "dirvish-hl-line": "Face used for Dirvish line highlighting in focused Dirvish window.", "dashboard-footer-icon-face": "Face used for icon in footer.", "dashboard-footer-face": "Face used for footer text.", "dashboard-no-items-face": "Face used for no items.", "dashboard-items-face": "Face used for items.", "dashboard-heading": "Face used for widget headings.", "dashboard-navigator": "Face used for the navigator.", "dashboard-banner-logo-title": "Face used for the banner title.", "dashboard-text-banner": "Face used for text banners.", "rectangle-preview": "The face to use for the \u2018string-rectangle\u2019 preview.", "transient-mismatched-key": "Face optionally used to highlight keys without a short-argument.", "transient-nonstandard-key": "Face optionally used to highlight keys conflicting with short-argument.", "transient-unreachable-key": "Face used for keys unreachable from the current prefix sequence.", "transient-key-exit": "Face used for keys of suffixes that exit the menu.", "transient-key-stack": "Face used for keys of sub-menus that exit the parent menu.", "transient-key-recurse": "Face used for keys of sub-menus whose suffixes return to the parent menu.", "transient-key-return": "Face used for keys of suffixes that return to the parent menu.", "transient-key-noop": "Face used for keys of suffixes that currently cannot be invoked.", "transient-key-stay": "Face used for keys of suffixes that don\u2019t exit the menu.", "transient-key": "Face used for keys.", "transient-delimiter": "Face used for delimiters and separators.", "transient-higher-level": "Face optionally used to highlight suffixes on higher levels.", "transient-disabled-suffix": "Face used for disabled levels while editing suffix levels.", "transient-enabled-suffix": "Face used for enabled levels while editing suffix levels.", "transient-active-infix": "Face used for the infix for which the value is being read.", "transient-inapt-suffix": "Face used for suffixes that are inapt at this time.", "transient-unreachable": "Face used for suffixes unreachable from the current prefix sequence.", "transient-inactive-value": "Face used for inactive values.", "transient-value": "Face used for values.", "transient-inapt-argument": "Face used for inapt arguments with a (currently ignored) value.", "transient-inactive-argument": "Face used for inactive arguments.", "transient-argument": "Face used for enabled arguments.", "transient-heading": "Face used for headings.", "image-dired-thumb-flagged": "Face for images flagged for deletion in thumbnail buffer.", "image-dired-thumb-mark": "Face for marked images in thumbnail buffer.", "image-dired-thumb-header-image-count": "Face for the image count in the header line of the thumbnail buffer.", "image-dired-thumb-header-file-size": "Face for the file size in the header line of the thumbnail buffer.", "image-dired-thumb-header-directory-name": "Face for the directory name in the header line of the thumbnail buffer.", "image-dired-thumb-header-file-name": "Face for the file name in the header line of the thumbnail buffer.", "erc-keyword-face": "ERC face for your keywords.", "erc-fool-face": "ERC face for fools on the channel.", "erc-pal-face": "ERC face for your pals.", "erc-dangerous-host-face": "ERC face for people on dangerous hosts.", "erc-current-nick-face": "ERC face for occurrences of your current nickname.", "bg:erc-color-face15": "ERC face.", "bg:erc-color-face14": "ERC face.", "bg:erc-color-face13": "ERC face.", "bg:erc-color-face12": "ERC face.", "bg:erc-color-face11": "ERC face.", "bg:erc-color-face10": "ERC face.", "bg:erc-color-face9": "ERC face.", "bg:erc-color-face8": "ERC face.", "bg:erc-color-face7": "ERC face.", "bg:erc-color-face6": "ERC face.", "bg:erc-color-face5": "ERC face.", "bg:erc-color-face4": "ERC face.", "bg:erc-color-face3": "ERC face.", "bg:erc-color-face2": "ERC face.", "bg:erc-color-face1": "ERC face.", "bg:erc-color-face0": "ERC face.", "fg:erc-color-face15": "ERC face.", "fg:erc-color-face14": "ERC face.", "fg:erc-color-face13": "ERC face.", "fg:erc-color-face12": "ERC face.", "fg:erc-color-face11": "ERC face.", "fg:erc-color-face10": "ERC face.", "fg:erc-color-face9": "ERC face.", "fg:erc-color-face8": "ERC face.", "fg:erc-color-face7": "ERC face.", "fg:erc-color-face6": "ERC face.", "fg:erc-color-face5": "ERC face.", "fg:erc-color-face4": "ERC face.", "fg:erc-color-face3": "ERC face.", "fg:erc-color-face2": "ERC face.", "fg:erc-color-face1": "ERC face.", "fg:erc-color-face0": "ERC face.", "erc-underline-face": "ERC underline face.", "erc-spoiler-face": "ERC spoiler face.", "erc-inverse-face": "ERC inverse face.", "erc-italic-face": "ERC italic face.", "erc-bold-face": "ERC bold face.", "erc-command-indicator-face": "Face for echoed command lines, including the prompt.", "erc-keep-place-indicator-arrow": "Face for arrow value of option \u2018erc-keep-place-indicator-style\u2019.", "erc-keep-place-indicator-line": "Face for option \u2018erc-keep-place-indicator-style\u2019.", "comint-highlight-prompt": "Face to use to highlight prompts.", "comint-highlight-input": "Face to use to highlight user input.", "ansi-color-bright-white": "Face used to render bright white color code.", "ansi-color-bright-cyan": "Face used to render bright cyan color code.", "ansi-color-bright-magenta": "Face used to render bright magenta color code.", "ansi-color-bright-blue": "Face used to render bright blue color code.", "ansi-color-bright-yellow": "Face used to render bright yellow color code.", "ansi-color-bright-green": "Face used to render bright green color code.", "ansi-color-bright-red": "Face used to render bright red color code.", "ansi-color-bright-black": "Face used to render bright black color code.", "ansi-color-white": "Face used to render white color code.", "ansi-color-cyan": "Face used to render cyan color code.", "ansi-color-magenta": "Face used to render magenta color code.", "ansi-color-blue": "Face used to render blue color code.", "ansi-color-yellow": "Face used to render yellow color code.", "ansi-color-green": "Face used to render green color code.", "ansi-color-red": "Face used to render red color code.", "ansi-color-black": "Face used to render black color code.", "ansi-color-inverse": "Face used to render inverted video text.", "ansi-color-fast-blink": "Face used to render rapidly blinking text.", "ansi-color-slow-blink": "Face used to render slowly blinking text.", "ansi-color-underline": "Face used to render underlined text.", "ansi-color-italic": "Face used to render italic text.", "ansi-color-faint": "Face used to render faint text.", "ansi-color-bold": "Face used to render bold text.", "erc-button-nick-default-face": "Default face for a buttonized nickname.", "erc-button": "ERC button face.", "erc-fill-wrap-merge-indicator-face": "ERC \u2018fill-wrap\u2019 merge-indicator face.", "erc-timestamp-face": "ERC timestamp face.", "erc-nick-msg-face": "ERC nickname face for private messages.", "erc-nick-default-face": "ERC nickname default face.", "erc-my-nick-face": "ERC face for your current nickname in messages sent by you.", "erc-information": "Face for local administrative messages of low to moderate importance.", "erc-error-face": "ERC face for errors.", "erc-action-face": "ERC face for actions generated by /ME.", "erc-notice-face": "ERC face for notices.", "erc-prompt-face": "ERC face for the prompt.", "erc-input-face": "ERC face used for your input.", "erc-header-line": "ERC face used for the header line.", "erc-direct-msg-face": "ERC face used for messages you receive in the main erc buffer.", "erc-my-nick-prefix-face": "ERC face used for my user mode prefix.", "erc-nick-prefix-face": "ERC face used for user mode prefix.", "erc-default-face": "ERC default face.", "prescient-secondary-highlight": "Additional face used to highlight parts of candidates.", "prescient-primary-highlight": "Face used to highlight the parts of candidates that match the input.", "company-echo-common": "Face used for the common part of completions in the echo area.", "company-echo": "Face used for completions in the echo area.", "company-preview-search": "Face used for the search string in the completion preview.", "company-preview-common": "Face used for the common part of the completion preview.", "company-preview": "Face used for the completion preview.", "company-tooltip-scrollbar-track": "Face used for the tooltip scrollbar track (trough).", "company-tooltip-scrollbar-thumb": "Face used for the tooltip scrollbar thumb (bar).", "company-tooltip-quick-access-selection": "Face used for the selected quick-access hints shown in the tooltip.", "company-tooltip-quick-access": "Face used for the quick-access hints shown in the tooltip.", "company-tooltip-annotation-selection": "Face used for the selected completion annotation in the tooltip.", "company-tooltip-annotation": "Face used for the completion annotation in the tooltip.", "company-tooltip-common-selection": "Face used for the selected common completion in the tooltip.", "company-tooltip-common": "Face used for the common completion in the tooltip.", "company-tooltip-mouse": "Face used for the tooltip item under the mouse.", "company-tooltip-search-selection": "Face used for the search string inside the selection in the tooltip.", "company-tooltip-search": "Face used for the search string in the tooltip.", "company-tooltip-deprecated": "Face used for the deprecated items.", "company-tooltip-selection": "Face used for the selection in the tooltip.", "company-tooltip": "Face used for the tooltip.", "embark-selected": "Face for selected candidates.", "embark-collect-annotation": "Face for annotations in Embark Collect.", "embark-collect-group-separator": "Face for group titles in Embark Collect buffers.", "embark-collect-group-title": "Face for group titles in Embark Collect buffers.", "embark-collect-candidate": "Face for candidates in Embark Collect buffers.", "embark-verbose-indicator-shadowed": "Face used by the verbose action indicator for the shadowed targets.", "embark-verbose-indicator-title": "Face used by the verbose action indicator for the title.", "embark-verbose-indicator-documentation": "Face used by the verbose action indicator to display binding descriptions.", "embark-target": "Face used to highlight the target at point during \u2018embark-act\u2019.", "embark-keymap": "Face used to display keymaps.", "embark-keybinding": "Face used to display key bindings.", "embark-keybinding-repeat": "Face used to indicate keybindings as repeatable.", "ffap": "Face used to highlight the current buffer substring.", "orderless-match-face-3": "Face for matches of components numbered 3 mod 4.", "orderless-match-face-2": "Face for matches of components numbered 2 mod 4.", "orderless-match-face-1": "Face for matches of components numbered 1 mod 4.", "orderless-match-face-0": "Face for matches of components numbered 0 mod 4.", "consult-line-number-wrapped": "Face used to highlight line number prefixes after wrap around.", "consult-line-number-prefix": "Face used to highlight line number prefixes.", "consult-buffer": "Face used to highlight buffers in \u2018consult-buffer\u2019.", "consult-bookmark": "Face used to highlight bookmarks in \u2018consult-buffer\u2019.", "consult-grep-context": "Face used to highlight grep context in \u2018consult-grep\u2019.", "consult-file": "Face used to highlight files in \u2018consult-buffer\u2019.", "consult-line-number": "Face used to highlight location line in \u2018consult-global-mark\u2019.", "consult-key": "Face used to highlight keys, e.g., in \u2018consult-register\u2019.", "consult-help": "Face used to highlight help, e.g., in \u2018consult-register-store\u2019.", "consult-async-option": "Face used to highlight asynchronous command options.", "consult-async-split": "Face used to highlight punctuation character.", "consult-async-failed": "Face used if asynchronous process has failed.", "consult-async-finished": "Face used if asynchronous process has finished.", "consult-async-running": "Face used if asynchronous process is running.", "consult-narrow-indicator": "Face used for the narrowing indicator.", "consult-preview-insertion": "Face used for previews of text to be inserted.", "consult-preview-match": "Face used for match previews, e.g., in \u2018consult-line\u2019.", "consult-highlight-mark": "Face used for mark positions in completion candidates.", "consult-highlight-match": "Face used to highlight matches in the completion candidates.", "consult-preview-line": "Face used for line previews.", "nerd-icons-completion-dir-face": "Face for the directory icon.", "marginalia-file-priv-rare": "Face used to highlight a rare file privilege attribute.", "marginalia-file-priv-other": "Face used to highlight some other file privilege attribute.", "marginalia-file-priv-exec": "Face used to highlight the exec file privilege attribute.", "marginalia-file-priv-write": "Face used to highlight the write file privilege attribute.", "marginalia-file-priv-read": "Face used to highlight the read file privilege attribute.", "marginalia-file-priv-link": "Face used to highlight the link file privilege attribute.", "marginalia-file-priv-dir": "Face used to highlight the dir file privilege attribute.", "marginalia-file-priv-no": "Face used to highlight the no file privilege attribute.", "marginalia-file-owner": "Face used to highlight file owner and group names.", "marginalia-file-name": "Face used to highlight file names.", "marginalia-modified": "Face used to highlight buffer modification indicators.", "marginalia-string": "Face used to highlight string values.", "marginalia-number": "Face used to highlight numeric values.", "marginalia-size": "Face used to highlight sizes.", "marginalia-installed": "Face used to highlight the status of packages.", "marginalia-archive": "Face used to highlight package archives.", "marginalia-version": "Face used to highlight package versions.", "marginalia-date": "Face used to highlight dates.", "marginalia-mode": "Face used to highlight buffer major modes.", "marginalia-list": "Face used to highlight list expressions.", "marginalia-symbol": "Face used to highlight general symbols.", "marginalia-function": "Face used to highlight function symbols.", "marginalia-true": "Face used to highlight true variable values.", "marginalia-null": "Face used to highlight null or unbound variable values.", "marginalia-value": "Face used to highlight general variable values.", "marginalia-documentation": "Face used to highlight documentation strings.", "marginalia-off": "Face used to signal disabled modes.", "marginalia-on": "Face used to signal enabled modes.", "marginalia-lighter": "Face used to highlight minor mode lighters.", "marginalia-char": "Face used to highlight character annotations.", "marginalia-type": "Face used to highlight types.", "marginalia-key": "Face used to highlight keys.", "vertico-current": "Face used to highlight the currently selected candidate.", "vertico-group-separator": "Face used for the separator lines of the candidate groups.", "vertico-group-title": "Face used for the title text of the candidate group headlines.", "vertico-multiline": "Face used to highlight multiline replacement characters.", "nerd-icons-dsilver": "Face for dsilver icons.", "nerd-icons-lsilver": "Face for lsilver icons.", "nerd-icons-silver": "Face for silver icons.", "nerd-icons-dpink": "Face for dpink icons.", "nerd-icons-lpink": "Face for lpink icons.", "nerd-icons-pink": "Face for pink icons.", "nerd-icons-dcyan": "Face for dcyan icons.", "nerd-icons-lcyan": "Face for lcyan icons.", "nerd-icons-cyan-alt": "Face for cyan icons.", "nerd-icons-cyan": "Face for cyan icons.", "nerd-icons-dorange": "Face for dorange icons.", "nerd-icons-lorange": "Face for lorange icons.", "nerd-icons-orange": "Face for orange icons.", "nerd-icons-dpurple": "Face for dpurple icons.", "nerd-icons-lpurple": "Face for lpurple icons.", "nerd-icons-purple-alt": "Face for purple icons.", "nerd-icons-purple": "Face for purple icons.", "nerd-icons-dmaroon": "Face for dmaroon icons.", "nerd-icons-lmaroon": "Face for lmaroon icons.", "nerd-icons-maroon": "Face for maroon icons.", "nerd-icons-dblue": "Face for dblue icons.", "nerd-icons-lblue": "Face for lblue icons.", "nerd-icons-blue-alt": "Face for blue icons.", "nerd-icons-blue": "Face for blue icons.", "nerd-icons-dyellow": "Face for dyellow icons.", "nerd-icons-lyellow": "Face for lyellow icons.", "nerd-icons-yellow": "Face for yellow icons.", "nerd-icons-dgreen": "Face for dgreen icons.", "nerd-icons-lgreen": "Face for lgreen icons.", "nerd-icons-green": "Face for green icons.", "nerd-icons-red-alt": "Face for dred icons.", "nerd-icons-dred": "Face for dred icons.", "nerd-icons-lred": "Face for lred icons.", "nerd-icons-red": "Face for red icons.", "all-the-icons-dsilver": "Face for dsilver icons", "all-the-icons-lsilver": "Face for lsilver icons", "all-the-icons-silver": "Face for silver icons", "all-the-icons-dpink": "Face for dpink icons", "all-the-icons-lpink": "Face for lpink icons", "all-the-icons-pink": "Face for pink icons", "all-the-icons-dcyan": "Face for dcyan icons", "all-the-icons-lcyan": "Face for lcyan icons", "all-the-icons-cyan-alt": "Face for cyan icons", "all-the-icons-cyan": "Face for cyan icons", "all-the-icons-dorange": "Face for dorange icons", "all-the-icons-lorange": "Face for lorange icons", "all-the-icons-orange": "Face for orange icons", "all-the-icons-dpurple": "Face for dpurple icons", "all-the-icons-lpurple": "Face for lpurple icons", "all-the-icons-purple-alt": "Face for purple icons", "all-the-icons-purple": "Face for purple icons", "all-the-icons-dmaroon": "Face for dmaroon icons", "all-the-icons-lmaroon": "Face for lmaroon icons", "all-the-icons-maroon": "Face for maroon icons", "all-the-icons-dblue": "Face for dblue icons", "all-the-icons-lblue": "Face for lblue icons", "all-the-icons-blue-alt": "Face for blue icons", "all-the-icons-blue": "Face for blue icons", "all-the-icons-dyellow": "Face for dyellow icons", "all-the-icons-lyellow": "Face for lyellow icons", "all-the-icons-yellow": "Face for yellow icons", "all-the-icons-dgreen": "Face for dgreen icons", "all-the-icons-lgreen": "Face for lgreen icons", "all-the-icons-green": "Face for green icons", "all-the-icons-red-alt": "Face for dred icons", "all-the-icons-dred": "Face for dred icons", "all-the-icons-lred": "Face for lred icons", "all-the-icons-red": "Face for red icons", "adob--hack": "A hack to make fringe refresh work. Do not use.", "auto-dim-other-buffers-hide": "Face with a (presumably) dimmed background and matching foreground.", "auto-dim-other-buffers": "Face with a (presumably) dimmed background for non-selected window.", "epa-field-body": "Face for the body of the attribute field.", "epa-field-name": "Face for the name of the attribute field.", "epa-mark": "Face used for displaying the high validity.", "epa-string": "Face used for displaying the string.", "epa-validity-disabled": "Face used for displaying the disabled validity.", "epa-validity-low": "Face used for displaying the low validity.", "epa-validity-medium": "Face for medium validity EPA information.", "epa-validity-high": "Face for high validity EPA information.", "mm-command-output": "Face used for displaying output from commands.", "edmacro-label": "Face used for labels in \u2018edit-kbd-macro\u2019.", "kmacro-menu-marked": "Face used for keyboard macros marked for duplication.", "kmacro-menu-flagged": "Face used for keyboard macros flagged for deletion.", "kmacro-menu-mark": "Face used for the Keyboard Macro Menu marks.", "custom-group-subtitle": "Face for the \"Subgroups:\" subtitle in Custom buffers.", "custom-group-tag": "Face for low level group tags.", "custom-group-tag-1": "Face for group tags.", "custom-face-tag": "Face used for face tags.", "custom-visibility": "Face for the \u2018custom-visibility\u2019 widget.", "custom-variable-button": "Face used for pushable variable tags.", "custom-variable-tag": "Face used for unpushable variable tags.", "custom-variable-obsolete": "Face used for obsolete variables.", "custom-comment-tag": "Face used for the comment tag on variables or faces.", "custom-comment": "Face used for comments on variables or faces.", "custom-link": "Face for links in customization buffers.", "custom-state": "Face used for State descriptions in the customize buffer.", "custom-documentation": "Face used for documentation strings in customization buffers.", "custom-button-pressed-unraised": "Face for pressed custom buttons if \u2018custom-raised-buttons\u2019 is nil.", "custom-button-pressed": "Face for pressed custom buttons if \u2018custom-raised-buttons\u2019 is non-nil.", "custom-button-unraised": "Face for custom buffer buttons if \u2018custom-raised-buttons\u2019 is nil.", "custom-button-mouse": "Mouse face for custom buffer buttons if \u2018custom-raised-buttons\u2019 is non-nil.", "custom-button": "Face for custom buffer buttons if \u2018custom-raised-buttons\u2019 is non-nil.", "custom-saved": "Face used when the customize item has been saved.", "custom-themed": "Face used when the customize item has been set by a theme.", "custom-changed": "Face used when the customize item has been changed.", "custom-set": "Face used when the customize item has been set.", "custom-modified": "Face used when the customize item has been modified.", "custom-rogue": "Face used when the customize item is not defined for customization.", "custom-invalid": "Face used when the customize item is invalid.", "widget-button-pressed": "Face used for pressed buttons.", "widget-unselected": "Face used for unselected widgets.", "widget-inactive": "Face used for inactive widgets.", "widget-single-line-field": "Face used for editable fields spanning only a single line.", "widget-field": "Face used for editable fields.", "widget-button": "Face used for widget buttons.", "widget-documentation": "Face used for documentation text.", "bookmark-face": "Face used to highlight current line.", "bookmark-menu-bookmark": "Face used to highlight bookmark names in bookmark menu buffers.", "dired-ignored": "Face used for files suffixed with \u2018completion-ignored-extensions\u2019.", "dired-special": "Face used for sockets, pipes, block devices and char devices.", "dired-broken-symlink": "Face used for broken symbolic links.", "dired-symlink": "Face used for symbolic links.", "dired-directory": "Face used for subdirectories.", "dired-set-id": "Face used to highlight permissions of suid and guid files.", "dired-perm-write": "Face used to highlight permissions of group- and world-writable files.", "dired-warning": "Face used to highlight a part of a buffer that needs user attention.", "dired-flagged": "Face used for files flagged for deletion.", "dired-marked": "Face used for marked files.", "dired-mark": "Face used for Dired marks.", "dired-header": "Face used for directory headers.", "Info-quoted": "Face used for quoted elements.", "info-index-match": "Face used to highlight matches in an index entry.", "info-header-node": "Face for Info nodes in a node header.", "info-header-xref": "Face for Info cross-references in a node header.", "info-xref-visited": "Face for visited Info cross-references.", "info-xref": "Face for unvisited Info cross-references.", "info-menu-star": "Face used to emphasize \u2018*\u2019 in an Info menu.", "info-menu-header": "Face for headers in Info menus.", "info-title-4": "Face for info titles at level 4.", "info-title-3": "Face for info titles at level 3.", "info-title-2": "Face for info titles at level 2.", "info-title-1": "Face for info titles at level 1.", "info-node": "Face for Info node names.", "package-status-avail-obso": "Face used on the status and version of avail-obso packages.", "package-status-incompat": "Face used on the status and version of incompat packages.", "package-status-unsigned": "Face used on the status and version of unsigned packages.", "package-status-dependency": "Face used on the status and version of dependency packages.", "package-status-from-source": "Face used on the status and version of installed packages.", "package-status-installed": "Face used on the status and version of installed packages.", "package-status-disabled": "Face used on the status and version of disabled packages.", "package-status-held": "Face used on the status and version of held packages.", "package-status-new": "Face used on the status and version of new packages.", "package-status-available": "Face used on the status and version of available packages.", "package-status-external": "Face used on the status and version of external packages.", "package-status-built-in": "Face used on the status and version of built-in packages.", "package-description": "Face used on package description summaries in the package menu.", "package-name": "Face used on package names in the package menu.", "package-help-section-name": "Face used on section names in package description buffers.", "browse-url-button": "Face for \u2018browse-url\u2019 buttons (i.e., links).", "icon-button": "Face for buttons.", "icon": "Face for buttons.", "tooltip": "Face for tooltips.", "eldoc-highlight-function-argument": "Face used for the argument at point in a function\u2019s argument list.", "elisp-shorthand-font-lock-face": "Face for highlighting shorthands in Emacs Lisp.", "vc-ignored-state": "Face for VC modeline state when the file is registered, but ignored.", "vc-edited-state": "Face for VC modeline state when the file is edited.", "vc-missing-state": "Face for VC modeline state when the file is missing from the file system.", "vc-removed-state": "Face for VC modeline state when the file was removed from the VC system.", "vc-conflict-state": "Face for VC modeline state when the file contains merge conflicts.", "vc-locally-added-state": "Face for VC modeline state when the file is locally added.", "vc-locked-state": "Face for VC modeline state when the file locked.", "vc-needs-update-state": "Face for VC modeline state when the file needs update.", "vc-up-to-date-state": "Face for VC modeline state when the file is up to date.", "vc-state-base": "Base face for VC state indicator.", "buffer-menu-buffer": "Face for buffer names in the Buffer Menu.", "tabulated-list-fake-header": "Face used on fake header lines.", "match": "Face used to highlight matches permanently.", "query-replace": "Face for highlighting query replacement matches.", "tab-bar-tab-ungrouped": "Tab bar face for ungrouped tab when tab groups are used.", "tab-bar-tab-group-inactive": "Tab bar face for inactive group tab.", "tab-bar-tab-group-current": "Tab bar face for current group tab.", "tab-bar-tab-inactive": "Tab bar face for non-selected tab.", "tab-bar-tab": "Tab bar face for selected tab.", "file-name-shadow": "Face used by \u2018file-name-shadow-mode\u2019 for the shadow.", "isearch-group-2": "Face for highlighting Isearch the even group matches.", "isearch-group-1": "Face for highlighting Isearch the odd group matches.", "lazy-highlight": "Face for lazy highlighting of matches other than the current one.", "isearch-fail": "Face for highlighting failed part in Isearch echo-area message.", "isearch": "Face for highlighting Isearch matches.", "mouse-drag-and-drop-region": "Face to highlight original text during dragging.", "font-lock-misc-punctuation-face": "Font Lock mode face used to highlight miscellaneous punctuation.", "font-lock-delimiter-face": "Font Lock mode face used to highlight delimiters.", "font-lock-bracket-face": "Font Lock mode face used to highlight brackets, braces, and parens.", "font-lock-punctuation-face": "Font Lock mode face used to highlight punctuation characters.", "font-lock-property-use-face": "Font Lock mode face used to highlight property references.", "font-lock-property-name-face": "Font Lock mode face used to highlight properties of an object.", "font-lock-operator-face": "Font Lock mode face used to highlight operators.", "font-lock-number-face": "Font Lock mode face used to highlight numbers.", "font-lock-escape-face": "Font Lock mode face used to highlight escape sequences in strings.", "font-lock-regexp-grouping-construct": "Font Lock mode face used to highlight grouping constructs in Lisp regexps.", "font-lock-regexp-grouping-backslash": "Font Lock mode face for backslashes in Lisp regexp grouping constructs.", "font-lock-regexp-face": "Font Lock mode face used to highlight regexp literals.", "font-lock-preprocessor-face": "Font Lock mode face used to highlight preprocessor directives.", "font-lock-negation-char-face": "Font Lock mode face used to highlight easy to overlook negation.", "font-lock-warning-face": "Font Lock mode face used to highlight warnings.", "font-lock-constant-face": "Font Lock mode face used to highlight constants and labels.", "font-lock-type-face": "Font Lock mode face used to highlight type and class names.", "font-lock-variable-use-face": "Font Lock mode face used to highlight variable references.", "font-lock-variable-name-face": "Font Lock mode face used to highlight variable names.", "font-lock-function-call-face": "Font Lock mode face used to highlight function calls.", "font-lock-function-name-face": "Font Lock mode face used to highlight function names.", "font-lock-builtin-face": "Font Lock mode face used to highlight builtins.", "font-lock-keyword-face": "Font Lock mode face used to highlight keywords.", "font-lock-doc-markup-face": "Font Lock mode face used to highlight embedded documentation mark-up.", "font-lock-doc-face": "Font Lock mode face used to highlight documentation embedded in program code.", "font-lock-string-face": "Font Lock mode face used to highlight strings.", "font-lock-comment-delimiter-face": "Font Lock mode face used to highlight comment delimiters.", "font-lock-comment-face": "Font Lock mode face used to highlight comments.", "completions-common-part": "Face for the parts of completions which matched the pattern.", "completions-first-difference": "Face for the first character after point in completions.", "completions-highlight": "Default face for highlighting the current completion candidate.", "completions-annotations": "Face to use for annotations in the *Completions* buffer.", "completions-group-separator": "Face used for the separator lines between the candidate groups.", "completions-group-title": "Face used for the title text of the candidate group headlines.", "blink-matching-paren-offscreen": "Face for showing in the echo area matched open paren that is off-screen.", "separator-line": "Face for separator lines.", "next-error-message": "Face used to highlight the current error message in the \u2018next-error\u2019 buffer.", "next-error": "Face used to highlight next error locus.", "confusingly-reordered": "Face for highlighting text that was bidi-reordered in confusing ways.", "help-for-help-header": "Face used for headers in the \u2018help-for-help\u2019 buffer.", "abbrev-table-name": "Face used for displaying the abbrev table name in \u2018edit-abbrevs-mode\u2019.", "button": "Default face used for buttons.", "show-paren-mismatch": "Face used for a mismatching paren.", "show-paren-match-expression": "Face used for a matching paren when highlighting the whole expression.", "show-paren-match": "Face used for a matching paren.", "tty-menu-selected-face": "Face for displaying the currently selected item in TTY menus.", "tty-menu-disabled-face": "Face for displaying disabled items in TTY menus.", "tty-menu-enabled-face": "Face for displaying enabled items in TTY menus.", "read-multiple-choice-face": "Face for the symbol name in \u2018read-multiple-choice\u2019 output.", "success": "Basic face used to indicate successful operation.", "warning": "Basic face used to highlight warnings.", "error": "Basic face used to highlight errors and to denote failure.", "glyphless-char": "Face for displaying non-graphic characters (e.g. U+202A (LRE)).", "help-key-binding": "Face for keybindings in *Help* buffers.", "help-argument-name": "Face to highlight argument names in *Help* buffers.", "menu": "Basic face for the font and colors of the menu bar and popup menus.", "tab-line": "Tab line face.", "tab-bar": "Tab bar face.", "tool-bar": "Basic tool-bar face.", "mouse": "Basic face for the mouse color under X.", "cursor": "Basic face for the cursor color under X.", "border": "Basic face for the frame border under X.", "scroll-bar": "Basic face for the scroll bar colors under X.", "fringe": "Basic face for the fringes to the left and right of windows under X.", "minibuffer-prompt": "Face for minibuffer prompts.", "child-frame-border": "Basic face for the internal border of child frames.", "internal-border": "Basic face for the internal border.", "window-divider-last-pixel": "Basic face for last pixel line/column of window dividers.", "window-divider-first-pixel": "Basic face for first pixel line/column of window dividers.", "window-divider": "Basic face for window dividers.", "vertical-border": "Face used for vertical window dividers on ttys.", "header-line-highlight": "Basic header line face for highlighting.", "header-line": "Basic header-line face.", "mode-line-buffer-id": "Face used for buffer identification parts of the mode line.", "mode-line-emphasis": "Face used to emphasize certain mode line features.", "mode-line-highlight": "Basic mode line face for highlighting.", "mode-line-inactive": "Basic mode line face for non-selected windows.", "mode-line-active": "Face for the selected mode line.", "mode-line": "Face for the mode lines as well as header lines.", "nobreak-hyphen": "Face for displaying nobreak hyphens.", "nobreak-space": "Face for displaying nobreak space.", "homoglyph": "Face for lookalike characters.", "escape-glyph": "Face for characters displayed as sequences using \u2018^\u2019 or \u2018\\\u2019.", "fill-column-indicator": "Face for displaying fill column indicator.", "line-number-minor-tick": "Face for highlighting \"minor ticks\" (as in a ruler).", "line-number-major-tick": "Face for highlighting \"major ticks\" (as in a ruler).", "line-number-current-line": "Face for displaying the current line number.", "line-number": "Face for displaying line numbers.", "trailing-whitespace": "Basic face for highlighting trailing whitespace.", "secondary-selection": "Basic face for displaying the secondary selection.", "region": "Basic face for highlighting the region.", "highlight": "Basic face for highlighting.", "link-visited": "Basic face for visited links.", "link": "Basic face for unvisited links.", "shadow": "Basic face for shadowed text.", "variable-pitch-text": "The proportional face used for longer texts.", "variable-pitch": "The basic variable-pitch face.", "fixed-pitch-serif": "The basic fixed-pitch face with serifs.", "fixed-pitch": "The basic fixed-pitch face.", "underline": "Basic underlined face.", "bold-italic": "Basic bold-italic face.", "italic": "Basic italic face.", "bold": "Basic bold face.", "default": "Basic default face."}, SYNTAX_DOCS={"bg": "Basic default face.", "p": "Basic default face.", "kw": "Font Lock mode face used to highlight keywords.", "bi": "Font Lock mode face used to highlight builtins.", "pp": "Font Lock mode face used to highlight preprocessor directives.", "fnd": "Font Lock mode face used to highlight function names.", "fnc": "Font Lock mode face used to highlight function calls.", "ty": "Font Lock mode face used to highlight type and class names.", "prop": "Font Lock mode face used to highlight properties of an object.", "con": "Font Lock mode face used to highlight constants and labels.", "num": "Font Lock mode face used to highlight numbers.", "str": "Font Lock mode face used to highlight strings.", "esc": "Font Lock mode face used to highlight escape sequences in strings.", "re": "Font Lock mode face used to highlight regexp literals.", "doc": "Font Lock mode face used to highlight documentation embedded in program code.", "cm": "Font Lock mode face used to highlight comments.", "cmd": "Font Lock mode face used to highlight comment delimiters.", "var": "Font Lock mode face used to highlight variable names.", "op": "Font Lock mode face used to highlight operators.", "punc": "Font Lock mode face used to highlight punctuation characters."}; // face/category -> docstring first line, for element hovers +let MAP={"kw": "#d3d3d3", "bi": "#d3d3d3", "pp": "#d3d3d3", "fnd": "#0000ff", "fnc": "#0000ff", "dec": "", "ty": "#e5e5e5", "prop": "#e5e5e5", "con": "#d3d3d3", "num": "#000000", "esc": "#000000", "str": "#696969", "re": "#696969", "doc": "#696969", "cm": "#696969", "cmd": "#696969", "var": "#e5e5e5", "op": "#000000", "punc": "#000000", "p": "#000000", "bg": "#ffffff"}, PALETTE=[["#ffffff", "bg", "ground"], ["#000000", "fg", "ground"], ["#d3d3d3", "lightgray", "lightgray"], ["#0000ff", "blue1", "blue"], ["#e5e5e5", "gray90", "gray"], ["#696969", "dimgray", "dimgray"], ["#eedc82", "lightgoldenrod2", "lightgoldenrod"], ["#b4eeb4", "darkseagreen2", "darkseagreen"], ["#bfbfbf", "grey75", "grey"], ["#333333", "grey20", "grey"], ["#f2f2f2", "grey95", "grey"], ["#ff00ff", "magenta", "magenta"], ["#b0e2ff", "lightskyblue1", "lightskyblue"], ["#cd00cd", "magenta3", "magenta"], ["#afeeee", "paleturquoise", "paleturquoise"], ["#ffc1c1", "rosybrown1", "rosybrown"], ["#40e0d0", "turquoise", "turquoise"], ["#a020f0", "purple", "purple"], ["#3a5fcd", "royalblue3", "royalblue"], ["#ff0000", "red", "red"], ["#ff8c00", "dark-orange", "dark-orange"], ["#228b22", "forestgreen", "forestgreen"], ["#8b6508", "darkgoldenrod4", "darkgoldenrod"], ["#8b4c39", "salmon4", "salmon"], ["#22aa22", "color-24", "color-24"], ["#ddffdd", "color-25", "color-25"], ["#cceecc", "color-26", "color-26"], ["#aa2222", "color-27", "color-27"], ["#ffdddd", "color-28", "color-28"], ["#eecccc", "color-29", "color-29"], ["#7f7f7f", "grey50", "grey"], ["#cccccc", "grey80", "grey"], ["#cd8162", "lightsalmon3", "lightsalmon"], ["#aaaa11", "color-33", "color-33"], ["#ffffcc", "color-34", "color-34"], ["#eeeebb", "color-35", "color-35"], ["#4a708b", "skyblue4", "skyblue"], ["#6e8b3d", "darkolivegreen4", "darkolivegreen"], ["#8b6914", "goldenrod4", "goldenrod"], ["#999999", "grey60", "grey"], ["#4d4d4d", "grey30", "grey"], ["#b22222", "firebrick", "firebrick"], ["#00ff00", "green", "green"], ["#556b2f", "darkolivegreen", "darkolivegreen"], ["#8b3a3a", "indianred4", "indianred"], ["#b8860b", "darkgoldenrod", "darkgoldenrod"], ["#00ffff", "cyan", "cyan"], ["#66cdaa", "medium-aquamarine", "medium-aquamarine"], ["#ffa500", "orange", "orange"], ["#d02090", "violet-red", "violet-red"], ["#add8e6", "light-blue", "light-blue"], ["#cd5c5c", "indian-red", "indian-red"], ["#aaa", "color-52", "color-52"], ["#000", "color-53", "color-53"], ["#aa0", "color-54", "color-54"], ["#070", "color-55", "color-55"], ["#daa520", "goldenrod", "goldenrod"], ["#00bfff", "deep-sky-blue", "deep-sky-blue"], ["#ee00ee", "magenta2", "magenta"], ["#ffff00", "yellow", "yellow"], ["#6b6b6b", "color-60", "color-60"], ["#979797", "color-61", "color-61"], ["unspecified", "color-62", "color-62"], ["#223fbf", "color-63", "color-63"], ["#8f0075", "color-64", "color-64"], ["#145a00", "color-65", "color-65"], ["#804000", "color-66", "color-66"], ["#efcbcf", "color-67", "color-67"], ["#ffd700", "gold", "gold"], ["#8b0000", "darkred", "darkred"], ["#f0e68c", "khaki", "khaki"], ["#8b008b", "dark-magenta", "dark-magenta"], ["#ff4500", "orange-red", "orange-red"], ["#deb887", "burlywood", "burlywood"], ["#cd8500", "orange3", "orange"], ["#00008b", "dark-blue", "dark-blue"], ["#9400d3", "dark-violet", "dark-violet"], ["#6a9fb5", "color-77", "color-77"], ["#2188b6", "color-78", "color-78"], ["#75b5aa", "color-79", "color-79"], ["#0595bd", "color-80", "color-80"], ["#446674", "color-81", "color-81"], ["#48746d", "color-82", "color-82"], ["#6d8143", "color-83", "color-83"], ["#72584b", "color-84", "color-84"], ["#915b2d", "color-85", "color-85"], ["#7e5d5f", "color-86", "color-86"], ["#694863", "color-87", "color-87"], ["#843031", "color-88", "color-88"], ["#838484", "color-89", "color-89"], ["#b48d56", "color-90", "color-90"], ["#90a959", "color-91", "color-91"], ["#677174", "color-92", "color-92"], ["#2c7d6e", "color-93", "color-93"], ["#3d6837", "color-94", "color-94"], ["#ce7a4e", "color-95", "color-95"], ["#ff505b", "color-96", "color-96"], ["#e69dd6", "color-97", "color-97"], ["#eb595a", "color-98", "color-98"], ["#7f7869", "color-99", "color-99"], ["#ff9300", "color-100", "color-100"], ["#8f5536", "color-101", "color-101"], ["#d4843e", "color-102", "color-102"], ["#fc505b", "color-103", "color-103"], ["#68295b", "color-104", "color-104"], ["#5d54e1", "color-105", "color-105"], ["#ac4142", "color-106", "color-106"], ["#716e68", "color-107", "color-107"], ["#ffcc0e", "color-108", "color-108"], ["#8b1a1a", "firebrick4", "firebrick"], ["#fff8dc", "cornsilk", "cornsilk"], ["#f5deb3", "wheat", "wheat"], ["#cd0000", "red3", "red"], ["#0000cd", "blue3", "blue"], ["#cc9393", "color-114", "color-114"], ["#bebebe", "gray", "gray"], ["#88090b", "color-116", "color-116"], ["#707183", "color-117", "color-117"], ["#7388d6", "color-118", "color-118"], ["#909183", "color-119", "color-119"], ["#709870", "color-120", "color-120"], ["#907373", "color-121", "color-121"], ["#6276ba", "color-122", "color-122"], ["#858580", "color-123", "color-123"], ["#80a880", "color-124", "color-124"], ["#887070", "color-125", "color-125"], ["#1e90ff", "dodger-blue", "dodger-blue"], ["#ff69b4", "hot-pink", "hot-pink"], ["#da70d6", "orchid", "orchid"], ["#fa8072", "salmon", "salmon"], ["#00ff7f", "spring-green", "spring-green"], ["#800040", "color-131", "color-131"], ["#603f00", "color-132", "color-132"], ["#004476", "color-133", "color-133"], ["#2266ff", "color-134", "color-134"], ["#dd4488", "color-135", "color-135"], ["#8fbc8f", "color-136", "color-136"], ["#5f9ea0", "color-137", "color-137"], ["#ffffe0", "lightyellow1", "lightyellow"], ["#3e3c36", "color-139", "color-139"], ["#8b8989", "snow4", "snow"], ["#242424", "grey14", "grey"], ["#cd69c9", "orchid3", "orchid"], ["#dda0dd", "plum", "plum"], ["#000053", "color-144", "color-144"], ["#001970", "color-145", "color-145"], ["#002984", "color-146", "color-146"], ["#49599a", "color-147", "color-147"], ["#9499b7", "color-148", "color-148"], ["#cdc9c9", "snow3", "snow"], ["#eeb422", "goldenrod2", "goldenrod"], ["#68228b", "darkorchid4", "darkorchid"]], SYNTAX={"kw": {"fg": "#d3d3d3", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "bi": {"fg": "#d3d3d3", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "pp": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": "font-lock-builtin-face", "height": null}, "fnd": {"fg": "#0000ff", "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "fnc": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": "font-lock-function-name-face", "height": null}, "dec": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "ty": {"fg": "#e5e5e5", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "prop": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": "font-lock-variable-name-face", "height": null}, "con": {"fg": "#d3d3d3", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": null, "underline": {"style": "line", "color": null}, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "num": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "esc": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": "font-lock-regexp-grouping-backslash", "height": null}, "str": {"fg": "#696969", "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": "italic", "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "re": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": "font-lock-string-face", "height": null}, "doc": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": "font-lock-string-face", "height": null}, "cm": {"fg": "#696969", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": "italic", "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "cmd": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": "font-lock-comment-face", "height": null}, "var": {"fg": "#e5e5e5", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": "italic", "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "op": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "punc": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "p": {"fg": "#000000", "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "bg": {"fg": "#ffffff", "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}}, UIMAP={"cursor": {"fg": null, "bg": "#000000", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "region": {"fg": null, "bg": "#eedc82", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": true, "inherit": null, "height": null}, "hl-line": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": true, "inherit": "highlight", "height": null}, "highlight": {"fg": null, "bg": "#b4eeb4", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "mode-line": {"fg": "#000000", "bg": "#bfbfbf", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": {"style": "released", "width": 1, "color": null}, "inverse": false, "extend": false, "inherit": null, "height": null}, "mode-line-highlight": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": {"style": "released", "width": 1, "color": null}, "inverse": false, "extend": false, "inherit": null, "height": null}, "mode-line-inactive": {"fg": "#333333", "bg": "#e5e5e5", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": {"style": "line", "width": 1, "color": "#bfbfbf"}, "inverse": false, "extend": false, "inherit": "mode-line", "height": null}, "fringe": {"fg": null, "bg": "#f2f2f2", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "line-number": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": ["shadow", "default"], "height": null}, "line-number-current-line": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": "line-number", "height": null}, "minibuffer-prompt": {"fg": "#ff00ff", "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "isearch": {"fg": "#b0e2ff", "bg": "#cd00cd", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "lazy-highlight": {"fg": null, "bg": "#afeeee", "distant-fg": "#000000", "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "isearch-fail": {"fg": null, "bg": "#ffc1c1", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "show-paren-match": {"fg": null, "bg": "#40e0d0", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "show-paren-mismatch": {"fg": "#ffffff", "bg": "#a020f0", "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "link": {"fg": "#3a5fcd", "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": {"style": "line", "color": null}, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "error": {"fg": "#ff0000", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "warning": {"fg": "#ff8c00", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "success": {"fg": "#228b22", "bg": null, "distant-fg": null, "family": null, "weight": "bold", "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}, "vertical-border": {"fg": null, "bg": null, "distant-fg": null, "family": null, "weight": null, "slant": null, "underline": null, "strike": null, "overline": null, "box": null, "inverse": false, "extend": false, "inherit": null, "height": null}}; +let LOCKED=new Set([]); // rows whose choice is decided (controls disabled, skipped by erase/reset batch actions) const DELTAE_MIN=0.02; // OKLab ΔE below this = colors too close to tell apart (perceptual-metrics spec) +const DEFAULT_UIMAP=JSON.parse(JSON.stringify(UIMAP)); +function syntaxBlank(k){return {fg:MAP[k]||null,bg:null,'distant-fg':null,family:null,weight:null,slant:null,underline:null,strike:null,overline:null,box:null,inverse:false,extend:false,inherit:null,height:null};} +function syncSyntaxCache(k){const s=SYNTAX[k]||syntaxBlank(k);MAP[k]=s.fg||'';} +function syncAllSyntaxCache(){CATS.forEach(c=>syncSyntaxCache(c[0]));} +function syncSyntaxFromCache(){CATS.forEach(c=>{const k=c[0];syntaxFace(k).fg=MAP[k]||null;});} +function syntaxFace(k){if(!SYNTAX[k])SYNTAX[k]=syntaxBlank(k);return SYNTAX[k];} +function setSyntaxFg(k,hex){syntaxFace(k).fg=hex||null;syncSyntaxCache(k);} +syncAllSyntaxCache(); +const DEFAULT_SYNTAX=JSON.parse(JSON.stringify(SYNTAX)); // --- tier-3 package faces: pure state helpers (Phase 1) --- -function pname(n){if(!n)return null;if(/^#/.test(n))return n;const p=PALETTE.find(p=>p[1]===n);return p?p[0]:null;} -function seedPkgmap(){const m={};for(const app in APPS){m[app]={};for(const row of APPS[app].faces){const face=row[0],d=row[2]||{};m[app][face]={fg:pname(d.fg),bg:pname(d.bg),bold:!!d.bold,italic:!!d.italic,underline:!!d.underline,strike:!!d.strike,inherit:d.inherit||null,height:d.height||1,source:'default'};}}return m;} -function packagesForExport(map){const out={};for(const app in map){const faces={};for(const face in map[app]){const f=map[app][face];if(f.source==='default'||f.source==='user'||f.source==='cleared'){const o={fg:f.fg,bg:f.bg,bold:f.bold,italic:f.italic,underline:!!f.underline,strike:!!f.strike,inherit:f.inherit,source:f.source};if(f.height&&f.height!==1)o.height=f.height;faces[face]=o;}}if(Object.keys(faces).length)out[app]=faces;}return out;} -function mergePackagesInto(map,pkgs){if(!pkgs)return;for(const app in pkgs){if(!map[app])map[app]={};for(const face in pkgs[app]){const f=pkgs[app][face]||{};map[app][face]={fg:f.fg??null,bg:f.bg??null,bold:!!f.bold,italic:!!f.italic,underline:!!f.underline,strike:!!f.strike,inherit:f.inherit??null,height:f.height||1,source:f.source||'user'};}}} +// Thin wrappers over the pure logic in app-core.js (inlined further down), +// passing the live module state. packagesForExport / mergePackagesInto live in +// the core verbatim and are used by name. +function pname(n){return nameToHex(n,PALETTE);} +function seedPkgmap(){return buildPkgmap(APPS,PALETTE);} let PKGMAP=seedPkgmap(); function esc(t){return t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');} // Pure color-math core (lin/rl/contrast/rating/hsv2rgb/rgb2hsv/hex2rgb/rgb2hex, -// plus OKLab/OKLCH/APCA/deltaE), inlined verbatim from colormath.js. normHex, -// textOn, and ratingColor stay below as UI-boundary helpers. +// plus OKLab/OKLCH/APCA/deltaE), inlined verbatim from colormath.js. // colormath.js — pure color-math core for theme-studio. // // One source of truth: node imports this module (tests); generate.py inlines its @@ -321,171 +480,1832 @@ function rgb2hsv(r, g, b) { function rgb2hex(r, g, b) { return '#' + [r, g, b].map(x => Math.max(0, Math.min(255, x)).toString(16).padStart(2, '0')).join(''); } -function textOn(h){const L=rl(h);return ((L+0.05)/0.05)>(1.05/(L+0.05))?'#000':'#fff';} + +// One Chroma×Lightness plane cell at a fixed hue: the sRGB color if the (L,C,H) +// is reachable, else flagged out of gamut. Forward-only (one conversion + a +// range check) — the binary-search clamp is reserved for committing a color. +function planeCell(L, C, H) { + const lab = oklch2oklab(L, C, H), lrgb = oklab2lrgb(lab.L, lab.a, lab.b); + return inGamut(lrgb) ? { inGamut: true, hex: lrgb2hex(lrgb) } : { inGamut: false, hex: null }; +} + +// Pairwise palette analysis. palette is [[hex, name], ...]. Returns the pairs +// closer than threshold (OKLab ΔE), closest-first and capped, the overflow count +// beyond the cap, and each color's nearest-neighbor distance for its chip title. +function paletteWarnings(palette, threshold = 0.02, cap = 5) { + const n = palette.length, nearest = new Array(n).fill(Infinity), pairs = []; + for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) { + const d = deltaE(palette[i][0], palette[j][0]); + if (d < nearest[i]) nearest[i] = d; + if (d < nearest[j]) nearest[j] = d; + if (d < threshold) pairs.push({ i, j, aName: palette[i][1], bName: palette[j][1], dE: d }); + } + pairs.sort((a, b) => a.dE - b.dE); + return { warnings: pairs.slice(0, cap), overflow: Math.max(0, pairs.length - cap), nearest }; +} + +// --- 3D-box relief colors, matching Emacs's renderer --------------------- +// Port of x_alloc_lighter_color (Emacs 30 xterm.c): highlight = bg x 1.2 +// (delta 0x8000), shadow = bg x 0.6 (delta 0x4000), both in 16-bit channel +// space. Backgrounds dimmer than 48000/65535 (by Emacs's 2R+3G+B/6 weighting) +// get an additive boost of delta*dimness*factor/2, because scaling alone +// barely moves a dark color. When the result still equals the background +// (pure black shadow, pure white highlight), Emacs retries with bg+delta. +function reliefColors(bgHex) { + const rgb = hex2rgb(bgHex); + if (rgb.some((c) => Number.isNaN(c))) return { hl: null, sh: null }; + const ch16 = rgb.map((c) => c * 257); + const one = (factor, delta) => { + let nw = ch16.map((c) => Math.min(0xffff, factor * c)); + const bright = (2 * ch16[0] + 3 * ch16[1] + ch16[2]) / 6; + if (bright < 48000) { + const md = delta * (1 - bright / 48000) * factor / 2; + nw = factor < 1 + ? nw.map((v) => Math.max(0, v - md)) + : nw.map((v) => Math.min(0xffff, v + md)); + } + if (nw.every((v, i) => Math.round(v) === ch16[i])) + nw = ch16.map((c) => Math.min(0xffff, c + delta)); + return '#' + nw.map((v) => Math.round(v / 257).toString(16).padStart(2, '0')).join(''); + }; + return { hl: one(1.2, 0x8000), sh: one(0.6, 0x4000) }; +} + +// OKLCH of a hex, and the pure black/white endpoint test. Shared by app-core +// and palette-generator-core (both previously kept their own identical copies). +function oklchOf(hex){return oklab2oklch(srgb2oklab(hex));} +function isPureEndpointHex(hex){const h=(hex||'').toLowerCase();return h==='#ffffff'||h==='#000000';} +// Pure package-model + dropdown logic, inlined verbatim from app-core.js. The +// wrappers above (pname/seedPkgmap/ddList/pkgEffFg/pkgEffBg) delegate here. +// Pure app logic — the package-face model and the dropdown option list — with no +// DOM and no module globals (every dependency is a parameter). It is unit-tested +// directly (test-app-core.mjs) and inlined into the page like colormath.js, so +// the browser runs the same code the tests import. The app.js wrappers (pname, +// seedPkgmap, ddList, pkgEffFg, pkgEffBg) are thin delegators that pass the +// live PALETTE / APPS / PKGMAP into these. +// +// The imports below are for the Node tests; generate.py strips them on inline, +// where normHex (app-util.js) and the colormath helpers are already present from +// the bodies inlined above this one. + +// Resolve a palette name (or a raw #hex) to a hex; null when the name is unknown. +function nameToHex(n,palette){if(!n)return null;if(/^#/.test(n))return n;const p=palette.find(p=>p[1]===n);return p?p[0]:null;} + +// Convert a face dict's legacy boolean style fields to the new shape: bold -> +// weight "bold", italic -> slant "italic", underline true -> {style:line,color}, +// strike true -> {color}. An explicit weight/slant already set wins over the +// legacy flag. Faces already in the new shape pass through, so this is safe on +// any input. Mirrors migrate_legacy in face_specs.py; keep the two in step. +function migrateLegacyFace(d){ + const out=Object.assign({},d||{}); + if('bold' in out){const b=out.bold;delete out.bold;if(b&&out.weight==null)out.weight='bold';} + if('italic' in out){const i=out.italic;delete out.italic;if(i&&out.slant==null)out.slant='italic';} + if('underline' in out){if(out.underline===true)out.underline={style:'line',color:null};else if(out.underline===false)out.underline=null;} + if('strike' in out){if(out.strike===true)out.strike={color:null};else if(out.strike===false)out.strike=null;} + return out; +} + +// --- face CSS rendering ------------------------------------------------------ +// Pure builders for the face preview/inline CSS strings. app.js's syntaxStyle / +// uiCss / ofs / udeco wrappers differ only in how they resolve fg/bg and whether +// they add a font-size; they all delegate here. cssWeight maps the curated weight +// names to numeric CSS weights; faceDecoration is the underline/strike value. +function cssWeight(w){const M={light:300,normal:400,medium:500,semibold:600,bold:700,heavy:900};return w&&M[w]!=null?M[w]:'normal';} +function faceDecoration(face){return ((face.underline?'underline ':'')+(face.strike?'line-through':'')).trim()||'none';} +// A face's :box, rendered as an inset box-shadow (no layout shift). Returns the +// box-shadow VALUE (or '' for no box). 'line' is a flat border in the box color +// (or the face's own color when unset); 'released'/'pressed' are the 3D button +// styles Emacs draws, derived from explicit box color when set, otherwise BG so +// they read on any color (reliefColors is ported from xterm.c). +function boxCss(b,bg){if(!b||!b.style)return '';const w=b.width||1; + if(b.style==='released'||b.style==='pressed'){ + const r=(b.color||bg)?reliefColors(b.color||bg):{hl:null,sh:null}; + const hl=r.hl||'#ffffff33',sh=r.sh||'#00000066'; + const [a,z]=b.style==='released'?[hl,sh]:[sh,hl]; + return `inset ${w}px ${w}px 0 ${a},inset -${w}px -${w}px 0 ${z}`;} + return `inset 0 0 0 ${w}px ${b.color||'currentColor'}`;} +// CSS declaration string for FACE with already-resolved FG/BG. opts: noBg +// (never emit background), fontSize (em number for height), boxBg (background +// handed to the relief shading). Declaration order matches the strings the four +// callers previously assembled by hand, so the rendered output is unchanged. +function faceCss(face,fg,bg,opts){ + opts=opts||{}; + const parts=['color:'+fg]; + if(bg&&!opts.noBg)parts.push('background:'+bg); + parts.push('font-weight:'+cssWeight(face.weight), + 'font-style:'+(face.slant||'normal'), + 'text-decoration:'+faceDecoration(face)); + if(opts.fontSize!=null)parts.push('font-size:'+opts.fontSize+'em'); + const bx=boxCss(face.box,opts.boxBg); + if(bx)parts.push('box-shadow:'+bx); + return parts.join(';'); +} + +// Single source of truth for the per-face attribute model. One row per +// attribute drives both normalizePkgFace (defaulting + palette resolution) and +// packagesForExport (which attrs serialize and when). Adding a face attribute +// is one row here, not an edit in four hand-kept lists. +// def : value when unset +// resolve : fg/bg/distant-fg run through the palette name->hex resolver +// coerce : 'bool' -> !!v ; 'height' -> v||1 ; default -> v ?? def +// emit : export rule -- 'always' | 'truthy' | 'non-one' | 'bool' +// A hoisted function rather than a const: the inlined page calls normalizePkgFace +// at top level (seedPkgmap) before this point in source order, and a const would +// be in its temporal dead zone there; a function declaration is hoisted. +function faceAttrs(){return [ + {k:'fg', def:null, resolve:true, emit:'always'}, + {k:'bg', def:null, resolve:true, emit:'always'}, + {k:'distant-fg', def:null, resolve:true, emit:'truthy'}, + {k:'family', def:null, emit:'truthy'}, + {k:'weight', def:null, emit:'truthy'}, + {k:'slant', def:null, emit:'truthy'}, + {k:'underline', def:null, emit:'truthy'}, + {k:'strike', def:null, emit:'truthy'}, + {k:'overline', def:null, emit:'truthy'}, + {k:'inherit', def:null, emit:'always'}, + {k:'height', def:1, coerce:'height', emit:'non-one'}, + {k:'box', def:null, emit:'truthy'}, + {k:'inverse', def:false, coerce:'bool', emit:'bool'}, + {k:'extend', def:false, coerce:'bool', emit:'bool'}, +];} + +function normalizePkgFace(d,source,palette){ + d=migrateLegacyFace(d||{}); + const resolve=(v)=>palette?nameToHex(v,palette):v; + const out={}; + for(const a of faceAttrs()){ + let v=a.resolve?resolve(d[a.k]):d[a.k]; + out[a.k]=a.coerce==='bool'?!!v:a.coerce==='height'?(v||1):(v??a.def); + } + out.source=source||d.source||'user'; + return out; +} + + +// Seed the package-face map from the app inventory's per-face defaults. +function buildPkgmap(apps,palette){const m={};for(const app in apps){m[app]={};for(const row of apps[app].faces){m[app][row[0]]=normalizePkgFace(row[2],'default',palette);}}return m;} + +// The package faces worth exporting (anything seeded or user-touched), trimmed. +// Driven by FACE_ATTRS: each attribute's `emit` rule decides whether it lands. +function packagesForExport(map){const out={};for(const app in map){const faces={};for(const face in map[app]){const f=map[app][face];if(f.source==='default'||f.source==='user'||f.source==='cleared'){const o={};for(const a of faceAttrs()){const v=f[a.k];if(a.emit==='always')o[a.k]=v;else if(a.emit==='truthy'){if(v)o[a.k]=v;}else if(a.emit==='non-one'){if(v&&v!==1)o[a.k]=v;}else if(a.emit==='bool'){if(v)o[a.k]=true;}}o.source=f.source;faces[face]=o;}}if(Object.keys(faces).length)out[app]=faces;}return out;} + +// Merge an imported package block into a face map, filling missing fields. +function mergePackagesInto(map,pkgs){if(!pkgs)return;for(const app in pkgs){if(!map[app])map[app]={};for(const face in pkgs[app]){const f=pkgs[app][face]||{};map[app][face]=normalizePkgFace(f,f.source||'user');}}} + +// Effective fg/bg for a package face, following its inherit chain. seen guards +// against an inherit cycle (returns null rather than recursing forever). +function effResolve(map,app,face,attr,seen){seen=seen||{};const f=map[app]&&map[app][face];if(!f||seen[face])return null;seen[face]=1;if(f[attr])return f[attr];if(f.inherit&&map[app][f.inherit])return effResolve(map,app,f.inherit,attr,seen);return null;} + +// Emacs built-in inherit chains for the syntax categories theme studio exposes. +// An unset category foreground resolves the way the generated theme renders in +// Emacs: build-theme.el writes no override for an unset face, so Emacs falls back +// to the face's own :inherit -- comment-delimiter->comment, doc->string, +// property-name->variable-name, function-call->function-name -- not to the +// default foreground. +const SYNTAX_INHERIT={cmd:'cm',doc:'str',prop:'var',fnc:'fnd'}; + +// Effective foreground for a syntax category, following the Emacs inherit chain. +// SYNTAX maps category -> face object with an optional `fg`; DEFAULTFG is the +// theme's default foreground (the chain's floor). `dec` (decorator) is pinned to +// `ty`: Emacs has no decorator face and renders decorators with +// font-lock-type-face, so a dec color set in the studio would never reach Emacs. +// Walk an inherit chain from START, returning the first truthy valueFn(key) or +// null. nextFn(key) gives the parent key; a seen-set guards against a cycle. +function walkInheritChain(start,nextFn,valueFn){ + let k=start;const seen={}; + while(k&&!seen[k]){seen[k]=1;const v=valueFn(k);if(v)return v;k=nextFn(k);} + return null; +} +function resolveSyntaxFg(cat,syntax,defaultFg){ + const start=(cat==='dec')?'ty':cat; + return walkInheritChain(start,k=>SYNTAX_INHERIT[k],k=>syntax[k]&&syntax[k].fg)||defaultFg; +} + +// Emacs built-in inherit chains for the ui faces whose parent is also a studio ui +// face, so an unset attribute previews the way Emacs renders it: mode-line-inactive +// inherits mode-line, line-number-current-line inherits line-number. +const UI_INHERIT={'mode-line-inactive':'mode-line','line-number-current-line':'line-number'}; + +// First set value of ATTR ('fg'/'bg') for ui FACE, walking UI_INHERIT; null when +// nothing up the chain is set. The caller applies its own floor (default fg, +// ground, or transparent), since that floor differs per attribute and face. +function resolveUiAttr(face,attr,uimap){ + return walkInheritChain(face,f=>UI_INHERIT[f],f=>uimap[f]&&uimap[f][attr]); +} + +// Turn a theme name into a safe filename slug: collapse runs of disallowed +// characters to a single dash, trim leading/trailing dashes, fall back to 'theme'. +function slugify(name){return name.replace(/[^A-Za-z0-9._-]+/g,'-').replace(/^-+|-+$/g,'')||'theme';} + +// --- background-contrast safety (palette-ramps spec, Phase 3) ---------------- +// An overlay background sits behind many foregrounds at once, so its real +// constraint is the worst-case contrast over the whole set, not one fg/bg pair. + +// The closed v1 set of code-overlay faces whose worst-case floor we compute. +// Other overlay faces (secondary-selection, isearch-fail, ...) are vNext, added +// explicitly rather than by a heuristic. Shared by app.js and the tests. +const COVERED_FACES=['region','hl-line','highlight','lazy-highlight','isearch']; + +// A covered face's foreground set: the distinct syntax-token colors plus the +// default foreground, each labeled (syntax role preferred, else 'default'). +// state = {covered:[face], syntaxAssignments:[{role,hex}], defaultFg}. Returns +// {set:[{hex,label}]}, or {set:[],reason} where reason is 'out-of-scope' (the +// face isn't in the covered set) or 'empty' (no syntax assignments constrain it). +function fgSetFor(face,state){ + const covered=(state&&state.covered)||COVERED_FACES; + if(!covered.includes(face))return {set:[],reason:'out-of-scope'}; + const syn=((state&&state.syntaxAssignments)||[]).filter(a=>a&&a.hex); + if(!syn.length)return {set:[],reason:'empty'}; + const byHex=new Map(); + const add=(hex,label,name,isRole)=>{const k=hex.toLowerCase(),cur=byHex.get(k);if(!cur)byHex.set(k,{hex:k,label,name:name||label});else if(isRole&&cur.label==='default'){cur.label=label;cur.name=name||label;}}; + if(state&&state.defaultFg)add(state.defaultFg,'default','default',false); + for(const a of syn)add(a.hex,a.role||a.hex,a.name||a.role||a.hex,true); + return {set:[...byHex.values()]}; +} + +// Worst-case (minimum) WCAG contrast of a background against a foreground set, +// with the limiting foreground's hex and label. fgSet is fgSetFor's set. An empty +// set returns nulls so the caller can show the no-set readout instead of a floor. +function floor(bgHex,fgSet){ + if(!fgSet||!fgSet.length)return {ratio:null,limitingHex:null,limitingLabel:null}; + let best=Infinity,lh=null,ll=null; + for(const f of fgSet){const r=contrast(f.hex,bgHex);if(r<best){best=r;lh=f.hex;ll=f.label;}} + return {ratio:best,limitingHex:lh,limitingLabel:ll}; +} + +// The lightest background at (hue, chroma) whose worst-case floor over fgSet still +// clears target (a WCAG ratio). Scans L up from black to bracket the first +// dark-side crossing, then binary-searches it to tol 0.001. status: +// 'ok' - a ceiling L was found +// 'none' - even pure black fails (a foreground is too dark for the target) +// 'all' - no foreground set to constrain (vacuously safe everywhere) +// 'clamp' - the ceiling L can't hold the requested chroma (gamut-clamped there) +function lMax(hue,chroma,fgSet,target){ + if(!fgSet||!fgSet.length)return {L:1,status:'all'}; + const at=(L)=>{const {hex,clamped}=oklch2hex(L,chroma,hue);return {r:floor(hex,fgSet).ratio,clamped};}; + if(at(0).r<target)return {L:null,status:'none'}; + let loL=0,hiL=null; + for(let L=0.01;L<=1+1e-9;L+=0.01){const c=Math.min(L,1);if(at(c).r<target){hiL=c;break;}loL=c;} + if(hiL===null)return {L:1,status:'all'}; + for(let i=0;i<20;i++){const mid=(loL+hiL)/2;if(at(mid).r>=target)loL=mid;else hiL=mid;} + return {L:loL,status:at(loL).clamped?'clamp':'ok'}; +} + +// --- color columns ----------------------------------------------------------- +// Columns are structural, not inferred by color. Generated ramp entries are named +// base-1/base/base+1 and remain in that base column regardless of their hex. A +// manually-added color starts as its own singleton column. The flat palette stays +// the editable truth; these pure functions group it, regenerate a ramp, and plan +// assignment re-point across a regenerate. + +function isReservedGroundLikeName(name){return /^(bg|fg)(?:[-_+].+|\d.*)$/i.test(name||'');} +function interpOklabHex(a,b,t,offset){ + const lab={L:a.L+(b.L-a.L)*t,a:a.a+(b.a-a.a)*t,b:a.b+(b.b-a.b)*t}; + const lrgb=oklab2lrgb(lab.L,lab.a,lab.b); + return {hex:lrgb2hex(lrgb),offset,clamped:!inGamut(lrgb)}; +} +function columnStem(name){name=name||'color';if(/^color-\d+$/.test(name))return name;name=name.replace(/[+-]\d+$/,'');return name.replace(/\d+$/,'')||'color';} +function columnOffset(name){const m=(name||'').match(/([+-]\d+)$/);return m?parseInt(m[1],10):0;} +function legacyColumnStem(name){return isReservedGroundLikeName(name)?name:columnStem(name);} +function legacyColumnOffset(name){return isReservedGroundLikeName(name)?0:columnOffset(name);} +function columnIdOf(entry){return (entry&&entry[2])||legacyColumnStem(entry&&entry[1]);} +function groundRoleOfEntry(entry,ground){ + if(!entry)return null; + const [hex,name]=entry,col=entry[2],n=(name||'').toLowerCase(),h=(hex||'').toLowerCase(); + const bg=(ground&&ground.bg||'').toLowerCase(),fg=(ground&&ground.fg||'').toLowerCase(); + if(/^ground[+-]\d+$/i.test(name||''))return 'step'; + if(col==='ground'){ + if(bg&&h===bg)return 'bg'; + if(fg&&h===fg)return 'fg'; + return 'step'; + } + if(bg&&h===bg&&(n==='bg'||n==='ground'))return 'bg'; + if(fg&&h===fg&&n==='fg')return 'fg'; + return null; +} +function nameOfGroundRole(palette,ground,role){ + const found=palette.find(entry=>groundRoleOfEntry(entry,ground)===role); + return found?found[1]:null; +} + +function normalizePaletteEntryCore(entry){ + const hex=entry&&entry[0],name=(entry&&entry[1])||'color'; + return [hex,name,(entry&&entry[2])||columnIdOf(entry)]; +} + +function groundColumnMembersFromPalette(palette,ground){ + const byRole={bg:null,fg:null,steps:[]}; + for(const entry of palette){ + const role=groundRoleOfEntry(entry,ground); + if(role==='bg'||role==='fg')byRole[role]={hex:entry[0],name:entry[1]}; + else if(role==='step')byRole.steps.push({hex:entry[0],name:entry[1]}); + } + const stepIndex=m=>{const x=(m.name||'').match(/^ground[+-](\d+)$/i);return x?parseInt(x[1],10):Infinity;}; + byRole.steps.sort((a,b)=>stepIndex(a)-stepIndex(b)); + return [byRole.bg||{hex:ground&&ground.bg,name:'bg'},...byRole.steps,byRole.fg||{hex:ground&&ground.fg,name:'fg'}].filter(m=>m.hex); +} + +function clearPalettePlan(palette,ground){ + const normalized=palette.map(normalizePaletteEntryCore),removed=[],keep=[]; + normalized.filter(entry=>!groundRoleOfEntry(entry,ground)).forEach(([hex,name])=>{if(name)removed.push({hex,name});}); + const addEndpoint=(role,hex,name)=>{ + const found=normalized.find(entry=>groundRoleOfEntry(entry,ground)===role); + if(found)keep.push(found);else if(hex)keep.push([hex,name,'ground']); + }; + addEndpoint('bg',ground&&ground.bg,'bg'); + addEndpoint('fg',ground&&ground.fg,'fg'); + return {palette:keep,removed}; +} + +function deletePaletteColumnPlan(palette,ground,columnId){ + const normalized=palette.map(normalizePaletteEntryCore),removed=[],keep=[]; + for(const entry of normalized){ + if(groundRoleOfEntry(entry,ground)||columnIdOf(entry)!==columnId)keep.push(entry); + else removed.push({hex:entry[0],name:entry[1]}); + } + return {palette:keep,removed}; +} + +function areAllLocked(keys,locked){ + const has=k=>locked instanceof Set?locked.has(k):Array.isArray(locked)&&locked.includes(k); + return !!(keys&&keys.length)&&keys.every(has); +} +function lockToggleLabel(keys,locked){return areAllLocked(keys,locked)?'unlock all':'lock all';} +function toggleLockSet(keys,locked){ + const next=new Set(locked||[]),all=areAllLocked(keys,next); + (keys||[]).forEach(k=>all?next.delete(k):next.add(k)); + return next; +} + +// Group a flat palette into the ground strip plus structural columns. ground is +// {bg,fg}; those endpoint hexes form the pinned ground column even when absent +// from the palette, and ground+N entries are reserved for that column. Everything +// else groups by its stable column id, not by OKLCH hue/chroma or display name. +// Legacy two-field entries fall back to their generated-name stem until edited. +// Reverse lookup: every palette hex referenced by an assignment (syntax, ui, or +// package fg / bg / box-color), plus the ground endpoints, which are always in +// use. Values may be palette names or hexes; nameToHex resolves both, so a tile +// whose hex is absent from this set is genuinely unreferenced. Biased safe: an +// unresolvable value simply marks nothing, so a used color is never flagged. +function usedPaletteHexes(palette,syntax,uimap,pkgmap,ground){ + const used=new Set(); + const add=v=>{const h=nameToHex(v,palette);if(h)used.add(h.toLowerCase());}; + const addFace=f=>{if(!f)return;add(f.fg);add(f.bg);if(f.box&&f.box.color)add(f.box.color);}; + if(ground){if(ground.bg)add(ground.bg);if(ground.fg)add(ground.fg);} + for(const k in (syntax||{}))addFace(syntax[k]); + for(const face in (uimap||{}))addFace(uimap[face]); + for(const app in (pkgmap||{}))for(const face in pkgmap[app])addFace(pkgmap[app][face]); + return used; +} +// Enumerate where a palette color is used, as "area > element" strings. scopes +// is [{area, faces:{element: faceObj}}] -- one scope per view area (color/code, +// ui faces, each package app), element keyed by its display label. A face counts +// if any of fg / bg / box-color resolves (by hex or palette name) to the target. +function paletteUsages(hex,scopes,palette){ + const target=(hex||'').toLowerCase(); + if(!target)return []; + const out=[]; + for(const {area,faces} of (scopes||[])){ + for(const element in (faces||{})){ + const f=faces[element];if(!f)continue; + const vals=[f.fg,f.bg,f.box&&f.box.color]; + if(vals.some(v=>{const h=nameToHex(v,palette);return h&&h.toLowerCase()===target;}))out.push(area+' > '+element); + } + } + return out; +} +function columnsFromPalette(palette,ground){ + const bg=ground&&ground.bg,fg=ground&&ground.fg; + const groundStrip=[]; + if(bg)groundStrip.push({hex:bg,role:'bg',name:nameOfGroundRole(palette,ground,'bg')}); + if(fg)groundStrip.push({hex:fg,role:'fg',name:nameOfGroundRole(palette,ground,'fg')}); + const byColumn=new Map(),columns=[]; + for(const entry of palette){ + const [hex,name]=entry; + if(groundRoleOfEntry(entry,ground))continue; + const column=columnIdOf(entry),offset=entry[2]?columnOffset(name):legacyColumnOffset(name); + if(!byColumn.has(column))byColumn.set(column,{column,members:[]}); + byColumn.get(column).members.push({hex,name,offset,column}); + } + for(const f of byColumn.values()){ + const base=(f.members.find(m=>m.offset===0)||f.members[0]).hex; + columns.push({base,column:f.column,stem:f.column,members:f.members.map(m=>({hex:m.hex,name:m.name,column:m.column}))}); + } + return {ground:groundStrip,columns}; +} +// Regenerate a column's members as a symmetric span around the base: n=0 is the +// base alone, n>=1 divides the OKLab intervals black..base and base..white into +// n interior steps per side. Pure black/white endpoint duplicates and rounded +// base duplicates are skipped. {members:[{hex,offset,clamped}]} or +// {members:[],error:'bad-hex'}. +function regenColumn(baseHex,n,opts){ + opts=opts||{}; + const hex=typeof baseHex==='string'?normHex(baseHex):null; + if(!hex)return {members:[],error:'bad-hex'}; + const k=Math.min(8,Math.max(0,Math.round(n||0))); + if(k===0)return {members:[{hex,offset:0,clamped:false}]}; + // Bound the span to the ground endpoints when given: the dark side ramps toward + // the darker ground (bg), the light side toward the lighter ground (fg), so no + // generated step is darker than bg or lighter than fg. Falls back to pure + // black/white when no ground is supplied. isPureEndpointHex still dedupes the + // black/white case when bg/fg are themselves pure. + const g=opts.ground||{}; + const gb=(g.bg&&normHex(g.bg))?srgb2oklab(normHex(g.bg)):srgb2oklab('#000000'); + const gf=(g.fg&&normHex(g.fg))?srgb2oklab(normHex(g.fg)):srgb2oklab('#ffffff'); + const darkEnd=gb.L<=gf.L?gb:gf, lightEnd=gb.L<=gf.L?gf:gb; + const base=srgb2oklab(hex),steps=[]; + for(let i=1;i<=k;i++){ + const dark=interpOklabHex(darkEnd,base,i/(k+1),i-k-1); + const light=interpOklabHex(base,lightEnd,i/(k+1),i); + steps.push(dark,light); + } + const members=[...steps.filter(s=>!isPureEndpointHex(s.hex)&&s.hex.toLowerCase()!==hex),{hex,offset:0,clamped:false}].sort((a,b)=>a.offset-b.offset); + return {members}; +} +// Rank a column's current member hexes by lightness and give each a signed offset +// from the base (the matching hex, or the nearest by lightness if the base isn't +// present). Lets a regenerate match old positions to new ramp offsets. +function rankByLightness(memberHexes,baseHex){ + const items=memberHexes.map(h=>({hex:h,L:oklchOf(h).L})).sort((a,b)=>a.L-b.L); + let bi=items.findIndex(m=>m.hex.toLowerCase()===(baseHex||'').toLowerCase()); + if(bi<0){const bl=oklchOf(baseHex).L;let best=Infinity;items.forEach((m,i)=>{const d=Math.abs(m.L-bl);if(d<best){best=d;bi=i;}});} + return items.map((m,i)=>({hex:m.hex,offset:i-bi})); +} +// Plan the assignment re-point for a regenerate: for each old ranked member, the +// new member at the same offset is the same position. {map:[[old,new]]} for +// positions whose hex changed; {removed:[hex]} for positions with no new +// counterpart (the caller leaves their references a visible "(gone)"). +function stepRepointPlan(oldRanked,newMembers){ + const byOff=new Map(newMembers.map(m=>[m.offset,m.hex])),map=[],removed=[]; + for(const o of oldRanked){ + const nh=byOff.get(o.offset); + if(nh===undefined)removed.push(o.hex); + else if(nh.toLowerCase()!==o.hex.toLowerCase())map.push([o.hex,nh]); + } + return {map,removed}; +} + +// Preserve structural order. Generated ramps are inserted in offset order, and +// columns are emitted in first-seen palette order. No color sorting happens here. +function sortColumnMembers(column){return Object.assign({},column,{members:[...column.members]});} +function sortColumns(columns){return columns.map(sortColumnMembers);} +function lightestFirstMembers(members){return [...members].sort((a,b)=>oklchOf(b.hex).L-oklchOf(a.hex).L);} + +// Dropdown order for color selection mirrors the visual palette organization: +// bg/fg first, then structural columns in display order. Within each group, +// choices run lightest-to-darkest. Stored palette order stays untouched; this is +// selection-only organization. +function paletteOptionList(cur,palette,ground){ + const have=cur===''||palette.some(p=>p[0]===cur)||[ground&&ground.bg,ground&&ground.fg].filter(Boolean).includes(cur); + const out=[['','— default —']],seen=new Set(); + if(!have)out.push([cur,'(gone)']); + const add=(hex,name)=>{if(!hex)return;const key=hex.toLowerCase()+'|'+(name||'');if(seen.has(key))return;seen.add(key);out.push([hex,name||hex]);}; + const grouped=columnsFromPalette(palette,ground||{}); + const groundMembers=grouped.ground.map(g=>({hex:g.hex,name:g.name||g.role})) + .concat(palette.filter(entry=>groundRoleOfEntry(entry,ground)==='step').map(([hex,name])=>({hex,name}))); + groundMembers.forEach(m=>add(m.hex,m.name)); + sortColumns(grouped.columns).forEach(f=>lightestFirstMembers(f.members).forEach(m=>add(m.hex,m.name))); + return out; +} +// Grid model for the gallery color picker. Mirrors the palette panel layout: a +// ground row (bg/fg + ground steps) then one row per color family, members run +// dark->light to match the panel. cur marks the one selected cell. The leading +// "default" entry (clears the assignment) and, when cur points at a color no +// longer in the palette, a "(gone)" entry live outside the family grid so every +// dropdown choice stays reachable. Pure — shares columnsFromPalette / sortColumns +// with the panel and the option list. +function galleryModel(cur,palette,ground){ + const want=(cur||'').toLowerCase(),sel=h=>(h||'').toLowerCase()===want; + const byLightAsc=(a,b)=>oklchOf(a.hex).L-oklchOf(b.hex).L; + const cell=m=>({hex:m.hex,name:m.name||m.hex,selected:sel(m.hex)}); + const rows=[]; + const groundCells=groundColumnMembersFromPalette(palette,ground||{}) + .filter(m=>m&&m.hex).sort(byLightAsc).map(cell); + if(groundCells.length)rows.push({kind:'ground',cells:groundCells}); + sortColumns(columnsFromPalette(palette,ground||{}).columns).forEach(f=>{ + const cells=[...f.members].filter(m=>m&&m.hex).sort(byLightAsc).map(cell); + if(cells.length)rows.push({kind:'column',column:f.column,cells}); + }); + const have=cur===''||cur==null||rows.some(r=>r.cells.some(c=>sel(c.hex))); + const gone=(cur&&!have)?{hex:cur,name:'(gone)',selected:true}:null; + return {default:{hex:'',selected:cur===''||cur==null},gone,rows}; +} +function spanNeighborHex(cur,palette,ground,dir){ + if(!cur)return null; + const wanted=(cur||'').toLowerCase(),groups=[],byLight=(a,b)=>oklchOf(a.hex).L-oklchOf(b.hex).L; + const addGroup=members=>{ + const seen=new Set(),g=[]; + members.filter(m=>m&&m.hex).sort(byLight).forEach(m=>{const h=m.hex.toLowerCase();if(!seen.has(h)){seen.add(h);g.push(m);}}); + if(g.length)groups.push(g); + }; + addGroup(groundColumnMembersFromPalette(palette,ground||{})); + sortColumns(columnsFromPalette(palette,ground||{}).columns).forEach(f=>addGroup(f.members)); + for(const g of groups){ + const i=g.findIndex(m=>(m.hex||'').toLowerCase()===wanted); + if(i<0)continue; + const next=g[i+(dir>0?1:-1)]; + return next?next.hex:null; + } + return null; +} + +// The package apps for the assignment-view dropdown, keyed and sorted by display +// label (case-insensitive). generate.py builds APPS as bespoke apps first then +// inventory apps, so the raw key order isn't alphabetical; this orders the list +// the reader scans. An app missing a label falls back to its key. +function appViewKeysSorted(apps){ + return Object.keys(apps||{}).sort((a,b)=> + String((apps[a]&&apps[a].label)||a).localeCompare( + String((apps[b]&&apps[b].label)||b), undefined, {sensitivity:'base'})); +} + +// The prev/next arrows step the view-dropdown selection by DIR (-1/+1), clamped +// to [0, LEN-1] with no wrap. An empty list (LEN<=0) keeps CUR. +function stepViewIndex(cur,len,dir){ + if(!(len>0)) return cur; + return Math.max(0, Math.min(len-1, cur+dir)); +} + +// Which of the six per-face setting boxes (fg, bg, style, inherit, height, box) +// differ from the face's seed default, so the table can mark a non-default box. +// A non-default height looks identical to the default in the number input, so the +// mark is the only at-a-glance signal. cur and def are face objects; the caller +// resolves fg/bg to hex first so a palette-name-vs-hex difference doesn't read as a +// change. The four style attributes collapse to one "style" flag. +function faceBoxNonDefaults(cur,def){ + cur=cur||{}; def=def||{}; + const eq=(a,b)=>(a??null)===(b??null); + return { + fg: !eq(cur.fg,def.fg), + bg: !eq(cur.bg,def.bg), + style: ['weight','slant','strike'].some(a=>JSON.stringify(cur[a]??null)!==JSON.stringify(def[a]??null)), + inherit: !eq(cur.inherit,def.inherit), + height: (cur.height||1)!==(def.height||1), + box: JSON.stringify(cur.box??null)!==JSON.stringify(def.box??null), + }; +} + +// True when the per-row expander hides at least one attribute that differs from +// the face's default, so the collapsed toggle can flag it. Covers exactly the +// attributes the expander holds: distant-fg, family, underline, overline, +// inverse, extend, and (for ui/syntax) inherit + height. The in-row controls +// (fg/bg/weight/slant/strike/box) have their own cell markers and are excluded. +function overflowNonDefault(cur,def,showInheritHeight){ + cur=cur||{}; def=def||{}; + const eq=(a,b)=>JSON.stringify(a??null)===JSON.stringify(b??null); + if(['distant-fg','family','underline','overline'].some(a=>!eq(cur[a],def[a])))return true; + if((!!cur.inverse)!==(!!def.inverse))return true; + if((!!cur.extend)!==(!!def.extend))return true; + if(showInheritHeight){ + if(!eq(cur.inherit,def.inherit))return true; + if((cur.height||1)!==(def.height||1))return true; + } + return false; +} + +// Height bounds for a face :height scaling factor. 0.1 is Emacs's own floor (a +// smaller value errors out) and doubles as the modeline-shrink-to-nothing value; +// 2.0 is the studio's chosen ceiling. The number input's min/max attributes only +// guard its stepper arrows — typed or pasted values bypass them — so every height +// edit is coerced through clampHeight instead. +const HEIGHT_MIN=0.1, HEIGHT_MAX=2.0; +// Coerce a height-field value to either null (unset → inherit the default height) +// or a number clamped into [min,max]. Blank/whitespace/non-numeric → null; any +// number, including 0, a negative, or an over-max value, snaps into range. +function clampHeight(raw,min=HEIGHT_MIN,max=HEIGHT_MAX){ + if(raw===null||raw===undefined)return null; + const s=(''+raw).trim(); + if(s==='')return null; + const n=parseFloat(s); + if(!isFinite(n))return null; + return n<min?min:n>max?max:n; +} + +// Compose an element-hover tooltip: the face's docstring on top, the existing +// hover text (e.g. the bare face name) below it, separated by a blank line. A +// missing doc or base collapses to whichever is present; missing both yields ''. +// Keyed lookups (FACE_DOCS[face], SYNTAX_DOCS[kind]) supply DOC; BASE is +// whatever title the element carried before. +function composeHoverTitle(doc,base){ + doc=doc||''; base=base||''; + if(doc&&base)return doc+'\n\n'+base; + return doc||base; +} +// Pure color/UI-boundary helpers (normHex, ratingColor, textOn), inlined from +// app-util.js. textOn uses rl from the colormath core above. +// Pure color/UI-boundary helpers: hex-input parsing, the contrast-rating status +// color, and the readable text color for a background. These are kept out of +// colormath.js (the pure math core) but are unit-tested and inlined into the page +// the same way. textOn leans on rl from colormath; the import is for the tests — +// generate.py strips it on inline, where rl is already present from the inlined +// colormath core. + +// Normalize a hex string: trim, accept an optional leading #, require exactly six +// hex digits, lowercase the result. Returns null for anything else. +function normHex(s){s=s.trim();if(/^[0-9a-fA-F]{6}$/.test(s))s='#'+s;return /^#[0-9a-fA-F]{6}$/.test(s)?s.toLowerCase():null;} + +// Map a WCAG contrast ratio to a status color: AAA green (>=7), AA grey (>=4.5), +// otherwise the fail red. function ratingColor(r){return r>=7?'#5d9b86':r>=4.5?'#a9b2bb':'#cb6b4d';} + +// Pick black or white text for a background hex, by WCAG relative luminance. +function textOn(h){const L=rl(h);return ((L+0.05)/0.05)>(1.05/(L+0.05))?'#000':'#fff';} + +// Hover text for a contrast ratio. The number's color already encodes the tier +// (ratingColor: green AAA, grey AA, red fail), so the cell drops the PASS/FAIL +// word and this explains the color on hover. +function contrastTitle(r){ + const n=r.toFixed(1)+':1'; + if(r>=7) return n+' (green): passes WCAG AA and AAA'; + if(r>=4.5) return n+' (grey): passes WCAG AA, not AAA'; + return n+' (red): fails WCAG AA'; +} +// Pure palette-generator planner and browser-side generator panel. +// Pure palette-generator planner. It depends on the shared palette-column model +// from app-core.js, but owns candidate hue selection, naming, contrast filtering, +// and conversion from preview columns to palette entries. + +function generatedExistingNames(palette){ + return new Set((palette||[]).map(p=>(p&&p[1]||'').toLowerCase()).filter(Boolean)); +} +const DEFAULT_COLOR_NAMES=[['black','#000000'],['white','#ffffff'],['red','#ff0000'],['green','#008000'],['blue','#0000ff'],['yellow','#ffff00'],['cyan','#00ffff'],['magenta','#ff00ff'],['gray','#808080']]; +function nearestColorName(hex,colorNames){ + const h=typeof hex==='string'?normHex(hex):null; + if(!h)return 'generated'; + let best=(colorNames&&colorNames[0]&&colorNames[0][0])||DEFAULT_COLOR_NAMES[0][0],bd=Infinity; + for(const [name,nhex] of (colorNames&&colorNames.length?colorNames:DEFAULT_COLOR_NAMES)){const d=deltaE(h,nhex);if(d<bd){bd=d;best=name;}} + return best; +} +function uniqueGeneratedName(base,used){ + let name=base||'generated',i=2; + if(!used.has(name.toLowerCase())){used.add(name.toLowerCase());return name;} + while(used.has((name+'-alt'+i).toLowerCase()))i++; + const out=name+'-alt'+i;used.add(out.toLowerCase());return out; +} +function hueOfHex(hex,fallback){ + const h=typeof hex==='string'?normHex(hex):null; + if(!h)return fallback; + const lch=oklchOf(h); + return Number.isFinite(lch.H)?lch.H:fallback; +} +function generatorSourceHue(palette,ground,cfg){ + const fallback=((cfg&&typeof cfg.baseHue==='number'&&isFinite(cfg.baseHue))?cfg.baseHue:250)%360; + if(cfg&&cfg.sourceMode==='palette'){const hs=paletteBaseHues(palette,ground);return hs.length?hs[0]:(fallback+360)%360;} + if(cfg&&cfg.sourceMode==='selected'&&typeof cfg.selectedHex==='string'&&normHex(cfg.selectedHex))return hueOfHex(cfg.selectedHex,fallback); + const bg=hueOfHex(ground&&ground.bg,fallback),fg=hueOfHex(ground&&ground.fg,fallback); + if(Math.abs(bg-fallback)>0.001||Math.abs(fg-fallback)>0.001)return ((bg+fg)/2+360)%360; + return (fallback+360)%360; +} +function generatorHues(baseHue,scheme,count,rng){ + const n=Math.max(1,Math.min(12,Math.round(count||8))), b=((baseHue%360)+360)%360; + if(scheme==='random'){ + const rnd=typeof rng==='function'?rng:Math.random; + return Array.from({length:n},()=>Math.floor(rnd()*360)); + } + if(scheme==='analogous'){ + const spread=Math.min(120,Math.max(30,n*14)), start=b-spread/2; + return Array.from({length:n},(_,i)=>(start+(n===1?0:(spread*i)/(n-1))+360)%360); + } + if(scheme==='triadic'){ + const offsets=[0,120,240,30,150,270,60,180,300,90,210,330]; + return offsets.slice(0,n).map(o=>(b+o)%360); + } + if(scheme==='manual')return Array.from({length:n},(_,i)=>(b+(i*360)/n)%360); + return Array.from({length:n},(_,i)=>(b+(i*360)/n)%360); +} +function generatorChroma(mode){ + return mode==='subdued'?0.055:mode==='vivid'?0.13:0.085; +} +function generatorTarget(mode){return mode==='aaa'?7:mode==='none'?0:4.5;} +function jitterHue(h,rng,spread){ + const rnd=typeof rng==='function'?rng:Math.random; + return (h+(rnd()*2-1)*spread+360)%360; +} +function paletteBaseHues(palette,ground){ + const cols=columnsFromPalette(palette||[],ground||{}).columns; + return cols.map(c=>hueOfHex(c.base,0)).filter(Number.isFinite); +} +function paletteBaseHexes(palette,ground){ + return columnsFromPalette(palette||[],ground||{}).columns.map(c=>normHex(c.base)).filter(Boolean); +} +function sourceAnchorHues(palette,ground,cfg,baseHue){ + const mode=cfg&&cfg.sourceMode||'bg-fg'; + if(mode==='none')return []; + if(mode==='palette')return paletteBaseHues(palette,ground); + if(mode==='selected'&&typeof (cfg&&cfg.selectedHex)==='string'&&normHex(cfg.selectedHex))return [hueOfHex(cfg.selectedHex,baseHue)]; + const anchors=[]; + if(ground&&ground.bg)anchors.push(hueOfHex(ground.bg,baseHue)); + if(ground&&ground.fg)anchors.push(hueOfHex(ground.fg,baseHue)); + return anchors.filter(Number.isFinite); +} +function sourceAnchorHexes(palette,ground,cfg){ + const mode=cfg&&cfg.sourceMode||'bg-fg'; + if(mode==='none')return []; + if(mode==='palette')return paletteBaseHexes(palette,ground); + if(mode==='selected'&&typeof (cfg&&cfg.selectedHex)==='string'&&normHex(cfg.selectedHex))return [normHex(cfg.selectedHex)]; + return [ground&&ground.bg,ground&&ground.fg].map(h=>typeof h==='string'?normHex(h):null).filter(Boolean); +} +function bridgeHues(anchors,count,rng){ + if(anchors.length<2)return generatorHues(anchors[0]||250,'random',count,rng); + const sorted=[...anchors].sort((a,b)=>a-b),pairs=[]; + for(let i=0;i<sorted.length;i++){ + const a=sorted[i],b=sorted[(i+1)%sorted.length]+(i===sorted.length-1?360:0); + pairs.push(((a+b)/2)%360); + } + return Array.from({length:count},(_,i)=>jitterHue(pairs[i%pairs.length],rng,10)); +} +function repeatOffsets(base,offsets,count){ + return Array.from({length:count},(_,i)=>(base+offsets[i%offsets.length]+360)%360); +} +function harmonyHues(intent,src,baseHue,count,rng){ + const b=src&&src.length?src[0]:baseHue, n=Math.max(1,Math.min(12,Math.round(count||8))); + if(intent==='complementary')return repeatOffsets(b,[180],n); + if(intent==='analogous')return repeatOffsets(b,[-30,30,-60,60,0],n); + if(intent==='split-complementary')return repeatOffsets(b,[150,210,0],n); + if(intent==='triadic')return repeatOffsets(b,[0,120,240],n); + if(intent==='tetradic')return repeatOffsets(b,[0,60,180,240],n); + if(intent==='square')return repeatOffsets(b,[0,90,180,270],n); + if(intent==='monochromatic')return Array.from({length:n},()=>jitterHue(b,rng,3)); + if(intent==='rainbow')return Array.from({length:n},(_,i)=>(b+(i*360)/n)%360); + return null; +} +function intentHues(intent,anchors,baseHue,count,rng){ + const n=Math.max(1,Math.min(12,Math.round(count||8))), src=anchors&&anchors.length?anchors:[baseHue]; + const harmony=harmonyHues(intent,src,baseHue,n,rng); + if(harmony)return harmony; + if(intent==='near-palette'||intent==='near-selected')return Array.from({length:n},(_,i)=>jitterHue(src[i%src.length],rng,18)); + if(intent==='fill-gaps')return generatorHues(src[0]||baseHue,'random',n,rng); + if(intent==='complements')return Array.from({length:n},(_,i)=>jitterHue((src[i%src.length]+180)%360,rng,18)); + if(intent==='bridges')return bridgeHues(src,n,rng); + return generatorHues(baseHue,'random',n,rng); +} +function vibeHueBias(hues,vibe,rng){ + const rnd=typeof rng==='function'?rng:Math.random; + const pick=bands=>bands[Math.floor(rnd()*bands.length)]; + if(vibe==='warm')return hues.map(()=>jitterHue(pick([12,28,44,58]),rng,14)); + if(vibe==='cool')return hues.map(()=>jitterHue(pick([170,195,220,250,278]),rng,16)); + if(vibe==='earthy')return hues.map(h=>jitterHue([28,42,58,82,112].reduce((a,b)=>Math.abs(b-h)<Math.abs(a-h)?b:a,42),rng,12)); + return hues; +} +function candidateLightnesses(bgHex,vibe){ + const bgL=typeof bgHex==='string'&&normHex(bgHex)?oklchOf(bgHex).L:0; + if(vibe==='pastel')return bgL>0.55?[0.74,0.68,0.80,0.62,0.86,0.56,0.50]:[0.82,0.88,0.76,0.92,0.70,0.64]; + if(vibe==='deep'||vibe==='jewel')return bgL>0.55?[0.30,0.24,0.36,0.18,0.42,0.48]:[0.56,0.62,0.50,0.68,0.44,0.74]; + return bgL>0.55 + ? [0.34,0.28,0.40,0.22,0.46,0.16,0.52,0.10,0.58] + : [0.70,0.76,0.64,0.82,0.58,0.88,0.52,0.94,0.46]; +} +function randomChroma(rng){ + const rnd=typeof rng==='function'?rng:Math.random; + return 0.10+rnd()*0.09; +} +function vibeChroma(vibe,rng){ + const rnd=typeof rng==='function'?rng:Math.random; + // [base, range]: chroma is base + rnd()*range. Table, not an if-ladder, so a + // vibe is one row to read or tune. The default covers unknown vibes. + const t={muted:[0.045,0.035],pastel:[0.035,0.045],deep:[0.085,0.055], + jewel:[0.12,0.075],earthy:[0.055,0.04],warm:[0.08,0.06], + cool:[0.08,0.06],neon:[0.18,0.09],strange:[0.145,0.095], + balanced:[0.075,0.045]}; + const [base,range]=t[vibe]||[0.12,0.07]; + return base+rnd()*range; +} +function accentCandidateForHue(hue,ground,cfg){ + const C=cfg&&cfg.vibe?vibeChroma(cfg.vibe,cfg.rng):(cfg&&cfg.scheme==='random'?randomChroma(cfg.rng):generatorChroma(cfg&&cfg.chromaMode)), target=generatorTarget(cfg&&cfg.contrastMode), bg=ground&&ground.bg; + let best=null; + for(const L of candidateLightnesses(bg,cfg&&cfg.vibe)){ + const c=oklch2hex(L,C,hue), r=bg?contrast(c.hex,bg):Infinity; + const item={hex:c.hex,L,C,hue,contrast:r,clamped:c.clamped}; + if(!best||r>best.contrast)best=item; + if(r>=target&&!isPureEndpointHex(c.hex))return item; + } + return best&&best.contrast>=target&&!isPureEndpointHex(best.hex)?best:null; +} +function candidateForHueLightness(hue,L,C,ground,cfg){ + const target=generatorTarget(cfg&&cfg.contrastMode),bg=ground&&ground.bg,c=oklch2hex(L,C,hue),r=bg?contrast(c.hex,bg):Infinity; + return r>=target&&!isPureEndpointHex(c.hex)?{hex:c.hex,L,C,hue,contrast:r,clamped:c.clamped}:null; +} +function minDistanceToSet(hex,set){ + return set.length?Math.min(...set.map(h=>deltaE(hex,h))):Infinity; +} +function hueDistance(a,b){return Math.abs((((a-b+540)%360)-180));} +function anchorHueSet(hexes){ + return hexes.map(hex=>oklchOf(hex)).filter(lch=>lch.C>0.025&&Number.isFinite(lch.H)).map(lch=>lch.H); +} +function minHueDistance(hue,hues){ + return hues.length?Math.min(...hues.map(h=>hueDistance(hue,h))):180; +} +function perceptualGapCandidates(palette,ground,cfg,sourceMode,baseHue,count,scheme,intent,hueAware){ + const anchors=sourceAnchorHexes(palette,ground,Object.assign({},cfg,{sourceMode})); + if(anchors.length<2){ + return intentHues('fill-gaps',sourceAnchorHues(palette,ground,Object.assign({},cfg,{sourceMode}),baseHue),baseHue,count,cfg.rng) + .map(hue=>accentCandidateForHue(hue,ground,Object.assign({},cfg,{scheme,intent}))).filter(Boolean); + } + const C=cfg&&cfg.vibe?vibeChroma(cfg.vibe,cfg.rng):(scheme==='random'?randomChroma(cfg.rng):generatorChroma(cfg&&cfg.chromaMode)); + const hueStep=10,hueOffset=(typeof cfg.rng==='function'?cfg.rng():Math.random())*hueStep; + const pool=[],seen=new Set(); + for(let hue=hueOffset;hue<360;hue+=hueStep){ + for(const L of candidateLightnesses(ground&&ground.bg,cfg&&cfg.vibe)){ + const cand=candidateForHueLightness(hue,L,C,ground,cfg); + if(!cand)continue; + const key=cand.hex.toLowerCase(); + if(seen.has(key))continue; + seen.add(key);pool.push(cand); + } + } + const picked=[],occupied=[...anchors]; + const occupiedHues=anchorHueSet(anchors); + while(picked.length<count&&pool.length){ + let bestI=-1,bestScore=-1,bestContrast=-1; + for(let i=0;i<pool.length;i++){ + const cand=pool[i],perceptual=minDistanceToSet(cand.hex,occupied); + const hueBonus=hueAware?0.10*(minHueDistance(cand.hue,occupiedHues)/180):0; + const score=perceptual+hueBonus; + if(score>bestScore+1e-9||(Math.abs(score-bestScore)<1e-9&&cand.contrast>bestContrast)){ + bestI=i;bestScore=score;bestContrast=cand.contrast; + } + } + const cand=pool.splice(bestI,1)[0]; + picked.push(cand);occupied.push(cand.hex);occupiedHues.push(cand.hue); + } + return picked; +} +function generatedMembers(baseHex,baseName,spanCount,columnId){ + const hex=typeof baseHex==='string'?normHex(baseHex):null; + return hex?[{hex,name:baseName,offset:0,clamped:false,columnId}]:[]; +} +function planPaletteGenerator(palette,ground,config){ + const cfg=config||{}; + const requestedSource=cfg.sourceMode||'bg-fg', resolvedSource=requestedSource==='selected' + ? (typeof cfg.selectedHex==='string'&&normHex(cfg.selectedHex)?'selected':'bg-fg') + : requestedSource; + const sourceMode=['selected','palette','none','bg-fg'].includes(resolvedSource)?resolvedSource:'bg-fg'; + const scheme=cfg.scheme||'random', intent=cfg.intent||(cfg.scheme&&cfg.scheme!=='random'?'scheme':'random'), count=Math.max(1,Math.min(12,Math.round(cfg.accentCount??5))); + const spanCount=Math.max(0,Math.min(8,Math.round(cfg.spanCount??2))); + const used=generatedExistingNames(palette), baseHue=generatorSourceHue(palette,ground,Object.assign({},cfg,{sourceMode})); + const anchors=sourceAnchorHues(palette,ground,Object.assign({},cfg,{sourceMode}),baseHue); + const columns=[], rejected=[]; + const candidates=(intent==='fill-gaps'||intent==='fill-hue-gaps') + ? perceptualGapCandidates(palette,ground,cfg,sourceMode,baseHue,count,scheme,intent,intent==='fill-hue-gaps').map(cand=>({cand,hue:cand&&cand.hue})) + : vibeHueBias(intent&&intent!=='manual'&&intent!=='scheme'?intentHues(intent,anchors,baseHue,count,cfg.rng):generatorHues(baseHue,scheme,count,cfg.rng),cfg.vibe,cfg.rng) + .map(hue=>({hue,cand:accentCandidateForHue(hue,ground,Object.assign({},cfg,{scheme,intent}))})); + for(const {cand,hue} of candidates){ + if(!cand){rejected.push({hue,reason:'contrast'});continue;} + const name=uniqueGeneratedName(nearestColorName(cand.hex,cfg.colorNames),used), columnId=name; + const members=generatedMembers(cand.hex,name,spanCount,columnId); + columns.push({name,columnId,baseHex:cand.hex,L:cand.L,C:cand.C,hue:cand.hue,contrast:cand.contrast,clamped:cand.clamped,members}); + } + const contrasts=columns.map(c=>c.contrast).filter(Number.isFinite); + return { + sourceMode, + scheme, + intent, + vibe: cfg.vibe||null, + baseHue, + accentCount:count, + spanCount, + columns, + rejected, + summary:{ + generated:columns.length, + rejected:rejected.length, + clamped:columns.reduce((n,c)=>n+(c.clamped?1:0)+c.members.filter(m=>m.clamped).length,0), + minContrast:contrasts.length?Math.min(...contrasts):null, + }, + }; +} +function entriesForGeneratedColumn(column){ + if(!column||!Array.isArray(column.members))return []; + const columnId=column.columnId||column.name||'generated'; + return column.members.map(m=>[m.hex,m.name,columnId]); +} +// Browser-side palette-generator panel. The pure planner lives in +// palette-generator-core.js; this file only gathers controls, renders previews, +// and commits selected generated colors into normal palette entries. +let GEN_PROPOSAL=null, GEN_SELECTION=null; +const GENERATOR_CONTROLS={ + genintent:{ + labelTitle:'what kind of candidate colors to look for', + options:[ + ['random','random','Pure exploration: reroll unrelated candidate base colors.'], + ['near-palette','near palette','Generate candidates near the current palette base colors.'], + ['fill-gaps','fill gaps','Find missing perceptual colors using OKLab distance.'], + ['fill-hue-gaps','fill hue gaps','Find missing perceptual colors while rewarding underrepresented hue regions.'], + ['complements','complements','Generate colors opposite palette or selected anchors.'], + ['bridges','bridges','Generate colors between existing palette anchors.'], + ['near-selected','near selected','Generate candidates near the current selector color.'], + ['complementary','complementary','Use the hue opposite the anchor.'], + ['analogous','analogous','Use neighboring hues around the anchor.'], + ['split-complementary','split complementary','Use hues on both sides of the anchor complement.'], + ['triadic','triadic','Use three hues spaced 120 degrees apart.'], + ['tetradic','tetradic','Use two complementary hue pairs.'], + ['square','square','Use four hues spaced 90 degrees apart.'], + ['monochromatic','monochromatic','Stay near one hue and vary color character.'], + ['rainbow','rainbow','Spread candidates evenly around the full hue wheel.'], + ], + }, + genvibe:{ + labelTitle:'the character of generated candidate colors', + options:[ + ['bold','bold','Higher chroma, assertive candidate colors.'], + ['balanced','balanced','Moderate chroma candidate colors.'], + ['muted','muted','Lower chroma, quieter candidate colors.'], + ['pastel','pastel','Light, low-chroma candidate colors.'], + ['deep','deep','Lower-lightness, richer candidate colors.'], + ['jewel','jewel','Saturated, rich candidate colors.'], + ['earthy','earthy','Warmer, reduced-chroma earth-tone candidates.'], + ['neon','neon','Very high-chroma candidate colors.'], + ['strange','strange','More unusual, high-variance candidate colors.'], + ['warm','warm','Bias candidates toward red, orange, and yellow.'], + ['cool','cool','Bias candidates toward green, cyan, blue, and violet.'], + ], + }, + gensource:{ + labelTitle:'where starting hues come from', + options:[ + ['palette','palette','Use current base color columns as anchors; span tiles are ignored.'], + ['none','none','Use no anchors; useful for pure random exploration.'], + ['bg-fg','bg/fg','Use the current background and foreground as anchors.'], + ['selected','selected','Use the current selector tile as the anchor.'], + ], + }, + gencontrast:{ + labelTitle:'minimum contrast against the current bg', + options:[ + ['aa','AA','Require WCAG AA contrast against the current background.'], + ['aaa','AAA','Require WCAG AAA contrast against the current background.'], + ['none','none','Do not reject candidates by WCAG contrast.'], + ], + }, +}; +function generatorOptionTitle(id,value){ + const ctl=GENERATOR_CONTROLS[id]; + const row=ctl&&ctl.options.find(o=>o[0]===value); + return row?row[2]:''; +} +function populateGeneratorSelects(){ + Object.entries(GENERATOR_CONTROLS).forEach(([id,ctl])=>{ + const el=document.getElementById(id);if(!el)return; + const cur=el.value||el.dataset.default||ctl.options[0][0]; + el.innerHTML=''; + ctl.options.forEach(([value,label])=>{const o=document.createElement('option');o.value=value;o.textContent=label;el.appendChild(o);}); + el.value=ctl.options.some(o=>o[0]===cur)?cur:ctl.options[0][0]; + const label=el.closest('label');if(label)label.title=ctl.labelTitle; + }); +} +function genConfig(){ + const intent=document.getElementById('genintent'),vibe=document.getElementById('genvibe'), + source=document.getElementById('gensource'), + accents=document.getElementById('genaccents'),contrastSel=document.getElementById('gencontrast'); + return { + intent:intent?intent.value:'random', + vibe:vibe?vibe.value:'bold', + sourceMode:source?source.value:'palette', + scheme:'random', + baseHue:250, + accentCount:accents?parseInt(accents.value,10):5, + spanCount:0, + contrastMode:contrastSel?contrastSel.value:'aa', + selectedHex:curHex(), + colorNames:COLOR_NAMES, + }; +} +function syncGeneratorControls(){syncGeneratorSelectTitles();} +function syncGeneratorSelectTitles(){ + Object.keys(GENERATOR_CONTROLS).forEach(id=>{const el=document.getElementById(id);if(el)el.title=generatorOptionTitle(id,el.value);}); +} +function setGenMessage(msg,err){const m=document.getElementById('genmsg');if(!m)return;m.textContent=msg||'';m.style.color=err?'#cb6b4d':'#8a9496';} +function renderGeneratorPreview(){ + const host=document.getElementById('genpreview');if(!host)return;host.innerHTML=''; + if(!GEN_PROPOSAL){setGenMessage('',false);return;} + GEN_PROPOSAL.columns.forEach((col,ci)=>{ + const strip=document.createElement('div');strip.className='gencol'; + const head=document.createElement('div');head.className='genhead'; + head.innerHTML=`<span title="${esc(col.name)}">${esc(col.name)}</span><button class="genappend" data-col="${ci}" title="append this generated column to the palette">+</button>`; + head.querySelector('.genappend').onclick=()=>appendGeneratedColumn(ci); + strip.appendChild(head); + col.members.forEach((m,mi)=>{ + const chip=document.createElement('div'),tc=textOn(m.hex); + chip.className='genchip'+(GEN_SELECTION&&GEN_SELECTION.hex===m.hex&&GEN_SELECTION.name===m.name?' sel':''); + chip.dataset.col=String(ci);chip.dataset.member=String(mi);chip.dataset.hex=m.hex;chip.dataset.name=m.name; + chip.style.background=m.hex;chip.style.color=tc;chip.title=m.name+' '+m.hex+(m.clamped?' (sRGB clamped)':''); + chip.innerHTML=`<div class="gn">${esc(m.name.replace(/-/g,' '))}</div><div class="gh">${m.hex}</div>`; + chip.onclick=()=>selectGeneratedTile(ci,mi); + strip.appendChild(chip); + }); + host.appendChild(strip); + }); + const s=GEN_PROPOSAL.summary; + setGenMessage(s.generated+' column(s) previewed'+(s.rejected?(', '+s.rejected+' rejected'):'')+(s.minContrast?(', min '+s.minContrast.toFixed(1)+':1'):''),false); +} +function resetGeneratorPreviewState(){ + GEN_PROPOSAL=null;GEN_SELECTION=null; + renderGeneratorPreview(); +} +function previewGenerator(){ + const cfg=genConfig(); + resetGeneratorPreviewState(); + GEN_PROPOSAL=planPaletteGenerator(PALETTE,{bg:MAP['bg'],fg:MAP['p']},cfg); + renderGeneratorPreview(); +} +function clearGeneratorPreview(){resetGeneratorPreviewState();} +function selectGeneratedTile(ci,mi){ + if(!GEN_PROPOSAL||!GEN_PROPOSAL.columns[ci])return; + const m=GEN_PROPOSAL.columns[ci].members[mi];if(!m)return; + selectedIdx=null;GEN_SELECTION={column:ci,member:mi,hex:m.hex,name:m.name}; + setHex(m.hex);document.getElementById('newname').value=m.name; + renderPalette();renderGeneratorPreview(); + notify('loaded generated "'+m.name+'" into the selector - add it to commit',false); +} +function appendGeneratedColumn(ci){ + if(!GEN_PROPOSAL||!GEN_PROPOSAL.columns[ci])return; + const colName=GEN_PROPOSAL.columns[ci].name, entries=entriesForGeneratedColumn(GEN_PROPOSAL.columns[ci]); + const existing=new Set(PALETTE.map(p=>(p[1]||'').toLowerCase())); + if(entries.some(e=>existing.has(e[1].toLowerCase()))){notify('generated names already exist - preview again for fresh names',true);return;} + PALETTE.push(...entries);GEN_SELECTION=null;selectedIdx=null; + refreshPaletteState();previewGenerator(); + notify('added generated column "'+colName+'"',false); +} +function initGeneratorControls(){ + populateGeneratorSelects(); + Object.keys(GENERATOR_CONTROLS).forEach(id=>{const el=document.getElementById(id);if(el)el.onchange=syncGeneratorControls;}); + syncGeneratorControls(); +} +// The contrast-cell readout shared by every table: a WCAG ratio colored by its +// table verdict. Callers compute r for their own fg/bg. +function verdictFor(r,target=4.5){return r>=target?'PASS':'FAIL';} +function crHtml(r){return `<span style="color:${ratingColor(r)}" title="${esc(contrastTitle(r))}">${r.toFixed(1)}</span>`;} +// Effective fg/bg with the standard fallback: an unset foreground reads as the +// default fg (MAP['p']), an unset background as the ground (MAP['bg']). All three +// tiers resolve their raw value through these before measuring or rendering. +function effFg(v){return v||MAP['p'];} +function effBg(v){return v||MAP['bg'];} +// The ground pair (background + default foreground), passed to every app-core +// helper that needs to resolve ground roles. Was the literal {bg:MAP['bg'], +// fg:MAP['p']} repeated across app.js, palette-actions.js, and the browser gates. +function groundPair(){return {bg:MAP['bg'],fg:MAP['p']};} function cid(l){return l.replace(/\W/g,'');} -function buildLangSel(){const s=document.getElementById('langsel');s.innerHTML='';for(const lang in SAMPLES){const o=document.createElement('option');o.value=lang;o.textContent=lang;s.appendChild(o);}} +function buildLangSel(){const s=document.getElementById('langsel');s.innerHTML='';for(const lang of Object.keys(SAMPLES).sort((a,b)=>a.localeCompare(b))){const o=document.createElement('option');o.value=lang;o.textContent=lang;s.appendChild(o);}if(SAMPLES['Elisp'])s.value='Elisp';} function renderCode(){ const lang=document.getElementById('langsel').value;let html=''; for(const line of SAMPLES[lang]){ if(line.length===0){html+='\n';continue;} - for(const [k,t] of line){const c=MAP[k]||'#cdced1';const w=BOLD[k]?'bold':'normal';const s=ITALIC[k]?'italic':'normal'; - html+=`<span data-k="${k}" style="color:${c};font-weight:${w};font-style:${s}">${esc(t)}</span>`;} + for(const [k,t] of line)html+=`<span data-k="${k}" style="${syntaxStyle(k)}">${esc(t)}</span>`; html+='\n';} const cp=document.getElementById('codepre');cp.innerHTML=html; cp.onclick=(e)=>{const s=e.target.closest('[data-k]');if(s)flashAssign(s.dataset.k);}; buildMockFrame(); } +// Custom color dropdown: a real swatch + name + hex per row, since native +// <option> background colors render unreliably on Linux Chrome. The popup is +// fixed-positioned on <body> so a table's overflow can't clip it. +let _ddPop=null; +function closeColorDropdown(){if(_ddPop){_ddPop.remove();_ddPop=null;}} +document.addEventListener('pointerdown',e=>{if(_ddPop&&!e.target.closest('.cdd')&&!e.target.closest('.cddpop'))closeColorDropdown();}); +function mkColorDropdown(options,cur,onPick,opts={}){ + const wrap=document.createElement('div');wrap.className='cstep'; + const left=document.createElement('button'),right=document.createElement('button'); + left.className='cstepbtn';right.className='cstepbtn';left.type=right.type='button'; + left.textContent='‹';right.textContent='›';left.title='move to next darker color in this column';right.title='move to next lighter color in this column'; + const t=document.createElement('div');t.className='cdd'+(opts.compact?' compact':'');t.tabIndex=0; + const nameOf=h=>{const o=options.find(p=>p[0]===h);return o?o[1]:(h||'none');}; + const displayHex=h=>h||(opts.defaultHex||''); + const displayName=h=>h?nameOf(h):(opts.defaultName||nameOf(h)); + function step(dir){if(wrap.dataset.locked==='1')return;const next=spanNeighborHex(cur,PALETTE,groundPair(),dir);if(!next)return;cur=next;paint();onPick(next);} + function paintStepButtons(){ + const locked=wrap.dataset.locked==='1'; + left.disabled=locked||!spanNeighborHex(cur,PALETTE,groundPair(),-1); + right.disabled=locked||!spanNeighborHex(cur,PALETTE,groundPair(),1); + } + function paint(){const shown=displayHex(cur),nm=displayName(cur),ttl=cur?(nm+' '+cur):(nm+(shown?' -> '+shown:''));t.style.background=shown||'#161412';t.style.color=shown?textOn(shown):'#b4b1a2';t.dataset.val=cur||'';t.title=ttl;t.classList.toggle('is-default',!cur);t.classList.toggle('gone',!!cur&&nameOf(cur)==='(gone)'); + t.innerHTML=opts.compact?`<span class="cddsw" style="background:${shown||'transparent'}"></span>`:`<span class="cddsw" style="background:${shown||'transparent'}"></span>${esc(nm)}`;paintStepButtons();} + paint(); + left.onclick=e=>{e.stopPropagation();step(-1);}; + right.onclick=e=>{e.stopPropagation();step(1);}; + t.onclick=(e)=>{e.stopPropagation();if(wrap.dataset.locked==='1')return;if(_ddPop){closeColorDropdown();return;} + // 2D gallery: a grid of swatches in the palette-panel shape (ground strip, + // then one row per family) instead of a long vertical list. galleryModel is + // the shared pure layout (app-core.js). + const pop=document.createElement('div');pop.className='cddpop cddgrid'; + const model=galleryModel(cur,PALETTE,groundPair()); + const pick=(hex)=>{cur=hex;paint();closeColorDropdown();onPick(hex);}; + const head=document.createElement('div');head.className='cddghead'; + const def=document.createElement('button');def.type='button'; + def.className='cddgdef'+(model.default.selected?' sel':''); + def.textContent=opts.defaultName||'default';def.title='clear — use the default'; + def.onclick=(ev)=>{ev.stopPropagation();pick('');};head.appendChild(def); + if(model.gone){const g=document.createElement('span');g.className='cddgc gone sel'; + g.style.background=model.gone.hex;g.title='(gone) '+model.gone.hex;head.appendChild(g); + const gl=document.createElement('span');gl.className='cddglbl';gl.textContent='(gone) '+model.gone.hex;head.appendChild(gl);} + pop.appendChild(head); + for(const row of model.rows){const rr=document.createElement('div');rr.className='cddgrow'; + for(const c of row.cells){const sw=document.createElement('button');sw.type='button'; + sw.className='cddgc'+(c.selected?' sel':'');sw.style.background=c.hex; + sw.dataset.hex=c.hex;sw.dataset.name=c.name;sw.title=c.name+' '+c.hex; + sw.onclick=(ev)=>{ev.stopPropagation();pick(c.hex);};rr.appendChild(sw);} + pop.appendChild(rr);} + document.body.appendChild(pop);const r=t.getBoundingClientRect(); + pop.style.left=r.left+'px';pop.style.minWidth=r.width+'px'; + pop.style.top=(r.bottom+2)+'px'; + const ph=pop.getBoundingClientRect().height; + if(r.bottom+ph>window.innerHeight-6)pop.style.top=Math.max(6,r.top-ph-2)+'px'; + const pr=pop.getBoundingClientRect(); + if(pr.right>window.innerWidth-6)pop.style.left=Math.max(6,window.innerWidth-6-pr.width)+'px'; + _ddPop=pop;}; + t.setValue=h=>{cur=h;paint();}; + wrap.setValue=h=>{cur=h;paint();}; + wrap.syncLocked=paintStepButtons; + wrap.appendChild(left);wrap.appendChild(t);wrap.appendChild(right);paintStepButtons(); + return wrap;} +// Standard option list for a swatch dropdown: a "default" entry, then the +// palette in the same ground/column order as the palette panel. If cur is set +// but no longer in the palette, surface it as a "(gone)" entry so the row still +// shows what it points at. Shared by all three tiers. +function ddList(cur){return paletteOptionList(cur,PALETTE,groundPair());} +// Shared lock toggle for any table row. lockKey is namespaced per tier (bare +// syntax kind, 'ui:'+face, 'pkg:'+app+':'+face). els are the row's editable +// controls — native selects/buttons/inputs are disabled; the custom swatch +// dropdown (a div) gets data-locked so its onclick refuses to open. +function mkLockCell(lockKey,els){ + const td=document.createElement('td');td.style.textAlign='center'; + const lk=document.createElement('button');lk.className='lockbtn'; + function paint(){const on=LOCKED.has(lockKey);lk.textContent=on?'🔒':'🔓';lk.classList.toggle('on',on); + lk.title=on?'locked — click to unlock':'click to lock this decision'; + (els||[]).forEach(el=>{if(!el)return; + if(el.tagName==='SELECT'||el.tagName==='BUTTON'||el.tagName==='INPUT')el.disabled=on; + else{el.dataset.locked=on?'1':'';el.classList.toggle('locked',on);if(el.syncLocked)el.syncLocked();}});} + lk.onclick=()=>{LOCKED.has(lockKey)?LOCKED.delete(lockKey):LOCKED.add(lockKey);paint();updateLockToggles();}; + paint();td.appendChild(lk);return td;} +// The in-row style controls, shared by the syntax / UI / package tables: a weight +// selector, a slant selector, and box-like underline and strike controls. Each +// edit mutates the face object and calls onChange to repaint. Returns the control +// elements so the caller lays them out and hands them to mkLockCell. +const WEIGHT_OPTS=[['light','light'],['normal','normal'],['medium','medium'],['semibold','semibold'],['bold','bold'],['heavy','heavy']]; +const SLANT_OPTS=[['normal','normal'],['italic','italic'],['oblique','oblique']]; +// A compact custom dropdown for an enum attribute (weight / slant), themed like +// the color dropdown. The trigger shows the current value drawn in its own weight +// or slant; the popup lists each option drawn with the attribute applied, so the +// choice previews itself. opts.styleFor(value) returns the preview style props +// ({fontWeight} / {fontStyle}); opts.placeholder is the unset-state label. +function mkEnumDropdown(options,get,set,opts={}){ + const t=document.createElement('div');t.className='cdd enumdd';t.tabIndex=0; + const styleFor=opts.styleFor||(()=>({})); + const labelOf=v=>{const o=options.find(p=>p[0]===v);return o?o[1]:'';}; + function applyPreview(el,v){el.style.fontWeight='';el.style.fontStyle='';const s=styleFor(v);if(s.fontWeight)el.style.fontWeight=s.fontWeight;if(s.fontStyle)el.style.fontStyle=s.fontStyle;} + function paint(){const v=get()||'';t.dataset.val=v;t.classList.toggle('is-default',!v); + t.textContent=v?labelOf(v):(opts.placeholder||'set');applyPreview(t,v);t.title=opts.title||'';} + paint(); + t.onclick=(e)=>{e.stopPropagation();if(t.dataset.locked==='1')return;if(_ddPop){closeColorDropdown();return;} + const pop=document.createElement('div');pop.className='cddpop enumpop';const cur=get()||''; + const pick=v=>{set(v||null);paint();closeColorDropdown();}; + const def=document.createElement('button');def.type='button'; + def.className='enumopt enumdef'+(cur===''?' sel':'');def.textContent='default'; + def.title='clear — use the default';def.onclick=ev=>{ev.stopPropagation();pick('');};pop.appendChild(def); + for(const [v,label] of options){const b=document.createElement('button');b.type='button'; + b.className='enumopt'+(v===cur?' sel':'');b.textContent=label;applyPreview(b,v); + b.onclick=ev=>{ev.stopPropagation();pick(v);};pop.appendChild(b);} + document.body.appendChild(pop);const r=t.getBoundingClientRect(); + pop.style.left=r.left+'px';pop.style.minWidth=r.width+'px';pop.style.top=(r.bottom+2)+'px'; + const ph=pop.getBoundingClientRect().height; + if(r.bottom+ph>window.innerHeight-6)pop.style.top=Math.max(6,r.top-ph-2)+'px'; + _ddPop=pop;}; + t.setValue=()=>paint();t.syncLocked=()=>paint(); + return t;} +// Underline control: none / line / wave glyph buttons plus a color swatch shown +// while a style is active. Mirrors mkBoxControl; get()/set() read and write the +// underline object ({style,color}) or null. +function mkLineStyleControl(states,get,set,opts={}){const wrap=document.createElement('div');wrap.className='boxctl'; + const cluster=document.createElement('div');cluster.className='boxcluster';const btns={}; + states.forEach(([v,title,glyph])=>{const b=document.createElement('button');b.className='boxbtn';b.dataset.style=v;b.textContent=glyph;b.title=title; + b.onclick=()=>{const cur=get();set(v?(opts.toState?opts.toState(v,cur):Object.assign({color:(cur&&cur.color)||null},opts.styled?{style:v}:{})):null);paint();}; + cluster.appendChild(b);btns[v]=b;}); + const dd=mkColorDropdown(ddList((get()&&get().color)||''),(get()&&get().color)||'',h=>{const cur=get();if(!cur)return;set(Object.assign({},cur,{color:h||null}));paint();},{compact:true,defaultHex:opts.defaultHex}); + function paint(){const cur=get(),active=opts.styled?(cur&&cur.style?cur.style:''):(cur?'on':''); + for(const v in btns)btns[v].classList.toggle('on',v===active); + dd.style.display=active?'':'none';dd.setValue(cur&&cur.color?cur.color:''); + const locked=wrap.dataset.locked==='1';for(const v in btns)btns[v].disabled=locked; + const ddoff=locked||!active;dd.dataset.locked=ddoff?'1':'';dd.classList.toggle('locked',ddoff);if(dd.syncLocked)dd.syncLocked();} + wrap.syncLocked=()=>paint();wrap.append(cluster,dd);paint();return wrap;} +function mkUnderlineControl(get,set,opts={}){ + return mkLineStyleControl([['','no underline',''],['line','underline','_'],['wave','wavy underline','~']],get,set,Object.assign({styled:true},opts));} +function mkStrikeControl(get,set,opts={}){ + return mkLineStyleControl([['','no strike',''],['on','strike-through','S']],get,set,Object.assign({styled:false},opts));} +// In-row style controls: weight + slant selectors and a strike control. The +// underline control lives in the per-row expander (it carries the wave/color +// detail), keeping the row compact. +function mkStyleControls(face,onChange,opts={}){ + const w=mkEnumDropdown(WEIGHT_OPTS,()=>face.weight,v=>{face.weight=v;onChange();},{placeholder:'weight',title:'font weight',styleFor:v=>({fontWeight:cssWeight(v)})}); + const s=mkEnumDropdown(SLANT_OPTS,()=>face.slant,v=>{face.slant=v;onChange();},{placeholder:'slant',title:'font slant',styleFor:v=>({fontStyle:v||'normal'})}); + const k=mkStrikeControl(()=>face.strike,v=>{face.strike=v;onChange();},opts); + return [w,s,k];} +function mkOverlineControl(get,set,opts={}){ + return mkLineStyleControl([['','no overline',''],['on','overline','O']],get,set,Object.assign({styled:false},opts));} +function mkCheck(get,set){const c=document.createElement('input');c.type='checkbox';c.className='detailcheck';c.checked=!!get();c.onchange=()=>set(c.checked);return c;} +// The per-row attribute editor revealed by the expander: distant-fg, family, +// overline, inverse, extend, and (for ui/syntax, where inherit/height have no +// inline column) inherit + height. Each control mutates FACE and calls onChange. +// Returns the element plus the interactive controls so the row's lock cell can +// disable them. opts.inheritOptions and opts.showInheritHeight gate the last two. +// Hover help for each expander field, so the detail labels explain themselves the +// way the table-header labels do. Keyed by the label text passed to add(). +const DETAIL_HOVERS={ + 'distant fg':'foreground swapped in when the text sits on a background too close to its own color to read (Emacs :distant-foreground)', + 'family':'font family for this face; blank inherits the default (Emacs :family)', + 'underline':'underline style and color (Emacs :underline)', + 'overline':'a line drawn above the text (Emacs :overline)', + 'inverse':'swap the foreground and background (Emacs :inverse-video)', + 'extend':'extend the background past the end of the line to the window edge (Emacs :extend)', + 'inherit':'base face this one inherits unset attributes from (Emacs :inherit)', + 'height':'text size as a scaling factor of the inherited height, 0.1 to 2.0 (Emacs :height)' +}; +function mkDetailEditor(face,onChange,opts={}){ + const wrap=document.createElement('div');wrap.className='detailedit';const locks=[]; + const add=(label,el)=>{const g=document.createElement('label');g.className='detailfield';g.title=DETAIL_HOVERS[label]||'';const s=document.createElement('span');s.textContent=label;g.append(s,el);wrap.appendChild(g);locks.push(el);}; + const df=mkColorDropdown(ddList(face['distant-fg']||''),face['distant-fg']||'',h=>{face['distant-fg']=h||null;onChange();},{compact:true,defaultHex:opts.defaultHex}); + add('distant fg',df); + const fam=document.createElement('input');fam.type='text';fam.className='detailinput';fam.placeholder='font family';fam.value=face.family||'';fam.onchange=()=>{face.family=fam.value.trim()||null;onChange();}; + add('family',fam); + add('underline',mkUnderlineControl(()=>face.underline,v=>{face.underline=v;onChange();},opts)); + add('overline',mkOverlineControl(()=>face.overline,v=>{face.overline=v;onChange();},opts)); + add('inverse',mkCheck(()=>face.inverse,v=>{face.inverse=v;onChange();})); + add('extend',mkCheck(()=>face.extend,v=>{face.extend=v;onChange();})); + if(opts.showInheritHeight){ + const isel=document.createElement('select');isel.className='chip detailsel'; + (opts.inheritOptions||['']).forEach(o=>{const op=document.createElement('option');op.value=o;op.textContent=o||'— none —';isel.appendChild(op);}); + isel.value=face.inherit||'';isel.onchange=()=>{face.inherit=isel.value||null;onChange();};add('inherit',isel); + const hin=document.createElement('input');hin.type='number';hin.min=''+HEIGHT_MIN;hin.max=''+HEIGHT_MAX;hin.step='0.05';hin.className='hstep';hin.value=face.height||1;hin.onchange=()=>{const raw=hin.value,h=clampHeight(raw);face.height=h;hin.value=h==null?1:h;if(h!=null&&parseFloat(raw)!==h)notify('height clamped to '+h+' (allowed '+HEIGHT_MIN+'–'+HEIGHT_MAX+')',false);onChange();};add('height',hin); + } + return {el:wrap,locks};} +// Wire a per-row expander: a toggle button plus a hidden detail row (colspan +// across the table) holding mkDetailEditor. The caller drops the button into a +// cell, adds the returned locks to the row's lock cell, and inserts detailRow +// right after the main row. +// Which rows have their detail expanded, keyed by the row's element/face key. +// Held outside the DOM so a table rebuild (a package edit rebuilds the whole +// table) re-opens the rows that were open, instead of collapsing them under the +// user — editing a value in an open expander must not close it. +let EXPANDED=new Set(); +function mkExpander(face,colspan,onChange,opts={}){ + const detail=document.createElement('tr');detail.className='detailrow';detail.style.display='none'; + if(opts.expandKey&&EXPANDED.has(opts.expandKey))detail.style.display=''; + const btn=document.createElement('button');btn.className='exptoggle'; + // The disclosure triangle shows the row's state: ▶ collapsed, ▼ expanded. + const setGlyph=()=>{const open=detail.style.display!=='none';btn.textContent=open?'▼':'▶';btn.classList.toggle('on',open);}; + // Flag the toggle when collapsed and at least one hidden attribute differs from + // the default, so a non-default attribute is never invisible. ndCheck re-runs + // after every edit (for tiers whose onChange does not rebuild the row). + const ndCheck=opts.ndCheck||(()=>false); + const refreshNd=()=>{const nd=ndCheck();btn.classList.toggle('exp-nd',nd);btn.title=nd?'more attributes (some differ from default)':'more attributes';}; + const wrapped=()=>{onChange();refreshNd();}; + const td=document.createElement('td');td.colSpan=colspan;const {el,locks}=mkDetailEditor(face,wrapped,opts);td.appendChild(el);detail.appendChild(td); + btn.onclick=()=>{const willOpen=detail.style.display==='none';detail.style.display=willOpen?'':'none'; + if(opts.expandKey){willOpen?EXPANDED.add(opts.expandKey):EXPANDED.delete(opts.expandKey);} + setGlyph();syncExpandAllBtns();}; + refreshNd();setGlyph(); + return {btn,detail,locks};} +// Expand/collapse every row in a table at once, then sync the per-row triangles. +function setAllExpanded(tableId,expand){ + const tb=document.getElementById(tableId);if(!tb)return; + tb.querySelectorAll('tr.detailrow').forEach(d=>{d.style.display=expand?'':'none';const k=d.dataset.detailFor;if(k){expand?EXPANDED.add(k):EXPANDED.delete(k);}}); + tb.querySelectorAll('.exptoggle').forEach(b=>{b.textContent=expand?'▼':'▶';b.classList.toggle('on',expand);}); +} +// The header-level expand/collapse-all toggle for a table. Its label and triangle +// track the aggregate: any row open -> ▼ collapse all; all closed -> ▶ expand all. +const EXPALL_TABLE={syntaxexpandall:'legbody',uiexpandall:'uibody',pkgexpandall:'pkgbody'}; +function syncExpandAllBtns(){ + for(const id in EXPALL_TABLE){const btn=document.getElementById(id);const tb=document.getElementById(EXPALL_TABLE[id]);if(!btn||!tb)continue; + const anyOpen=[...tb.querySelectorAll('tr.detailrow')].some(d=>d.style.display!=='none'); + btn.textContent=anyOpen?'▼ collapse all':'▶ expand all';} +} +function toggleAllExpanded(id){ + const tableId=EXPALL_TABLE[id],tb=document.getElementById(tableId);if(!tb)return; + const anyOpen=[...tb.querySelectorAll('tr.detailrow')].some(d=>d.style.display!=='none'); + setAllExpanded(tableId,!anyOpen);syncExpandAllBtns(); +} +// Column count for a table's detail-row colspan, read from its header so the +// expander never hardcodes a width that drifts when a column is added. +function tableColCount(tableId){const h=document.querySelector('#'+tableId+' thead tr');return h?h.cells.length:1;} +// Apply a batch action to every editable row in a tier. keyFn maps a row entry to +// its lock key, or null to skip the row entirely (syntax bg and the default fg); +// resetFn does the actual clearing. Locked rows are left untouched. +function clearUnlockedRows(items,keyFn,resetFn){ + for(const it of items){const k=keyFn(it);if(k===null)continue;if(!LOCKED.has(k))resetFn(it);} +} +function rebuildColorTables(){ + buildTable();buildUITable();if(document.getElementById('pkgbody'))buildPkgTable(); +} +function refreshPaletteState(opts={}){ + renderPalette();rebuildColorTables(); + if(opts.pkgPreview)buildPkgPreview(); + if(opts.code!==false)renderCode(); + if(opts.ground!==false)applyGround(); + if(opts.covered)repaintCovered(); +} +function syntaxLockKeys(){return CATS.map(c=>c[0]);} +function uiLockKeys(){return UI_FACES.map(f=>'ui:'+f[0]);} +function pkgLockKeys(){const app=curApp();return APPS[app].faces.map(f=>'pkg:'+app+':'+f[0]);} +function tierLockKeys(tier){return tier==='syntax'?syntaxLockKeys():tier==='ui'?uiLockKeys():pkgLockKeys();} +function updateLockToggle(tier){ + const ids={syntax:'syntaxlocktoggle',ui:'uilocktoggle',pkg:'pkglocktoggle'},b=document.getElementById(ids[tier]);if(!b)return; + b.textContent=lockToggleLabel(tierLockKeys(tier),LOCKED); +} +function updateLockToggles(){updateLockToggle('syntax');updateLockToggle('ui');updateLockToggle('pkg');updateViewLockIndicators();} +function toggleAllLocks(tier){ + const all=areAllLocked(tierLockKeys(tier),LOCKED); + LOCKED=toggleLockSet(tierLockKeys(tier),LOCKED); + if(tier==='syntax')buildTable();else if(tier==='ui')buildUITable();else buildPkgTable(); + updateLockToggles(); + notify((all?'unlocked ':'locked ')+(tier==='pkg'?'package':tier)+' rows',false); +} +function clearUnlocked(){ + clearUnlockedRows(CATS,c=>(c[0]==='bg'||c[0]==='p')?null:c[0],c=>{SYNTAX[c[0]]=syntaxBlank(c[0]);SYNTAX[c[0]].fg=null;syncSyntaxCache(c[0]);}); + buildTable();renderCode();notify('erased editable syntax elements',false); +} +function resetUnlocked(){ + clearUnlockedRows(CATS,c=>c[0],c=>{const k=c[0];SYNTAX[k]=JSON.parse(JSON.stringify(DEFAULT_SYNTAX[k]||syntaxBlank(k)));syncSyntaxCache(k);}); + rebuildColorTables();buildPkgPreview();renderCode();applyGround();repaintCovered(); + notify('reset editable syntax elements to captured defaults',false); +} +function clearUnlockedUI(){ + clearUnlockedRows(UI_FACES,f=>'ui:'+f[0],f=>{UIMAP[f[0]]=uiFaceBlank();}); + buildUITable();buildMockFrame();notify('erased editable UI faces',false); +} +function resetUnlockedUI(){ + clearUnlockedRows(UI_FACES,f=>'ui:'+f[0],f=>{UIMAP[f[0]]=JSON.parse(JSON.stringify(DEFAULT_UIMAP[f[0]]||uiFaceBlank()));}); + buildUITable();buildMockFrame();notify('reset editable UI faces to captured defaults',false); +} +function clearUnlockedPkg(){ + const app=curApp(); + clearUnlockedRows(APPS[app].faces,f=>'pkg:'+app+':'+f[0],f=>{PKGMAP[app][f[0]]=normalizePkgFace({source:'cleared'},'cleared');}); + pkgChanged();notify('erased editable '+app+' faces',false); +} function buildTable(){ const tb=document.getElementById('legbody');tb.innerHTML=''; for(const [kind,label,ex] of CATS){ const tr=document.createElement('tr');tr.dataset.kind=kind; - const sel=document.createElement('select');sel.className='chip'; - const cur=MAP[kind];const have=PALETTE.some(p=>p[0]===cur); - const list=have?PALETTE:[[cur,'(gone) '+cur],...PALETTE]; - for(const [hex,name] of list){const o=document.createElement('option');o.value=hex;o.textContent=name+' '+hex;o.style.background=hex;o.style.color=textOn(hex);sel.appendChild(o);} - sel.value=cur; + const sf=syntaxFace(kind),cur=sf.fg||'',list=ddList(cur); const exTd=document.createElement('td');exTd.className='ex';exTd.textContent=ex; const crTd=document.createElement('td');crTd.style.whiteSpace='nowrap';crTd.style.fontSize='10pt'; - function styleChip(){sel.style.background=sel.value;sel.style.color=textOn(sel.value);} - function styleEx(){exTd.style.color=(kind==='bg'?MAP['p']:MAP[kind]);exTd.style.background=MAP['bg'];exTd.style.fontWeight=BOLD[kind]?'bold':'normal';exTd.style.fontStyle=ITALIC[kind]?'italic':'normal';} - function styleCr(){const r=contrast((kind==='bg'?MAP['p']:MAP[kind]),MAP['bg']);crTd.innerHTML=`<span style="color:${ratingColor(r)}">${r.toFixed(1)} ${rating(r)}</span>`;} - styleChip();styleEx();styleCr(); - sel.onchange=()=>{MAP[kind]=sel.value;styleChip();styleEx();styleCr();renderCode();if(kind==='bg'){applyGround();buildTable();}}; - // style buttons + function rowFg(){return kind==='bg'?MAP['p']:effFg(syntaxFace(kind).fg);} + function rowBg(){return syntaxFace(kind).bg||MAP['bg'];} + function styleEx(){const s=syntaxFace(kind);exTd.style.color=rowFg();exTd.style.background=rowBg();exTd.style.fontWeight=cssWeight(s.weight);exTd.style.fontStyle=s.slant||'normal';exTd.style.textDecoration=(s.underline?'underline ':'')+(s.strike?'line-through':'')||'none';exTd.style.boxShadow=boxCss(s.box,rowBg());} + function styleCr(){const r=contrast(rowFg(),rowBg());crTd.innerHTML=crHtml(r);} + const dd=mkColorDropdown(list,cur,(hex)=>{const s=syntaxFace(kind);s.fg=hex||null;syncSyntaxCache(kind);styleEx();styleCr();renderCode();if(kind==='bg'||kind==='p'){applyGround();buildTable();buildPkgTable();buildPkgPreview();}repaintCovered();},{compact:true,defaultHex:rowFg()}); + const bgd=mkColorDropdown(ddList(sf.bg||''),sf.bg||'',hex=>{const s=syntaxFace(kind);s.bg=hex||null;styleEx();styleCr();renderCode();repaintCovered();},{compact:true,defaultHex:rowBg()}); + styleEx();styleCr(); const stTd=document.createElement('td'); - if(kind!=='bg'){const defs=[['B','a','bold'],['I','a','italic']]; - const btns={}; - defs.forEach(([id,ch,mode])=>{const b=document.createElement('button');b.className='sbtn';b.style.fontWeight=mode==='bold'?'bold':'normal';b.style.fontStyle=mode==='italic'?'italic':'normal';b.textContent=ch; - b.onclick=()=>{if(mode==='bold'){BOLD[kind]=!BOLD[kind];}else{ITALIC[kind]=!ITALIC[kind];}refresh();renderCode();styleEx();}; - btns[mode]=b;stTd.appendChild(b);}); - function refresh(){btns.bold.classList.toggle('on',!!BOLD[kind]);btns.italic.classList.toggle('on',!!ITALIC[kind]);} - refresh();} - const c0=document.createElement('td');c0.appendChild(sel); - const c2=document.createElement('td');c2.className='cat';c2.textContent=label;c2.style.cursor='pointer';c2.title='flash this category in the code';c2.onclick=()=>flashTokens(kind); - tr.appendChild(c2);tr.appendChild(c0);tr.appendChild(stTd);tr.appendChild(exTd);tr.appendChild(crTd); - tb.appendChild(tr);} -} -let dragFrom=null,selectedIdx=null; + const stCtls=mkStyleControls(syntaxFace(kind),()=>{styleEx();renderCode();},{defaultHex:rowFg()}); + const stCluster=document.createElement('div');stCluster.className='stylecluster';stCtls.forEach(c=>stCluster.appendChild(c));stTd.appendChild(stCluster); + const c0=document.createElement('td');c0.appendChild(dd); + const cB=document.createElement('td');cB.appendChild(bgd); + const cX=document.createElement('td');const boxCtl=mkBoxControl(()=>syntaxFace(kind).box,b=>{syntaxFace(kind).box=b;styleEx();renderCode();},{compact:true});cX.appendChild(boxCtl); + const exp=mkExpander(syntaxFace(kind),tableColCount('legtable'),()=>{styleEx();renderCode();},{expandKey:kind,showInheritHeight:true,inheritOptions:[''].concat(BASE_INHERITS),defaultHex:rowFg(),ndCheck:()=>overflowNonDefault(syntaxFace(kind),DEFAULT_SYNTAX[kind],true)}); + exp.detail.dataset.detailFor=kind; + const lkTd=mkLockCell(kind,[dd,bgd,...stCtls,boxCtl,...exp.locks]); + const c2=document.createElement('td');c2.className='cat';c2.title=composeHoverTitle(SYNTAX_DOCS[kind],c2.title);c2.appendChild(exp.btn); + const c2lbl=document.createElement('span');c2lbl.textContent=' '+label;c2lbl.style.cursor='pointer';c2lbl.title='flash this category in the code';c2lbl.onclick=()=>flashTokens(kind);c2.appendChild(c2lbl); + tr.appendChild(lkTd);tr.appendChild(c2);tr.appendChild(c0);tr.appendChild(cB);tr.appendChild(stTd);tr.appendChild(cX);tr.appendChild(crTd);tr.appendChild(exTd); + tb.appendChild(tr);tb.appendChild(exp.detail);} + updateLockToggle('syntax');syncExpandAllBtns(); +} +function clearPalette(){ + normalizePalette(); + const plan=clearPalettePlan(PALETTE,groundPair()); + plan.removed.forEach(({hex,name})=>rememberGone(hex,name)); + PALETTE=plan.palette;selectedIdx=null; + refreshPaletteState(); + notify('cleared palette to bg and fg',false); +} +let selectedIdx=null; +// When a named palette color is deleted, remember its hex keyed by name so that +// recreating a color with the same name can re-bind the assignments still pointing +// at the old (now "(gone)") hex. Consumed once per name; cleared on import. +let lastGone={}; +// Re-point every assignment — syntax faces, UI faces, package faces — from one hex +// to another. Used when a palette color's value is edited and when a deleted name +// is recreated. +function repointFaceFields(face,oldHex,newHex){ + if(face.fg===oldHex)face.fg=newHex; + if(face.bg===oldHex)face.bg=newHex; + if(face.box&&face.box.color===oldHex)face.box.color=newHex; +} +function repointHex(oldHex,newHex){ + if(oldHex===newHex)return; + for(const k in SYNTAX){repointFaceFields(SYNTAX[k],oldHex,newHex);syncSyntaxCache(k);} + for(const f in UIMAP)repointFaceFields(UIMAP[f],oldHex,newHex); + for(const ap in PKGMAP)for(const fc in PKGMAP[ap])repointFaceFields(PKGMAP[ap][fc],oldHex,newHex); +} +// On adding a color, if its name matches a recently-deleted one, re-bind the +// stranded assignments to the new hex. Returns true when a heal context existed. +function healGone(name,newHex){const k=name.toLowerCase();if(!(k in lastGone))return false;const g=lastGone[k];delete lastGone[k];repointHex(g,newHex);return true;} +function rememberGone(hex,name){if(name)lastGone[name.toLowerCase()]=hex;} +function normalizePaletteEntry(entry){ + const hex=entry&&entry[0],name=(entry&&entry[1])||'color'; + return [hex,name,(entry&&entry[2])||columnIdOf(entry)]; +} +function ensureGroundEndpoints(){ + const g=groundPair(); + if(g.bg&&!PALETTE.some(entry=>groundRoleOfEntry(entry,g)==='bg'))PALETTE.unshift([g.bg,'bg','ground']); + if(g.fg&&!PALETTE.some(entry=>groundRoleOfEntry(entry,g)==='fg'))PALETTE.push([g.fg,'fg','ground']); +} +function normalizePalette(){PALETTE=PALETTE.map(normalizePaletteEntry);ensureGroundEndpoints();} +// The ground column is explicit: bg pins the top endpoint, fg pins the bottom +// endpoint, and generated ground+N steps live between them. +function groundColumnMembers(){ + return groundColumnMembersFromPalette(PALETTE,groundPair()); +} +function groundSpanCount(){return PALETTE.filter(entry=>groundRoleOfEntry(entry,groundPair())==='step').length;} +function groundSpanControl(){ + const d=document.createElement('div');d.className='fcount'; + d.innerHTML=`<span title="number of ground colors between bg and fg">span <input type="number" min="0" max="8" value="${groundSpanCount()}"></span>`; + d.querySelector('input').onchange=(e)=>setGroundSpan(Math.max(0,Math.min(8,parseInt(e.target.value,10)||0))); + return d; +} +function setGroundSpan(n){ + const old=PALETTE.filter(entry=>groundRoleOfEntry(entry,groundPair())==='step'); + const bg=srgb2oklab(MAP['bg']),fg=srgb2oklab(MAP['p']); + const entries=[]; + let step=1; + for(let i=1;i<=n;i++){ + const t=i/(n+1); + const lab={L:bg.L+(fg.L-bg.L)*t,a:bg.a+(fg.a-bg.a)*t,b:bg.b+(fg.b-bg.b)*t}; + const hex=lrgb2hex(oklab2lrgb(lab.L,lab.a,lab.b)); + if(hex.toLowerCase()==='#ffffff'||hex.toLowerCase()==='#000000')continue; + entries.push([hex,'ground+'+(step++),'ground']); + } + for(const [oldHex,oldName] of old){ + const next=entries.find(([,name])=>name===oldName); + if(next&&next[0].toLowerCase()!==oldHex.toLowerCase())repointHex(oldHex,next[0]); + } + for(let i=PALETTE.length-1;i>=0;i--)if(groundRoleOfEntry(PALETTE[i],groundPair())==='step')PALETTE.splice(i,1); + let at=PALETTE.findIndex(entry=>groundRoleOfEntry(entry,groundPair())==='bg'); + if(at<0)at=0; else at+=1; + PALETTE.splice(Math.min(at,PALETTE.length),0,...entries); + selectedIdx=null;refreshPaletteState(); + notify('set ground span to '+n,false); +} // Pairwise OKLab ΔE over the palette. Returns the sub-threshold pairs (sorted // closest-first) and each color's nearest-neighbor distance for its chip title. -function paletteDeltas(){ - const n=PALETTE.length,nearest=new Array(n).fill(Infinity),pairs=[]; - for(let i=0;i<n;i++)for(let j=i+1;j<n;j++){const d=deltaE(PALETTE[i][0],PALETTE[j][0]); - if(d<nearest[i])nearest[i]=d;if(d<nearest[j])nearest[j]=d; - if(d<DELTAE_MIN)pairs.push({i,j,d});} - pairs.sort((a,b)=>a.d-b.d); - return {pairs,nearest}; -} -function renderPaletteWarnings(pairs){ - const w=document.getElementById('palwarn');if(!w)return; - if(!pairs.length){w.style.display='none';w.innerHTML='';return;} - const cap=5,shown=pairs.slice(0,cap); - let html='<div class="pwh">too-similar colors</div>'; - html+=shown.map(p=>`<div class="pwl">${esc(PALETTE[p.i][1]+' / '+PALETTE[p.j][1])} — \u0394E ${p.d.toFixed(3)}, hard to distinguish</div>`).join(''); - if(pairs.length>cap)html+=`<div class="pwl">and ${pairs.length-cap} more</div>`; - w.innerHTML=html;w.style.display='block'; +// One palette chip for PALETTE[i], with its remove / rename / select handlers. +// Families sort deterministically, so the old move-arrow / drag reordering is gone. +function paletteChip(i,nearest,used,scopes){ + const [hex,name]=PALETTE[i],tc=textOn(hex),nde=nearest[i]; + const role=groundRoleOfEntry(PALETTE[i],groundPair()); + const locked=(role==='bg'||role==='fg'); + const d=document.createElement('div');d.className='pchip'+(i===selectedIdx?' sel':'');d.style.background=hex; + d.dataset.paletteIndex=String(i); + d.title=name+' '+hex+(nde===Infinity||nde===undefined?'':' — nearest ΔE '+nde.toFixed(3)); + if(used&&!used.has(hex.toLowerCase())){d.classList.add('unused');d.title+=' — not used in the theme';} + else if(scopes){const u=paletteUsages(hex,scopes,PALETTE);if(u.length)d.title+='\n'+u.join('\n');} + const rm=locked?`<span class="lock" title="${role==='bg'?'background':'foreground'} — can't remove" style="color:${tc}">🔒</span>`:`<button class="rm" title="remove" style="color:${tc}">×</button>`; + d.innerHTML=`${rm}<input class="nm" value="${name}" readonly style="color:${tc}"><div class="hx" style="color:${tc}">${hex}</div>`; + if(!locked)d.querySelector('.rm').onclick=(e)=>{e.stopPropagation();rememberGone(hex,name);PALETTE.splice(i,1);if(selectedIdx===i)selectedIdx=null;refreshPaletteState({code:false,ground:false});}; + const nm=d.querySelector('.nm'); + const finishNameEdit=()=>{nm.readOnly=true;nm.classList.remove('editing');}; + nm.onclick=(e)=>{e.preventDefault();e.stopPropagation();selectColor(i);}; + nm.ondblclick=(e)=>{e.preventDefault();e.stopPropagation();nm.readOnly=false;nm.classList.add('editing');nm.focus();nm.setSelectionRange(0,0);}; + nm.onblur=finishNameEdit; + nm.onkeydown=(e)=>{if(e.key==='Enter'){e.preventDefault();nm.blur();}else if(e.key==='Escape'){e.preventDefault();nm.value=PALETTE[i][1];nm.blur();}}; + nm.onchange=(e)=>{PALETTE[i][1]=e.target.value;buildTable();buildUITable();}; + d.onclick=(e)=>{if(e.target.closest('.rm'))return;selectColor(i);}; + return d; +} +function setChipPreviewColor(i,hex){ + const chip=document.querySelector('#pals .pchip[data-palette-index="'+i+'"]');if(!chip)return; + const tc=textOn(hex);chip.style.background=hex; + chip.querySelectorAll('.nm,.hx,.rm,.lock').forEach(e=>e.style.color=tc); +} +function previewSelectedChip(hex){if(selectedIdx===null)return;setChipPreviewColor(selectedIdx,hex);} +function restoreSelectedChip(){if(selectedIdx===null||!PALETTE[selectedIdx])return;setChipPreviewColor(selectedIdx,PALETTE[selectedIdx][0]);} +function paletteIndexByHexName(hex,name){ + for(let i=0;i<PALETTE.length;i++)if(PALETTE[i][0]===hex&&PALETTE[i][1]===name)return i; + return -1; +} +function selectColumnBase(f){ + const baseMember=f.members.find(m=>m.hex.toLowerCase()===f.base.toLowerCase())||f.members[0]; + const i=paletteIndexByHexName(baseMember.hex,baseMember.name); + if(i>=0)selectColor(i); +} +function isGroundEntry(entry){ + return !!groundRoleOfEntry(entry,groundPair()); +} +function moveColumn(columnId,dir){ + normalizePalette(); + const columns=sortColumns(columnsFromPalette(PALETTE,groundPair()).columns); + const pos=columns.findIndex(f=>f.column===columnId); + const next=columns[pos+dir]; + if(pos<0||!next)return; + const moving=[],rest=[]; + PALETTE.forEach(entry=>{ + if(!isGroundEntry(entry)&&columnIdOf(entry)===columnId)moving.push(entry); + else rest.push(entry); + }); + const nextPositions=[]; + rest.forEach((entry,i)=>{if(!isGroundEntry(entry)&&columnIdOf(entry)===next.column)nextPositions.push(i);}); + if(!nextPositions.length)return; + const at=dir<0?nextPositions[0]:nextPositions[nextPositions.length-1]+1; + PALETTE=rest.slice(0,at).concat(moving,rest.slice(at)); + selectedIdx=null;refreshPaletteState(); + notify('moved "'+columnId+'" '+(dir<0?'left':'right'),false); } +function deleteColumn(columnId,label){ + normalizePalette(); + const plan=deletePaletteColumnPlan(PALETTE,groundPair(),columnId); + if(!plan.removed.length){notify('nothing to delete in "'+(label||columnId)+'"',true);return;} + const title=label||columnId; + if(!confirm('Delete color column "'+title+'"?\n\nThis removes '+plan.removed.length+' palette color(s). Existing face assignments will stay on their old hex values and show as "(gone)".'))return; + plan.removed.forEach(({hex,name})=>rememberGone(hex,name)); + PALETTE=plan.palette;selectedIdx=null; + refreshPaletteState(); + notify('deleted column "'+title+'" — '+plan.removed.length+' color(s) now show "(gone)" where used',false); +} +function columnHeader(f,position,count){ + const h=document.createElement('div');h.className='fhead'; + const label=(f.members.find(m=>m.hex.toLowerCase()===f.base.toLowerCase())||{}).name||f.column||f.base; + h.innerHTML=`<button class="cmove left" title="move column left" ${position===0?'disabled':''}>‹</button><button class="ctitle" title="select base color"></button><button class="cmove right" title="move column right" ${position===count-1?'disabled':''}>›</button><button class="cdel" title="delete column with confirmation">×</button>`; + h.querySelector('.ctitle').textContent=label; + h.querySelector('.ctitle').onclick=()=>selectColumnBase(f); + h.querySelector('.left').onclick=(e)=>{e.stopPropagation();moveColumn(f.column,-1);}; + h.querySelector('.right').onclick=(e)=>{e.stopPropagation();moveColumn(f.column,1);}; + h.querySelector('.cdel').onclick=(e)=>{e.stopPropagation();deleteColumn(f.column,label);}; + return h; +} +// Render the palette as structural color columns: pinned ground column, then +// first-seen palette columns. Grouping uses the stable column id stored on each +// palette entry, so renaming a color never moves it. +// Palette display mode: full (every span tile) or base-only (one tile per +// column), toggled by the arrow control to conserve vertical space. +let paletteShowFull=true; function renderPalette(){ + normalizePalette(); const p=document.getElementById('pals');p.innerHTML=''; - const {pairs,nearest}=paletteDeltas(); - PALETTE.forEach((pc,i)=>{const [hex,name]=pc;const tc=textOn(hex); - const nde=nearest[i]; - const locked=(hex===MAP['bg']||hex===MAP['p']); - const d=document.createElement('div');d.className='pchip'+(i===selectedIdx?' sel':'');d.style.background=hex;d.draggable=true; - d.title=name+' '+hex+(nde===Infinity?'':' — nearest \u0394E '+nde.toFixed(3)); - const lft=i>0?`<button class="mv l" title="move left" style="color:${tc}">‹</button>`:''; - const rgt=i<PALETTE.length-1?`<button class="mv r" title="move right" style="color:${tc}">›</button>`:''; - const rm=locked?`<span class="lock" title="${hex===MAP['bg']?'background':'foreground'} — can't remove" style="color:${tc}">🔒</span>`:`<button class="rm" title="remove" style="color:${tc}">×</button>`; - d.innerHTML=`${rm}${lft}${rgt}<input class="nm" value="${name}" style="color:${tc}"><div class="hx" style="color:${tc}">${hex}</div>`; - if(!locked)d.querySelector('.rm').onclick=(e)=>{e.stopPropagation();PALETTE.splice(i,1);if(selectedIdx===i)selectedIdx=null;renderPalette();buildTable();buildUITable();}; - if(lft)d.querySelector('.mv.l').onclick=(e)=>{e.stopPropagation();moveColor(i,-1);}; - if(rgt)d.querySelector('.mv.r').onclick=(e)=>{e.stopPropagation();moveColor(i,1);}; - d.querySelector('.nm').onchange=(e)=>{PALETTE[i][1]=e.target.value;buildTable();buildUITable();}; - d.onclick=(e)=>{if(e.target.closest('.rm')||e.target.closest('.nm')||e.target.closest('.mv'))return;selectColor(i);}; - d.ondragstart=()=>{dragFrom=i;d.classList.add('drag');}; - d.ondragend=()=>{d.classList.remove('drag');document.querySelectorAll('.pchip.over').forEach(x=>x.classList.remove('over'));}; - d.ondragover=(e)=>{e.preventDefault();if(dragFrom!==null&&dragFrom!==i)d.classList.add('over');}; - d.ondragleave=()=>d.classList.remove('over'); - d.ondrop=(e)=>{e.preventDefault();d.classList.remove('over');if(dragFrom===null||dragFrom===i)return;const m=PALETTE.splice(dragFrom,1)[0];PALETTE.splice(i,0,m);dragFrom=null;selectedIdx=null;renderPalette();buildTable();buildUITable();}; - p.appendChild(d);}); - renderPaletteWarnings(pairs); + const tg=document.createElement('button');tg.className='paltoggle';tg.id='paltoggle'; + tg.textContent=paletteShowFull?'▼':'▶'; + tg.title=paletteShowFull?'showing full palette with spans — click for base colors only':'showing base colors only — click for the full palette'; + tg.onclick=()=>{paletteShowFull=!paletteShowFull;renderPalette();}; + p.appendChild(tg); + // nearest drives the per-chip ΔE tooltip; the too-similar warning box was + // removed (the same info is reachable inline via the contrast field). + const {nearest}=paletteWarnings(PALETTE,DELTAE_MIN,5); + const {ground,columns}=columnsFromPalette(PALETTE,groundPair()); + const usedHexes=usedPaletteHexes(PALETTE,SYNTAX,UIMAP,PKGMAP,groundPair()); + // Per-view-area scopes for the hover "view area > element" usage list. Area + // names match the view dropdown; elements use each tier's display label. + const usageScopes=[ + {area:'color/code assignments',faces:Object.fromEntries(CATS.filter(c=>c[0]!=='bg'&&c[0]!=='p').map(c=>[c[1]||c[0],syntaxFace(c[0])]))}, + {area:'ui faces',faces:Object.fromEntries(UI_FACES.map(u=>[u[1]||u[0],UIMAP[u[0]]]))}, + ...Object.keys(APPS).map(app=>({area:APPS[app].label,faces:Object.fromEntries(APPS[app].faces.map(r=>[r[1]||r[0],PKGMAP[app][r[0]]]))})) + ]; + const used=new Set(); + const idxOf=(hex,name)=>{for(let i=0;i<PALETTE.length;i++)if(!used.has(i)&&PALETTE[i][0]===hex&&PALETTE[i][1]===name){used.add(i);return i;}return -1;}; + const strip=(cls)=>{const s=document.createElement('div');s.className='fstrip'+(cls||'');p.appendChild(s);return s;}; + if(ground.length){ + const gs=strip(' ground');gs.dataset.column='ground'; + const gh=document.createElement('div');gh.className='fhead';gh.textContent='ground';gs.appendChild(gh); + gs.appendChild(groundSpanControl()); + (paletteShowFull?groundColumnMembers():groundColumnMembers().filter(m=>!/^ground[+-]\d+$/i.test(m.name||''))).forEach(m=>{ + const i=idxOf(m.hex,m.name); + if(i>=0)gs.appendChild(paletteChip(i,nearest,usedHexes,usageScopes)); + else{const tc=textOn(m.hex),sw=document.createElement('div');sw.className='pchip';sw.style.background=m.hex;sw.title=(m.name||'ground')+' '+m.hex; + sw.innerHTML=`<input class="nm" value="${m.name||'ground'}" disabled style="color:${tc}"><div class="hx" style="color:${tc}">${m.hex}</div>`;gs.appendChild(sw);} + }); + } + const ordered=sortColumns(columns); + ordered.forEach((f,pos)=>{ + const s=strip('');s.dataset.column=f.column||f.base; + s.appendChild(columnHeader(f,pos,ordered.length)); + s.appendChild(columnCountControl(f)); + (paletteShowFull?f.members:f.members.filter(m=>m.hex.toLowerCase()===f.base.toLowerCase())).forEach(m=>{const i=idxOf(m.hex,m.name);if(i>=0)s.appendChild(paletteChip(i,nearest,usedHexes,usageScopes));}); + if(f.members.every(m=>!usedHexes.has(m.hex.toLowerCase())))s.classList.add('unused-col'); + }); buildUITable();if(document.getElementById('pkgbody'))buildPkgTable(); } +// The per-column count control under a chromatic strip. Its value is the column's +// current per-side reach; setting N regenerates the column as base ±N. +function columnCountControl(f){ + const per=Math.max(0,...rankByLightness(f.members.map(m=>m.hex),f.base).map(m=>Math.abs(m.offset))); + const d=document.createElement('div');d.className='fcount'; + d.innerHTML=`<span title="set the column span: N generated steps on each side of the base — this replaces the column">span ± <input type="number" min="0" max="8" value="${per}"></span>`; + d.querySelector('input').onchange=(e)=>setColumnCount(f.base,Math.max(0,Math.min(8,parseInt(e.target.value,10)||0))); + return d; +} +// Regenerate a column as a symmetric base ±N span, replacing its current members. +// References to a surviving position (matched by signed lightness rank) follow the +// new hex; references to a position removed by lowering N leave their old hex, +// which is no longer in the palette and so renders as "(gone)". +// Replace oldHexes in the palette with a fresh base ±n ramp, repointing surviving +// references and leaving removed ones on their now-gone hex. Returns the removed +// count, or null on a bad base. Shared by the count control and the base edit. +function regenColumnInPlace(oldHexes,baseHex,baseName,n,columnId){ + const r=regenColumn(baseHex,n,{ground:groundPair()}); + if(r.error){notify('cannot regenerate from '+baseHex,true);return null;} + const plan=stepRepointPlan(rankByLightness(oldHexes,baseHex),r.members); + const oldSet=new Set(oldHexes.map(h=>h.toLowerCase())); + let at=PALETTE.length; + for(let i=0;i<PALETTE.length;i++)if(oldSet.has(PALETTE[i][0].toLowerCase())){at=i;break;} + for(let i=PALETTE.length-1;i>=0;i--)if(oldSet.has(PALETTE[i][0].toLowerCase()))PALETTE.splice(i,1); + const col=columnId||columnStem(baseName); + const entries=r.members.map(m=>[m.hex,m.offset===0?baseName:baseName+(m.offset>0?'+'+m.offset:String(m.offset)),col]); + PALETTE.splice(Math.min(at,PALETTE.length),0,...entries); + for(const [o,nw] of plan.map)repointHex(o,nw); + return plan.removed.length; +} +function setColumnCount(baseHex,n){ + const {columns}=columnsFromPalette(PALETTE,groundPair()); + const column=columns.find(f=>f.base.toLowerCase()===baseHex.toLowerCase()); + if(!column)return; + const baseName=(column.members.find(m=>m.hex.toLowerCase()===baseHex.toLowerCase())||{}).name||'color'; + const removed=regenColumnInPlace(column.members.map(m=>m.hex),baseHex,baseName,n,column.column); + if(removed===null)return; + selectedIdx=null;refreshPaletteState(); + notify('regenerated "'+baseName+'" to ±'+n+(removed?(' — '+removed+' removed step(s) show "(gone)" where used'):''),false); +} function notify(msg,err){const m=document.getElementById('palmsg');if(!m)return;m.textContent=msg;m.style.color=err?'#cb6b4d':'#8a9496';m.style.opacity='1';clearTimeout(m._t);m._t=setTimeout(()=>{m.style.opacity='0';},err?4000:2800);} function applyEdit(){if(selectedIdx!==null)updateColor();else addColor();} -function moveColor(i,dir){const j=i+dir;if(j<0||j>=PALETTE.length)return;const t=PALETTE[i];PALETTE[i]=PALETTE[j];PALETTE[j]=t;if(selectedIdx===i)selectedIdx=j;else if(selectedIdx===j)selectedIdx=i;renderPalette();buildTable();buildUITable();} -function selectColor(i){selectedIdx=i;const [hex,name]=PALETTE[i];setHex(hex);document.getElementById('newname').value=name;renderPalette();notify('editing "'+name+'" — change the value, then Enter (or Update selected) to save',false);} +function selectColor(i){selectedIdx=i;GEN_SELECTION=null;const [hex,name]=PALETTE[i];setHex(hex);document.getElementById('newname').value=name;renderPalette();renderGeneratorPreview();notify('editing "'+name+'" — change the value, then Enter (or Update selected) to save',false);} function updateColor(){ if(selectedIdx===null){notify('click a palette color to select it first',true);return;} - const i=selectedIdx,oldHex=PALETTE[i][0]; + const i=selectedIdx,oldHex=PALETTE[i][0],oldRole=groundRoleOfEntry(PALETTE[i],groundPair()); const newHex=curHex(); const newName=(document.getElementById('newname').value.trim())||PALETTE[i][1]; if(PALETTE.some((p,j)=>j!==i&&p[1].toLowerCase()===newName.toLowerCase())){notify('another color is already named "'+newName+'" — names must be unique',true);return;} - PALETTE[i]=[newHex,newName]; - for(const k in MAP){if(MAP[k]===oldHex)MAP[k]=newHex;} - for(const f in UIMAP){if(UIMAP[f].fg===oldHex)UIMAP[f].fg=newHex;if(UIMAP[f].bg===oldHex)UIMAP[f].bg=newHex;} - for(const ap in PKGMAP)for(const fc in PKGMAP[ap]){const o=PKGMAP[ap][fc];if(o.fg===oldHex)o.fg=newHex;if(o.bg===oldHex)o.bg=newHex;} - closePicker();renderPalette();buildTable();buildUITable();renderCode();applyGround();notify('updated "'+newName+'"',false); + const isGroundEdit=oldRole==='bg'||oldRole==='fg'; + // If the edited color is a column base with a ramp, recolor the whole column: regenerate from the new base at the same count. + const columns=columnsFromPalette(PALETTE,groundPair()).columns; + const column=isGroundEdit?null:columns.find(f=>f.base.toLowerCase()===oldHex.toLowerCase()); + const count=column?Math.max(0,...rankByLightness(column.members.map(m=>m.hex),column.base).map(m=>Math.abs(m.offset))):0; + const columnId=isGroundEdit?'ground':(PALETTE[i][2]||columnStem(PALETTE[i][1])); + PALETTE[i]=[newHex,newName,columnId]; + const duplicateOldHex=PALETTE.some((p,j)=>j!==i&&p[0].toLowerCase()===oldHex.toLowerCase()); + if(isGroundEdit)repointHex(oldHex,newHex); + else if(!duplicateOldHex&&oldHex!==MAP['bg']&&oldHex!==MAP['p'])repointHex(oldHex,newHex); + if(column&&count>0){ + const oldHexes=column.members.map(m=>m.hex.toLowerCase()===oldHex.toLowerCase()?newHex:m.hex); + regenColumnInPlace(oldHexes,newHex,newName,count,column.column||columnId); + closePicker();selectedIdx=null;refreshPaletteState();notify('recolored "'+newName+'" column from the new base',false);return; + } + closePicker();refreshPaletteState();notify('updated "'+newName+'"',false); } -function normHex(s){s=s.trim();if(/^[0-9a-fA-F]{6}$/.test(s))s='#'+s;return /^#[0-9a-fA-F]{6}$/.test(s)?s.toLowerCase():null;} -function curHex(){return normHex(document.getElementById('newhexstr').value)||'#888888';} -let pkH=0,pkS=0,pkV=0.5,pickerOn=false; -let pkMode='any'; +const DEFAULT_PICKER_HEX='#67809c'; +let [pkH,pkS,pkV]=rgb2hsv(...hex2rgb(DEFAULT_PICKER_HEX)),pickerOn=false; +function curHex(){return normHex(document.getElementById('newhexstr').value)||DEFAULT_PICKER_HEX;} +let pkMode='any'; // contrast mask: any / aa / aaa (what constraint to mask) +let pkModel='hsv'; // color model for editing: hsv / oklch (orthogonal to pkMode) +const OKLCH_CMAX=0.4; // chroma axis range for the C×L plane (and the C dial); past sRGB at most hues, so the gamut grey shows the reachable region function pkThresh(){return pkMode==='aa'?4.5:pkMode==='aaa'?7:0;} function drawMask(){const cv=document.getElementById('svmask');if(!cv)return;const sv=document.getElementById('sv'),w=cv.width=sv.clientWidth,h=cv.height=sv.clientHeight,ctx=cv.getContext('2d');ctx.clearRect(0,0,w,h);const T=pkThresh();if(!T)return;ctx.fillStyle='rgba(8,7,6,0.66)';const step=4;for(let x=0;x<w;x+=step){const S=x/w;for(let y=0;y<h;y+=step){const V=1-y/h,[r,g,b]=hsv2rgb(pkH,S,V);if(contrast(rgb2hex(r,g,b),MAP['bg'])<T)ctx.fillRect(x,y,step,step);}}} -function paintPicker(){const sv=document.getElementById('sv');if(!sv)return;sv.style.background=`linear-gradient(to top,#000,rgba(0,0,0,0)),linear-gradient(to right,#fff,rgba(255,255,255,0)),hsl(${pkH},100%,50%)`;const w=sv.clientWidth,h=sv.clientHeight,hh=document.getElementById('hue').clientHeight;document.getElementById('svcur').style.left=(pkS*w)+'px';document.getElementById('svcur').style.top=((1-pkV)*h)+'px';document.getElementById('huecur').style.top=((pkH/360)*hh)+'px';drawMask();} +// Phase 4b: the SV box becomes a Chroma×Lightness plane in OKLCH mode. Per cell +// the in-gamut test is forward-only (oklch→oklab→linear-rgb + range check), never +// the binary search — that is reserved for committing a color. The rendered +// bitmap is cached on (hue, dims, mask, bg) so dragging C/L (fixed hue) reuses it. +let _planeCache={key:null,data:null}; +function paintOklchPlane(H){ + const cv=document.getElementById('svmask');if(!cv)return; + const sv=document.getElementById('sv'),w=cv.width=sv.clientWidth,h=cv.height=sv.clientHeight,ctx=cv.getContext('2d'); + const T=pkThresh(),key=Math.round(H)+'|'+w+'|'+h+'|'+pkMode+'|'+MAP['bg']; + if(_planeCache.key===key&&_planeCache.data){ctx.putImageData(_planeCache.data,0,0);return;} + const step=4; + for(let x=0;x<w;x+=step){const C=(x/w)*OKLCH_CMAX; + for(let y=0;y<h;y+=step){const L=1-y/h,cell=planeCell(L,C,H); + if(!cell.inGamut){ctx.fillStyle='#15120f';ctx.fillRect(x,y,step,step);continue;} + ctx.fillStyle=cell.hex;ctx.fillRect(x,y,step,step); + if(T&&contrast(cell.hex,MAP['bg'])<T){ctx.fillStyle='rgba(8,7,6,0.66)';ctx.fillRect(x,y,step,step);}}} + _planeCache={key,data:ctx.getImageData(0,0,w,h)}; +} +// --- safe-lightness guidance (spec Phase 5) ---------------------------------- +let pkSafeFace=''; // covered overlay face the picker's lightness is checked against (or '') +function setSafeFace(f){pkSafeFace=f;if(pickerOn)paintPicker();} +// Shade the band of the C×L plane whose lightness is too light to keep pkSafeFace +// readable over its foreground set, with the L_max ceiling as the band's lower +// edge. One marker computed via lMax at the current chroma, not a per-pixel mask. +function paintSafeBand(C,H){ + const el=document.getElementById('svsafe');if(!el)return; + if(!pkSafeFace||pkModel!=='oklch'){el.style.display='none';return;} + const fs=fgSetForFace(pkSafeFace); + if(fs.reason||!fs.set.length){el.style.display='none';return;} + const sv=document.getElementById('sv'),h=sv.clientHeight,res=lMax(H,C,fs.set,WORST_TARGET); + if(res.status==='all'){el.style.display='none';return;} + el.style.display='block';el.style.top='0px'; + el.style.height=(res.status==='none'?h:Math.max(0,(1-res.L)*h))+'px'; + el.title='safe-lightness ceiling for '+pkSafeFace+' ('+(res.status==='none'?'no safe lightness — a foreground is too dark':'L_max '+res.L.toFixed(3)+(res.status==='clamp'?', chroma-clamped':''))+')'; +} +function paintPicker(){const sv=document.getElementById('sv');if(!sv)return; + const w=sv.clientWidth,h=sv.clientHeight,hh=document.getElementById('hue').clientHeight; + if(pkModel==='oklch'){const [L,C,H]=readOklch();sv.style.background='#15120f';paintOklchPlane(H); + document.getElementById('svcur').style.left=(Math.min(1,C/OKLCH_CMAX)*w)+'px'; + document.getElementById('svcur').style.top=((1-L)*h)+'px'; + document.getElementById('huecur').style.top=((H/360)*hh)+'px';paintSafeBand(C,H);return;} + const sb=document.getElementById('svsafe');if(sb)sb.style.display='none'; + sv.style.background=`linear-gradient(to top,#000,rgba(0,0,0,0)),linear-gradient(to right,#fff,rgba(255,255,255,0)),hsl(${pkH},100%,50%)`; + document.getElementById('svcur').style.left=(pkS*w)+'px';document.getElementById('svcur').style.top=((1-pkV)*h)+'px';document.getElementById('huecur').style.top=((pkH/360)*hh)+'px';drawMask();} function pkReadout(h){const e=document.getElementById('pkhex');if(e)e.textContent=h;const c=document.getElementById('pkcon');if(c){const r=contrast(h,MAP['bg']);c.textContent=r.toFixed(1)+' '+rating(r);c.style.color=ratingColor(r);} const o=document.getElementById('pkoklch');if(o){const lch=oklab2oklch(srgb2oklab(h));o.textContent='OKLCH '+lch.L.toFixed(3)+' '+lch.C.toFixed(3)+' '+Math.round(lch.H)+'\u00b0';} const a=document.getElementById('pkapca');if(a){const lc=apca(h,MAP['bg']);a.textContent='APCA Lc '+lc.toFixed(0);a.title='APCA Lc '+lc.toFixed(1)+' (APCA-W3 0.1.9), text on the ground color. Positive = dark text on a light background, negative = light text on a dark background.';}} -function syncHex(){const v=normHex(document.getElementById('newhexstr').value);if(!v)return;document.getElementById('swatch').style.background=v;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(v));if(pickerOn)paintPicker();pkReadout(v);} -function setHex(h){h=normHex(h)||h;document.getElementById('newhexstr').value=h;document.getElementById('swatch').style.background=h;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(h));if(pickerOn)paintPicker();pkReadout(h);} -function pkSet(){const hex=rgb2hex(...hsv2rgb(pkH,pkS,pkV));document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex;paintPicker();pkReadout(hex);} +function previewPickerHex(hex){if(pickerOn&&selectedIdx!==null)previewSelectedChip(hex);} +function syncHex(){const v=normHex(document.getElementById('newhexstr').value);if(!v)return;document.getElementById('swatch').style.background=v;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(v));if(pickerOn)paintPicker();pkReadout(v);previewPickerHex(v);} +function setHex(h){h=normHex(h)||h;document.getElementById('newhexstr').value=h;document.getElementById('swatch').style.background=h;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(h));if(pickerOn)paintPicker();pkReadout(h);previewPickerHex(h);} +function pkSet(){const hex=rgb2hex(...hsv2rgb(pkH,pkS,pkV));document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex;paintPicker();pkReadout(hex);previewPickerHex(hex);if(pkModel==='oklch')oklchInputsFromHex(hex);} +// --- OKLCH editing model (Phase 4a): L/C/H dials orthogonal to the HSV square --- +function setOklchInputs(L,C,H){ + const put=(id,v)=>{const e=document.getElementById(id);if(e)e.value=v;}; + put('okL',L.toFixed(3));put('okLn',L.toFixed(3));put('okC',C.toFixed(3));put('okCn',C.toFixed(3)); + const h=String(Math.round(H));put('okH',h);put('okHn',h);} +function oklchInputsFromHex(hex){const lch=oklab2oklch(srgb2oklab(normHex(hex)||DEFAULT_PICKER_HEX));setOklchInputs(lch.L,lch.C,lch.H);} +function readOklch(){return [parseFloat(document.getElementById('okL').value)||0,parseFloat(document.getElementById('okC').value)||0,parseFloat(document.getElementById('okH').value)||0];} +function pkClampStatus(on){const s=document.getElementById('pkclamp');if(!s)return;s.classList.toggle('show',on);s.textContent=on?'chroma clamped to sRGB':'';} +function pkOklchSet(){const [L,C,H]=readOklch();const {hex,clamped}=oklch2hex(L,C,H); + document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex; + [pkH,pkS,pkV]=rgb2hsv(...hex2rgb(hex));paintPicker();pkReadout(hex);previewPickerHex(hex); + if(clamped)oklchInputsFromHex(hex); // snap the dials to the reachable color + pkClampStatus(clamped);} +function setPkModel(m){pkModel=m;document.querySelectorAll('.pmodel button').forEach(x=>x.classList.toggle('on',x.dataset.pm===m)); + const oc=document.getElementById('oklchctl');if(oc)oc.classList.toggle('show',m==='oklch'); + if(m==='oklch')oklchInputsFromHex(curHex());else pkClampStatus(false);} function buildPkChips(){const c=document.getElementById('pkchips');if(!c)return;c.innerHTML='';const T=pkThresh();PALETTE.forEach(([hex,name])=>{const s=document.createElement('div');s.className='pc';s.style.background=hex;s.title=name+' '+hex;const ok=!T||contrast(hex,MAP['bg'])>=T;if(!ok){s.style.opacity='0.22';s.title+=' (below '+pkMode.toUpperCase()+')';}s.onclick=()=>{if(ok)setHex(hex);};c.appendChild(s);});} -function openPicker(){pickerOn=true;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(curHex()));buildPkChips();document.getElementById('picker').style.display='block';paintPicker();pkReadout(curHex());setTimeout(()=>document.addEventListener('pointerdown',pkOutside),0);} -function closePicker(){if(!pickerOn)return;pickerOn=false;const p=document.getElementById('picker');if(p)p.style.display='none';document.removeEventListener('pointerdown',pkOutside);} +function openPicker(){pickerOn=true;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(curHex()));buildPkChips();document.getElementById('picker').style.display='block';setPkModel(pkModel);paintPicker();pkReadout(curHex());previewPickerHex(curHex());setTimeout(()=>document.addEventListener('pointerdown',pkOutside),0);} +function closePicker(){if(!pickerOn)return;restoreSelectedChip();pickerOn=false;const p=document.getElementById('picker');if(p)p.style.display='none';document.removeEventListener('pointerdown',pkOutside);} function pkOutside(e){if(!e.target.closest('#picker')&&!e.target.closest('#swatch'))closePicker();} function pkDrag(el,fn){el.addEventListener('pointerdown',e=>{e.preventDefault();fn(e);const mv=ev=>fn(ev),up=()=>{document.removeEventListener('pointermove',mv);document.removeEventListener('pointerup',up);};document.addEventListener('pointermove',mv);document.addEventListener('pointerup',up);});} function initPicker(){const sw=document.getElementById('swatch');if(!sw)return;sw.style.background=curHex();sw.onclick=()=>pickerOn?closePicker():openPicker(); - pkDrag(document.getElementById('sv'),e=>{const r=document.getElementById('sv').getBoundingClientRect();pkS=Math.max(0,Math.min(1,(e.clientX-r.left)/r.width));pkV=1-Math.max(0,Math.min(1,(e.clientY-r.top)/r.height));pkSet();}); - pkDrag(document.getElementById('hue'),e=>{const r=document.getElementById('hue').getBoundingClientRect();pkH=Math.max(0,Math.min(1,(e.clientY-r.top)/r.height))*360;pkSet();}); - document.querySelectorAll('.pmode button').forEach(b=>b.onclick=()=>{pkMode=b.dataset.m;document.querySelectorAll('.pmode button').forEach(x=>x.classList.toggle('on',x===b));drawMask();buildPkChips();});} + const sf=document.getElementById('safefor');if(sf&&sf.options.length<=1)COVERED_FACES.forEach(f=>{const o=document.createElement('option');o.value=f;o.textContent=f;sf.appendChild(o);}); + pkDrag(document.getElementById('sv'),e=>{const r=document.getElementById('sv').getBoundingClientRect();const fx=Math.max(0,Math.min(1,(e.clientX-r.left)/r.width)),fy=Math.max(0,Math.min(1,(e.clientY-r.top)/r.height)); + if(pkModel==='oklch'){setOklchInputs(1-fy,fx*OKLCH_CMAX,readOklch()[2]);pkOklchSet();}else{pkS=fx;pkV=1-fy;pkSet();}}); + pkDrag(document.getElementById('hue'),e=>{const r=document.getElementById('hue').getBoundingClientRect();const fy=Math.max(0,Math.min(1,(e.clientY-r.top)/r.height)); + if(pkModel==='oklch'){const [L,C]=readOklch();setOklchInputs(L,C,fy*360);pkOklchSet();}else{pkH=fy*360;pkSet();}}); + document.querySelectorAll('.pmode button').forEach(b=>b.onclick=()=>{pkMode=b.dataset.m;document.querySelectorAll('.pmode button').forEach(x=>x.classList.toggle('on',x===b));paintPicker();buildPkChips();}); + document.querySelectorAll('.pmodel button').forEach(b=>b.onclick=()=>setPkModel(b.dataset.pm)); + [['okL','okLn',3],['okC','okCn',3],['okH','okHn',0]].forEach(([r,n,dp])=>{ + const re=document.getElementById(r),ne=document.getElementById(n); + if(re)re.addEventListener('input',()=>{if(ne)ne.value=(+re.value).toFixed(dp);pkOklchSet();}); + if(ne)ne.addEventListener('input',()=>{if(re)re.value=ne.value;pkOklchSet();});});} function addColor(){const h=curHex();const name=document.getElementById('newname').value.trim(); if(!name){notify('name the color before adding it',true);return;} if(PALETTE.some(p=>p[1].toLowerCase()===name.toLowerCase())){notify('a color named "'+name+'" already exists — select it and use Update selected to change its value',true);return;} - PALETTE.push([h,name]);document.getElementById('newname').value='';selectedIdx=null;closePicker();renderPalette();buildTable();notify('added "'+name+'"',false);} + PALETTE.push([h,name,columnIdOf([h,name])]);const healed=healGone(name,h);document.getElementById('newname').value='';selectedIdx=null;GEN_SELECTION=null;closePicker(); + refreshPaletteState({code:healed,ground:healed,pkgPreview:healed}); + renderGeneratorPreview(); + notify(healed?('added "'+name+'" and reconnected its face references'):('added "'+name+'"'),false);} function themeName(){return (document.getElementById('themename').value||'theme').trim()||'theme';} -function fileSlug(){return themeName().replace(/[^A-Za-z0-9._-]+/g,'-').replace(/^-+|-+$/g,'')||'theme';} -function exportObj(){const a={};CATS.forEach(c=>a[c[0]]=MAP[c[0]]);const o={name:themeName(),palette:PALETTE,assignments:a,bold:Object.keys(BOLD).filter(k=>BOLD[k]),italic:Object.keys(ITALIC).filter(k=>ITALIC[k]),ui:UIMAP};const pk=packagesForExport(PKGMAP);if(Object.keys(pk).length)o.packages=pk;return o;} +function fileSlug(){return slugify(themeName());} +function exportObj(){normalizePalette();const o={name:themeName(),palette:PALETTE,syntax:SYNTAX,ui:UIMAP};if(LOCKED.size)o.locks=[...LOCKED];const pk=packagesForExport(PKGMAP);if(Object.keys(pk).length)o.packages=pk;return o;} function exportState(){const t=document.getElementById('export');t.value=JSON.stringify(exportObj(),null,1);t.style.display='block';t.focus();t.select();} function toggleJSON(){const t=document.getElementById('export'),b=document.getElementById('jsonbtn');if(t.style.display==='block'){t.style.display='none';b.textContent='show';}else{exportState();b.textContent='hide';}} -function updateTitle(){const n=document.getElementById('themename').value.trim();document.getElementById('pagetitle').textContent=(n||'Untitled')+': theme';const sb=document.getElementById('savebtn');if(sb){sb.style.display=n||fileHandle?'':'none';sb.title=fileHandle?'overwrite the imported/saved file':'choose where to save';}} -let fileHandle=null; -function exportTheme(){const blob=new Blob([JSON.stringify(exportObj(),null,1)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=fileSlug()+'.json';a.click();} -async function saveTheme(){const data=JSON.stringify(exportObj(),null,1); - if(!window.showSaveFilePicker){exportTheme();notify('saved via download (browser has no Save-File support)',false);return;} - try{if(!fileHandle)fileHandle=await window.showSaveFilePicker({suggestedName:fileSlug()+'.json',types:[{description:'theme JSON',accept:{'application/json':['.json']}}]}); - const w=await fileHandle.createWritable();await w.write(data);await w.close();notify('saved "'+themeName()+'"',false);updateTitle(); - }catch(e){if(e&&e.name!=='AbortError')notify('save failed: '+e.message,true);}} -function applyImported(text){const d=JSON.parse(text);if(d.name)document.getElementById('themename').value=d.name;if(d.palette)PALETTE=d.palette;if(d.assignments)Object.assign(MAP,d.assignments); - BOLD={};(d.bold||[]).forEach(k=>BOLD[k]=true);ITALIC={};(d.italic||[]).forEach(k=>ITALIC[k]=true); - if(d.ui)Object.assign(UIMAP,d.ui); +function updateTitle(){const n=document.getElementById('themename').value.trim();document.getElementById('pagetitle').textContent=(n||'Untitled')+': theme';} +// Export the theme JSON. Prefer the File System Access API (showSaveFilePicker) +// so re-exporting overwrites the chosen file in place -- a blob download routes +// through the browser's downloads folder, which uniquifies a re-save as +// "name (1).json" rather than replacing it. Fall back to the blob download where +// the API is absent (mirrors importTheme's showOpenFilePicker/fileinput fallback). +async function exportTheme(){ + const data=JSON.stringify(exportObj(),null,1); + if(!window.showSaveFilePicker){const blob=new Blob([data],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=fileSlug()+'.json';a.click();return;} + try{const h=await window.showSaveFilePicker({suggestedName:fileSlug()+'.json',types:[{description:'theme JSON',accept:{'application/json':['.json']}}]}); + const w=await h.createWritable();await w.write(data);await w.close(); + notify('saved "'+fileSlug()+'.json"',false); + }catch(e){if(e&&e.name!=='AbortError')notify('export failed: '+e.message,true);}} +function applyImported(text){const d=JSON.parse(text);lastGone={};if(d.name)document.getElementById('themename').value=d.name;if(d.palette)PALETTE=d.palette.map(normalizePaletteEntry); + if(!d.syntax)throw new Error('theme JSON is missing syntax; convert older files first'); + SYNTAX={};CATS.forEach(c=>{const k=c[0];SYNTAX[k]=Object.assign(syntaxBlank(k),migrateLegacyFace(d.syntax[k]||{}));});syncAllSyntaxCache(); + LOCKED=new Set(d.locks||[]); + if(d.ui)for(const k in d.ui)UIMAP[k]=Object.assign(uiFaceBlank(),migrateLegacyFace(d.ui[k])); PKGMAP=seedPkgmap();if(d.packages)mergePackagesInto(PKGMAP,d.packages); - renderPalette();buildTable();buildUITable();buildPkgTable();buildPkgPreview();renderCode();applyGround();updateTitle();} -// File-input fallback (no File System Access API): no writable handle, so save still prompts. + refreshPaletteState({pkgPreview:true});updateTitle();} function importFile(ev){const f=ev.target.files[0];if(!f)return;const r=new FileReader(); - r.onload=()=>{try{applyImported(r.result);fileHandle=null;updateTitle();}catch(e){alert('bad theme file: '+e.message);}}; + r.onload=()=>{try{applyImported(r.result);updateTitle();}catch(e){alert('bad theme file: '+e.message);}}; r.readAsText(f);ev.target.value='';} -// Preferred import: keep the file handle so a later save overwrites the same file. async function importTheme(){ if(!window.showOpenFilePicker){const fi=document.getElementById('fileinput');if(fi)fi.click();return;} try{const [h]=await window.showOpenFilePicker({types:[{description:'theme JSON',accept:{'application/json':['.json']}}]}); - const file=await h.getFile();applyImported(await file.text());fileHandle=h;updateTitle(); - notify('imported "'+(themeName()||file.name)+'" — save now overwrites it',false); + const file=await h.getFile();applyImported(await file.text());updateTitle(); + notify('imported "'+(themeName()||file.name)+'"',false); }catch(e){if(e&&e.name!=='AbortError')notify('import failed: '+e.message,true);}} -function applyGround(){document.querySelectorAll('pre').forEach(p=>p.style.background=MAP['bg']);document.querySelectorAll('.ex').forEach(e=>e.style.background=MAP['bg']);} +// The blanket covers only the code panes and syntax example cells. UI-face +// preview cells also carry .ex, but a face with its own bg must keep it, so +// those rows repaint through paintUI (which also re-rates the contrast cell +// against the new ground for faces without their own bg). +function applyGround(){document.querySelectorAll('pre').forEach(p=>p.style.background=MAP['bg']);UI_FACES.forEach(([f])=>{if(document.getElementById('uiprev-'+f))paintUI(f);});} function uf(f){return UIMAP[f]||{};} -function udeco(o){return `font-weight:${o.bold?'bold':'normal'};font-style:${o.italic?'italic':'normal'};text-decoration:${(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none'}`;} +// Map a weight name to a CSS font-weight for the live previews. The named +// weights light/medium/semibold/heavy aren't CSS keywords, so resolve to the +// numeric scale; an unset weight renders normal. +// cssWeight, boxCss, faceDecoration, and faceCss live in app-core.js now. +// udeco keeps its own (untrimmed) decoration form, so it stays here. +function udeco(o){return 'font-weight:'+cssWeight(o.weight)+';font-style:'+(o.slant||'normal')+';text-decoration:'+((o.underline?'underline ':'')+(o.strike?'line-through':'')||'none');} +function syntaxStyle(k){const s=syntaxFace(k),fg=(k==='bg'?MAP['p']:resolveSyntaxFg(k,SYNTAX,MAP['p'])),bg=s.bg||null;return faceCss(s,fg,bg,{boxBg:bg||MAP['bg']});} +// The per-row box control: none / line / raised / pressed plus optional line +// color. get()/set() read and write the face's box object (null = no box). +// Box control: a 2x2 cluster of radio buttons for the four box styles (no box / +// line / pressed / raised), plus a compact color swatch shown only while a box +// style is active. Replaces the old wide select+swatch to reclaim column width. +// Box control: a 2x2 cluster of the four box styles (no box / line / pressed / +// raised) plus a compact color swatch shown while a style is active. Shares the +// cluster/dropdown/paint machinery with mkLineStyleControl; it differs only in +// that its state object carries `width`, so it passes a toState builder. +function mkBoxControl(get,set,opts={}){ + return mkLineStyleControl( + [['','no box',''],['line','line box','□'],['pressed','pressed','▼'],['released','raised','▲']], + get,set, + Object.assign({styled:true,toState:(v,cur)=>({style:v,width:(cur&&cur.width)||1,color:(cur&&cur.color)||null})},opts));} function flashRow(tr){if(!tr)return;tr.scrollIntoView({block:'center',behavior:'smooth'});tr.classList.remove('flash');void tr.offsetWidth;tr.classList.add('flash');} function flashEl(el){if(!el)return;el.scrollIntoView({block:'nearest',inline:'nearest',behavior:'smooth'});el.classList.remove('flashtok');void el.offsetWidth;el.classList.add('flashtok');} // Flash every matching element but scroll only the first into view, so a face @@ -497,12 +2317,13 @@ function flashUi(f){flashRow(document.querySelector(`#uibody tr[data-face="${f}" function flashUiPreview(f){const sp=document.querySelectorAll(`#mockframe [data-face="${f}"]`);if(sp.length){flashEls(sp);return;}const cell=document.getElementById('uiprev-'+f);if(cell)flashEl(cell);} function flashPkg(f){flashRow(document.querySelector(`#pkgbody tr[data-face="${f}"]`));} function flashPkgPreview(f){const sp=document.querySelectorAll(`#pkgpreview [data-face="${f}"]`);if(sp.length){flashEls(sp);return;}const row=document.querySelector(`#pkgbody tr[data-face="${f}"]`);if(row)flashEl(row.querySelector('.cat'));} -function mockSpan(k,t){return `<span data-k="${k}" style="color:${MAP[k]||MAP['p']};font-weight:${BOLD[k]?'bold':'normal'};font-style:${ITALIC[k]?'italic':'normal'}">${esc(t)}</span>`;} +function mockSpan(k,t){return `<span data-k="${k}" style="${syntaxStyle(k)}">${esc(t)}</span>`;} +function uiCss(o,fgv,bgv,opts={}){const fg=fgv===undefined?effFg(o.fg):fgv,bg=bgv===undefined?o.bg:bgv;return faceCss(o,fg,bg,{noBg:opts.noBg,boxBg:bg||MAP['bg']});} function syncMockHeight(){const t=document.getElementById('uitable'),m=document.getElementById('mockframe');if(!t||!m)return;const lb=m.previousElementSibling,lbh=lb?lb.getBoundingClientRect().height+10:30;m.style.height=Math.max(t.getBoundingClientRect().height-lbh,220)+'px';} function buildMockFrame(){ const fr=document.getElementById('mockframe');if(!fr)return; const bg=MAP['bg'],fg=MAP['p']; - const ln=uf('line-number'),lnc=uf('line-number-current-line'),hl=uf('hl-line'),hil=uf('highlight'),reg=uf('region'),isr=uf('isearch'),isf=uf('isearch-fail'),laz=uf('lazy-highlight'),par=uf('show-paren-match'),parx=uf('show-paren-mismatch'),cur=uf('cursor'),ml=uf('mode-line'),mli=uf('mode-line-inactive'),mb=uf('minibuffer-prompt'),frng=uf('fringe'),vb=uf('vertical-border'),lnk=uf('link'),err=uf('error'),wrn=uf('warning'),suc=uf('success'); + const ln=uf('line-number'),lnc=uf('line-number-current-line'),hl=uf('hl-line'),hil=uf('highlight'),reg=uf('region'),isr=uf('isearch'),isf=uf('isearch-fail'),laz=uf('lazy-highlight'),par=uf('show-paren-match'),parx=uf('show-paren-mismatch'),cur=uf('cursor'),ml=uf('mode-line'),mli=uf('mode-line-inactive'),mlh=uf('mode-line-highlight'),mb=uf('minibuffer-prompt'),frng=uf('fringe'),vb=uf('vertical-border'),lnk=uf('link'),err=uf('error'),wrn=uf('warning'),suc=uf('success'); const lines=[ {t:[['cmd',';; '],['cm','init.el - your config']]}, {t:[['punc','('],['kw','require'],['p',' '],['con',"'cl-lib"],['punc',')']]}, @@ -510,78 +2331,165 @@ function buildMockFrame(){ {t:[['punc','('],['kw','defun'],['p',' '],['fnd','cj/greet'],['p',' '],['punc','('],['var','name'],['punc',')']]}, {t:[['p',' '],['punc','('],['fnc','message'],['p',' '],['str','"hi %s"'],['p',' '],['var','name'],['punc','))']],cur:1}, {t:[['p',' '],['punc','('],['kw','setq'],['p',' '],['var','count'],['p',' '],['num','42'],['punc',')']],region:1}, - {plain:' (if (> count 0)',match:1}, - {plain:' (setq total (+ total count))',hl:1}, - {t:[['p',' '],['punc','('],['fnc','process'],['p',' '],['var','items'],['punc',')']]}, - {plain:' (cl-incf count)',lazy:1}, + {t:[['p',' '],['punc','('],['kw','if'],['p',' '],['punc','('],['op','>'],['p',' '],['var','count'],['p',' '],['num','0'],['punc',')']],match:1}, + {t:[['p',' '],['punc','('],['kw','setq'],['p',' '],['var','total'],['p',' '],['punc','('],['op','+'],['p',' '],['var','total'],['p',' '],['var','count'],['punc','))']],hl:1}, + {t:[['p',' '],['punc','('],['fnc','process'],['p',' '],['var','highlights'],['punc',')']],cont:1,high:1}, + {t:[['p',' '],['punc','('],['fnc','cl-incf'],['p',' '],['var','count'],['punc',')']],lazy:1}, {t:[['p',' '],['punc','('],['kw','setq'],['p',' '],['var','done'],['p',' '],['con','t'],['punc',')']],paren:1}, - {plain:' (oops nested))',mismatch:1} + {t:[['p',' '],['punc','('],['fnc','oops'],['p',' '],['var','nested'],['punc','))']],mismatch:1} ]; + // An overlay face (region, highlight, isearch, lazy-highlight) merges the way + // Emacs does: its background applies, and its foreground overrides the tokens + // only when set — otherwise the underlying syntax colors show through. + const overlay=(tokens,face,dface)=>{ + const inner=face.fg + ? `<span style="color:${face.fg}">${tokens.map(([,t])=>esc(t)).join('')}</span>` + : tokens.map(([k,t])=>mockSpan(k,t)).join(''); + return `<span data-face="${dface}" style="${uiCss(face,face.fg||'inherit',face.bg||'transparent')}">${inner}</span>`; + }; + // Emacs box cursor: it sits on the character at point, drawn in the configured + // cursor background, with the glyph in the configured cursor foreground. + // Falls back to a trailing block only if the line has no glyph (point at EOL). + const withCursor=(tokens)=>{ + let out='',placed=false; + const cell=ch=>`<span data-face="cursor" style="${uiCss(cur,cur.fg||bg,cur.bg||fg)}">${esc(ch)}</span>`; + for(const [k,t] of tokens){ + const m=placed?-1:t.search(/\S/); + if(m>=0){ + if(m>0)out+=mockSpan(k,t.slice(0,m)); + out+=cell(t[m]); + if(t.length>m+1)out+=mockSpan(k,t.slice(m+1)); + placed=true; + } else out+=mockSpan(k,t); + } + if(!placed)out+=cell(' '); + return out; + }; let buf=''; lines.forEach((L,i)=>{ const isc=L.cur; - const nFg=isc?(lnc.fg||fg):(ln.fg||fg), nBg=isc?(lnc.bg||'transparent'):(ln.bg||'transparent'); - const rowBg=isc?(hl.bg||'transparent'):'transparent'; + const nFg=isc?(resolveUiAttr('line-number-current-line','fg',UIMAP)||fg):(ln.fg||fg), nBg=isc?(resolveUiAttr('line-number-current-line','bg',UIMAP)||'transparent'):(ln.bg||'transparent'); + const rowFace=isc?hl:null,rowStyle=rowFace?uiCss(rowFace,rowFace.fg||'inherit',rowFace.bg||'transparent'):'background:transparent'; let cd; - if(L.plain){ - if(L.match)cd=`<span data-face="isearch" style="color:${isr.fg||fg};background:${isr.bg||'transparent'}">${esc(L.plain)}</span>`; - else if(L.lazy)cd=`<span data-face="lazy-highlight" style="color:${laz.fg||fg};background:${laz.bg||'transparent'}">${esc(L.plain)}</span>`; - else if(L.hl)cd=`<span data-face="highlight" style="background:${hil.bg||'transparent'};color:${hil.fg||fg}">${esc(L.plain)}</span>`; - else if(L.mismatch)cd=esc(L.plain.slice(0,-1))+`<span data-face="show-paren-mismatch" style="background:${parx.bg||'transparent'};color:${parx.fg||fg};font-weight:bold">${esc(L.plain.slice(-1))}</span>`; - else cd=esc(L.plain); - } else if(L.paren){cd=L.t.map(([k,t],j)=>j===L.t.length-1?`<span data-face="show-paren-match" style="background:${par.bg||'transparent'};color:${par.fg||MAP[k]||fg};font-weight:bold">${esc(t)}</span>`:mockSpan(k,t)).join('');} - else{cd=L.t.map(([k,t])=>mockSpan(k,t)).join('');if(L.region)cd=`<span data-face="region" style="background:${reg.bg||'transparent'}">${cd}</span>`;} - if(isc)cd+=`<span data-face="cursor" style="background:${cur.bg||fg};color:${bg}"> </span>`; + if(isc)cd=withCursor(L.t); + else if(L.region)cd=overlay(L.t,reg,'region'); + else if(L.high)cd=overlay(L.t,hil,'highlight'); + else if(L.match)cd=overlay(L.t,isr,'isearch'); + else if(L.lazy)cd=overlay(L.t,laz,'lazy-highlight'); + else if(L.hl)cd=overlay(L.t,hl,'hl-line'); + else if(L.paren)cd=L.t.map(([k,t],j)=>j===L.t.length-1?`<span data-face="show-paren-match" style="${uiCss(par,par.fg||syntaxFace(k).fg||fg,par.bg||'transparent')}">${esc(t)}</span>`:mockSpan(k,t)).join(''); + else if(L.mismatch)cd=L.t.map(([k,t],j)=>{if(j!==L.t.length-1)return mockSpan(k,t);const head=t.slice(0,-1),bad=t.slice(-1);return (head?mockSpan(k,head):'')+`<span data-face="show-paren-mismatch" style="${uiCss(parx,parx.fg||syntaxFace(k).fg||fg,parx.bg||'transparent')}">${esc(bad)}</span>`;}).join(''); + else cd=L.t.map(([k,t])=>mockSpan(k,t)).join(''); const nFace=isc?'line-number-current-line':'line-number'; - buf+=`<div class="ln" style="background:${rowBg}"><span class="fr" data-face="fringe" style="background:${frng.bg||bg}"></span><span class="num" data-face="${nFace}" style="color:${nFg};background:${nBg}">${i+1}</span><span class="cd">${cd||' '}</span></div>`; + buf+=`<div class="ln" ${rowFace?'data-face="hl-line" ':''}style="${rowStyle}"><span class="fr" data-face="fringe" style="${uiCss(frng,frng.fg||fg,frng.bg||bg)};text-align:center;font-size:10px;overflow:hidden" title="fringe">${L.cont?'↪':''}</span><span class="num" data-face="${nFace}" style="${uiCss(isc?lnc:ln,nFg,nBg)}">${i+1}</span><span class="cd">${cd||' '}</span></div>`; }); - let html=`<div class="mbuf" style="display:flex;background:${bg}"><div style="flex:1;min-width:0">${buf}</div><div data-face="vertical-border" title="vertical-border" style="width:3px;flex:0 0 auto;background:${vb.fg||vb.bg||'#2f343a'}"></div></div>`; - html+=`<div class="bar" data-face="mode-line" style="background:${ml.bg||fg};color:${ml.fg||bg};${udeco(ml)}"> init.el (Emacs Lisp) L5 git:main </div>`; - html+=`<div class="bar" data-face="mode-line-inactive" style="background:${mli.bg||bg};color:${mli.fg||fg};${udeco(mli)}"> *Messages* (Fundamental) </div>`; - html+=`<div class="echo" style="color:${fg}"><span data-face="minibuffer-prompt" style="color:${mb.fg||fg};${udeco(mb)}">I-search:</span> count <span data-face="isearch-fail" style="color:${isf.fg||fg};background:${isf.bg||'transparent'};${udeco(isf)}">zzz [no match]</span></div>`; - html+=`<div class="echo"><span data-face="link" style="color:${lnk.fg||fg};${udeco(lnk)}">https://gnu.org</span> <span data-face="error" style="color:${err.fg||fg};${udeco(err)}">error</span> <span data-face="warning" style="color:${wrn.fg||fg};${udeco(wrn)}">warning</span> <span data-face="success" style="color:${suc.fg||fg};${udeco(suc)}">ok</span></div>`; + let html=`<div class="mbuf" style="background:${bg}"><div class="mbuftext">${buf}</div><div class="vborder" data-face="vertical-border" title="vertical-border" style="background:${vb.fg||vb.bg||'#2f343a'}"></div></div>`; + const mlhStyle=uiCss(mlh,mlh.fg||ml.fg||bg,mlh.bg||ml.bg||fg); + html+=`<div class="bar" data-face="mode-line" style="${uiCss(ml,ml.fg||bg,ml.bg||fg)}"> init.el (Emacs Lisp) L5 <span data-face="mode-line-highlight" title="mode-line-highlight (hover)" style="${mlhStyle}">git:main</span> </div>`; + html+=`<div class="bar" data-face="mode-line-inactive" style="${uiCss(mli,resolveUiAttr('mode-line-inactive','fg',UIMAP)||fg,resolveUiAttr('mode-line-inactive','bg',UIMAP)||bg)}"> *Messages* (Fundamental)</div>`; + html+=`<div class="echo" style="color:${fg}"><span data-face="minibuffer-prompt" style="${uiCss(mb,mb.fg||fg,mb.bg||null)}">I-search:</span> count <span data-face="isearch-fail" style="${uiCss(isf,isf.fg||fg,isf.bg||'transparent')}">zzz [no match]</span></div>`; + html+=`<div class="echo"><span data-face="link" style="${uiCss(lnk,lnk.fg||fg,lnk.bg||null)}">https://gnu.org</span> <span data-face="error" style="${uiCss(err,err.fg||fg,err.bg||null)}">error</span> <span data-face="warning" style="${uiCss(wrn,wrn.fg||fg,wrn.bg||null)}">warning</span> <span data-face="success" style="${uiCss(suc,suc.fg||fg,suc.bg||null)}">ok</span></div>`; fr.innerHTML=html;fr.style.background=bg;fr.style.color=fg; fr.onclick=(e)=>{const u=e.target.closest('[data-face]');if(u){flashUi(u.dataset.face);return;}const k=e.target.closest('[data-k]');if(k)flashAssign(k.dataset.k);}; } -function colorDropdown(value,onpick){ - const sel=document.createElement('select');sel.className='chip'; - const none=document.createElement('option');none.value='';none.textContent='— none —';none.style.background='#161412';none.style.color='#b4b1a2';sel.appendChild(none); - for(const [hex,name] of PALETTE){const o=document.createElement('option');o.value=hex;o.textContent=name+' '+hex;o.style.background=hex;o.style.color=textOn(hex);sel.appendChild(o);} - sel.value=value||''; - function style(){if(sel.value){sel.style.background=sel.value;sel.style.color=textOn(sel.value);}else{sel.style.background='#161412';sel.style.color='#b4b1a2';}} - style(); - sel.onchange=()=>{style();onpick(sel.value||null);}; - return sel; -} -function uiSelect(face,attr){return colorDropdown(UIMAP[face][attr],v=>{UIMAP[face][attr]=v;paintUI(face);buildMockFrame();});} +// All three tiers share one dropdown — the swatch div from mkColorDropdown. The +// native <select> rendered swatch colors unreliably on Linux Chrome, so it is +// gone. '' (the default entry) maps back to null in the stored model. +function uiSelect(face,attr){const cur=UIMAP[face][attr]||''; + return mkColorDropdown(ddList(cur),cur,h=>{UIMAP[face][attr]=h||null;paintUI(face);buildMockFrame();},{compact:true,defaultHex:attr==='fg'?effFg(null):effBg(null)});} const BASE_INHERITS=['fixed-pitch','variable-pitch','default','link','bold','italic','shadow']; -function seedFace(d){return {fg:pname(d.fg),bg:pname(d.bg),bold:!!d.bold,italic:!!d.italic,underline:!!d.underline,strike:!!d.strike,inherit:d.inherit||null,height:d.height||1,source:'default'};} -function curApp(){const s=document.getElementById('appsel');return s&&s.value?s.value:Object.keys(APPS)[0];} -function pkgEffFg(app,face,seen){seen=seen||{};const f=PKGMAP[app]&&PKGMAP[app][face];if(!f||seen[face])return null;seen[face]=1;if(f.fg)return f.fg;if(f.inherit&&PKGMAP[app][f.inherit])return pkgEffFg(app,f.inherit,seen);return null;} -function pkgEffBg(app,face,seen){seen=seen||{};const f=PKGMAP[app]&&PKGMAP[app][face];if(!f||seen[face])return null;seen[face]=1;if(f.bg)return f.bg;if(f.inherit&&PKGMAP[app][f.inherit])return pkgEffBg(app,f.inherit,seen);return null;} -function buildAppSel(){const s=document.getElementById('appsel');if(!s)return;s.innerHTML='';for(const app in APPS){const o=document.createElement('option');o.value=app;o.textContent=APPS[app].label;s.appendChild(o);}s.onchange=pkgChanged;} +function uiFaceBlank(){return {fg:null,bg:null,'distant-fg':null,family:null,weight:null,slant:null,underline:null,strike:null,overline:null,box:null,inverse:false,extend:false,inherit:null,height:null};} +function seedFace(d){return normalizePkgFace({fg:pname(d.fg),bg:pname(d.bg),'distant-fg':pname(d['distant-fg']),family:d.family,weight:d.weight,slant:d.slant,bold:d.bold,italic:d.italic,underline:d.underline,strike:d.strike,overline:d.overline,inherit:d.inherit,height:d.height,box:d.box,inverse:d.inverse,extend:d.extend},'default');} +function curApp(){const s=document.getElementById('viewsel');const v=s&&s.value;return (v&&v[0]!=='@')?v:Object.keys(APPS)[0];} +function pkgEffFg(app,face,seen){return effResolve(PKGMAP,app,face,'fg',seen);} +function pkgEffBg(app,face,seen){return effResolve(PKGMAP,app,face,'bg',seen);} +// One dropdown drives the whole assignment panel: two editor entries (@code, +// @ui) then a non-selectable "package faces" optgroup holding every app, +// alphabetically by label. onViewChange shows exactly one of the three view blocks. +// Lock keys for one view value (@code / @ui / a package app), so the view +// dropdown can flag a view whose every element is locked. +function viewLockKeys(v){ + if(v==='@code')return syntaxLockKeys(); + if(v==='@ui')return uiLockKeys(); + return (APPS[v]?APPS[v].faces:[]).map(f=>'pkg:'+v+':'+f[0]); +} +// Prefix a lock glyph on every view whose elements are all locked; leave the rest +// bare. The base label rides in dataset.label so re-running never stacks glyphs. +function updateViewLockIndicators(){const s=document.getElementById('viewsel');if(!s)return; + for(const o of s.querySelectorAll('option')){const base=o.dataset.label||o.textContent; + o.textContent=(areAllLocked(viewLockKeys(o.value),LOCKED)?'🔒 ':'')+base;}} +function buildViewSel(){const s=document.getElementById('viewsel');if(!s)return;s.innerHTML=''; + const mk=(v,t)=>{const o=document.createElement('option');o.value=v;o.dataset.label=t;o.textContent=t;return o;}; + s.appendChild(mk('@code','color/code assignments')); + s.appendChild(mk('@ui','ui faces')); + const og=document.createElement('optgroup');og.label='package faces'; + for(const app of appViewKeysSorted(APPS))og.appendChild(mk(app,APPS[app].label)); + s.appendChild(og);updateViewLockIndicators();} +// The ‹ › buttons flanking the dropdown step the selection by DIR and re-render +// the view (faces table + preview), so you can walk the list without reopening it. +function stepView(dir){ + const s=document.getElementById('viewsel');if(!s)return; + const i=stepViewIndex(s.selectedIndex,s.options.length,dir); + if(i!==s.selectedIndex){s.selectedIndex=i;onViewChange();} +} +// The ‹ › buttons flanking the language dropdown step the selection by DIR and +// re-render the code sample + package preview, mirroring the view-dropdown nav. +function stepLang(dir){ + const s=document.getElementById('langsel');if(!s)return; + const i=stepViewIndex(s.selectedIndex,s.options.length,dir); + if(i!==s.selectedIndex){s.selectedIndex=i;renderCode();buildPkgPreview();} +} +function onViewChange(){const s=document.getElementById('viewsel');const v=(s&&s.value)||'@code'; + const show=(id,on)=>{const e=document.getElementById(id);if(e)e.style.display=on?'':'none';}; + show('view-code',v==='@code');show('view-ui',v==='@ui');show('view-pkg',v[0]!=='@'); + if(v==='@code')renderCode(); + else if(v==='@ui'){buildUITable();buildMockFrame();syncMockHeight();} + else pkgChanged();} function pkgChanged(){buildPkgTable();buildPkgPreview();syncPkgHeight();} function buildPkgTable(){ const app=curApp(),tb=document.getElementById('pkgbody');if(!tb)return;tb.innerHTML=''; const flt=(document.getElementById('pkgfilter').value||'').trim().toLowerCase(); const inh=[''].concat(BASE_INHERITS).concat(APPS[app].faces.map(r=>r[0])); - for(const [face,label,def] of APPS[app].faces){ + for(const row of APPS[app].faces){ + const face=row[0],label=row[1]; if(flt&&!(face.toLowerCase().includes(flt)||label.toLowerCase().includes(flt)))continue; const f=PKGMAP[app][face],tr=document.createElement('tr');tr.dataset.face=face; - const c0=document.createElement('td');c0.className='cat';c0.textContent=label;c0.title=face;c0.style.cursor='pointer';c0.onclick=()=>flashPkgPreview(face); - const cf=document.createElement('td');cf.appendChild(colorDropdown(f.fg,v=>{f.fg=v;f.source='user';pkgChanged();})); - const cb=document.createElement('td');cb.appendChild(colorDropdown(f.bg,v=>{f.bg=v;f.source='user';pkgChanged();})); - const cw=document.createElement('td');[['B','bold'],['I','italic'],['U','underline'],['S','strike']].forEach(([ch,at])=>{const b=document.createElement('button');b.className='sbtn'+(f[at]?' on':'');b.textContent='a';b.style.fontWeight=at==='bold'?'bold':'normal';b.style.fontStyle=at==='italic'?'italic':'normal';b.style.textDecoration=at==='underline'?'underline':at==='strike'?'line-through':'none';b.title=at;b.onclick=()=>{f[at]=!f[at];f.source='user';pkgChanged();};cw.appendChild(b);}); - const ci=document.createElement('td');const isel=document.createElement('select');isel.className='chip';isel.style.cssText='width:150px;font:10pt monospace';inh.forEach(o=>{const op=document.createElement('option');op.value=o;op.textContent=o||'— none —';isel.appendChild(op);});isel.value=f.inherit||'';isel.onchange=()=>{f.inherit=isel.value||null;f.source='user';pkgChanged();};ci.appendChild(isel); - const ch=document.createElement('td');const hin=document.createElement('input');hin.type='number';hin.min='0.8';hin.max='2.5';hin.step='0.05';hin.value=f.height||1;hin.className='hstep';hin.onchange=()=>{f.height=parseFloat(hin.value)||1;f.source='user';pkgChanged();};ch.appendChild(hin); - const cc=document.createElement('td');cc.style.fontSize='10pt';cc.style.whiteSpace='nowrap';const efg=pkgEffFg(app,face)||MAP['p'],ebg=pkgEffBg(app,face)||MAP['bg'],r=contrast(efg,ebg);cc.innerHTML=`<span style="color:${ratingColor(r)}">${r.toFixed(1)} ${rating(r)}</span>`; - const cr=document.createElement('td');const rb=document.createElement('button');rb.className='sbtn';rb.textContent='↺';rb.title='reset to default';rb.onclick=()=>{PKGMAP[app][face]=seedFace(def);pkgChanged();};cr.appendChild(rb); - tr.append(c0,cf,cb,cw,ci,ch,cc,cr);tb.appendChild(tr); + const def=normalizePkgFace(row[2]||{},'default',PALETTE); + const nd=faceBoxNonDefaults( + {fg:nameToHex(f.fg,PALETTE),bg:nameToHex(f.bg,PALETTE),weight:f.weight,slant:f.slant,underline:f.underline,strike:f.strike,inherit:f.inherit,height:f.height,box:f.box}, + {fg:nameToHex(def.fg,PALETTE),bg:nameToHex(def.bg,PALETTE),weight:def.weight,slant:def.slant,underline:def.underline,strike:def.strike,inherit:def.inherit,height:def.height,box:def.box}); + const exp=mkExpander(f,tableColCount('pkgtable'),()=>{f.source='user';pkgChanged();},{expandKey:face,showInheritHeight:true,inheritOptions:inh,defaultHex:effFg(pkgEffFg(app,face)),ndCheck:()=>overflowNonDefault(f,def,true)}); + exp.detail.dataset.detailFor=face; + const c0=document.createElement('td');c0.className='cat';c0.title=composeHoverTitle(FACE_DOCS[face],face);c0.appendChild(exp.btn); + const c0lbl=document.createElement('span');c0lbl.textContent=' '+label;c0lbl.style.cursor='pointer';c0lbl.onclick=()=>flashPkgPreview(face);c0.appendChild(c0lbl); + const fgd=mkColorDropdown(ddList(f.fg||''),f.fg||'',h=>{f.fg=h||null;f.source='user';pkgChanged();},{compact:true,defaultHex:effFg(pkgEffFg(app,face))}), + bgd=mkColorDropdown(ddList(f.bg||''),f.bg||'',h=>{f.bg=h||null;f.source='user';pkgChanged();},{compact:true,defaultHex:effBg(pkgEffBg(app,face))}); + const cf=document.createElement('td');cf.appendChild(fgd); + const cb=document.createElement('td');cb.appendChild(bgd); + const cw=document.createElement('td'); + const pkCtls=mkStyleControls(f,()=>{f.source='user';pkgChanged();},{defaultHex:effFg(pkgEffFg(app,face))}); + const pkCluster=document.createElement('div');pkCluster.className='stylecluster';pkCtls.forEach(c=>pkCluster.appendChild(c));cw.appendChild(pkCluster); + const cc=document.createElement('td');cc.style.fontSize='10pt';cc.style.whiteSpace='nowrap';const efg=effFg(pkgEffFg(app,face)),ebg=effBg(pkgEffBg(app,face)),r=contrast(efg,ebg);cc.innerHTML=crHtml(r); + const cx=document.createElement('td');const boxCtl=mkBoxControl(()=>f.box,b=>{f.box=b;f.source='user';pkgChanged();},{compact:true});cx.appendChild(boxCtl); + const cL=mkLockCell('pkg:'+app+':'+face,[fgd,bgd,...pkCtls,boxCtl,...exp.locks]); + if(nd.fg)cf.classList.add('nd');if(nd.bg)cb.classList.add('nd');if(nd.style)cw.classList.add('nd'); + if(nd.box)cx.classList.add('nd'); + tr.append(cL,c0,cf,cb,cw,cx,cc);tb.appendChild(tr);tb.appendChild(exp.detail); } applyTableSort('pkgbody'); + updateLockToggle('pkg');syncExpandAllBtns(); } -function ofs(app,face){const f=PKGMAP[app][face]||{},fg=pkgEffFg(app,face)||MAP['p'],bg=pkgEffBg(app,face);const dec=(f.underline?'underline ':'')+(f.strike?'line-through':'');return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${f.bold?'bold':'normal'};font-style:${f.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'};font-size:${(f.height||1)}em`;} +// The per-package preview renderers live in previews.js, spliced here so the +// PACKAGE_PREVIEWS registry below can reference them. +// previews.js -- the bespoke per-package preview renderers, extracted from +// app.js. Pure preview HTML builders (ofs/os/previewLines + renderXxxPreview); +// they reference shared globals (PKGMAP, MAP, faceCss, effFg, ...) and are +// inlined into the page's single script element via the PREVIEWS_J token in app.js. +function ofs(app,face){const f=PKGMAP[app][face]||{},fg=effFg(pkgEffFg(app,face)),bg=pkgEffBg(app,face);return faceCss(f,fg,bg,{fontSize:(f.height||1),boxBg:bg||MAP['bg']});} function os(app,face,txt){return `<span data-face="${face}" style="${ofs(app,face)}">${txt}</span>`;} +// Shared wrapper for the line-based package previews: a monospace pre block. +// Each renderer builds its own L array of os(...) lines and returns previewLines(L). +function previewLines(L){return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} function renderOrgPreview(){const a='org-mode',L=[]; L.push(os(a,'org-document-info-keyword','#+TITLE:')+' '+os(a,'org-document-title','Project Notes')); L.push(os(a,'org-document-info-keyword','#+AUTHOR:')+' '+os(a,'org-document-info','Craig Jennings')); @@ -597,7 +2505,7 @@ function renderOrgPreview(){const a='org-mode',L=[]; L.push(' '+os(a,'org-checkbox','[X]')+' done item '+os(a,'org-checkbox-statistics-done','[2/2]')); L.push(' '+os(a,'org-checkbox','[ ]')+' open item '+os(a,'org-checkbox-statistics-todo','[0/3]')+' '+os(a,'org-warning','(!)')); L.push(os(a,'org-level-2','** ')+os(a,'org-done','DONE')+os(a,'org-headline-done',' Ship the tool')); - L.push(os(a,'org-level-3','*** ')+os(a,'org-headline-todo','Heading three')); + L.push(os(a,'org-level-3','*** ')+os(a,'org-todo','TODO')+os(a,'org-headline-todo',' Heading three')); L.push(os(a,'org-level-4','**** four')+' / '+os(a,'org-level-5','***** five')+' / '+os(a,'org-level-6','****** six')+' / '+os(a,'org-level-7','******* seven')+' / '+os(a,'org-level-8','******** eight')); L.push(' Inline '+os(a,'org-code','=code=')+', '+os(a,'org-verbatim','~verbatim~')+', '+os(a,'org-inline-src-block','src_py{1+1}')+','); L.push(' a '+os(a,'org-link','[[https://gnu.org][link]]')+', a '+os(a,'org-target','<<target>>')+', a '+os(a,'org-macro','{{{macro}}}')+','); @@ -625,7 +2533,7 @@ function renderOrgPreview(){const a='org-mode',L=[]; L.push(' '+os(a,'org-agenda-structure-secondary','secondary')+' '+os(a,'org-agenda-structure-filter','filter')+' '+os(a,'org-agenda-restriction-lock','lock')+' '+os(a,'org-agenda-column-dateline','col-date')); L.push(' Filters: '+os(a,'org-agenda-filter-category','cat')+' '+os(a,'org-agenda-filter-tags','tags')+' '+os(a,'org-agenda-filter-effort','effort')+' '+os(a,'org-agenda-filter-regexp','re')); L.push(' '+os(a,'org-mode-line-clock','[0:45]')+' / '+os(a,'org-mode-line-clock-overrun','[OVER]')+' '+os(a,'org-dispatcher-highlight','[d]ispatch')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`; + return previewLines(L); } function renderMagitPreview(){const a='magit',L=[]; L.push(os(a,'magit-header-line',' Magit: dotemacs ')+' '+os(a,'magit-header-line-key','g')+os(a,'magit-header-line-log-select',' refresh')); @@ -676,7 +2584,7 @@ function renderMagitPreview(){const a='magit',L=[]; L.push(' '+os(a,'magit-blame-hash','b5b1869f')+' '+os(a,'magit-blame-name','Craig')+' '+os(a,'magit-blame-date','2026-06-08')+' '+os(a,'magit-blame-summary','enlarge picker')+' '+os(a,'magit-blame-highlight','hl')+' '+os(a,'magit-blame-dimmed','dim')); L.push(os(a,'magit-section-heading','Signatures')+os(a,'magit-left-margin',' ')); L.push(' '+os(a,'magit-signature-good','good')+' '+os(a,'magit-signature-bad','bad')+' '+os(a,'magit-signature-untrusted','untrusted')+' '+os(a,'magit-signature-expired','expired')+' '+os(a,'magit-signature-expired-key','expired-key')+' '+os(a,'magit-signature-revoked','revoked')+' '+os(a,'magit-signature-error','error')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderElfeedPreview(){const a='elfeed',L=[]; L.push(os(a,'elfeed-search-filter-face','@6-months-ago +unread')+' '+os(a,'elfeed-search-unread-count-face','3/120')+' '+os(a,'elfeed-search-last-update-face','updated 02:24')); L.push(''); @@ -688,7 +2596,7 @@ function renderElfeedPreview(){const a='elfeed',L=[]; L.push(os(a,'elfeed-log-date-face','02:24:02')+' '+os(a,'elfeed-log-warn-level-face','WARN ')+' slow feed: example.com'); L.push(os(a,'elfeed-log-date-face','02:24:03')+' '+os(a,'elfeed-log-error-level-face','ERROR')+' failed: bad.example'); L.push(os(a,'elfeed-log-date-face','02:24:04')+' '+os(a,'elfeed-log-debug-level-face','DEBUG')+' parsed 340 entries'); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderGhostelPreview(){const a='ghostel',L=[]; L.push(os(a,'ghostel-default','craig@host')+' '+os(a,'ghostel-color-green','~/code')+' $ ls'+os(a,'ghostel-fake-cursor',' ')+os(a,'ghostel-fake-cursor-box','[ ]')); L.push(''); @@ -696,42 +2604,122 @@ function renderGhostelPreview(){const a='ghostel',L=[]; L.push(os(a,'ghostel-default','bright:')+' '+os(a,'ghostel-color-bright-black','black')+' '+os(a,'ghostel-color-bright-red','red')+' '+os(a,'ghostel-color-bright-green','green')+' '+os(a,'ghostel-color-bright-yellow','yellow')+' '+os(a,'ghostel-color-bright-blue','blue')+' '+os(a,'ghostel-color-bright-magenta','magenta')+' '+os(a,'ghostel-color-bright-cyan','cyan')+' '+os(a,'ghostel-color-bright-white','white')); L.push(''); L.push(os(a,'ghostel-default','default terminal output, 256-color text and a blinking ')+os(a,'ghostel-fake-cursor','cursor')+'.'); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderDashboardPreview(){const a='dashboard',L=[]; - L.push(os(a,'dashboard-text-banner',' ___ _ __ ___ __ _ ___ ___')); - L.push(os(a,'dashboard-banner-logo-title',' Welcome back, Craig')); + L.push(os(a,'dashboard-text-banner',' [ dashboard banner image ]')); + L.push(os(a,'dashboard-banner-logo-title','Emacs: The Editor That Saves Your Soul')); + L.push(''); + L.push(os(a,'dashboard-navigator',' Code Files Terminal Agenda')); + L.push(os(a,'dashboard-navigator',' Feeds Books Flashcards Music')); + L.push(os(a,'dashboard-navigator',' Email IRC Telegram')); + L.push(os(a,'dashboard-navigator',' Slack Linear')); + L.push(''); + L.push(''); + L.push(os(a,'dashboard-heading','Projects:')); + L.push(' ~/'); + L.push(' ~/.emacs.d/'); + L.push(' ~/projects/work/'); + L.push(' ~/org/roam/'); + L.push(' ~/projects/home/'); L.push(''); - L.push(os(a,'dashboard-heading','Recent Files')); - L.push(' '+os(a,'dashboard-items-face','init.el')); - L.push(' '+os(a,'dashboard-items-face','notes.org')); L.push(os(a,'dashboard-heading','Bookmarks')); - L.push(' '+os(a,'dashboard-no-items-face','-- no items --')); + L.push(' Cesar Aira, The Little Buddhist Monk & the Proof'); + L.push(' Edward Abbey, The Fool’s Progress: An Honest Novel'); + L.push(' Agatha Christie, The A.B.C. Murders'); L.push(''); - L.push(os(a,'dashboard-navigator','[ Projects ] [ Recent ] [ Agenda ]')); - L.push(os(a,'dashboard-footer-icon-face','*')+' '+os(a,'dashboard-footer-face','Happy hacking, Craig!')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + L.push(os(a,'dashboard-heading','Recent Files:')); + L.push(' theme-theme.el'); + L.push(' todo.org'); + L.push(' theme-studio-palette-generator-spec.org'); + return previewLines(L);} function renderMu4ePreview(){const a='mu4e',L=[]; - L.push(os(a,'mu4e-title-face','mu4e')+' '+os(a,'mu4e-context-face','[Personal]')+' '+os(a,'mu4e-ok-face','online')+' '+os(a,'mu4e-warning-face','2 retry')+' '+os(a,'mu4e-modeline-face','12/340')); + const pad=(s,n)=>{s=String(s);return s.length>=n?s.slice(0,n):s+' '.repeat(n-s.length);}; + // One header line: the flags column in mu4e-header-marks-face, the rest of the + // row in the message's state face (unread, replied, flagged, ...). + const row=(flags,date,from,subj,face)=> + os(a,'mu4e-header-marks-face',pad(flags,4))+os(a,face,pad(date,12)+pad(from,17)+subj); + // status / context bar + L.push(os(a,'mu4e-title-face','mu4e')+' '+os(a,'mu4e-context-face','[Personal]')+' '+os(a,'mu4e-ok-face','online')+' '+os(a,'mu4e-warning-face','2 retrying')+' '+os(a,'mu4e-modeline-face','[12/340]')); L.push(''); - L.push(os(a,'mu4e-header-title-face','Date Flags From Subject')); - L.push(os(a,'mu4e-header-value-face','2026-06-08')+' '+os(a,'mu4e-header-marks-face','N')+' '+os(a,'mu4e-unread-face','Alice')+' '+os(a,'mu4e-unread-face','Unread message')); - L.push(os(a,'mu4e-header-value-face','2026-06-07')+' '+os(a,'mu4e-header-marks-face','R')+' '+os(a,'mu4e-header-face','Bob')+' '+os(a,'mu4e-replied-face','Replied thread')); - L.push(os(a,'mu4e-header-value-face','2026-06-06')+' '+os(a,'mu4e-header-marks-face','F')+' '+os(a,'mu4e-header-face','Carol')+' '+os(a,'mu4e-forwarded-face','Forwarded note')); - L.push(os(a,'mu4e-header-value-face','2026-06-05')+' '+os(a,'mu4e-header-marks-face','D')+' '+os(a,'mu4e-draft-face','(draft)')+' '+os(a,'mu4e-draft-face','Draft in progress')); - L.push(os(a,'mu4e-header-value-face','2026-06-04')+' '+os(a,'mu4e-header-marks-face','T')+' '+os(a,'mu4e-trashed-face','Dan')+' '+os(a,'mu4e-moved-face','Trashed and moved')); - L.push(os(a,'mu4e-header-highlight-face','2026-06-03 ! Eve Flagged ')+os(a,'mu4e-flagged-face','important')+os(a,'mu4e-related-face',' (related)')); + // column header + the message list, one row per state + L.push(os(a,'mu4e-header-title-face',pad('Flags',4)+pad('Date',12)+pad('From',17)+'Subject')); + L.push(row('N','2026-06-14','Christine Park','Re: quarterly numbers','mu4e-unread-face')); + L.push(row('','2026-06-13','Bob Lin','Lunch on Friday?','mu4e-header-face')); + // current line at point: the whole row gets the highlight background + L.push(os(a,'mu4e-header-highlight-face',row('R','2026-06-13','dev-list','merged the parser fix','mu4e-replied-face'))); + L.push(row('F','2026-06-12','Carol Reyes','Fwd: the signed contract','mu4e-forwarded-face')); + L.push(row('D','2026-06-11','(draft)','Notes to finish later','mu4e-draft-face')); + L.push(row('T','2026-06-10','spam@nowhere','You have won a prize','mu4e-trashed-face')); + L.push(row('','2026-06-09','Erin (cc)','thread you follow','mu4e-related-face')); + L.push(row('!','2026-06-08','Frank Diaz','budget needs sign-off','mu4e-flagged-face')); L.push(''); - L.push(os(a,'mu4e-header-key-face','From:')+' '+os(a,'mu4e-contact-face','Alice <alice@example.com>')); - L.push(os(a,'mu4e-header-key-face','To:')+' '+os(a,'mu4e-special-header-value-face','craig, list@gnu.org')); - L.push(os(a,'mu4e-header-key-face','Attach:')+' '+os(a,'mu4e-attach-number-face','[1]')+' report.pdf link '+os(a,'mu4e-url-number-face','[2]')+' '+os(a,'mu4e-link-face','https://gnu.org')); + // a message view below the list + L.push(os(a,'mu4e-header-key-face','From:')+' '+os(a,'mu4e-contact-face','Christine Park <christine@example.com>')); + L.push(os(a,'mu4e-header-key-face','To:')+' '+os(a,'mu4e-special-header-value-face','craig, dev-list@gnu.org')); + L.push(os(a,'mu4e-header-key-face','Subject:')+' '+os(a,'mu4e-header-value-face','Re: quarterly numbers')); L.push(''); - L.push(' body with a '+os(a,'mu4e-highlight-face','search hit')+' and '+os(a,'mu4e-region-code','code region')+'.'); - L.push(' '+os(a,'mu4e-cited-1-face','> level 1')+' '+os(a,'mu4e-cited-2-face','>> 2')+' '+os(a,'mu4e-cited-3-face','>>> 3')+' '+os(a,'mu4e-cited-4-face','> 4')+' '+os(a,'mu4e-cited-5-face','> 5')+' '+os(a,'mu4e-cited-6-face','> 6')+' '+os(a,'mu4e-cited-7-face','> 7')); - L.push(' '+os(a,'mu4e-system-face','*** system message ***')+' '+os(a,'mu4e-footer-face','-- sent with mu4e')); + L.push(' Body with a '+os(a,'mu4e-highlight-face','search hit')+', a link '+os(a,'mu4e-url-number-face','[1]')+' '+os(a,'mu4e-link-face','https://example.com')+', and a '+os(a,'mu4e-region-code','code region')+'.'); + L.push(' '+os(a,'mu4e-system-face','*** mu: 340 messages indexed ***')); + L.push(' '+os(a,'mu4e-footer-face','-- Sent with mu4e')); L.push(''); - L.push(os(a,'mu4e-compose-header-face','Subject:')+' new mail'); L.push(os(a,'mu4e-compose-separator-face','--text follows this line--')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} +function renderGnusPreview(){const a='gnus',L=[]; + // mu4e renders the open message with gnus, so this is the article view: + // a header block, a body with inline emphasis and a button, then a quoted + // reply chain (one cite face per nesting level) and the signature. + L.push(os(a,'gnus-header-name','From: ')+os(a,'gnus-header-from','Christine Park <christine@example.com>')); + L.push(os(a,'gnus-header-name','To: ')+os(a,'gnus-header-content','craig@cjennings.net')); + L.push(os(a,'gnus-header-name','Newsgroups: ')+os(a,'gnus-header-newsgroups','gnu.emacs.help')); + L.push(os(a,'gnus-header-name','Subject: ')+os(a,'gnus-header-subject','Re: quarterly numbers')); + L.push(os(a,'gnus-header-name','Date: ')+os(a,'gnus-header-content','Sat, 14 Jun 2026 09:12:04 -0500')); + L.push(''); + L.push('Thanks for the draft. The '+os(a,'gnus-emphasis-bold','revenue line')+' is '+os(a,'gnus-emphasis-italic','close')+', but the '+os(a,'gnus-emphasis-underline','footnote')+' is '+os(a,'gnus-emphasis-strikethru','wrong')+' '+os(a,'gnus-emphasis-highlight-words','FIXME')+'.'); + L.push('See the worksheet: '+os(a,'gnus-button','[https://example.com/q2]')); + L.push(''); + L.push(os(a,'gnus-cite-attribution','On Fri, Bob Lin wrote:')); + L.push(os(a,'gnus-cite-1','> The Q2 totals are ready for review.')); + L.push(os(a,'gnus-cite-2','>> Did the Segpay refund post yet?')); + L.push(os(a,'gnus-cite-3','>>> Yes, it cleared on the 5th.')); + L.push(os(a,'gnus-cite-4','>>>> Good, then we are square.')); + L.push(os(a,'gnus-cite-5','>>>>> earlier reply, level 5')); + L.push(os(a,'gnus-cite-6','>>>>>> level 6')); + L.push(os(a,'gnus-cite-7','>>>>>>> level 7')); + L.push(os(a,'gnus-cite-8','>>>>>>>> level 8')); + L.push(os(a,'gnus-cite-9','>>>>>>>>> level 9')); + L.push(os(a,'gnus-cite-10','>>>>>>>>>> level 10')); + L.push(os(a,'gnus-cite-11','>>>>>>>>>>> level 11')); + L.push(''); + L.push(os(a,'gnus-signature','-- ')); + L.push(os(a,'gnus-signature','Christine Park, Finance')); + return previewLines(L);} +function renderOrgFacesPreview(){const a='org-faces',L=[]; + L.push('Agenda header row -- one face per keyword and priority (this config, not built-in org):'); + L.push(''); + L.push(os(a,'org-faces-todo','TODO')+' Draft the spec '+os(a,'org-faces-priority-a','[#A]')); + L.push(os(a,'org-faces-project','PROJECT')+' Theme studio overhaul '+os(a,'org-faces-priority-b','[#B]')); + L.push(os(a,'org-faces-doing','DOING')+' Wire the faces '+os(a,'org-faces-priority-c','[#C]')); + L.push(os(a,'org-faces-waiting','WAITING')+' On review '+os(a,'org-faces-priority-d','[#D]')); + L.push(os(a,'org-faces-verify','VERIFY')+' Confirm the round-trip'); + L.push(os(a,'org-faces-stalled','STALLED')+' Blocked on upstream'); + L.push(os(a,'org-faces-delegated','DELEGATED')+' Handed to Kostya'); + L.push(os(a,'org-faces-failed','FAILED')+' Could not reproduce'); + L.push(os(a,'org-faces-done','DONE')+' Shipped the module'); + L.push(os(a,'org-faces-cancelled','CANCELLED')+' Dropped the approach'); + L.push(''); + L.push('Unfocused (auto-dim) -- the -dim variants auto-dim remaps onto in non-selected windows:'); + L.push(''); + L.push(os(a,'org-faces-todo-dim','TODO')+' Draft the spec '+os(a,'org-faces-priority-a-dim','[#A]')); + L.push(os(a,'org-faces-project-dim','PROJECT')+' Theme studio overhaul '+os(a,'org-faces-priority-b-dim','[#B]')); + L.push(os(a,'org-faces-doing-dim','DOING')+' Wire the faces '+os(a,'org-faces-priority-c-dim','[#C]')); + L.push(os(a,'org-faces-waiting-dim','WAITING')+' On review '+os(a,'org-faces-priority-d-dim','[#D]')); + L.push(os(a,'org-faces-verify-dim','VERIFY')+' Confirm the round-trip'); + L.push(os(a,'org-faces-stalled-dim','STALLED')+' Blocked on upstream'); + L.push(os(a,'org-faces-delegated-dim','DELEGATED')+' Handed to Kostya'); + L.push(os(a,'org-faces-failed-dim','FAILED')+' Could not reproduce'); + L.push(os(a,'org-faces-done-dim','DONE')+' Shipped the module'); + L.push(os(a,'org-faces-cancelled-dim','CANCELLED')+' Dropped the approach'); + return previewLines(L);} function renderLspPreview(){const a='lsp-mode',L=[]; L.push(os(a,'lsp-signature-face','process(')+os(a,'lsp-signature-highlight-function-argument','items: list')+os(a,'lsp-signature-face',') -> None')); L.push(os(a,'lsp-signature-posframe',' docs: iterate over items and process each one ')); @@ -744,22 +2732,22 @@ function renderLspPreview(){const a='lsp-mode',L=[]; L.push(' getName() '+os(a,'lsp-details-face','str the cached getter')); L.push(''); L.push(os(a,'lsp-installation-buffer-face','Installing pyright...')+' '+os(a,'lsp-installation-finished-buffer-face','done.')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderGitGutterPreview(){const a='git-gutter',L=[]; L.push(os(a,'git-gutter:added','+')+os(a,'git-gutter:separator','|')+' added line of code'); L.push(os(a,'git-gutter:modified','~')+os(a,'git-gutter:separator','|')+' modified line of code'); L.push(os(a,'git-gutter:deleted','_')+os(a,'git-gutter:separator','|')+' (deleted lines marker)'); L.push(os(a,'git-gutter:unchanged',' ')+os(a,'git-gutter:separator','|')+' '+os(a,'git-gutter:unchanged','unchanged line of code')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderFlycheckPreview(){const a='flycheck',L=[]; L.push(os(a,'flycheck-fringe-error','E')+os(a,'flycheck-fringe-warning','W')+os(a,'flycheck-fringe-info','I')+' x = '+os(a,'flycheck-error','undefined_name')+'('+os(a,'flycheck-warning','unused_arg')+') '+os(a,'flycheck-info','# note')); - L.push(' '+os(a,'flycheck-delimited-error','[')+os(a,'flycheck-error-delimiter','err')+os(a,'flycheck-delimited-error',']')); + L.push(' '+os(a,'flycheck-error-delimiter','[')+os(a,'flycheck-delimited-error','err')+os(a,'flycheck-error-delimiter',']')); L.push(''); L.push(os(a,'flycheck-error-list-checker-name','pyright')+' '+os(a,'flycheck-verify-select-checker','(selected checker)')); L.push(os(a,'flycheck-error-list-filename','main.py')+':'+os(a,'flycheck-error-list-line-number','12')+':'+os(a,'flycheck-error-list-column-number','4')+' '+os(a,'flycheck-error-list-error','error')+' '+os(a,'flycheck-error-list-error-message','undefined name x')+' '+os(a,'flycheck-error-list-id','[E0602]')); L.push(os(a,'flycheck-error-list-filename','main.py')+':'+os(a,'flycheck-error-list-line-number','18')+':'+os(a,'flycheck-error-list-column-number','1')+' '+os(a,'flycheck-error-list-warning','warning')+' '+os(a,'flycheck-error-list-error-message','unused import')+' '+os(a,'flycheck-error-list-id-with-explainer','[W0611?]')); L.push(os(a,'flycheck-error-list-highlight','main.py:20 '+os(a,'flycheck-error-list-info','info')+' highlighted row')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderDiredPreview(){const a='dired',L=[]; L.push(os(a,'dired-header','/home/craig/code:')); L.push(' '+os(a,'dired-perm-write','drwxr-xr-x')+' craig 4096 '+os(a,'dired-directory','src/')); @@ -772,7 +2760,7 @@ function renderDiredPreview(){const a='dired',L=[]; L.push(' '+os(a,'dired-set-id','-rwsr-xr-x')+' root 900 setuid.bin'); L.push(' '+os(a,'dired-special','prw-r--r--')+' craig 0 fifo.pipe'); L.push(os(a,'dired-warning','! disk space low on /home')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderDirvishPreview(){const a='dirvish',L=[]; L.push(os(a,'dirvish-inactive','~/code')+' '+os(a,'dirvish-free-space','[free 24G]')); L.push(os(a,'dirvish-hl-line',' '+os(a,'dirvish-file-modes','-rw-r--r--')+' '+os(a,'dirvish-file-link-number','1')+' '+os(a,'dirvish-file-user-id','craig')+' '+os(a,'dirvish-file-group-id','staff')+' '+os(a,'dirvish-file-size','4.0K')+' '+os(a,'dirvish-file-time','Jun 8 02:24')+' init.el ')); @@ -784,7 +2772,7 @@ function renderDirvishPreview(){const a='dirvish',L=[]; L.push(' '+os(a,'dirvish-media-info-heading','Media')+' '+os(a,'dirvish-media-info-property-key','Dimensions:')+' 1920x1080'); L.push(' proc '+os(a,'dirvish-proc-running','running')+' / '+os(a,'dirvish-proc-finished','finished')+' / '+os(a,'dirvish-proc-failed','failed')); L.push(' narrow '+os(a,'dirvish-narrow-match-face-0','m0')+' '+os(a,'dirvish-narrow-match-face-1','m1')+' '+os(a,'dirvish-narrow-match-face-2','m2')+' '+os(a,'dirvish-narrow-match-face-3','m3')+os(a,'dirvish-narrow-split',' | ')+os(a,'dirvish-emerge-group-title','Group: images')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderCalibredbPreview(){const a='calibredb',L=[]; L.push(os(a,'calibredb-search-header-library-name-face','Calibre')+' '+os(a,'calibredb-search-header-library-path-face','~/books')+' '+os(a,'calibredb-search-header-total-face','412 books')+' '+os(a,'calibredb-search-header-filter-face','tag:scifi')+' '+os(a,'calibredb-search-header-sort-face','sort:date')+' '+os(a,'calibredb-search-header-highlight-face','[*]')); L.push(''); @@ -794,40 +2782,40 @@ function renderCalibredbPreview(){const a='calibredb',L=[]; L.push(os(a,'calibredb-title-detailed-view-face','Foundation (detailed)')+' '+os(a,'calibredb-language-face','eng')+' '+os(a,'calibredb-favorite-face','* fav')+' '+os(a,'calibredb-archive-face','archived')); L.push(os(a,'calibredb-ids-face','isbn:0553293354')+' '+os(a,'calibredb-file-face','foundation.epub')+' '+os(a,'calibredb-comment-face','A classic of the genre.')); L.push(os(a,'calibredb-edit-annotation-header-title-face','Annotations')+' '+os(a,'calibredb-highlight-face','highlighted passage')+' '+os(a,'calibredb-current-page-button-face','[page 42]')+' '+os(a,'calibredb-mouse-face','hover row')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderErcPreview(){const a='erc',L=[]; L.push(os(a,'erc-header-line',' #emacs on Libera.Chat 18 users ')); L.push(os(a,'erc-timestamp-face','[10:24]')+' '+os(a,'erc-notice-face','*** alice has joined #emacs')); - L.push(os(a,'erc-timestamp-face','[10:25]')+' <'+os(a,'erc-my-nick-prefix-face','@')+os(a,'erc-my-nick-face','craig')+'> '+os(a,'erc-default-face','hello everyone')); - L.push(os(a,'erc-timestamp-face','[10:25]')+' <'+os(a,'erc-nick-prefix-face','+')+os(a,'erc-nick-default-face','bob')+'> '+os(a,'erc-input-face','hi craig, see ')+os(a,'erc-button','this link')+os(a,'erc-input-face',' cc ')+os(a,'erc-button-nick-default-face','@alice')); + L.push(os(a,'erc-timestamp-face','[10:25]')+' <'+os(a,'erc-my-nick-prefix-face','@')+os(a,'erc-my-nick-face','craig')+'> '+os(a,'erc-input-face','hello everyone')); + L.push(os(a,'erc-timestamp-face','[10:25]')+' <'+os(a,'erc-nick-prefix-face','+')+os(a,'erc-nick-default-face','bob')+'> '+os(a,'erc-default-face','hi craig, see ')+os(a,'erc-button','this link')+os(a,'erc-default-face',' cc ')+os(a,'erc-button-nick-default-face','@alice')); L.push(os(a,'erc-timestamp-face','[10:26]')+' '+os(a,'erc-action-face','* craig waves')+' '+os(a,'erc-keyword-face','emacs')+' '+os(a,'erc-pal-face','<friend>')+' '+os(a,'erc-fool-face','<troll>')+' '+os(a,'erc-dangerous-host-face','<bad@host>')); L.push(os(a,'erc-timestamp-face','[10:27]')+' '+os(a,'erc-direct-msg-face','(DM)')+' <'+os(a,'erc-nick-msg-face','bob')+'> psst '+os(a,'erc-current-nick-face','craig')+' '+os(a,'erc-information','-info-')); L.push(os(a,'erc-error-face','*** ERROR: connection reset')); L.push(os(a,'erc-command-indicator-face','/help')+' '+os(a,'erc-bold-face','bold')+' '+os(a,'erc-italic-face','italic')+' '+os(a,'erc-underline-face','underline')+' '+os(a,'erc-inverse-face','inverse')+' '+os(a,'erc-spoiler-face','spoiler')); L.push(os(a,'erc-keep-place-indicator-arrow','>')+os(a,'erc-keep-place-indicator-line',' ---- last read ---- ')+os(a,'erc-fill-wrap-merge-indicator-face','+')); L.push(os(a,'erc-prompt-face','craig>')+' '+os(a,'erc-input-face','type a message...')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderOrgdrillPreview(){const a='org-drill',L=[]; L.push('Q: The capital of France is '+os(a,'org-drill-hidden-cloze-face','[...]')+'.'); L.push('A: The capital of France is '+os(a,'org-drill-visible-cloze-face','Paris')+'.'); L.push(' '+os(a,'org-drill-visible-cloze-hint-face','hint: P____')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderOrgnoterPreview(){const a='org-noter',L=[]; L.push('org-noter paper.pdf'); L.push(' page 1 '+os(a,'org-noter-notes-exist-face','[notes]')); L.push(' page 2 '+os(a,'org-noter-no-notes-exist-face','[no notes]')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderSignelPreview(){const a='signel',L=[]; L.push(os(a,'signel-timestamp-face','[10:24]')+' '+os(a,'signel-my-msg-face','Me: hey, are we still on for tonight?')); - L.push(os(a,'signel-timestamp-face','[10:25]')+' '+os(a,'signel-other-msg-face','Alice: yes! see you at 7')); + L.push(os(a,'signel-timestamp-face','[10:25]')+' '+os(a,'signel-other-msg-face','Christine: yes! see you at 7')); L.push(os(a,'signel-error-face','(failed to send -- retrying)')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderPearlPreview(){const a='pearl',L=[]; L.push(os(a,'pearl-preamble-summary','PEARL-42 Fix the broken picker')); L.push('State: '+os(a,'pearl-modified-local','In Progress')+' Priority: '+os(a,'pearl-modified-highlight','High')+' Estimate: '+os(a,'pearl-modified-unknown','?')); L.push(' '+os(a,'pearl-editable-comment','> add a comment (editable)')); L.push(' '+os(a,'pearl-readonly-comment','> created by automation (read-only)')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderShrPreview(){const a='shr',L=[]; L.push(os(a,'shr-text','shr renders nov (EPUB), eww (web), elfeed, and HTML mail.')); L.push(''); @@ -837,7 +2825,7 @@ function renderShrPreview(){const a='shr',L=[]; L.push(os(a,'shr-text','Body text flows in shr-text, with a ')+os(a,'shr-link','hyperlink')+os(a,'shr-text',' and a ')+os(a,'shr-selected-link','focused link')+os(a,'shr-text',',')); L.push(os(a,'shr-text','some ')+os(a,'shr-code','inline_code()')+os(a,'shr-text',', a ')+os(a,'shr-mark','highlighted mark')+os(a,'shr-text',', ')+os(a,'shr-strike-through','struck out')+os(a,'shr-text',', a footnote')+os(a,'shr-sup','[1]')+os(a,'shr-text',',')); L.push(os(a,'shr-text','an ')+os(a,'shr-abbreviation','HTML')+os(a,'shr-text',' abbreviation, and an ')+os(a,'shr-sliced-image','[image]')+os(a,'shr-text',' slice.')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderSlackPreview(){const a='slack',L=[]; L.push(os(a,'slack-room-info-title-room-name-face','#general')+' '+os(a,'slack-room-info-title-face','Acme Workspace')); L.push(os(a,'slack-room-info-section-title-face','Topic')+' '+os(a,'slack-room-info-section-label-face','daily standup')+' '+os(a,'slack-room-unread-face','3 unread')); @@ -857,14 +2845,14 @@ function renderSlackPreview(){const a='slack',L=[]; L.push('Users: '+os(a,'slack-user-active-face','alice (active)')+' '+os(a,'slack-user-dnd-face','bob (dnd)')+' '+os(a,'slack-profile-image-face','[img]')+' '+os(a,'slack-user-profile-header-face','Profile')+' '+os(a,'slack-user-profile-property-name-face','Title:')+' Dev'); L.push('Search: '+os(a,'slack-search-result-message-header-face','#general')+' '+os(a,'slack-search-result-message-username-face','craig')); L.push('Modeline: '+os(a,'slack-modeline-has-unreads-face','* unreads')+' '+os(a,'slack-modeline-channel-has-unreads-face','#ch')+' '+os(a,'slack-modeline-thread-has-unreads-face','thread')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} + return previewLines(L);} function renderTelegaPreview(){const a='telega',L=[]; L.push(os(a,'telega-root-heading','Telegram')+' '+os(a,'telega-tracking','[tracking]')+' '+os(a,'telega-unread-unmuted-modeline','5 unread')); - L.push(os(a,'telega-has-chatbuf-brackets','[')+os(a,'telega-username','Alice')+os(a,'telega-has-chatbuf-brackets',']')+' '+os(a,'telega-user-online-status','online')+' '+os(a,'telega-unmuted-count','3')+' '+os(a,'telega-mention-count','@2')+os(a,'telega-delim-face',' | ')+os(a,'telega-secret-title','Secret')+' '+os(a,'telega-muted-count','muted')); + L.push(os(a,'telega-has-chatbuf-brackets','[')+os(a,'telega-username','Christine')+os(a,'telega-has-chatbuf-brackets',']')+' '+os(a,'telega-user-online-status','online')+' '+os(a,'telega-unmuted-count','3')+' '+os(a,'telega-mention-count','@2')+os(a,'telega-delim-face',' | ')+os(a,'telega-secret-title','Secret')+' '+os(a,'telega-muted-count','muted')); L.push(os(a,'telega-username','Bob')+' '+os(a,'telega-user-non-online-status','last seen recently')+' '+os(a,'telega-contact-birthdays-today','birthday today')+' '+os(a,'telega-shadow','shadow')+' '+os(a,'telega-link','link')+' '+os(a,'telega-blue','blue')+' '+os(a,'telega-red','red')); L.push(''); L.push(os(a,'telega-msg-heading','Today')); - L.push(os(a,'telega-msg-user-title','Alice')+' '+os(a,'telega-msg-inline-reply','| reply to Bob')+' '+os(a,'telega-msg-inline-forward','fwd from Carol')+' '+os(a,'telega-msg-inline-other','via bot')); + L.push(os(a,'telega-msg-user-title','Christine')+' '+os(a,'telega-msg-inline-reply','| reply to Bob')+' '+os(a,'telega-msg-inline-forward','fwd from Carol')+' '+os(a,'telega-msg-inline-other','via bot')); L.push(' '+os(a,'telega-entity-type-bold','bold')+' '+os(a,'telega-entity-type-italic','italic')+' '+os(a,'telega-entity-type-underline','underline')+' '+os(a,'telega-entity-type-strikethrough','strike')+' '+os(a,'telega-entity-type-code','code')+' '+os(a,'telega-entity-type-spoiler','spoiler')); L.push(' '+os(a,'telega-entity-type-pre','pre block')+' '+os(a,'telega-entity-type-blockquote','> quote')+' '+os(a,'telega-entity-type-mention','@user')+' '+os(a,'telega-entity-type-hashtag','#tag')+' '+os(a,'telega-entity-type-cashtag','$USD')+' '+os(a,'telega-entity-type-botcommand','/start')+' '+os(a,'telega-entity-type-texturl','link')); L.push(os(a,'telega-msg-self-title','Me')+' '+os(a,'telega-reaction',':+1: 2')+' '+os(a,'telega-reaction-chosen',':heart: 1')+' '+os(a,'telega-reaction-paid',':star: 5')+' '+os(a,'telega-reaction-paid-chosen',':star: paid')+' '+os(a,'telega-msg-deleted','(deleted)')+' '+os(a,'telega-msg-sponsored','Sponsored')); @@ -876,44 +2864,248 @@ function renderTelegaPreview(){const a='telega',L=[]; L.push('Palette '+os(a,'telega-palette-builtin-blue','blue')+' '+os(a,'telega-palette-builtin-green','green')+' '+os(a,'telega-palette-builtin-orange','orange')+' '+os(a,'telega-palette-builtin-purple','purple')); L.push(os(a,'telega-link-preview-sitename','example.com')+' '+os(a,'telega-link-preview-title','Link preview title')); L.push('Webpage '+os(a,'telega-webpage-title','Title')+' '+os(a,'telega-webpage-subtitle','Subtitle')+' '+os(a,'telega-webpage-header','Header')+' '+os(a,'telega-webpage-subheader','Subheader')+' '+os(a,'telega-webpage-outline','outline')+' '+os(a,'telega-webpage-fixed','fixed')+' '+os(a,'telega-webpage-preformatted','pre')+' '+os(a,'telega-webpage-marked','marked')+' '+os(a,'telega-webpage-strike-through','strike')+' '+os(a,'telega-webpage-chat-link','chat-link')); - return `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} -function genericPreview(app){let h='<div style="padding:10px 14px;font:12pt/1.8 monospace">';for(const [face,label,def] of APPS[app].faces){const f=PKGMAP[app][face],efg=pkgEffFg(app,face)||MAP['p'],ebg=pkgEffBg(app,face);h+=`<div data-face="${face}" style="color:${efg};${ebg?'background:'+ebg+';':''}font-weight:${f.bold?'bold':'normal'};font-style:${f.italic?'italic':'normal'};font-size:${(f.height||1)}em">${esc(label)}</div>`;}return h+'</div>';} -function buildPkgPreview(){const app=curApp(),p=document.getElementById('pkgpreview');if(!p)return;const pv=APPS[app].preview;const bespoke=['org','magit','elfeed','ghostel','dashboard','mu4e','lsp','gitgutter','flycheck','dired','dirvish','calibredb','erc','orgdrill','orgnoter','signel','pearl','slack','telega','shr'].includes(pv);p.innerHTML=pv==='org'?renderOrgPreview():pv==='magit'?renderMagitPreview():pv==='elfeed'?renderElfeedPreview():pv==='ghostel'?renderGhostelPreview():pv==='dashboard'?renderDashboardPreview():pv==='mu4e'?renderMu4ePreview():pv==='lsp'?renderLspPreview():pv==='gitgutter'?renderGitGutterPreview():pv==='flycheck'?renderFlycheckPreview():pv==='dired'?renderDiredPreview():pv==='dirvish'?renderDirvishPreview():pv==='calibredb'?renderCalibredbPreview():pv==='erc'?renderErcPreview():pv==='orgdrill'?renderOrgdrillPreview():pv==='orgnoter'?renderOrgnoterPreview():pv==='signel'?renderSignelPreview():pv==='pearl'?renderPearlPreview():pv==='slack'?renderSlackPreview():pv==='telega'?renderTelegaPreview():pv==='shr'?renderShrPreview():genericPreview(app);p.style.background=MAP['bg'];p.onclick=(e)=>{const u=e.target.closest('[data-face]');if(u)flashPkg(u.dataset.face);};const lbl=document.getElementById('pkgprevlabel');if(lbl)lbl.textContent=bespoke?(APPS[app].label+' preview'):'preview (generic — face names in their own colors)';} -function resetApp(){const app=curApp();PKGMAP[app]={};for(const [face,label,d] of APPS[app].faces)PKGMAP[app][face]=seedFace(d);pkgChanged();} + return previewLines(L);} +function genericPreview(app){let h='<div style="padding:10px 14px;font:12pt/1.8 monospace">';for(const [face,label] of APPS[app].faces)h+=`<div data-face="${face}" style="${ofs(app,face)}">${esc(label)}</div>`;return h+'</div>';} +// Bespoke split preview: a focused window beside its auto-dimmed twin, both +// showing the language selected at the top of the page (kept in sync via the +// langsel onchange, which re-runs buildPkgPreview). The left pane carries the +// real per-token syntax colors; the right pane shows what auto-dim does -- every +// default/font-lock face remaps to the single `auto-dim-other-buffers' face, so +// the same code collapses to one faded foreground on the dim background. The +// trailing row demonstrates `auto-dim-other-buffers-hide' (org hidden text whose +// foreground matches the background, so it vanishes in a dimmed window). +function renderAutodimPreview(){ + const a='auto-dim-other-buffers'; + const langsel=document.getElementById('langsel'); + const lang=(langsel&&langsel.value)||Object.keys(SAMPLES)[0]; + const lines=(SAMPLES[lang]||[]).slice(0,9); + let lit=''; + for(const line of lines){ + if(!line.length){lit+='\n';continue;} + for(const [k,t] of line)lit+=`<span data-k="${k}" style="${syntaxStyle(k)}">${esc(t)}</span>`; + lit+='\n';} + const dimFg=effFg(pkgEffFg(a,'auto-dim-other-buffers')),dimBg=pkgEffBg(a,'auto-dim-other-buffers')||'#000000'; + let dim=''; + for(const line of lines){ + if(!line.length){dim+='\n';continue;} + for(const [,t] of line)dim+=esc(t); + dim+='\n';} + const hideFg=effFg(pkgEffFg(a,'auto-dim-other-buffers-hide')),hideBg=pkgEffBg(a,'auto-dim-other-buffers-hide')||dimBg; + const foldText='··· folded body (hidden when dimmed) ···'; + const accent=uf('cursor').bg||'#67809c'; + const pane=(label,body,bg,focused)=> + `<div style="flex:1;min-width:20ch;border:${focused?'2px solid '+accent:'1px solid #2a2a2a'};border-radius:4px;overflow:hidden">` + +`<div style="text-align:center;font:bold 10pt monospace;padding:4px;color:${focused?'#cdced1':'#8a8a8a'};background:${focused?'#1a1a1a':'#0a0a0a'};border-bottom:1px solid #2a2a2a">${label}</div>` + +`<div style="padding:10px 12px;font:12pt/1.6 monospace;white-space:pre;background:${bg}">${body}</div></div>`; + const litBody=lit+'\n'+`<span style="color:#5e6770">${esc(foldText)}</span>`; + const dimBody=`<span data-face="auto-dim-other-buffers" style="color:${dimFg}">${dim}</span>\n` + +`<span data-face="auto-dim-other-buffers-hide" style="color:${hideFg};background:${hideBg}">${esc(foldText)}</span>`; + return `<div style="display:flex;gap:12px;padding:12px 16px;background:${MAP['bg']}">` + +pane('normal',litBody,MAP['bg'],true) + +pane('auto-dim',dimBody,dimBg,false) + +`</div>`; +} +function renderMarkdownPreview(){const a='markdown-mode',L=[]; + const dl='markdown-header-delimiter-face',mk='markdown-markup-face'; + L.push(os(a,mk,'---')); + L.push(os(a,'markdown-metadata-key-face','title:')+' '+os(a,'markdown-metadata-value-face','Project Name')); + L.push(os(a,'markdown-metadata-key-face','version:')+' '+os(a,'markdown-metadata-value-face','1.2.0')); + L.push(os(a,mk,'---')); + L.push(''); + L.push(os(a,dl,'#')+' '+os(a,'markdown-header-face-1','Project Name')); + L.push(''); + L.push(os(a,'markdown-comment-face','<!-- a one-line tagline -->')); + L.push('A library for '+os(a,'markdown-bold-face','**doing things**')+' and '+os(a,'markdown-italic-face','*other things*')+'.'); + L.push(''); + L.push(os(a,dl,'##')+' '+os(a,'markdown-header-face-2','Installation')); + L.push(''); + L.push('Run '+os(a,'markdown-inline-code-face','`npm install project`')+' to get started.'); + L.push(''); + L.push(os(a,mk,'```')+os(a,'markdown-language-keyword-face','sh')); + L.push(os(a,'markdown-pre-face',' git clone https://example.com/project.git')); + L.push(os(a,'markdown-pre-face',' cd project; make')); + L.push(os(a,mk,'```')); + L.push(''); + L.push(os(a,dl,'###')+' '+os(a,'markdown-header-face-3','Usage')); + L.push(''); + L.push(os(a,'markdown-list-face','- ')+'See the '+os(a,'markdown-link-face','[docs]')+os(a,'markdown-url-face','(https://example.com/docs)')+' for details.'); + L.push(os(a,'markdown-list-face','- ')+'Or browse '+os(a,'markdown-plain-url-face','https://example.com')+' directly.'); + L.push(os(a,'markdown-gfm-checkbox-face','- [x]')+' shipped '+os(a,'markdown-gfm-checkbox-face','- [ ]')+' planned'); + L.push(''); + L.push(os(a,'markdown-blockquote-face','> A note worth quoting, with a footnote')+os(a,'markdown-footnote-marker-face','[^1]')+os(a,'markdown-blockquote-face','.')); + L.push(''); + L.push(os(a,'markdown-table-face','| Option | Default |')); + L.push(os(a,'markdown-table-face','|--------|---------|')); + L.push(os(a,'markdown-table-face','| debug | false |')); + L.push(''); + L.push(os(a,'markdown-hr-face','---')); + L.push(''); + L.push(os(a,'markdown-strike-through-face','~~deprecated~~')+' '+os(a,'markdown-highlight-face','==important==')+' '+os(a,'markdown-math-face','$E = mc^2$')); + L.push(os(a,'markdown-html-tag-delimiter-face','<')+os(a,'markdown-html-tag-name-face','kbd')+os(a,'markdown-html-tag-delimiter-face','>')+'Ctrl-C'+os(a,'markdown-html-tag-delimiter-face','</')+os(a,'markdown-html-tag-name-face','kbd')+os(a,'markdown-html-tag-delimiter-face','>')); + L.push(os(a,'markdown-footnote-marker-face','[^1]:')+' '+os(a,'markdown-footnote-text-face','the footnote text.')); + return previewLines(L);} + +const PACKAGE_PREVIEWS={ + autodim:renderAutodimPreview,markdown:renderMarkdownPreview, + org:renderOrgPreview,magit:renderMagitPreview,elfeed:renderElfeedPreview,ghostel:renderGhostelPreview, + dashboard:renderDashboardPreview,mu4e:renderMu4ePreview,gnus:renderGnusPreview,orgfaces:renderOrgFacesPreview,lsp:renderLspPreview,gitgutter:renderGitGutterPreview, + flycheck:renderFlycheckPreview,dired:renderDiredPreview,dirvish:renderDirvishPreview,calibredb:renderCalibredbPreview, + erc:renderErcPreview,orgdrill:renderOrgdrillPreview,orgnoter:renderOrgnoterPreview,signel:renderSignelPreview, + pearl:renderPearlPreview,slack:renderSlackPreview,telega:renderTelegaPreview,shr:renderShrPreview +}; +function buildPkgPreview(){ + const app=curApp(),p=document.getElementById('pkgpreview');if(!p)return; + const renderer=PACKAGE_PREVIEWS[APPS[app].preview]; + p.innerHTML=renderer?renderer():genericPreview(app); + p.style.background=MAP['bg']; + p.onclick=(e)=>{const u=e.target.closest('[data-face]');if(u)flashPkg(u.dataset.face);}; + const lbl=document.getElementById('pkgprevlabel');if(lbl)lbl.textContent=renderer?(APPS[app].label+' preview'):'preview (generic — face names in their own colors)'; +} +function resetApp(){const app=curApp();for(const [face,,d] of APPS[app].faces)if(!LOCKED.has('pkg:'+app+':'+face))PKGMAP[app][face]=seedFace(d);pkgChanged();notify('reset editable '+app+' faces to package defaults',false);} function syncPkgHeight(){const t=document.getElementById('pkgtable'),m=document.getElementById('pkgpreview');if(!t||!m)return;const lb=m.previousElementSibling,lbh=lb?lb.getBoundingClientRect().height+10:30;m.style.height=Math.max(t.getBoundingClientRect().height-lbh,220)+'px';} -function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)return;const o=UIMAP[face];pv.style.color=o.fg||MAP['p'];pv.style.background=o.bg||MAP['bg'];pv.style.fontWeight=o.bold?'bold':'normal';pv.style.fontStyle=o.italic?'italic':'normal';pv.style.textDecoration=(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none';} +// --- worst-case readout for the covered overlay faces (spec Phase 4) --------- +// Default WCAG target for the worst-case verdict (AA). AAA is selectable. +let WORST_TARGET=4.5; +// The live v1 foreground set for a covered overlay face: the syntax-token colors +// (every assignable category except the ground) plus the default foreground. +function fgSetForFace(face){ + const syntaxAssignments=CATS.filter(c=>c[0]!=='bg'&&c[0]!=='p').map(c=>({role:c[0],name:c[1],hex:effFg(syntaxFace(c[0]).fg)})); + return fgSetFor(face,{covered:COVERED_FACES,syntaxAssignments,defaultFg:MAP['p']}); +} +function coveredContrastReport(face){ + if(uf(face).fg)return null; + const r=fgSetForFace(face); + if(r.reason==='out-of-scope')return null; + if(r.reason==='empty'||!r.set.length)return {empty:true}; + const bg=effBg(uf(face).bg); + const rows=r.set.map(f=>{ + const ratio=contrast(f.hex,bg); + return {label:f.label,name:f.name||f.label,hex:f.hex,ratio,verdict:verdictFor(ratio,WORST_TARGET)}; + }).sort((a,b)=>a.ratio-b.ratio); + return {bg,rows,worst:rows[0],failures:rows.filter(x=>x.ratio<WORST_TARGET)}; +} +function failureTitle(report){ + if(!report||!report.failures||!report.failures.length)return ''; + const lines=['failing covered-text contrasts against '+report.bg+':']; + report.failures.forEach(f=>lines.push(`${f.ratio.toFixed(1)} FAIL ${f.label} (${f.name}) ${f.hex}`)); + return lines.join('\n'); +} +// The worst-case contrast cell for a covered face: the floor over its foreground +// set against its effective background. Returns null for an out-of-scope face so +// the caller keeps the single-pair readout. +function worstCellHtml(face){ + const report=coveredContrastReport(face); + if(report===null)return null; + if(report.empty)return '<span title="this overlay has no syntax foreground set yet">no fg set</span>'; + return `<span style="color:${ratingColor(report.worst.ratio)}" title="${esc(failureTitle(report)||'all covered text clears '+WORST_TARGET.toFixed(1))}">${report.worst.ratio.toFixed(1)}</span>`; +} +// Repaint every covered overlay face (their floors depend on the syntax palette, +// so a syntax-color edit has to refresh them even though it doesn't rebuild the table). +function repaintCovered(){COVERED_FACES.forEach(f=>{if(UIMAP[f]&&document.getElementById('uicr-'+f))paintUI(f);});} +function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)return;const o=UIMAP[face];pv.style.color=effFg(o.fg);pv.style.background=effBg(o.bg);pv.style.fontWeight=cssWeight(o.weight);pv.style.fontStyle=o.slant||'normal';pv.style.textDecoration=(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none';pv.style.boxShadow=boxCss(o.box,effBg(o.bg)); + const report=coveredContrastReport(face); + pv.title=''; + const cr=document.getElementById('uicr-'+face);if(cr){cr.title='';if(report!==null){if(report.empty){cr.title='this overlay has no syntax foreground set yet';cr.innerHTML='<span title="this overlay has no syntax foreground set yet">no fg set</span>';}else{const title=failureTitle(report)||'all covered text clears '+WORST_TARGET.toFixed(1);cr.title=title;cr.innerHTML=`<span style="color:${ratingColor(report.worst.ratio)}" title="${esc(title)}">${report.worst.ratio.toFixed(1)}</span>`;}}else{const efg=effFg(o.fg),ebg=effBg(o.bg),r=contrast(efg,ebg);cr.innerHTML=crHtml(r);}}} function buildUITable(){ const tb=document.getElementById('uibody');tb.innerHTML=''; for(const [face,label,ex] of UI_FACES){ const tr=document.createElement('tr');tr.dataset.face=face; - const c0=document.createElement('td');c0.className='cat';c0.textContent=label;c0.style.cursor='pointer';c0.title='flash this face in the live preview';c0.onclick=()=>flashUiPreview(face); - const cF=document.createElement('td');cF.appendChild(uiSelect(face,'fg')); - const cB=document.createElement('td');cB.appendChild(uiSelect(face,'bg')); - const cS=document.createElement('td');[['B','bold'],['I','italic'],['U','underline'],['S','strike']].forEach(([ch,at])=>{const b=document.createElement('button');b.className='sbtn'+(UIMAP[face][at]?' on':'');b.textContent='a';b.style.fontWeight=at==='bold'?'bold':'normal';b.style.fontStyle=at==='italic'?'italic':'normal';b.style.textDecoration=at==='underline'?'underline':at==='strike'?'line-through':'none';b.title=at;b.onclick=()=>{UIMAP[face][at]=!UIMAP[face][at];paintUI(face);buildMockFrame();};cS.appendChild(b);}); + const exp=mkExpander(UIMAP[face],tableColCount('uitable'),()=>{paintUI(face);buildMockFrame();},{expandKey:face,showInheritHeight:true,inheritOptions:[''].concat(BASE_INHERITS),defaultHex:effFg(UIMAP[face].fg),ndCheck:()=>overflowNonDefault(UIMAP[face],DEFAULT_UIMAP[face],true)}); + exp.detail.dataset.detailFor=face; + const c0=document.createElement('td');c0.className='cat';c0.title=composeHoverTitle(FACE_DOCS[face],c0.title);c0.appendChild(exp.btn); + const c0lbl=document.createElement('span');c0lbl.textContent=' '+label;c0lbl.style.cursor='pointer';c0lbl.title='flash this face in the live preview';c0lbl.onclick=()=>flashUiPreview(face);c0.appendChild(c0lbl); + const fgSel=uiSelect(face,'fg'),bgSel=uiSelect(face,'bg'); + const cF=document.createElement('td');cF.appendChild(fgSel); + const cB=document.createElement('td');cB.appendChild(bgSel); + const cS=document.createElement('td'); + const stCtls=mkStyleControls(UIMAP[face],()=>{paintUI(face);buildMockFrame();},{defaultHex:effFg(UIMAP[face].fg)}); + const uiCluster=document.createElement('div');uiCluster.className='stylecluster';stCtls.forEach(c=>uiCluster.appendChild(c));cS.appendChild(uiCluster); + const cC=document.createElement('td');cC.id='uicr-'+face;cC.style.whiteSpace='nowrap';cC.style.fontSize='10pt'; const cP=document.createElement('td');cP.className='ex';cP.id='uiprev-'+face;cP.textContent=ex;cP.style.padding='4px 10px';cP.style.borderRadius='4px'; - tr.appendChild(c0);tr.appendChild(cF);tr.appendChild(cB);tr.appendChild(cS);tr.appendChild(cP);tb.appendChild(tr);paintUI(face); + const cX=document.createElement('td');const boxCtl=mkBoxControl(()=>UIMAP[face].box,b=>{UIMAP[face].box=b;paintUI(face);buildMockFrame();},{compact:true});cX.appendChild(boxCtl); + const cL=mkLockCell('ui:'+face,[fgSel,bgSel,...stCtls,boxCtl,...exp.locks]); + tr.appendChild(cL);tr.appendChild(c0);tr.appendChild(cF);tr.appendChild(cB);tr.appendChild(cS);tr.appendChild(cX);tr.appendChild(cC);tr.appendChild(cP);tb.appendChild(tr);tb.appendChild(exp.detail);paintUI(face); } applyTableSort('uibody'); + updateLockToggle('ui');syncExpandAllBtns(); } -let D={}; -function srt(c){const tb=document.getElementById('legbody');const r=[...tb.rows];D[c]=!D[c]; - r.sort((a,b)=>{const x=(c===0?a.querySelector('select').value:a.cells[0].innerText).toLowerCase(), - y=(c===0?b.querySelector('select').value:b.cells[0].innerText).toLowerCase(); - return (x<y?-1:x>y?1:0)*(D[c]?1:-1);});r.forEach(x=>tb.appendChild(x));} -// Generic header-click sort for the package and UI tables. Reads a select -// value, a numeric input, or cell text (numeric when the text leads with a -// number, e.g. contrast or size). The sort is remembered per table and -// re-applied after a rebuild so editing a face does not reset it. +// Generic header-click sort, shared by all three tables. Reads a swatch +// dropdown's value, a select value, a numeric input, or cell text (numeric when +// the text leads with a number, e.g. contrast or size). The UI and package +// tables remember the sort (applyTableSort runs on rebuild) so editing a row +// does not reset it; the syntax table sorts on click only. let tableSort={}; -function cellVal(td){if(!td)return '';const s=td.querySelector('select');if(s)return s.value.toLowerCase();const i=td.querySelector('input');if(i)return parseFloat(i.value)||0;const t=td.innerText.trim();const n=parseFloat(t);return (!isNaN(n)&&/^[-\d.]/.test(t))?n:t.toLowerCase();} +function cellVal(td){if(!td)return '';const dd=td.querySelector('.cdd');if(dd)return (dd.dataset.val||'').toLowerCase();const s=td.querySelector('select');if(s)return s.value.toLowerCase();const i=td.querySelector('input');if(i)return parseFloat(i.value)||0;const t=td.innerText.trim();const n=parseFloat(t);return (!isNaN(n)&&/^[-\d.]/.test(t))?n:t.toLowerCase();} function srtTable(tbId,col){tableSort[tbId]={col,asc:!(tableSort[tbId]&&tableSort[tbId].col===col&&tableSort[tbId].asc)};applyTableSort(tbId);} -function applyTableSort(tbId){const s=tableSort[tbId];if(!s)return;const tb=document.getElementById(tbId);if(!tb)return;const dir=s.asc?1:-1;const r=[...tb.rows];r.sort((a,b)=>{const x=cellVal(a.cells[s.col]),y=cellVal(b.cells[s.col]);return ((typeof x==='number'&&typeof y==='number')?x-y:(x<y?-1:x>y?1:0))*dir;});r.forEach(x=>tb.appendChild(x));} -buildLangSel();buildAppSel();renderPalette();buildTable();buildUITable();renderCode();applyGround();updateTitle();initPicker();buildPkgTable();buildPkgPreview();syncMockHeight();syncPkgHeight(); +function applyTableSort(tbId){const s=tableSort[tbId];if(!s)return;const tb=document.getElementById(tbId);if(!tb)return;const dir=s.asc?1:-1; + // Sort only the main rows; each expander detail row rides along right after its + // parent (matched by data-detail-for) so a sort never separates the pair. + const details={};[...tb.rows].forEach(x=>{if(x.classList.contains('detailrow'))details[x.dataset.detailFor]=x;}); + const mains=[...tb.rows].filter(x=>!x.classList.contains('detailrow')); + mains.sort((a,b)=>{const x=cellVal(a.cells[s.col]),y=cellVal(b.cells[s.col]);return ((typeof x==='number'&&typeof y==='number')?x-y:(x<y?-1:x>y?1:0))*dir;}); + mains.forEach(x=>{tb.appendChild(x);const key=x.dataset.face||x.dataset.kind;if(key&&details[key])tb.appendChild(details[key]);});} +function initApp(){ + paletteShowFull=false; // open collapsed to base colors; the arrow expands the spans + buildLangSel();buildViewSel();renderPalette();rebuildColorTables();renderCode();applyGround(); + initGeneratorControls(); + updateTitle();initPicker();buildPkgPreview();syncMockHeight();syncPkgHeight(); + onViewChange(); +} +initApp(); addEventListener('resize',()=>{syncMockHeight();syncPkgHeight();}); +// Shared gate harness. Each call site keeps its literal location.hash==='#NAMEtest' +// check (run-tests.sh greps it); gate() owns the ok/notes/A setup and the verdict +// postamble. Note format standardized to ' fails=note1,note2'. +function gate(id, body){ + const name=id.toUpperCase(); + let ok=true;const notes=[]; + const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + body(A); + const verdict=name+' '+(ok?'PASS':'FAIL'); + document.title=verdict; + const d=document.createElement('div');d.id=id; + d.textContent=verdict+(notes.length?' fails='+notes.join(','):''); + document.body.appendChild(d); +} +function withSavedState(keys, body){ + // Snapshot the named studio globals, run BODY, then restore them in a finally + // so opening the studio at a #gate hash doesn't leave its state mutated for + // interactive use. Each key maps to a [get, set, clone] triple over the live + // let-binding. Scope the keys to what the gate actually touches. + // JSON clone (not structuredClone): the studio data objects carry values + // structuredClone throws on, and a JSON round-trip of the data is exactly what + // the gates' own local saves already use. + const jc=x=>JSON.parse(JSON.stringify(x)); + const reg={ + PALETTE:[()=>PALETTE, v=>{PALETTE=v;}, jc], + MAP:[()=>MAP, v=>{MAP=v;}, jc], + SYNTAX:[()=>SYNTAX, v=>{SYNTAX=v;}, jc], + UIMAP:[()=>UIMAP, v=>{UIMAP=v;}, jc], + PKGMAP:[()=>PKGMAP, v=>{PKGMAP=v;}, jc], + LOCKED:[()=>LOCKED, v=>{LOCKED.clear();for(const k of v)LOCKED.add(k);}, s=>new Set(s)], + }; + const snap=keys.map(k=>[k, reg[k][2](reg[k][0]())]); + try{ body(); } + finally{ for(const [k,v] of snap) reg[k][1](v); } +} +// Shared preview-face validator for the #mdtest / #mupreviewtest / #gnustest +// gates: render HTML into a detached div, then assert it exercises at least +// MINCOUNT data-faces, that every data-face is a real face of the package +// (drawn from FACES, the app's face rows), and that each face in REQUIRED is +// present. A is the gate's assertion collector; NAME labels the failure note. +function assertPreviewFaces(A, html, faces, minCount, name, required){ + const box=document.createElement('div');box.innerHTML=html; + const valid=new Set((faces||[]).map(r=>r[0])); + const used=[...box.querySelectorAll('[data-face]')].map(e=>e.dataset.face); + A(used.length>=minCount,'preview exercises many faces ('+used.length+')'); + const bad=used.filter(f=>!valid.has(f)); + A(bad.length===0,'every data-face is a real '+name+' face; bad='+bad.join(',')); + for(const f of required) A(used.includes(f),'preview includes '+f); +} // Phase-1 self-test (open with #selftest): seed -> export -> import -> compare. function pkgSelftest(){ const seeded=seedPkgmap(); - seeded['org-mode']['org-level-2']={fg:'#e8bd30',bg:null,bold:false,italic:false,inherit:'org-level-1',height:1.2,source:'user'}; + seeded['org-mode']['org-level-2']={fg:'#e8bd30',bg:null,weight:null,slant:null,inherit:'org-level-1',height:1.2,source:'user'}; const exp=packagesForExport(seeded); const round=seedPkgmap();mergePackagesInto(round,exp); const roundtrip=JSON.stringify(exp)===JSON.stringify(packagesForExport(round)); @@ -921,31 +3113,271 @@ function pkgSelftest(){ const l2=exp['org-mode']['org-level-2']; const inherited=l2.inherit==='org-level-1'&&l2.source==='user'; const height=l2.height===1.2 && !('height' in (exp['org-mode']['org-todo'])); - const sc=seedPkgmap();sc['org-mode']['org-todo']={fg:null,bg:null,bold:false,italic:false,inherit:null,height:1,source:'cleared'}; + const sc=seedPkgmap();sc['org-mode']['org-todo']={fg:null,bg:null,weight:null,slant:null,inherit:null,height:1,source:'cleared'}; const cleared='org-todo' in packagesForExport(sc)['org-mode']; const su=seedPkgmap();mergePackagesInto(su,{'zzz-pkg':{'zzz-face':{fg:'#112233',source:'user'}}}); const unknown=!!(su['zzz-pkg']&&su['zzz-pkg']['zzz-face'].fg==='#112233'); - PKGMAP['__cyc']={a:{fg:null,bg:null,bold:false,italic:false,inherit:'b',height:1,source:'user'},b:{fg:null,bg:null,bold:false,italic:false,inherit:'a',height:1,source:'user'}}; + PKGMAP['__cyc']={a:{fg:null,bg:null,weight:null,slant:null,inherit:'b',height:1,source:'user'},b:{fg:null,bg:null,weight:null,slant:null,inherit:'a',height:1,source:'user'}}; let cyc=true;try{pkgEffFg('__cyc','a');}catch(e){cyc=false;}delete PKGMAP['__cyc']; const verdict=(roundtrip&&oldjson&&inherited&&height&&cleared&&unknown&&cyc)?'PASS':'FAIL'; document.title='SELFTEST '+verdict; const d=document.createElement('div');d.id='selftest';d.textContent='SELFTEST '+verdict+' roundtrip='+roundtrip+' oldjson='+oldjson+' inherit='+inherited+' height='+height+' cleared='+cleared+' unknown='+unknown+' cycle='+cyc;document.body.appendChild(d); } if(location.hash==='#selftest')pkgSelftest(); +// Lock-mechanism gate (open with #locktest): two behaviors the refactor must +// preserve, across all three tiers. (1) Locking a row disables its controls via +// the shared mkLockCell. (2) reset/erase batch actions update editable rows but +// leave locked rows (syntax bare-kind, ui:, pkg: keys) untouched. +if(location.hash==='#locktest')gate('locktest',A=>withSavedState(['PALETTE','MAP','SYNTAX','UIMAP','PKGMAP','LOCKED'],()=>{ + const cssRgb=h=>{const [r,g,b]=hex2rgb(h);return 'rgb('+r+', '+g+', '+b+')';}; + LOCKED.clear();buildTable(); + {const k=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p')[0]; + const tr=document.querySelector('#legbody tr[data-kind="'+k+'"]'),step=tr.querySelector('.cstep'),lb=tr.querySelector('.lockbtn'); + A(step.dataset.locked!=='1','syntax-dd-starts-unlocked');lb.click(); + A(step.dataset.locked==='1'&&step.classList.contains('locked')&&step.querySelector('.cstepbtn').disabled,'syntax-lock-disables-dd');lb.click(); + A(step.dataset.locked!=='1'&&!step.classList.contains('locked'),'syntax-unlock-reenables-dd');} + LOCKED.clear();buildUITable(); + {const f=UI_FACES[0][0]; + const tr=document.querySelector('#uibody tr[data-face="'+f+'"]'),step=tr.querySelector('.cstep'),lb=tr.querySelector('.lockbtn'); + A(step.dataset.locked!=='1','ui-dd-starts-unlocked');lb.click(); + A(step.dataset.locked==='1'&&step.classList.contains('locked')&&step.querySelector('.cstepbtn').disabled,'ui-lock-disables-dd');lb.click(); + A(step.dataset.locked!=='1'&&!step.classList.contains('locked'),'ui-unlock-reenables-dd');} + {UIMAP['region'].fg=null;UIMAP['region'].bg='#888888';buildUITable(); + const tr=document.querySelector('#uibody tr[data-face="region"]'),fg=tr.cells[2].querySelector('.cdd'),bg=tr.cells[3].querySelector('.cdd'); + A(fg.classList.contains('is-default'),'compact default color button has default outline class'); + A(!bg.classList.contains('is-default'),'compact assigned color button does not have default outline class');} + {setSyntaxFg('kw','');buildTable(); + const dd=document.querySelector('#legbody tr[data-kind="kw"] .cdd'); + A(dd&&dd.style.backgroundColor===cssRgb(MAP['p']),'syntax default fg swatch shows inherited fg color');} + {UIMAP['fringe'].bg=null;buildUITable(); + const dd=document.querySelector('#uibody tr[data-face="fringe"]').cells[3].querySelector('.cdd'); + A(dd&&dd.style.backgroundColor===cssRgb(MAP['bg']),'ui default bg swatch shows inherited ground color');} + {const app=curApp(),face=APPS[app].faces[0][0];PKGMAP[app][face].fg=null;PKGMAP[app][face].inherit=null;buildPkgTable(); + const dd=document.querySelector('#pkgbody tr[data-face="'+face+'"]').cells[2].querySelector('.cdd'); + A(dd&&dd.style.backgroundColor===cssRgb(MAP['p']),'package default fg swatch shows inherited/default fg color');} + {PALETTE=[['#000000','bg','ground'],['#ffffff','fg','ground'],['#222222','gray-dark','gray'],['#888888','gray-mid','gray'],['#dddddd','gray-light','gray']];setSyntaxFg('bg','#000000');setSyntaxFg('p','#ffffff');setSyntaxFg('kw','#888888');LOCKED.clear();buildTable(); + const tr=document.querySelector('#legbody tr[data-kind="kw"]'),btns=tr.querySelectorAll('.cstepbtn');btns[1].click(); + A(MAP['kw']==='#dddddd'&&tr.querySelector('.cdd').dataset.val==='#dddddd','syntax right arrow steps to lighter color');btns[0].click(); + A(MAP['kw']==='#888888','syntax left arrow steps to darker color');} + {UIMAP['region'].bg='#888888';LOCKED.clear();buildUITable();const tr=document.querySelector('#uibody tr[data-face="region"]'),btns=tr.cells[3].querySelectorAll('.cstepbtn');btns[1].click(); + A(UIMAP['region'].bg==='#dddddd','ui right arrow steps to lighter color');btns[0].click(); + A(UIMAP['region'].bg==='#888888','ui left arrow steps to darker color');} + {const app=curApp(),face=APPS[app].faces[0][0];PKGMAP[app][face].fg='#888888';LOCKED.clear();buildPkgTable();const tr=document.querySelector('#pkgbody tr[data-face="'+face+'"]'),btns=tr.cells[2].querySelectorAll('.cstepbtn');btns[1].click(); + A(PKGMAP[app][face].fg==='#dddddd','pkg right arrow steps to lighter color');btns[0].click(); + A(PKGMAP[app][face].fg==='#888888','pkg left arrow steps to darker color');} + {const ks=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p'),k1=ks[0],k2=ks[1]; + setSyntaxFg(k1,'#111111');setSyntaxFg(k2,'#222222');LOCKED.clear();LOCKED.add(k1);clearUnlocked(); + A(MAP[k1]==='#111111','syntax-erase-keeps-locked');A(MAP[k2]==='','syntax-erase-wipes-unlocked');} + {const ks=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p'),k1=ks[0],k2=ks[1]; + setSyntaxFg(k1,'#111111');setSyntaxFg(k2,'#222222');LOCKED.clear();LOCKED.add(k1);resetUnlocked(); + A(MAP[k1]==='#111111','syntax-reset-keeps-locked');A(MAP[k2]===DEFAULT_SYNTAX[k2].fg,'syntax-reset-restores-unlocked-default');} + {const f1=UI_FACES[0][0],f2=UI_FACES[1][0]; + UIMAP[f1].fg='#111111';UIMAP[f2].fg='#222222';LOCKED.clear();LOCKED.add('ui:'+f1);clearUnlockedUI(); + A(UIMAP[f1].fg==='#111111','ui-erase-keeps-locked');A(UIMAP[f2].fg===null,'ui-erase-wipes-unlocked');} + {const f1=UI_FACES[0][0],f2=UI_FACES[1][0]; + UIMAP[f1].fg='#111111';UIMAP[f2].fg='#222222';LOCKED.clear();LOCKED.add('ui:'+f1);resetUnlockedUI(); + A(UIMAP[f1].fg==='#111111','ui-reset-keeps-locked');A(JSON.stringify(UIMAP[f2])===JSON.stringify(DEFAULT_UIMAP[f2]),'ui-reset-restores-unlocked-default');} + {const app=curApp(),pf=APPS[app].faces.map(r=>r[0]),p1=pf[0],p2=pf[1]; + PKGMAP[app][p1].fg='#111111';PKGMAP[app][p2].fg='#222222';LOCKED.clear();LOCKED.add('pkg:'+app+':'+p1);clearUnlockedPkg(); + A(PKGMAP[app][p1].fg==='#111111','pkg-erase-keeps-locked');A(PKGMAP[app][p2].fg===null,'pkg-erase-wipes-unlocked');} + {const app=curApp(),rows=APPS[app].faces,p1=rows[0][0],p2=rows[1][0],d2=rows[1][2]; + PKGMAP[app][p1].fg='#111111';PKGMAP[app][p2]=normalizePkgFace({fg:'#222222',bg:'#333333',bold:true,source:'user'},'user'); + LOCKED.clear();LOCKED.add('pkg:'+app+':'+p1);resetApp(); + A(PKGMAP[app][p1].fg==='#111111','pkg-reset-keeps-locked'); + A(JSON.stringify(PKGMAP[app][p2])===JSON.stringify(seedFace(d2)),'pkg-reset-restores-unlocked-default');} + {LOCKED.clear();buildTable();const b=document.getElementById('syntaxlocktoggle');A(b&&b.textContent==='lock all','syntax toggle starts as lock all');b.click(); + A(syntaxLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','syntax lock-all locks every syntax row and flips label');b.click(); + A(syntaxLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','syntax unlock-all clears every syntax lock and flips label');} + {LOCKED.clear();buildUITable();const b=document.getElementById('uilocktoggle');A(b&&b.textContent==='lock all','ui toggle starts as lock all');b.click(); + A(uiLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','ui lock-all locks every UI row and flips label');b.click(); + A(uiLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','ui unlock-all clears every UI lock and flips label');} + {LOCKED.clear();buildPkgTable();const b=document.getElementById('pkglocktoggle');A(b&&b.textContent==='lock all','pkg toggle starts as lock all');b.click(); + A(pkgLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','pkg lock-all locks every current package row and flips label');b.click(); + A(pkgLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','pkg unlock-all clears every current package lock and flips label');} + {LOCKED.clear();const app=curApp(),faces=APPS[app].faces.map(r=>r[0]),filter=document.getElementById('pkgfilter'); + if(filter&&faces.length>1){filter.value=faces[0];buildPkgTable();const b=document.getElementById('pkglocktoggle');b.click(); + A(faces.every(face=>LOCKED.has('pkg:'+app+':'+face)),'pkg lock-all covers the whole package even when filtered'); + filter.value='';buildPkgTable();}} + })); +// Sort gate (open with #sorttest): all three tables now share srtTable/cellVal. +// Verifies the syntax table (which used to have its own srt) sorts by color +// value and by element name, that a repeat click reverses, and that the UI and +// package tables still sort. Guards the unified sort for the later stages. +if(location.hash==='#sorttest')gate('sorttest',A=>{ + const ddVals=tb=>[...document.querySelectorAll('#'+tb+' tr:not(.detailrow)')].map(tr=>{const dd=tr.cells[2].querySelector('.cdd');return dd?(dd.dataset.val||''):'';}); + const txtVals=tb=>[...document.querySelectorAll('#'+tb+' tr:not(.detailrow)')].map(tr=>tr.cells[1].innerText.trim().toLowerCase()); + const asc=a=>a.every((v,i)=>i===0||a[i-1]<=v),desc=a=>a.every((v,i)=>i===0||a[i-1]>=v); + buildTable(); + srtTable('legbody',2);A(asc(ddVals('legbody')),'legbody-color-asc'); + srtTable('legbody',2);A(desc(ddVals('legbody')),'legbody-color-desc'); + srtTable('legbody',1);A(asc(txtVals('legbody')),'legbody-elements-asc'); + buildUITable();srtTable('uibody',1);A(asc(txtVals('uibody')),'uibody-face-asc'); + buildPkgTable();srtTable('pkgbody',2);A(asc(ddVals('pkgbody')),'pkgbody-fg-asc'); + }); +// Live-buffer rendering gate (open with #mocktest): pins the face-faithfulness +// fixes so they cannot silently regress — overlay faces keep syntax colors and +// honor their styles, the cursor sits on a glyph, line numbers honor weight, the +// fringe shows its foreground indicator, and the mode-line carries its box. +if(location.hash==='#mocktest')gate('mocktest',A=>withSavedState(['UIMAP','PKGMAP'],()=>{ + const Q=s=>document.querySelector('#mockframe '+s); + buildMockFrame(); + A(Q('[data-face="highlight"] [data-k]'),'highlight-keeps-token-colors'); + A(Q('[data-face="region"] [data-k]'),'region-keeps-token-colors'); + const curCell=Q('[data-face="cursor"]'); + A(curCell&&curCell.textContent.trim().length===1,'cursor-on-glyph'); + UIMAP['cursor']={fg:'#112233',bg:'#aabbcc',weight:null,slant:null,underline:null,strike:null,box:null};buildMockFrame(); + const curStyled=Q('[data-face="cursor"]'),curSt=curStyled&&curStyled.getAttribute('style')||''; + A(curSt.includes('#112233')&&curSt.includes('#aabbcc'),'cursor preview honors fg and bg: '+curSt); + UIMAP['hl-line']={fg:'#112233',bg:'#aabbcc',weight:null,slant:null,underline:null,strike:null,box:null};buildMockFrame(); + const hlStyled=Q('[data-face="hl-line"]'),hlSt=hlStyled&&hlStyled.getAttribute('style')||''; + A(hlSt.includes('#112233')&&hlSt.includes('#aabbcc'),'hl-line preview honors fg and bg: '+hlSt); + UIMAP['link']={fg:'#112233',bg:'#aabbcc',weight:null,slant:null,underline:{style:'line',color:null},strike:null,box:null};buildMockFrame(); + const linkStyled=Q('[data-face="link"]'),linkSt=linkStyled&&linkStyled.getAttribute('style')||''; + A(linkSt.includes('#112233')&&linkSt.includes('#aabbcc'),'inline UI face preview honors fg and bg: '+linkSt); + const missing=UI_FACES.map(f=>f[0]).filter(f=>!Q('[data-face="'+f+'"]')); + A(missing.length===0,'all UI faces are represented in live buffer preview: '+missing.join(',')); + buildTable();buildUITable();buildPkgTable(); + [['#legbody tr[data-kind="kw"]',5],['#uibody tr[data-face="mode-line"]',5],['#pkgbody tr',5]].forEach(([sel,idx])=>{ + const cell=document.querySelector(sel)?.cells[idx],ctl=cell&&cell.querySelector('.boxctl'); + A(cell&&ctl&&ctl.getBoundingClientRect().width<=cell.getBoundingClientRect().width,'box control fits its table cell for '+sel); + }); + const laz=Q('[data-face="lazy-highlight"]'); + A(laz&&/background:\s*(?!transparent)/.test(laz.getAttribute('style')||''),'overlay-honors-background-style'); + A([...document.querySelectorAll('#mockframe .fr')].some(e=>e.textContent.trim()),'fringe-indicator-present'); + const mlbar=Q('[data-face="mode-line"]'); + A(mlbar&&/box-shadow/.test(mlbar.getAttribute('style')||''),'mode-line-box'); + const textBox=Q('.mbuftext'),border=Q('[data-face="vertical-border"]'),mock=document.getElementById('mockframe'); + if(textBox&&border&&mock){ + const tr=textBox.getBoundingClientRect(),br=border.getBoundingClientRect(); + const ch=parseFloat(getComputedStyle(textBox).fontSize)*0.65; + A(br.left-tr.right<=ch*4.8,'vertical-border-near-text'); + }else A(false,'vertical-border-layout-elements-present'); + UIMAP['line-number-current-line'].weight='bold';buildMockFrame(); + const curNum=Q('[data-face="line-number-current-line"]'); + A(curNum&&/font-weight:\s*700/.test(curNum.getAttribute('style')||''),'line-number-honors-weight'); + UIMAP['region'].weight=null;UIMAP['region'].slant=null;UIMAP['region'].underline=null;buildUITable(); + const regionRow=[...document.querySelectorAll('#uibody tr')].find(r=>r.dataset.face==='region'); + const pickEnum=(dd,label)=>{dd.click();const o=[..._ddPop.querySelectorAll('.enumopt')].find(b=>b.textContent===label);if(o)o.click();}; + const uiWeight=regionRow.querySelector('.enumdd'); + A(uiWeight&&uiWeight.dataset.val==='','ui weight dropdown starts empty when model is unset'); + pickEnum(uiWeight,'bold'); + A(UIMAP['region'].weight==='bold','ui weight dropdown writes the model'); + const app=curApp(),face=APPS[app].faces[0][0];PKGMAP[app][face].weight=null;buildPkgTable(); + const pkgWeight=()=>document.querySelector('#pkgbody tr[data-face="'+face+'"] .enumdd'); + A(pkgWeight()&&pkgWeight().dataset.val==='','pkg weight dropdown starts empty when model is unset'); + pickEnum(pkgWeight(),'heavy'); + A(PKGMAP[app][face].weight==='heavy'&&PKGMAP[app][face].source==='user','pkg weight dropdown writes the model and marks the face edited'); + })); +// Palette-generator gate (open with #generatortest): previewing is non-mutating, +// clicking a generated tile loads the existing selector, adding creates a normal +// singleton base column, and appending a preview column commits all span members +// under one stable column id. +if(location.hash==='#generatortest')gate('generatortest',A=>{ + const before=JSON.stringify(PALETTE); + A(document.getElementById('genaccents').value==='5','default accent count is 5'); + A(document.getElementById('gensource').value==='palette','default generator source is palette'); + A(document.querySelector('label:has(#genintent)').title==='what kind of candidate colors to look for','intent label hover explains the control'); + A(document.getElementById('genintent').title.includes('Pure exploration'),'intent select hover explains random'); + A([...document.getElementById('genintent').options].some(o=>o.value==='fill-hue-gaps'),'fill hue gaps intent is available'); + document.getElementById('genintent').value='fill-hue-gaps';syncGeneratorControls(); + A(document.getElementById('genintent').title.includes('underrepresented hue regions'),'fill hue gaps hover explains hue bonus'); + document.getElementById('genintent').value='near-palette';syncGeneratorControls(); + A(document.getElementById('genintent').title.includes('current palette base colors'),'intent select hover updates for selected intent'); + A(!document.getElementById('genscheme'),'legacy scheme control is removed from the generator UI'); + A(!document.getElementById('genhue'),'legacy base hue control is removed from the generator UI'); + document.getElementById('genaccents').value='3'; + document.getElementById('genintent').value='fill-gaps'; + document.getElementById('genvibe').value='warm'; + document.getElementById('gensource').value='palette'; + previewGenerator(); + A(GEN_PROPOSAL&&GEN_PROPOSAL.intent==='fill-gaps'&&GEN_PROPOSAL.vibe==='warm'&&GEN_PROPOSAL.sourceMode==='palette','intent/vibe/source feed the generator proposal'); + A(JSON.stringify(PALETTE)===before,'preview does not mutate palette'); + A(document.querySelectorAll('#genpreview .gencol').length===3,'renders requested preview columns'); + A(parseInt(getComputedStyle(document.querySelector('#genpreview .genchip')).width,10)===128,'generated candidate tile width matches palette tiles'); + A(parseInt(getComputedStyle(document.querySelector('#genpreview .genchip')).height,10)===58,'generated candidate tile height matches palette tiles'); + A([...document.querySelectorAll('#genpreview .gencol')].every(c=>c.querySelectorAll('.genchip').length===1),'preview columns show only one base tile each'); + const tile=document.querySelector('#genpreview .genchip[data-col="0"][data-member="0"]'); + A(!!tile,'base generated tile exists'); + const tileHex=tile&&tile.dataset.hex,tileName=tile&&tile.dataset.name; + if(tile)tile.click(); + A(document.getElementById('newhexstr').value===tileHex,'generated tile loads selector hex'); + A(document.getElementById('newname').value===tileName,'generated tile loads selector name'); + document.getElementById('genaccents').value='2'; + previewGenerator(); + A(document.querySelectorAll('#genpreview .gencol').length===2,'preview again replaces old preview columns'); + A(!document.querySelector('#genpreview .genchip.sel'),'preview again clears generated tile selection'); + document.getElementById('genaccents').value='3'; + previewGenerator(); + addColor(); + A(PALETTE.some(p=>p[0]===tileHex&&p[1]===tileName),'add color commits selected generated tile'); + const afterSingle=PALETTE.length; + previewGenerator(); + const append=document.querySelector('#genpreview .genappend[data-col="0"]'); + A(!!append,'append generated column button exists'); + if(append)append.click(); + A(PALETTE.length===afterSingle+1,'append commits one generated base color'); + const added=PALETTE.slice(-1); + A(new Set(added.map(p=>p[2])).size===1,'appended generated span has one stable column id'); + A(!/[+-]\d+$/.test(added[0][1]),'appended generated color is a base name, not a signed span neighbor'); + GEN_PROPOSAL={summary:{generated:1,rejected:0,minContrast:null},columns:[{name:'medium-aquamarine',members:[{name:'medium-aquamarine',hex:'#66cdaa',offset:0,columnId:'medium-aquamarine'}]}]}; + renderGeneratorPreview(); + A(document.querySelector('#genpreview .genchip .gn').textContent==='medium aquamarine','generated tile names display spaces instead of hyphens'); + }); +// Auto-dim gate (open with #autodimtest): the bespoke split preview shows the +// selected language in both panes -- the left in real syntax colors, the right +// collapsed to the single auto-dim-other-buffers face -- and tracks the langsel. +if(location.hash==='#autodimtest')gate('autodimtest',A=>{ + const langs=Object.keys(SAMPLES),ls=document.getElementById('langsel'); + ls.value=langs[0]; + const box=document.createElement('div');box.innerHTML=renderAutodimPreview(); + A(box.innerHTML.includes('>normal<'),'left pane has a "normal" header'); + A(box.innerHTML.includes('>auto-dim<'),'right pane has an "auto-dim" header'); + A(box.querySelectorAll('[data-k]').length>0,'focused pane carries per-token syntax spans'); + const dimFace=box.querySelector('[data-face="auto-dim-other-buffers"]'); + A(!!dimFace,'dimmed pane uses the auto-dim-other-buffers face'); + A(dimFace&&dimFace.querySelectorAll('[data-k]').length===0,'dimmed code is uniform, no per-token syntax'); + A(!!box.querySelector('[data-face="auto-dim-other-buffers-hide"]'),'the hide face is represented'); + if(langs.length>1){const t1=box.textContent;ls.value=langs[1]; + const box2=document.createElement('div');box2.innerHTML=renderAutodimPreview(); + A(box2.textContent!==t1,'preview tracks the language selector');ls.value=langs[0];} + }); if(location.hash.startsWith('#pick')){openPicker();const m=location.hash.slice(5);if(m){const b=document.querySelector('.pmode button[data-m="'+m+'"]');if(b)b.click();}} if(location.hash==='#cursortest'){document.getElementById('newhexstr').value='#67809c';openPicker();const sc=document.getElementById('svcur'),hc=document.getElementById('huecur');const L=parseFloat(sc.style.left||'0'),T=parseFloat(sc.style.top||'0'),H=parseFloat(hc.style.top||'0');const ok=L>1&&T>1&&H>1;document.title='CURSORTEST '+(ok?'PASS':'FAIL');const d=document.createElement('div');d.id='cursortest';d.textContent='CURSORTEST '+(ok?'PASS':'FAIL')+' left='+sc.style.left+' top='+sc.style.top+' hue='+hc.style.top;document.body.appendChild(d);} if(location.hash.startsWith('#app')){const ap=location.hash.slice(4),s=document.getElementById('appsel');if(s&&ap){s.value=ap;pkgChanged();}} -if(location.hash==='#deltatest'){const save=PALETTE.slice();let ok=true;const notes=[];const W=()=>document.getElementById('palwarn'); - PALETTE=[['#0d0b0a','ground'],['#cdced1','fg'],['#67809c','blue'],['#69829e','blue2']];renderPalette(); - const t1=W().textContent;if(!(W().style.display!=='none'&&/blue \/ blue2/.test(t1)&&/ΔE/.test(t1))){ok=false;notes.push('near-pair did not fire: '+t1);} - PALETTE=[['#0d0b0a','ground'],['#cdced1','fg'],['#67809c','blue'],['#e8bd30','gold'],['#cb6b4d','terra']];renderPalette(); - if(W().style.display!=='none'){ok=false;notes.push('spread palette warned: '+W().textContent);} - PALETTE=[['#0d0b0a','ground'],['#cdced1','fg']];for(let k=0;k<7;k++){const v=(0x67+k).toString(16).padStart(2,'0');PALETTE.push(['#'+v+'809c','c'+k]);}renderPalette(); - const tc=W().textContent;const nums=[...tc.matchAll(/ΔE (\d+\.\d+)/g)].map(m=>parseFloat(m[1])); - if(!/and \d+ more/.test(tc)){ok=false;notes.push('no cap suffix: '+tc);} - if(!(nums.length===5&&nums.every((n,k)=>k===0||n>=nums[k-1]))){ok=false;notes.push('not 5-capped ascending: '+nums.join(','));} - PALETTE=save;renderPalette(); - document.title='DELTATEST '+(ok?'PASS':'FAIL');const d=document.createElement('div');d.id='deltatest';d.textContent='DELTATEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} +if(location.hash==='#planetest'){let ok=true;const notes=[]; + document.getElementById('newhexstr').value='#67809c';openPicker();setPkModel('oklch');paintPicker(); + const sv=document.getElementById('sv'),cv=document.getElementById('svmask'),ctx=cv.getContext('2d'); + const [L,C,H]=readOklch(); + const expLeft=Math.min(1,C/OKLCH_CMAX)*sv.clientWidth,expTop=(1-L)*sv.clientHeight; + const gotLeft=parseFloat(document.getElementById('svcur').style.left),gotTop=parseFloat(document.getElementById('svcur').style.top); + if(Math.abs(gotLeft-expLeft)>2||Math.abs(gotTop-expTop)>2){ok=false;notes.push('crosshair off got '+gotLeft.toFixed(1)+','+gotTop.toFixed(1)+' exp '+expLeft.toFixed(1)+','+expTop.toFixed(1));} + const Coog=0.38,Loog=0.5,labO=oklch2oklab(Loog,Coog,H),oog=!inGamut(oklab2lrgb(labO.L,labO.a,labO.b)); + const oogX=Math.min(cv.width-2,Math.round((Coog/OKLCH_CMAX)*cv.width)),oogY=Math.round((1-Loog)*cv.height); + const dO=ctx.getImageData(oogX,oogY,1,1).data,greyO=Math.abs(dO[0]-0x15)<10&&Math.abs(dO[1]-0x12)<10&&Math.abs(dO[2]-0x0f)<10; + if(oog&&!greyO){ok=false;notes.push('OOG cell not masked rgb '+dO[0]+','+dO[1]+','+dO[2]);} + const inX=Math.round((0.03/OKLCH_CMAX)*cv.width),inY=Math.round(0.5*cv.height); + const dI=ctx.getImageData(inX,inY,1,1).data,greyI=Math.abs(dI[0]-0x15)<10&&Math.abs(dI[1]-0x12)<10&&Math.abs(dI[2]-0x0f)<10; + if(greyI){ok=false;notes.push('in-gamut cell rendered as OOG grey rgb '+dI[0]+','+dI[1]+','+dI[2]);} + document.title='PLANETEST '+(ok?'PASS':'FAIL');const d=document.createElement('div');d.id='planetest';d.textContent='PLANETEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} +if(location.hash==='#oklchtest'){let ok=true;const notes=[]; + document.getElementById('newhexstr').value='#67809c';openPicker(); + const before=document.getElementById('newhexstr').value; + setPkModel('oklch'); + if(pkModel!=='oklch'){ok=false;notes.push('model not oklch');} + if(!document.getElementById('oklchctl').classList.contains('show')){ok=false;notes.push('oklch dials hidden');} + if(document.getElementById('newhexstr').value!==before){ok=false;notes.push('color changed on model switch: '+document.getElementById('newhexstr').value);} + pkMode='any';document.querySelector('.pmode button[data-m="aa"]').click(); + if(pkModel!=='oklch'){ok=false;notes.push('mask toggle reset model');} + if(pkMode!=='aa'){ok=false;notes.push('mask did not set aa');} + setPkModel('hsv'); + if(pkMode!=='aa'){ok=false;notes.push('model switch reset mask to '+pkMode);} + if(pkModel!=='hsv'){ok=false;notes.push('model not hsv after switch');} + setPkModel('oklch');setOklchInputs(0.591,0.052,251.6);pkOklchSet(); + const driven=document.getElementById('newhexstr').value,dl=oklab2oklch(srgb2oklab(driven)); + if(!(Math.abs(dl.L-0.591)<0.02&&Math.abs(dl.C-0.052)<0.02)){ok=false;notes.push('dials did not drive color: '+driven);} + const {clamped}=oklch2hex(0.7,0.4,140);setOklchInputs(0.7,0.4,140);pkOklchSet(); + if(!(clamped&&document.getElementById('pkclamp').classList.contains('show'))){ok=false;notes.push('clamp status missing for out-of-gamut C');} + document.title='OKLCHTEST '+(ok?'PASS':'FAIL');const d=document.createElement('div');d.id='oklchtest';d.textContent='OKLCHTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} if(location.hash==='#readouttest'){const hex='#67809c';document.getElementById('newhexstr').value=hex;openPicker();pkReadout(hex); const o=document.getElementById('pkoklch').textContent,a=document.getElementById('pkapca').textContent,w=document.getElementById('pkcon').textContent; const lch=oklab2oklch(srgb2oklab(hex)); @@ -956,4 +3388,785 @@ if(location.hash==='#readouttest'){const hex='#67809c';document.getElementById(' const sane=Math.abs(lch.L-0.591)<0.01&&Math.abs(lch.C-0.052)<0.01&&Math.abs(lch.H-251.6)<2; const ok=wired&&sane;document.title='READOUTTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='readouttest';d.textContent='READOUTTEST '+(ok?'PASS':'FAIL')+' oklch='+o+' | apca='+a+' | wcag='+w;document.body.appendChild(d);} -</script>
\ No newline at end of file +// Worst-case readout gate (open with #contrasttest): a covered overlay face shows +// the floor over its foreground set and names the limiting foreground, an +// out-of-scope face keeps the single-pair readout, and an empty set reads "no fg set". +if(location.hash==='#contrasttest')gate('contrasttest',A=>{ + const saveMAP=Object.assign({},MAP),saveUI=JSON.parse(JSON.stringify(UIMAP)); + CATS.forEach(c=>{if(c[0]!=='bg'&&c[0]!=='p')setSyntaxFg(c[0],'');}); + setSyntaxFg('p','#f0fef0');setSyntaxFg('kw','#67809c');setSyntaxFg('str','#a3b18a');setSyntaxFg('bg','#000000'); + UIMAP['region']={fg:null,bg:'#202830',weight:null,slant:null,underline:null,strike:null}; + buildUITable(); + const cell=document.getElementById('uicr-region'); + A(cell&&/^\d+\.\d$/.test(cell.textContent.trim()),'region shows a bare worst-case number (no PASS/FAIL word): '+(cell&&cell.textContent)); + A(cell&&!cell.textContent.includes('#67809c'),'compact readout omits limiting fg details: '+(cell&&cell.textContent)); + A(cell&&cell.title.includes('kw (keyword) #67809c'),'hover names failing keyword blue: '+(cell&&cell.title)); + A(!document.querySelector('#uiprev-region .crerr'),'region preview no longer carries a failing-contrast badge'); + const firstFail=cell.title.split('\n')[1]; + A(firstFail&&firstFail.includes('kw (keyword) #67809c'),'failures are sorted from worst first (in the cell hover): '+firstFail); + const fl=floor('#202830',fgSetForFace('region').set); + A(fl.limitingHex==='#67809c','floor limiting is blue, got '+fl.limitingHex); + A(Math.abs(fl.ratio-contrast('#67809c','#202830'))<1e-9,'floor ratio matches blue-on-bg'); + UIMAP['region']={fg:'#f0fef0',bg:'#202830',weight:null,slant:null,underline:null,strike:null}; + buildUITable(); + const pairCell=document.getElementById('uicr-region'),pairWant=contrast('#f0fef0','#202830'); + A(pairCell&&Math.abs(parseFloat(pairCell.textContent)-pairWant)<0.06,'region with explicit fg rates its own fg/bg pair: got '+(pairCell&&pairCell.textContent.trim())+' want '+pairWant.toFixed(1)); + A(!document.querySelector('#uiprev-region .crerr'),'region with explicit fg does not show covered-text error badge'); + A(pairCell&&!pairCell.title.includes('#67809c'),'region explicit fg hover omits underlying syntax failures: '+(pairCell&&pairCell.title)); + const ml=document.getElementById('uicr-mode-line'); + A(worstCellHtml('mode-line')===null,'mode-line is out of scope (single-pair)'); + A(ml&&/^\d/.test(ml.textContent.trim()),'mode-line cell is a numeric ratio: '+(ml&&ml.textContent)); + UIMAP['region']={fg:null,bg:'#202830',weight:null,slant:null,underline:null,strike:null}; + setSyntaxFg('p','');CATS.forEach(c=>{if(c[0]!=='bg')setSyntaxFg(c[0],'');});buildUITable(); + const empty=document.getElementById('uicr-region'); + A(empty&&empty.textContent.trim()==='no fg set','empty set reads the no-set message: '+(empty&&empty.textContent)); + // A two-color face (own fg AND own bg) rates its own pair, never the ground bg. + UIMAP['mode-line']={fg:'#112233',bg:'#aabbcc',weight:null,slant:null,underline:null,strike:null}; + buildUITable(); + const two=document.getElementById('uicr-mode-line'),twoWant=contrast('#112233','#aabbcc'); + A(two&&Math.abs(parseFloat(two.textContent)-twoWant)<0.06,'ui two-color face rates own fg-on-bg: got '+(two&&two.textContent.trim())+' want '+twoWant.toFixed(1)); + const tApp=Object.keys(APPS)[0],tFace=APPS[tApp].faces[0][0],savePF=JSON.parse(JSON.stringify(PKGMAP[tApp][tFace])); + Object.assign(PKGMAP[tApp][tFace],{fg:'#112233',bg:'#aabbcc',inherit:null});buildPkgTable(); + const prow=document.querySelector('#pkgbody tr[data-face="'+tFace+'"]'),pcell=prow&&prow.children[6]; + A(pcell&&Math.abs(parseFloat(pcell.textContent)-twoWant)<0.06,'pkg two-color face rates own fg-on-bg: got '+(pcell&&pcell.textContent.trim())+' want '+twoWant.toFixed(1)); + PKGMAP[tApp][tFace]=savePF;buildPkgTable(); + // A ground-bg change must not clobber a face's own preview bg, must leave a + // two-color ratio alone, and must re-rate a ground-dependent face's cell. + UIMAP['fringe']={fg:'#ddeeff',bg:null,weight:null,slant:null,underline:null,strike:null}; + buildUITable(); + setSyntaxFg('bg','#440000');applyGround(); + const pv=document.getElementById('uiprev-mode-line'); + A(pv&&pv.style.background==='rgb(170, 187, 204)','ground change keeps a face own preview bg: got '+(pv&&pv.style.background)); + const twoAfter=document.getElementById('uicr-mode-line'); + A(twoAfter&&Math.abs(parseFloat(twoAfter.textContent)-twoWant)<0.06,'ground change leaves a two-color ratio alone: got '+(twoAfter&&twoAfter.textContent.trim())); + const frc=document.getElementById('uicr-fringe'),frWant=contrast('#ddeeff','#440000'); + A(frc&&Math.abs(parseFloat(frc.textContent)-frWant)<0.06,'ground change re-rates a ground-dependent face: got '+(frc&&frc.textContent.trim())+' want '+frWant.toFixed(1)); + // A default-fg (p) change through the real syntax dropdown re-rates a face + // whose fg falls back to it. Drives the DOM so the handler wiring is pinned. + UIMAP['fringe']={fg:null,bg:'#aabbcc',weight:null,slant:null,underline:null,strike:null}; + buildUITable(); + const pLocked=LOCKED.has('p');if(pLocked){LOCKED.delete('p');buildTable();} + const pdd=document.querySelector('#legbody tr[data-kind="p"] .cdd'); + if(pdd){pdd.click(); + const pHex=PALETTE.find(p=>p[0]!==MAP['p'])[0]; + const prow=[...document.querySelectorAll('.cddpop .cddgc')].find(c=>c.dataset.hex===pHex); + if(prow)prow.click(); + const pf=document.getElementById('uicr-fringe'),pfWant=contrast(pHex,'#aabbcc'); + A(prow&&pf&&Math.abs(parseFloat(pf.textContent)-pfWant)<0.06,'default-fg change re-rates a p-fallback face: got '+(pf&&pf.textContent.trim())+' want '+pfWant.toFixed(1)); + }else A(false,'syntax table has a p row with a dropdown'); + if(pLocked){LOCKED.add('p');buildTable();} + for(const k in MAP)delete MAP[k];Object.assign(MAP,saveMAP);syncSyntaxFromCache();for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveUI);buildUITable();applyGround(); + }); +// Bevel gate (open with #beveltest): released/pressed boxes derive their +// highlight and shadow from the face's effective bg per Emacs's relief +// algorithm, and pressed draws the shadow edge first. +if(location.hash==='#beveltest')gate('beveltest',A=>{ + const saveUI=JSON.parse(JSON.stringify(UIMAP)),saveP=PALETTE.slice(),savePK=JSON.parse(JSON.stringify(PKGMAP)); + UIMAP['mode-line']={fg:'#d8dee9',bg:'#30343c',weight:null,slant:null,underline:null,strike:null,box:{style:'released',width:1,color:null}}; + buildUITable(); + const pv=document.getElementById('uiprev-mode-line'); + const bs=pv&&pv.style.boxShadow; + A(bs&&bs.includes('rgb(113, 118, 127)'),'released highlight derives from the face bg (#71767f): '+bs); + A(bs&&bs.includes('rgb(15, 17, 22)'),'released shadow derives from the face bg (#0f1116): '+bs); + UIMAP['mode-line'].box={style:'pressed',width:1,color:null};paintUI('mode-line'); + const bs2=pv&&pv.style.boxShadow; + A(bs2&&bs2.includes('rgb(15, 17, 22)')&&bs2.includes('rgb(113, 118, 127)')&&bs2.indexOf('rgb(15, 17, 22)')<bs2.indexOf('rgb(113, 118, 127)'),'pressed swaps the pair (shadow edge first): '+bs2); + UIMAP['mode-line'].box={style:'line',width:1,color:'#ff0000'};paintUI('mode-line'); + A(pv&&pv.style.boxShadow.includes('rgb(255, 0, 0)'),'line style keeps its explicit color: '+(pv&&pv.style.boxShadow)); + UIMAP['mode-line'].box={style:'released',width:1,color:'#ff0000'};paintUI('mode-line'); + const bs3=pv&&pv.style.boxShadow; + A(bs3&&bs3.includes('rgb(255, 42, 42)')&&bs3.includes('rgb(143, 0, 0)'),'released style derives relief from explicit box color: '+bs3); + PALETTE=[['#ff0000','red','red'],['#30343c','slate','slate']]; + buildUITable(); + const mlrow=document.querySelector('#uibody tr[data-face="mode-line"]'),boxCell=mlrow&&mlrow.cells[5],lineBtn=boxCell&&boxCell.querySelector('.boxbtn[data-style="line"]'),boxDd=boxCell&&boxCell.querySelector('.cdd'); + if(lineBtn&&boxDd){lineBtn.click();boxDd.click();const redRow=[...document.querySelectorAll('.cddpop .cddgc')].find(c=>(c.dataset.name||'').includes('red'));if(redRow)redRow.click();} + A(UIMAP['mode-line'].box&&UIMAP['mode-line'].box.color==='#ff0000','UI box color dropdown writes box.color'); + const app=curApp(),face=APPS[app].faces[0][0];PKGMAP[app][face].box={style:'line',width:1,color:null};buildPkgTable(); + const prow=document.querySelector('#pkgbody tr[data-face="'+face+'"]'),pbox=prow&&prow.cells[5],pdd=pbox&&pbox.querySelector('.cdd'); + if(pdd){pdd.click();const redRow=[...document.querySelectorAll('.cddpop .cddgc')].find(c=>(c.dataset.name||'').includes('red'));if(redRow)redRow.click();} + A(PKGMAP[app][face].box&&PKGMAP[app][face].box.color==='#ff0000','package box color dropdown writes box.color'); + PALETTE=saveP;PKGMAP=savePK;for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveUI);buildUITable();buildPkgTable(); + }); +// Gallery gate (open with #gallerytest): the color dropdown opens a 2D grid in +// the palette-panel shape. Driven on a throwaway dropdown so no real face state +// is mutated. Covers: grid opens, every palette color has a cell, a cell click +// fires onPick + updates the trigger, the pick highlights on reopen, the default +// chip clears. +if(location.hash==='#gallerytest')gate('gallerytest',A=>withSavedState(['MAP','SYNTAX'],()=>{ + let picked='__none__'; + const dd=mkColorDropdown(ddList(''),'',(hex)=>{picked=hex;},{}); + document.body.appendChild(dd); + const trig=dd.querySelector('.cdd');trig.click(); + const pop=document.querySelector('.cddpop.cddgrid'); + A(pop,'dropdown opens a grid popup'); + const cells=pop?[...pop.querySelectorAll('.cddgc')]:[]; + A(cells.length>=PALETTE.length,'grid covers every palette color: '+cells.length+' cells for '+PALETTE.length+' palette colors'); + A(pop&&pop.querySelector('.cddgdef'),'grid has a default chip'); + A(pop&&pop.querySelector('.cddgrow'),'grid lays the colors out in rows'); + const target=PALETTE.find(p=>p[0]!==MAP['bg'])||PALETTE[0]; + const cell=cells.find(c=>c.dataset.hex===target[0]); + A(cell,'the target color has a cell'); + if(cell){cell.click(); + A(picked===target[0],'clicking a cell calls onPick with the color: '+picked); + A(trig.dataset.val===target[0],'the trigger button updates to the picked color: '+trig.dataset.val); + trig.click(); + const sel=document.querySelector('.cddpop.cddgrid .cddgc.sel'); + A(sel&&sel.dataset.hex===target[0],'the picked color is highlighted on reopen: '+(sel&&sel.dataset.hex)); + closeColorDropdown();} + trig.click();const defc=document.querySelector('.cddpop.cddgrid .cddgdef');if(defc)defc.click(); + A(picked==='','the default chip clears the assignment: '+JSON.stringify(picked)); + dd.remove();closeColorDropdown(); + })); +// Preview-link gate (open with #previewlinktest): known bespoke-preview face +// mappings stay wired to the face that Emacs actually uses. +if(location.hash==='#previewlinktest')gate('previewlinktest',A=>{ + const box=document.createElement('div'); + box.innerHTML=renderOrgPreview(); + const headline=[...box.querySelectorAll('[data-face="org-headline-todo"]')].find(e=>e.textContent.includes('Heading three')); + A(!!headline&&headline.previousElementSibling&&headline.previousElementSibling.dataset.face==='org-todo','org headline-todo follows a TODO keyword span'); + box.innerHTML=renderFlycheckPreview(); + const delim=[...box.querySelectorAll('[data-face="flycheck-error-delimiter"]')].map(e=>e.textContent).join(''); + const enclosed=[...box.querySelectorAll('[data-face="flycheck-delimited-error"]')].map(e=>e.textContent).join(''); + A(delim==='[]','flycheck delimiters use flycheck-error-delimiter'); + A(enclosed==='err','flycheck enclosed text uses flycheck-delimited-error'); + box.innerHTML=renderErcPreview(); + const own=[...box.querySelectorAll('[data-face="erc-input-face"]')].some(e=>e.textContent.includes('hello everyone')); + const bob=[...box.querySelectorAll('[data-face="erc-default-face"]')].some(e=>e.textContent.includes('hi craig')); + A(own,'erc own sent message uses erc-input-face'); + A(bob,'erc remote message uses erc-default-face'); + }); +// Safe-lightness gate (open with #safetest): the OKLCH picker shades the unsafe +// lightness band for a selected covered face and hides it when no face is selected. +if(location.hash==='#safetest')gate('safetest',A=>withSavedState(['MAP','SYNTAX'],()=>{ + const saveMAP=Object.assign({},MAP); + setSyntaxFg('p','#f0fef0');setSyntaxFg('kw','#67809c');setSyntaxFg('bg','#000000'); + document.getElementById('newhexstr').value='#202830';openPicker();setPkModel('oklch'); + setSafeFace('region'); + const band=document.getElementById('svsafe'); + A(band&&band.style.display==='block','safe band shows for an in-scope face'); + A(band&&parseFloat(band.style.height)>0,'safe band has a positive height: '+(band&&band.style.height)); + setSafeFace(''); + A(band&&band.style.display==='none','safe band hidden when no face is selected'); + for(const k in MAP)delete MAP[k];Object.assign(MAP,saveMAP);syncSyntaxFromCache(); + setPkModel('hsv');closePicker(); + })); +// Gone-rebind gate (open with #healtest): deleting a named color then recreating +// the name re-points face references stranded on the old hex to the new color. +if(location.hash==='#healtest')gate('healtest',A=>{ + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveU=JSON.parse(JSON.stringify(UIMAP)),savePK=JSON.parse(JSON.stringify(PKGMAP)),saveG=Object.assign({},lastGone),saveSel=selectedIdx; + PALETTE=[['#0d0b0a','ground'],['#cdced1','fg'],['#67809c','blue']];setSyntaxFg('kw','#67809c');lastGone={};selectedIdx=null;renderPalette();buildTable(); + const blue=[...document.querySelectorAll('#pals .pchip')].find(c=>c.querySelector('.nm')&&c.querySelector('.nm').value==='blue'); + A(!!(blue&&blue.querySelector('.rm')),'blue chip has a remove button'); + if(blue&&blue.querySelector('.rm'))blue.querySelector('.rm').click(); + A(!PALETTE.some(p=>p[1]==='blue'),'blue was deleted'); + A(lastGone['blue']==='#67809c','delete recorded the gone name->hex'); + document.getElementById('newhexstr').value='#5a7a9a';document.getElementById('newname').value='blue';selectedIdx=null;addColor(); + A(MAP['kw']==='#5a7a9a','face reference re-bound to the recreated name, got '+MAP['kw']); + A(!('blue' in lastGone),'heal consumed the gone entry'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);syncSyntaxFromCache();for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveU);PKGMAP=savePK;lastGone=saveG;selectedIdx=saveSel; + renderPalette();buildTable();buildUITable();if(document.getElementById('pkgbody'))buildPkgTable(); + }); +// Column-strip gate (open with #columntest): the palette renders as a pinned +// ground column plus structural columns, chips keep their controls, and renaming +// a color leaves it in the same strip because the column id is stable. +if(location.hash==='#columntest')gate('columntest',A=>{ + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveG=Object.assign({},lastGone),saveSel=selectedIdx; + setSyntaxFg('bg','#0d0b0a');setSyntaxFg('p','#f0fef0'); + PALETTE=[['#0d0b0a','ground'],['#f0fef0','fg'],['#c0402a','red'],['#3a6ea5','blue'],['#808080','gray']];selectedIdx=null;renderPalette(); + const strips=[...document.querySelectorAll('#pals .fstrip')]; + A(strips.length&&strips[0].dataset.column==='ground','ground column is pinned first'); + A(strips[0].querySelectorAll('.pchip').length===2,'ground column carries bg + fg endpoints'); + A(!!strips[0].querySelector('.fhead + .fcount + .pchip'),'span control sits between header and tiles for ground'); + A(strips.length>=4,'ground + red + blue + gray columns, got '+strips.length); + PALETTE=[['#3a6ea5','blue','blue']];setSyntaxFg('bg','#0d0b0a');setSyntaxFg('p','#f0fef0');selectedIdx=null;renderPalette(); + const fgChip=[...document.querySelectorAll('#pals .fstrip[data-column="ground"] .pchip')].find(c=>c.querySelector('.nm')&&c.querySelector('.nm').value==='fg'); + A(!!fgChip&&!fgChip.querySelector('.nm').disabled,'missing fg endpoint is normalized into a selectable real chip'); + if(fgChip)fgChip.click(); + A(selectedIdx!==null&&PALETTE[selectedIdx]&&PALETTE[selectedIdx][1]==='fg'&&document.getElementById('newhexstr').value.toLowerCase()==='#f0fef0','clicking normalized fg selects it and updates the color editor'); + PALETTE=[['#0d0b0a','ground'],['#f0fef0','fg'],['#c0402a','red'],['#3a6ea5','blue'],['#808080','gray']];selectedIdx=null;renderPalette(); + const resetStrips=[...document.querySelectorAll('#pals .fstrip')]; + const blueHead=resetStrips.find(s=>s.dataset.column==='blue')&&resetStrips.find(s=>s.dataset.column==='blue').querySelector('.ctitle'); + A(!!blueHead,'normal column header has a selectable title'); + if(blueHead)blueHead.click(); + A(selectedIdx!==null&&PALETTE[selectedIdx][1]==='blue'&&document.getElementById('newhexstr').value.toLowerCase()==='#3a6ea5','clicking a column title selects its base color'); + const blueRight=resetStrips.find(s=>s.dataset.column==='blue')&&resetStrips.find(s=>s.dataset.column==='blue').querySelector('.cmove.right'); + if(blueRight)blueRight.click(); + const moved=[...document.querySelectorAll('#pals .fstrip')].map(s=>s.dataset.column); + A(moved.indexOf('blue')>moved.indexOf('gray'),'right arrow moves a color column after its neighbor'); + A(!document.querySelector('#pals .fstrip[data-column="ground"] .cdel'),'ground column has no delete button'); + const redChip=[...document.querySelectorAll('#pals .pchip')].find(c=>c.querySelector('.nm')&&c.querySelector('.nm').value==='red'); + A(!!redChip&&!!redChip.querySelector('.rm')&&!!redChip.querySelector('.nm'),'a column chip keeps remove + rename controls'); + if(redChip){ + const redName=redChip.querySelector('.nm');selectedIdx=null;redName.click(); + A(selectedIdx!==null&&PALETTE[selectedIdx][1]==='red','single-clicking a tile name selects the whole tile'); + // Re-query the chip from the live DOM each time: selecting re-renders the + // palette, so a node captured earlier is detached and getComputedStyle on it + // returns "" (match -> null). Look it up by the current selection index. + const liveChip=()=>document.querySelector('#pals .pchip[data-palette-index="'+selectedIdx+'"]'); + const chipHex=chip=>{const m=chip&&getComputedStyle(chip).backgroundColor.match(/\d+/g);return m?rgb2hex(...m.slice(0,3).map(Number)):null;}; + openPicker();setHex('#00ff00'); + A(chipHex(liveChip())==='#00ff00','picker edits preview on the selected palette chip'); + closePicker(); + A(chipHex(liveChip())==='#c0402a'&&PALETTE[selectedIdx][0]==='#c0402a','closing picker restores selected chip without mutating palette'); + A(redName.readOnly===true&&!redName.classList.contains('editing'),'single-clicking a tile name does not enter name edit mode'); + redName.dispatchEvent(new MouseEvent('dblclick',{bubbles:true,cancelable:true})); + A(redName.readOnly===false&&redName.classList.contains('editing'),'double-clicking a tile name enters edit mode'); + A(redName.selectionStart===0&&redName.selectionEnd===0,'double-clicking places the cursor at the beginning of the name'); + redName.blur(); + } + const redColumn=redChip&&redChip.closest('.fstrip').dataset.column; + const ri=PALETTE.findIndex(p=>p[1]==='red');PALETTE[ri][1]='zztop-absurd';renderPalette(); + const renamed=[...document.querySelectorAll('#pals .pchip')].find(c=>c.querySelector('.nm')&&c.querySelector('.nm').value==='zztop-absurd'); + A(!!renamed&&renamed.closest('.fstrip').dataset.column===redColumn,'a renamed color stays in the same strip'); + PALETTE=[['#0d0b0a','bg','ground'],['#f0fef0','fg','ground'],['#0d0b0a','bg2'],['#0d0b0a','bg-alt']];setSyntaxFg('bg','#0d0b0a');setSyntaxFg('p','#f0fef0');selectedIdx=null;renderPalette(); + const bg2Chip=[...document.querySelectorAll('#pals .pchip')].find(c=>c.querySelector('.nm')&&c.querySelector('.nm').value==='bg2'); + A(!!bg2Chip&&bg2Chip.closest('.fstrip').dataset.column==='bg2'&&!!bg2Chip.querySelector('.rm')&&!bg2Chip.querySelector('.lock'),'same-hex bg2 remains a normal removable color column chip'); + if(bg2Chip){bg2Chip.click();document.getElementById('newhexstr').value='#101820';document.getElementById('newname').value='bg2';updateColor();} + A(MAP['bg']==='#0d0b0a','editing same-hex bg2 does not repoint the real bg assignment'); + A(PALETTE.some(p=>p[1]==='bg2'&&p[0]==='#101820'),'editing same-hex bg2 updates only that palette tile'); + PALETTE=[['#0d0b0a','bg','ground'],['#f0fef0','fg','ground'],['#c0402a','red','red'],['#3a6ea5','blue','blue'],['#92acc2','blue+1','blue'],['#808080','gray','gray']]; + setSyntaxFg('kw','#92acc2');lastGone={};selectedIdx=PALETTE.findIndex(p=>p[1]==='blue+1');renderPalette(); + const del=document.querySelector('#pals .fstrip[data-column="blue"] .cdel'); + A(!!del,'normal column has a delete button'); + const beforeDelete=PALETTE.map(p=>p.join('|')).join('||'),oldConfirm=window.confirm; + window.confirm=()=>false; + if(del)del.click(); + A(PALETTE.map(p=>p.join('|')).join('||')===beforeDelete,'canceling column delete leaves the palette unchanged'); + window.confirm=()=>true; + if(del)del.click(); + window.confirm=oldConfirm; + A(!PALETTE.some(p=>p[2]==='blue'),'column delete removes every entry with the stable column id'); + A(PALETTE.some(p=>p[1]==='red')&&PALETTE.some(p=>p[1]==='gray'),'column delete leaves neighboring columns alone'); + A(PALETTE.some(p=>groundRoleOfEntry(p,groundPair())==='bg')&&PALETTE.some(p=>groundRoleOfEntry(p,groundPair())==='fg'),'column delete leaves ground entries alone'); + A(MAP['kw']==='#92acc2','column delete leaves face references on removed hexes'); + buildTable(); + const goneTitle=document.querySelector('#legbody tr[data-kind="kw"] .cdd')?.title||''; + A(goneTitle==='(gone) #92acc2','gone color hover has one hex value: '+goneTitle); + A(lastGone['blue']==='#3a6ea5'&&lastGone['blue+1']==='#92acc2','column delete records every removed name for recovery'); + A(selectedIdx===null,'column delete clears selected color'); + PALETTE=[['#0d0b0a','bg','ground'],['#f0fef0','fg','ground'],['#c0402a','red','red'],['#3a6ea5','blue','blue'],['#92acc2','blue+1','blue']]; + setSyntaxFg('kw','#3a6ea5');selectedIdx=2;clearPalette(); + A(PALETTE.length===2&&PALETTE.every(p=>groundRoleOfEntry(p,groundPair())),'clear palette leaves only bg and fg tiles'); + A(!PALETTE.some(p=>p[1]==='red'||p[1]==='blue'||p[1]==='blue+1'),'clear palette removes normal color columns and spans'); + A(MAP['kw']==='#3a6ea5','clear palette leaves existing face references on gone hexes'); + A(lastGone['blue']==='#3a6ea5'&&lastGone['blue+1']==='#92acc2','clear palette records removed names for recovery'); + A(selectedIdx===null,'clear palette clears selected color'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);syncSyntaxFromCache();lastGone=saveG;selectedIdx=saveSel;renderPalette(); + }); +// Count-control gate (open with #counttest): the per-column count regenerates the +// column — count up adds symmetric steps, count down drops the extremes, a +// reference to a surviving step follows the new hex, a reference to a removed step +// is left on its old (now-gone) hex. +if(location.hash==='#counttest')gate('counttest',A=>{ + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveU=JSON.parse(JSON.stringify(UIMAP)),saveSel=selectedIdx; + paletteShowFull=true; // this gate asserts span tiles, so render the full palette + setSyntaxFg('bg','#204060');setSyntaxFg('p','#f0fef0'); + PALETTE=[['#204060','bg'],['#f0fef0','fg']]; + setGroundSpan(2); + A(MAP['bg']==='#204060'&&MAP['p']==='#f0fef0','spanning ground keeps bg/fg assignments on endpoints'); + A(PALETTE.some(p=>p[1]==='ground+1')&&PALETTE.some(p=>p[1]==='ground+2'),'spanning ground adds interior ground+N entries'); + A(document.querySelector('#pals .fstrip[data-column="ground"] .fhead + .fcount + .pchip'),'ground span control renders before tiles'); + setSyntaxFg('bg','#ffffff');setSyntaxFg('p','#000000'); + PALETTE=[['#ffffff','bg'],['#bbbbbb','ground+1','ground'],['#777777','ground+2','ground'],['#000000','fg']]; + renderPalette(); + const groundNames=[...document.querySelectorAll('#pals .fstrip[data-column="ground"] .pchip .nm')].map(e=>e.value); + A(groundNames.join('|')==='bg|ground+1|ground+2|fg','ground column order is bg, ground steps, fg even when bg is lighter: '+groundNames.join('|')); + setSyntaxFg('bg','#204060');setSyntaxFg('p','#f0fef0'); + setGroundSpan(1); + A(!PALETTE.some(p=>p[1]==='ground+2'),'lowering ground span removes dropped interior steps'); + PALETTE=[['#204060','bg'],['#f0fef0','fg'],['#e0e0e0','near-white','near-white']]; + setColumnCount('#e0e0e0',4); + A(!PALETTE.some(p=>p[0].toLowerCase()==='#ffffff'&&p[1]!=='fg'),'spanning a near-white base skips generated pure-white tiles'); + PALETTE=[['#204060','bg'],['#f0fef0','fg'],['#101010','near-black','near-black']]; + setColumnCount('#101010',4); + A(!PALETTE.some(p=>p[0].toLowerCase()==='#000000'&&p[1]!=='bg'),'spanning a near-black base skips generated pure-black tiles'); + PALETTE=[['#204060','bg'],['#f0fef0','fg']]; + regenColumn('#67809c',2,{ground:groundPair()}).members.forEach(m=>PALETTE.push([m.hex,m.offset===0?'blue':'blue'+(m.offset>0?'+'+m.offset:m.offset)])); + const innerOld=regenColumn('#67809c',2,{ground:groundPair()}).members.find(m=>m.offset===1).hex; // survives a count change + const outerOld=regenColumn('#67809c',2,{ground:groundPair()}).members.find(m=>m.offset===2).hex; // dropped on count-down + UIMAP['region']={fg:null,bg:innerOld,weight:null,slant:null,underline:null,strike:null}; + UIMAP['highlight']={fg:null,bg:outerOld,weight:null,slant:null,underline:null,strike:null}; + selectedIdx=null;renderPalette(); + const blueSpanInput=document.querySelector('#pals .fstrip[data-column="blue"] .fcount input'); + A(blueSpanInput&&blueSpanInput.max==='8','normal column span control allows up to 8 per side'); + setColumnCount('#67809c',1); + const palHexes=new Set(PALETTE.map(p=>p[0].toLowerCase())); + A(!palHexes.has(outerOld.toLowerCase()),'outer step removed from palette on count down'); + A(UIMAP['highlight'].bg.toLowerCase()===outerOld.toLowerCase(),'a removed-step reference stays on its old (gone) hex'); + const newInner=regenColumn('#67809c',1,{ground:groundPair()}).members.find(m=>m.offset===1).hex; + A(UIMAP['region'].bg.toLowerCase()===newInner.toLowerCase(),'a surviving-step reference followed the regenerate, got '+UIMAP['region'].bg); + setColumnCount('#67809c',3); + const want3=regenColumn('#67809c',3,{ground:groundPair()}).members.map(m=>m.hex.toLowerCase()); + const have=new Set(PALETTE.map(p=>p[0].toLowerCase())); + A(want3.every(h=>have.has(h)),'count up to 3 adds all 7 span colors to the palette'); + {const _lum=h=>{const n=parseInt(h.slice(1),16),r=(n>>16&255)/255,g=(n>>8&255)/255,b=(n&255)/255;const f=c=>c<=0.03928?c/12.92:((c+0.055)/1.055)**2.4;return 0.2126*f(r)+0.7152*f(g)+0.0722*f(b);}; + const lo=_lum(MAP['bg']),hi=_lum(MAP['p']),blue=PALETTE.filter(p=>p[2]==='blue').map(p=>_lum(p[0])); + A(blue.length&&blue.every(L=>L>=lo-1e-6&&L<=hi+1e-6),'generated span stays within the bg/fg bounds');} + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);syncSyntaxFromCache();for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveU);selectedIdx=saveSel;renderPalette(); + }); +// Base-edit + ground-edit gate (open with #baseedittest): editing a column base +// recolors the whole column at the same count and references follow; editing a +// ground swatch writes the bg/fg assignment. +if(location.hash==='#baseedittest')gate('baseedittest',A=>{ + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveU=JSON.parse(JSON.stringify(UIMAP)),saveSel=selectedIdx; + setSyntaxFg('bg','#0d0b0a');setSyntaxFg('p','#f0fef0'); + PALETTE=[['#0d0b0a','ground'],['#f0fef0','fg']]; + regenColumn('#67809c',2,{ground:groundPair()}).members.forEach(m=>PALETTE.push([m.hex,m.offset===0?'blue':'blue'+(m.offset>0?'+'+m.offset:m.offset)])); + UIMAP['region']={fg:null,bg:'#67809c',weight:null,slant:null,underline:null,strike:null}; + renderPalette();buildUITable(); + selectedIdx=PALETTE.findIndex(p=>p[0].toLowerCase()==='#67809c'); + document.getElementById('newhexstr').value='#3a8a8a';document.getElementById('newname').value='teal'; + updateColor(); + const column=columnsFromPalette(PALETTE,groundPair()).columns[0]; + A(column&&column.members.some(m=>m.hex.toLowerCase()==='#3a8a8a'),'column base recolored to the new hex'); + A(column&&column.members.length===5,'count preserved (±2 → 5 members), got '+(column&&column.members.length)); + A(!new Set(PALETTE.map(p=>p[0].toLowerCase())).has('#67809c'),'old base removed from palette'); + A(UIMAP['region'].bg.toLowerCase()==='#3a8a8a','a reference to the base followed to the new base hex'); + // ground edit: select bg, change hex, MAP.bg follows + selectedIdx=PALETTE.findIndex(p=>p[0].toLowerCase()==='#0d0b0a'); + document.getElementById('newhexstr').value='#101010';document.getElementById('newname').value='ground'; + updateColor(); + A(MAP['bg'].toLowerCase()==='#101010','editing the bg swatch wrote the bg assignment, got '+MAP['bg']); + // fg edit: even when a normal column shares the old fg hex, editing fg must not regenerate that column as fg-*. + setSyntaxFg('bg','#0d0b0a');setSyntaxFg('p','#e0e0e0'); + PALETTE=[['#0d0b0a','bg','ground'],['#e0e0e0','fg','ground'],['#c0c0c0','silver-1','silver'],['#e0e0e0','silver','silver'],['#f4f4f4','silver+1','silver']]; + selectedIdx=PALETTE.findIndex(p=>p[1]==='fg'); + document.getElementById('newhexstr').value='#d8d8d8';document.getElementById('newname').value='fg'; + updateColor(); + A(MAP['p'].toLowerCase()==='#d8d8d8','editing the fg swatch wrote the fg assignment, got '+MAP['p']); + A(PALETTE.some(p=>p[1]==='silver'&&p[2]==='silver'),'editing fg does not rename a same-hex normal column base'); + A(!PALETTE.some(p=>/^fg[+-]\d+$/.test(p[1])),'editing fg does not generate fg span tiles from a same-hex normal column'); + A(PALETTE.find(p=>p[1]==='fg')[2]==='ground','editing fg preserves the ground column id'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);syncSyntaxFromCache();for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveU);selectedIdx=saveSel;renderPalette(); + }); +// Round-trip gate (open with #roundtriptest): export stays a flat palette with +// stable column ids, and import does not need color-derived column reconstruction. +if(location.hash==='#roundtriptest')gate('roundtriptest',A=>{ + const stable=o=>Array.isArray(o)?o.map(stable):(o&&typeof o==='object'?Object.fromEntries(Object.keys(o).sort().map(k=>[k,stable(o[k])])):o); + const diff=(a,b,p='')=>{if(JSON.stringify(a)===JSON.stringify(b))return '';if(typeof a!==typeof b||!a||!b||typeof a!=='object')return p+': '+JSON.stringify(a)+' != '+JSON.stringify(b); + const ks=[...new Set([...Object.keys(a),...Object.keys(b)])].sort();for(const k of ks){const d=diff(a[k],b[k],p?p+'.'+k:k);if(d)return d;}return p;}; + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveL=new Set(LOCKED); + PALETTE=[['#ffffff','bg','ground'],['#000000','fg','ground'],['#224466','blue','blue'],['#446688','renamed-blue','blue']]; + setSyntaxFg('bg','#ffffff');setSyntaxFg('p','#000000'); + LOCKED=new Set(['kw','ui:region','pkg:'+curApp()+':'+APPS[curApp()].faces[0][0]]); + const before=JSON.stringify(exportObj()); + applyImported(before); + const after=JSON.stringify(exportObj()); + const bObj=stable(JSON.parse(before)),aObj=stable(JSON.parse(after)); + A(JSON.stringify(bObj)===JSON.stringify(aObj),'export → import → export is semantically stable: '+diff(bObj,aObj)); + const obj=JSON.parse(after); + A(Array.isArray(obj.palette)&&obj.palette.every(e=>Array.isArray(e)&&e.length>=3&&typeof e[2]==='string'),'exported palette carries flat [hex,name,columnId] entries'); + A(obj.palette.some(e=>e[1]==='renamed-blue'&&e[2]==='blue'),'renamed color keeps its stable column id through export/import'); + A(obj.locks&&obj.locks.includes('kw')&&obj.locks.includes('ui:region'),'lock state survives export/import'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);syncSyntaxFromCache();LOCKED=saveL; + }); +// View-selector gate (open with #viewtest): the assignment panel is driven by a +// single #viewsel dropdown -- two editor entries (@code, @ui) then a "package +// faces" optgroup of every app, alphabetically by label -- and switching it +// shows exactly one of the three view blocks. +if(location.hash==='#viewtest')gate('viewtest',A=>{ + const sel=document.getElementById('viewsel'); + A(!!sel,'viewsel-exists'); + if(sel){ + A(sel.options[0]&&sel.options[0].value==='@code','first-option-code'); + A(sel.options[1]&&sel.options[1].value==='@ui','second-option-ui'); + const og=sel.querySelector('optgroup'); + A(og&&og.label==='package faces','package-faces-optgroup'); + if(og){const appOpts=[...og.querySelectorAll('option')].map(o=>o.value); + A(JSON.stringify(appOpts)===JSON.stringify(appViewKeysSorted(APPS)),'optgroup-lists-apps-alphabetically');} + const vis=id=>{const e=document.getElementById(id);return !!e&&e.style.display!=='none';}; + sel.value='@code';onViewChange(); + A(vis('view-code')&&!vis('view-ui')&&!vis('view-pkg'),'code-view-only'); + sel.value='@ui';onViewChange(); + A(!vis('view-code')&&vis('view-ui')&&!vis('view-pkg'),'ui-view-only'); + const firstApp=Object.keys(APPS)[0];sel.value=firstApp;onViewChange(); + A(!vis('view-code')&&!vis('view-ui')&&vis('view-pkg'),'pkg-view-only'); + A(curApp()===firstApp,'curApp-returns-selected-app'); + A(!document.querySelector('#pkgbody .sbtn[title="reset to default"]'),'no-per-row-reset-button'); + } + }); +// Non-default-marker gate (open with #ndtest): a per-face setting cell gets the +// .nd corner flag only when its value differs from the face's seed default. Cell +// order in a pkg row: 0 lock, 1 label, 2 fg, 3 bg, 4 style, 5 box, 6 contrast. +// inherit + height live in the row expander, so a non-default height flags the +// expander toggle (exp-nd) rather than an inline cell. +if(location.hash==='#ndtest')gate('ndtest',A=>withSavedState(['PKGMAP','LOCKED'],()=>{ + LOCKED.clear(); + const app=curApp(),row=APPS[app].faces[0],face=row[0]; + PKGMAP[app][face]=seedFace(row[2]||{});buildPkgTable(); + const tr0=document.querySelector('#pkgbody tr[data-face="'+face+'"]'); + A(tr0&&![...tr0.cells].some(c=>c.classList.contains('nd')),'default-face-has-no-marker'); + PKGMAP[app][face].height=1.7;PKGMAP[app][face].source='user';buildPkgTable(); + const tr1=document.querySelector('#pkgbody tr[data-face="'+face+'"]'); + A(tr1.querySelector('.exptoggle').classList.contains('exp-nd'),'nondefault-height-flags-expander'); + A(!tr1.cells[4].classList.contains('nd'),'unchanged-style-box-stays-unmarked'); + PKGMAP[app][face].height=(row[2]&&row[2].height)||1;PKGMAP[app][face].weight=seedFace(row[2]||{}).weight==='bold'?null:'bold';buildPkgTable(); + const tr2=document.querySelector('#pkgbody tr[data-face="'+face+'"]'); + A(tr2.cells[4].classList.contains('nd'),'toggled-weight-marks-style-box'); + A(!tr2.querySelector('.exptoggle').classList.contains('exp-nd'),'restored-height-unflags-expander'); + PKGMAP[app][face]=seedFace(row[2]||{});buildPkgTable(); + })); +// Contrast-cell gate (open with #crtest): the per-face contrast column shows a +// bare colored number (no PASS/FAIL word); the WCAG verdict lives in the hover. +if(location.hash==='#crtest')gate('crtest',A=>{ + const app=curApp(),face=APPS[app].faces[0][0];buildPkgTable(); + const cell=document.querySelector('#pkgbody tr[data-face="'+face+'"]').cells[6]; + const span=cell&&cell.querySelector('span'); + A(span&&/^\d+\.\d$/.test(span.textContent.trim()),'contrast cell is a bare number: '+(span&&span.textContent)); + A(span&&!/PASS|FAIL/.test(span.textContent),'no PASS/FAIL word in the contrast cell'); + A(span&&span.title&&/(passes|fails) WCAG/i.test(span.title),'contrast cell carries a WCAG hover: '+(span&&span.title)); + }); +// View-nav gate (open with #navtest): the prev/next arrows flanking the view +// dropdown step the selection (clamped, no wrap) and re-render the view. +if(location.hash==='#navtest')gate('navtest',A=>{ + const sel=document.getElementById('viewsel'),prev=document.getElementById('viewprev'),next=document.getElementById('viewnext'); + A(!!prev&&!!next,'nav arrows exist'); + if(sel&&prev&&next){ + const vis=id=>{const e=document.getElementById(id);return !!e&&e.style.display!=='none';}; + sel.selectedIndex=0;onViewChange(); + next.click();A(sel.selectedIndex===1,'next advances the selection'); + prev.click();A(sel.selectedIndex===0,'prev steps back'); + prev.click();A(sel.selectedIndex===0,'prev clamps at the first option'); + sel.selectedIndex=sel.options.length-1;onViewChange(); + next.click();A(sel.selectedIndex===sel.options.length-1,'next clamps at the last option'); + sel.selectedIndex=2;onViewChange(); + A(sel.options[2]&&sel.options[2].value[0]!=='@'&&vis('view-pkg'),'stepping to a package shows the pkg view'); + } + }); +// Markdown-preview gate (open with #mdtest): markdown-mode has a dedicated README +// renderer, and every data-face it emits is a real markdown-mode face. +if(location.hash==='#mdtest')gate('mdtest',A=>{ + A(APPS['markdown-mode']&&APPS['markdown-mode'].preview==='markdown','markdown-mode wired to the markdown preview'); + A(!!PACKAGE_PREVIEWS['markdown'],'markdown renderer registered'); + if(PACKAGE_PREVIEWS['markdown']&&APPS['markdown-mode']){ + assertPreviewFaces(A, PACKAGE_PREVIEWS['markdown'](), APPS['markdown-mode'].faces, 15, 'markdown', + ['markdown-header-face-1','markdown-bold-face','markdown-inline-code-face','markdown-blockquote-face','markdown-gfm-checkbox-face','markdown-table-face']); + } + }); +// mu4e-preview gate (open with #mupreviewtest): the mu4e preview is a realistic +// headers list + message view, and every data-face it emits is a real mu4e face. +if(location.hash==='#mupreviewtest')gate('mupreviewtest',A=>{ + assertPreviewFaces(A, renderMu4ePreview(), APPS['mu4e']&&APPS['mu4e'].faces, 20, 'mu4e', + ['mu4e-unread-face','mu4e-flagged-face','mu4e-replied-face','mu4e-draft-face','mu4e-trashed-face','mu4e-header-highlight-face','mu4e-header-marks-face','mu4e-contact-face','mu4e-compose-separator-face']); + }); +// gnus-preview gate (open with #gnustest): gnus is its own view package (it drives +// the mu4e article view), and every data-face its preview emits is a real gnus face. +if(location.hash==='#gnustest')gate('gnustest',A=>{ + A(!!APPS['gnus'],'gnus is a registered view package'); + A(APPS['gnus']&&APPS['gnus'].preview==='gnus','gnus uses the gnus preview renderer'); + assertPreviewFaces(A, renderGnusPreview(), APPS['gnus']&&APPS['gnus'].faces, 20, 'gnus', + ['gnus-header-name','gnus-header-from','gnus-header-subject','gnus-cite-1','gnus-cite-attribution','gnus-signature','gnus-button','gnus-emphasis-highlight-words']); + }); +// picker-distinct gate (open with #pickertest): the color picker panel must stand +// out from the page background. It carries a highlighted gold accent border, and its +// background is meaningfully lighter than the body so the two are easy to tell apart. +if(location.hash==='#pickertest')gate('pickertest',A=>{ + const pk=document.getElementById('picker');A(!!pk,'picker element exists'); + if(pk){const cs=getComputedStyle(pk),body=getComputedStyle(document.body); + const bc=(cs.borderTopColor.match(/\d+/g)||[]).slice(0,3).map(Number); + const pkbg=(cs.backgroundColor.match(/\d+/g)||[]).slice(0,3).map(Number); + const bdbg=(body.backgroundColor.match(/\d+/g)||[]).slice(0,3).map(Number); + A(bc.join(',')==='232,189,48','picker carries the gold accent border (got '+cs.borderTopColor+')'); + const lift=pkbg.map((c,i)=>c-bdbg[i]); + A(lift.every(d=>d>=12),'picker background is clearly lighter than the page (per-channel lift '+lift.join(',')+')'); + } + }); +// Box-cluster gate (open with #boxtest): the box control is a 2x2 cluster of +// four radio buttons (none / line / pressed / raised); the color swatch shows +// only while a box style is active. +if(location.hash==='#boxtest')gate('boxtest',A=>{ + LOCKED.clear();const f=UI_FACES[0][0];const saveBox=UIMAP[f].box; + UIMAP[f].box=null;buildUITable(); + const cell=document.querySelector('#uibody tr[data-face="'+f+'"]').cells[5]; + A(!!cell.querySelector('.boxcluster'),'box-cluster-present'); + A(cell.querySelectorAll('.boxbtn').length===4,'four-box-buttons'); + const dd=cell.querySelector('.cstep'); + A(dd&&dd.style.display==='none','color-hidden-when-no-box'); + const lineBtn=cell.querySelector('.boxbtn[data-style="line"]');lineBtn.click(); + A(UIMAP[f].box&&UIMAP[f].box.style==='line','line-click-sets-style'); + A(lineBtn.classList.contains('on'),'line-button-active'); + A(dd.style.display!=='none','color-shown-when-box-active'); + cell.querySelector('.boxbtn[data-style=""]').click(); + A(UIMAP[f].box===null,'blank-click-clears-box'); + A(dd.style.display==='none','color-hidden-again-after-clear'); + UIMAP[f].box=saveBox;buildUITable(); + }); +// Style-cluster gate (open with #styletest): the style cell holds a weight +// selector, a slant selector, and box-like underline and strike controls. +if(location.hash==='#styletest')gate('styletest',A=>{ + buildUITable();const f=UI_FACES[0][0]; + const cell=document.querySelector('#uibody tr[data-face="'+f+'"]').cells[4]; + const cluster=cell.querySelector('.stylecluster'); + A(!!cluster,'style-cluster-present'); + const dds=cluster?cluster.querySelectorAll('.enumdd'):[]; + A(dds.length===2,'weight-and-slant-custom-dropdowns-present'); + dds[0]&&dds[0].click(); + const wopts=_ddPop?[..._ddPop.querySelectorAll('.enumopt')]:[]; + A(wopts.some(b=>b.textContent==='semibold'),'weight-dropdown-spells-out-the-curated-range: '+wopts.map(b=>b.textContent).join(',')); + const wbold=wopts.find(b=>b.textContent==='bold'); + A(wbold&&wbold.style.fontWeight==='700','weight-options-preview-their-own-weight: bold renders 700, got '+(wbold&&wbold.style.fontWeight)); + closeColorDropdown(); + dds[1]&&dds[1].click(); + const sopts=_ddPop?[..._ddPop.querySelectorAll('.enumopt')]:[]; + A(sopts.some(b=>b.textContent==='oblique'),'slant-dropdown-offers-oblique: '+sopts.map(b=>b.textContent).join(',')); + const sital=sopts.find(b=>b.textContent==='italic'); + A(sital&&sital.style.fontStyle==='italic','slant-options-preview-their-own-slant: italic renders italic'); + closeColorDropdown(); + A(cluster&&cluster.querySelectorAll('.boxctl').length===1,'strike-control-in-row-underline-moved-to-expander'); + }); +// Expander gate (open with #expandtest): the per-row "more" toggle reveals a +// detail row with the overflow attribute editor, and its controls write the model. +if(location.hash==='#expandtest')gate('expandtest',A=>{ + buildUITable(); + const row=document.querySelector('#uibody tr[data-face="region"]'); + const detail=document.querySelector('#uibody tr.detailrow[data-detail-for="region"]'); + A(!!detail,'detail-row-present'); + A(detail&&detail.style.display==='none','detail-row-hidden-by-default'); + const btn=row.querySelector('.exptoggle'); + A(!!btn,'expander-toggle-present'); + btn&&btn.click(); + A(detail&&detail.style.display!=='none','toggle-reveals-detail-row'); + const ed=detail&&detail.querySelector('.detailedit'); + A(ed&&ed.querySelectorAll('.detailfield').length>=6,'detail-editor-has-the-overflow-fields'); + // ui faces also expose inherit + height in the expander + A(ed&&ed.querySelector('select.detailsel'),'ui-expander-offers-inherit'); + A(ed&&ed.querySelector('input.hstep'),'ui-expander-offers-height'); + // underline moved into the expander; its wave style writes a styled object + const uiUnder=ed&&ed.querySelector('.boxctl .boxbtn[data-style="wave"]'); + A(!!uiUnder,'underline-control-in-expander'); + uiUnder&&uiUnder.click(); + A(UIMAP['region'].underline&&UIMAP['region'].underline.style==='wave','underline-control-writes-a-wavy-object'); + // family text input writes the model + const fam=ed&&ed.querySelector('input.detailinput'); + if(fam){fam.value='Iosevka';fam.dispatchEvent(new Event('change'));} + A(UIMAP['region'].family==='Iosevka','family-input-writes-the-model'); + // inverse checkbox writes the model + const inv=ed&&ed.querySelector('input.detailcheck'); + if(inv){inv.checked=true;inv.dispatchEvent(new Event('change'));} + A(UIMAP['region'].inverse===true,'inverse-checkbox-writes-the-model'); + // a hidden non-default attribute flags the collapsed toggle (reset region to its + // default first, since the edits above left several overflow attrs changed) + UIMAP['region']=JSON.parse(JSON.stringify(DEFAULT_UIMAP['region']));buildUITable(); + const cleanbtn=document.querySelector('#uibody tr[data-face="region"] .exptoggle'); + A(cleanbtn&&!cleanbtn.classList.contains('exp-nd'),'toggle-unflagged-when-overflow-matches-default'); + UIMAP['region']=JSON.parse(JSON.stringify(DEFAULT_UIMAP['region']));UIMAP['region'].overline={color:null};buildUITable(); + const ndbtn=document.querySelector('#uibody tr[data-face="region"] .exptoggle'); + A(ndbtn&&ndbtn.classList.contains('exp-nd'),'collapsed-toggle-flags-a-hidden-non-default-attr'); + // package expander now exposes inherit + height (folded out of inline columns) + buildPkgTable();const pface=APPS[curApp()].faces[0][0]; + const pdetail=document.querySelector('#pkgbody tr.detailrow[data-detail-for="'+pface+'"]'); + A(pdetail&&pdetail.querySelector('select.detailsel'),'package-expander-offers-inherit'); + }); +// Height-clamp gate (open with #heighttest): the expander height field coerces a +// typed value into [HEIGHT_MIN,HEIGHT_MAX] and writes the clamped number back, so +// an out-of-range type/paste can't reach the model. Guards the fact that an +// <input type=number> min/max only constrain its steppers, never typed text. +if(location.hash==='#heighttest')gate('heighttest',A=>{ + const face=UI_FACES[0][0],save=JSON.parse(JSON.stringify(UIMAP[face])); + buildUITable(); + const hin=()=>document.querySelector('#uibody tr.detailrow[data-detail-for="'+face+'"] .hstep'); + const typeHeight=(v)=>{const h=hin();h.value=v;h.dispatchEvent(new Event('change'));}; + typeHeight('5'); + A(UIMAP[face].height===HEIGHT_MAX,'above-max-clamps-to-ceiling: '+UIMAP[face].height); + A(hin().value===''+HEIGHT_MAX,'field-shows-the-clamped-ceiling: '+hin().value); + typeHeight('0.05'); + A(UIMAP[face].height===HEIGHT_MIN,'below-floor-clamps-to-floor: '+UIMAP[face].height); + typeHeight('1.2'); + A(UIMAP[face].height===1.2,'in-range-value-passes-through: '+UIMAP[face].height); + typeHeight(''); + A(UIMAP[face].height===null,'blank-unsets-to-null: '+UIMAP[face].height); + UIMAP[face]=save;buildUITable(); + }); +// Language-dropdown gate (open with #langtest): the language list is sorted +// alphabetically with Elisp pinned as the default selection, and the ‹ › arrows +// step the selection (clamped, no wrap). +if(location.hash==='#langtest')gate('langtest',A=>{ + buildLangSel(); + const s=document.getElementById('langsel'); + const labels=[...s.options].map(o=>o.value); + const sorted=[...labels].sort((a,b)=>a.localeCompare(b)); + A(JSON.stringify(labels)===JSON.stringify(sorted),'languages are alphabetical: '+labels.join(',')); + A(s.value==='Elisp','Elisp is the default selection: '+s.value); + s.selectedIndex=0;stepLang(-1); + A(s.selectedIndex===0,'prev clamps at the first language'); + stepLang(1); + A(s.selectedIndex===1,'next steps forward one'); + s.selectedIndex=s.options.length-1;stepLang(1); + A(s.selectedIndex===s.options.length-1,'next clamps at the last language'); + }); +// View-lock-indicator gate (open with #viewlocktest): the view dropdown prefixes a +// lock glyph on a view whose every element is locked, and clears it otherwise. +if(location.hash==='#viewlocktest')gate('viewlocktest',A=>withSavedState(['LOCKED'],()=>{ + LOCKED.clear();updateViewLockIndicators(); + const s=document.getElementById('viewsel'),codeOpt=()=>[...s.options].find(o=>o.value==='@code'); + A(codeOpt()&&!codeOpt().textContent.startsWith('🔒'),'unlocked view shows no lock glyph: '+(codeOpt()&&codeOpt().textContent)); + syntaxLockKeys().forEach(k=>LOCKED.add(k));updateViewLockIndicators(); + A(codeOpt()&&codeOpt().textContent.startsWith('🔒'),'fully-locked view shows the lock glyph: '+(codeOpt()&&codeOpt().textContent)); + A(codeOpt()&&codeOpt().textContent.includes('color/code assignments'),'glyph prefixes the base label, not replaces it'); + LOCKED.delete(syntaxLockKeys()[0]);updateViewLockIndicators(); + A(codeOpt()&&!codeOpt().textContent.startsWith('🔒'),'unlocking one element clears the glyph'); + LOCKED.clear();updateViewLockIndicators(); + })); +// Detail-hover gate (open with #detailhovertest): every label in the expander +// detail row carries an explanatory hover, the way the table-header labels do. +if(location.hash==='#detailhovertest')gate('detailhovertest',A=>{ + buildUITable(); + const f=UI_FACES[0][0],detail=document.querySelector('#uibody tr.detailrow[data-detail-for="'+f+'"]'); + const fields=detail?[...detail.querySelectorAll('.detailfield')]:[]; + A(fields.length>0,'detail row has fields'); + A(fields.every(g=>g.title&&g.title.length>0),'every detail field has a hover: '+fields.map(g=>g.querySelector('span').textContent+(g.title?'+':'-')).join(' ')); + const inh=fields.find(g=>g.querySelector('span').textContent==='inherit'); + A(inh&&/inherit/i.test(inh.title),'inherit field hover mentions inheritance: '+(inh&&inh.title)); + }); +// Expand/collapse-all gate (open with #expandalltest): the header toggle opens or +// closes every row's detail at once, the per-row triangles track state (▶ closed, +// ▼ open), and the header button's label follows the aggregate. +if(location.hash==='#expandalltest')gate('expandalltest',A=>{ + buildUITable(); + const tb=document.getElementById('uibody'),btn=document.getElementById('uiexpandall'); + const details=()=>[...tb.querySelectorAll('tr.detailrow')]; + const open=()=>details().filter(d=>d.style.display!=='none').length; + const firstTog=()=>tb.querySelector('.exptoggle'); + A(firstTog()&&firstTog().textContent==='▶','row toggle starts collapsed (▶): '+(firstTog()&&firstTog().textContent)); + A(btn&&btn.textContent.indexOf('▶')===0&&/expand all/.test(btn.textContent),'button starts ▶ expand all: '+(btn&&btn.textContent)); + toggleAllExpanded('uiexpandall'); + A(open()===details().length&&open()>0,'expand all opens every row: '+open()+'/'+details().length); + A(firstTog().textContent==='▼','row toggles flip to ▼ after expand all'); + A(btn.textContent.indexOf('▼')===0&&/collapse all/.test(btn.textContent),'button flips to ▼ collapse all: '+btn.textContent); + toggleAllExpanded('uiexpandall'); + A(open()===0,'collapse all closes every row'); + A(firstTog().textContent==='▶','row toggles return to ▶ after collapse all'); + firstTog().click(); + A(open()===1,'a single row toggle opens just that row'); + A(btn.textContent.indexOf('▼')===0,'button reflects a single open row as ▼ collapse all'); + }); +// Expander-persistence gate (open with #expandpersisttest): a package edit rebuilds +// the whole table, so an open expander must reopen instead of collapsing under the +// user. Editing a value inside the open expander must not close the row. +if(location.hash==='#expandpersisttest')gate('expandpersisttest',A=>withSavedState(['PKGMAP'],()=>{ + EXPANDED.clear(); + const app=curApp(),face=APPS[app].faces[0][0];buildPkgTable(); + const row=()=>document.querySelector('#pkgbody tr[data-face="'+face+'"]'); + const detail=()=>document.querySelector('#pkgbody tr.detailrow[data-detail-for="'+face+'"]'); + A(detail()&&detail().style.display==='none','expander starts collapsed'); + row().querySelector('.exptoggle').click(); + A(detail()&&detail().style.display!=='none','expander opens on toggle'); + const hin=detail().querySelector('.hstep');hin.value='1.4';hin.dispatchEvent(new Event('change')); + A(detail()&&detail().style.display!=='none','expander stays open after an in-expander edit rebuilds the row'); + A(PKGMAP[app][face].height===1.4,'the in-expander edit still wrote the model'); + row().querySelector('.exptoggle').click();buildPkgTable(); + A(detail()&&detail().style.display==='none','a collapsed expander stays collapsed across a rebuild'); + EXPANDED.clear();buildPkgTable(); + })); +// Palette default-state gate (open with #paldefaulttest): the studio opens with +// the palette collapsed to base colors so the span tints don't crowd the first +// view. initApp() ran at page load, so the live toggle reflects the opening state. +if(location.hash==='#paldefaulttest')gate('paldefaulttest',A=>{ + const tg=document.getElementById('paltoggle'); + A(!!tg,'palette toggle present after boot'); + A(tg&&tg.textContent==='▶','palette opens collapsed to base colors (arrow shows right-pointing ▶)'); + }); +// Palette display-toggle gate (open with #paltoggletest): the arrow control +// collapses each column to its base color and expands back to full spans. +if(location.hash==='#paltoggletest')gate('paltoggletest',A=>{ + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP); + paletteShowFull=true; // start expanded so the first click collapses to base-only + setSyntaxFg('bg','#101010');setSyntaxFg('p','#f0f0f0'); + PALETTE=[['#101010','bg','ground'],['#f0f0f0','fg','ground']]; + regenColumn('#67809c',2,{ground:groundPair()}).members.forEach(m=>PALETTE.push([m.hex,m.offset===0?'blue':'blue'+(m.offset>0?'+'+m.offset:m.offset),'blue'])); + renderPalette(); + const tg=document.getElementById('paltoggle'); + A(!!tg,'palette-toggle-present'); + const blueChips=()=>document.querySelectorAll('#pals .fstrip[data-column="blue"] .pchip').length; + A(blueChips()===5,'full-mode-shows-base-and-spans'); + tg.click(); + A(blueChips()===1,'base-only-shows-just-the-base'); + A(document.getElementById('paltoggle').textContent==='▶','base-only-shows-right-arrow'); + document.getElementById('paltoggle').click(); + A(blueChips()===5,'toggling-back-restores-spans'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);syncSyntaxFromCache();renderPalette(); + }); +// Unused-tile gate (open with #unusedtest): a palette color referenced nowhere +// in the theme gets the .unused flag; a column with no used members gets +// .unused-col; referenced colors stay unflagged. +if(location.hash==='#unusedtest')gate('unusedtest',A=>{ + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveSyn=JSON.parse(JSON.stringify(SYNTAX)),saveU=JSON.parse(JSON.stringify(UIMAP)); + setSyntaxFg('bg','#101010');setSyntaxFg('p','#f0f0f0'); + PALETTE=[['#101010','bg','ground'],['#f0f0f0','fg','ground'],['#67809c','blue','blue'],['#123456','teal','teal']]; + for(const f in UIMAP)UIMAP[f]={fg:null,bg:null,weight:null,slant:null,underline:null,strike:null}; + setSyntaxFg('kw','#67809c'); + renderPalette(); + const tealStrip=document.querySelector('#pals .fstrip[data-column="teal"]'); + const blueStrip=document.querySelector('#pals .fstrip[data-column="blue"]'); + const tealChip=tealStrip&&tealStrip.querySelector('.pchip'); + const blueChip=blueStrip&&blueStrip.querySelector('.pchip'); + A(tealChip&&tealChip.classList.contains('unused'),'unreferenced-tile-flagged'); + A(blueChip&&!blueChip.classList.contains('unused'),'referenced-tile-not-flagged'); + A(tealStrip&&tealStrip.classList.contains('unused-col'),'all-unused-column-flagged'); + A(blueStrip&&!blueStrip.classList.contains('unused-col'),'used-column-not-flagged'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);for(const k in SYNTAX)delete SYNTAX[k];Object.assign(SYNTAX,saveSyn);for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveU);syncSyntaxFromCache();renderPalette(); + }); +// Gone-assignment gate (open with #gonetest): a swatch whose assigned color is +// no longer in the palette gets the .gone flag; an assignment to a present color +// does not. +if(location.hash==='#gonetest')gate('gonetest',A=>{ + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveU=JSON.parse(JSON.stringify(UIMAP)); + setSyntaxFg('bg','#101010');setSyntaxFg('p','#f0f0f0'); + PALETTE=[['#101010','bg','ground'],['#f0f0f0','fg','ground'],['#67809c','blue','blue']]; + UIMAP['region']={fg:null,bg:'#deadbe',weight:null,slant:null,underline:null,strike:null}; + UIMAP['highlight']={fg:null,bg:'#67809c',weight:null,slant:null,underline:null,strike:null}; + buildUITable(); + const goneDd=document.querySelector('#uibody tr[data-face="region"]').cells[3].querySelector('.cdd'); + const okDd=document.querySelector('#uibody tr[data-face="highlight"]').cells[3].querySelector('.cdd'); + A(goneDd&&goneDd.classList.contains('gone'),'assignment-to-missing-color-flagged'); + A(okDd&&!okDd.classList.contains('gone'),'assignment-to-present-color-not-flagged'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveU);syncSyntaxFromCache();buildUITable(); + }); +// Tile-usage-hover gate (open with #usagetest): a tile's title lists the +// "view area > element" pairings that use its color, under the name/hex line. +if(location.hash==='#usagetest')gate('usagetest',A=>{ + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveU=JSON.parse(JSON.stringify(UIMAP)); + setSyntaxFg('bg','#101010');setSyntaxFg('p','#f0f0f0'); + PALETTE=[['#101010','bg','ground'],['#f0f0f0','fg','ground'],['#67809c','blue','blue']]; + const f0=UI_FACES[0][0],f0label=UI_FACES[0][1]||f0; + for(const f in UIMAP)UIMAP[f]={fg:null,bg:null,weight:null,slant:null,underline:null,strike:null}; + UIMAP[f0]={fg:null,bg:'#67809c',weight:null,slant:null,underline:null,strike:null}; + renderPalette(); + const blueChip=document.querySelector('#pals .fstrip[data-column="blue"] .pchip'); + A(blueChip&&blueChip.title.includes('ui faces > '+f0label),'hover-title-lists-ui-face-usage'); + A(blueChip&&blueChip.title.split('\n').length>1,'usage-list-on-its-own-line-under-current-info'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveU);syncSyntaxFromCache();renderPalette(); + }); +// Element-docstring hovers (open with #hovertest): each table's category cell +// carries the face's Emacs docstring on top of its prior hover text, and the +// existing label-span hints are left intact (added in addition, not replaced). +if(location.hash==='#hovertest')gate('hovertest',A=>{ + buildTable();buildUITable();buildPkgTable(); + const synCell=document.querySelector('#legbody tr[data-kind="kw"] .cat'); + A(synCell&&synCell.title===SYNTAX_DOCS['kw'],'syntax cat cell shows the category face docstring: '+(synCell&&synCell.title)); + const synLbl=document.querySelector('#legbody tr[data-kind="kw"] .cat span'); + A(synLbl&&synLbl.title==='flash this category in the code','syntax label-span hint left intact'); + const uiCell=document.querySelector('#uibody tr[data-face="mode-line"] .cat'); + A(uiCell&&uiCell.title===FACE_DOCS['mode-line'],'ui cat cell shows the face docstring: '+(uiCell&&uiCell.title)); + const app=curApp(),docFace=APPS[app].faces.map(r=>r[0]).find(f=>FACE_DOCS[f]); + A(docFace,'a package face with a docstring exists to test'); + if(docFace){const pkgCell=document.querySelector('#pkgbody tr[data-face="'+docFace+'"] .cat'); + A(pkgCell&&pkgCell.title===FACE_DOCS[docFace]+'\n\n'+docFace,'package cat cell shows docstring on top of the face name: '+(pkgCell&&JSON.stringify(pkgCell.title)));} + }); +// Export via the File System Access API (open with #savetest): exportTheme writes +// the theme JSON straight to the picked file handle and closes it, so re-exporting +// overwrites in place instead of the browser uniquifying to "name (1).json". +if(location.hash==='#savetest'){(async()=>{let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + let written='',closed=false,pickerArgs=null; + const orig=window.showSaveFilePicker; + window.showSaveFilePicker=async(opts)=>{pickerArgs=opts;return {name:'WIP.json',createWritable:async()=>({write:async d=>{written+=d;},close:async()=>{closed=true;}})};}; + try{ + await exportTheme(); + A(written===JSON.stringify(exportObj(),null,1),'export writes the theme JSON to the picked file'); + A(closed,'writable stream is closed so the file is committed'); + A(pickerArgs&&/\.json$/.test(pickerArgs.suggestedName||''),'picker suggests a .json name: '+(pickerArgs&&pickerArgs.suggestedName)); + }catch(e){A(false,'exportTheme threw: '+e.message);} + finally{window.showSaveFilePicker=orig;} + document.title='SAVETEST '+(ok?'PASS':'FAIL'); + const d=document.createElement('div');d.id='savetest';d.textContent='SAVETEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);})();} +</script> |
