diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-14 18:14:29 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-14 18:14:29 -0500 |
| commit | 214c16fe127e007965b21d38d0c9c24f8c995b4c (patch) | |
| tree | b4a54af6a928122c3f8af374618b07da5fc798c8 | |
| parent | 814f7dbf74af01e7932be7a994ecc8297e843f37 (diff) | |
| download | dotemacs-214c16fe127e007965b21d38d0c9c24f8c995b4c.tar.gz dotemacs-214c16fe127e007965b21d38d0c9c24f8c995b4c.zip | |
feat(theme-studio): palette generator and preview fidelity
Two strands land together because the generated theme-studio.html bundles every source file into one page and can't be split cleanly.
The palette generator is a preview-first panel: palette-generator-core.js plans the palette and palette-generator-ui.js draws it. Generated colors stay inspectable and tunable through the existing selector, and committing one creates a normal base column. It adds source-mode and scheme controls, a configurable accent count, and color names from color-names.json.
For preview fidelity, syntax and UI colors now resolve through the real Emacs inherit chains, so the preview matches how Emacs renders the theme. resolveSyntaxFg pins dec to ty (Emacs has no decorator face) and otherwise follows comment-delimiter to comment, doc to string, property to variable, function-call to function-name. resolveUiAttr walks mode-line-inactive to mode-line and line-number-current-line to line-number. The decorator label now reads "decorator to type" to match the type face Emacs uses for it.
Design recorded in the two theme-studio specs under docs/.
| -rw-r--r-- | docs/theme-studio-palette-generator-spec.org | 345 | ||||
| -rw-r--r-- | docs/theme-studio-semantic-theme-architecture-spec.org | 262 | ||||
| -rw-r--r-- | scripts/theme-studio/README.md | 28 | ||||
| -rw-r--r-- | scripts/theme-studio/app-core.js | 53 | ||||
| -rw-r--r-- | scripts/theme-studio/app.js | 117 | ||||
| -rw-r--r-- | scripts/theme-studio/browser-gates.js | 90 | ||||
| -rw-r--r-- | scripts/theme-studio/color-names.json | 542 | ||||
| -rw-r--r-- | scripts/theme-studio/generate.py | 10 | ||||
| -rw-r--r-- | scripts/theme-studio/palette-generator-core.js | 267 | ||||
| -rw-r--r-- | scripts/theme-studio/palette-generator-ui.js | 152 | ||||
| -rwxr-xr-x | scripts/theme-studio/run-tests.sh | 2 | ||||
| -rw-r--r-- | scripts/theme-studio/styles.css | 24 | ||||
| -rw-r--r-- | scripts/theme-studio/test-app-core.mjs | 441 | ||||
| -rw-r--r-- | scripts/theme-studio/test_generate.py | 8 | ||||
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 709 | ||||
| -rw-r--r-- | scripts/theme-studio/theme-studio.template.html | 13 | ||||
| -rw-r--r-- | scripts/theme-studio/theme.json | 1000 |
17 files changed, 3414 insertions, 649 deletions
diff --git a/docs/theme-studio-palette-generator-spec.org b/docs/theme-studio-palette-generator-spec.org index b4814706a..8139f3d95 100644 --- a/docs/theme-studio-palette-generator-spec.org +++ b/docs/theme-studio-palette-generator-spec.org @@ -1,12 +1,12 @@ #+TITLE: Theme Studio Palette Generator -- Spec -#+AUTHOR: Codex -#+DATE: 2026-06-13 +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-14 #+TODO: TODO | DONE SUPERSEDED CANCELLED * Metadata | Status | draft | |----------+-------| -| Owner | Codex | +| Owner | Craig | |----------+-------| | Reviewer | Craig | |----------+-------| @@ -14,218 +14,261 @@ |----------+-------| * Summary -Theme Studio needs a palette generator designed for dense Emacs themes, not a generic graphic-design palette toy. It should start from bg/fg, generate editable color columns in OKLCH, constrain candidate colors for readable text and UI use, and let the user preview/apply changes without losing existing assignments. +Theme Studio should grow a constraint-first palette generator that creates preview palette columns from the current theme context, then lets the user explicitly commit individual colors or whole columns. It should reuse the existing color selector as the single-color workbench instead of adding a second picker. -The generator is a panel over the existing palette-column model. It proposes columns, spans, and lightness/chroma bands; the user chooses whether to append, replace, or regenerate selected unlocked columns. +The v1 feature generates palette columns only. It does not assign faces automatically; once generated colors are applied, they become normal editable palette colors with stable column ids. * Problem / Context -Theme Studio now has stable palette columns, spans, contrast metrics, OKLCH editing, and default face inventories. That gives enough substrate to generate palettes, but the workflow is still manual: each accent must be chosen, spanned, checked for contrast, checked for distinguishability, assigned, and then adjusted by eye. +Theme Studio now has stable color columns, span controls, OKLCH editing, contrast metrics, DeltaE warnings, locks, package faces, and a live preview. The missing workflow is generating a coherent set of candidate base colors without manually choosing each hue, checking contrast, spanning, and then adjusting by eye. -Most palette generators optimize for attractive swatches, posters, or branding. Emacs themes have different constraints. The colors are mostly foreground text over a fixed dark or light ground, often shown in dense code, with UI backgrounds, selections, diffs, search hits, diagnostics, and package faces layered on top. A pretty palette can still be unusable if several accents collapse in low chroma, miss the bg contrast target, or produce harsh UI tints. +Generic palette generators are not a good fit by themselves. They optimize visual harmony before text readability, while Emacs themes need dense syntax colors, UI overlays, selections, search hits, diagnostics, and package faces to remain legible over a fixed ground. -The generator should therefore treat color theory as a candidate source, then filter candidates through Theme Studio's theme-specific constraints. +There is also UI overlap with the existing color selector. The selector already edits one active color, shows a swatch, drives the hex field and OKLCH picker, and adds or updates palette colors. A generator should feed that selector, not duplicate it. * Goals and Non-Goals ** Goals -- Generate editable palette columns from bg/fg, a base hue, an accent count, span count, chroma controls, and contrast targets. -- Keep OKLCH as the default generation space so lightness and chroma behave perceptually. -- Offer harmony modes that are useful for syntax themes: syntax-balanced, analogous, split-complementary, triadic, tetradic, warm/cool balanced, and manual hues. -- Preview proposed palettes before applying them. -- Apply proposals in scoped ways: append as new columns, replace selected columns, regenerate spans only, or regenerate unlocked generator-owned columns. -- Preserve stable column ids and existing assignments where possible. -- Expose enough metrics to explain why generated colors were clamped, muted, rejected, or adjusted. +- Generate candidate palette columns from explicit source modes and current theme constraints. +- Default to OKLCH generation so lightness and chroma are predictable. +- Default to a syntax-balanced scheme designed for readable code themes. +- Preview generated columns without mutating the real palette. +- Reuse the current color selector to inspect and tune one generated tile at a time. +- Allow adding a generated tile as a new base color column. +- Allow appending a whole generated column. +- Preserve stable column ids and existing assignments where possible when applying proposals. +- Expose diagnostics for contrast, DeltaE, gamut clamp, and rejected candidates. ** Non-Goals -- Automatically assign every face in the theme. Seeding and advisory features remain separate. -- Import palettes from external files. Import organization has its own spec/task. -- Replace the existing manual picker or per-column span controls. +- Automatically assign generated colors to syntax/UI/package faces in v1. +- Replace the existing manual picker, swatch, or per-column span controls. +- Import external palettes, CSS colors, image colors, or theme files. Import organization remains separate. - Generate terminal/ANSI palettes in v1. -- Support advanced appearance models like CAM16-UCS or Jzazbz in v1. +- Add OKHSL/OKHSV generation modes in v1. +- Rewrite bg/fg automatically in v1. ** Scope tiers -- v1: OKLCH-based generator panel, candidate preview, apply modes, generator-owned column metadata, tests, and README docs. -- Out of scope: fully automatic theme assignment, image/screenshot extraction, CSS/theme import, terminal colors. -- vNext: OKHSL/OKHSV editing/generation mode, low-contrast preset bands, CVD-aware candidate scoring, named style presets, terminal/ANSI palette derivation. +- v1: generator panel, pure planner, preview columns, existing color selector integration, add generated tile as base column, append whole generated column, diagnostics, tests, README docs. +- Out of scope: automatic face assignment, external imports, image extraction, terminal colors, rewriting bg/fg. +- vNext: replace selected columns, regenerate selected spans, regenerate generator-owned columns, OKHSL/OKHSV controls, whole-palette harmonization, CVD-aware scoring, named style presets, terminal palette derivation. * Design -For the user, the generator is a compact panel above the palette columns. The user sets the ground pair, picks a scheme, picks a base hue or manual hues, chooses accent count and span count, adjusts chroma/style bias, chooses a contrast target, then clicks preview. The preview renders proposed columns as temporary strips with metric badges. Applying the preview either appends columns, replaces selected columns, regenerates spans on selected columns, or updates generator-owned unlocked columns. +The generator is a panel above the real palette columns. It contains generation controls and a preview area, but no separate color picker. The existing color selector remains the place where one color is inspected, tuned, named, and committed. -For the implementer, the generator is a pure planning layer plus a thin DOM panel. The planner accepts the current palette state and a generator config, returns a proposal object, and never mutates global state. The proposal contains column plans with stable ids, base hexes, generated member hexes, names, clamp flags, contrast/readability diagnostics, and rejected candidate notes. The apply step converts an accepted proposal into the existing palette-column entries and uses the existing repoint behavior where a column is being replaced. +For the user, the workflow is: -The generator should use OKLCH for v1. It chooses hue positions from a scheme, then finds a useful lightness/chroma pair for each accent against the current bg. A "syntax-balanced" mode should be the default because it matches the product better than classical harmony. It spaces hues around the wheel, but it keeps colors in a text-safe lightness band and reduces chroma when needed to preserve readability and distinguishability. +1. Choose a generator source mode and scheme from compact controls in the generator panel. +2. Click preview. +3. Inspect temporary generated columns above the real palette. +4. Click a generated tile to load it into the existing color selector. +5. Optionally tune that color with the existing hex/OKLCH picker. +6. Commit either one generated tile as a new base column, or a whole generated column as normal palette entries. -The panel should not feel like a separate app. The proposed columns should look like columns, with the same span direction and tile affordances as real palette columns, but visually marked as a preview. The user should be able to click a proposed tile to inspect it in the picker before applying. +The preview layer is separate from =PALETTE=. Rendering preview columns must not create real palette entries, move existing columns, or change assignments. Preview tiles should look like palette tiles, but have a distinct preview treatment such as a dashed border/header or subtle "preview" label. + +The generator controls should be compact and explicit: + +- =Source= is a segmented control in v1: =bg/fg= and, if implemented in v1, =selected=. =bg/fg= is the default. Each option has a short tooltip: =bg/fg= means "generate from the current ground/foreground constraints"; =selected= means "harmonize around selected palette columns." +- =Scheme= can be a select/dropdown because it has more choices. A scheme is the hue-placement strategy used to propose candidate accents. The choices are not final color decisions; they are starting layouts that the planner filters through contrast, lightness, chroma, gamut, and DeltaE constraints. +- The scheme dropdown should include one-line help in the option title or adjacent help text. For example: =syntax-balanced= means "spread readable code accents across the hue wheel"; =analogous= means "nearby hues"; =triadic= means "three evenly-spaced hue families"; =manual= means "use the hues entered by the user." +- =Accent count= controls how many base columns the proposal tries to generate. The default is 8, with a v1 range of 3-12. +- =Span count= controls how many generated span steps each proposed column includes. The default should be conservative, likely 0 or the current preferred span setting, with a v1 range of 0-4. + +Therefore, the number of generated columns is configurable: it is the =accent count=, subject to rejected candidates. If the user asks for 8 accents and 2 are rejected by constraints, the preview should show 6 generated columns and report 2 rejected candidates in the summary. It should not silently backfill unrelated colors unless that behavior is explicitly added later. + +The current color selector gains a third selection mode: + +- No selection: =+ add color= creates a new manual base column from the selector value. +- Real palette tile selected: =update selected= changes that palette tile or recolors its column as today. +- Generated preview tile selected: the selector shows the generated hex/name, and =+ add color= commits that generated color as a new base color column. =update selected= is disabled unless a real palette tile is selected. + +A small status label near the selector should make this state explicit: + +#+begin_example +editing: new color +editing: palette color blue +editing: generated color blue-2 +#+end_example + +Generated columns are proposed from a source mode: + +- =bg/fg only= is the v1 default. It uses the ground and foreground as constraints, plus the chosen scheme/base hue. +- =selected columns= is v1 optional if cheap; otherwise vNext. It harmonizes around columns the user explicitly selected. +- =whole palette= is vNext. It should not be automatic in v1 because imported, experimental, or throwaway colors could make generation unpredictable. + +Generation is constraint-first. The planner chooses hue candidates from the scheme, then searches for useful OKLCH lightness/chroma values that satisfy the current background, contrast target, DeltaE separation, and gamut constraints. Classical harmony schemes are input layouts, not the final authority. + +For implementers, the feature is a pure planning layer plus DOM rendering/application: + +- The planner receives current palette, ground, source mode, generator config, and locks. +- The planner returns a proposal object and never mutates global state. +- The DOM layer renders proposal columns as preview columns. +- Applying one tile or one column converts proposal members into normal palette entries using existing column-id, span, name-collision, and gone/repoint behavior. ** Generator config -The v1 config fields: +V1 config fields: +- sourceMode: bg-fg, selected-columns - scheme: syntax-balanced, analogous, split-complementary, triadic, tetradic, warm-cool, manual - baseHue: degrees, used by non-manual schemes - manualHues: list of degrees, used by manual mode - accentCount: integer, default 8, range 3-12 -- spanCount: integer, default current column span default, range 0-4 +- spanCount: integer, default 0 or current preferred span, range 0-4 in v1 - textLightnessBand: min/max OKLCH L for text accents -- uiTintLightnessBand: min/max OKLCH L for background/highlight tints -- chroma: global target chroma - chromaBias: subdued, balanced, vivid -- contrastTarget: none, WCAG AA, WCAG AAA, APCA target -- deltaEMin: default to the existing palette warning threshold unless overridden -- locks: respect locked columns, respect locked assignments +- contrastTarget: none, WCAG AA, WCAG AAA +- deltaEMin: default to the existing palette warning threshold +- locks: respect locked columns and assignments where apply modes touch existing data ** Proposal object -The planner returns: - -#+begin_src js -{ - config, - columns: [ - { - columnId, - name, - baseHex, - members: [{ hex, name, offset, clamped, metrics }], - diagnostics: [{ level, message }] - } - ], - rejected: [{ hue, reason, nearestColumnId }], - summary: { generated, clamped, rejected, minContrast, minDeltaE } -} -#+end_src - -This shape is intentionally close to existing column/ramp data. It should be easy to unit test and easy to render as a preview. +The planner returns a proposal object with the generator config, proposed columns, rejected candidates, and a summary. Each proposed column carries a stable column id, display name, base hex, member colors with offsets and clamp/metric data, and column-level diagnostics. Rejected candidates carry the attempted hue, rejection reason, and nearest conflicting column when relevant. The summary includes generated count, clamped count, rejected count, minimum contrast, and minimum DeltaE. -** Display -The generator panel sits between the palette controls and the real color columns. It has: +This shape is intentionally close to the existing palette-column model. Preview rendering should not need a second color model. -- scheme segmented control -- base hue control -- accent count and span count numeric controls -- chroma/style controls -- contrast target control -- preview, append, replace selected, regenerate spans, and clear preview buttons -- summary row showing min contrast, min DeltaE, clamped count, and rejected count +** Applying generated colors +The first v1 apply actions are deliberately small: -The preview renders as temporary columns. They are visually distinct from committed columns but use the same tile size, naming, and lightness order. Applying the preview re-renders the real palette and clears the preview. +- =Add generated tile as base column=: creates a new normal palette column from the selected preview tile. The new column id is derived from the tile name and suffixes on collision. +- =Append generated column=: adds every member of that preview column after existing real columns. Members keep a stable shared column id. +- =Clear preview=: discards proposal state without changing the real palette. -** Apply modes -- Append: add all proposed columns after existing columns, suffixing names/ids on collision. -- Replace selected: replace selected normal columns one-for-one by visual order; extra proposed columns append, extra selected columns are removed only after confirmation. -- Regenerate spans only: keep selected column ids and base colors, update span count/knobs. -- Regenerate generator-owned: update only columns marked as generator-owned and unlocked. +The following apply actions are deferred unless v1 implementation is already straightforward: -The ground column remains pinned. The generator may read bg/fg and recommend ground-tint spans, but v1 does not rewrite bg/fg unless the user explicitly includes that later. +- replace selected columns +- regenerate spans only +- regenerate generator-owned columns -** Persistence -Generated columns become normal palette columns after apply, but they carry optional metadata: +When a generated tile is committed as a base column, it starts as a one-tile column. The user can then span it using the existing column span widget. This keeps the one-color action easy to understand and avoids surprising multi-tile commits from a tile-level button. -#+begin_src js -{ source: "generator", generator: { scheme, version, generatedAt } } -#+end_src +** Display +The panel sits between the color selector row and the committed palette columns. It has: + +- source mode segmented control +- scheme segmented control or select +- base hue/manual hue controls +- accent count and span count numeric controls +- chroma bias control +- contrast target control +- preview and clear-preview buttons +- summary row: min contrast, min DeltaE, clamped count, rejected count -The metadata is advisory. Editing or renaming a generated column should not break the palette. A later regenerate-generator-owned action can use the metadata, but normal manual editing always wins. +Preview columns render below the controls and above committed columns. A generated tile click loads the existing selector. A generated column header click loads the column base into the selector. A column-level =append column= button commits the whole preview column. * Alternatives Considered -** Drag a generic harmony wheel into the palette -- Good, because it is familiar from design tools and visually appealing. -- Bad, because it optimizes hue relationships before text readability, which is backwards for Theme Studio. -- Neutral, because a hue wheel can still be a useful input control inside a constraint-first generator. +** Generic harmony wheel that writes directly into the palette +- Good, because it is familiar and visually compact. +- Bad, because it mutates real palette state before the user can inspect results, and it optimizes hue harmony before readability. +- Neutral, because a hue wheel can still be useful as an input control inside a preview-first generator. + +** Separate generator-specific color picker +- Good, because preview tuning could be isolated from committed palette editing. +- Bad, because Theme Studio already has a capable single-color selector, and a second picker would duplicate hex, OKLCH, contrast, swatch, and add/update semantics. +- Neutral, because a future advanced generator could add a small detail panel, but v1 should not. + +** Generate from the whole palette by default +- Good, because it can harmonize with everything already on screen. +- Bad, because the palette may contain experiments, imported colors, temporary colors, or intentionally clashing accents; using all of them makes results hard to predict. +- Neutral, because whole-palette harmonization is valuable as an explicit vNext mode. + +** Full automatic face assignment +- Good, because it could produce a near-complete theme in one action. +- Bad, because it crosses into seeding, locks, role maps, and package face behavior that already have separate product decisions. +- Neutral, because the palette generator can feed a later seeding workflow. -** Classical palette generator only -- Good, because analogous/complementary/triadic/tetradic modes are easy to explain. -- Bad, because they do not know about bg/fg, contrast, syntax density, UI tints, or low-chroma distinguishability. -- Neutral, because those modes are still useful as candidate hue layouts. +** Add OKHSL/OKHSV now +- Good, because those controls may feel friendlier than raw OKLCH. +- Bad, because v1 already has OKLCH math and the feature risk is workflow/state, not another color model. +- Neutral, because OKHSL/OKHSV remain good vNext candidates. -** Full automatic theme seeding -- Good, because it could produce a near-complete theme in one action. -- Bad, because it crosses into role assignment, package defaults, and guide-support behavior that already have separate tasks/specs. -- Neutral, because the palette generator can become one input to the seeding engine later. - -** Add many color spaces now -- Good, because OKHSL/OKHSV may be friendlier than raw OKLCH sliders for some users. -- Bad, because v1 already has the right perceptual foundation, and extra spaces would increase UI and test surface before the generation workflow is proven. -- Neutral, because OKHSL/OKHSV are good vNext candidates. - -* Decisions [0/4] -** TODO Default to syntax-balanced OKLCH generation -- Owner / by-when: Craig / spec review -- Context: Generic harmony modes produce attractive swatches but do not optimize for readable code text. -- Decision: We will make syntax-balanced the default scheme and OKLCH the default generation space. -- Consequences: Easier to generate useful Emacs themes first; harder to present the feature as a conventional color-wheel generator. - -** TODO Keep generation separate from face assignment -- Owner / by-when: Craig / spec review -- Context: Automatic assignment would touch syntax, UI, package faces, seeding, locks, and guide-support rules. -- Decision: We will generate palette columns only in v1 and leave assignment/seeding to existing or separate workflows. -- Consequences: Easier to ship a focused generator; harder because the user still must map colors onto faces. - -** TODO Use preview-first apply modes -- Owner / by-when: Craig / spec review +* Decisions [5/5] +** DONE Default to preview-first generation - Context: Generator output can disrupt a carefully tuned palette if it mutates immediately. -- Decision: We will render proposals as temporary preview columns and require an explicit append/replace/regenerate apply action. -- Consequences: Easier to inspect and avoid destructive changes; harder because the UI needs a proposal state and apply semantics. +- Decision: We will render generated colors as temporary preview columns and require explicit add/append actions. +- Consequences: Easier to inspect and avoid accidental data loss; harder because the UI needs proposal state and apply semantics. + +** DONE Reuse the current color selector +- Context: The existing selector already edits one color, shows metrics, opens the picker, and adds/updates palette colors. +- Decision: We will make generated tile clicks load the existing selector instead of adding a second generator picker. +- Consequences: Easier to keep editing behavior consistent; harder because the selector now needs clear state for new, palette, and generated selections. -** TODO Defer OKHSL/OKHSV to vNext -- Owner / by-when: Craig / spec review +** DONE Keep v1 palette-only +- Context: Automatic assignment would touch syntax, UI, package faces, locks, and seeding rules. +- Decision: We will generate palette columns only in v1 and leave face assignment to existing/manual workflows. +- Consequences: Easier to ship a focused generator; harder because the user still maps colors onto faces. + +** DONE Default generation source to bg/fg only +- Context: Existing palette colors may be experimental or imported; using all of them by default makes generation unpredictable. +- Decision: We will default to bg/fg constraints plus explicit scheme/base hue. Selected-column source can be included if scoped; whole-palette source is vNext. +- Consequences: Easier to understand why a proposal was generated; harder because matching an existing palette requires an explicit source mode. + +** DONE Defer OKHSL/OKHSV to vNext - Context: OKHSL/OKHSV may be friendlier interaction models, but OKLCH already supports the required perceptual generation math. - Decision: We will ship OKLCH generation first and consider OKHSL/OKHSV after v1 is usable. - Consequences: Easier to keep v1 small and rigorous; harder because some users may find OKLCH controls less familiar. * Implementation phases ** Phase 1 -- Planner core -Add pure generator functions in app-core.js or a new generator module. Inputs are current palette, ground, generator config, and locks. Outputs are proposal objects. Unit tests cover scheme hue placement, OKLCH candidate generation, gamut clamp reporting, naming/id collision handling, and no mutation of input state. +Add pure generator functions in =app-core.js= or a new generator module. Inputs are current palette, ground, generator config, source selection, and locks. Outputs are proposal objects. Unit tests cover scheme hue placement, OKLCH candidate generation, gamut clamp reporting, name/id collision handling, and no mutation of input state. ** Phase 2 -- Candidate scoring -Add scoring and adjustment for contrast target, DeltaE separation, chroma bias, and text lightness band. Unit tests cover rejected candidates, clamped colors, low-chroma distinguishability, and deterministic output for fixed config. +Add bounded scoring/adjustment for contrast target, DeltaE separation, chroma bias, and text lightness band. Unit tests cover rejected candidates, clamped colors, low-chroma distinguishability, and deterministic output for fixed config. ** Phase 3 -- Generator panel and preview rendering -Add the panel, controls, preview columns, summary metrics, and clear-preview behavior. Browser gate: preview creates temporary columns without changing the committed palette. +Add the panel, controls, proposal state, preview columns, summary metrics, and clear-preview behavior. Browser gate: preview creates temporary columns without changing committed =PALETTE=. -** Phase 4 -- Apply modes -Implement append, replace selected, regenerate spans only, and regenerate generator-owned modes. Browser gates cover collision suffixing, locked column preservation, selected-column replacement, and generator-owned regeneration. +** Phase 4 -- Existing selector integration +Add generated-preview selection state to the color selector. Clicking a preview tile loads its hex/name. The selector status label shows generated-vs-palette-vs-new. =+ add color= commits the selected preview tile as a new one-tile base column. Browser gates cover generated tile selection and add-as-column behavior. -** Phase 5 -- Persistence and import/export -Round-trip optional generator metadata without requiring it for normal palette behavior. Existing imports without metadata continue to work. Browser gate extends roundtrip coverage. +** Phase 5 -- Append generated column +Add column-level append. The generated column becomes normal palette entries with one stable column id. Collisions suffix names/ids deterministically. Browser gates cover append, collision suffixing, and unchanged existing assignments. -** Phase 6 -- Documentation and cleanup -Document the generator in README and note the limits: palette only, no automatic face assignment. Remove any dead prototype code and keep make test green. +** Phase 6 -- Persistence and metadata +Round-trip optional generator metadata for applied columns without requiring it for normal palette behavior. Existing palettes without metadata continue to work. Browser gate extends roundtrip coverage. + +** Phase 7 -- Documentation and cleanup +Document generator controls, source modes, preview behavior, selector integration, and limits in =scripts/theme-studio/README.md=. Remove prototype code and keep =make -C scripts/theme-studio test= green. * Acceptance criteria -- [ ] A user can preview a syntax-balanced palette from current bg/fg without mutating the real palette. -- [ ] A user can append generated columns and then edit/reorder/span them like normal columns. -- [ ] A user can replace selected columns with a generated proposal without losing locked columns. +- [ ] Previewing a generated palette does not mutate committed =PALETTE=. +- [ ] Preview columns appear above committed columns and are visually distinct. +- [ ] Clicking a generated tile loads that color into the existing selector. +- [ ] The selector clearly says whether it is editing a new color, palette color, or generated color. +- [ ] =+ add color= on a selected generated tile creates a new one-tile base column. +- [ ] Appending a generated column creates normal editable palette entries with one stable column id. +- [ ] Name and column-id collisions are suffixed deterministically. - [ ] Generated colors report clamp, contrast, and DeltaE diagnostics. -- [ ] Export/import preserves committed generated columns and optional generator metadata. - [ ] Existing manual palette workflows still work without opening the generator panel. -- [ ] Theme Studio tests cover planner functions, preview rendering, apply modes, and round-trip behavior. +- [ ] Theme Studio tests cover planner functions, preview rendering, selector integration, apply behavior, and round-trip metadata. * Readiness dimensions -- Data model & ownership: The proposal is transient and generator-owned until applied. Applied columns become normal user-editable palette columns with optional advisory generator metadata. -- Errors, empty states & failure: Invalid config disables preview with an inline message naming the bad field. Rejected candidates appear in the summary rather than silently disappearing. Replace-selected asks for confirmation before removing unmatched selected columns. -- Security & privacy: N/A because generation is local, deterministic color math with no credentials or external requests. -- Observability: The preview summary shows generated, clamped, rejected, min contrast, and min DeltaE counts. Each tile exposes diagnostics in its title or detail panel. -- Performance & scale: Expected accent counts are small, 3-12 bases with up to 4 steps each. Candidate search should stay synchronous; if broader searches are added later, add progress/cancel. -- Reuse & lost opportunities: Reuse OKLCH, gamut clamp, ramp, contrast, APCA, DeltaE, locks, column ids, and existing browser gates. Do not invent a second color math stack. -- Architecture fit & weak points: The weak point is proposal/apply state in the DOM app. Keep planner pure and make the DOM layer only render/apply proposal objects. -- Config surface: Public knobs are scheme, hue/manual hues, accent count, span count, lightness bands, chroma bias, contrast target, DeltaE threshold, and lock behavior. Defaults should favor readable dark-theme syntax. -- Documentation plan: Update scripts/theme-studio/README.md with generator controls, apply modes, limits, and how generated columns become normal columns. -- Dev tooling: Existing scripts/theme-studio make test remains the gate. Add node tests for planner/scoring and browser hash gates for preview/apply. -- Rollout, compatibility & rollback: The generator is additive. Existing palettes load unchanged. Applied columns can be manually deleted or replaced; optional metadata can be ignored by old code. -- External APIs & deps: N/A for v1. No network or external package dependency is needed. +- Data model & ownership: Proposal state is transient and generator-owned. Applied colors become normal user-owned palette entries. Optional generator metadata is advisory and must not override manual edits. +- Errors, empty states & failure: Invalid config disables preview with an inline message naming the bad field. No preview shows an empty-state line. Rejected candidates appear in the summary. Apply failures must not partially mutate committed palette state. +- Security & privacy: N/A because generation is local deterministic color math with no credentials, network calls, or private external data. +- Observability: The preview summary shows generated, clamped, rejected, min contrast, and min DeltaE. Tile titles or details expose per-color diagnostics. +- Performance & scale: Expected accent counts are 3-12 bases with 0-4 span steps. Candidate search should remain synchronous and bounded. Broader search, if added later, needs progress/cancel. +- Reuse & lost opportunities: Reuse OKLCH, gamut clamp, contrast, DeltaE, palette columns, span generation, locks, existing selector, and existing browser gates. Do not add a second color math stack or picker. +- Architecture fit & weak points: The weak point is proposal/apply state in the DOM app. Keep planner pure and DOM code limited to rendering/applying proposal objects. +- Config surface: Public knobs are source mode, scheme, base/manual hue, accent count, span count, chroma bias, contrast target, DeltaE threshold, and lightness band. Defaults favor readable dark-theme syntax. +- Documentation plan: Update the Theme Studio README with generator controls, source modes, selector integration, preview/apply behavior, and v1 limits. +- Dev tooling: Use =make -C scripts/theme-studio test= as the primary gate and =make -C scripts/theme-studio coverage= for instrumented JS/generator coverage. +- Rollout, compatibility & rollback: The generator is additive. Existing palettes load unchanged. Preview can be cleared. Applied generated columns can be deleted manually. +- External APIs & deps: N/A because v1 has no external APIs or new dependencies. * Risks, Rabbit Holes, and Drawbacks -- Candidate search can become a rabbit hole. V1 should use deterministic bounded search around target OKLCH bands, not open-ended optimization. -- "Syntax-balanced" could become subjective. Keep it documented as a default heuristic, not a claim of universal taste. -- Preview/apply modes can overcomplicate the UI. If the panel feels heavy, hide advanced knobs behind a disclosure and keep preview/apply visible. -- Optional generator metadata could drift from manual edits. Treat it as advisory only. +- Candidate search can become an optimization rabbit hole. V1 should use deterministic bounded search around target OKLCH bands. +- "Syntax-balanced" is subjective. Keep it documented as a heuristic, not a claim of universal taste. +- Selector state can become confusing. The status label and disabled update button are required, not polish. +- Whole-palette harmonization is tempting but should wait until preview/apply basics feel good. +- Optional metadata can drift after manual edits. Treat it as advisory only. * Testing / Verification / Rollout Use the existing Theme Studio test stack: - Node tests for planner/scoring/collision/immutability. - Browser hash gate for preview-only non-mutation. -- Browser hash gate for append/replace/regenerate apply modes. -- Round-trip gate for generator metadata. -- Manual Chrome pass on a dark palette and a light palette. +- Browser hash gate for generated tile -> selector state. +- Browser hash gate for add-generated-tile-as-column. +- Browser hash gate for append generated column. +- Round-trip gate for optional generator metadata. +- Manual Chrome pass on at least one dark palette and one light palette. * References / Appendix - [[file:design/theme-studio-color-harmony.org][theme-studio color harmony explainer]] @@ -235,7 +278,17 @@ Use the existing Theme Studio test stack: - [[file:../todo.org::*theme-studio import organization workflow needs a spec][import organization task]] * Review and iteration history -** 2026-06-13 Saturday @ 16:31:01 -0500 -- Codex -- author +** 2026-06-13 Saturday @ 16:31:01 -0500 -- Craig -- author - What: Initial draft using the spec-create workflow. - Why: Palette generation has real design trade-offs around color space, preview/apply behavior, assignment boundaries, and how much generator state should persist. - Artifacts: [[file:../todo.org::*theme-studio palette generator][theme-studio palette generator task]]. + +** 2026-06-14 Sunday @ 00:44:00 -0500 -- Craig -- author +- What: Reworked the draft around preview-first generation, existing color selector integration, generated tile add-as-column behavior, and source-mode defaults. +- Why: Craig clarified the desired UX: generated colors should be inspectable/tunable through the existing selector, and committing one generated color should create a normal base column. +- Artifacts: [[file:../todo.org::*theme-studio palette generator][theme-studio palette generator task]]. + +** 2026-06-14 Sunday @ 01:07:00 -0500 -- Craig -- author +- What: Folded Craig's inline comments into the design, clarifying source/scheme controls, guidance text, the meaning of schemes, configurable accent count, and rejected-candidate behavior. Removed the comment/source blocks. +- Why: The generator UI needed to say what the user actually sees before implementation can proceed. +- Artifacts: [[file:../todo.org::*theme-studio palette generator][theme-studio palette generator task]]. diff --git a/docs/theme-studio-semantic-theme-architecture-spec.org b/docs/theme-studio-semantic-theme-architecture-spec.org new file mode 100644 index 000000000..2fa318efb --- /dev/null +++ b/docs/theme-studio-semantic-theme-architecture-spec.org @@ -0,0 +1,262 @@ +#+TITLE: Theme Studio Semantic Theme Architecture -- Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-14 +#+TODO: TODO | DONE SUPERSEDED CANCELLED + +* Metadata +| Status | draft | +|----------+-------| +| Owner | Craig | +|----------+-------| +| Reviewer | Craig | +|----------+-------| +| Related | [[file:../todo.org::*theme-studio semantic theme architecture][theme-studio semantic theme architecture task]] | +|----------+-------| + +* Summary +Theme Studio currently exports JSON into a flat generated Emacs theme: every face receives resolved hex colors and attributes directly. That is faithful to the current UI state, but it loses the middle layer that makes themes like Modus easy to reason about: named palette entries, semantic color roles, reusable face templates, and a thin generated wrapper. + +This spec proposes a future Theme Studio output architecture that can preserve those layers. The v1 goal is not to replace the current flat exporter immediately; it is to define the model, converter shape, migration path, and test surface needed to support a more maintainable theme system. + +* Problem / Context +Theme Studio has grown from a color picker into a full theme workbench: palette columns, syntax assignments, UI faces, package faces, generated candidates, contrast checks, locks, and a JSON-to-Emacs-theme converter. The converter currently emits direct =custom-theme-set-faces= specs. That works, but it makes the generated theme hard to maintain outside Theme Studio because the meaning of each color is already flattened away. + +The design discussion around Modus showed a different structure. Modus keeps concrete theme files small and pushes the durable logic into a shared engine: palettes define available colors, semantic mappings define what those colors mean, face specs use semantic names, and a theme constructor resolves everything when the theme loads. + +Theme Studio does not need to become Modus, but it can borrow the architecture. This would make generated themes easier to inspect, easier to customize by hand, and more able to support rules such as "comments and comment delimiters should normally share a color" without forcing every rule into the UI. + +* Goals and Non-Goals +** Goals +- Define a layered Theme Studio theme architecture: palette data, semantic role mapping, face templates, and generated theme wrapper. +- Preserve the current flat exporter as a compatibility path until the layered output is proven. +- Allow semantic roles to group multiple faces under one design decision. +- Allow advisory semantic rules that detect inconsistent mappings, such as comment and comment delimiter diverging. +- Keep the generated output loadable by normal Emacs =load-theme=. +- Make the design testable with pure converter tests and real Emacs theme-load tests. +- Make future Theme Studio features, such as face-seeding and role-aware palette generation, easier to build. + +** Non-Goals +- Do not enforce any specific semantic design rule in v1. The comment/comment-delim rule is an example of what the layer can express, not a required policy. +- Do not replace Theme Studio's current JSON format in one step. +- Do not require users to edit generated Elisp by hand. +- Do not implement a full Modus-compatible theme engine. +- Do not automatically reassign faces based on semantic rules. +- Do not remove direct per-face overrides from Theme Studio. + +** Scope tiers +- v1: Specify the layered model, add optional export fields or an intermediate converter model, generate a loadable layered theme file, preserve current flat output, add tests, document the architecture. +- Out of scope: UI for editing all semantic roles, automatic role inference from arbitrary imported themes, rule enforcement, Modus compatibility. +- vNext: Role editor UI, advisory rule panel, role-aware palette generator integration, semantic imports, user-defined rule packs, derivative theme wrappers. + +* Design +The architecture has four layers. + +Palette data is the color inventory. It answers "what named colors exist?" A Theme Studio palette entry already has most of this information: hex, display name, and stable column id. The layered exporter should preserve that as named Elisp data rather than immediately substituting every hex into every face. + +Semantic role mapping assigns design meaning to palette entries. It answers "what does this color do?" Examples include =syntax-keyword=, =syntax-comment=, =ui-region-bg=, =org-todo-fg=, and =mode-line-bg=. A role may point to a palette color, a literal hex, or another role. The important shift is that faces stop depending directly on "blue" or "#67809c"; they depend on "keyword" or "org-todo foreground". + +Face templates map Emacs faces to semantic roles and structural attributes. They answer "which faces use which roles?" A template can say =font-lock-comment-face= uses =syntax-comment=, =font-lock-comment-delimiter-face= uses =syntax-comment-delimiter=, and =org-todo= uses =org-todo-fg= plus =org-todo-bg= with bold and box attributes. Templates are where syntax, UI, and package face coverage live. + +The generated theme wrapper ties the layers together. It declares the theme, binds palette and semantic role values, expands the face templates, calls =custom-theme-set-faces= and =custom-theme-set-variables= where needed, then provides the theme. For users, the result is still a normal loadable Emacs theme. + +For the user, this does not need to introduce a new workflow immediately. Theme Studio can still export JSON and build a theme. The difference is that the generated Elisp becomes more intelligible: + +#+begin_src emacs-lisp +(defconst theme-palette + '((bg "#000000") + (fg "#e0e0e0") + (blue "#67809c"))) + +(defconst theme-semantics + '((syntax-keyword blue) + (syntax-comment fg-muted) + (ui-region-bg blue))) + +(custom-theme-set-faces + 'theme + `(font-lock-keyword-face ((,c :foreground ,syntax-keyword))) + `(region ((,c :background ,ui-region-bg)))) +#+end_src + +For implementers, the converter gains an intermediate representation: + +#+begin_src text +theme.json + -> palette model + -> semantic role model + -> face template model + -> generated theme file +#+end_src + +The current JSON can be projected into this model conservatively. If a face has no semantic role, the converter can synthesize a private role for that face, or fall back to a direct literal. That lets the layered exporter coexist with the current state format while the UI catches up. + +** Palette data +Palette data should be owned by the palette panel. It persists user-authored color names, hexes, and column ids. Generated span members remain palette entries, but the exporter may choose whether to expose every span member as a public named color or treat some as generated/private names. + +The palette layer should avoid face knowledge. It should not know that =blue= is a keyword or that =gold= is an Org title. That meaning belongs to the semantic layer. + +** Semantic role mapping +Semantic roles are owned by the theme design layer. Some roles can be generated from existing Theme Studio assignments: + +- syntax rows become roles such as =syntax-keyword= and =syntax-string=. +- UI rows become roles such as =ui-region-fg=, =ui-region-bg=, and =ui-mode-line-bg=. +- package rows become roles such as =org-todo-fg= or =magit-branch-fg= when those roles are worth naming. + +The role map should support three value kinds: + +- palette reference: =syntax-keyword -> blue= +- role reference: =syntax-comment-delimiter -> syntax-comment= +- literal: =warning-underline -> "#ff0000"= + +Role references are what make rules and shared intent useful. If comment and comment delimiter should move together, =syntax-comment-delimiter= can point at =syntax-comment=. If the user intentionally separates them, the mapping can become explicit and the advisory can report that it was intentionally diverged. + +** Semantic rules +The semantic layer can support rules because it knows relationships that the flat face table does not. A rule can be advisory, enforced, or ignored, but v1 should only define the mechanism and ship rules as documentation/test fixtures unless a specific rule is agreed later. + +Example advisory rules: + +- =syntax-comment= and =syntax-comment-delimiter= normally use the same foreground. +- =org-headline-done= should normally be no brighter than active headline faces. +- =region= with explicit foreground/background should be checked as its own pair, while bg-only region should be checked against covered text. +- warning/error/success roles should stay distinguishable by DeltaE and not only by text label. + +The comment/comment-delim rule is a note, not a v1 requirement. The spec deliberately does not mandate it because Theme Studio should preserve deliberate design choices even when they differ from the guide. + +** Face templates +Face templates should be data, not ad hoc emitted strings. They need to represent: + +- target face symbol +- foreground role or literal +- background role or literal +- inherit face or role +- structural attributes: bold, italic, underline, strike, height, box +- optional conditions such as display class, if the project later supports them + +The first implementation can keep one template table inside =build-theme.el=. Later, this can move into generated files or a shared Theme Studio runtime. + +Templates should support both semantic and direct output. This keeps migration safe: + +#+begin_src emacs-lisp +(font-lock-keyword-face :foreground syntax-keyword) +(some-face :foreground "#aabbcc") ; direct fallback +#+end_src + +** Generated theme wrapper +The wrapper is the loadable artifact. It should be self-contained unless we intentionally introduce a shared runtime. + +Two wrapper styles are viable: + +- Single-file layered output: every generated theme contains its palette, semantic map, resolver, templates, and =custom-theme-set-faces= call. +- Shared runtime plus thin generated wrapper: a common =theme-studio-theme.el= library resolves roles and expands templates, while each generated theme mostly supplies data. + +V1 should prefer single-file layered output. It is easier to test, easier to share, and avoids requiring a generated theme user to install Theme Studio runtime files. A shared runtime can be introduced later if duplication becomes painful. + +* Alternatives Considered +** Keep only the flat exporter +- Good, because it is simple, already works, and exactly reflects the current UI state. +- Bad, because it loses role intent and makes hand inspection or downstream customization harder. +- Neutral, because it should remain the compatibility and debugging baseline. + +** Generate a full Modus-style engine +- Good, because it is a proven architecture for serious Emacs themes. +- Bad, because Modus solves a broader package-quality problem than Theme Studio needs, and copying its engine would add complexity before we know the required seams. +- Neutral, because Modus remains the reference for structuring palette, semantics, and wrappers. + +** Add semantic roles only in JSON, still emit flat Elisp +- Good, because it lets Theme Studio reason about roles without changing the generated theme format. +- Bad, because users inspecting the generated theme still see only flattened direct specs. +- Neutral, because this may be a useful intermediate phase. + +** Make semantic rules mandatory +- Good, because it can preserve consistency and prevent accidental drift. +- Bad, because theme design often has deliberate exceptions, and hard rules can fight the user's eye. +- Neutral, because some rules may later become opt-in enforcement once proven. + +* Decisions [3/3] +** DONE Output layered themes without replacing flat output immediately +- Context: The current flat exporter is useful and working. The layered architecture is broader and should not block current Theme Studio iteration. +- Decision: We will add layered output as an additional converter path or mode before considering it the default. +- Consequences: Easier rollback and comparison; harder because tests must cover two output paths during the transition. + +** DONE Treat semantic rules as advisory in v1 +- Context: The semantic layer can express design relationships, but not every relationship should be forced. +- Decision: We will model rules as advisory validation data in v1 and avoid enforcement unless a separate product decision promotes a rule. +- Consequences: Easier to preserve user intent; harder because inconsistent themes can still be exported. + +** DONE Prefer self-contained generated layered themes for v1 +- Context: A shared runtime would reduce generated file size, but it creates another dependency for loading a generated theme. +- Decision: We will prefer a self-contained layered theme file in v1. +- Consequences: Easier sharing and loading; harder because each generated theme duplicates resolver/template code. + +* Implementation phases +** Phase 1 -- Intermediate model +Add a pure intermediate representation in the converter: palette entries, semantic roles, face templates, and resolved face specs. Keep existing flat output unchanged. + +** Phase 2 -- Semantic projection +Project current Theme Studio JSON into semantic roles. Start with syntax and core UI roles, then package roles only where names are clear and useful. Preserve direct literals as fallback. + +** Phase 3 -- Layered renderer +Generate a self-contained layered =*-theme.el= from the intermediate model. The generated file should still load with normal =load-theme= and should produce the same effective faces as the flat renderer for covered data. + +** Phase 4 -- Advisory rule surface +Add pure rule checks over the semantic map. Output diagnostics in tests or converter warnings first; defer UI display. + +** Phase 5 -- Tests and comparison tooling +Add tests that convert the same JSON through flat and layered paths, load both themes in isolated Emacs sessions, and compare representative effective face attributes. + +** Phase 6 -- Documentation and rollout +Document the architecture, Makefile target, and compatibility story. Keep flat output as the default until manual inspection and tests show the layered output is trustworthy. + +* Acceptance criteria +- [ ] The current flat JSON-to-theme converter still works. +- [ ] A layered converter can generate a self-contained loadable =*-theme.el=. +- [ ] The layered generated theme preserves default, syntax, UI, and package face attributes for representative fixtures. +- [ ] The layered generated file exposes palette data and semantic mappings in readable Elisp. +- [ ] Semantic role references resolve deterministically, including role-to-role references. +- [ ] Cycles in semantic role references fail with an actionable converter error. +- [ ] Advisory rules can report findings without blocking output. +- [ ] Documentation explains the four layers and how they relate to the current flat exporter. + +* Readiness dimensions +- Data model & ownership: Palette data remains owned by Theme Studio's palette model. Semantic roles are owned by the theme design layer. Face templates are owned by the converter/runtime. Generated theme wrappers are build artifacts. +- Errors, empty states & failure: Missing roles, invalid palette references, and role cycles must name the role and input file. Unknown faces should fall back to direct specs or fail only when a strict mode is explicitly requested. +- Security & privacy: N/A because theme files contain colors and face names, not credentials or private content. +- Observability: Converter output should say whether it wrote flat or layered output and list advisory rule findings. Later UI can display semantic findings. +- Performance & scale: Expected scale is hundreds to low thousands of faces. Role resolution should be linear with cycle detection; tests should include a large package-face fixture. +- Reuse & lost opportunities: Reuse =build-theme.el= parsing, face attr construction, box conversion, and Makefile targets. Do not duplicate Emacs face attribute rules. +- Architecture fit & weak points: The converter is the right first integration point because it can prove the model without changing the browser UI. Weak point: projecting semantics from flat assignments may create artificial roles; mitigate by marking generated/private roles distinctly. +- Config surface: New converter mode or Make target, likely =theme-studio-theme-layered= or =MODE=layered=. Defaults keep current flat output. +- Documentation plan: Update Theme Studio README and add a short architecture note. Cross-link this spec from the theme converter task. +- Dev tooling: Extend existing =make theme-studio-theme= or add a sibling target. Add ERT coverage for converter behavior and a comparison test between flat/layered outputs. +- Rollout, compatibility & rollback: Keep flat output as default. Layered output is opt-in until proven. Rollback is switching the Make target/mode back to flat. +- External APIs & deps: No external APIs. Emacs =custom-theme-set-faces= and =load-theme= semantics are the only runtime dependency. + +* Risks, Rabbit Holes, and Drawbacks +- Role projection may invent names that look meaningful but are only direct mappings from one face. Keep generated/private role names visibly distinct. +- A role editor UI could become a second Theme Studio. Defer UI until the converter model proves useful. +- Advisory rules can become noisy. Start with a small list, no enforcement, and clear wording. +- Self-contained output duplicates helper code. Accept that cost until sharing/runtime needs are clearer. + +* Testing / Verification / Rollout +The test surface should start in =tests/test-build-theme.el= or a sibling converter test. Required tests: + +- role reference resolution +- cycle detection +- layered output loads in Emacs +- flat/layered equivalence for representative fixture faces +- advisory rule returns findings but does not block output +- generated file remains self-contained + +Rollout should keep the current flat output path as the default and add a separate layered target. Manual verification should compare a real Theme Studio JSON through both outputs in a current Emacs session. + +* References / Appendix +- Modus Themes source: [[https://github.com/protesilaos/modus-themes][github.com/protesilaos/modus-themes]] +- Current converter: [[file:../scripts/theme-studio/build-theme.el][scripts/theme-studio/build-theme.el]] +- Current Theme Studio README: [[file:../scripts/theme-studio/README.md][scripts/theme-studio/README.md]] +- Package-face model spec: [[file:design/theme-studio-package-faces-spec.org][theme-studio-package-faces-spec.org]] + +* Review and iteration history +** 2026-06-14 Sunday @ 14:37:00 -0500 -- Craig -- author +- What: Initial draft for a Modus-inspired layered Theme Studio output architecture. +- Why: Craig asked how palette data, semantic role mappings, face templates, and generated wrappers could work together, including whether the semantic layer can support advisory consistency rules. +- Artifacts: [[file:theme-studio-semantic-theme-architecture-spec.org][this spec]]. diff --git a/scripts/theme-studio/README.md b/scripts/theme-studio/README.md index 6ca3285ec..df3d92607 100644 --- a/scripts/theme-studio/README.md +++ b/scripts/theme-studio/README.md @@ -30,6 +30,34 @@ During color work, disable Hyprland inactive-window dimming so colors read true: hyprctl keyword decoration:dim_inactive false ``` +## Build A Theme + +Convert a Theme Studio JSON export into a loadable Emacs theme: + +```bash +make theme-studio-theme JSON=/path/to/theme.json +``` + +That writes `themes/<name>-theme.el`, where `<name>` comes from the JSON +`name` field. To write somewhere else: + +```bash +make theme-studio-theme JSON=/path/to/theme.json OUT=/tmp/themes +``` + +To apply a generated theme in the current Emacs session after disabling every +enabled custom theme: + +```bash +make theme-studio-theme-load THEME=theme +``` + +To rebuild a JSON export and cleanly reload the theme named by that JSON: + +```bash +make theme-studio-theme-reload JSON=/path/to/theme.json +``` + ## Tests ```bash diff --git a/scripts/theme-studio/app-core.js b/scripts/theme-studio/app-core.js index 5da521773..2761031b9 100644 --- a/scripts/theme-studio/app-core.js +++ b/scripts/theme-studio/app-core.js @@ -33,6 +33,51 @@ function mergePackagesInto(map,pkgs){if(!pkgs)return;for(const app in pkgs){if(! // 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. +function resolveSyntaxFg(cat,syntax,defaultFg){ + let k=(cat==='dec')?'ty':cat; + const seen={}; + while(k&&!seen[k]){ + seen[k]=1; + const fg=syntax[k]&&syntax[k].fg; + if(fg)return fg; + k=SYNTAX_INHERIT[k]; + } + return 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){ + let f=face; + const seen={}; + while(f&&!seen[f]){ + seen[f]=1; + const v=uimap[f]&&uimap[f][attr]; + if(v)return v; + f=UI_INHERIT[f]; + } + return null; +} + // Standard swatch-dropdown option list: a default entry, then the palette. When // cur is set but no longer in the palette, surface it as a "(gone)" entry first. function optList(cur,palette){const have=cur===''||palette.some(p=>p[0]===cur);return [['','— default —'],...(have?palette:[[cur,'(gone)'],...palette])];} @@ -93,9 +138,9 @@ function fgSetFor(face,state){ 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,isRole)=>{const k=hex.toLowerCase(),cur=byHex.get(k);if(!cur)byHex.set(k,{hex:k,label});else if(isRole&&cur.label==='default')cur.label=label;}; - if(state&&state.defaultFg)add(state.defaultFg,'default',false); - for(const a of syn)add(a.hex,a.role||a.hex,true); + 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()]}; } @@ -322,4 +367,4 @@ function spanNeighborHex(cur,palette,ground,dir){ return null; } -export { nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, optList, paletteOptionList, spanNeighborHex, slugify, ramp, fgSetFor, floor, lMax, COVERED_FACES, columnsFromPalette, regenColumn, rankByLightness, stepRepointPlan, sortColumns, sortColumnMembers, groundRoleOfEntry, groundColumnMembersFromPalette, clearPalettePlan, deletePaletteColumnPlan, areAllLocked, lockToggleLabel, toggleLockSet }; +export { nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, resolveSyntaxFg, resolveUiAttr, optList, paletteOptionList, spanNeighborHex, slugify, ramp, fgSetFor, floor, lMax, COVERED_FACES, columnsFromPalette, regenColumn, rankByLightness, stepRepointPlan, sortColumns, sortColumnMembers, groundRoleOfEntry, groundColumnMembersFromPalette, clearPalettePlan, deletePaletteColumnPlan, areAllLocked, lockToggleLabel, toggleLockSet }; diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index 45b1f4864..4b331c555 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -1,4 +1,5 @@ const SAMPLES=SAMPLES_J, CATS=CATS_J, UI_FACES=UIFACES_J, APPS=APPS_J; +const COLOR_NAMES=COLOR_NAMES_J; let MAP=MAP_J, PALETTE=PALETTE_J, SYNTAX=SYNTAX_J, UIMAP=UIMAP_J; let LOCKED=new Set(LOCKS_J); // 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) @@ -28,9 +29,13 @@ APP_CORE_J // Pure color/UI-boundary helpers (normHex, ratingColor, textOn), inlined from // app-util.js. textOn uses rl from the colormath core above. APP_UTIL_J +// Pure palette-generator planner and browser-side generator panel. +PALETTE_GENERATOR_CORE_J +PALETTE_GENERATOR_UI_J // The contrast-cell readout shared by every table: a WCAG ratio colored by its -// AA/AAA rating, with the rating word. Callers compute r for their own fg/bg. -function crHtml(r){return `<span style="color:${ratingColor(r)}">${r.toFixed(1)} ${rating(r)}</span>`;} +// table verdict. Callers compute r for their own fg/bg. +function verdictFor(r,target=4.5){return r>=target?'PASS':'FAIL';} +function crHtml(r,target=4.5){const v=verdictFor(r,target);return `<span style="color:${ratingColor(r)}">${r.toFixed(1)} ${v}</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. @@ -61,21 +66,25 @@ function mkColorDropdown(options,cur,onPick,opts={}){ 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,{bg:MAP['bg'],fg:MAP['p']},dir);if(!next)return;cur=next;paint();onPick(next);} function paintStepButtons(){ const locked=wrap.dataset.locked==='1'; left.disabled=locked||!spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},-1); right.disabled=locked||!spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},1); } - function paint(){const nm=nameOf(cur),ttl=cur?(nm+' '+cur):nm;t.style.background=cur||'#161412';t.style.color=cur?textOn(cur):'#b4b1a2';t.dataset.val=cur||'';t.title=ttl;t.classList.toggle('is-default',!cur); - t.innerHTML=opts.compact?`<span class="cddsw" style="background:${cur||'transparent'}"></span>`:`<span class="cddsw" style="background:${cur||'transparent'}"></span>${esc(nm)}`;paintStepButtons();} + 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.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;} const pop=document.createElement('div');pop.className='cddpop'; for(const [hex,name] of options){const row=document.createElement('div');row.className='cddrow'+(hex===cur?' sel':''); - row.innerHTML=`<span class="cddsw" style="background:${hex||'transparent'}"></span><span class="cddnm">${esc(name)}</span><span class="cddhx">${hex||''}</span>`; + const shown=displayHex(hex),nm=hex?name:(opts.defaultName||name); + row.style.background=hex?'':shown;row.style.color=shown?textOn(shown):''; + row.innerHTML=`<span class="cddsw" style="background:${shown||'transparent'}"></span><span class="cddnm">${esc(nm)}</span><span class="cddhx">${hex||shown||''}</span>`; row.onclick=(ev)=>{ev.stopPropagation();cur=hex;paint();closeColorDropdown();onPick(hex);}; pop.appendChild(row);} document.body.appendChild(pop);const r=t.getBoundingClientRect(); @@ -182,8 +191,8 @@ function buildTable(){ 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=s.bold?'bold':'normal';exTd.style.fontStyle=s.italic?'italic':'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}); - const bgd=mkColorDropdown(ddList(sf.bg||''),sf.bg||'',hex=>{const s=syntaxFace(kind);s.bg=hex||null;styleEx();styleCr();renderCode();repaintCovered();},{compact:true}); + 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'); const stBtns=mkStyleButtons(at=>syntaxFace(kind)[at],at=>{const s=syntaxFace(kind);s[at]=!s[at];styleEx();renderCode();}); @@ -200,7 +209,7 @@ function buildTable(){ PALETTE_ACTIONS_J 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 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],oldRole=groundRoleOfEntry(PALETTE[i],{bg:MAP['bg'],fg:MAP['p']}); @@ -319,8 +328,9 @@ function initPicker(){const sw=document.getElementById('swatch');if(!sw)return;s 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,columnIdOf([h,name])]);const healed=healGone(name,h);document.getElementById('newname').value='';selectedIdx=null;closePicker(); + 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 slugify(themeName());} @@ -366,7 +376,7 @@ function boxCss(b,bg){if(!b||!b.style)return '';const w=b.width||1; 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'}`;} -function syntaxStyle(k){const s=syntaxFace(k),fg=(k==='bg'?MAP['p']:effFg(s.fg)),bg=s.bg||null,dec=(s.underline?'underline ':'')+(s.strike?'line-through':''), +function syntaxStyle(k){const s=syntaxFace(k),fg=(k==='bg'?MAP['p']:resolveSyntaxFg(k,SYNTAX,MAP['p'])),bg=s.bg||null,dec=(s.underline?'underline ':'')+(s.strike?'line-through':''), bx=boxCss(s.box,bg||MAP['bg']); return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${s.bold?'bold':'normal'};font-style:${s.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'}${bx?';box-shadow:'+bx:''}`;} // The per-row box control: none / line / raised / pressed plus optional line @@ -374,7 +384,7 @@ function syntaxStyle(k){const s=syntaxFace(k),fg=(k==='bg'?MAP['p']:effFg(s.fg)) function mkBoxControl(get,set,opts={}){const wrap=document.createElement('div');wrap.className='boxctl'; const s=document.createElement('select');s.className='chip';s.style.cssText='width:84px;font:10pt monospace'; [['','no box'],['line','line'],['released','raised'],['pressed','pressed']].forEach(([v,l])=>{const o=document.createElement('option');o.value=v;o.textContent=l;s.appendChild(o);}); - const dd=mkColorDropdown(ddList((get()&&get().color)||''),(get()&&get().color)||'',h=>{const cur=get();if(!cur)return;set(Object.assign({},cur,{color:h||null}));},{compact:!!opts.compact}); + const dd=mkColorDropdown(ddList((get()&&get().color)||''),(get()&&get().color)||'',h=>{const cur=get();if(!cur)return;set(Object.assign({},cur,{color:h||null}));},{compact:!!opts.compact,defaultHex:opts.defaultHex}); function paint(){const cur=get();s.value=cur&&cur.style?cur.style:'';dd.setValue(cur&&cur.color?cur.color:''); const off=!cur||!cur.style||wrap.dataset.locked==='1';dd.dataset.locked=off?'1':'';dd.classList.toggle('locked',off);if(dd.syncLocked)dd.syncLocked();} s.onchange=()=>{const cur=get();set(s.value?{style:s.value,width:cur&&cur.width||1,color:cur&&cur.color||null}:null);paint();}; @@ -444,7 +454,7 @@ function buildMockFrame(){ 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 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(isc)cd=withCursor(L.t); @@ -459,9 +469,9 @@ function buildMockFrame(){ const nFace=isc?'line-number-current-line':'line-number'; 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>`; + 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>`; html+=`<div class="bar" data-face="mode-line" style="${uiCss(ml,ml.fg||bg,ml.bg||fg)}"> init.el (Emacs Lisp) L5 git:main </div>`; - html+=`<div class="bar" data-face="mode-line-inactive" style="${uiCss(mli,mli.fg||fg,mli.bg||bg)}"> *Messages* (Fundamental) </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; @@ -471,7 +481,7 @@ function buildMockFrame(){ // 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});} + 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 uiFaceBlank(){return {fg:null,bg:null,bold:false,italic:false,underline:false,strike:false};} function seedFace(d){return normalizePkgFace({fg:pname(d.fg),bg:pname(d.bg),bold:d.bold,italic:d.italic,underline:d.underline,strike:d.strike,inherit:d.inherit,height:d.height,box:d.box},'default');} @@ -488,8 +498,8 @@ function buildPkgTable(){ 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 fgd=mkColorDropdown(ddList(f.fg||''),f.fg||'',h=>{f.fg=h||null;f.source='user';pkgChanged();},{compact:true}), - bgd=mkColorDropdown(ddList(f.bg||''),f.bg||'',h=>{f.bg=h||null;f.source='user';pkgChanged();},{compact:true}); + 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'); @@ -624,17 +634,31 @@ function renderGhostelPreview(){const a='ghostel',L=[]; 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>`;} 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!')); + 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 `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} 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')); @@ -827,25 +851,47 @@ 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],hex:effFg(syntaxFace(c[0]).fg)})); + 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']}); } -// The worst-case contrast cell for a covered face: the floor over its foreground -// set against its effective background, naming the limiting foreground. Returns -// null for an out-of-scope face so the caller keeps the single-pair readout. -function worstCellHtml(face){ +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 '<span title="this overlay has no syntax foreground set yet">no fg set</span>'; - const bg=effBg(uf(face).bg),fl=floor(bg,r.set),verdict=fl.ratio>=WORST_TARGET?'PASS':'FAIL'; - const s='worst: '+fl.limitingLabel+' '+fl.limitingHex+' — '+fl.ratio.toFixed(1)+' '+verdict; - return `<span style="color:${ratingColor(fl.ratio)}" title="${esc(s)}">${esc(s)}</span>`; + 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)} ${report.worst.verdict}</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=o.bold?'bold':'normal';pv.style.fontStyle=o.italic?'italic':'normal';pv.style.textDecoration=(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none';pv.style.boxShadow=boxCss(o.box,effBg(o.bg)); - const cr=document.getElementById('uicr-'+face);if(cr){const w=worstCellHtml(face);if(w!==null){cr.innerHTML=w;}else{const efg=effFg(o.fg),ebg=effBg(o.bg),r=contrast(efg,ebg);cr.innerHTML=crHtml(r);}}} + const report=coveredContrastReport(face); + pv.querySelectorAll('.crerr').forEach(e=>e.remove()); + pv.title=''; + if(report&&report.failures&&report.failures.length){ + const badge=document.createElement('span');badge.className='crerr';badge.textContent=report.worst.ratio.toFixed(1)+' FAIL';badge.title=failureTitle(report);pv.title=badge.title;pv.appendChild(badge); + } + 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)} ${report.worst.verdict}</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){ @@ -877,6 +923,7 @@ function srtTable(tbId,col){tableSort[tbId]={col,asc:!(tableSort[tbId]&&tableSor 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));} function initApp(){ buildLangSel();buildAppSel();renderPalette();rebuildColorTables();renderCode();applyGround(); + initGeneratorControls(); updateTitle();initPicker();buildPkgPreview();syncMockHeight();syncPkgHeight(); } initApp(); diff --git a/scripts/theme-studio/browser-gates.js b/scripts/theme-studio/browser-gates.js index 372937f18..3e5e12628 100644 --- a/scripts/theme-studio/browser-gates.js +++ b/scripts/theme-studio/browser-gates.js @@ -25,6 +25,7 @@ if(location.hash==='#selftest')pkgSelftest(); // 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'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + 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'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); @@ -41,6 +42,15 @@ if(location.hash==='#locktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c 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(); @@ -134,6 +144,12 @@ if(location.hash==='#mocktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c 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'].bold=true;buildMockFrame(); const curNum=Q('[data-face="line-number-current-line"]'); A(curNum&&/font-weight:\s*bold/.test(curNum.getAttribute('style')||''),'line-number-honors-weight'); @@ -151,6 +167,62 @@ if(location.hash==='#mocktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c A(pkgBtn()&&pkgBtn().classList.contains('on')&&PKGMAP[app][face].bold===true,'pkg style button visual state turns on after rebuild'); document.title='MOCKTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='mocktest';d.textContent='MOCKTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} +// 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'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + 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'); + document.title='GENERATORTEST '+(ok?'PASS':'FAIL'); + const d=document.createElement('div');d.id='generatortest';d.textContent='GENERATORTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} 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();}} @@ -219,15 +291,27 @@ if(location.hash==='#contrasttest'){let ok=true;const notes=[];const A=(c,n)=>{i UIMAP['region']={fg:null,bg:'#202830',bold:false,italic:false,underline:false,strike:false}; buildUITable(); const cell=document.getElementById('uicr-region'); - A(cell&&/^worst:/.test(cell.textContent),'region shows the worst-case readout: '+(cell&&cell.textContent)); - A(cell&&cell.textContent.includes('#67809c'),'limiting fg is keyword blue: '+(cell&&cell.textContent)); - A(cell&&/\b(PASS|FAIL)\b/.test(cell.textContent),'readout carries a verdict'); + A(cell&&/^\d+\.\d (PASS|FAIL)$/.test(cell.textContent.trim()),'region shows compact worst-case readout: '+(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)); + const badge=document.querySelector('#uiprev-region .crerr'); + A(badge&&badge.textContent.trim()===cell.textContent.trim(),'region preview shows failing contrast badge: '+(badge&&badge.textContent)); + A(badge&&badge.title.includes('kw (keyword) #67809c'),'preview badge hover carries failures: '+(badge&&badge.title)); + const firstFail=badge&&badge.title.split('\n')[1]; + A(firstFail&&firstFail.includes('kw (keyword) #67809c'),'failures are sorted from worst first: '+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',bold:false,italic:false,underline:false,strike:false}; + 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',bold:false,italic:false,underline:false,strike:false}; 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)); diff --git a/scripts/theme-studio/color-names.json b/scripts/theme-studio/color-names.json new file mode 100644 index 000000000..d28f4f449 --- /dev/null +++ b/scripts/theme-studio/color-names.json @@ -0,0 +1,542 @@ +[ + [ + "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" + ] +] diff --git a/scripts/theme-studio/generate.py b/scripts/theme-studio/generate.py index fbaf7dd80..aa0e829fb 100644 --- a/scripts/theme-studio/generate.py +++ b/scripts/theme-studio/generate.py @@ -44,12 +44,17 @@ APP_CORE_BODY=strip_exports(read_text('app-core.js')) # test-app-util.mjs. Its `import rl` line is stripped on inline (rl is already in # the page from the colormath core). APP_UTIL_BODY=strip_exports(read_text('app-util.js')) +# Pure palette-generator planner and its browser UI panel, split from the shared +# app core so generation behavior and panel wiring can evolve locally. +PALETTE_GENERATOR_CORE_BODY=strip_exports(read_text('palette-generator-core.js')) +PALETTE_GENERATOR_UI_BODY=strip_exports(read_text('palette-generator-ui.js')) # Palette panel actions and rendering. This is stateful browser code, split from # app.js because color-column behavior changes often and benefits from locality. PALETTE_ACTIONS_BODY=strip_exports(read_text('palette-actions.js')) # Browser hash gates, split from app.js so the application code is not buried # under the test harness while still shipping one self-contained HTML file. BROWSER_GATES_BODY=strip_exports(read_text('browser-gates.js')) +COLOR_NAMES=read_json('color-names.json') ns={} src=read_text('samples.py') exec(src[:src.index('cols=')], ns) @@ -183,7 +188,7 @@ MAP,BOLD,ITALIC_MAP=initial_maps(COLS,DEFAULTS) PALETTE=[[MAP['bg'],"bg","ground"],[MAP['p'],"fg","ground"]] 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 · def","resolve push"], - ["fnc","function · call","printf rsync get"],["dec","decorator","@dataclass"], + ["fnc","function · call","printf rsync get"],["dec","decorator → 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]+/"], @@ -261,8 +266,11 @@ def fill_data(s): return (s.replace("COLORMATH_J",COLORMATH_BODY) .replace("APP_CORE_J",APP_CORE_BODY) .replace("APP_UTIL_J",APP_UTIL_BODY) + .replace("PALETTE_GENERATOR_CORE_J",PALETTE_GENERATOR_CORE_BODY) + .replace("PALETTE_GENERATOR_UI_J",PALETTE_GENERATOR_UI_BODY) .replace("PALETTE_ACTIONS_J",PALETTE_ACTIONS_BODY) .replace("BROWSER_GATES_J",BROWSER_GATES_BODY) + .replace("COLOR_NAMES_J",json.dumps(COLOR_NAMES)) .replace("SAMPLES_J",json.dumps(SAMPLES)) .replace("PALETTE_J",json.dumps(PALETTE)).replace("CATS_J",json.dumps(CATS)) .replace("UIFACES_J",json.dumps(UI_FACES)).replace("UIMAP_J",json.dumps(UIMAP)).replace("APPS_J",json.dumps(APPS)) diff --git a/scripts/theme-studio/palette-generator-core.js b/scripts/theme-studio/palette-generator-core.js new file mode 100644 index 000000000..6bce0d59a --- /dev/null +++ b/scripts/theme-studio/palette-generator-core.js @@ -0,0 +1,267 @@ +// 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. +import { normHex } from './app-util.js'; +import { oklch2hex, srgb2oklab, oklab2oklch, contrast, deltaE } from './colormath.js'; +import { columnsFromPalette } from './app-core.js'; + +function oklchOf(hex){return oklab2oklch(srgb2oklab(hex));} +function isPureEndpointHex(hex){const h=(hex||'').toLowerCase();return h==='#ffffff'||h==='#000000';} +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=oklab2oklch(srgb2oklab(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; + if(vibe==='muted')return 0.045+rnd()*0.035; + if(vibe==='pastel')return 0.035+rnd()*0.045; + if(vibe==='deep')return 0.085+rnd()*0.055; + if(vibe==='jewel')return 0.12+rnd()*0.075; + if(vibe==='earthy')return 0.055+rnd()*0.04; + if(vibe==='warm'||vibe==='cool')return 0.08+rnd()*0.06; + if(vibe==='neon')return 0.18+rnd()*0.09; + if(vibe==='strange')return 0.145+rnd()*0.095; + if(vibe==='balanced')return 0.075+rnd()*0.045; + return 0.12+rnd()*0.07; +} +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]); +} + +export { planPaletteGenerator, entriesForGeneratedColumn }; diff --git a/scripts/theme-studio/palette-generator-ui.js b/scripts/theme-studio/palette-generator-ui.js new file mode 100644 index 000000000..6cab65508 --- /dev/null +++ b/scripts/theme-studio/palette-generator-ui.js @@ -0,0 +1,152 @@ +// 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(); +} diff --git a/scripts/theme-studio/run-tests.sh b/scripts/theme-studio/run-tests.sh index 95a3dde68..f6a68c41b 100755 --- a/scripts/theme-studio/run-tests.sh +++ b/scripts/theme-studio/run-tests.sh @@ -55,7 +55,7 @@ CHROME="" for c in google-chrome-stable google-chrome chromium chromium-browser; do if command -v "$c" >/dev/null 2>&1; then CHROME="$c"; break; fi done -HASHES="selftest cursortest readouttest deltatest oklchtest planetest locktest sorttest mocktest contrasttest safetest healtest columntest counttest baseedittest roundtriptest beveltest previewlinktest" +HASHES="selftest cursortest readouttest deltatest oklchtest planetest locktest sorttest mocktest contrasttest safetest healtest columntest counttest baseedittest roundtriptest beveltest previewlinktest generatortest" if [ "$NO_BROWSER" = 1 ]; then skip_msg "browser hash gates (--no-browser)" elif [ -z "$CHROME" ]; then diff --git a/scripts/theme-studio/styles.css b/scripts/theme-studio/styles.css index 5de838b68..a2242728b 100644 --- a/scripts/theme-studio/styles.css +++ b/scripts/theme-studio/styles.css @@ -40,6 +40,7 @@ .boxctl .cstepbtn{width:18px} .legctl{margin:0 0 8px;display:flex;gap:8px;align-items:center} .cat{color:#b4b1a2} .ex{font-size:17px} + .crerr{display:inline-block;margin-left:8px;padding:0 4px;border-radius:3px;background:#2b130e;color:#cb6b4d;border:1px solid #7b3324;font:9pt monospace;vertical-align:middle} .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} .sbtn.on{background:#0d0b0a;color:#cdced1;border-color:#8a9496} .pals{display:flex;flex-direction:row;flex-wrap:wrap;gap:10px;align-items:flex-start} @@ -96,6 +97,24 @@ .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:0;padding:0} @@ -110,7 +129,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} diff --git a/scripts/theme-studio/test-app-core.mjs b/scripts/theme-studio/test-app-core.mjs index 95763cdaf..63e79a95c 100644 --- a/scripts/theme-studio/test-app-core.mjs +++ b/scripts/theme-studio/test-app-core.mjs @@ -7,12 +7,15 @@ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { - nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, optList, paletteOptionList, spanNeighborHex, slugify, + nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, resolveSyntaxFg, resolveUiAttr, optList, paletteOptionList, spanNeighborHex, slugify, clearPalettePlan, deletePaletteColumnPlan, groundColumnMembersFromPalette, areAllLocked, lockToggleLabel, toggleLockSet, } from './app-core.js'; +import { planPaletteGenerator, entriesForGeneratedColumn } from './palette-generator-core.js'; +import { oklch2hex, deltaE } from './colormath.js'; const here = fileURLToPath(new URL('.', import.meta.url)); const PAL = [['#67809c', 'blue'], ['#e8bd30', 'gold']]; +const COLOR_NAMES = JSON.parse(readFileSync(new URL('./color-names.json', import.meta.url), 'utf8')); test('nameToHex: Normal — resolves a palette name to its hex', () => { assert.equal(nameToHex('blue', PAL), '#67809c'); @@ -180,6 +183,381 @@ test('deletePaletteColumnPlan: Boundary — never deletes ground entries', () => assert.deepEqual(plan.removed, []); }); +test('planPaletteGenerator: Normal — builds deterministic preview columns without mutating palette', () => { + const pal = [['#0d0b0a', 'bg', 'ground'], ['#f0fef0', 'fg', 'ground'], ['#67809c', 'blue', 'blue']]; + const before = JSON.stringify(pal); + const plan = planPaletteGenerator(pal, { bg: '#0d0b0a', fg: '#f0fef0' }, { + sourceMode: 'bg-fg', + scheme: 'syntax-balanced', + baseHue: 250, + accentCount: 5, + spanCount: 2, + chromaMode: 'balanced', + contrastMode: 'aa', + }); + assert.equal(JSON.stringify(pal), before, 'planner is pure'); + assert.equal(plan.sourceMode, 'bg-fg'); + assert.equal(plan.scheme, 'syntax-balanced'); + assert.equal(plan.columns.length, 5); + assert.equal(plan.summary.generated, 5); + assert.equal(plan.summary.rejected, 0); + assert.ok(plan.summary.minContrast >= 4.5); + assert.ok(!/^generated-/.test(plan.columns[0].name), 'generated bases get nearest color names'); + assert.equal(plan.columns[0].members.length, 1); + assert.ok(plan.columns.every(c => c.columnId && c.members.some(m => m.offset === 0 && m.hex === c.baseHex))); +}); + +test('planPaletteGenerator: Normal — random scheme varies candidate bases for inspiration', () => { + const pal = [['#0d0b0a', 'bg', 'ground'], ['#f0fef0', 'fg', 'ground']]; + const seq = [0.10, 0.80, 0.35, 0.65, 0.90, 0.20, 0.48, 0.72, 0.04, 0.55, 0.30, 0.88]; + let at = 0; + const rng = () => seq[at++ % seq.length]; + const a = planPaletteGenerator(pal, { bg: '#0d0b0a', fg: '#f0fef0' }, { scheme: 'random', accentCount: 4, spanCount: 0, rng }); + const b = planPaletteGenerator(pal, { bg: '#0d0b0a', fg: '#f0fef0' }, { scheme: 'random', accentCount: 4, spanCount: 0, rng }); + assert.equal(a.scheme, 'random'); + assert.equal(a.columns.length, 4); + assert.notDeepEqual(a.columns.map(c => c.baseHex), b.columns.map(c => c.baseHex)); + assert.ok(a.columns.every(c => c.contrast >= 4.5)); + assert.ok(a.columns.some(c => c.C >= 0.11), 'random candidates include more saturated colors than the quiet default'); +}); + +test('planPaletteGenerator: Boundary — omitted scheme defaults to random', () => { + const plan = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { accentCount: 1, spanCount: 0, rng: () => 0.25 }); + assert.equal(plan.scheme, 'random'); +}); + +test('planPaletteGenerator: Boundary — omitted accent count defaults to five', () => { + const plan = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { spanCount: 0, rng: () => 0.25 }); + assert.equal(plan.accentCount, 5); + assert.equal(plan.columns.length, 5); +}); + +test('planPaletteGenerator: Boundary — accent count is clamped to the supported range', () => { + const low = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { accentCount: -4, spanCount: 0, rng: () => 0.25 }); + const high = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { accentCount: 99, spanCount: 0, rng: () => 0.25 }); + assert.equal(low.accentCount, 1); + assert.equal(low.columns.length, 1); + assert.equal(high.accentCount, 12); + assert.equal(high.columns.length, 12); +}); + +test('planPaletteGenerator: Boundary — unknown source mode falls back to bg/fg', () => { + const plan = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { + sourceMode: 'mystery', + baseHue: 17, + accentCount: 1, + spanCount: 0, + }); + assert.equal(plan.sourceMode, 'bg-fg'); + assert.notEqual(Math.round(plan.baseHue), 17); +}); + +test('planPaletteGenerator: Normal — intent near palette uses base columns as anchors', () => { + const pal = [ + ['#0d0b0a', 'bg', 'ground'], + ['#f0fef0', 'fg', 'ground'], + ['#67809c', 'blue', 'blue'], + ['#e8bd30', 'gold', 'gold'], + ['#ffffff', 'blue+1', 'blue'], + ]; + const plan = planPaletteGenerator(pal, { bg: '#0d0b0a', fg: '#f0fef0' }, { + intent: 'near-palette', + sourceMode: 'palette', + vibe: 'balanced', + accentCount: 4, + spanCount: 0, + rng: () => 0.5, + }); + const anchorHues = ['#67809c', '#e8bd30'].map(hex => Math.round(planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { sourceMode: 'selected', selectedHex: hex, accentCount: 1, spanCount: 0, rng: () => 0.1 }).baseHue)); + assert.equal(plan.intent, 'near-palette'); + assert.equal(plan.sourceMode, 'palette'); + assert.equal(plan.columns.length, 4); + assert.ok(plan.columns.every(c => anchorHues.some(h => Math.abs((((c.hue - h + 540) % 360) - 180)) <= 20))); + assert.ok(plan.columns.every(c => c.C >= 0.075 && c.C <= 0.13)); +}); + +test('planPaletteGenerator: Normal — intent fill gaps targets underused hue regions', () => { + const pal = [['#67809c', 'blue', 'blue'], ['#e8bd30', 'gold', 'gold'], ['#cb6b4d', 'terra', 'terra']]; + const plan = planPaletteGenerator(pal, { bg: '#000000', fg: '#ffffff' }, { + intent: 'fill-gaps', + sourceMode: 'palette', + vibe: 'muted', + accentCount: 3, + spanCount: 0, + rng: () => 0.5, + }); + assert.equal(plan.intent, 'fill-gaps'); + assert.equal(plan.columns.length, 3); + assert.ok(plan.columns.every(c => c.C <= 0.09), 'muted vibe keeps chroma lower'); + assert.ok(new Set(plan.columns.map(c => Math.round(c.hue))).size > 1, 'gap fill proposes multiple hue regions'); +}); + +test('planPaletteGenerator: Normal — fill gaps chooses missing perceptual colors', () => { + const anchor = (L,C,H) => oklch2hex(L, C, H).hex; + const pal = [ + [anchor(0.25, 0.08, 40), 'dark-warm', 'dark-warm'], + [anchor(0.75, 0.08, 40), 'light-warm', 'light-warm'], + [anchor(0.50, 0.08, 220), 'mid-cool', 'mid-cool'], + ]; + const plan = planPaletteGenerator(pal, { bg: '#000000', fg: '#ffffff' }, { + intent: 'fill-gaps', + sourceMode: 'palette', + vibe: 'balanced', + contrastMode: 'none', + accentCount: 5, + spanCount: 0, + rng: () => 0.5, + }); + const Ls = plan.columns.map(c => c.L), nearestAnchor = plan.columns.map(c => Math.min(...pal.map(p => deltaE(c.baseHex, p[0])))); + assert.equal(plan.columns.length, 5); + assert.ok(Math.max(...Ls) - Math.min(...Ls) > 0.35, 'candidate spread includes lightness, not hue alone'); + assert.ok(nearestAnchor.every(d => d > 0.14), 'candidates stay perceptually away from existing anchors: '+nearestAnchor.join(',')); +}); + +test('planPaletteGenerator: Boundary — fill gaps with no anchors falls back to random-style candidates', () => { + const seq = [0.10, 0.20, 0.30, 0.40, 0.50, 0.60]; + let at = 0; + const plan = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { + intent: 'fill-gaps', + sourceMode: 'none', + vibe: 'balanced', + accentCount: 3, + spanCount: 0, + rng: () => seq[at++ % seq.length], + }); + assert.equal(plan.columns.length, 3); + assert.ok(new Set(plan.columns.map(c => Math.round(c.hue))).size > 1); +}); + +test('planPaletteGenerator: Normal — fill hue gaps rewards underrepresented hue regions', () => { + const ground = { bg: '#000000', fg: '#ffffff' }; + const baseCfg = { sourceMode: 'bg-fg', vibe: 'balanced', contrastMode: 'aa', accentCount: 5, spanCount: 0, rng: () => 0.5 }; + const plain = planPaletteGenerator([], ground, { ...baseCfg, intent: 'fill-gaps' }); + const hueAware = planPaletteGenerator([], ground, { ...baseCfg, intent: 'fill-hue-gaps' }); + const yellowish = c => c.hue >= 45 && c.hue <= 105; + assert.equal(hueAware.intent, 'fill-hue-gaps'); + assert.notDeepEqual(hueAware.columns.map(c => Math.round(c.hue)), plain.columns.map(c => Math.round(c.hue))); + assert.ok(hueAware.columns.some(yellowish), 'hue-aware fill should surface a yellow/gold region early'); + assert.ok(!plain.columns.some(yellowish), 'plain perceptual fill stays available as a separate behavior'); +}); + +test('planPaletteGenerator: Normal — intent complements generates opposite anchor colors', () => { + const plan = planPaletteGenerator([['#67809c', 'blue', 'blue']], { bg: '#000000', fg: '#ffffff' }, { + intent: 'complements', + sourceMode: 'palette', + vibe: 'bold', + accentCount: 2, + spanCount: 0, + rng: () => 0.5, + }); + assert.equal(plan.intent, 'complements'); + assert.ok(plan.columns.every(c => c.C >= 0.12)); + assert.ok(plan.columns.every(c => Math.abs((((c.hue - (plan.baseHue + 180) + 540) % 360) - 180)) <= 20)); +}); + +test('planPaletteGenerator: Normal — intent bridges generates colors between anchors', () => { + const pal = [['#67809c', 'blue', 'blue'], ['#e8bd30', 'gold', 'gold'], ['#cb6b4d', 'terra', 'terra']]; + const plan = planPaletteGenerator(pal, { bg: '#000000', fg: '#ffffff' }, { + intent: 'bridges', + sourceMode: 'palette', + vibe: 'balanced', + accentCount: 3, + spanCount: 0, + rng: () => 0.5, + }); + assert.equal(plan.intent, 'bridges'); + assert.equal(plan.columns.length, 3); + assert.ok(new Set(plan.columns.map(c => Math.round(c.hue))).size > 1); + assert.ok(plan.columns.every(c => c.C >= 0.075 && c.C <= 0.13)); +}); + +test('planPaletteGenerator: Normal — harmony intents generate expected hue families', () => { + const planFor = intent => planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { + intent, + sourceMode: 'selected', + selectedHex: '#67809c', + baseHue: 250, + vibe: 'balanced', + accentCount: 4, + spanCount: 0, + rng: () => 0.5, + }); + const cfg = intent => planFor(intent).columns.map(c => Math.round(c.hue)); + const base = Math.round(planFor('triadic').baseHue); + const dist = (a, b) => Math.abs((((a - b + 540) % 360) - 180)); + assert.ok(cfg('complementary').every(h => dist(h, (base + 180) % 360) <= 1)); + assert.deepEqual(cfg('triadic').slice(0, 3).map(h => Math.round((h - base + 360) % 360)), [0, 120, 240]); + assert.deepEqual(cfg('square').map(h => Math.round((h - base + 360) % 360)), [0, 90, 180, 270]); + assert.deepEqual(cfg('tetradic').map(h => Math.round((h - base + 360) % 360)), [0, 60, 180, 240]); + assert.ok(new Set(cfg('rainbow')).size === 4); + assert.ok(cfg('monochromatic').every(h => dist(h, base) <= 3)); + assert.ok(cfg('split-complementary').slice(0, 2).every(h => dist(h, (base + 150) % 360) <= 1 || dist(h, (base + 210) % 360) <= 1)); + assert.ok(cfg('analogous').slice(0, 2).every(h => dist(h, (base + 330) % 360) <= 1 || dist(h, (base + 30) % 360) <= 1)); +}); + +test('planPaletteGenerator: Normal — harmony intents work with palette anchors', () => { + const pal = [['#67809c', 'blue', 'blue'], ['#e8bd30', 'gold', 'gold']]; + const intents = ['complementary', 'analogous', 'split-complementary', 'triadic', 'tetradic', 'square', 'monochromatic', 'rainbow']; + for (const intent of intents) { + const plan = planPaletteGenerator(pal, { bg: '#000000', fg: '#ffffff' }, { + intent, + sourceMode: 'palette', + vibe: 'balanced', + accentCount: 4, + spanCount: 0, + rng: () => 0.5, + }); + assert.equal(plan.sourceMode, 'palette', intent); + assert.equal(plan.intent, intent); + assert.equal(plan.columns.length, 4, intent); + assert.ok(plan.columns.every(c => c.contrast >= 4.5), intent); + } +}); + +test('planPaletteGenerator: Boundary — empty palette source falls back to configured hue', () => { + const plan = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { + intent: 'near-palette', + sourceMode: 'palette', + baseHue: 123, + vibe: 'balanced', + accentCount: 2, + spanCount: 0, + rng: () => 0.5, + }); + assert.equal(plan.sourceMode, 'palette'); + assert.equal(plan.columns.length, 2); + assert.ok(Math.abs(plan.baseHue - 123) < 0.001); + assert.ok(plan.columns.every(c => Math.abs((((c.hue - 123 + 540) % 360) - 180)) <= 20)); +}); + +test('planPaletteGenerator: Boundary — invalid selected source falls back to bg/fg', () => { + const plan = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { + intent: 'near-selected', + sourceMode: 'selected', + selectedHex: 'not-a-color', + baseHue: 22, + accentCount: 1, + spanCount: 0, + }); + assert.equal(plan.sourceMode, 'bg-fg'); + assert.notEqual(Math.round(plan.baseHue), 22); +}); + +test('planPaletteGenerator: Boundary — high contrast can reject every candidate', () => { + const plan = planPaletteGenerator([], { bg: '#777777', fg: '#ffffff' }, { + intent: 'random', + sourceMode: 'none', + vibe: 'pastel', + contrastMode: 'aaa', + accentCount: 4, + spanCount: 0, + rng: () => 0.5, + }); + assert.equal(plan.columns.length, 0); + assert.equal(plan.rejected.length, 4); + assert.equal(plan.summary.rejected, 4); + assert.equal(plan.summary.minContrast, null); +}); + +test('planPaletteGenerator: Boundary — contrast none keeps otherwise rejected candidates', () => { + const plan = planPaletteGenerator([], { bg: '#777777', fg: '#ffffff' }, { + intent: 'random', + sourceMode: 'none', + vibe: 'pastel', + contrastMode: 'none', + accentCount: 4, + spanCount: 0, + rng: () => 0.5, + }); + assert.equal(plan.columns.length, 4); + assert.equal(plan.rejected.length, 0); + assert.ok(plan.summary.minContrast < 7); +}); + +test('planPaletteGenerator: Normal — warm and cool vibes bias candidate hue families', () => { + const warm = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { intent: 'random', vibe: 'warm', accentCount: 4, spanCount: 0, rng: () => 0.25 }); + const cool = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { intent: 'random', vibe: 'cool', accentCount: 4, spanCount: 0, rng: () => 0.25 }); + assert.ok(warm.columns.every(c => c.hue < 90 || c.hue > 340), 'warm stays in red/orange/yellow hue families'); + assert.ok(cool.columns.every(c => c.hue > 140 && c.hue < 310), 'cool stays in green/cyan/blue/violet hue families'); +}); + +test('planPaletteGenerator: Normal — added vibes produce distinct chroma ranges', () => { + const mk = vibe => planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { intent: 'random', vibe, accentCount: 1, spanCount: 0, rng: () => 0.5 }).columns[0].C; + assert.ok(mk('pastel') < mk('deep')); + assert.ok(mk('jewel') > mk('balanced')); + assert.ok(mk('neon') > mk('jewel')); + assert.ok(mk('strange') > mk('bold')); +}); + +test('planPaletteGenerator: Boundary — selected source uses the selected color hue', () => { + const plan = planPaletteGenerator([['#67809c', 'blue', 'blue']], { bg: '#101010', fg: '#f0f0f0' }, { + sourceMode: 'selected', + selectedHex: '#e8bd30', + accentCount: 3, + spanCount: 0, + }); + assert.equal(plan.sourceMode, 'selected'); + assert.equal(plan.columns.length, 3); + assert.equal(plan.columns[0].members.length, 1); + assert.ok(Math.abs(plan.baseHue - 91) < 8, 'gold-ish selected color drives the hue base'); +}); + +test('planPaletteGenerator: Boundary — generated names avoid palette collisions', () => { + const plan = planPaletteGenerator([['#111111', 'steel-blue', 'steel-blue']], { bg: '#000000', fg: '#ffffff' }, { + sourceMode: 'selected', + selectedHex: '#67809c', + accentCount: 2, + spanCount: 0, + rng: () => 0.5, + colorNames: COLOR_NAMES, + }); + assert.ok(plan.columns.every(c => !/^generated/.test(c.name)), 'generated candidates use nearest color names'); + assert.equal(new Set(plan.columns.map(c => c.name)).size, 2, 'nearest names are uniqued'); + assert.ok(plan.columns.every(c => !/[+-]\d+$/.test(c.name)), 'unique generated base names do not look like span offsets'); +}); + +test('color name table: Normal — uses filtered X11/CSS-style names', () => { + assert.ok(COLOR_NAMES.length > 100); + assert.ok(COLOR_NAMES.some(([name]) => name === 'steel-blue')); + assert.ok(COLOR_NAMES.some(([name]) => name === 'dark-olive')); + assert.ok(!COLOR_NAMES.some(([name]) => name === 'dark-olive-green')); + assert.ok(COLOR_NAMES.some(([name]) => name === 'medium-aquamarine')); + assert.ok(COLOR_NAMES.some(([name]) => name === 'medium-turquoise')); + assert.ok(COLOR_NAMES.every(([name]) => !/\d+$/.test(name)), 'numbered variants are excluded'); + assert.ok(COLOR_NAMES.every(([, hex]) => /^#[0-9a-f]{6}$/.test(hex)), 'colors are normalized hex'); +}); + +test('planPaletteGenerator: Boundary — missing neutral source falls back to configured hue', () => { + const plan = planPaletteGenerator([], {}, { baseHue: 42, accentCount: 1, spanCount: 0 }); + assert.ok(Math.abs(plan.baseHue - 42) < 0.001); + assert.equal(plan.columns.length, 1); +}); + +test('planPaletteGenerator: Normal — analogous and triadic schemes choose distinct hue layouts', () => { + const base = { baseHue: 30, accentCount: 3, spanCount: 0 }; + const analogous = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { ...base, scheme: 'analogous' }); + const triadic = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { ...base, scheme: 'triadic' }); + assert.notDeepEqual( + analogous.columns.map(c => Math.round(c.hue)), + triadic.columns.map(c => Math.round(c.hue)), + ); + assert.ok(Math.abs(triadic.columns[1].hue - ((triadic.columns[0].hue + 120) % 360)) < 1); +}); + +test('entriesForGeneratedColumn: Normal — converts one preview column to stable palette entries', () => { + const plan = planPaletteGenerator([], { bg: '#000000', fg: '#ffffff' }, { accentCount: 1, spanCount: 1 }); + const entries = entriesForGeneratedColumn(plan.columns[0]); + assert.equal(entries.length, 1); + assert.ok(entries.every(e => e[2] === plan.columns[0].columnId)); + assert.deepEqual(entries.map(e => e[1]), [plan.columns[0].name]); +}); + +test('entriesForGeneratedColumn: Boundary — empty and partial columns are safe', () => { + assert.deepEqual(entriesForGeneratedColumn(null), []); + assert.deepEqual(entriesForGeneratedColumn({ name: 'candidate', members: [{ hex: '#123456', name: 'candidate' }] }), [['#123456', 'candidate', 'candidate']]); + assert.deepEqual(entriesForGeneratedColumn({ members: [{ hex: '#abcdef', name: 'unnamed' }] }), [['#abcdef', 'unnamed', 'generated']]); +}); + test('groundColumnMembersFromPalette: Normal — sorts bg, ground+N steps, then fg', () => { const members = groundColumnMembersFromPalette([ ['#ffffff', 'bg', 'ground'], @@ -319,6 +697,18 @@ test('inline-integrity: theme-studio.html contains the app-core.js body verbatim assert.ok(html.includes(body), 'generated page is missing the app-core.js body verbatim'); }); +test('inline-integrity: theme-studio.html contains palette-generator-core.js verbatim', () => { + const body = stripExports(readFileSync(here + 'palette-generator-core.js', 'utf8')); + const html = readFileSync(here + 'theme-studio.html', 'utf8'); + assert.ok(html.includes(body), 'generated page is missing palette-generator-core.js verbatim'); +}); + +test('inline-integrity: theme-studio.html contains palette-generator-ui.js verbatim', () => { + const body = stripExports(readFileSync(here + 'palette-generator-ui.js', 'utf8')); + const html = readFileSync(here + 'theme-studio.html', 'utf8'); + assert.ok(html.includes(body), 'generated page is missing palette-generator-ui.js verbatim'); +}); + test('inline-integrity: theme-studio.html contains palette-actions.js verbatim', () => { const body = stripExports(readFileSync(here + 'palette-actions.js', 'utf8')); const html = readFileSync(here + 'theme-studio.html', 'utf8'); @@ -330,3 +720,52 @@ test('inline-integrity: theme-studio.html contains browser-gates.js verbatim', ( const html = readFileSync(here + 'theme-studio.html', 'utf8'); assert.ok(html.includes(body), 'generated page is missing browser-gates.js verbatim'); }); + +// resolveSyntaxFg: an unset syntax category resolves through the Emacs inherit +// chain (the way the generated theme renders), not to the flat default fg. +test('resolveSyntaxFg: a set category uses its own foreground', () => { + assert.equal(resolveSyntaxFg('kw', { kw: { fg: '#aaaaaa' } }, '#ffffff'), '#aaaaaa'); +}); +test('resolveSyntaxFg: unset cmd inherits cm (comment-delimiter -> comment)', () => { + assert.equal(resolveSyntaxFg('cmd', { cm: { fg: '#888888' }, cmd: { fg: null } }, '#ffffff'), '#888888'); +}); +test('resolveSyntaxFg: unset doc inherits str (doc -> string)', () => { + assert.equal(resolveSyntaxFg('doc', { str: { fg: '#00aa00' }, doc: { fg: null } }, '#ffffff'), '#00aa00'); +}); +test('resolveSyntaxFg: unset prop inherits var (property-name -> variable-name)', () => { + assert.equal(resolveSyntaxFg('prop', { var: { fg: '#0000aa' }, prop: { fg: null } }, '#ffffff'), '#0000aa'); +}); +test('resolveSyntaxFg: unset fnc inherits fnd (function-call -> function-name)', () => { + assert.equal(resolveSyntaxFg('fnc', { fnd: { fg: '#aa00aa' }, fnc: { fg: null } }, '#ffffff'), '#aa00aa'); +}); +test('resolveSyntaxFg: dec is pinned to ty even when dec has its own fg', () => { + assert.equal(resolveSyntaxFg('dec', { ty: { fg: '#9b5fd0' }, dec: { fg: '#e8bd30' } }, '#ffffff'), '#9b5fd0'); +}); +test('resolveSyntaxFg: an unset chain bottoms out at the default fg', () => { + assert.equal(resolveSyntaxFg('cmd', { cm: { fg: null }, cmd: { fg: null } }, '#ffffff'), '#ffffff'); +}); +test('resolveSyntaxFg: a category with no inherit and no fg uses the default fg', () => { + assert.equal(resolveSyntaxFg('kw', { kw: { fg: null } }, '#ffffff'), '#ffffff'); +}); + +// resolveUiAttr: an unset ui face attribute resolves through the Emacs built-in +// ui inherit chain (mode-line-inactive -> mode-line, line-number-current-line -> +// line-number), returning null when nothing up the chain is set (caller floors it). +test('resolveUiAttr: a set ui face uses its own attribute', () => { + assert.equal(resolveUiAttr('mode-line', 'fg', { 'mode-line': { fg: '#111111' } }), '#111111'); +}); +test('resolveUiAttr: unset mode-line-inactive inherits mode-line', () => { + assert.equal(resolveUiAttr('mode-line-inactive', 'bg', + { 'mode-line': { bg: '#222222' }, 'mode-line-inactive': { bg: null } }), '#222222'); +}); +test('resolveUiAttr: unset line-number-current-line inherits line-number', () => { + assert.equal(resolveUiAttr('line-number-current-line', 'fg', + { 'line-number': { fg: '#333333' }, 'line-number-current-line': { fg: null } }), '#333333'); +}); +test('resolveUiAttr: returns null when nothing up the chain is set', () => { + assert.equal(resolveUiAttr('mode-line-inactive', 'fg', + { 'mode-line': { fg: null }, 'mode-line-inactive': { fg: null } }), null); +}); +test('resolveUiAttr: a face with no inherit and an unset attribute returns null', () => { + assert.equal(resolveUiAttr('region', 'bg', { 'region': { bg: null } }), null); +}); diff --git a/scripts/theme-studio/test_generate.py b/scripts/theme-studio/test_generate.py index 4e9afb1e5..ed2ce74ba 100644 --- a/scripts/theme-studio/test_generate.py +++ b/scripts/theme-studio/test_generate.py @@ -78,9 +78,11 @@ class ColormathInlining(unittest.TestCase): class AssembledPage(unittest.TestCase): PLACEHOLDERS = [ "STYLES_CSS", "APP_JS", "APP_CORE_J", "APP_UTIL_J", + "PALETTE_GENERATOR_CORE_J", "PALETTE_GENERATOR_UI_J", "PALETTE_ACTIONS_J", "BROWSER_GATES_J", "COLORMATH_J", "SAMPLES_J", "PALETTE_J", "CATS_J", "UIFACES_J", "UIMAP_J", "APPS_J", "SYNTAX_J", "MAP_J", + "COLOR_NAMES_J", ] def test_every_placeholder_is_substituted(self): @@ -101,6 +103,12 @@ class AssembledPage(unittest.TestCase): # app-util.js inlines verbatim after its import line is stripped. self.assertIn(generate.APP_UTIL_BODY, generate.HTML) + def test_page_carries_palette_generator_core_verbatim(self): + self.assertIn(generate.PALETTE_GENERATOR_CORE_BODY, generate.HTML) + + def test_page_carries_palette_generator_ui_verbatim(self): + self.assertIn(generate.PALETTE_GENERATOR_UI_BODY, generate.HTML) + def test_page_carries_palette_actions_verbatim(self): self.assertIn(generate.PALETTE_ACTIONS_BODY, generate.HTML) diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index b815e5bfa..d05ac912f 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -42,6 +42,7 @@ .boxctl .cstepbtn{width:18px} .legctl{margin:0 0 8px;display:flex;gap:8px;align-items:center} .cat{color:#b4b1a2} .ex{font-size:17px} + .crerr{display:inline-block;margin-left:8px;padding:0 4px;border-radius:3px;background:#2b130e;color:#cb6b4d;border:1px solid #7b3324;font:9pt monospace;vertical-align:middle} .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} .sbtn.on{background:#0d0b0a;color:#cdced1;border-color:#8a9496} .pals{display:flex;flex-direction:row;flex-wrap:wrap;gap:10px;align-items:flex-start} @@ -98,6 +99,24 @@ .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:0;padding:0} @@ -112,7 +131,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} @@ -162,6 +184,19 @@ <div id="pkchips" class="pkchips"></div> </div> </div> + <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> <div class="pals" id="pals"></div> <div class="palwarn" id="palwarn"></div> </section> @@ -205,7 +240,8 @@ </section> </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", "}"]]], "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", "\"$@\""]]]}, 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", "@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", {}], ["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", {}], ["magit-section-secondary-heading", "section secondary heading", {}], ["magit-section-heading-selection", "section heading selection", {}], ["magit-section-highlight", "section highlight", {}], ["magit-section-child-count", "section child count", {}], ["magit-diff-added", "diff added", {}], ["magit-diff-added-highlight", "diff added highlight", {}], ["magit-diff-removed", "diff removed", {}], ["magit-diff-removed-highlight", "diff removed highlight", {}], ["magit-diff-context", "diff context", {}], ["magit-diff-context-highlight", "diff context highlight", {}], ["magit-diff-file-heading", "diff file heading", {}], ["magit-diff-file-heading-highlight", "diff file heading highlight", {}], ["magit-diff-file-heading-selection", "diff file heading selection", {}], ["magit-diff-hunk-heading", "diff hunk heading", {}], ["magit-diff-hunk-heading-highlight", "diff hunk heading highlight", {}], ["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", {}], ["magit-diffstat-added", "diffstat added", {}], ["magit-diffstat-removed", "diffstat removed", {}], ["magit-branch-current", "branch current", {}], ["magit-branch-local", "branch local", {}], ["magit-branch-remote", "branch remote", {}], ["magit-branch-remote-head", "branch remote head", {}], ["magit-branch-upstream", "branch upstream", {}], ["magit-branch-warning", "branch warning", {}], ["magit-head", "head", {}], ["magit-tag", "tag", {}], ["magit-hash", "hash", {}], ["magit-filename", "filename", {}], ["magit-dimmed", "dimmed", {}], ["magit-keyword", "keyword", {}], ["magit-keyword-squash", "keyword squash", {}], ["magit-refname", "refname", {}], ["magit-refname-stash", "refname stash", {}], ["magit-refname-wip", "refname wip", {}], ["magit-refname-pullreq", "refname pullreq", {}], ["magit-log-author", "log author", {}], ["magit-log-date", "log date", {}], ["magit-log-graph", "log graph", {}], ["magit-header-line", "header line", {}], ["magit-header-line-key", "header line key", {}], ["magit-header-line-log-select", "header line log select", {}], ["magit-process-ok", "process ok", {}], ["magit-process-ng", "process ng", {}], ["magit-mode-line-process", "mode line process", {}], ["magit-mode-line-process-error", "mode line process error", {}], ["magit-bisect-good", "bisect good", {}], ["magit-bisect-bad", "bisect bad", {}], ["magit-bisect-skip", "bisect skip", {}], ["magit-blame-heading", "blame heading", {}], ["magit-blame-highlight", "blame highlight", {}], ["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", {}], ["magit-blame-margin", "blame margin", {}], ["magit-cherry-equivalent", "cherry equivalent", {}], ["magit-cherry-unmatched", "cherry unmatched", {}], ["magit-signature-good", "signature good", {}], ["magit-signature-bad", "signature bad", {}], ["magit-signature-untrusted", "signature untrusted", {}], ["magit-signature-expired", "signature expired", {}], ["magit-signature-expired-key", "signature expired key", {}], ["magit-signature-revoked", "signature revoked", {}], ["magit-signature-error", "signature error", {}], ["magit-reflog-commit", "reflog commit", {}], ["magit-reflog-amend", "reflog amend", {}], ["magit-reflog-merge", "reflog merge", {}], ["magit-reflog-checkout", "reflog checkout", {}], ["magit-reflog-reset", "reflog reset", {}], ["magit-reflog-rebase", "reflog rebase", {}], ["magit-reflog-cherry-pick", "reflog cherry pick", {}], ["magit-reflog-remote", "reflog remote", {}], ["magit-reflog-other", "reflog other", {}], ["magit-sequence-pick", "sequence pick", {}], ["magit-sequence-stop", "sequence stop", {}], ["magit-sequence-part", "sequence part", {}], ["magit-sequence-head", "sequence head", {}], ["magit-sequence-drop", "sequence drop", {}], ["magit-sequence-done", "sequence done", {}], ["magit-sequence-onto", "sequence onto", {}], ["magit-sequence-exec", "sequence exec", {}], ["magit-left-margin", "left margin", {}], ["git-commit-comment-action", "git commit comment action", {}], ["git-commit-comment-branch-local", "git commit comment branch local", {}], ["git-commit-comment-branch-remote", "git commit comment branch remote", {}], ["git-commit-comment-detached", "git commit comment detached", {}], ["git-commit-comment-file", "git commit comment file", {}], ["git-commit-comment-heading", "git commit comment heading", {}], ["git-commit-keyword", "git commit keyword", {}], ["git-commit-nonempty-second-line", "git commit nonempty second line", {}], ["git-commit-overlong-summary", "git commit overlong summary", {}], ["git-commit-summary", "git commit summary", {}], ["git-commit-trailer-token", "git commit trailer token", {}], ["git-commit-trailer-value", "git commit trailer value", {}]]}, "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", {"bold": true}], ["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", {}]]}, "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"}]]}, "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", {}], ["lsp-signature-highlight-function-argument", "signature highlight function argument", {}], ["lsp-signature-posframe", "signature posframe", {}], ["lsp-face-highlight-read", "face highlight read", {}], ["lsp-face-highlight-write", "face highlight write", {}], ["lsp-face-highlight-textual", "face highlight textual", {}], ["lsp-face-rename", "face rename", {}], ["lsp-rename-placeholder-face", "rename placeholder", {}], ["lsp-inlay-hint-face", "inlay hint", {}], ["lsp-inlay-hint-parameter-face", "inlay hint parameter", {}], ["lsp-inlay-hint-type-face", "inlay hint type", {}], ["lsp-details-face", "details", {}], ["lsp-installation-buffer-face", "installation buffer", {}], ["lsp-installation-finished-buffer-face", "installation finished buffer", {}]]}, "git-gutter": {"label": "git-gutter", "preview": "gitgutter", "faces": [["git-gutter:added", "added", {"fg": "#00ff00", "bold": true, "inherit": "default"}], ["git-gutter:modified", "modified", {"fg": "#ff00ff", "bold": true, "inherit": "default"}], ["git-gutter:deleted", "deleted", {"fg": "#ff0000", "bold": true, "inherit": "default"}], ["git-gutter:unchanged", "unchanged", {"bg": "#ffff00", "inherit": "default"}], ["git-gutter:separator", "separator", {"fg": "#00ffff", "bold": true, "inherit": "default"}]]}, "flycheck": {"label": "flycheck", "preview": "flycheck", "faces": [["flycheck-error", "error", {"underline": true}], ["flycheck-warning", "warning", {"underline": true}], ["flycheck-info", "info", {"underline": true}], ["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", {"inherit": "flycheck-error-list-id", "box": {"style": "released", "width": 1, "color": null}}], ["flycheck-error-list-highlight", "error list highlight", {"bold": true}], ["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", {"inherit": "highlight"}], ["dirvish-hl-line-inactive", "hl line inactive", {"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": true, "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": true, "inherit": "dired-ignored"}], ["dirvish-subtree-state", "subtree state", {"bg": "unspecified", "underline": true, "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", "bold": true}], ["dirvish-narrow-match-face-1", "narrow match 1", {"fg": "#8f0075", "bold": true}], ["dirvish-narrow-match-face-2", "narrow match 2", {"fg": "#145a00", "bold": true}], ["dirvish-narrow-match-face-3", "narrow match 3", {"fg": "#804000", "bold": true}], ["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": true, "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", "bold": true}], ["alert-low-face", "low", {"fg": "#00008b"}], ["alert-moderate-face", "moderate", {"fg": "#ffd700", "bold": true}], ["alert-normal-face", "normal", {}], ["alert-trivial-face", "trivial", {"fg": "#9400d3"}], ["alert-urgent-face", "urgent", {"fg": "#ff0000", "bold": true}]]}, "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": true}], ["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", {}], ["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", {"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": "font-lock-warning-face"}], ["consult-narrow-indicator", "narrow indicator", {"inherit": "warning"}], ["consult-preview-insertion", "preview insertion", {"inherit": "region"}], ["consult-preview-line", "preview line", {"inherit": "consult-preview-insertion"}], ["consult-preview-match", "preview match", {"inherit": "isearch"}], ["consult-separator", "separator", {"fg": "#ccc"}]]}, "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", {"strike": true, "inherit": "shadow"}], ["embark-collect-group-title", "collect group title", {"italic": true, "inherit": "shadow"}], ["embark-keybinding", "keybinding", {"inherit": "success"}], ["embark-keybinding-repeat", "keybinding repeat", {"inherit": "font-lock-builtin-face"}], ["embark-keymap", "keymap", {"italic": true}], ["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", {"bold": true, "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", "bold": true}], ["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", {"inherit": "font-lock-variable-name-face"}]]}, "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", {}], ["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", {"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", {"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": "generic", "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", {"bold": true, "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": true, "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": true}], ["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", "bold": true}], ["orderless-match-face-1", "match 1", {"fg": "#8f0075", "bold": true}], ["orderless-match-face-2", "match 2", {"fg": "#145a00", "bold": true}], ["orderless-match-face-3", "match 3", {"fg": "#804000", "bold": true}]]}, "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", {"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", {"bold": true}], ["prescient-secondary-highlight", "secondary highlight", {"underline": true, "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", {"bold": true, "inherit": "font-lock-string-face"}], ["transient-delimiter", "delimiter", {"inherit": "shadow"}], ["transient-disabled-suffix", "disabled suffix", {"fg": "#000000", "bg": "#ff0000", "bold": true}], ["transient-enabled-suffix", "enabled suffix", {"fg": "#000000", "bg": "#00ff00", "bold": true}], ["transient-heading", "heading", {"inherit": "font-lock-keyword-face"}], ["transient-higher-level", "higher level", {"box": {"style": "line", "width": 1, "color": "grey60"}}], ["transient-inactive-argument", "inactive argument", {"inherit": "shadow"}], ["transient-inactive-value", "inactive value", {"inherit": "shadow"}], ["transient-inapt-argument", "inapt argument", {"bold": true, "inherit": "shadow"}], ["transient-inapt-suffix", "inapt suffix", {"italic": true, "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", {"bold": true, "inherit": "font-lock-string-face"}]]}, "vertico": {"label": "vertico", "preview": "generic", "faces": [["vertico-current", "current", {"inherit": "highlight"}], ["vertico-group-separator", "group separator", {"strike": true, "inherit": "vertico-group-title"}], ["vertico-group-title", "group title", {"italic": true, "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", {"italic": true, "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-tag-face", "annotation tag", {"underline": true, "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-type-face", "annotation type", {"bold": true, "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-value-face", "annotation value", {"italic": true, "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", {"bold": true}], ["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", {"bold": true}], ["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", {"italic": true, "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": true}], ["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", {"italic": true}], ["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": true, "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", {"italic": true}], ["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", {"bold": true, "italic": true}], ["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": true}], ["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 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", "\"$@\""]]]}, 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-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", {}], ["magit-section-secondary-heading", "section secondary heading", {}], ["magit-section-heading-selection", "section heading selection", {}], ["magit-section-highlight", "section highlight", {}], ["magit-section-child-count", "section child count", {}], ["magit-diff-added", "diff added", {}], ["magit-diff-added-highlight", "diff added highlight", {}], ["magit-diff-removed", "diff removed", {}], ["magit-diff-removed-highlight", "diff removed highlight", {}], ["magit-diff-context", "diff context", {}], ["magit-diff-context-highlight", "diff context highlight", {}], ["magit-diff-file-heading", "diff file heading", {}], ["magit-diff-file-heading-highlight", "diff file heading highlight", {}], ["magit-diff-file-heading-selection", "diff file heading selection", {}], ["magit-diff-hunk-heading", "diff hunk heading", {}], ["magit-diff-hunk-heading-highlight", "diff hunk heading highlight", {}], ["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", {}], ["magit-diffstat-added", "diffstat added", {}], ["magit-diffstat-removed", "diffstat removed", {}], ["magit-branch-current", "branch current", {}], ["magit-branch-local", "branch local", {}], ["magit-branch-remote", "branch remote", {}], ["magit-branch-remote-head", "branch remote head", {}], ["magit-branch-upstream", "branch upstream", {}], ["magit-branch-warning", "branch warning", {}], ["magit-head", "head", {}], ["magit-tag", "tag", {}], ["magit-hash", "hash", {}], ["magit-filename", "filename", {}], ["magit-dimmed", "dimmed", {}], ["magit-keyword", "keyword", {}], ["magit-keyword-squash", "keyword squash", {}], ["magit-refname", "refname", {}], ["magit-refname-stash", "refname stash", {}], ["magit-refname-wip", "refname wip", {}], ["magit-refname-pullreq", "refname pullreq", {}], ["magit-log-author", "log author", {}], ["magit-log-date", "log date", {}], ["magit-log-graph", "log graph", {}], ["magit-header-line", "header line", {}], ["magit-header-line-key", "header line key", {}], ["magit-header-line-log-select", "header line log select", {}], ["magit-process-ok", "process ok", {}], ["magit-process-ng", "process ng", {}], ["magit-mode-line-process", "mode line process", {}], ["magit-mode-line-process-error", "mode line process error", {}], ["magit-bisect-good", "bisect good", {}], ["magit-bisect-bad", "bisect bad", {}], ["magit-bisect-skip", "bisect skip", {}], ["magit-blame-heading", "blame heading", {}], ["magit-blame-highlight", "blame highlight", {}], ["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", {}], ["magit-blame-margin", "blame margin", {}], ["magit-cherry-equivalent", "cherry equivalent", {}], ["magit-cherry-unmatched", "cherry unmatched", {}], ["magit-signature-good", "signature good", {}], ["magit-signature-bad", "signature bad", {}], ["magit-signature-untrusted", "signature untrusted", {}], ["magit-signature-expired", "signature expired", {}], ["magit-signature-expired-key", "signature expired key", {}], ["magit-signature-revoked", "signature revoked", {}], ["magit-signature-error", "signature error", {}], ["magit-reflog-commit", "reflog commit", {}], ["magit-reflog-amend", "reflog amend", {}], ["magit-reflog-merge", "reflog merge", {}], ["magit-reflog-checkout", "reflog checkout", {}], ["magit-reflog-reset", "reflog reset", {}], ["magit-reflog-rebase", "reflog rebase", {}], ["magit-reflog-cherry-pick", "reflog cherry pick", {}], ["magit-reflog-remote", "reflog remote", {}], ["magit-reflog-other", "reflog other", {}], ["magit-sequence-pick", "sequence pick", {}], ["magit-sequence-stop", "sequence stop", {}], ["magit-sequence-part", "sequence part", {}], ["magit-sequence-head", "sequence head", {}], ["magit-sequence-drop", "sequence drop", {}], ["magit-sequence-done", "sequence done", {}], ["magit-sequence-onto", "sequence onto", {}], ["magit-sequence-exec", "sequence exec", {}], ["magit-left-margin", "left margin", {}], ["git-commit-comment-action", "git commit comment action", {}], ["git-commit-comment-branch-local", "git commit comment branch local", {}], ["git-commit-comment-branch-remote", "git commit comment branch remote", {}], ["git-commit-comment-detached", "git commit comment detached", {}], ["git-commit-comment-file", "git commit comment file", {}], ["git-commit-comment-heading", "git commit comment heading", {}], ["git-commit-keyword", "git commit keyword", {}], ["git-commit-nonempty-second-line", "git commit nonempty second line", {}], ["git-commit-overlong-summary", "git commit overlong summary", {}], ["git-commit-summary", "git commit summary", {}], ["git-commit-trailer-token", "git commit trailer token", {}], ["git-commit-trailer-value", "git commit trailer value", {}]]}, "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", {"bold": true}], ["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", {}]]}, "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"}]]}, "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", {}], ["lsp-signature-highlight-function-argument", "signature highlight function argument", {}], ["lsp-signature-posframe", "signature posframe", {}], ["lsp-face-highlight-read", "face highlight read", {}], ["lsp-face-highlight-write", "face highlight write", {}], ["lsp-face-highlight-textual", "face highlight textual", {}], ["lsp-face-rename", "face rename", {}], ["lsp-rename-placeholder-face", "rename placeholder", {}], ["lsp-inlay-hint-face", "inlay hint", {}], ["lsp-inlay-hint-parameter-face", "inlay hint parameter", {}], ["lsp-inlay-hint-type-face", "inlay hint type", {}], ["lsp-details-face", "details", {}], ["lsp-installation-buffer-face", "installation buffer", {}], ["lsp-installation-finished-buffer-face", "installation finished buffer", {}]]}, "git-gutter": {"label": "git-gutter", "preview": "gitgutter", "faces": [["git-gutter:added", "added", {"fg": "#00ff00", "bold": true, "inherit": "default"}], ["git-gutter:modified", "modified", {"fg": "#ff00ff", "bold": true, "inherit": "default"}], ["git-gutter:deleted", "deleted", {"fg": "#ff0000", "bold": true, "inherit": "default"}], ["git-gutter:unchanged", "unchanged", {"bg": "#ffff00", "inherit": "default"}], ["git-gutter:separator", "separator", {"fg": "#00ffff", "bold": true, "inherit": "default"}]]}, "flycheck": {"label": "flycheck", "preview": "flycheck", "faces": [["flycheck-error", "error", {"underline": true}], ["flycheck-warning", "warning", {"underline": true}], ["flycheck-info", "info", {"underline": true}], ["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", {"inherit": "flycheck-error-list-id", "box": {"style": "released", "width": 1, "color": null}}], ["flycheck-error-list-highlight", "error list highlight", {"bold": true}], ["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", {"inherit": "highlight"}], ["dirvish-hl-line-inactive", "hl line inactive", {"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": true, "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": true, "inherit": "dired-ignored"}], ["dirvish-subtree-state", "subtree state", {"bg": "unspecified", "underline": true, "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", "bold": true}], ["dirvish-narrow-match-face-1", "narrow match 1", {"fg": "#8f0075", "bold": true}], ["dirvish-narrow-match-face-2", "narrow match 2", {"fg": "#145a00", "bold": true}], ["dirvish-narrow-match-face-3", "narrow match 3", {"fg": "#804000", "bold": true}], ["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": true, "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", "bold": true}], ["alert-low-face", "low", {"fg": "#00008b"}], ["alert-moderate-face", "moderate", {"fg": "#ffd700", "bold": true}], ["alert-normal-face", "normal", {}], ["alert-trivial-face", "trivial", {"fg": "#9400d3"}], ["alert-urgent-face", "urgent", {"fg": "#ff0000", "bold": true}]]}, "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": true}], ["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", {}], ["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", {"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": "font-lock-warning-face"}], ["consult-narrow-indicator", "narrow indicator", {"inherit": "warning"}], ["consult-preview-insertion", "preview insertion", {"inherit": "region"}], ["consult-preview-line", "preview line", {"inherit": "consult-preview-insertion"}], ["consult-preview-match", "preview match", {"inherit": "isearch"}], ["consult-separator", "separator", {"fg": "#ccc"}]]}, "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", {"strike": true, "inherit": "shadow"}], ["embark-collect-group-title", "collect group title", {"italic": true, "inherit": "shadow"}], ["embark-keybinding", "keybinding", {"inherit": "success"}], ["embark-keybinding-repeat", "keybinding repeat", {"inherit": "font-lock-builtin-face"}], ["embark-keymap", "keymap", {"italic": true}], ["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", {"bold": true, "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", "bold": true}], ["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", {"inherit": "font-lock-variable-name-face"}]]}, "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", {}], ["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", {"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", {"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": "generic", "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", {"bold": true, "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": true, "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": true}], ["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", "bold": true}], ["orderless-match-face-1", "match 1", {"fg": "#8f0075", "bold": true}], ["orderless-match-face-2", "match 2", {"fg": "#145a00", "bold": true}], ["orderless-match-face-3", "match 3", {"fg": "#804000", "bold": true}]]}, "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", {"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", {"bold": true}], ["prescient-secondary-highlight", "secondary highlight", {"underline": true, "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", {"bold": true, "inherit": "font-lock-string-face"}], ["transient-delimiter", "delimiter", {"inherit": "shadow"}], ["transient-disabled-suffix", "disabled suffix", {"fg": "#000000", "bg": "#ff0000", "bold": true}], ["transient-enabled-suffix", "enabled suffix", {"fg": "#000000", "bg": "#00ff00", "bold": true}], ["transient-heading", "heading", {"inherit": "font-lock-keyword-face"}], ["transient-higher-level", "higher level", {"box": {"style": "line", "width": 1, "color": "grey60"}}], ["transient-inactive-argument", "inactive argument", {"inherit": "shadow"}], ["transient-inactive-value", "inactive value", {"inherit": "shadow"}], ["transient-inapt-argument", "inapt argument", {"bold": true, "inherit": "shadow"}], ["transient-inapt-suffix", "inapt suffix", {"italic": true, "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", {"bold": true, "inherit": "font-lock-string-face"}]]}, "vertico": {"label": "vertico", "preview": "generic", "faces": [["vertico-current", "current", {"inherit": "highlight"}], ["vertico-group-separator", "group separator", {"strike": true, "inherit": "vertico-group-title"}], ["vertico-group-title", "group title", {"italic": true, "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", {"italic": true, "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-tag-face", "annotation tag", {"underline": true, "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-type-face", "annotation type", {"bold": true, "inherit": "web-mode-annotation-face"}], ["web-mode-annotation-value-face", "annotation value", {"italic": true, "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", {"bold": true}], ["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", {"bold": true}], ["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", {"italic": true, "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": true}], ["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", {"italic": true}], ["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": true, "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", {"italic": true}], ["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", {"bold": true, "italic": true}], ["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": true}], ["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"]]; 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"], ["#aaa", "color-22", "color-22"], ["#000", "color-23", "color-23"], ["#aa0", "color-24", "color-24"], ["#070", "color-25", "color-25"], ["#daa520", "goldenrod", "goldenrod"], ["#00bfff", "deep-sky-blue", "deep-sky-blue"], ["#ee00ee", "magenta2", "magenta"], ["#00ff00", "green", "green"], ["#ffff00", "yellow", "yellow"], ["#00ffff", "cyan", "cyan"], ["#6b6b6b", "color-32", "color-32"], ["#979797", "color-33", "color-33"], ["unspecified", "color-34", "color-34"], ["#223fbf", "color-35", "color-35"], ["#8f0075", "color-36", "color-36"], ["#145a00", "color-37", "color-37"], ["#804000", "color-38", "color-38"], ["#efcbcf", "color-39", "color-39"], ["#ffd700", "gold", "gold"], ["#8b0000", "darkred", "darkred"], ["#ffa500", "orange", "orange"], ["#f0e68c", "khaki", "khaki"], ["#8b008b", "dark-magenta", "dark-magenta"], ["#ff4500", "orange-red", "orange-red"], ["#deb887", "burlywood", "burlywood"], ["#b22222", "firebrick", "firebrick"], ["#cd8500", "orange3", "orange"], ["#00008b", "dark-blue", "dark-blue"], ["#9400d3", "dark-violet", "dark-violet"], ["#6a9fb5", "color-51", "color-51"], ["#2188b6", "color-52", "color-52"], ["#75b5aa", "color-53", "color-53"], ["#0595bd", "color-54", "color-54"], ["#446674", "color-55", "color-55"], ["#48746d", "color-56", "color-56"], ["#6d8143", "color-57", "color-57"], ["#72584b", "color-58", "color-58"], ["#915b2d", "color-59", "color-59"], ["#7e5d5f", "color-60", "color-60"], ["#694863", "color-61", "color-61"], ["#843031", "color-62", "color-62"], ["#838484", "color-63", "color-63"], ["#b48d56", "color-64", "color-64"], ["#90a959", "color-65", "color-65"], ["#677174", "color-66", "color-66"], ["#2c7d6e", "color-67", "color-67"], ["#3d6837", "color-68", "color-68"], ["#ce7a4e", "color-69", "color-69"], ["#ff505b", "color-70", "color-70"], ["#e69dd6", "color-71", "color-71"], ["#eb595a", "color-72", "color-72"], ["#7f7869", "color-73", "color-73"], ["#ff9300", "color-74", "color-74"], ["#8f5536", "color-75", "color-75"], ["#d4843e", "color-76", "color-76"], ["#fc505b", "color-77", "color-77"], ["#68295b", "color-78", "color-78"], ["#5d54e1", "color-79", "color-79"], ["#ac4142", "color-80", "color-80"], ["#716e68", "color-81", "color-81"], ["#ffcc0e", "color-82", "color-82"], ["#8b1a1a", "firebrick4", "firebrick"], ["#fff8dc", "cornsilk", "cornsilk"], ["#cd5c5c", "indian-red", "indian-red"], ["#f5deb3", "wheat", "wheat"], ["#add8e6", "light-blue", "light-blue"], ["#ccc", "color-88", "color-88"], ["#cd0000", "red3", "red"], ["#0000cd", "blue3", "blue"], ["#cc9393", "color-91", "color-91"], ["#cccccc", "grey80", "grey"], ["#bebebe", "gray", "gray"], ["#88090b", "color-94", "color-94"], ["#707183", "color-95", "color-95"], ["#7388d6", "color-96", "color-96"], ["#909183", "color-97", "color-97"], ["#709870", "color-98", "color-98"], ["#907373", "color-99", "color-99"], ["#6276ba", "color-100", "color-100"], ["#858580", "color-101", "color-101"], ["#80a880", "color-102", "color-102"], ["#887070", "color-103", "color-103"], ["#1e90ff", "dodger-blue", "dodger-blue"], ["#ff69b4", "hot-pink", "hot-pink"], ["#da70d6", "orchid", "orchid"], ["#fa8072", "salmon", "salmon"], ["#00ff7f", "spring-green", "spring-green"], ["#800040", "color-109", "color-109"], ["#603f00", "color-110", "color-110"], ["#004476", "color-111", "color-111"], ["grey60", "color-112", "color-112"], ["#aa2222", "color-113", "color-113"], ["#2266ff", "color-114", "color-114"], ["#aaaa11", "color-115", "color-115"], ["#dd4488", "color-116", "color-116"], ["#22aa22", "color-117", "color-117"], ["#8fbc8f", "color-118", "color-118"], ["#5f9ea0", "color-119", "color-119"], ["#ffffe0", "lightyellow1", "lightyellow"], ["#3e3c36", "color-121", "color-121"], ["#8b8989", "snow4", "snow"], ["#242424", "grey14", "grey"], ["#cd69c9", "orchid3", "orchid"], ["#dda0dd", "plum", "plum"], ["#000053", "color-126", "color-126"], ["#001970", "color-127", "color-127"], ["#002984", "color-128", "color-128"], ["#49599a", "color-129", "color-129"], ["#9499b7", "color-130", "color-130"], ["#cdc9c9", "snow3", "snow"], ["#eeb422", "goldenrod2", "goldenrod"], ["#68228b", "darkorchid4", "darkorchid"]], SYNTAX={"kw": {"fg": "#d3d3d3", "bg": null, "bold": true, "italic": false, "underline": false, "strike": false, "box": null}, "bi": {"fg": "#d3d3d3", "bg": null, "bold": true, "italic": false, "underline": false, "strike": false, "box": null}, "pp": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "font-lock-builtin-face"}, "fnd": {"fg": "#0000ff", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "fnc": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "font-lock-function-name-face"}, "dec": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "ty": {"fg": "#e5e5e5", "bg": null, "bold": true, "italic": false, "underline": false, "strike": false, "box": null}, "prop": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "font-lock-variable-name-face"}, "con": {"fg": "#d3d3d3", "bg": null, "bold": true, "italic": false, "underline": true, "strike": false, "box": null}, "num": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "esc": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "font-lock-regexp-grouping-backslash"}, "str": {"fg": "#696969", "bg": null, "bold": false, "italic": true, "underline": false, "strike": false, "box": null}, "re": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "font-lock-string-face"}, "doc": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "font-lock-string-face"}, "cm": {"fg": "#696969", "bg": null, "bold": true, "italic": true, "underline": false, "strike": false, "box": null}, "cmd": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "font-lock-comment-face"}, "var": {"fg": "#e5e5e5", "bg": null, "bold": true, "italic": true, "underline": false, "strike": false, "box": null}, "op": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "punc": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "p": {"fg": "#000000", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "bg": {"fg": "#ffffff", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null}}, UIMAP={"cursor": {"fg": null, "bg": "#000000", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "region": {"fg": null, "bg": "#eedc82", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "hl-line": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "highlight"}, "highlight": {"fg": null, "bg": "#b4eeb4", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "mode-line": {"fg": "#000000", "bg": "#bfbfbf", "bold": false, "italic": false, "underline": false, "strike": false, "box": {"style": "released", "width": 1, "color": null}}, "mode-line-inactive": {"fg": "#333333", "bg": "#e5e5e5", "bold": false, "italic": false, "underline": false, "strike": false, "box": {"style": "line", "width": 1, "color": "#bfbfbf"}, "inherit": "mode-line"}, "fringe": {"fg": null, "bg": "#f2f2f2", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "line-number": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": ["shadow", "default"]}, "line-number-current-line": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null, "inherit": "line-number"}, "minibuffer-prompt": {"fg": "#ff00ff", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "isearch": {"fg": "#b0e2ff", "bg": "#cd00cd", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "lazy-highlight": {"fg": null, "bg": "#afeeee", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "isearch-fail": {"fg": null, "bg": "#ffc1c1", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "show-paren-match": {"fg": null, "bg": "#40e0d0", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "show-paren-mismatch": {"fg": "#ffffff", "bg": "#a020f0", "bold": false, "italic": false, "underline": false, "strike": false, "box": null}, "link": {"fg": "#3a5fcd", "bg": null, "bold": false, "italic": false, "underline": true, "strike": false, "box": null}, "error": {"fg": "#ff0000", "bg": null, "bold": true, "italic": false, "underline": false, "strike": false, "box": null}, "warning": {"fg": "#ff8c00", "bg": null, "bold": true, "italic": false, "underline": false, "strike": false, "box": null}, "success": {"fg": "#228b22", "bg": null, "bold": true, "italic": false, "underline": false, "strike": false, "box": null}, "vertical-border": {"fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "box": 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) @@ -481,6 +517,51 @@ function mergePackagesInto(map,pkgs){if(!pkgs)return;for(const app in pkgs){if(! // 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. +function resolveSyntaxFg(cat,syntax,defaultFg){ + let k=(cat==='dec')?'ty':cat; + const seen={}; + while(k&&!seen[k]){ + seen[k]=1; + const fg=syntax[k]&&syntax[k].fg; + if(fg)return fg; + k=SYNTAX_INHERIT[k]; + } + return 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){ + let f=face; + const seen={}; + while(f&&!seen[f]){ + seen[f]=1; + const v=uimap[f]&&uimap[f][attr]; + if(v)return v; + f=UI_INHERIT[f]; + } + return null; +} + // Standard swatch-dropdown option list: a default entry, then the palette. When // cur is set but no longer in the palette, surface it as a "(gone)" entry first. function optList(cur,palette){const have=cur===''||palette.some(p=>p[0]===cur);return [['','— default —'],...(have?palette:[[cur,'(gone)'],...palette])];} @@ -541,9 +622,9 @@ function fgSetFor(face,state){ 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,isRole)=>{const k=hex.toLowerCase(),cur=byHex.get(k);if(!cur)byHex.set(k,{hex:k,label});else if(isRole&&cur.label==='default')cur.label=label;}; - if(state&&state.defaultFg)add(state.defaultFg,'default',false); - for(const a of syn)add(a.hex,a.role||a.hex,true); + 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()]}; } @@ -788,9 +869,425 @@ 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';} +// 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 oklchOf(hex){return oklab2oklch(srgb2oklab(hex));} +function isPureEndpointHex(hex){const h=(hex||'').toLowerCase();return h==='#ffffff'||h==='#000000';} +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=oklab2oklch(srgb2oklab(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; + if(vibe==='muted')return 0.045+rnd()*0.035; + if(vibe==='pastel')return 0.035+rnd()*0.045; + if(vibe==='deep')return 0.085+rnd()*0.055; + if(vibe==='jewel')return 0.12+rnd()*0.075; + if(vibe==='earthy')return 0.055+rnd()*0.04; + if(vibe==='warm'||vibe==='cool')return 0.08+rnd()*0.06; + if(vibe==='neon')return 0.18+rnd()*0.09; + if(vibe==='strange')return 0.145+rnd()*0.095; + if(vibe==='balanced')return 0.075+rnd()*0.045; + return 0.12+rnd()*0.07; +} +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 -// AA/AAA rating, with the rating word. Callers compute r for their own fg/bg. -function crHtml(r){return `<span style="color:${ratingColor(r)}">${r.toFixed(1)} ${rating(r)}</span>`;} +// table verdict. Callers compute r for their own fg/bg. +function verdictFor(r,target=4.5){return r>=target?'PASS':'FAIL';} +function crHtml(r,target=4.5){const v=verdictFor(r,target);return `<span style="color:${ratingColor(r)}">${r.toFixed(1)} ${v}</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. @@ -821,21 +1318,25 @@ function mkColorDropdown(options,cur,onPick,opts={}){ 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,{bg:MAP['bg'],fg:MAP['p']},dir);if(!next)return;cur=next;paint();onPick(next);} function paintStepButtons(){ const locked=wrap.dataset.locked==='1'; left.disabled=locked||!spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},-1); right.disabled=locked||!spanNeighborHex(cur,PALETTE,{bg:MAP['bg'],fg:MAP['p']},1); } - function paint(){const nm=nameOf(cur),ttl=cur?(nm+' '+cur):nm;t.style.background=cur||'#161412';t.style.color=cur?textOn(cur):'#b4b1a2';t.dataset.val=cur||'';t.title=ttl;t.classList.toggle('is-default',!cur); - t.innerHTML=opts.compact?`<span class="cddsw" style="background:${cur||'transparent'}"></span>`:`<span class="cddsw" style="background:${cur||'transparent'}"></span>${esc(nm)}`;paintStepButtons();} + 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.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;} const pop=document.createElement('div');pop.className='cddpop'; for(const [hex,name] of options){const row=document.createElement('div');row.className='cddrow'+(hex===cur?' sel':''); - row.innerHTML=`<span class="cddsw" style="background:${hex||'transparent'}"></span><span class="cddnm">${esc(name)}</span><span class="cddhx">${hex||''}</span>`; + const shown=displayHex(hex),nm=hex?name:(opts.defaultName||name); + row.style.background=hex?'':shown;row.style.color=shown?textOn(shown):''; + row.innerHTML=`<span class="cddsw" style="background:${shown||'transparent'}"></span><span class="cddnm">${esc(nm)}</span><span class="cddhx">${hex||shown||''}</span>`; row.onclick=(ev)=>{ev.stopPropagation();cur=hex;paint();closeColorDropdown();onPick(hex);}; pop.appendChild(row);} document.body.appendChild(pop);const r=t.getBoundingClientRect(); @@ -942,8 +1443,8 @@ function buildTable(){ 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=s.bold?'bold':'normal';exTd.style.fontStyle=s.italic?'italic':'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}); - const bgd=mkColorDropdown(ddList(sf.bg||''),sf.bg||'',hex=>{const s=syntaxFace(kind);s.bg=hex||null;styleEx();styleCr();renderCode();repaintCovered();},{compact:true}); + 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'); const stBtns=mkStyleButtons(at=>syntaxFace(kind)[at],at=>{const s=syntaxFace(kind);s[at]=!s[at];styleEx();renderCode();}); @@ -1202,7 +1703,7 @@ function setColumnCount(baseHex,n){ } 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 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],oldRole=groundRoleOfEntry(PALETTE[i],{bg:MAP['bg'],fg:MAP['p']}); @@ -1321,8 +1822,9 @@ function initPicker(){const sw=document.getElementById('swatch');if(!sw)return;s 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,columnIdOf([h,name])]);const healed=healGone(name,h);document.getElementById('newname').value='';selectedIdx=null;closePicker(); + 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 slugify(themeName());} @@ -1368,7 +1870,7 @@ function boxCss(b,bg){if(!b||!b.style)return '';const w=b.width||1; 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'}`;} -function syntaxStyle(k){const s=syntaxFace(k),fg=(k==='bg'?MAP['p']:effFg(s.fg)),bg=s.bg||null,dec=(s.underline?'underline ':'')+(s.strike?'line-through':''), +function syntaxStyle(k){const s=syntaxFace(k),fg=(k==='bg'?MAP['p']:resolveSyntaxFg(k,SYNTAX,MAP['p'])),bg=s.bg||null,dec=(s.underline?'underline ':'')+(s.strike?'line-through':''), bx=boxCss(s.box,bg||MAP['bg']); return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${s.bold?'bold':'normal'};font-style:${s.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'}${bx?';box-shadow:'+bx:''}`;} // The per-row box control: none / line / raised / pressed plus optional line @@ -1376,7 +1878,7 @@ function syntaxStyle(k){const s=syntaxFace(k),fg=(k==='bg'?MAP['p']:effFg(s.fg)) function mkBoxControl(get,set,opts={}){const wrap=document.createElement('div');wrap.className='boxctl'; const s=document.createElement('select');s.className='chip';s.style.cssText='width:84px;font:10pt monospace'; [['','no box'],['line','line'],['released','raised'],['pressed','pressed']].forEach(([v,l])=>{const o=document.createElement('option');o.value=v;o.textContent=l;s.appendChild(o);}); - const dd=mkColorDropdown(ddList((get()&&get().color)||''),(get()&&get().color)||'',h=>{const cur=get();if(!cur)return;set(Object.assign({},cur,{color:h||null}));},{compact:!!opts.compact}); + const dd=mkColorDropdown(ddList((get()&&get().color)||''),(get()&&get().color)||'',h=>{const cur=get();if(!cur)return;set(Object.assign({},cur,{color:h||null}));},{compact:!!opts.compact,defaultHex:opts.defaultHex}); function paint(){const cur=get();s.value=cur&&cur.style?cur.style:'';dd.setValue(cur&&cur.color?cur.color:''); const off=!cur||!cur.style||wrap.dataset.locked==='1';dd.dataset.locked=off?'1':'';dd.classList.toggle('locked',off);if(dd.syncLocked)dd.syncLocked();} s.onchange=()=>{const cur=get();set(s.value?{style:s.value,width:cur&&cur.width||1,color:cur&&cur.color||null}:null);paint();}; @@ -1446,7 +1948,7 @@ function buildMockFrame(){ 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 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(isc)cd=withCursor(L.t); @@ -1461,9 +1963,9 @@ function buildMockFrame(){ const nFace=isc?'line-number-current-line':'line-number'; 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>`; + 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>`; html+=`<div class="bar" data-face="mode-line" style="${uiCss(ml,ml.fg||bg,ml.bg||fg)}"> init.el (Emacs Lisp) L5 git:main </div>`; - html+=`<div class="bar" data-face="mode-line-inactive" style="${uiCss(mli,mli.fg||fg,mli.bg||bg)}"> *Messages* (Fundamental) </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; @@ -1473,7 +1975,7 @@ function buildMockFrame(){ // 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});} + 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 uiFaceBlank(){return {fg:null,bg:null,bold:false,italic:false,underline:false,strike:false};} function seedFace(d){return normalizePkgFace({fg:pname(d.fg),bg:pname(d.bg),bold:d.bold,italic:d.italic,underline:d.underline,strike:d.strike,inherit:d.inherit,height:d.height,box:d.box},'default');} @@ -1490,8 +1992,8 @@ function buildPkgTable(){ 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 fgd=mkColorDropdown(ddList(f.fg||''),f.fg||'',h=>{f.fg=h||null;f.source='user';pkgChanged();},{compact:true}), - bgd=mkColorDropdown(ddList(f.bg||''),f.bg||'',h=>{f.bg=h||null;f.source='user';pkgChanged();},{compact:true}); + 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'); @@ -1626,17 +2128,31 @@ function renderGhostelPreview(){const a='ghostel',L=[]; 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>`;} 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!')); + 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 `<div style="padding:12px 16px;font:12pt/1.7 monospace;white-space:pre">${L.join('\n')}</div>`;} 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')); @@ -1829,25 +2345,47 @@ 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],hex:effFg(syntaxFace(c[0]).fg)})); + 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']}); } -// The worst-case contrast cell for a covered face: the floor over its foreground -// set against its effective background, naming the limiting foreground. Returns -// null for an out-of-scope face so the caller keeps the single-pair readout. -function worstCellHtml(face){ +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 '<span title="this overlay has no syntax foreground set yet">no fg set</span>'; - const bg=effBg(uf(face).bg),fl=floor(bg,r.set),verdict=fl.ratio>=WORST_TARGET?'PASS':'FAIL'; - const s='worst: '+fl.limitingLabel+' '+fl.limitingHex+' — '+fl.ratio.toFixed(1)+' '+verdict; - return `<span style="color:${ratingColor(fl.ratio)}" title="${esc(s)}">${esc(s)}</span>`; + 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)} ${report.worst.verdict}</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=o.bold?'bold':'normal';pv.style.fontStyle=o.italic?'italic':'normal';pv.style.textDecoration=(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none';pv.style.boxShadow=boxCss(o.box,effBg(o.bg)); - const cr=document.getElementById('uicr-'+face);if(cr){const w=worstCellHtml(face);if(w!==null){cr.innerHTML=w;}else{const efg=effFg(o.fg),ebg=effBg(o.bg),r=contrast(efg,ebg);cr.innerHTML=crHtml(r);}}} + const report=coveredContrastReport(face); + pv.querySelectorAll('.crerr').forEach(e=>e.remove()); + pv.title=''; + if(report&&report.failures&&report.failures.length){ + const badge=document.createElement('span');badge.className='crerr';badge.textContent=report.worst.ratio.toFixed(1)+' FAIL';badge.title=failureTitle(report);pv.title=badge.title;pv.appendChild(badge); + } + 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)} ${report.worst.verdict}</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){ @@ -1879,6 +2417,7 @@ function srtTable(tbId,col){tableSort[tbId]={col,asc:!(tableSort[tbId]&&tableSor 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));} function initApp(){ buildLangSel();buildAppSel();renderPalette();rebuildColorTables();renderCode();applyGround(); + initGeneratorControls(); updateTitle();initPicker();buildPkgPreview();syncMockHeight();syncPkgHeight(); } initApp(); @@ -1910,6 +2449,7 @@ if(location.hash==='#selftest')pkgSelftest(); // 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'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + 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'),dd=tr.querySelector('.cdd'),lb=tr.querySelector('.lockbtn'); @@ -1926,6 +2466,15 @@ if(location.hash==='#locktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c 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(); @@ -2019,6 +2568,12 @@ if(location.hash==='#mocktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c 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'].bold=true;buildMockFrame(); const curNum=Q('[data-face="line-number-current-line"]'); A(curNum&&/font-weight:\s*bold/.test(curNum.getAttribute('style')||''),'line-number-honors-weight'); @@ -2036,6 +2591,62 @@ if(location.hash==='#mocktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c A(pkgBtn()&&pkgBtn().classList.contains('on')&&PKGMAP[app][face].bold===true,'pkg style button visual state turns on after rebuild'); document.title='MOCKTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='mocktest';d.textContent='MOCKTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} +// 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'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + 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'); + document.title='GENERATORTEST '+(ok?'PASS':'FAIL'); + const d=document.createElement('div');d.id='generatortest';d.textContent='GENERATORTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} 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();}} @@ -2104,15 +2715,27 @@ if(location.hash==='#contrasttest'){let ok=true;const notes=[];const A=(c,n)=>{i UIMAP['region']={fg:null,bg:'#202830',bold:false,italic:false,underline:false,strike:false}; buildUITable(); const cell=document.getElementById('uicr-region'); - A(cell&&/^worst:/.test(cell.textContent),'region shows the worst-case readout: '+(cell&&cell.textContent)); - A(cell&&cell.textContent.includes('#67809c'),'limiting fg is keyword blue: '+(cell&&cell.textContent)); - A(cell&&/\b(PASS|FAIL)\b/.test(cell.textContent),'readout carries a verdict'); + A(cell&&/^\d+\.\d (PASS|FAIL)$/.test(cell.textContent.trim()),'region shows compact worst-case readout: '+(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)); + const badge=document.querySelector('#uiprev-region .crerr'); + A(badge&&badge.textContent.trim()===cell.textContent.trim(),'region preview shows failing contrast badge: '+(badge&&badge.textContent)); + A(badge&&badge.title.includes('kw (keyword) #67809c'),'preview badge hover carries failures: '+(badge&&badge.title)); + const firstFail=badge&&badge.title.split('\n')[1]; + A(firstFail&&firstFail.includes('kw (keyword) #67809c'),'failures are sorted from worst first: '+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',bold:false,italic:false,underline:false,strike:false}; + 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',bold:false,italic:false,underline:false,strike:false}; 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)); diff --git a/scripts/theme-studio/theme-studio.template.html b/scripts/theme-studio/theme-studio.template.html index 0fe8ed5de..bc96f12d1 100644 --- a/scripts/theme-studio/theme-studio.template.html +++ b/scripts/theme-studio/theme-studio.template.html @@ -42,6 +42,19 @@ STYLES_CSS</style> <div id="pkchips" class="pkchips"></div> </div> </div> + <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> <div class="pals" id="pals"></div> <div class="palwarn" id="palwarn"></div> </section> diff --git a/scripts/theme-studio/theme.json b/scripts/theme-studio/theme.json index e4af0fc2d..2254e56d6 100644 --- a/scripts/theme-studio/theme.json +++ b/scripts/theme-studio/theme.json @@ -2,17 +2,67 @@ "name": "theme", "palette": [ [ - "#070b10", + "#0a0c0d", + "silver-4", + "silver" + ], + [ + "#2c2f32", + "silver-3", + "silver" + ], + [ + "#53575c", + "silver-2", + "silver" + ], + [ + "#7c838a", + "silver-1", + "silver" + ], + [ + "#a9b2bb", + "silver", + "silver" + ], + [ + "#bac1c8", + "silver+1", + "silver" + ], + [ + "#cbd0d6", + "silver+2", + "silver" + ], + [ + "#dce0e3", + "silver+3", + "silver" + ], + [ + "#edeff1", + "silver+4", + "silver" + ], + [ + "#040609", + "blue-4", + "blue" + ], + [ + "#171f28", "blue-3", "blue" ], [ - "#232e39", + "#303d4c", "blue-2", "blue" ], [ - "#445569", + "#4b5d73", "blue-1", "blue" ], @@ -22,87 +72,142 @@ "blue" ], [ - "#8b9eb4", + "#8498af", "blue+1", "blue" ], [ - "#b1becd", + "#a1b1c3", "blue+2", "blue" ], [ - "#d7dee6", + "#c0cbd7", "blue+3", "blue" ], [ + "#dfe5eb", + "blue+4", + "blue" + ], + [ "#e4eaf8", "fg", "blue" ], [ - "#0a0c0d", - "silver-4", - "silver" + "#00040d", + "sapphire-4", + "sapphire" ], [ - "#2c2f32", - "silver-3", - "silver" + "#011933", + "sapphire-3", + "sapphire" ], [ - "#53575c", - "silver-2", - "silver" + "#02325e", + "sapphire-2", + "sapphire" ], [ - "#7c838a", - "silver-1", - "silver" + "#044e8c", + "sapphire-1", + "sapphire" ], [ - "#a9b2bb", - "silver", - "silver" + "#086cbe", + "sapphire", + "sapphire" ], [ - "#bac1c8", - "silver+1", - "silver" + "#4a8acd", + "sapphire+1", + "sapphire" ], [ - "#cbd0d6", - "silver+2", - "silver" + "#78a7db", + "sapphire+2", + "sapphire" ], [ - "#dce0e3", - "silver+3", - "silver" + "#a5c4e8", + "sapphire+3", + "sapphire" ], [ - "#edeff1", - "silver+4", - "silver" + "#d2e2f4", + "sapphire+4", + "sapphire" + ], + [ + "#020e13", + "blue zircon-4", + "blue zircon" + ], + [ + "#113640", + "blue zircon-3", + "blue zircon" + ], + [ + "#266273", + "blue zircon-2", + "blue zircon" + ], + [ + "#3d93ab", + "blue zircon-1", + "blue zircon" + ], + [ + "#55c7e6", + "blue zircon", + "blue zircon" + ], + [ + "#7ed3eb", + "blue zircon+1", + "blue zircon" + ], + [ + "#a1def1", + "blue zircon+2", + "blue zircon" + ], + [ + "#c2e9f6", + "blue zircon+3", + "blue zircon" ], [ - "#140f02", + "#e1f4fa", + "blue zircon+4", + "blue zircon" + ], + [ + "#0d0901", + "gold-5", + "gold" + ], + [ + "#332908", "gold-4", "gold" ], [ - "#43370e", + "#5e4d17", "gold-3", "gold" ], [ - "#796420", + "#8c7426", "gold-2", "gold" ], [ - "#b49534", + "#be9e37", "gold-1", "gold" ], @@ -112,26 +217,31 @@ "gold" ], [ - "#f5d577", + "#f4d370", "gold+1", "gold" ], [ - "#f7e09d", + "#f6dd91", "gold+2", "gold" ], [ - "#faebbf", + "#f9e6ae", "gold+3", "gold" ], [ - "#fcf5df", + "#fbeeca", "gold+4", "gold" ], [ + "#fdf7e5", + "gold+5", + "gold" + ], + [ "#0f0402", "terracotta-4", "terracotta" @@ -177,27 +287,32 @@ "terracotta" ], [ - "#0f0000", + "#0a0000", + "ruby-6", + "fire" + ], + [ + "#2a0000", "ruby-5", "fire" ], [ - "#360000", + "#4f0000", "ruby-4", "fire" ], [ - "#630000", + "#780000", "ruby-3", "fire" ], [ - "#940000", + "#a30000", "ruby-2", "fire" ], [ - "#c80000", + "#d00000", "ruby-1", "fire" ], @@ -207,52 +322,52 @@ "fire" ], [ - "#ff5544", + "#ff4e3e", "ruby+1", "fire" ], [ - "#ff7e6c", + "#ff7361", "ruby+2", "fire" ], [ - "#ffa191", + "#ff9281", "ruby+3", "fire" ], [ - "#ffc1b6", + "#ffafa1", "ruby+4", "fire" ], [ - "#ffe1da", + "#ffcac0", "ruby+5", "fire" ], [ - "#020005", - "regal-5", - "regal" + "#ffe5df", + "ruby+6", + "fire" ], [ - "#0f021e", + "#040009", "regal-4", "regal" ], [ - "#23073b", + "#170429", "regal-3", "regal" ], [ - "#38105b", + "#2f0c4e", "regal-2", "regal" ], [ - "#4e1a7d", + "#4a1876", "regal-1", "regal" ], @@ -262,119 +377,109 @@ "regal" ], [ - "#7d4eb2", + "#8255b5", "regal+1", "regal" ], [ - "#9572c3", + "#9f80c9", "regal+2", "regal" ], [ - "#af94d3", + "#bea9dc", "regal+3", "regal" ], [ - "#c9b7e2", + "#ded4ee", "regal+4", "regal" ], [ - "#e4dbf1", - "regal+5", - "regal" - ], - [ - "#030906", - "jade-4", - "sage" - ], - [ - "#142821", - "jade-3", - "sage" - ], - [ - "#2a4b40", - "jade-2", - "sage" - ], - [ - "#437262", - "jade-1", - "sage" - ], - [ - "#5d9b86", - "jade", - "sage" - ], - [ - "#7eaf9d", - "jade+1", - "sage" - ], - [ - "#9ec2b5", - "jade+2", - "sage" - ], - [ - "#bed6cd", - "jade+3", - "sage" - ], - [ - "#deebe6", - "jade+4", - "sage" - ], - [ "#000000", "ground", "ground" ], [ - "#040404", + "#050506", "ground+1", "ground" ], [ - "#17181a", + "#1d1e20", "ground+2", "ground" ], [ - "#2f3134", + "#393b3f", "ground+3", "ground" ], [ - "#4a4c51", + "#585a60", "ground+4", "ground" ], [ - "#666970", + "#787c84", "ground+5", "ground" ], [ - "#848790", + "#9b9fa9", "ground+6", "ground" ], [ - "#a3a7b1", + "#bfc4d0", "ground+7", "ground" ], [ - "#c3c8d4", - "ground+8", - "ground" + "#000300", + "emerald-4", + "emerald" + ], + [ + "#001600", + "emerald-3", + "emerald" + ], + [ + "#012e02", + "emerald-2", + "emerald" + ], + [ + "#024704", + "emerald-1", + "emerald" + ], + [ + "#046307", + "emerald", + "emerald" + ], + [ + "#448141", + "emerald+1", + "emerald" + ], + [ + "#73a06f", + "emerald+2", + "emerald" + ], + [ + "#a1bf9e", + "emerald+3", + "emerald" + ], + [ + "#cfdfce", + "emerald+4", + "emerald" ] ], "syntax": { @@ -442,7 +547,7 @@ "box": null }, "dec": { - "fg": "#b49534", + "fg": "#a9b2bb", "bg": null, "bold": false, "italic": false, @@ -451,7 +556,7 @@ "box": null }, "ty": { - "fg": "#f2ca49", + "fg": "#be9e37", "bg": null, "bold": false, "italic": false, @@ -487,7 +592,7 @@ "box": null }, "str": { - "fg": "#7eaf9d", + "fg": "#73a06f", "bg": null, "bold": false, "italic": false, @@ -496,8 +601,8 @@ "box": null }, "esc": { - "fg": "#9572c3", - "bg": null, + "fg": "#c3c8d4", + "bg": "#050506", "bold": false, "italic": false, "underline": false, @@ -505,7 +610,7 @@ "box": null }, "re": { - "fg": "#7eaf9d", + "fg": "#73a06f", "bg": null, "bold": false, "italic": false, @@ -514,7 +619,7 @@ "box": null }, "doc": { - "fg": "#a9b2bb", + "fg": "#d9e7f6", "bg": null, "bold": false, "italic": false, @@ -523,7 +628,7 @@ "box": null }, "cm": { - "fg": "#9572c3", + "fg": "#73a06f", "bg": null, "bold": false, "italic": true, @@ -532,7 +637,7 @@ "box": null }, "cmd": { - "fg": "#9572c3", + "fg": "#73a06f", "bg": null, "bold": false, "italic": true, @@ -570,24 +675,29 @@ }, "ui": { "cursor": { - "fg": null, - "bg": "#000000", + "fg": "#000000", + "bg": "#bac1c8", "bold": false, "italic": false, "underline": false, "strike": false }, "region": { - "fg": null, - "bg": "#eedc82", + "fg": "#f4d370", + "bg": "#332908", "bold": false, "italic": false, "underline": false, - "strike": false + "strike": false, + "box": { + "style": "line", + "width": 1, + "color": "#be9e37" + } }, "hl-line": { "fg": null, - "bg": "#17181a", + "bg": "#1d1e20", "bold": false, "italic": false, "underline": false, @@ -596,23 +706,24 @@ }, "highlight": { "fg": null, - "bg": "#43370e", + "bg": "#332908", "bold": false, "italic": false, "underline": false, - "strike": false + "strike": false, + "box": null }, "mode-line": { - "fg": "#000000", - "bg": "#67809c", - "bold": false, + "fg": "#cbd0d6", + "bg": "#303d4c", + "bold": true, "italic": false, "underline": false, "strike": false, "box": { "style": "released", "width": 1, - "color": "#53575c" + "color": "#0a0c0d" } }, "mode-line-inactive": { @@ -626,45 +737,40 @@ "box": null }, "fringe": { - "fg": null, - "bg": "#f2f2f2", - "bold": false, + "fg": "#fbeeca", + "bg": null, + "bold": true, "italic": false, "underline": false, "strike": false }, "line-number": { - "fg": null, + "fg": "#4b5d73", "bg": null, "bold": false, "italic": false, "underline": false, - "strike": false, - "inherit": [ - "shadow", - "default" - ] + "strike": false }, "line-number-current-line": { - "fg": null, + "fg": "#f6dd91", "bg": null, "bold": false, "italic": false, "underline": false, - "strike": false, - "inherit": "line-number" + "strike": false }, "minibuffer-prompt": { - "fg": "#ff00ff", + "fg": "#a1b1c3", "bg": null, - "bold": false, + "bold": true, "italic": false, "underline": false, "strike": false }, "isearch": { - "fg": "#b0e2ff", - "bg": "#cd00cd", + "fg": null, + "bg": "#332908", "bold": false, "italic": false, "underline": false, @@ -672,23 +778,24 @@ }, "lazy-highlight": { "fg": null, - "bg": "#afeeee", + "bg": "#332908", "bold": false, "italic": false, "underline": false, "strike": false }, "isearch-fail": { - "fg": null, - "bg": "#ffc1c1", - "bold": false, + "fg": "#cb6b4d", + "bg": "#000000", + "bold": true, "italic": false, "underline": false, - "strike": false + "strike": false, + "box": null }, "show-paren-match": { - "fg": null, - "bg": "#40e0d0", + "fg": "#000000", + "bg": "#448141", "bold": false, "italic": false, "underline": false, @@ -696,14 +803,14 @@ }, "show-paren-mismatch": { "fg": "#ffffff", - "bg": "#a020f0", + "bg": "#a30000", "bold": false, "italic": false, "underline": false, "strike": false }, "link": { - "fg": "#b1becd", + "fg": "#a1b1c3", "bg": "#000000", "bold": false, "italic": false, @@ -711,15 +818,20 @@ "strike": false }, "error": { - "fg": "#940000", - "bg": "#000000", + "fg": "#ff4e3e", + "bg": "#4f0000", "bold": true, "italic": false, "underline": false, - "strike": false + "strike": false, + "box": { + "style": "line", + "width": 1, + "color": "#a30000" + } }, "warning": { - "fg": "#b49534", + "fg": "#be9e37", "bg": "#000000", "bold": true, "italic": false, @@ -735,12 +847,13 @@ "strike": false }, "vertical-border": { - "fg": null, + "fg": "#303d4c", "bg": null, "bold": false, "italic": false, "underline": false, - "strike": false + "strike": false, + "box": null } }, "locks": [ @@ -750,38 +863,81 @@ "bi", "pp", "fnc", - "dec", - "ty", "prop", - "con", - "num", - "esc", - "re", - "doc", - "cm", - "cmd", "var", "op", "punc", - "str", - "ui:mode-line-inactive", - "ui:mode-line", - "ui:highlight", "fnd", - "pkg:org-mode:org-document-title", - "pkg:org-mode:org-document-info", "ui:success", "ui:warning", - "ui:error", "ui:link", + "ui:minibuffer-prompt", + "pkg:org-mode:org-document-title", + "pkg:org-mode:org-document-info", + "re", + "esc", + "doc", + "ui:line-number-current-line", + "dec", + "ui:cursor", + "ui:line-number", + "ui:vertical-border", + "ui:hl-line", + "con", + "num", + "ui:show-paren-mismatch", + "ui:show-paren-match", + "ui:fringe", + "pkg:org-mode:org-document-info-keyword", + "pkg:org-mode:org-ellipsis", + "pkg:org-mode:org-table", + "pkg:org-mode:org-table-row", + "pkg:org-mode:org-meta-line", + "pkg:org-mode:org-headline-done", "pkg:org-mode:org-headline-todo", + "ui:isearch-fail", + "pkg:org-mode:org-table-header", + "ui:highlight", + "ui:isearch", + "ui:lazy-highlight", + "ui:mode-line-inactive", + "ui:mode-line", + "cm", + "cmd", + "ui:error", + "ui:region", + "pkg:ghostel:ghostel-color-bright-red", + "pkg:ghostel:ghostel-color-bright-yellow", + "pkg:ghostel:ghostel-color-magenta", + "pkg:ghostel:ghostel-color-bright-magenta", + "pkg:ghostel:ghostel-color-bright-cyan", + "pkg:ghostel:ghostel-color-cyan", + "pkg:ghostel:ghostel-color-bright-white", + "pkg:ghostel:ghostel-color-white", + "pkg:ghostel:ghostel-default", + "pkg:ghostel:ghostel-color-black", + "pkg:ghostel:ghostel-color-bright-black", + "pkg:ghostel:ghostel-color-blue", + "pkg:ghostel:ghostel-color-bright-blue", + "pkg:ghostel:ghostel-fake-cursor-box", + "pkg:ghostel:ghostel-color-red", + "pkg:ghostel:ghostel-color-green", + "pkg:ghostel:ghostel-color-bright-green", + "pkg:ghostel:ghostel-fake-cursor", + "str", + "ty", + "pkg:ghostel:ghostel-color-yellow", + "pkg:org-mode:org-level-1", + "pkg:org-mode:org-level-3", + "pkg:org-mode:org-level-2", "pkg:org-mode:org-done", - "ui:hl-line" + "pkg:org-mode:org-priority", + "pkg:org-mode:org-todo" ], "packages": { "org-mode": { "org-document-title": { - "fg": "#b1becd", + "fg": "#f4d370", "bg": null, "bold": true, "italic": false, @@ -792,7 +948,7 @@ "height": 1.2 }, "org-document-info": { - "fg": "#bac1c8", + "fg": "#f2ca49", "bg": null, "bold": true, "italic": false, @@ -800,31 +956,30 @@ "strike": false, "inherit": null, "source": "user", - "height": 1.1 + "height": 1.15 }, "org-document-info-keyword": { - "fg": "#666970", + "fg": "#7c838a", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", + "inherit": null, "source": "user" }, "org-level-1": { - "fg": "#dce0e3", + "fg": "#f4d370", "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "user", - "height": 1.1 + "source": "user" }, "org-level-2": { - "fg": "#faebbf", + "fg": "#a5c4e8", "bg": null, "bold": false, "italic": false, @@ -834,7 +989,7 @@ "source": "user" }, "org-level-3": { - "fg": "#c9b7e2", + "fg": "#73a06f", "bg": null, "bold": false, "italic": false, @@ -844,17 +999,17 @@ "source": "user" }, "org-level-4": { - "fg": "#2ba178", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "user" + "source": "default" }, "org-level-5": { - "fg": "#cb6b4d", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -864,7 +1019,7 @@ "source": "default" }, "org-level-6": { - "fg": "#998162", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -874,7 +1029,7 @@ "source": "default" }, "org-level-7": { - "fg": "#5d9b86", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -884,7 +1039,7 @@ "source": "default" }, "org-level-8": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -894,7 +1049,7 @@ "source": "default" }, "org-headline-todo": { - "fg": null, + "fg": "#cbd0d6", "bg": null, "bold": false, "italic": false, @@ -904,7 +1059,7 @@ "source": "user" }, "org-headline-done": { - "fg": "#848790", + "fg": "#a9b2bb", "bg": null, "bold": false, "italic": true, @@ -914,37 +1069,47 @@ "source": "user" }, "org-todo": { - "fg": "#cb6b4d", - "bg": null, + "fg": "#cfdfce", + "bg": "#046307", "bold": true, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user", + "box": { + "style": "line", + "width": 1, + "color": "#448141" + } }, "org-done": { - "fg": "#666970", - "bg": null, + "fg": "#9b9fa9", + "bg": "#2c2f32", "bold": true, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "user" + "source": "user", + "box": { + "style": "line", + "width": 1, + "color": "#53575c" + } }, "org-priority": { "fg": "#f2ca49", - "bg": null, - "bold": true, + "bg": "#000000", + "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "org-tag": { - "fg": "#998162", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -954,7 +1119,7 @@ "source": "default" }, "org-tag-group": { - "fg": "#998162", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -964,7 +1129,7 @@ "source": "default" }, "org-special-keyword": { - "fg": "#5e6770", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -974,7 +1139,7 @@ "source": "default" }, "org-drawer": { - "fg": "#5e6770", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -984,7 +1149,7 @@ "source": "default" }, "org-property-value": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -994,17 +1159,17 @@ "source": "default" }, "org-checkbox": { - "fg": "#f2ca49", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", + "inherit": null, "source": "default" }, "org-checkbox-statistics-todo": { - "fg": "#cb6b4d", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1014,7 +1179,7 @@ "source": "default" }, "org-checkbox-statistics-done": { - "fg": "#5d9b86", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1024,9 +1189,9 @@ "source": "default" }, "org-warning": { - "fg": "#cb6b4d", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1034,7 +1199,7 @@ "source": "default" }, "org-link": { - "fg": "#e4eaf8", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1044,7 +1209,7 @@ "source": "default" }, "org-footnote": { - "fg": "#e4eaf8", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1054,17 +1219,17 @@ "source": "default" }, "org-date": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", + "inherit": null, "source": "default" }, "org-sexp-date": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1074,8 +1239,8 @@ "source": "default" }, "org-date-selected": { - "fg": "#000000", - "bg": "#f2ca49", + "fg": null, + "bg": null, "bold": false, "italic": false, "underline": false, @@ -1084,7 +1249,7 @@ "source": "default" }, "org-target": { - "fg": "#6624a0", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1094,7 +1259,7 @@ "source": "default" }, "org-macro": { - "fg": "#6624a0", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1104,7 +1269,7 @@ "source": "default" }, "org-cite": { - "fg": "#e4eaf8", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1114,7 +1279,7 @@ "source": "default" }, "org-cite-key": { - "fg": "#e4eaf8", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1124,87 +1289,87 @@ "source": "default" }, "org-block": { - "fg": "#e4eaf8", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", - "source": "user" + "inherit": null, + "source": "default" }, "org-block-begin-line": { - "fg": "#5e6770", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", - "source": "user" + "inherit": null, + "source": "default" }, "org-block-end-line": { - "fg": "#5e6770", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", - "source": "user" + "inherit": null, + "source": "default" }, "org-code": { - "fg": "#cb6b4d", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", + "inherit": null, "source": "default" }, "org-verbatim": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", + "inherit": null, "source": "default" }, "org-inline-src-block": { - "fg": "#cb6b4d", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", + "inherit": null, "source": "default" }, "org-quote": { - "fg": "#a9b2bb", + "fg": null, "bg": null, "bold": false, - "italic": true, + "italic": false, "underline": false, "strike": false, "inherit": null, "source": "default" }, "org-verse": { - "fg": "#a9b2bb", + "fg": null, "bg": null, "bold": false, - "italic": true, + "italic": false, "underline": false, "strike": false, "inherit": null, "source": "default" }, "org-latex-and-related": { - "fg": "#f2ca49", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1214,37 +1379,42 @@ "source": "default" }, "org-table": { - "fg": "#838d97", + "fg": "#bac1c8", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", - "source": "default" + "inherit": null, + "source": "user" }, "org-table-header": { - "fg": "#e4eaf8", - "bg": "#2f343a", - "bold": true, + "fg": "#cbd0d6", + "bg": "#4b5d73", + "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user", + "box": { + "style": "line", + "width": 1, + "color": "#53575c" + } }, "org-table-row": { - "fg": null, + "fg": "#bac1c8", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "org-formula": { - "fg": "#cb6b4d", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1255,7 +1425,7 @@ }, "org-column": { "fg": null, - "bg": "#2f343a", + "bg": null, "bold": false, "italic": false, "underline": false, @@ -1264,9 +1434,9 @@ "source": "default" }, "org-column-title": { - "fg": "#e4eaf8", - "bg": "#2f343a", - "bold": true, + "fg": null, + "bg": null, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1274,9 +1444,9 @@ "source": "default" }, "org-list-dt": { - "fg": "#f2ca49", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1284,27 +1454,27 @@ "source": "default" }, "org-meta-line": { - "fg": "#5e6770", + "fg": "#7c838a", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": "fixed-pitch", - "source": "default" + "inherit": null, + "source": "user" }, "org-ellipsis": { - "fg": "#5e6770", + "fg": "#8498af", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "org-hide": { - "fg": "#000000", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1314,7 +1484,7 @@ "source": "default" }, "org-indent": { - "fg": "#000000", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1324,7 +1494,7 @@ "source": "default" }, "org-archived": { - "fg": "#5e6770", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1344,9 +1514,9 @@ "source": "default" }, "org-dispatcher-highlight": { - "fg": "#f2ca49", - "bg": "#264364", - "bold": true, + "fg": null, + "bg": null, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1354,18 +1524,17 @@ "source": "default" }, "org-agenda-structure": { - "fg": "#e4eaf8", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default", - "height": 1.1 + "source": "default" }, "org-agenda-structure-secondary": { - "fg": "#e4eaf8", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1375,9 +1544,9 @@ "source": "default" }, "org-agenda-structure-filter": { - "fg": "#cb6b4d", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1385,31 +1554,29 @@ "source": "default" }, "org-agenda-date": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default", - "height": 1.05 + "source": "default" }, "org-agenda-date-today": { - "fg": "#f2ca49", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default", - "height": 1.05 + "source": "default" }, "org-agenda-date-weekend": { - "fg": "#838d97", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1417,9 +1584,9 @@ "source": "default" }, "org-agenda-date-weekend-today": { - "fg": "#f2ca49", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1427,7 +1594,7 @@ "source": "default" }, "org-agenda-current-time": { - "fg": "#f2ca49", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1437,7 +1604,7 @@ "source": "default" }, "org-agenda-done": { - "fg": "#5d9b86", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1447,7 +1614,7 @@ "source": "default" }, "org-agenda-dimmed-todo-face": { - "fg": "#5e6770", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1457,7 +1624,7 @@ "source": "default" }, "org-agenda-calendar-event": { - "fg": "#e4eaf8", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1467,7 +1634,7 @@ "source": "default" }, "org-agenda-calendar-sexp": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1477,7 +1644,7 @@ "source": "default" }, "org-agenda-calendar-daterange": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1487,7 +1654,7 @@ "source": "default" }, "org-agenda-diary": { - "fg": "#5d9b86", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1498,7 +1665,7 @@ }, "org-agenda-clocking": { "fg": null, - "bg": "#264364", + "bg": null, "bold": false, "italic": false, "underline": false, @@ -1508,7 +1675,7 @@ }, "org-agenda-column-dateline": { "fg": null, - "bg": "#2f343a", + "bg": null, "bold": false, "italic": false, "underline": false, @@ -1518,7 +1685,7 @@ }, "org-agenda-restriction-lock": { "fg": null, - "bg": "#cb6b4d", + "bg": null, "bold": false, "italic": false, "underline": false, @@ -1527,9 +1694,9 @@ "source": "default" }, "org-agenda-filter-category": { - "fg": "#f2ca49", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1537,9 +1704,9 @@ "source": "default" }, "org-agenda-filter-effort": { - "fg": "#f2ca49", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1547,9 +1714,9 @@ "source": "default" }, "org-agenda-filter-regexp": { - "fg": "#f2ca49", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1557,9 +1724,9 @@ "source": "default" }, "org-agenda-filter-tags": { - "fg": "#f2ca49", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1567,7 +1734,7 @@ "source": "default" }, "org-scheduled": { - "fg": "#5d9b86", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1577,9 +1744,9 @@ "source": "default" }, "org-scheduled-today": { - "fg": "#5d9b86", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1587,7 +1754,7 @@ "source": "default" }, "org-scheduled-previously": { - "fg": "#cb6b4d", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1597,7 +1764,7 @@ "source": "default" }, "org-upcoming-deadline": { - "fg": "#f2ca49", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1607,7 +1774,7 @@ "source": "default" }, "org-upcoming-distant-deadline": { - "fg": "#998162", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1617,9 +1784,9 @@ "source": "default" }, "org-imminent-deadline": { - "fg": "#cb6b4d", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -1627,7 +1794,7 @@ "source": "default" }, "org-time-grid": { - "fg": "#998162", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1638,7 +1805,7 @@ }, "org-clock-overlay": { "fg": null, - "bg": "#264364", + "bg": null, "bold": false, "italic": false, "underline": false, @@ -1647,7 +1814,7 @@ "source": "default" }, "org-mode-line-clock": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, @@ -1657,9 +1824,9 @@ "source": "default" }, "org-mode-line-clock-overrun": { - "fg": "#cb6b4d", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, @@ -2771,84 +2938,84 @@ }, "elfeed": { "elfeed-search-date-face": { - "fg": "#838d97", + "fg": "#7c838a", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-search-title-face": { - "fg": "#a9b2bb", + "fg": "#7c838a", "bg": null, "bold": false, - "italic": false, + "italic": true, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-search-unread-title-face": { - "fg": "#e4eaf8", + "fg": "#f2ca49", "bg": null, "bold": true, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-search-feed-face": { - "fg": "#5d9b86", + "fg": "#73a06f", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-search-tag-face": { - "fg": "#998162", + "fg": "#bea9dc", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-search-unread-count-face": { - "fg": "#f2ca49", + "fg": "#a1b1c3", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-search-filter-face": { - "fg": "#e4eaf8", + "fg": "#8498af", "bg": null, "bold": true, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-search-last-update-face": { - "fg": "#5e6770", + "fg": "#8498af", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-log-date-face": { "fg": "#838d97", @@ -2881,24 +3048,24 @@ "source": "default" }, "elfeed-log-info-level-face": { - "fg": "#5d9b86", + "fg": "#73a06f", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "elfeed-log-debug-level-face": { - "fg": "#5e6770", + "fg": "#c3c8d4", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" } }, "mu4e": { @@ -3275,14 +3442,14 @@ }, "ghostel": { "ghostel-default": { - "fg": "#cdced1", + "fg": "#edeff1", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-fake-cursor": { "fg": "#000000", @@ -3292,258 +3459,263 @@ "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user", + "box": { + "style": "line", + "width": 1, + "color": "#bfc4d0" + } }, "ghostel-fake-cursor-box": { - "fg": "#a9b2bb", + "fg": "#bac1c8", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-black": { - "fg": "#5e6770", + "fg": "#9b9fa9", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-red": { - "fg": "#cb6b4d", + "fg": "#ff0000", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-green": { - "fg": "#2ba178", + "fg": "#73a06f", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-yellow": { - "fg": "#f2ca49", + "fg": "#be9e37", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-blue": { - "fg": "#e4eaf8", + "fg": "#4a8acd", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-magenta": { - "fg": "#6624a0", + "fg": "#9f80c9", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-cyan": { - "fg": "#5d9b86", + "fg": "#3d93ab", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-white": { - "fg": "#a9b2bb", + "fg": "#bac1c8", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-bright-black": { - "fg": "#838d97", + "fg": "#bfc4d0", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-bright-red": { - "fg": "#de4949", + "fg": "#ff4e3e", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-bright-green": { - "fg": "#84b068", + "fg": "#a1bf9e", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-bright-yellow": { - "fg": "#eed376", + "fg": "#f6dd91", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-bright-blue": { - "fg": "#7a9abe", + "fg": "#a5c4e8", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-bright-magenta": { - "fg": "#b07fd0", + "fg": "#bea9dc", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-bright-cyan": { - "fg": "#7fc0a8", + "fg": "#7ed3eb", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" }, "ghostel-color-bright-white": { - "fg": "#e4eaf8", + "fg": "#edeff1", "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, "inherit": null, - "source": "default" + "source": "user" } }, "dashboard": { "dashboard-banner-logo-title": { - "fg": "#f2ca49", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": null, + "inherit": "default", "source": "default" }, "dashboard-text-banner": { - "fg": "#838d97", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": null, - "source": "default" + "inherit": "default", + "source": "user" }, "dashboard-heading": { - "fg": "#e4eaf8", + "fg": null, "bg": null, - "bold": true, + "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": null, + "inherit": "font-lock-keyword-face", "source": "default" }, "dashboard-items-face": { - "fg": "#cdced1", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": null, + "inherit": "widget-button", "source": "default" }, "dashboard-navigator": { - "fg": "#e4eaf8", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": null, + "inherit": "font-lock-keyword-face", "source": "default" }, "dashboard-no-items-face": { - "fg": "#5e6770", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": null, + "inherit": "widget-button", "source": "default" }, "dashboard-footer-face": { - "fg": "#998162", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": null, + "inherit": "font-lock-doc-face", "source": "default" }, "dashboard-footer-icon-face": { - "fg": "#f2ca49", + "fg": null, "bg": null, "bold": false, "italic": false, "underline": false, "strike": false, - "inherit": null, + "inherit": "dashboard-footer-face", "source": "default" } }, |
