diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 7dfb39d0e..40252a779 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -17,7 +17,7 @@ cargo run -p simple-button # Milestone 1 demo cargo run -p text-and-layout # Milestone 2 demo cargo run -p text-and-layout --release # Release mode (much faster rendering) cargo run -p widget-catalog # Browse all available widgets -cargo run -p widget-catalog -- --theme macos-dark # …in a preset's true chrome (also macos-light, fluent-{light,dark}, material3-{light,dark}, intui-{light,dark}); the title-bar ThemeSwitcher re-tints live but keeps the shapes the tree was built with +cargo run -p widget-catalog -- --theme macos-dark # …in a preset's true chrome (also macos-light, fluent-{light,dark}, kde-{light,dark}, material3-{light,dark}, intui-{light,dark}; `--theme system` starts in Native mode, Breeze's chrome on a KDE desktop); the title-bar ThemeSwitcher re-tints live but keeps the shapes the tree was built with cargo test -p teksilo-core # Test a specific crate cargo test -p teksilo-widgets # Includes layout integration tests cargo doc --no-deps --open # Generate docs @@ -91,6 +91,7 @@ cargo teksilo probe install # write the probe harness into scri cargo teksilo init # probe + brief every agent configured in the project cargo teksilo init -y # install non-interactively cargo teksilo agent install claude --user # install only Claude instructions for this user +cargo teksilo update # refresh installed harness, instructions, companion binaries (-i, -y, --dry-run, --agent, --user) cargo teksilo status # project and search model status (read-only) cargo teksilo build-vectors # MAINTAINER ONLY: re-encode the corpus (see below) ``` @@ -208,6 +209,16 @@ Tests are fully headless — no Xvfb, no GPU, no display server needed. ``` teksilo-tokens Pure data: Theme, Color, TextStyle, SpacingTokens, alignment +teksilo-kde-scheme KDE Plasma's colour model and desktop settings, with no I/O (the environment is a + getter and files are text passed in, so it runs the same in a test): the KConfig + parser and `kdeglobals` cascade (`KdeConfigLocations`), `KdeColorScheme` (a + `.colors` file or kdeglobals; stock `breeze_light` / `breeze_dark`), KDE's colour + arithmetic to the byte (`math`), the Disabled / Inactive effects, `resolve()` → + every set in every state, `with_accent` (Plasma's accent application) and + `KdeDesktop` (scheme + the user's accent + fonts + animation speed + + `widgetStyle` + `breezerc`). Depends on teksilo-tokens alone. Clean-room: written + from `engineering/docs/kde-breeze-spec.md`, never from KDE or Qt source + (`tests/clean_room_vocabulary.rs` guards it). teksilo-canvas Canvas API, RenderFrame, Path, Paint, geometry, TextBackend trait. Also `wake` (RedrawWaker/WakeKind: wake one window from any thread), `image_geometry` (ImageFit/ImageOrientation/ImageGeometry/PixelRect: placement, device-pixel @@ -516,11 +527,14 @@ teksilo-render wgpu renderer: rect/SDF/quad pipelines, atlas upload, pa teksilo-platform winit + AccessKit adapter, event translation, clipboard, OS theme, native file dialogs (FileDialogBackend trait + RfdAsyncBackend), external (OS) drag-and-drop (ExternalDndBackend trait + per-OS backends), - native (OS) menu bar (NativeMenuBackend trait + macOS NSMenu backend; behind the `native-menu` feature) + native (OS) menu bar (NativeMenuBackend trait + macOS NSMenu backend; behind the `native-menu` feature), + OS theme (`os_theme::query_native_desktop() -> NativeDesktop { os, kde }`, the KDE + reader on top of teksilo-kde-scheme; `OsThemeWatch` hears the desktop change) teksilo-app TeksiloAppBuilder, WindowManager, event loop. Exposes an async-agnostic `on_loop_tick(poll_source, FnMut() -> bool)` hook + routes `AsyncCompletionPayload` (a teksilo-core type) to a window's tree — the only core touch-points the - optional async executor needs. + optional async executor needs. Also the native-theme seam + (`NativeThemeRequest`, `resolve_native_theme`, `TeksiloAppBuilder::native_theme`). teksilo-async Optional main-thread async executor (opt-in, OFF by default). `spawn_local` / `spawn_local_with` / `spawn_blocking`, driven by teksilo-app's `on_loop_tick` hook; runtime-free core (no tokio/async-std). `!Send` futures capture `Signal`s @@ -576,10 +590,11 @@ teksilo-preview-ui Reusable 3-pane previewer GUI (navigator + canvas + knob teksilo-widgets-previewer Bundle binary that combines teksilo-widgets + teksilo-preview + teksilo-preview-ui to preview the stock catalog. Two CLI modes: standalone (whole catalog) and targeted (preview one widget). -teksilo-theme-material3 / teksilo-theme-fluent / teksilo-theme-macos +teksilo-theme-material3 / teksilo-theme-fluent / teksilo-theme-macos / teksilo-theme-kde Sibling design-language presets (tokens + Tier-3 chrome), reached as - `teksilo::prelude::{material3, fluent, macos}::{light, dark}` behind the - umbrella features `theme-material3` / `theme-fluent` / `theme-macos`. + `teksilo::prelude::{material3, fluent, macos, kde}::{light, dark}` behind the + umbrella features `theme-material3` / `theme-fluent` / `theme-macos` / + `theme-kde`. `teksilo-theme-kde` builds its colours from teksilo-kde-scheme. See **Theming** below. teksilo-inspector In-app debug introspection panel (F12), `cfg(debug_assertions)` only. Apps opt in with `install_inspector_in_debug()`. See docs/inspector.md. @@ -594,7 +609,7 @@ teksilo-fmt / cargo-teksilo-fmt / teksilo-fmt-lsp pre-commit gate), and its LSP server. ``` -Dependency flow: `tokens → canvas → core → data → widgets`, `canvas → text`, `core + data → settings`, `canvas → render → platform → app`, `settings → app`, `i18n-macros → i18n`, `macros → teksilo` (the `teksu!` proc macro, re-exported by the umbrella), `core → preview`, `preview-ui → preview + widgets`, `widgets-previewer → (preview + preview-ui + widgets)`, `widgets → scene` (scene sits at the teksilo-widgets tier and reuses the full widget catalog as its heavyweight content), `core → webview` (webview sits at the teksilo-widgets tier but depends only on core; `teksilo-app` gains an optional dep on it under its `web-view` feature for event routing), `(app + core) → async → {tokio, async-std}` (optional executor; the `on_loop_tick` hook in app and the `AsyncCompletionHandle` types in core stay async-runtime-free, so teksilo-app never depends on teksilo-async), `(core + tokens + canvas + platform) → terminal` (renders into the wgpu surface, so unlike webview it needs no widgets dep either), `(core + canvas) → automation → automation-mcp` (GUI-free toolkit + its rmcp server binary; the core-only-peer shape teksilo-data has) +Dependency flow: `tokens → canvas → core → data → widgets`, `tokens → kde-scheme → {platform, theme-kde}` (the KDE colour model reaches the OS reader and the Breeze preset, and nothing else), `canvas → text`, `core + data → settings`, `canvas → render → platform → app`, `settings → app`, `i18n-macros → i18n`, `macros → teksilo` (the `teksu!` proc macro, re-exported by the umbrella), `core → preview`, `preview-ui → preview + widgets`, `widgets-previewer → (preview + preview-ui + widgets)`, `widgets → scene` (scene sits at the teksilo-widgets tier and reuses the full widget catalog as its heavyweight content), `core → webview` (webview sits at the teksilo-widgets tier but depends only on core; `teksilo-app` gains an optional dep on it under its `web-view` feature for event routing), `(app + core) → async → {tokio, async-std}` (optional executor; the `on_loop_tick` hook in app and the `AsyncCompletionHandle` types in core stay async-runtime-free, so teksilo-app never depends on teksilo-async), `(core + tokens + canvas + platform) → terminal` (renders into the wgpu surface, so unlike webview it needs no widgets dep either), `(core + canvas) → automation → automation-mcp` (GUI-free toolkit + its rmcp server binary; the core-only-peer shape teksilo-data has) External sibling crates (maintained alongside Teksilo, released on their own cadence): `text-typeset` and `text-document`. Both are **ordinary crates.io dependencies** pinned in `[workspace.dependencies]`, so a fresh clone, a `cargo publish` and every CI job resolve them with no preparation step. `text-document` is re-exported up to the umbrella as `teksilo::text_document`, so a consumer reaches `TextDocument` without adding a second direct dependency. Working against local checkouts is a `[patch.crates-io]` in a gitignored `.cargo/config.toml` — see README, "Development environment". @@ -953,9 +968,9 @@ When a window loses OS focus, its widgets change appearance — the modern deskt - **The primitive (mirrors the global text-scale plumbing):** `WidgetTree::window_active_signal() -> Signal` (starts `true` — a window must not be born inactive), written by `set_window_active` ([crates/teksilo-core/src/widget_tree.rs](crates/teksilo-core/src/widget_tree.rs)). Surfaced as `ctx.window_active()` / `ctx.window_active_signal()` on `BuildContext`, `ctx.window_active: bool` on `PaintContext`, and `ctx.window_active()` on `EventContext`. App layer feeds it from `WindowEvent::Focused`/`Occluded` ([crates/teksilo-app/src/app.rs](crates/teksilo-app/src/app.rs), already computing `focused && !occluded`). - **Repaint, never relayout:** a focus flip calls `WidgetArena::mark_all_needs_paint_only()` (a new paint-only mark beside `mark_all_dirty`) — geometry never changes (the caret keeps its space). Cheaper than `set_theme`'s `mark_all_dirty`; makes any paint-time `ctx.window_active` reader correct with no per-widget binding. - **Automatic, default-on (correctness, not opt-in).** Two complementary layers — **theme-side** for everything that's a plain accent colour, **per-widget** only where the theme genuinely can't decide: - 1. **Accent desaturation (theme-side — the broad sweep).** When inactive, the paint walker paints against `effective_theme.for_inactive_window()` ([crates/teksilo-core/src/styles/theme.rs](crates/teksilo-core/src/styles/theme.rs) → `ColorTokens::for_inactive_window` in [crates/teksilo-tokens/src/theme.rs](crates/teksilo-tokens/src/theme.rs)), which desaturates the accent family (`accent`/`accent_hover`/`accent_pressed`/`accent_disabled`/`accent_subtle_bg`) + focus indicators (`border_focused`/`focus_ring`) toward graphite — the Qt `QPalette::Inactive` model (swap at [rendering_impl.rs](crates/teksilo-core/src/widget_tree/rendering_impl.rs)). Because every themed control resolves its accent from the live `ColorTokens` *at paint time* (e.g. Toggle reads `colors.accent`), this **one swap greys out every accent control with zero per-widget code**: `Button`(Filled/Destructive), `Toggle`, checked `Checkbox`/`RadioButton`, selected `TabBar` tab + `SegmentedControl` segment, `Slider` fill, `ProgressBar`, `Badge`, focus rings. It applies to **any preset** that populates these tokens — IntUI, **Material 3** (where `accent` = M3 *primary*; covered with no material3-specific code), future presets. Repaint-only (colours, not typography). Don't add per-widget window-active accent checks — they belong here. + 1. **Accent desaturation (theme-side, the broad sweep).** When inactive, the paint walker paints against the inactive projection of `effective_theme` (`Theme::for_inactive_window` in [crates/teksilo-core/src/styles/theme.rs](crates/teksilo-core/src/styles/theme.rs) → `ColorTokens::for_inactive_window` in [crates/teksilo-tokens/src/theme.rs](crates/teksilo-tokens/src/theme.rs)), which desaturates the accent family (`accent`/`accent_hover`/`accent_pressed`/`accent_disabled`/`accent_subtle_bg`) + focus indicators (`border_focused`/`focus_ring`) toward graphite: the Qt `QPalette::Inactive` model (swap at [rendering_impl.rs](crates/teksilo-core/src/widget_tree/rendering_impl.rs)). Because every themed control resolves its accent from the live `ColorTokens` *at paint time* (e.g. Toggle reads `colors.accent`), this **one swap greys out every accent control with zero per-widget code**: `Button`(Filled/Destructive), `Toggle`, checked `Checkbox`/`RadioButton`, selected `TabBar` tab + `SegmentedControl` segment, `Slider` fill, `ProgressBar`, `Badge`, focus rings. It applies to **any preset** that populates these tokens: IntUI, **Material 3** (where `accent` = M3 *primary*; covered with no material3-specific code), future presets. Repaint-only (colours, not typography). Don't add per-widget window-active accent checks: they belong here. **The swap is one call, `Theme::for_paint_state(PaintState { window_active, high_contrast })`** (`WidgetTree::paint_state()`): without a hook it is the generic chain (`for_inactive_window` when inactive, then `for_high_contrast` under the OS increase-contrast preference, byte-identical to before); a theme whose palette defines its own inactive / high-contrast colours registers a **`PaintProjection(fn(&Theme, PaintState) -> Theme)`** extension (`Theme::with_paint_projection`, the `DensityProjection` pattern) and the walker paints the hook's answer instead (the KDE preset registers one that paints the colour scheme's own Inactive state and its own high-contrast tokens, so its muting is scheme-driven and its focus ring stays). `for_inactive_window` / `for_high_contrast` stay the generic transforms and never consult the hook. Recipe colours baked at build time (`ColorProp::Bound`, e.g. a Filled button fill) resolve through `Theme::paint_colors` against `window_active_signal` + `BuildContext::high_contrast_signal()` (`teksilo-widgets` `styles::window_resolution_colors`), so they follow the hook and high contrast too; never call `colors.for_inactive_window()` directly from a widget. 2. **Caret hiding (per-widget — behavioural, a theme can't hide a caret).** The caret hides in an inactive window for every policy, in *both* text stacks (`RichTextEditor` rich_text/, and `TextInputField` primitives/ — base for `TextInput`/`PasswordField`/`SpinBox`/`SearchField`). The frame-loop `tick()` has no context, so `build()` registers an effect on `window_active_signal` that mirrors the value onto the editor state and hides the caret **synchronously** on deactivation (the loop may not tick while parked); blink + paint gates AND-in `state.window_active`. Restored immediately on reactivate. - 3. **Selection desaturation (per-widget — needs *view* focus + a dedicated token).** Vivid `SurfaceRole::Selected` only when **view-focused AND window-active**, else the muted `SurfaceRole::SelectedInactive`. Per-widget — *not* theme-side — because it (a) depends on *view* keyboard focus (a per-view runtime state the theme can't know) and (b) uses the hand-tuned `surface_selected_inactive` / `selection_bg_inactive` tokens so view-unfocused and window-inactive show **one** consistent muted colour (a theme desaturation of `surface_selected` would be a *different* computed gray). Composed in `StandardListItem`/`StandardTreeItem` `bg_signal` (`is_focused.and(is_window_active)`), `TableView`/`TreeTableView` selection-band paint (`view_focused.get() && ctx.window_active`), and the text engines (swap to `selection_bg_inactive`). A custom `.selection_color(..)` stays fixed (macOS semantics). `MenuList` excluded (an open menu is always active). + 3. **Selection desaturation (per-widget: needs *view* focus + a dedicated token).** Vivid `SurfaceRole::Selected` only when **view-focused AND window-active** (by default, see below), else the muted `SurfaceRole::SelectedInactive`. Per-widget, *not* theme-side, because it (a) depends on *view* keyboard focus (a per-view runtime state the theme can't know) and (b) uses the hand-tuned `surface_selected_inactive` / `selection_bg_inactive` tokens so view-unfocused and window-inactive show **one** consistent muted colour (a theme desaturation of `surface_selected` would be a *different* computed gray). Composed in `StandardListItem`/`StandardTreeItem` `bg_signal` from the style's `StandardItemStyle::selection_emphasis()` (default `SelectionEmphasis::ViewFocusedAndWindowActive`; `WindowActive` mutes only on window blur, Qt's per-window colour groups, which the KDE preset returns; build it with `SelectionEmphasis::emphasised(&cfg.is_focused, &cfg.is_window_active)` so chrome and label agree), `TableView`/`TreeTableView` selection-band paint (`view_focused.get() && ctx.window_active`), and the text engines (swap to `selection_bg_inactive`). A custom `.selection_color(..)` stays fixed (macOS semantics). `MenuList` excluded (an open menu is always active). - **Opt-in for custom content:** wrap the subtree in `DimWhenInactive::new().factor(f).child(w)` (default factor 70 %) ([crates/teksilo-core/src/dim_when_inactive.rs](crates/teksilo-core/src/dim_when_inactive.rs)), driving a node-level opacity scope from `window_active_signal`. Layout-/a11y-transparent; opacity snaps (reduced-motion-correct). Re-exported in `teksilo::prelude` as `DimWhenInactive`. - **a11y unaffected** (caret/selection/accent are all paint-only). **Test** with `WidgetTree::set_window_active(bool)` (a fresh tree is active). @@ -1178,6 +1193,11 @@ Migration status: **every themable widget is on Tier 3** — [`ComponentStyleSlo **Sibling presets.** `teksilo-theme-material3` (feature `theme-material3`) and `teksilo-theme-fluent` (feature `theme-fluent`) are complete design languages — tokens plus Tier-3 widget chrome — reachable as `teksilo::prelude::{material3, fluent}::{light, dark}`. Fluent is transcribed from WinUI's own `Common_themeresources_any.xaml` + control theme-resource dictionaries: light/dark colour dictionaries (full set on the `FluentPalette` theme extension), `ControlCornerRadius` 4 / `OverlayCornerRadius` 8, the WinUI type ramp at zero tracking, and `Control*AnimationDuration` on `ControlFastOutSlowInKeySpline` (`cubic-bezier(0,0,0,1)`). 25 style slots installed; eight are real `impl FooStyle` (button **elevation edge** — heavier stroke bottom-in-light / top-in-dark, dropped on press; two-tone high-contrast focus ring, never the accent; `ToggleSwitch` off-outline + morphing knob; filled unchecked checkbox/radio; field **accent focus underline**; two-circle slider thumb; neutral menu hover; list **selection pill** 3×16). `fluent::{light,dark}_with_accent(Color)` rebuilds the accent family around a seed. Mica/Acrylic use WinUI's own opaque fallbacks; Segoe UI Variable is named (not bundled) under the optional `system-fonts` feature. `teksilo-theme-macos` (feature `theme-macos`, `teksilo::prelude::macos`) is the third complete preset — macOS **Aqua / Dark Aqua**, with every literal tagged `[HIG]` / `[measured]` / `[derived]` at its definition because Apple publishes almost none of this (no radius table, no control heights, no focus-ring geometry, one animation duration). AppKit's semantic set — four label grades, two selection families, the bezel, the eight System Settings accents — is on the `MacOsPalette` extension. 6/10 dp radii (menus at a measured 9), a **22 dp** control height (a third under Fluent's 32), the published SF type ramp with Apple's **signed** tracking (−0.08 at 13 pt, 0 at 12, +0.06 at 11 — the only Teksilo preset that tracks non-uniformly), and Core Animation's 0.25 s on the *symmetric* `cubic-bezier(0.42,0,0.58,1)`. 28 slots installed; eight are real `impl FooStyle` (push-button **bezel** — shadow + face gradient + hairline + Dark-Aqua catch-light, dropped on press, and deliberately absent from the accent-filled default button; a focus ring that **is the accent**, built as a solid contrast-bearing band plus a translucent halo so it still clears WCAG 1.4.11; `NSSwitch` 18 dp knob in a 22 dp track; 14 dp bezelled checkbox/radio; field **accent focus halo**; plain round slider knob; menu row **accent fill + white label**; list **selection capsule**). `macos::{light,dark}_with_accent(Color)` + the `SystemAccent` enum rebuild the accent family; `linkColor` deliberately does not follow. Four WCAG deviations from Apple's own numbers are documented at their assignment with the measurement that forced each. The OS accent is **not** read (the platform layer returns only light/dark on macOS); vibrancy uses each material's opaque fallback; San Francisco is named (not bundled) under the optional `system-fonts` feature. Still pending: image-backed styles, `ImageTheme` TOML loader, a GTK4-Adwaita preset. +`teksilo-theme-kde` (feature `theme-kde`, `teksilo::prelude::kde`) is the fourth complete preset: KDE Plasma's **Breeze** as Plasma 6.8 ships it (`BREEZE_VERSION`). Unlike the others its colours are a KDE colour scheme, not a hand-picked palette: `teksilo-kde-scheme` reads and resolves it as KDE does, `kde::{light,dark}` are Breeze Light and Dark, `from_scheme(&KdeColorScheme)` is any scheme (light or dark as KDE judges it), `{light,dark}_with_accent(Color)` + `PlasmaAccent` apply Plasma's accent the way its colour settings do, `from_desktop(&KdeDesktop, density)` is a configured desktop (its scheme with the user's accent, its fonts, `AnimationDurationFactor` scaling the motion, `breezerc` as `BreezeStyleConfig`), and `native_theme(&desktop, density) -> Option` is the seam form (`None` unless the widget style is Breeze). The `KdePalette` extension keeps every scheme colour in every state; the colour tokens are those colours lifted to WCAG by rule where the scheme falls short (D1 to D9 in `color.rs`, each lift and its premise tested). Every literal carries a provenance tag defined in the specification's table (`[M]` / `[S]` / `[Hp]` Breeze style facts, `[qt]` / `[measured-qt]` Qt behaviour, `[derived]`, `[wcag]`, `[unpublished]`), and the specification (`engineering/docs/kde-breeze-spec.md`) is where a Breeze number is argued; the reference data is `tools/kde_golden/vectors`. Geometry: one 5 px radius, text-driven control heights, a 2 px focus ring half inside the control in the scheme's focus colour, 100 ms linear motion, Noto Sans and Hack sizes and line boxes (drawn in the bundled faces unless the crate's own `bundled-fonts` / `system-fonts` features are on, which the umbrella does not forward). Real `impl FooStyle` blocks cover the buttons, indicators, fields, combo box, spin box, slider, menu, popup frame, tooltip, list rows, tables, tabs, scroll bar and progress bar (`styles/`); the card, panel, dialog and the like are the recipe styles built with Breeze's metrics (`styles::recipes`), and the crate's slot tests give the reason for every slot left to the widget. It uses three framework hooks added for it: a `PaintProjection` (the scheme's own Inactive and high-contrast colours, see **Window-Active Appearance**), `StandardItemStyle::selection_emphasis` returning `WindowActive`, and `MenuItemStyle::pressed_label_role`. `tests/breeze_reference.rs` compares the controls' pixels with Plasma's through the production renderer. The crate's `//!` Known limitations are the authority on what is not Breeze's. KDE, Plasma and Breeze are KDE e.V.'s names, used only to say what is reproduced (`NOTICE`, `TRADEMARKS.md`). + +**Native mode and the native-theme seam.** `ThemeMode::Native` adopts the live desktop's colours; the KDE preset is a separate thing that can also supply Native mode's shapes. `teksilo_platform::os_theme::query_native_desktop() -> NativeDesktop { os: OsThemeColors, kde: Option }` is what the desktop reports (KDE through teksilo-kde-scheme; GNOME and Cinnamon through the portal and the GTK theme's CSS; macOS and Windows only light or dark, from winit). `teksilo-app` resolves every OS-following theme through one function: `resolve_native_theme(factory, &NativeThemeRequest { desktop, dark, density })` asks the app's `TeksiloAppBuilder::native_theme` factory and otherwise returns `presets::intui::from_os` (Int UI in the desktop's colours); the window manager then gives the result the id `system` and the window's density, and sets no theme when a re-read changes nothing it was resolved from. `native_theme_kde()` (`TeksiloAppBuilderKdeExt`, feature `theme-kde`) installs `kde::native_theme` as the factory, so Native mode shows Breeze on a Breeze desktop and Int UI in its colours elsewhere. `OsThemeWatch` (Linux: portal `SettingChanged`, `KGlobalSettings.notifyChange`, kconfig `ConfigChanged` on `kdeglobals` / `breezerc`; 150 ms debounce; an mtime check when a window takes focus covers a session with no bus) posts `OsThemeChanged` and the window manager reads the desktop again, refreshing reduced motion too. **Switching re-tints but keeps the shapes the tree was built with**: a widget picks its chrome at build, so the factory's shapes reach only a window built in Native mode; a later switch applies its colours. Reference: [docs/native-theme.md](docs/native-theme.md). + + **Roles** stay relevant — they name *what* a value represents (`TextRole::Primary`, `SurfaceRole::Hover`), resolved against the current theme at paint time. Widget builders accept `impl Into` so any of `Color | Signal | TextRole | SurfaceRole | BorderRole | DynamicTextRole(Signal) | DynamicSurfaceRole(..) | DynamicBorderRole(..)` works. Interaction-driven colors use the `Signal` pattern: @@ -1302,7 +1322,7 @@ Seven things about that list that have each cost real time: - Signal-based reactivity (Signal, Prop, ObserverHandle, scoped effects) - Gesture recognition (UIKit-style state machines, auto-wired from handlers) - Overlay system (OverlayManager, OverlayRequest, positioning) -- Design tokens (full Theme system) + four-tier styling ladder (tokens → variants → recipes → style protocols). `ThemeAppearance::{Light, Dark}` required field. `presets::intui::{light, dark}` shipped in `teksilo-core`; no `Theme::default()` / `Theme::*_default()`. Recipe types (`ShapeRecipe`, `FillRecipe`, `BorderRecipe`, `ShadowRecipe`, `PerStateRecipe`, `WidgetState`) in `teksilo_core::styles`. Per-widget style traits (`ButtonStyle`, `ToggleStyle`, `CheckboxStyle`, `RadioStyle`, `IconButtonStyle`, `PanelStyle`, `CardStyle`, `TooltipStyle`, `MenuItemStyle`, `PopoverStyle`, `SliderStyle`, `TextInputStyle`, `ComboBoxStyle`, `ScrollBarStyle`, `StandardItemStyle`, `TabStyle`, `SplitButtonStyle`, … — see the **Theming** section above, and `ComponentStyleSlots` for the authoritative roster) with default `Recipe*Style` impls in `teksilo-widgets/src/styles/`. All themable widgets delegate visual chrome via `style.make_body(cfg, ctx)`; apps install per-call (`.style(impl …Style)`) or theme-wide (`theme.style_slots. = Some(Rc::new(...))`). The legacy per-widget dim structs are deleted. Sibling preset crates: **Material 3** (`theme-material3`), **Fluent / Windows 11** (`theme-fluent`) and **macOS Aqua** (`theme-macos`) all ship complete. Image-backed styles + the `ImageTheme` manifest loader are still pending. Reference: [docs/styling-system.md](docs/styling-system.md). +- Design tokens (full Theme system) + four-tier styling ladder (tokens → variants → recipes → style protocols). `ThemeAppearance::{Light, Dark}` required field. `presets::intui::{light, dark}` shipped in `teksilo-core`; no `Theme::default()` / `Theme::*_default()`. Recipe types (`ShapeRecipe`, `FillRecipe`, `BorderRecipe`, `ShadowRecipe`, `PerStateRecipe`, `WidgetState`) in `teksilo_core::styles`. Per-widget style traits (`ButtonStyle`, `ToggleStyle`, `CheckboxStyle`, `RadioStyle`, `IconButtonStyle`, `PanelStyle`, `CardStyle`, `TooltipStyle`, `MenuItemStyle`, `PopoverStyle`, `SliderStyle`, `TextInputStyle`, `ComboBoxStyle`, `ScrollBarStyle`, `StandardItemStyle`, `TabStyle`, `SplitButtonStyle`, …: see the **Theming** section above, and `ComponentStyleSlots` for the authoritative roster) with default `Recipe*Style` impls in `teksilo-widgets/src/styles/`. All themable widgets delegate visual chrome via `style.make_body(cfg, ctx)`; apps install per-call (`.style(impl …Style)`) or theme-wide (`theme.style_slots. = Some(Rc::new(...))`). The legacy per-widget dim structs are deleted. Sibling preset crates: **Material 3** (`theme-material3`), **Fluent / Windows 11** (`theme-fluent`), **macOS Aqua** (`theme-macos`) and **KDE Plasma's Breeze** (`theme-kde`) all ship complete; Native mode (`ThemeMode::Native`, `native_theme_kde()`) follows the live desktop. Image-backed styles + the `ImageTheme` manifest loader are still pending. Reference: [docs/styling-system.md](docs/styling-system.md). - Window management (multi-window, modal dialogs, custom title bar — Wayland + macOS + Windows + X11; X11 needs a WM with `_NET_WM_MOVERESIZE`, probed before window creation, else it falls back to native decorations. X11 has no OS window menu, so `PlatformTitleBarHost::has_window_menu()` is `false` there and `TitleBar` builds its own) - GPU rendering (7 render pipelines + the blur pass, glyph atlas, path atlas) - All 29 layout primitives (including Grid, Wrap, AspectRatio, Switcher, MasonryLayout, ColumnFlow, FormLayout, DeadZone) @@ -1360,6 +1380,7 @@ Seven things about that list that have each cost real time: - Event types: `crates/teksilo-core/src/event.rs` - Theme + styling system: [crates/teksilo-core/src/styles/](crates/teksilo-core/src/styles/) (`theme.rs`, `theme_appearance.rs`, `theme_extension.rs`, `recipe.rs`, `component_style_slots.rs`, one `*_style.rs` trait file per themable widget). IntUI preset: [crates/teksilo-core/src/presets/intui.rs](crates/teksilo-core/src/presets/intui.rs). Default `Recipe*Style` impls: [crates/teksilo-widgets/src/styles/](crates/teksilo-widgets/src/styles/). Reference: [docs/styling-system.md](docs/styling-system.md) - Color tokens: `crates/teksilo-tokens/src/color.rs` +- KDE Plasma and Native mode: the colour model [crates/teksilo-kde-scheme/src/](crates/teksilo-kde-scheme/src/) (`config.rs` the KConfig grammar and cascade, `locations.rs`, `scheme.rs`, `math.rs`, `effects.rs`, `palette.rs`, `accent.rs`, `desktop.rs`, `fonts.rs`), the Breeze preset [crates/teksilo-theme-kde/src/](crates/teksilo-theme-kde/src/) (`color.rs` the scheme-to-tokens mapping and its WCAG lifts, `palette.rs` `KdePalette`, `geometry.rs` / `shape.rs` / `typography.rs` / `motion.rs`, `styles/` one file per Breeze style), the OS reader [crates/teksilo-platform/src/os_theme.rs](crates/teksilo-platform/src/os_theme.rs) + `os_theme/kde.rs` + `os_theme/watch.rs`, the seam [crates/teksilo-app/src/native_theme.rs](crates/teksilo-app/src/native_theme.rs), and the install hook [crates/teksilo/src/kde_native_install.rs](crates/teksilo/src/kde_native_install.rs). Specification: [engineering/docs/kde-breeze-spec.md](engineering/docs/kde-breeze-spec.md). Reference: [docs/native-theme.md](docs/native-theme.md) - Motion subsystems: [crates/teksilo-core/src/animation.rs](crates/teksilo-core/src/animation.rs) (signal-tween `AnimationScheduler`), [crates/teksilo-core/src/animated_quad.rs](crates/teksilo-core/src/animated_quad.rs) (shader-quad `AnimatedQuadRegistry`), [crates/teksilo-core/src/frame_tick_scheduler.rs](crates/teksilo-core/src/frame_tick_scheduler.rs) (per-frame-effect `FrameTickScheduler` — `Pulse` / `Cycle`), [crates/teksilo-core/src/motion_visibility.rs](crates/teksilo-core/src/motion_visibility.rs) (shared `alive` / `painted_this_frame` / `painted_recently` helpers). Reference: [docs/idle-and-animation.md](docs/idle-and-animation.md), [docs/animation.md](docs/animation.md). - Button (reference widget): [crates/teksilo-widgets/src/button.rs](crates/teksilo-widgets/src/button.rs) - Switcher: [crates/teksilo-widgets/src/primitives/switcher.rs](crates/teksilo-widgets/src/primitives/switcher.rs) @@ -1550,4 +1571,4 @@ If the app uses persistence, chain `.app_paths(...)` (or `.application(qualifier Framework-internals reference: [docs/architecture.md](docs/architecture.md) — scrolling, arena state, Canvas API, rendering pipeline, HiDPI, threading, testability, crate dependency graph, architectural comparisons, open questions. Subsystems with a dedicated reference doc (events, layout, animation, theming, i18n, shortcuts, accessibility, settings, multi-window, drag-and-drop, data models, …) are stubbed with one-paragraph pointers; section numbers are preserved so external `§N` refs still resolve. Doc index: [docs/SUMMARY.md](docs/SUMMARY.md). -Additional documentation: [docs/live-image.md](docs/live-image.md), [docs/widgets-overview.md](docs/widgets-overview.md), [docs/accessibility-overrides.md](docs/accessibility-overrides.md), [docs/settings.md](docs/settings.md), [docs/drag-and-drop.md](docs/drag-and-drop.md), [docs/title-bar.md](docs/title-bar.md), [docs/multi-window.md](docs/multi-window.md), [docs/idle-and-animation.md](docs/idle-and-animation.md), [docs/telemetry.md](docs/telemetry.md), [docs/table-view.md](docs/table-view.md), [docs/inspector.md](docs/inspector.md), [docs/web-view.md](docs/web-view.md), [docs/automation-mcp.md](docs/automation-mcp.md), [docs/property-testing.md](docs/property-testing.md), [docs/segmented-control.md](docs/segmented-control.md) +Additional documentation: [docs/live-image.md](docs/live-image.md), [docs/widgets-overview.md](docs/widgets-overview.md), [docs/accessibility-overrides.md](docs/accessibility-overrides.md), [docs/settings.md](docs/settings.md), [docs/drag-and-drop.md](docs/drag-and-drop.md), [docs/title-bar.md](docs/title-bar.md), [docs/multi-window.md](docs/multi-window.md), [docs/idle-and-animation.md](docs/idle-and-animation.md), [docs/telemetry.md](docs/telemetry.md), [docs/table-view.md](docs/table-view.md), [docs/inspector.md](docs/inspector.md), [docs/web-view.md](docs/web-view.md), [docs/automation-mcp.md](docs/automation-mcp.md), [docs/property-testing.md](docs/property-testing.md), [docs/segmented-control.md](docs/segmented-control.md), [docs/native-theme.md](docs/native-theme.md) diff --git a/.claude/CLAUDE.review.md b/.claude/CLAUDE.review.md index ecf83b030..b293c5e3f 100644 --- a/.claude/CLAUDE.review.md +++ b/.claude/CLAUDE.review.md @@ -86,6 +86,11 @@ These are the explicit rules the codebase is meant to follow. Flag deviations. - **Error types:** `thiserror` (`#[derive(thiserror::Error)]` with `#[error("...")]` per variant; `#[from]` for transparent conversions, `#[source]` for nested chains). Do not hand-roll `Display` / `std::error::Error` / `From`. +- **Clean-room KDE code:** `teksilo-kde-scheme` is written from + `engineering/docs/kde-breeze-spec.md` only, never from KDE or Qt source. + `crates/teksilo-kde-scheme/tests/clean_room_vocabulary.rs` fails on upstream identifiers in + the scanned directories; flag a new KDE crate missing from its `SCANNED` list, a word removed + from its list, and code whose structure mirrors upstream rather than the specification. --- @@ -128,8 +133,10 @@ teksilo-preview-ui Reusable 3-pane previewer GUI teksilo-widgets-previewer Bundle binary for the stock catalog teksilo-terminal Embeddable terminal-emulator widget; PTY + VT delegated behind a TerminalEngine trait. Renders into the wgpu surface (unlike webview) -teksilo-theme-material3 / teksilo-theme-fluent / teksilo-theme-macos +teksilo-theme-material3 / teksilo-theme-fluent / teksilo-theme-macos / teksilo-theme-kde Sibling design-language presets (tokens + Tier-3 chrome), feature-gated +teksilo-kde-scheme KDE Plasma colour-scheme model and desktop settings; no I/O (environment and + files passed in); read by teksilo-platform and teksilo-theme-kde teksilo-inspector In-app debug introspection panel; cfg(debug_assertions) only teksilo-automation GUI-free automation toolkit (DTOs, execute(), tool catalog, wire protocol) teksilo-automation-mcp MCP server binary driving it @@ -143,7 +150,7 @@ Stated dependency flow (verify): `canvas → render → platform → app`, `settings → app`, `i18n-macros → i18n`, `core → preview`, `preview-ui → preview + widgets`, `widgets → scene`, `core → webview`, `(app + core) → async → {tokio, async-std}`, `(core + tokens + canvas + platform) → terminal`, -`(core + canvas) → automation → automation-mcp`. +`(core + canvas) → automation → automation-mcp`, `tokens → kde-scheme → {platform, theme-kde}`. --- @@ -196,7 +203,11 @@ axis at each child's final main size, then places. All in logical pixels. `Leadi `ThemeAppearance::{Light, Dark}` is a required field. Presets `presets::intui::{light, dark}` ship in `teksilo-core`. There is no `Theme::default()`. Per-widget style traits live in `teksilo-core/src/styles/`; default `Recipe*Style` impls in `teksilo-widgets/src/styles/`. Apps -install styles per-call (`.style(...)`) or theme-wide (`theme.style_slots. = ...`). +install styles per-call (`.style(...)`) or theme-wide (`theme.style_slots. = ...`). The +sibling presets (Material 3, Fluent, macOS, KDE's Breeze) are separate crates behind umbrella +features. `ThemeMode::Native` adopts the live desktop's colours through one function, +`resolve_native_theme`; a theme change after a window is built re-tints and keeps the shapes the +tree was built with, by design (`docs/native-theme.md`). ### App entry point @@ -271,6 +282,7 @@ Core: - Accessibility: `crates/teksilo-core/src/accessibility.rs` - Animation: `crates/teksilo-core/src/animation.rs`, `animated_quad.rs`, `frame_tick_scheduler.rs`, `motion_visibility.rs` - Theme/styling: `crates/teksilo-core/src/styles/` (+ preset `presets/intui.rs`); default impls `crates/teksilo-widgets/src/styles/` +- Native mode: `crates/teksilo-platform/src/os_theme.rs` (+ `os_theme/kde.rs`, `os_theme/watch.rs`), `crates/teksilo-app/src/native_theme.rs` - Actions/Intents/Shortcuts: `crates/teksilo-core/src/{action,intent,shortcut}.rs`; `IntentKind` derive in `crates/teksilo-macros/src/intent_kind.rs` - Drag-and-drop core: `crates/teksilo-core/src/drag_payload.rs`, `drag_state.rs`, `widget_tree/drag_drop_impl.rs` diff --git a/.claude/skills/teksilo/SKILL.md b/.claude/skills/teksilo/SKILL.md index 1e0e59215..7781ea002 100644 --- a/.claude/skills/teksilo/SKILL.md +++ b/.claude/skills/teksilo/SKILL.md @@ -36,7 +36,7 @@ thread, persistence, file dialogs, the clipboard, notifications, menus, a terminal, a web view), run `cargo teksilo search ""`. Several subsystems are off by default and invisible until you look: the async executor and its Tokio / async-std adapters (`teksilo-tokio`, `teksilo-async-std`), -`web-view`, `terminal`, the automation bridge, the Material 3 / Fluent / macOS +`web-view`, `terminal`, the automation bridge, the Material 3 / Fluent / macOS / KDE theme presets, and the separate `teksilo-charts` and `teksilo-scene` crates. ## Sources of truth — when they disagree, trust in THIS order @@ -55,10 +55,11 @@ theme presets, and the separate `teksilo-charts` and `teksilo-scene` crates. If the version this app resolves (`cargo teksilo status`, or `Cargo.lock`) is not the one this copy ships with, the skill was installed for another release. Say so, and refresh it before relying on the prose: `cargo install -cargo-teksilo --version `, then `cargo teksilo agent -install claude --force` (add `--user` for a copy under the home directory). -`cargo teksilo status` flags a user-scope copy that differs from the installed -tool's. +cargo-teksilo --version --locked`, then `cargo +teksilo update` (`--user` for a copy under the home directory). `cargo teksilo +status` flags a tool older than the app, a user-scope copy that differs from the +installed tool's, and an automation server or formatter on PATH from an older +release, each with the command that fixes it. ## The tool: `cargo teksilo` @@ -96,6 +97,7 @@ cargo teksilo show docs/scroll-area.md # read a search result in full, offl cargo teksilo show docs/scroll-area.md --lines 166-172 cargo teksilo probe install # materialise the Python automation harness cargo teksilo init # the harness, plus a teksilo brief for every agent here +cargo teksilo update # bring everything installed to this release ``` - **`symbol`** replaces reading the widget source by hand. It emits the type's @@ -132,6 +134,18 @@ cargo teksilo init # the harness, plus a teksilo brief f `--user` supports `claude`, `vibe`, and `opencode`; it writes no project harness. `--force` replaces conflicting generated files. Shared-file text outside the managed region is preserved. `agent list` reports supported targets and state. +- **`update`** brings what is already installed to this release: the harness, + the project's agent instructions, and the `teksilo-automation-mcp` and + `cargo-teksilo-fmt` binaries on PATH (through `cargo install`). It lists the + changes and asks first; `-y` skips the question, `-i` asks per action and + shows the equivalent command, `--dry-run` only lists. `--agent` limits it to + the project's agent instructions, `--user` updates the ones under the home + directory instead. It installs nothing that is not there, and refuses when + the tool is older than the app. You cannot answer its question: run it with + `--dry-run`, show the user the list, and run it again with `-y` once they + agree. Afterwards, ask them to restart your session (and the MCP server, if + `teksilo-automation-mcp` was replaced), since this one still has the old + instructions. - **`model fetch`** explicitly downloads the semantic search model. Search uses BM25 until it is cached; a lookup never starts a download. - **`status --json`** reports the resolved version and installed tooling. diff --git a/.claude/skills/teksilo/reference/automation.md b/.claude/skills/teksilo/reference/automation.md index 6aa7aceb4..0f922acd0 100644 --- a/.claude/skills/teksilo/reference/automation.md +++ b/.claude/skills/teksilo/reference/automation.md @@ -53,6 +53,9 @@ Install the server matching the framework version your app pins: cargo install teksilo-automation-mcp --version ``` +`cargo teksilo status` says when the server on PATH is from an older or another +release, and `cargo teksilo update` replaces it. + Then: ```bash diff --git a/.claude/skills/teksilo/reference/teksilo_app_guide.md b/.claude/skills/teksilo/reference/teksilo_app_guide.md index 57967a1d5..6518a5a45 100644 --- a/.claude/skills/teksilo/reference/teksilo_app_guide.md +++ b/.claude/skills/teksilo/reference/teksilo_app_guide.md @@ -67,7 +67,7 @@ opt-outs: | `async` / `tokio` / `async-std` | Optional main-thread async executor (`.install_async()`, `ctx.spawn_local`, `spawn_blocking`). `tokio` / `async-std` imply `async` and add the reactor (`.install_async_tokio()` / `.install_async_async_std()`) so a handler can `.await` that runtime's futures: timers, sockets, `reqwest`. Off by default. See `cargo teksilo show docs/async.md` | | `terminal` | Embeddable `Terminal` widget: a real shell over a PTY. Off by default | | `automation` | Debug-only automation bridge (`.install_automation_bridge_in_debug()`) for agents and tests. Off by default | -| `theme-material3` / `theme-fluent` / `theme-macos` | Material 3, Fluent (Windows 11) and macOS Aqua presets, as `material3::light()` etc. from the prelude | +| `theme-material3` / `theme-fluent` / `theme-macos` / `theme-kde` | Material 3, Fluent (Windows 11), macOS Aqua and KDE Plasma (Breeze) presets, as `material3::light()` etc. from the prelude. `theme-kde` also adds `.native_theme_kde()` (see Theming). Its font features (`bundled-fonts`, `system-fonts`) are not forwarded: depend on `teksilo-theme-kde` directly for Plasma's own faces | | `telemetry` | Privacy-respecting analytics wiring + `PrivacySettings` widget | | `fonts-cjk-sc` / `fonts-thai` / `fonts-all` / `system-emoji` | Extra bundled script fonts / runtime color-emoji fallback | @@ -363,6 +363,17 @@ let theme = intui::light(); // or intui::dark() `ctx.set_theme(...)` updates an internal signal and dirty-marks nodes — no rebuild, and focus/scroll/interaction state survive. +**Following the desktop.** `.theme_mode(ThemeMode::FollowSystem)` picks Int UI light or +dark by the desktop's preference; `ThemeMode::Native` adopts the desktop's own colours +(accent, surfaces, selection; on a KDE Plasma session its colour scheme) in Int UI's +shapes, and both track the desktop while the app runs. A handler +switches to Native with `ctx.follow_system_theme()` and back with `ctx.set_theme(..)`. With +the `theme-kde` feature, `.native_theme_kde()` (the prelude's `TeksiloAppBuilderKdeExt`) +makes a window *built* in Native mode on a Breeze desktop use the desktop's fonts and +animation speed and Breeze's shapes as well; any other desktop keeps Int UI in its colours. A theme change after the window is built is a +re-tint: the shapes stay those it was built with. `.native_theme(|request| ..)` installs +a factory of your own. Reference: `cargo teksilo show docs/native-theme.md`. + **Variants** select a widget's look (Tier 1): ```rust,ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3d6e17b0..b9cc89a53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,6 +151,18 @@ jobs: - name: Test run: cargo test --workspace + # teksilo-theme-kde's two font features are off by default and nothing + # in the workspace turns them on, so the workspace steps above build and + # test only the build without them. `bundled-fonts` embeds the faces and + # adds `font_registrar()`, `system-fonts` only names the families. With + # both on, every item and test gated on either feature compiles and + # runs (tests/fonts.rs checks the registrar's faces under the first); + # the workspace steps cover the ones gated on neither. + - name: Check and test teksilo-theme-kde (bundled-fonts, system-fonts) + run: | + cargo check -p teksilo-theme-kde --all-targets --features bundled-fonts,system-fonts + cargo test -p teksilo-theme-kde --features bundled-fonts,system-fonts + # cargo-teksilo's `semantic` feature (search embeddings) pulls in # fastembed -> ort (ONNX Runtime) plus the onig_sys / esaxx-rs C/C++ sys # crates. If that build ever breaks on some platform, `cargo install @@ -666,6 +678,96 @@ jobs: fi echo "package size within guard" + # The same guard for the two KDE crates. teksilo-theme-kde embeds + # Plasma's Noto Sans and Hack under `fonts/` (about 5.6 MB of faces, + # 3.2 MiB packaged), so a face added there moves it towards crates.io's + # 10 MB limit; the fallback, should it ever pass the guard, is Latin + # subsets of the faces. teksilo-kde-scheme is small, and is measured + # because it is packaged here anyway: teksilo-theme-kde depends on it at + # the same version, which crates.io has only once it is published, so + # the two are packaged together. + # + # `--exclude-lockfile`: cargo resolves a package's Cargo.lock against + # the crates.io index, with only the packages named on the command line + # supplied locally. On a release tag, where the Release workflow runs + # this file before publishing, teksilo-core and the other workspace + # crates at the new version are not on crates.io yet, so the resolution + # would fail and block every release. The lockfile adds about 40 KB to + # the compressed teksilo-theme-kde package, far inside the margin + # between the 9 MiB guard and the 10 MB limit. + # + # The listing then checks what the package holds: every tracked file + # under teksilo-theme-kde's `fonts/` (the faces and their licences, which + # the OFL and the Hack licence require to travel with the faces), and, + # in either crate, no test fixture (teksilo-kde-scheme's vendored Breeze + # schemes are LGPL and test-only), no `tools/` and no golden `vectors/`. + - name: KDE crates package guard + if: matrix.name == 'debug' + run: | + rm -f target/package/teksilo-theme-kde-*.crate target/package/teksilo-kde-scheme-*.crate + cargo package -p teksilo-theme-kde -p teksilo-kde-scheme --no-verify --allow-dirty --exclude-lockfile + + shopt -s nullglob + guard_mib=9 + guard=$((guard_mib * 1024 * 1024)) + failed=0 + for name in teksilo-theme-kde teksilo-kde-scheme; do + crates=(target/package/"$name"-*.crate) + if [ "${#crates[@]}" -ne 1 ]; then + echo "::error::expected exactly one $name .crate, found ${#crates[@]}: ${crates[*]}" + ls -la target/package/ || true + exit 1 + fi + crate="${crates[0]}" + bytes=$(( $(wc -c < "$crate") )) + mib=$(awk -v b="$bytes" 'BEGIN { printf "%.2f", b / 1048576 }') + echo "$crate is ${bytes} bytes (${mib} MiB); guard ${guard_mib} MiB, crates.io limit 10 MB" + if [ "$bytes" -gt "$guard" ]; then + echo "::error::$name .crate is ${mib} MiB, over the ${guard_mib} MiB guard" + failed=1 + fi + done + + theme_list=$(cargo package -p teksilo-theme-kde --list --allow-dirty) + scheme_list=$(cargo package -p teksilo-kde-scheme --list --allow-dirty) + + # The expected font files are the tracked ones, so adding or + # renaming a face needs no edit here; an emptied `fonts/` would pass + # that vacuously, hence the two counts. + mapfile -t fonts < <(git ls-files -- crates/teksilo-theme-kde/fonts | sed 's|^crates/teksilo-theme-kde/||') + faces=0 + licences=0 + for f in "${fonts[@]}"; do + case "$f" in + *.ttf) faces=$((faces + 1)) ;; + *LICENSE*) licences=$((licences + 1)) ;; + esac + if ! grep -qxF -- "$f" <<< "$theme_list"; then + echo "::error::teksilo-theme-kde's package is missing $f" + failed=1 + fi + done + if [ "$faces" -eq 0 ] || [ "$licences" -eq 0 ]; then + echo "::error::crates/teksilo-theme-kde/fonts tracks ${faces} faces and ${licences} licences; expected at least one of each" + failed=1 + fi + echo "teksilo-theme-kde packages ${faces} faces and ${licences} licences" + + for name in teksilo-theme-kde teksilo-kde-scheme; do + if [ "$name" = teksilo-theme-kde ]; then list=$theme_list; else list=$scheme_list; fi + stray=$(grep -E '(^|/)(fixtures|tools|vectors)(/|$)' <<< "$list" || true) + if [ -n "$stray" ]; then + echo "::error::$name's package carries files that must stay in the repository:" + echo "$stray" + failed=1 + fi + done + + if [ "$failed" -ne 0 ]; then + exit 1 + fi + echo "KDE packages within guard, with their fonts and licences and without fixtures, tools or vectors" + # cargo-teksilo embeds two copies of files that also live elsewhere in # the repo, so an app that only depends on the `teksilo` crate — not # this checkout — still gets them. Nothing else enforces the copies diff --git a/CHANGELOG.md b/CHANGELOG.md index bb6bd8c58..21082c49d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,251 @@ by crate for clarity, not because crates version independently. ## [Unreleased] +### Added + +#### KDE Plasma + +- **New crate `teksilo-theme-kde`, the KDE Plasma preset.** `light()` and + `dark()` are Breeze Light and Breeze Dark (ids `kde.light`, `kde.dark`); + `light_with_accent` and `dark_with_accent` apply an accent as Plasma's + colour settings do, `PlasmaAccent` lists Plasma's ten accent swatches with + stable names for saving a choice, and `from_scheme` themes any KDE colour + scheme. Breeze's colours, radius, focus ring, text sizes and motion, the + scheme's muted colours in an inactive window, and the preset's own colours + under the OS increase contrast preference (7:1 text, 4.5:1 outlines). + `KdePalette` carries the whole resolved scheme. +- **Breeze chrome for the stock controls.** Buttons (`Filled` as the default + button, `Ghost` and `Link` flat), tool and split buttons, check boxes, + radio buttons, switches, line edits (`TextInput`, an editable + `RichTextEditor`), combo and spin boxes, sliders, menus, popups, tooltips, + list and tree rows, tables, tabs, scroll bars and progress bars are drawn + and sized as Breeze's, growing to the density's target size; other widgets + keep Teksilo's chrome in Breeze's colours and shapes. +- **Breeze made readable.** Where stock Breeze or the user's scheme falls + short of WCAG (secondary text, links, status and focus colours, outlines, + the scroll-bar thumb, text on the accent, an inactive window's shading, a + translucent colour), the preset lifts the colour, keeping its hue, and + check box, radio and table rows are 24 px to stay target sizes. The crate + documentation lists every deviation from Breeze and the known limitations. +- **The theme of the live KDE desktop.** `from_desktop` builds it from a + `KdeDesktop`: the colour scheme with the user's accent, the desktop's + fonts as the text styles (the controls grow with them), its animation + speed (0 means reduced motion) and its `breezerc` settings. `native_theme` + gives that theme on a desktop using the Breeze widget style, `None` on any + other. +- **Plasma's fonts, optionally.** The themes write in the bundled Inter and + JetBrains Mono at the sizes of Plasma's Noto Sans and Hack. The + `bundled-fonts` feature embeds Noto Sans and Hack (about 5.6 MB; pass + `font_registrar()` to `TeksiloAppBuilder::register_fonts`), and + `system-fonts` uses the desktop's installed copies. Neither changes a size. +- **New crate `teksilo-kde-scheme`.** Reads a KDE desktop from contents the + caller supplies, touching no file or environment and running no command: + the configuration cascade (`KConfig`), colour schemes (`KdeColorScheme`, + resolved into KDE's palette, accented as Plasma does), fonts and the whole + desktop (`KdeDesktop`), with KDE's colour arithmetic in `math`. +- **The `theme-kde` feature of `teksilo`**, the preset in + `teksilo::prelude::kde`. Its font features are not forwarded. +- **`native_theme_kde()` on `TeksiloAppBuilder`** (`TeksiloAppBuilderKdeExt`, + in the prelude). A window built in `ThemeMode::Native` on a KDE desktop + with the Breeze style shows Breeze's chrome in the desktop's live colours, + fonts and animation speed, at the window's density; elsewhere it keeps + IntUI in the desktop's colours. `ThemeMode::FollowSystem` is unaffected, + and switching to Native at runtime re-tints but keeps the shapes the + windows were built with. + +#### Theming + +- **`PaintProjection`.** A theme registered with `with_paint_projection` + paints its own colours in an inactive window and under the OS increase + contrast preference; `BuildContext::prefers_high_contrast` reads the + preference at build time. +- **A negative `ShapeTokens::focus_ring_offset`** draws the focus ring + partly or wholly inside the control; every stock ring follows it. +- **Style hooks for sizes and states.** `StandardItemStyle`, `TableStyle`, + `TabStyle`, `ScrollBarStyle` and `ProgressBarStyle` declare their row, + header, tab, thumb and bar sizes at the active density; + `StandardItemStyle::selection_emphasis` says whether a selection mutes + with view focus or only with the window, and + `MenuItemStyle::pressed_label_role` colours a pressed row's label. + `StandardItemStyle::min_height_decorated` sets the height of a row with a + check box or leading slot, and `ProgressBarStyle::paints_busy_indicator` + lets a style draw an indeterminate bar's motion in place of the sweep. + Every default keeps an existing style as it was. +- **`ComponentStyleSlots::NAMES`**, the name of every theme-wide style slot. +- **More desktop colours in the native theme.** `ColorTokens::from_os_colors` + uses a report's view, link, status and focus colours, made readable where + they fall short. +- **`teksilo_tokens::contrast`** and **`MotionTokens::scaled`** (durations + times a desktop's animation-speed factor). + +#### Platform and app + +- **`os_theme::query_native_desktop`** gives the desktop's colour report + and, on KDE Plasma, the KDE desktop itself; **`OsThemeWatch`** reports each + appearance change on Linux, reconnecting when the session bus goes away. +- **Native and follow-system themes update live.** Under `ThemeMode::Native` + or `ThemeMode::FollowSystem`, every window takes the desktop's new accent, + colour scheme or light or dark setting without a restart, and reduced + motion follows the desktop's animation speed. On a KDE session with no + bus, changed KDE files are read when a window takes focus. +- **`TeksiloAppBuilder::native_theme`** answers each `NativeThemeRequest` + with the app's own Native-mode theme, or `None` for IntUI in the desktop's + colours; `resolve_native_theme` gives the theme a request resolves to, + `configured_native_theme()` returns the installed factory, and + `presets::intui::from_os` is the IntUI default on its own. + +#### Demos and documentation + +- **The widget catalog offers KDE**: KDE Light and KDE Dark in the theme + switcher, `--theme kde-light` and `--theme kde-dark` for Breeze's chrome, + and `--theme system` for Native mode, which is Breeze on a Breeze desktop. +- **A guide to Native mode**, and the KDE preset in the README, the styling + guide and the application guide. + +#### cargo-teksilo + +- `cargo teksilo update` brings what is already installed to the tool's + release: the probe harness, the project's agent instructions, and the + `teksilo-automation-mcp` and `cargo-teksilo-fmt` binaries on PATH. It lists + the changes and asks first; `-y` skips the question, `-i` asks per action + and shows the equivalent command, `--dry-run` only lists. `--agent` limits + it to the project's agent instructions, `--user` updates the ones under the + home directory instead. It ends by saying to restart the agent session or + the MCP server when it replaced what they run. +- `cargo teksilo status` reports `cargo-teksilo-fmt` on a `Fmt` line, and the + tool's own version on a `Tool` line; `--json` adds `formatter`, + `tool_outdated` and `tool_update_command`. + +### Changed + +#### Theming + +- **Breaking.** **`OsThemeColors` is `#[non_exhaustive]` and has ten new + fields** (`view_bg`, `view_fg`, `alternate_bg`, `inactive_fg`, `link`, + `link_visited`, `error`, `warning`, `success`, `focus`). Outside + `teksilo-tokens`, build it with the new `OsThemeColors::new(scheme)` or + `OsThemeColors::default()` and assign fields, not a struct literal. It + now implements `PartialEq`. +- **Breaking.** **`MacOsMenuItemStyle` and `FluentMenuItemStyle` are no + longer unit structs**; they hold their `MenuItemRecipe` in `recipe`. Use + `::default()` for the compact sizes, `::for_tokens` for a density's or + `::new` with a recipe. +- **The native theme takes more of a GTK theme's colours.** + **Behaviour change:** with a GTK theme that ships its CSS (Breeze GTK, for + example), `ThemeMode::Native` also follows its content surface, editor + text, link, error, warning and success colours, and Breeze GTK's focus + colour. + +#### Platform + +- **Linux desktop settings are read without `busctl`.** The portal and the + screen reader flag are read over the session bus directly; a silent bus + delays startup by at most a second, where each read waited up to 25 seconds. + They are now also read without `busctl` installed and from portals older + than version 2 of the settings interface. +- **A Linux session is KDE only when it says so.** **Behaviour change:** + `XDG_CURRENT_DESKTOP` must list `KDE` or `KDE_FULL_SESSION` be `true`, + checked before GNOME. A name merely containing KDE no longer counts. +- **Native mode on KDE reads all of KDE's configuration.** + **Behaviour change:** every `kdeglobals` KDE reads, `kdedefaults` and + `XDG_CONFIG_DIRS` included, with administrator locks honoured; without + colours of its own, the look-and-feel package's or named scheme's, as in + KDE. Before, only the user's own `kdeglobals` was read. +- **Native mode on KDE takes the user's accent.** **Behaviour change:** the + accent is KDE's selection colour, as KDE applications show it, and a + chosen `AccentColor` applies to a scheme the profile never wrote. The + portal's accent is used only when no `kdeglobals` can be read. The view, + link, error, warning, success and focus colours are read from the scheme + too, so a desktop with another widget style shows them in IntUI. +- **Light or dark on KDE follows the colours.** **Behaviour change:** a + scheme is dark when its window background is, not when its name says + "dark", so Breeze Twilight is no longer taken for light. +- **Reduced motion follows KDE's animation speed.** **Behaviour change:** an + instant speed (`AnimationDurationFactor=0`) turns on + `AccessibilityPreferences::reduced_motion`. + +#### App + +- **A theme that follows the OS keeps the app's density.** + **Behaviour change:** under Native and FollowSystem, a window's theme and + each OS change keep the app's density instead of resetting to compact. +- **A Native window opens in the window system's light or dark state when + the desktop states neither.** **Behaviour change:** before, it always + opened light. +- **A FollowSystem window opens in the desktop's light or dark preference.** + **Behaviour change:** before, it went by the window system alone, which Linux + never gives, so it opened light on a dark desktop. +- **A Native theme keeps the desktop's colours without a light or dark + preference.** **Behaviour change:** on GNOME's default style, an OS change + and `follow_system_theme` keep its accent and GTK colours, not plain IntUI. +- **An OS theme event that changes nothing no longer relays out and + repaints every window.** + +#### cargo-teksilo + +- `cargo teksilo status` prints the `cargo install` command for a tool, an + automation server or a formatter that is older than the app's Teksilo, + including a patch-level difference, which it previously reported as + matching. The server's JSON `state` can now be `older`. +- `cargo teksilo update --user` replaces `agent install --user --force` as the + remedy `status` gives for a differing user-scope copy. +- `cargo teksilo init` and `update` no longer ask when `$CI` or `$CLAUDECODE` + is set, even on a terminal: they print the listing and exit with an error + naming `-y`, which now tells an agent to show the list to the user first. + +### Removed + +- **Breaking.** **`TabRecipe::tool_window_height` and + `recipe_tab_style::TAB_TOOL_WINDOW_HEIGHT`.** Nothing read them. Drop + `tool_window_height` from a `TabRecipe` struct literal. + +### Fixed + +- **Builds without link-time optimisation embed each bundled font once,** + 4.8 MB less in a debug build of an app with text input. + +#### Theming + +- **List and tree rows are as tall as the active style says.** + **Behaviour change:** a self-measuring row is 24 px under macOS and 40 px + under Fluent at the compact density, rising to the density's target size + above it. Before, every row was at least 28 px (44 px with a subtitle). +- **Table rows and headers are as tall as the active style says.** + **Behaviour change:** 24 px under macOS and 40 px under Fluent at the + compact density, rising to the target size above it (Int UI rows to 32 px + at comfortable, 44 px at touch), where every table had 28 px rows under a + 32 px header. A height the app sets still wins, here and for rows. +- **Tabs are as tall and as padded as the active style says.** + **Behaviour change:** without a `tab_bar_height`, 26 px under macOS and + 32 px under Fluent (8 px padding) at the compact density and 44 px at + touch, where every tab was 50 px with 12 px padding. Int UI tabs stay + 50 px, their padding growing with the density. +- **Scroll-bar thumbs are as long as the active style says.** + **Behaviour change:** at least 28 px under macOS and 30 px under Fluent at + the compact density, where every thumb was held at 24 px. +- **Accent chrome a recipe builds follows high contrast.** + **Behaviour change:** a `Filled` button's fill and a button's focus border + take the high-contrast palette, as the other accent controls did. +- **High contrast keeps each theme's own colours and reaches its targets.** + **Behaviour change:** Int UI, macOS, Fluent and Material 3 lift their own + accent, focus ring, secondary text and selection until text reaches 7:1 + and the ring 4.5:1. Before, every dark theme took Int UI's teal, and macOS + and Fluent kept black text on their accent below 4:1. +- **macOS and Fluent sliders under right-to-left layout.** + **Behaviour change:** a horizontal slider runs from the right, so the knob + stays under the pointer and moves the way the arrow keys point. +- **macOS and Fluent vertical sliders.** **Behaviour change:** the minimum + is at the bottom and `ArrowUp` moves the knob up; the knob used to sit + opposite to the value. +- **macOS and Fluent menus at touch densities.** **Behaviour change:** the + separators, the shortcut column and a `max_visible_items` cap follow the + density, as the rows did. macOS rows stay 22 px. +- **A menu row shows its pressed state while a pointer holds it.** + **Behaviour change:** before, only Enter or Space showed it. +- **A breadcrumb crumb shows its hover, focus and link colours.** + **Behaviour change:** before, every crumb painted in the secondary text + colour, and assistive technology could neither click nor focus it. + ## [0.19.0] - 2026-10-16 ### Added diff --git a/Cargo.lock b/Cargo.lock index 3e1f007bb..4ebb0f9b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11720,6 +11720,7 @@ dependencies = [ "teksilo-terminal", "teksilo-text", "teksilo-theme-fluent", + "teksilo-theme-kde", "teksilo-theme-macos", "teksilo-theme-material3", "teksilo-tokens", @@ -11984,6 +11985,16 @@ dependencies = [ "teksilo-widgets", ] +[[package]] +name = "teksilo-kde-scheme" +version = "0.19.0" +dependencies = [ + "proptest", + "serde_json", + "teksilo-tokens", + "thiserror 2.0.21", +] + [[package]] name = "teksilo-macros" version = "0.19.0" @@ -12009,6 +12020,7 @@ dependencies = [ "accesskit 0.25.1", "accesskit_winit", "arboard", + "async-io", "async-std", "dispatch2", "libc", @@ -12023,8 +12035,10 @@ dependencies = [ "teksilo-automation", "teksilo-canvas", "teksilo-core", + "teksilo-kde-scheme", "teksilo-render", "teksilo-tokens", + "tempfile", "thiserror 2.0.21", "wayland-backend", "wayland-client", @@ -12134,6 +12148,7 @@ dependencies = [ "teksilo-i18n", "teksilo-text", "teksilo-theme-fluent", + "teksilo-theme-kde", "teksilo-theme-macos", "teksilo-theme-material3", "teksilo-tokens", @@ -12212,8 +12227,33 @@ dependencies = [ "teksilo-core", "teksilo-data", "teksilo-i18n", + "teksilo-target-conformance", + "teksilo-tokens", + "teksilo-widgets", +] + +[[package]] +name = "teksilo-theme-kde" +version = "0.19.0" +dependencies = [ + "pollster 1.0.1", + "proptest", + "serde_json", + "sha2 0.10.9", + "teksilo-app", + "teksilo-canvas", + "teksilo-core", + "teksilo-data", + "teksilo-i18n", + "teksilo-kde-scheme", + "teksilo-platform", + "teksilo-render", + "teksilo-target-conformance", + "teksilo-text", "teksilo-tokens", "teksilo-widgets", + "thiserror 2.0.21", + "wgpu", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 264498a01..8c5d1180e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,11 @@ tempfile = "3" # used in the sibling `text-typeset` / `text-document` repos. Never a # production dependency — `[dev-dependencies]` only. proptest = "1" +# Test-only: `teksilo-theme-kde`'s `tests/fonts.rs` checks its bundled font +# files against the sha256 recorded for their upstream releases. 0.10 is +# already in the lockfile through `wry` and others. Never a production +# dependency: `[dev-dependencies]` only. +sha2 = "0.10" # Test-only: statistics-driven micro-benchmarking, behind the pointer-dispatch # performance budget (`crates/teksilo-core/benches/pointer_dispatch.rs`). The # default features pull `rayon` and `plotters` in for HTML reports; the budget @@ -207,6 +212,8 @@ teksilo-preview-ui = { path = "crates/teksilo-preview-ui", version = "0.19.0" } teksilo-theme-material3 = { path = "crates/teksilo-theme-material3", version = "0.19.0" } teksilo-theme-macos = { path = "crates/teksilo-theme-macos", version = "0.19.0" } teksilo-theme-fluent = { path = "crates/teksilo-theme-fluent", version = "0.19.0" } +teksilo-kde-scheme = { path = "crates/teksilo-kde-scheme", version = "0.19.0" } +teksilo-theme-kde = { path = "crates/teksilo-theme-kde", version = "0.19.0" } # Path only, no `version`: this crate is `publish = false`, so a versioned # dependency on it would have to resolve from crates.io the moment a diff --git a/NOTICE b/NOTICE index f1ee347b7..657d2e523 100644 --- a/NOTICE +++ b/NOTICE @@ -98,6 +98,82 @@ resource file, or artwork is included, and no token value is transcribed from one. +1.5 KDE Plasma / Breeze -- crates/teksilo-kde-scheme, crates/teksilo-theme-kde +--------------------------------------------------------------------------- + +teksilo-kde-scheme reads the files a KDE Plasma desktop keeps its look in: +the KConfig text format and its cascade, the KDE colour-scheme model, Qt's +font descriptions and the related desktop settings. Its behaviour is +re-implemented from Teksilo's own written specification, +engineering/docs/kde-breeze-spec.md, which describes KDE's formats, rules +and values in prose. No KDE or Qt source code is included in Teksilo, and +none was copied or translated: the specification was written from the +upstream sources by one author, and the code is written from the +specification alone. Values such as default colours and numeric constants +are facts; the specification's ledger records, for each one, the upstream +file and line it was observed in. + +teksilo-theme-kde, the KDE Plasma theme preset, builds Teksilo themes from +those colour schemes and transcribes the Breeze design language from the +same specification: the widget style's metrics, painting rules and motion, +and the Kirigami units it relies on. Each value is tagged at its definition +with where it was observed ([M], [S] and [Hp] for the Breeze widget style, +[K] for Kirigami, [Qd] for the desktop QtQuick style, [qt] for Qt's public +documentation, [measured-qt] for the Qt runtime) or how Teksilo derived it +([derived], [wcag], [unpublished]). Where a stock Breeze colour falls short +of WCAG, the theme lifts it by a stated rule and says so. No KDE or Qt +source code, artwork, icon or font is included in it. + +The specification describes these upstream releases: + + plasma/breeze v6.8.0 + plasma/plasma-workspace v6.8.0 + plasma/plasma-integration v6.8.0 + plasma/xdg-desktop-portal-kde v6.8.0 + frameworks/kconfig v6.31.0 + frameworks/kcolorscheme v6.31.0 + frameworks/kguiaddons v6.31.0 + frameworks/kirigami v6.31.0 + frameworks/qqc2-desktop-style v6.31.0 + qt/qtbase v6.11.1 (the Qt runtime observed) + + Source: https://invent.kde.org/ + +KDE publishes its colour and configuration libraries under the GNU Lesser +General Public License (Plasma's colour-scheme settings under version 2.0 +only) and the Breeze widget style under the GNU General Public License, +as stated in each upstream file. Teksilo takes no code under either +licence. + +Test fixtures. Three stock Breeze colour schemes from plasma/breeze v6.8.0 +are vendored verbatim, so the tests can compare Teksilo's constants +against them: + + crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/ + BreezeLight.colors c46470b75e7df3e1ebff344748b92347f8d5adfa306aef9c91c4d2edae2cd6df + BreezeDark.colors 11fce1d8ab97b529162e50eb200d1a93fab65c7612f9bebd0230a6a348d0cc21 + BreezeClassic.colors 19dcbd1883c03d2d9e0176a39e3ec9df6c18d6e22f0474e6c886aa20aff76508 + +BreezeLight.colors and BreezeDark.colors carry their own copyright and +LGPL-2.0-or-later headers; BreezeClassic.colors carries none and comes from +the same release. The GNU Library General Public License, version 2, sits +beside them as LICENSE.txt. The fixtures, and the two tests that read +them, tests/breeze_files.rs and tests/golden_accent.rs, are excluded from +the published teksilo-kde-scheme crate (the "exclude" list in its +Cargo.toml) and are never compiled into a library or binary. + +The specification describes Plasma's accent tint in OKLab, the colour space +Björn Ottosson published at https://bottosson.github.io/posts/oklab/ with +reference code under the MIT License. The space and its published matrices +are credited to him. + +KDE(R) and the K Desktop Environment(R) logo are registered trademarks of +KDE e.V. Teksilo is not affiliated with, endorsed by or sponsored by KDE +e.V.; the names KDE, Plasma and Breeze are used only to say which desktop's +files teksilo-kde-scheme reads and which design language the specification +describes and teksilo-theme-kde reproduces. See TRADEMARKS.md. + + =========================================================================== 2. BUNDLED FONTS =========================================================================== @@ -107,8 +183,10 @@ beside the font file, at the path given with each entry. "Default" in the Embedded column means the face is compiled into any binary built with that crate's default features. -None of these faces carries a Reserved Font Name, so the OFL's font-name -restriction does not apply to them. +None of the faces under the SIL Open Font License carries a Reserved Font +Name, so the OFL's font-name restriction does not apply to them. Hack's +Bitstream Vera licence has a naming condition of its own for modified +copies; see 2.7. 2.1 Inter -- crates/teksilo-text/fonts/ @@ -171,15 +249,88 @@ restriction does not apply to them. feature, which is off by default +2.6 Noto Sans -- crates/teksilo-theme-kde/fonts/ +--------------------------------------------------------------------------- + Copyright 2022 The Noto Project Authors + (https://github.com/notofonts/latin-greek-cyrillic) + + Licence: SIL Open Font License, Version 1.1 + Text: crates/teksilo-theme-kde/fonts/noto-sans-LICENSE.txt + (the release's OFL.txt, verbatim) + Files: NotoSans-Variable.ttf, NotoSans-Italic-Variable.ttf + Embedded: only with the teksilo-theme-kde "bundled-fonts" feature, + which is off by default + + Release: NotoSans-v2.015, notofonts/latin-greek-cyrillic + https://github.com/notofonts/latin-greek-cyrillic/releases/tag/NotoSans-v2.015 + Archive: NotoSans-v2.015.zip + 0c34df072a3fa7efbb7cbf34950e1f971a4447cffe365d3a359e2d4089b958f5 + + File (renamed from the archive path) sha256 + NotoSans-Variable.ttf + NotoSans/full/variable-ttf/NotoSans[wdth,wght].ttf + ae80a5e79e4afbf493e9f4684ec4988e6a861b3598102ee48950b4b7a754cdff + NotoSans-Italic-Variable.ttf + NotoSans/full/variable-ttf/NotoSans-Italic[wdth,wght].ttf + 8bcb0ae904c973257ec957ca92559d425469d92e0913d489fe80fbb49c3b6ded + noto-sans-LICENSE.txt + OFL.txt + cee9892f9f0cc8fe882c9e9537ee6a89621d86ee7ceaf70b02e2b2b1c25c061a + + Noto is a trademark of Google LLC. + + +2.7 Hack -- crates/teksilo-theme-kde/fonts/ +--------------------------------------------------------------------------- + Copyright (c) 2018 Source Foundry Authors + (https://github.com/source-foundry/Hack) + Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. + The work in the DejaVu project was committed to the public domain. + + Licence: MIT License (the Hack project's work) and the Bitstream Vera + License (Bitstream Vera Sans Mono, which Hack derives from) + Text: crates/teksilo-theme-kde/fonts/hack-LICENSE.txt + (the project's LICENSE.md at tag v3.003, verbatim; the + release archive carries no licence file) + Files: Hack-Regular.ttf, Hack-Bold.ttf, Hack-Italic.ttf, + Hack-BoldItalic.ttf + Embedded: only with the teksilo-theme-kde "bundled-fonts" feature, + which is off by default + + Release: v3.003, source-foundry/Hack + https://github.com/source-foundry/Hack/releases/tag/v3.003 + Archive: Hack-v3.003-ttf.zip + 0c2604631b1f055041c68a0e09ae4801acab6c5072ba2db6a822f53c3f8290ac + + File sha256 + Hack-Regular.ttf 15f55cc0c85a2988d2b4b3a8cdb5d77fdfbaf319e1bb5309d725db9818fb7125 + Hack-Bold.ttf 5bbf531eff7f8a0c2559c9a0656718e2828a012a9b1f60b5f54006d59a4de8d4 + Hack-Italic.ttf 096fb67a2b85f3c866e9cb3e965b27c2c10b977315f4d3d7f095674be35091c1 + Hack-BoldItalic.ttf 64f74a079700b7dfe128551a1e28875d5ba980971e55f5e0f0596e37bdc6a6bc + hack-LICENSE.txt 1f61bb7c790c59b4b0ecdf304628b94e42ae4c8020094a8c3da73381ab212623 + + The Bitstream Vera License lets the fonts be modified only if they are + renamed to names containing neither "Bitstream" nor "Vera", and forbids + selling a copy of the fonts by itself. Teksilo redistributes them + unmodified, as part of a larger work. Bitstream Vera is a trademark of + Bitstream, Inc. + +The tests in crates/teksilo-theme-kde/tests/fonts.rs check every file in +crates/teksilo-theme-kde/fonts/ against the sha256 values above. + + =========================================================================== 3. FONTS NAMED BUT NOT REDISTRIBUTED =========================================================================== -Two theme presets can ask the platform's text stack for a proprietary system -face by name. Neither font is included in this repository or in any binary -built from it, and no licence to either is granted or implied. The face -resolves only on a machine that already has it installed; on any other, the -text stack falls back to the bundled Inter and JetBrains Mono. +Three theme presets can ask the platform's text stack for a system face by +name, under their "system-fonts" feature. The face resolves only on a +machine that already has it installed; on any other, the text stack falls +back to the bundled Inter and JetBrains Mono. + +The Microsoft and Apple faces are proprietary. Neither is included in this +repository or in any binary built from it, and no licence to either is +granted or implied. Segoe UI Variable, Consolas Microsoft Corporation teksilo-theme-fluent, "system-fonts" @@ -187,6 +338,16 @@ text stack falls back to the bundled Inter and JetBrains Mono. SF Pro Text, SF Mono Apple Inc. teksilo-theme-macos, "system-fonts" +Noto Sans and Hack, KDE Plasma's default faces, are free fonts, the +releases in 2.6 and 2.7. Under "system-fonts" alone teksilo-theme-kde +embeds neither and uses the copies installed on the machine, under the +licences they were installed with; only its "bundled-fonts" feature +embeds them. + + Noto Sans, Hack The Noto Project Authors; Source Foundry + Authors and Bitstream, Inc. + teksilo-theme-kde, "system-fonts" + The same applies to the platform colour-emoji faces (Noto Color Emoji, Apple Color Emoji, Segoe UI Emoji) that the teksilo-text "system-emoji" feature reads from well-known paths at startup: located at runtime, never embedded. diff --git a/README.md b/README.md index 32dd5dc6d..0d41144d2 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ The rules under which Teksilo is built: **Internationalization.** Translations are checked at compile time via macro on top of Fluent: missing or misspelled keys are build errors. Right-to-left layout, locale-aware number and date formatting, and re-rendering on locale change are built in. -**Themes.** Default light and dark, inspired by JetBrains' Int UI; switching is instant and preserves focus, scroll position, and selection. On Linux, the active palette (accent, surface, selection, tooltip colors) follows the desktop environment (GNOME, KDE, Cinnamon). Material 3, Fluent (Windows 11) and macOS Aqua presets ship behind the `theme-material3` / `theme-fluent` / `theme-macos` features. Apps can override anything from a single color to a whole widget's chrome via the four-tier styling system (tokens → variants → recipes → style protocols), described in [`docs/styling-system.md`](docs/styling-system.md). +**Themes.** Default light and dark, inspired by JetBrains' Int UI; switching is instant and preserves focus, scroll position, and selection. Native mode adopts the live desktop colours: on Linux the active palette (accent, surface, selection, tooltip colors) follows the desktop environment (GNOME, KDE, Cinnamon) and is re-read when the desktop changes it, with the shapes still Int UI's (see [`docs/native-theme.md`](docs/native-theme.md)). The presets are a separate thing: Material 3, Fluent (Windows 11), macOS Aqua and KDE Plasma (Breeze) ship behind the `theme-material3` / `theme-fluent` / `theme-macos` / `theme-kde` features, each with its own geometry and widget chrome, and the KDE preset also gives Native mode on a Breeze desktop Breeze's shapes through `native_theme_kde()`. Apps can override anything from a single color to a whole widget's chrome via the four-tier styling system (tokens → variants → recipes → style protocols), described in [`docs/styling-system.md`](docs/styling-system.md). **Input.** Keyboard shortcuts, menus, and accessibility actions flow through one rebindable pipeline; a user remap updates every surface that mentions the binding. External-source events (databases, file watchers) bypass it; widgets subscribe directly. @@ -397,4 +397,4 @@ See TRADEMARKS.md for the full policy; for anything it doesn't cover, contact tr Teksilo builds on the work of others: AccessKit; winit and wgpu; HarfBuzz (via harfrust), swash, fontdb, etagere, and ICU4X; unicode-bidi; Fluent and the Mozilla l10n team; the published design notes of the Druid, Masonry, and Xilem projects; and SwiftUI's layout protocol. Anthropic and Mistral provided the language models whose code generation contributed substantially under human review. -The theme presets follow design languages published by others: JetBrains' Int UI, which the default light and dark themes are drawn from; Microsoft's Fluent and the WinUI theme resources; Google's Material 3; and Apple's macOS Human Interface Guidelines. Each preset is an independent implementation, and none of those vendors is affiliated with or endorses Teksilo. See [TRADEMARKS.md](TRADEMARKS.md) and [NOTICE](NOTICE). +The theme presets follow design languages published by others: JetBrains' Int UI, which the default light and dark themes are drawn from; Microsoft's Fluent and the WinUI theme resources; Google's Material 3; Apple's macOS Human Interface Guidelines; and the Breeze style of KDE Plasma. Each preset is an independent implementation, and none of those vendors or projects is affiliated with or endorses Teksilo. See [TRADEMARKS.md](TRADEMARKS.md) and [NOTICE](NOTICE). diff --git a/TRADEMARKS.md b/TRADEMARKS.md index 0afece457..b85728724 100644 --- a/TRADEMARKS.md +++ b/TRADEMARKS.md @@ -40,7 +40,7 @@ More generally, don't do anything with the name or FernTech's branding that woul ## Third-party trademarks -Teksilo ships theme presets that reproduce the look of design languages published by other vendors, and each is named for what it reproduces: `macos`, `fluent`, `material3`, and the `intui` default. The name is there to say which design language a preset imitates — a theme reproducing Apple's Aqua cannot be identified without naming it. We use the word and nothing else: no logo, no wordmark, no icon set, and no proprietary font is redistributed. See [NOTICE](NOTICE) for what each preset actually derives from and on what terms. +Teksilo ships theme presets that reproduce the look of design languages published by other vendors, and each is named for what it reproduces: `macos`, `fluent`, `material3`, `kde`, and the `intui` default. The name is there to say which design language a preset imitates: a theme reproducing Apple's Aqua cannot be identified without naming it. We use the word and nothing else: no logo, no wordmark, no icon set, and no proprietary font is redistributed. See [NOTICE](NOTICE) for what each preset actually derives from and on what terms. The marks belong to their owners: @@ -48,6 +48,7 @@ The marks belong to their owners: - **Fluent**, **WinUI**, **Windows**, **Segoe UI**, **Consolas** — Microsoft Corporation. - **Material Design**, **Noto**, **Roboto** — Google LLC. - **Int UI**, **JetBrains**, **JetBrains Mono** — JetBrains s.r.o. +- **KDE**, **KDE Plasma**, **Plasma**, **Breeze**: KDE e.V. (KDE is a registered trademark of KDE e.V.). The `teksilo-kde-scheme` crate uses these names only to say which desktop's configuration files it reads and which design language they describe, and the `teksilo-theme-kde` preset only to say which design language it reproduces. Teksilo is not affiliated with, endorsed by, sponsored by, or certified by any of them. The presets are independent implementations by FernTech, built from published specifications and — where a vendor publishes nothing — from measurement. They are not vendor-supplied, not vendor-reviewed, and not guaranteed to match any vendor's current design. diff --git a/crates/cargo-teksilo/README.md b/crates/cargo-teksilo/README.md index 956773a9c..b61c5e653 100644 --- a/crates/cargo-teksilo/README.md +++ b/crates/cargo-teksilo/README.md @@ -30,6 +30,7 @@ cargo teksilo init --agent codex --agent claude cargo teksilo agent install cursor cline cargo teksilo agent list cargo teksilo probe install +cargo teksilo update cargo teksilo model fetch cargo teksilo status --json cargo teksilo --version @@ -50,6 +51,24 @@ section. `--user` supports `claude`, `vibe`, and `opencode` and writes no harnes Shared-file text outside the managed region is preserved. `--force` replaces conflicting generated files. +`update` brings what is already installed to this release: the probe harness, +the project's agent instructions, and the `teksilo-automation-mcp` and +`cargo-teksilo-fmt` binaries on PATH, reinstalled with `cargo install`. It lists +the changes and asks before making them; `-y` skips the question, and `-i` asks +per action, showing the equivalent command; `--dry-run` only lists. Where +nobody can answer (stdin is not a terminal, or `$CI` or `$CLAUDECODE` is set), +it prints the list and exits with an error naming `-y`, rather than waiting. +`--agent` limits it to the +project's agent instructions; `--user` updates the instructions under the home +directory instead, and needs no project. It installs nothing that is absent, +and refuses when the tool is older than the app: install the matching +cargo-teksilo first. With `-y`, probe files with local edits are replaced only +with `--force`. + +`status` reports the tool, the app's Teksilo, the harness, the search model, +and the automation server and formatter on PATH. A tool, server or formatter +older than the app's release is reported with the command that updates it. + `--quiet` suppresses informational messages but still says which release answered; `--verbose` shows paths and diagnostics. `search --json`, `status --json`, and `agent list --json` emit structured output. API declarations and document contents are never shortened. diff --git a/crates/cargo-teksilo/embedded/extract_widget_api.py b/crates/cargo-teksilo/embedded/extract_widget_api.py index 1ecd6f0d5..9f65e26d9 100755 --- a/crates/cargo-teksilo/embedded/extract_widget_api.py +++ b/crates/cargo-teksilo/embedded/extract_widget_api.py @@ -254,6 +254,10 @@ def marker_end(self) -> str: "theme-fluent": CrateSpec("teksilo-theme-fluent", "teksilo_theme_fluent", "theme-fluent", "Fluent Theme", "Themes", False, False), "theme-macos": CrateSpec("teksilo-theme-macos", "teksilo_theme_macos", "theme-macos", "macOS Theme", "Themes", False, False), "theme-material3": CrateSpec("teksilo-theme-material3", "teksilo_theme_material3", "theme-material3", "Material 3 Theme", "Themes", False, False), + "theme-kde": CrateSpec("teksilo-theme-kde", "teksilo_theme_kde", "theme-kde", "KDE Plasma Theme", "Themes", False, False), + # The KDE colour-scheme model the KDE preset and the platform reader share + # (`teksilo-theme-kde` re-exports it as `scheme`). + "kde-scheme": CrateSpec("teksilo-kde-scheme", "teksilo_kde_scheme", "kde-scheme", "KDE Colour Schemes", "Themes", False, False), # Storybook-equivalent previewer: trait/registry crate + reusable GUI library "preview": CrateSpec("teksilo-preview", "teksilo_preview", "preview", "Widget Previewer Core", "Preview", False, False), @@ -263,7 +267,7 @@ def marker_end(self) -> str: "fmt": CrateSpec("teksilo-fmt", "teksilo_fmt", "fmt", "teksu! Formatter", "Tooling", False, False), "parse": CrateSpec("teksilo-parse", "teksilo_parse", "parse", "teksu! Parser", "Tooling", False, False), - # The umbrella crate's OWN files (its toast/webview install-hook extension + # The umbrella crate's OWN files (its toast/webview/KDE install-hook extension # traits — automation_install.rs only re-exports teksilo-app's, so it does # not add anything new here) — distinct from the crates it merely # re-exports, each of which already has its own entry above. @@ -504,13 +508,15 @@ def _is_scannable(p: Path) -> bool: # teksilo-inspector "TeksiloAppBuilderInspectorExt": "inspector", - # The umbrella crate's OWN files (toast_install.rs / webview_install.rs) — - # these two extension traits are teksilo's own code, not a re-export of - # another crate. `TeksiloAppBuilderAutomationExt`, by contrast, is genuinely + # The umbrella crate's OWN files (toast_install.rs, webview_install.rs and + # kde_native_install.rs): these three extension traits are teksilo's own + # code, not a re-export of another crate. `TeksiloAppBuilderAutomationExt`, + # by contrast, is genuinely # defined in teksilo-app (automation_bridge.rs) and merely re-exported # through automation_install.rs — see the "teksilo-app" group below. "TeksiloAppBuilderToastExt": "teksilo", "TeksiloAppBuilderWebViewExt": "teksilo", + "TeksiloAppBuilderKdeExt": "teksilo", # teksilo-widgets — the toast/notification surface re-exported in prelude "EventContextToastExt": "widgets", diff --git a/crates/cargo-teksilo/embedded/skill/SKILL.md b/crates/cargo-teksilo/embedded/skill/SKILL.md index 1e0e59215..7781ea002 100644 --- a/crates/cargo-teksilo/embedded/skill/SKILL.md +++ b/crates/cargo-teksilo/embedded/skill/SKILL.md @@ -36,7 +36,7 @@ thread, persistence, file dialogs, the clipboard, notifications, menus, a terminal, a web view), run `cargo teksilo search ""`. Several subsystems are off by default and invisible until you look: the async executor and its Tokio / async-std adapters (`teksilo-tokio`, `teksilo-async-std`), -`web-view`, `terminal`, the automation bridge, the Material 3 / Fluent / macOS +`web-view`, `terminal`, the automation bridge, the Material 3 / Fluent / macOS / KDE theme presets, and the separate `teksilo-charts` and `teksilo-scene` crates. ## Sources of truth — when they disagree, trust in THIS order @@ -55,10 +55,11 @@ theme presets, and the separate `teksilo-charts` and `teksilo-scene` crates. If the version this app resolves (`cargo teksilo status`, or `Cargo.lock`) is not the one this copy ships with, the skill was installed for another release. Say so, and refresh it before relying on the prose: `cargo install -cargo-teksilo --version `, then `cargo teksilo agent -install claude --force` (add `--user` for a copy under the home directory). -`cargo teksilo status` flags a user-scope copy that differs from the installed -tool's. +cargo-teksilo --version --locked`, then `cargo +teksilo update` (`--user` for a copy under the home directory). `cargo teksilo +status` flags a tool older than the app, a user-scope copy that differs from the +installed tool's, and an automation server or formatter on PATH from an older +release, each with the command that fixes it. ## The tool: `cargo teksilo` @@ -96,6 +97,7 @@ cargo teksilo show docs/scroll-area.md # read a search result in full, offl cargo teksilo show docs/scroll-area.md --lines 166-172 cargo teksilo probe install # materialise the Python automation harness cargo teksilo init # the harness, plus a teksilo brief for every agent here +cargo teksilo update # bring everything installed to this release ``` - **`symbol`** replaces reading the widget source by hand. It emits the type's @@ -132,6 +134,18 @@ cargo teksilo init # the harness, plus a teksilo brief f `--user` supports `claude`, `vibe`, and `opencode`; it writes no project harness. `--force` replaces conflicting generated files. Shared-file text outside the managed region is preserved. `agent list` reports supported targets and state. +- **`update`** brings what is already installed to this release: the harness, + the project's agent instructions, and the `teksilo-automation-mcp` and + `cargo-teksilo-fmt` binaries on PATH (through `cargo install`). It lists the + changes and asks first; `-y` skips the question, `-i` asks per action and + shows the equivalent command, `--dry-run` only lists. `--agent` limits it to + the project's agent instructions, `--user` updates the ones under the home + directory instead. It installs nothing that is not there, and refuses when + the tool is older than the app. You cannot answer its question: run it with + `--dry-run`, show the user the list, and run it again with `-y` once they + agree. Afterwards, ask them to restart your session (and the MCP server, if + `teksilo-automation-mcp` was replaced), since this one still has the old + instructions. - **`model fetch`** explicitly downloads the semantic search model. Search uses BM25 until it is cached; a lookup never starts a download. - **`status --json`** reports the resolved version and installed tooling. diff --git a/crates/cargo-teksilo/embedded/skill/reference/automation.md b/crates/cargo-teksilo/embedded/skill/reference/automation.md index 6aa7aceb4..0f922acd0 100644 --- a/crates/cargo-teksilo/embedded/skill/reference/automation.md +++ b/crates/cargo-teksilo/embedded/skill/reference/automation.md @@ -53,6 +53,9 @@ Install the server matching the framework version your app pins: cargo install teksilo-automation-mcp --version ``` +`cargo teksilo status` says when the server on PATH is from an older or another +release, and `cargo teksilo update` replaces it. + Then: ```bash diff --git a/crates/cargo-teksilo/embedded/skill/reference/teksilo_app_guide.md b/crates/cargo-teksilo/embedded/skill/reference/teksilo_app_guide.md index 57967a1d5..6518a5a45 100644 --- a/crates/cargo-teksilo/embedded/skill/reference/teksilo_app_guide.md +++ b/crates/cargo-teksilo/embedded/skill/reference/teksilo_app_guide.md @@ -67,7 +67,7 @@ opt-outs: | `async` / `tokio` / `async-std` | Optional main-thread async executor (`.install_async()`, `ctx.spawn_local`, `spawn_blocking`). `tokio` / `async-std` imply `async` and add the reactor (`.install_async_tokio()` / `.install_async_async_std()`) so a handler can `.await` that runtime's futures: timers, sockets, `reqwest`. Off by default. See `cargo teksilo show docs/async.md` | | `terminal` | Embeddable `Terminal` widget: a real shell over a PTY. Off by default | | `automation` | Debug-only automation bridge (`.install_automation_bridge_in_debug()`) for agents and tests. Off by default | -| `theme-material3` / `theme-fluent` / `theme-macos` | Material 3, Fluent (Windows 11) and macOS Aqua presets, as `material3::light()` etc. from the prelude | +| `theme-material3` / `theme-fluent` / `theme-macos` / `theme-kde` | Material 3, Fluent (Windows 11), macOS Aqua and KDE Plasma (Breeze) presets, as `material3::light()` etc. from the prelude. `theme-kde` also adds `.native_theme_kde()` (see Theming). Its font features (`bundled-fonts`, `system-fonts`) are not forwarded: depend on `teksilo-theme-kde` directly for Plasma's own faces | | `telemetry` | Privacy-respecting analytics wiring + `PrivacySettings` widget | | `fonts-cjk-sc` / `fonts-thai` / `fonts-all` / `system-emoji` | Extra bundled script fonts / runtime color-emoji fallback | @@ -363,6 +363,17 @@ let theme = intui::light(); // or intui::dark() `ctx.set_theme(...)` updates an internal signal and dirty-marks nodes — no rebuild, and focus/scroll/interaction state survive. +**Following the desktop.** `.theme_mode(ThemeMode::FollowSystem)` picks Int UI light or +dark by the desktop's preference; `ThemeMode::Native` adopts the desktop's own colours +(accent, surfaces, selection; on a KDE Plasma session its colour scheme) in Int UI's +shapes, and both track the desktop while the app runs. A handler +switches to Native with `ctx.follow_system_theme()` and back with `ctx.set_theme(..)`. With +the `theme-kde` feature, `.native_theme_kde()` (the prelude's `TeksiloAppBuilderKdeExt`) +makes a window *built* in Native mode on a Breeze desktop use the desktop's fonts and +animation speed and Breeze's shapes as well; any other desktop keeps Int UI in its colours. A theme change after the window is built is a +re-tint: the shapes stay those it was built with. `.native_theme(|request| ..)` installs +a factory of your own. Reference: `cargo teksilo show docs/native-theme.md`. + **Variants** select a widget's look (Tier 1): ```rust,ignore diff --git a/crates/cargo-teksilo/src/automation.rs b/crates/cargo-teksilo/src/automation.rs deleted file mode 100644 index e6a92e909..000000000 --- a/crates/cargo-teksilo/src/automation.rs +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 FernTech - -//! The `teksilo-automation-mcp` on the user's PATH, and whether it matches. -//! -//! A server built from another release "will answer confidently about tools -//! and semantics the app does not have", so `status` says which one an agent -//! would be handed. The PATH lookup and the version query are injected into -//! [`inspect`], so a test never depends on the machine it runs on. - -use std::{ - io::Read, - path::{Path, PathBuf}, - process::{Command, Stdio}, - time::{Duration, Instant}, -}; - -/// The binary name `cargo install teksilo-automation-mcp` produces. -pub const BINARY: &str = "teksilo-automation-mcp"; - -/// How long `--version` may take before the server is reported as unreadable. -const VERSION_TIMEOUT: Duration = Duration::from_secs(2); - -/// What `status` found. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Server { - /// Nothing named [`BINARY`] on PATH. - NotInstalled, - /// On PATH, same major and minor as the app (a patch difference is fine). - Matching { version: String }, - /// On PATH, but built from another minor or major release. - Mismatch { version: String, expected: String }, - /// On PATH, but `--version` failed, timed out or printed nothing usable. - Unknown, -} - -impl Server { - /// The `state` of the JSON field. - pub fn state(&self) -> &'static str { - match self { - Server::NotInstalled => "missing", - Server::Matching { .. } => "matching", - Server::Mismatch { .. } => "mismatch", - Server::Unknown => "unknown", - } - } - - /// The version the server reported, when it did. - pub fn version(&self) -> Option<&str> { - match self { - Server::Matching { version } | Server::Mismatch { version, .. } => Some(version), - _ => None, - } - } - - /// The text after the `MCP` label of the human report. - pub fn line(&self) -> String { - match self { - Server::NotInstalled => "not installed".into(), - Server::Matching { version } => format!("{version} (matches)"), - Server::Mismatch { version, expected } => { - format!("{version} (differs from {expected})") - } - Server::Unknown => "installed (version unreadable)".into(), - } - } - - /// The remedy, only for a mismatch. - pub fn help(&self) -> Option { - match self { - Server::Mismatch { expected, .. } => Some(format!( - "help: the {BINARY} on your PATH was built from another release; \ - `cargo install {BINARY} --version {expected}` replaces it" - )), - _ => None, - } - } - - /// The JSON value of the report's `automation_server` field. - pub fn json(&self, expected: &str) -> serde_json::Value { - serde_json::json!({ - "state": self.state(), - "version": self.version(), - "expected": expected, - }) - } -} - -/// Classify the server against `expected`, the version the app resolved. -/// -/// `locate` finds the binary and `version_of` asks it for its version; the -/// latter is called only when the former found something. -pub fn inspect( - expected: &str, - locate: impl FnOnce() -> Option, - version_of: impl FnOnce(&Path) -> Option, -) -> Server { - let Some(path) = locate() else { - return Server::NotInstalled; - }; - let Some(version) = version_of(&path) else { - return Server::Unknown; - }; - match ( - crate::guard::semver_parts(&version), - crate::guard::semver_parts(expected), - ) { - (Some((a, b, _)), Some((c, d, _))) if (a, b) == (c, d) => Server::Matching { version }, - (Some(_), Some(_)) => Server::Mismatch { - version, - expected: expected.to_string(), - }, - _ => Server::Unknown, - } -} - -/// [`inspect`] against the real PATH. -pub fn inspect_path(expected: &str) -> Server { - inspect( - expected, - || crate::symbol::which(BINARY), - |path| run_version(path, VERSION_TIMEOUT), - ) -} - -/// Run ` --version` and parse its output, giving up after `timeout`. -fn run_version(path: &Path, timeout: Duration) -> Option { - let mut child = Command::new(path) - .arg("--version") - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .spawn() - .ok()?; - let deadline = Instant::now() + timeout; - loop { - match child.try_wait() { - Ok(Some(status)) if status.success() => break, - Ok(Some(_)) | Err(_) => return None, - Ok(None) if Instant::now() >= deadline => { - let _ = child.kill(); - let _ = child.wait(); - return None; - } - Ok(None) => std::thread::sleep(Duration::from_millis(10)), - } - } - let mut out = String::new(); - child - .stdout - .take()? - .take(4096) - .read_to_string(&mut out) - .ok()?; - parse_version(&out) -} - -/// `teksilo-automation-mcp 0.15.1` → `0.15.1`. -fn parse_version(output: &str) -> Option { - let word = output.lines().next()?.split_whitespace().last()?; - word.starts_with(|c: char| c.is_ascii_digit()) - .then(|| word.to_string()) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn found() -> Option { - Some(PathBuf::from("/nowhere/teksilo-automation-mcp")) - } - - #[test] - fn a_missing_binary_is_not_queried() { - let server = inspect( - "0.15.1", - || None, - |_| panic!("the version of a binary that is not there"), - ); - assert_eq!(server, Server::NotInstalled); - assert_eq!(server.line(), "not installed"); - assert!(server.help().is_none()); - } - - #[test] - fn the_same_release_matches_and_a_patch_difference_does_too() { - for version in ["0.15.1", "0.15.0", "0.15.9"] { - let server = inspect("0.15.1", found, |_| Some(version.into())); - assert_eq!( - server, - Server::Matching { - version: version.into() - } - ); - assert!(server.help().is_none()); - } - } - - #[test] - fn another_minor_or_major_names_the_install_command() { - for version in ["0.14.3", "0.16.0", "1.15.1"] { - let server = inspect("0.15.1", found, |_| Some(version.into())); - assert_eq!(server.state(), "mismatch", "{version}"); - assert_eq!( - server.help().unwrap(), - "help: the teksilo-automation-mcp on your PATH was built from another \ - release; `cargo install teksilo-automation-mcp --version 0.15.1` replaces it" - ); - } - } - - #[test] - fn an_unreadable_version_is_unknown_not_a_mismatch() { - assert_eq!(inspect("0.15.1", found, |_| None), Server::Unknown); - assert_eq!( - inspect("0.15.1", found, |_| Some("garbage".into())), - Server::Unknown - ); - assert_eq!( - inspect("unknown", found, |_| Some("0.15.1".into())), - Server::Unknown - ); - } - - #[test] - fn the_json_field_carries_state_version_and_expected() { - let server = Server::Mismatch { - version: "0.14.0".into(), - expected: "0.15.1".into(), - }; - assert_eq!( - server.json("0.15.1"), - serde_json::json!({"state":"mismatch","version":"0.14.0","expected":"0.15.1"}) - ); - assert_eq!( - Server::NotInstalled.json("0.15.1"), - serde_json::json!({"state":"missing","version":null,"expected":"0.15.1"}) - ); - } - - #[test] - fn clap_version_output_parses() { - assert_eq!( - parse_version("teksilo-automation-mcp 0.15.1\n").as_deref(), - Some("0.15.1") - ); - assert_eq!(parse_version(""), None); - assert_eq!(parse_version("usage: nothing"), None); - } - - #[cfg(unix)] - #[test] - fn a_server_that_hangs_is_given_up_on() { - use std::os::unix::fs::PermissionsExt; - let dir = tempfile::tempdir().unwrap(); - let script = dir.path().join("hang"); - std::fs::write(&script, "#!/bin/sh\nexec sleep 30\n").unwrap(); - std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap(); - let started = Instant::now(); - assert_eq!(run_version(&script, Duration::from_millis(200)), None); - assert!(started.elapsed() < Duration::from_secs(5)); - } - - #[cfg(unix)] - #[test] - fn a_server_that_answers_is_read() { - use std::os::unix::fs::PermissionsExt; - let dir = tempfile::tempdir().unwrap(); - let script = dir.path().join("answer"); - std::fs::write(&script, "#!/bin/sh\necho 'teksilo-automation-mcp 0.15.1'\n").unwrap(); - std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap(); - assert_eq!( - run_version(&script, Duration::from_secs(5)).as_deref(), - Some("0.15.1") - ); - } -} diff --git a/crates/cargo-teksilo/src/companion.rs b/crates/cargo-teksilo/src/companion.rs new file mode 100644 index 000000000..f34077da7 --- /dev/null +++ b/crates/cargo-teksilo/src/companion.rs @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The companion binaries on the user's PATH, and whether they match the app. +//! +//! `teksilo-automation-mcp` and `cargo-teksilo-fmt` are installed on their own +//! and nothing moves them when the app does. A server built from another +//! release "will answer confidently about tools and semantics the app does not +//! have", and a formatter from another release formats a `teksu!` grammar the +//! app does not have, so `status` says which one is ahead on the PATH and +//! `update` replaces it. The PATH lookup and the version query are injected +//! into [`inspect`], so a test never depends on the machine it runs on. + +use std::{ + io::Read, + path::{Path, PathBuf}, + process::{Command, Stdio}, + time::{Duration, Instant}, +}; + +use crate::install::Install; + +/// A binary published from this workspace beside cargo-teksilo, whose crate +/// and binary share one name. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Companion { + pub binary: &'static str, +} + +/// The automation MCP server. +pub const MCP: Companion = Companion { + binary: "teksilo-automation-mcp", +}; + +/// The `teksu!` formatter. +pub const FMT: Companion = Companion { + binary: "cargo-teksilo-fmt", +}; + +/// How long `--version` may take before the binary is reported as unreadable. +const VERSION_TIMEOUT: Duration = Duration::from_secs(2); + +/// How the binary on PATH relates to the app's release. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum State { + /// Nothing of that name on PATH. + NotInstalled, + /// Same major and minor as the app, and the same patch or a later one. + Matching { version: String }, + /// Same major and minor as the app, earlier patch. + Older { version: String }, + /// Built from another minor or major release. + Mismatch { version: String }, + /// On PATH, but `--version` failed, timed out or printed nothing usable. + Unknown, +} + +/// What [`inspect`] found for one companion. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Found { + pub companion: Companion, + /// The version the app resolved. + pub expected: String, + /// Where the binary is, when it is anywhere. + pub path: Option, + pub state: State, +} + +impl Found { + /// The `state` of the JSON field. + pub fn state(&self) -> &'static str { + match self.state { + State::NotInstalled => "missing", + State::Matching { .. } => "matching", + State::Older { .. } => "older", + State::Mismatch { .. } => "mismatch", + State::Unknown => "unknown", + } + } + + /// The version the binary reported, when it did. + pub fn version(&self) -> Option<&str> { + match &self.state { + State::Matching { version } + | State::Older { version } + | State::Mismatch { version } => Some(version), + _ => None, + } + } + + /// The text after the label of the human report. + pub fn line(&self) -> String { + let expected = &self.expected; + match &self.state { + State::NotInstalled => "not installed".into(), + State::Matching { version } => format!("{version} (matches)"), + State::Older { version } => format!("{version} (older than {expected})"), + State::Mismatch { version } => format!("{version} (differs from {expected})"), + State::Unknown => "installed (version unreadable)".into(), + } + } + + /// The command that brings the binary to the app's release, when it needs one. + pub fn install(&self) -> Option { + match self.state { + State::Older { .. } | State::Mismatch { .. } | State::Unknown => Some( + Install::release(self.companion.binary, &self.expected, self.path.as_deref()), + ), + State::NotInstalled | State::Matching { .. } => None, + } + } + + /// The remedy, for every state [`install`](Self::install) has a command for. + pub fn help(&self) -> Option { + let command = self.install()?.display(); + let binary = self.companion.binary; + Some(match &self.state { + State::Older { version } => format!( + "help: {binary} {version} is older than Teksilo {}; `{command}` updates it", + self.expected + ), + State::Mismatch { .. } => format!( + "help: the {binary} on your PATH was built from another release; \ + `{command}` replaces it" + ), + _ => format!( + "help: the {binary} on your PATH does not report its version; \ + `{command}` replaces it" + ), + }) + } + + /// The JSON value of the report's field for this companion. + pub fn json(&self) -> serde_json::Value { + serde_json::json!({ + "state": self.state(), + "version": self.version(), + "expected": self.expected, + }) + } +} + +/// Classify `companion` against `expected`, the version the app resolved. +/// +/// `locate` finds the binary and `version_of` asks it for its version; the +/// latter is called only when the former found something. +pub fn inspect( + companion: Companion, + expected: &str, + locate: impl FnOnce() -> Option, + version_of: impl FnOnce(&Path) -> Option, +) -> Found { + let path = locate(); + let state = match &path { + None => State::NotInstalled, + Some(path) => match version_of(path) { + None => State::Unknown, + Some(version) => classify(version, expected), + }, + }; + Found { + companion, + expected: expected.to_string(), + path, + state, + } +} + +fn classify(version: String, expected: &str) -> State { + match ( + crate::guard::semver_parts(&version), + crate::guard::semver_parts(expected), + ) { + (Some((a, b, p)), Some((c, d, q))) if (a, b) == (c, d) => { + if p < q { + State::Older { version } + } else { + State::Matching { version } + } + } + (Some(_), Some(_)) => State::Mismatch { version }, + _ => State::Unknown, + } +} + +/// [`inspect`] against the real PATH. +pub fn inspect_path(companion: Companion, expected: &str) -> Found { + inspect( + companion, + expected, + || crate::symbol::which(companion.binary), + |path| run_version(path, VERSION_TIMEOUT), + ) +} + +/// Run ` --version` and parse its output, giving up after `timeout`. +fn run_version(path: &Path, timeout: Duration) -> Option { + let mut child = Command::new(path) + .arg("--version") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .ok()?; + let deadline = Instant::now() + timeout; + loop { + match child.try_wait() { + Ok(Some(status)) if status.success() => break, + Ok(Some(_)) | Err(_) => return None, + Ok(None) if Instant::now() >= deadline => { + let _ = child.kill(); + let _ = child.wait(); + return None; + } + Ok(None) => std::thread::sleep(Duration::from_millis(10)), + } + } + let mut out = String::new(); + child + .stdout + .take()? + .take(4096) + .read_to_string(&mut out) + .ok()?; + parse_version(&out) +} + +/// `teksilo-automation-mcp 0.15.1` → `0.15.1`. +fn parse_version(output: &str) -> Option { + let word = output.lines().next()?.split_whitespace().last()?; + word.starts_with(|c: char| c.is_ascii_digit()) + .then(|| word.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn found() -> Option { + Some(PathBuf::from("/nowhere/teksilo-automation-mcp")) + } + + fn mcp(expected: &str, version: Option<&str>) -> Found { + inspect(MCP, expected, found, |_| version.map(String::from)) + } + + #[test] + fn a_missing_binary_is_not_queried() { + let server = inspect( + MCP, + "0.15.1", + || None, + |_| panic!("the version of a binary that is not there"), + ); + assert_eq!(server.state, State::NotInstalled); + assert_eq!(server.line(), "not installed"); + assert!(server.help().is_none()); + assert!(server.install().is_none()); + } + + #[test] + fn the_same_release_or_a_later_patch_matches() { + for version in ["0.15.1", "0.15.9"] { + let server = mcp("0.15.1", Some(version)); + assert_eq!( + server.state, + State::Matching { + version: version.into() + } + ); + assert!(server.help().is_none()); + } + } + + #[test] + fn an_earlier_patch_is_older_and_names_the_install_command() { + let server = mcp("0.15.1", Some("0.15.0")); + assert_eq!(server.state(), "older"); + assert_eq!(server.line(), "0.15.0 (older than 0.15.1)"); + assert_eq!( + server.help().unwrap(), + "help: teksilo-automation-mcp 0.15.0 is older than Teksilo 0.15.1; \ + `cargo install teksilo-automation-mcp --version 0.15.1 --locked` updates it" + ); + } + + #[test] + fn another_minor_or_major_names_the_install_command() { + for version in ["0.14.3", "0.16.0", "1.15.1"] { + let server = mcp("0.15.1", Some(version)); + assert_eq!(server.state(), "mismatch", "{version}"); + assert_eq!( + server.help().unwrap(), + "help: the teksilo-automation-mcp on your PATH was built from another \ + release; `cargo install teksilo-automation-mcp --version 0.15.1 --locked` \ + replaces it" + ); + } + } + + #[test] + fn the_formatter_is_checked_under_its_own_name() { + let fmt = inspect( + FMT, + "0.15.1", + || Some(PathBuf::from("/nowhere/cargo-teksilo-fmt")), + |_| Some("0.15.0".into()), + ); + assert!( + fmt.help() + .unwrap() + .contains("`cargo install cargo-teksilo-fmt --version 0.15.1 --locked`") + ); + } + + #[test] + fn an_unreadable_version_is_unknown_not_a_mismatch() { + assert_eq!(mcp("0.15.1", None).state, State::Unknown); + assert_eq!(mcp("0.15.1", Some("garbage")).state, State::Unknown); + assert_eq!(mcp("unknown", Some("0.15.1")).state, State::Unknown); + assert!(mcp("0.15.1", None).help().unwrap().contains("replaces it")); + } + + #[test] + fn the_json_field_carries_state_version_and_expected() { + assert_eq!( + mcp("0.15.1", Some("0.14.0")).json(), + serde_json::json!({"state":"mismatch","version":"0.14.0","expected":"0.15.1"}) + ); + assert_eq!( + inspect(MCP, "0.15.1", || None, |_| None).json(), + serde_json::json!({"state":"missing","version":null,"expected":"0.15.1"}) + ); + } + + #[test] + fn clap_version_output_parses() { + assert_eq!( + parse_version("teksilo-automation-mcp 0.15.1\n").as_deref(), + Some("0.15.1") + ); + assert_eq!(parse_version(""), None); + assert_eq!(parse_version("usage: nothing"), None); + } + + #[cfg(unix)] + #[test] + fn a_server_that_hangs_is_given_up_on() { + use std::os::unix::fs::PermissionsExt; + let dir = tempfile::tempdir().unwrap(); + let script = dir.path().join("hang"); + std::fs::write(&script, "#!/bin/sh\nexec sleep 30\n").unwrap(); + std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap(); + let started = Instant::now(); + assert_eq!(run_version(&script, Duration::from_millis(200)), None); + assert!(started.elapsed() < Duration::from_secs(5)); + } + + #[cfg(unix)] + #[test] + fn a_server_that_answers_is_read() { + use std::os::unix::fs::PermissionsExt; + let dir = tempfile::tempdir().unwrap(); + let script = dir.path().join("answer"); + std::fs::write(&script, "#!/bin/sh\necho 'teksilo-automation-mcp 0.15.1'\n").unwrap(); + std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap(); + assert_eq!( + run_version(&script, Duration::from_secs(5)).as_deref(), + Some("0.15.1") + ); + } +} diff --git a/crates/cargo-teksilo/src/guard.rs b/crates/cargo-teksilo/src/guard.rs index b575be445..f6db795d2 100644 --- a/crates/cargo-teksilo/src/guard.rs +++ b/crates/cargo-teksilo/src/guard.rs @@ -152,7 +152,7 @@ pub fn refusal_text(app: &str, tool: &str, what: &str, sources: Option<&Path>) - Some(parts) if parts < FIRST_PUBLISHED => { "use the resolved crate sources, or upgrade to Teksilo 0.13.0 or later".to_string() } - Some(_) => format!("cargo install cargo-teksilo --version {app} --locked"), + Some(_) => self_install(app, None).display(), None => "install cargo-teksilo from the matching framework checkout".to_string(), }; let mut message = format!("tool {tool} cannot serve {what} for Teksilo {app}\nhelp: {help}"); @@ -165,6 +165,42 @@ pub fn refusal_text(app: &str, tool: &str, what: &str, sources: Option<&Path>) - message } +/// Whether this binary is an older release than the app's teksilo, at any level. +/// +/// A patch-older tool still answers ([`Verdict::Degraded`]), which is right for +/// a lookup and wrong for anything that writes: `update` would "refresh" every +/// file it installed to the older release. An unparseable version is not +/// older; [`check`] already refuses it. +pub fn tool_is_older(app: &str) -> bool { + match (semver_parts(TOOL_VERSION), semver_parts(app)) { + (Some(tool), Some(app)) => tool < app, + _ => false, + } +} + +/// The command that installs the cargo-teksilo matching `app`. +/// +/// From the checkout when the app builds against one, whose version crates.io +/// may not have yet, and into the root this binary was installed under. +pub fn self_install(app: &str, checkout: Option<&Path>) -> crate::install::Install { + let exe = std::env::current_exe().ok(); + match checkout { + Some(root) => crate::install::Install::path( + &root.join("crates").join("cargo-teksilo"), + exe.as_deref(), + ), + None => crate::install::Install::release("cargo-teksilo", app, exe.as_deref()), + } +} + +/// The help line for a tool older than the app. +pub fn outdated_tool_text(app: &str, checkout: Option<&Path>) -> String { + format!( + "help: cargo-teksilo {TOOL_VERSION} is older than Teksilo {app}; `{}` updates it", + self_install(app, checkout).display() + ) +} + /// The note printed when a command answers across a patch-level difference. pub fn degraded_text(app: &str, tool: &str) -> String { format!("note: app uses Teksilo {app}; tool uses {tool} (compatible patch versions)") @@ -253,6 +289,42 @@ mod tests { ); } + #[test] + fn only_a_newer_app_makes_the_tool_older() { + let (major, minor, patch) = semver_parts(TOOL_VERSION).unwrap(); + for newer in [ + format!("{major}.{minor}.{}", patch + 1), + format!("{major}.{}.0", minor + 1), + format!("{}.0.0", major + 1), + ] { + assert!(tool_is_older(&newer), "{newer}"); + } + assert!(!tool_is_older(TOOL_VERSION)); + assert!(!tool_is_older("0.0.1")); + assert!(!tool_is_older("not-a-version")); + } + + #[test] + fn the_tool_installs_from_a_checkout_when_the_app_uses_one() { + let checkout = Path::new("/src/teksilo"); + let line = outdated_tool_text("9.9.9", Some(checkout)); + // The platform's own separator throughout: a literal `crates/...` + // joined onto the checkout printed `/src/teksilo\crates/cargo-teksilo` on Windows. + let path = checkout.join("crates").join("cargo-teksilo"); + assert!( + line.contains(&format!( + "`cargo install --path {} --locked`", + path.display() + )), + "{line}" + ); + let line = outdated_tool_text("9.9.9", None); + assert!( + line.contains("`cargo install cargo-teksilo --version 9.9.9 --locked`"), + "{line}" + ); + } + #[test] fn the_degraded_note_names_both_versions() { let t = degraded_text("0.12.0", "0.12.1"); diff --git a/crates/cargo-teksilo/src/install.rs b/crates/cargo-teksilo/src/install.rs new file mode 100644 index 000000000..81de62e0a --- /dev/null +++ b/crates/cargo-teksilo/src/install.rs @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The `cargo install` line that brings a binary to a given release. +//! +//! `status` prints it and `update` runs it, so both build it here: a printed +//! remedy that differs from what `update` does would send a reader to a +//! command that installs somewhere else. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// One `cargo install` invocation, as arguments after `cargo`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Install { + args: Vec, +} + +impl Install { + /// `cargo install --version --locked`, into the root the + /// binary at `located` came from. + pub fn release(name: &str, version: &str, located: Option<&Path>) -> Self { + Self::with_root( + vec![ + "install".into(), + name.into(), + "--version".into(), + version.into(), + "--locked".into(), + ], + located, + ) + } + + /// `cargo install --path --locked`, for a version a checkout has + /// and crates.io may not. + pub fn path(path: &Path, located: Option<&Path>) -> Self { + Self::with_root( + vec![ + "install".into(), + "--path".into(), + path.display().to_string(), + "--locked".into(), + ], + located, + ) + } + + fn with_root(mut args: Vec, located: Option<&Path>) -> Self { + if let Some(root) = located.and_then(non_default_root) { + args.push("--root".into()); + args.push(root.display().to_string()); + } + Self { args } + } + + /// The command as a reader would type it. + pub fn display(&self) -> String { + std::iter::once("cargo") + .chain(self.args.iter().map(String::as_str)) + .map(quote) + .collect::>() + .join(" ") + } + + /// Run it, with cargo's own progress on the terminal. + pub fn run(&self) -> Result<(), String> { + // Cargo names itself in `CARGO` for the subcommands it runs, which is + // the cargo this invocation came through. + let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into()); + let status = Command::new(cargo) + .args(&self.args) + .status() + .map_err(|e| format!("could not run `{}`: {e}", self.display()))?; + if status.success() { + Ok(()) + } else { + Err(format!("`{}` failed", self.display())) + } + } +} + +/// The install root of a binary at `/bin/`, unless it is the root +/// `cargo install` uses anyway. +/// +/// Without it, replacing a binary that lives under a `--root` of its own would +/// install a second copy into `~/.cargo/bin` and leave the one PATH serves as +/// it was. Left out for the default root, so the usual line stays the usual +/// line. +fn non_default_root(binary: &Path) -> Option { + let bin = binary.parent()?; + if bin.file_name()? != "bin" { + return None; + } + let root = bin.parent()?; + let same = |other: PathBuf| { + let canonical = |p: &Path| std::fs::canonicalize(p).unwrap_or_else(|_| p.to_path_buf()); + canonical(&other) == canonical(root) + }; + if default_root().is_some_and(same) { + None + } else { + Some(root.to_path_buf()) + } +} + +/// Where `cargo install` puts binaries when given no `--root`. +/// +/// `install.root` in a cargo config file is not read: a root set there makes +/// `--root` redundant rather than wrong. +fn default_root() -> Option { + if let Some(root) = std::env::var_os("CARGO_INSTALL_ROOT") { + return Some(root.into()); + } + if let Some(home) = std::env::var_os("CARGO_HOME") { + return Some(home.into()); + } + crate::setup::home_dir().ok().map(|h| h.join(".cargo")) +} + +fn quote(word: &str) -> String { + if word.contains(char::is_whitespace) { + format!("'{word}'") + } else { + word.to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_release_line_names_the_version_and_locks() { + assert_eq!( + Install::release("teksilo-automation-mcp", "0.19.0", None).display(), + "cargo install teksilo-automation-mcp --version 0.19.0 --locked" + ); + } + + #[test] + fn a_binary_under_its_own_root_is_replaced_there() { + let root = tempfile::tempdir().unwrap(); + let binary = root.path().join("bin/cargo-teksilo-fmt"); + let line = Install::release("cargo-teksilo-fmt", "0.19.0", Some(&binary)).display(); + assert!( + line.ends_with(&format!("--locked --root {}", root.path().display())), + "{line}" + ); + } + + #[test] + fn a_binary_outside_a_bin_directory_gets_no_root() { + let line = Install::release( + "cargo-teksilo", + "0.19.0", + Some(Path::new("/work/target/debug/cargo-teksilo")), + ) + .display(); + assert!(!line.contains("--root"), "{line}"); + } + + #[test] + fn a_checkout_installs_by_path() { + assert_eq!( + Install::path(Path::new("/src/my teksilo/crates/cargo-teksilo"), None).display(), + "cargo install --path '/src/my teksilo/crates/cargo-teksilo' --locked" + ); + } +} diff --git a/crates/cargo-teksilo/src/main.rs b/crates/cargo-teksilo/src/main.rs index f98ae4320..ce78c4aaf 100644 --- a/crates/cargo-teksilo/src/main.rs +++ b/crates/cargo-teksilo/src/main.rs @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2026 FernTech -mod automation; +mod companion; mod guard; +mod install; mod output; mod probe; mod resolve; @@ -11,6 +12,7 @@ mod setup; mod show; mod status; mod symbol; +mod update; mod vectors; use clap::{Args, Parser, Subcommand}; @@ -103,6 +105,27 @@ enum Command { #[command(subcommand)] command: ModelCommand, }, + /// Refresh installed tooling to this release. + Update { + /// Update the project's agent instructions only. + #[arg(long)] + agent: bool, + /// Update the agent instructions under your home directory instead. + #[arg(long)] + user: bool, + /// Skip the confirmation. + #[arg(short = 'y', long, conflicts_with = "interactive")] + yes: bool, + /// Confirm each action, showing its equivalent command. + #[arg(short = 'i', long)] + interactive: bool, + /// List what would change, and change nothing. + #[arg(long, conflicts_with_all = ["yes", "interactive"])] + dry_run: bool, + /// With --yes, also replace probe files that have local edits. + #[arg(long)] + force: bool, + }, /// Show project and tooling status. Status { #[arg(long)] @@ -199,6 +222,10 @@ fn project(dir: &Path) -> Result { .ok_or_else(|| "no Cargo.toml found\nhelp: run inside a Cargo project".into()) } fn version(root: &Path) -> Result { + resolution(root).map(|r| r.version) +} +/// The app's resolution, refused when this tool cannot serve it. +fn resolution(root: &Path) -> Result { let r = resolve::resolve(root).map_err(|e| e.to_string())?; match guard::check(&r.version) { guard::Verdict::Refuse { app, tool } => Err(guard::refusal_text( @@ -211,11 +238,11 @@ fn version(root: &Path) -> Result { if let Some(note) = verdict.note() { output::provenance(note); } - Ok(r.version) + Ok(r) } } } -fn install_probe(root: &Path, version: &str, force: bool) -> Result<(), String> { +pub(crate) fn install_probe(root: &Path, version: &str, force: bool) -> Result<(), String> { let written = probe::materialise(root, force).map_err(|e| e.to_string())?; probe::record_provenance(root, version).map_err(|e| e.to_string())?; if output::verbose() { @@ -250,6 +277,133 @@ fn success(message: impl std::fmt::Display) { println!("{message}"); } } +fn update( + dir: &Path, + scope: update::Scope, + yes: bool, + interactive: bool, + dry_run: bool, + force: bool, +) -> Result<(), String> { + // `-i` is nothing but questions, so refuse before printing half of them. + if interactive && let Some(reason) = setup::nobody_to_ask() { + return Err(format!( + "--interactive asks before each action, and {reason}\n\ + help: run `cargo teksilo update --dry-run` to list the changes" + )); + } + let (root, version, header) = if scope == update::Scope::User { + // As `agent install --user`: no project, and the copies are this + // release's whatever a project nearby resolves. + let home = setup::home_dir().map_err(|e| e.to_string())?; + let header = format!("Home {}", home.display()); + (home, guard::TOOL_VERSION.to_string(), header) + } else { + let root = project(dir)?; + let r = resolution(&root)?; + // A patch-older tool passes the guard, and would write an older + // release's files over the ones it is meant to refresh. + if guard::tool_is_older(&r.version) { + return Err(format!( + "tool {} cannot update tooling for Teksilo {}\nhelp: run `{}`, then `cargo teksilo update` again", + guard::TOOL_VERSION, + r.version, + guard::self_install(&r.version, r.checkout_root().as_deref()).display() + )); + } + let header = format!("Project {}\nTeksilo {}", root.display(), r.version); + (root, r.version, header) + }; + let companions = if scope == update::Scope::Project { + [companion::MCP, companion::FMT] + .into_iter() + .map(|c| companion::inspect_path(c, &version)) + .collect() + } else { + Vec::new() + }; + let items = update::plan(&root, scope, &version, companions)?; + if items.is_empty() { + success("Nothing to update."); + return Ok(()); + } + println!("{header}\n"); + for item in &items { + for line in item.describe(&root) { + println!("{line}"); + } + } + if dry_run { + return Ok(()); + } + let confirm = |question: &str| setup::confirm(question, "--yes").map_err(|e| e.to_string()); + let mut accepted = Vec::new(); + let mut skipped = Vec::new(); + if interactive { + for item in &items { + println!(); + for line in item.describe(&root) { + println!("{line}"); + } + println!(" $ {}", item.manual()); + if confirm("Apply?")? { + accepted.push(item); + } else { + skipped.push(item); + } + } + } else if yes || confirm("\nProceed?")? { + accepted.extend(&items); + } else { + return Ok(()); + } + // Agreeing to a listing that names the edited probe files is consent to + // replacing them; `--yes` agrees to a listing nobody read. + let consented = !yes || force; + let mut updated = Vec::new(); + for item in &accepted { + if let Err(e) = item.apply(&root, &version, consented) { + if !updated.is_empty() { + success(format!("Updated: {}.", updated.join(", "))); + } + return Err(e); + } + updated.push(item.name()); + } + if !updated.is_empty() { + success(format!("Updated: {}.", updated.join(", "))); + } + // What is already running keeps the old copy: an agent read its + // instructions when its session started, and its MCP client spawned the + // server once. + let mut restart = Vec::new(); + if accepted + .iter() + .any(|i| matches!(i, update::Item::Agent { .. })) + { + restart.push("the agent session, to read the new instructions"); + } + if accepted + .iter() + .any(|i| matches!(i, update::Item::Binary(found) if found.companion == companion::MCP)) + { + restart.push("the MCP server, to run the new teksilo-automation-mcp"); + } + if !restart.is_empty() { + success(format!("help: restart {}", restart.join(", and "))); + } + if !skipped.is_empty() { + success(format!( + "Skipped: {}.", + skipped + .iter() + .map(|i| i.name()) + .collect::>() + .join(", ") + )); + } + Ok(()) +} fn run(dir: &Path, command: Command) -> Result<(), String> { match command { Command::Symbol { args } => process_result(symbol::run(dir, &args)), @@ -288,6 +442,21 @@ fn run(dir: &Path, command: Command) -> Result<(), String> { status::report(dir); Ok(()) } + Command::Update { + agent, + user, + yes, + interactive, + dry_run, + force, + } => update( + dir, + update::Scope::from_flags(agent, user), + yes, + interactive, + dry_run, + force, + ), Command::Probe { command: ProbeCommand::Install { force }, } => { @@ -404,5 +573,19 @@ mod tests { Cargo::try_parse_from(["cargo", "teksilo", "--quiet", "--verbose", "status"]).is_err() ); assert!(Cargo::try_parse_from(["cargo", "teksilo", "setup"]).is_err()); + for args in [ + &["update"][..], + &["update", "--agent"], + &["update", "--user"], + &["update", "--agent", "--user", "-i"], + &["update", "-y", "--force"], + &["update", "--dry-run"], + &["update", "--user", "--dry-run"], + ] { + let argv = ["cargo", "teksilo"].iter().chain(args); + assert!(Cargo::try_parse_from(argv).is_ok(), "{args:?}"); + } + assert!(Cargo::try_parse_from(["cargo", "teksilo", "update", "-y", "-i"]).is_err()); + assert!(Cargo::try_parse_from(["cargo", "teksilo", "update", "--dry-run", "-y"]).is_err()); } } diff --git a/crates/cargo-teksilo/src/probe.rs b/crates/cargo-teksilo/src/probe.rs index aca065173..dae99f2ee 100644 --- a/crates/cargo-teksilo/src/probe.rs +++ b/crates/cargo-teksilo/src/probe.rs @@ -91,18 +91,7 @@ pub fn materialise(project: &Path, force: bool) -> Result { // three files wants to hear about three, not to re-run and be stopped // again by the next one. if !force { - let mut modified = locally_modified(&dest, &previous); - for file in walk(&PROBE) { - let rel = shipped_path(file.path()); - if !previous.contains_key(&rel) - && dest.join(&rel).exists() - && std::fs::read(dest.join(&rel))? != file.contents() - { - modified.push(rel); - } - } - modified.sort(); - modified.dedup(); + let modified = edited(&dest, &previous)?; if !modified.is_empty() { return Err(ProbeError::LocallyModified { count: modified.len(), @@ -155,6 +144,52 @@ pub fn materialise(project: &Path, force: bool) -> Result { Ok(written) } +/// What [`materialise`] followed by [`record_provenance`] would find, without +/// writing anything. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProbeState { + /// No harness: nothing recorded, no generated directory. + Absent, + /// Every shipped file, the manifest and the recorded version are this + /// release's. + Current, + /// Something would change. + Stale { + /// The version the harness was recorded for, if any. + recorded: Option, + /// Generated files with local edits, which `materialise` refuses to + /// replace without `force`. + edited: Vec, + }, +} + +/// The dry run of a probe install for `version`. +/// +/// Built from the same pieces as [`materialise`] (the edit check, the shipped +/// set and the manifest it would write), so `Current` means a run would +/// change nothing. +pub fn inspect(project: &Path, version: &str) -> ProbeState { + let dest = project.join(DEST); + let recorded = recorded_provenance(project); + if recorded.is_none() && !dest.exists() { + return ProbeState::Absent; + } + let previous = read_manifest(&dest); + let edited = edited(&dest, &previous).unwrap_or_default(); + let wanted: BTreeMap = walk(&PROBE) + .into_iter() + .map(|f| (shipped_path(f.path()), digest(f.contents()))) + .collect(); + let files_current = walk(&PROBE).into_iter().all(|f| { + std::fs::read(dest.join(shipped_path(f.path()))).is_ok_and(|body| body == f.contents()) + }); + if files_current && previous == wanted && recorded.as_deref() == Some(version) { + ProbeState::Current + } else { + ProbeState::Stale { recorded, edited } + } +} + /// Record which teksilo this harness came from, in the consumer's manifest. /// /// Provenance only — never content. A later run compares this against the @@ -287,6 +322,24 @@ fn write_manifest(dest: &Path, manifest: &BTreeMap) -> Result<() Ok(()) } +/// Generated files a consumer has edited: changed since the manifest recorded +/// them, or present without a manifest entry and different from what ships. +fn edited(dest: &Path, previous: &BTreeMap) -> std::io::Result> { + let mut modified = locally_modified(dest, previous); + for file in walk(&PROBE) { + let rel = shipped_path(file.path()); + if !previous.contains_key(&rel) + && dest.join(&rel).exists() + && std::fs::read(dest.join(&rel))? != file.contents() + { + modified.push(rel); + } + } + modified.sort(); + modified.dedup(); + Ok(modified) +} + fn locally_modified(dest: &Path, previous: &BTreeMap) -> Vec { let mut modified: Vec = previous .iter() @@ -378,6 +431,45 @@ mod tests { assert!(w.unchanged.len() > 5); } + #[test] + fn inspect_is_current_exactly_when_an_install_changes_nothing() { + let p = project(); + assert_eq!(inspect(p.path(), "0.19.0"), ProbeState::Absent); + + materialise(p.path(), false).unwrap(); + record_provenance(p.path(), "0.19.0").unwrap(); + assert_eq!(inspect(p.path(), "0.19.0"), ProbeState::Current); + let w = materialise(p.path(), false).unwrap(); + assert!(w.created.is_empty() && w.updated.is_empty()); + + // Same files, recorded for another release. + assert_eq!( + inspect(p.path(), "0.20.0"), + ProbeState::Stale { + recorded: Some("0.19.0".into()), + edited: vec![], + } + ); + + // A deleted file is not an edit, but an install would recreate it. + std::fs::remove_file(p.path().join(DEST).join("session.py")).unwrap(); + assert!(matches!( + inspect(p.path(), "0.19.0"), + ProbeState::Stale { ref edited, .. } if edited.is_empty() + )); + materialise(p.path(), false).unwrap(); + assert_eq!(inspect(p.path(), "0.19.0"), ProbeState::Current); + + std::fs::write(p.path().join(DEST).join("session.py"), b"# mine\n").unwrap(); + assert_eq!( + inspect(p.path(), "0.19.0"), + ProbeState::Stale { + recorded: Some("0.19.0".into()), + edited: vec!["session.py".into()], + } + ); + } + #[test] fn a_local_edit_is_refused_not_overwritten() { let p = project(); diff --git a/crates/cargo-teksilo/src/setup.rs b/crates/cargo-teksilo/src/setup.rs index 3adb55395..962c75771 100644 --- a/crates/cargo-teksilo/src/setup.rs +++ b/crates/cargo-teksilo/src/setup.rs @@ -69,6 +69,11 @@ and a wrong answer reads exactly like a right one. - `cargo teksilo agent install `: instructions only. `--user` supports claude, vibe, and opencode. `agent list` shows targets and installation state. - `cargo teksilo probe install`: install the automation harness in scripts/teksilo_probe/. +- `cargo teksilo update`: bring the installed harness, instructions and companion + binaries to this release; asks first (`-y` skips, `-i` asks per action). + `--agent` for instructions only, `--user` for those under the home directory. + You cannot answer its question: run `--dry-run`, show the user the list, and + pass `-y` once they agree. Then ask them to restart your session. - `cargo teksilo model fetch`: explicitly download the search model. Search uses BM25 until cached; init and search never download weights. - `cargo teksilo status --json`: project version and tooling state. @@ -111,7 +116,7 @@ as separate `teksilo-*` dependencies. On by default already: `widgets`, `i18n`, | An embedded shell | feature `terminal` | `Terminal` | | Driving the running app from an agent or a test | feature `automation` | `.install_automation_bridge_in_debug()` | | Product analytics | feature `telemetry` | `.telemetry(bundle)`, `PrivacySettings` | -| Material 3, Fluent or macOS look | feature `theme-material3`, `theme-fluent` or `theme-macos` | `material3::light()` etc., from the prelude | +| Material 3, Fluent, macOS or KDE Plasma look | feature `theme-material3`, `theme-fluent`, `theme-macos` or `theme-kde` | `material3::light()` etc., from the prelude | | Bar, line and pie charts | crate `teksilo-charts`, at the same version as `teksilo` | `BarChart`, `LineChart`, `PieChart` | | A pan/zoom canvas: node graphs, corkboards, maps | crate `teksilo-scene`, at the same version as `teksilo` | `SceneView`, `SceneModel` | @@ -208,10 +213,13 @@ pub enum SetupError { Probe(#[from] crate::probe::ProbeError), #[error( - "stdin is not a terminal, so there is nobody to answer this prompt.\n\ - Pass {hint} to run non-interactively." + "{reason}, so there is nobody to answer this prompt.\n\ + help: show the list above to the user, and pass {hint} once they agree" )] - NotATerminal { hint: &'static str }, + NobodyToAsk { + reason: &'static str, + hint: &'static str, + }, #[error("could not find a home directory (neither $HOME nor %USERPROFILE% is set)")] NoHome, @@ -1048,16 +1056,42 @@ fn remove_strays(root: &Path, dir: &Path, keep: &[&Path]) -> Result Option<&'static str> { + if !std::io::stdin().is_terminal() { + return Some("stdin is not a terminal"); + } + unattended_marker(|name| std::env::var_os(name)) +} + +/// Environment variables set where nobody watches the terminal. +const UNATTENDED: [(&str, &str); 2] = [ + ("CI", "$CI is set"), + ( + "CLAUDECODE", + "$CLAUDECODE is set, so a coding agent runs this", + ), +]; + +fn unattended_marker(var: impl Fn(&str) -> Option) -> Option<&'static str> { + UNATTENDED.iter().find_map(|(name, reason)| { + let value = var(name)?; + let off = value.is_empty() || value == "0" || value.eq_ignore_ascii_case("false"); + (!off).then_some(*reason) + }) +} + +/// Refuse to prompt when nobody can answer, naming the flag that would have +/// avoided the question. pub fn require_interactive(hint: &'static str) -> Result<(), SetupError> { - if std::io::stdin().is_terminal() { - Ok(()) - } else { - Err(SetupError::NotATerminal { hint }) + match nobody_to_ask() { + None => Ok(()), + Some(reason) => Err(SetupError::NobodyToAsk { reason, hint }), } } @@ -1083,6 +1117,29 @@ pub fn confirm(question: &str, hint: &'static str) -> Result { mod tests { use super::*; + #[test] + fn ci_and_agent_markers_mean_nobody_answers() { + let marker = |pairs: &[(&str, &str)]| { + unattended_marker(|name| { + pairs + .iter() + .find(|(n, _)| *n == name) + .map(|(_, v)| std::ffi::OsString::from(v)) + }) + }; + assert_eq!(marker(&[]), None); + assert_eq!(marker(&[("CI", "true")]), Some("$CI is set")); + assert_eq!(marker(&[("CI", "1")]), Some("$CI is set")); + for off in ["", "0", "false", "FALSE"] { + assert_eq!(marker(&[("CI", off)]), None, "CI={off:?}"); + } + assert!( + marker(&[("CLAUDECODE", "1")]) + .unwrap() + .contains("coding agent") + ); + } + fn temp() -> tempfile::TempDir { tempfile::tempdir().unwrap() } diff --git a/crates/cargo-teksilo/src/status.rs b/crates/cargo-teksilo/src/status.rs index ca572b9f2..240fe10ec 100644 --- a/crates/cargo-teksilo/src/status.rs +++ b/crates/cargo-teksilo/src/status.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2026 FernTech -use crate::{guard, resolve, setup, symbol, vectors}; +use crate::{companion, guard, resolve, setup, symbol, vectors}; use std::path::Path; /// Machine-readable agent status uses the same target selection as installation. @@ -87,11 +87,18 @@ pub fn report(dir: &Path) { // Only an app's own release says which server it needs; without one, the // release this tool was built for is the best guess there is. let expected = version.unwrap_or(guard::TOOL_VERSION); - let server = crate::automation::inspect_path(expected); + let server = companion::inspect_path(companion::MCP, expected); + let formatter = companion::inspect_path(companion::FMT, expected); + let checkout = resolution + .as_ref() + .and_then(|r| r.as_ref().ok()) + .and_then(|r| r.checkout_root()); + let outdated = version.filter(|v| guard::tool_is_older(v)); + let tool_update = outdated.map(|v| guard::self_install(v, checkout.as_deref()).display()); if crate::output::json() { println!( "{}", - serde_json::json!({"tool_version":guard::TOOL_VERSION,"project":root,"teksilo_version":version,"teksilo_enabled_by":enabled_by,"compatible":compatible,"resolution_error":error,"agents":rows,"user_agents":user_rows,"model":model,"python":python,"probe_version":probe,"automation_server":server.json(expected)}) + serde_json::json!({"tool_version":guard::TOOL_VERSION,"tool_outdated":outdated.is_some(),"tool_update_command":tool_update,"project":root,"teksilo_version":version,"teksilo_enabled_by":enabled_by,"compatible":compatible,"resolution_error":error,"agents":rows,"user_agents":user_rows,"model":model,"python":python,"probe_version":probe,"automation_server":server.json(),"formatter":formatter.json()}) ); return; } @@ -125,6 +132,18 @@ pub fn report(dir: &Path) { "help: any other cargo teksilo command, or `cargo generate-lockfile`, creates it", ); } + println!( + "Tool {}{}", + guard::TOOL_VERSION, + outdated + .map(|v| format!(" (older than {v})")) + .unwrap_or_default() + ); + if let Some(app) = outdated { + // Every answer the tool gives is this older release's, so `--quiet` + // keeps it, as it keeps which release answered. + crate::output::provenance(guard::outdated_tool_text(app, checkout.as_deref())); + } println!("Probe {}", probe.as_deref().unwrap_or("missing")); println!("Model {model}"); println!( @@ -135,9 +154,11 @@ pub fn report(dir: &Path) { "missing" } ); - println!("MCP {}", server.line()); - if let Some(help) = server.help() { - crate::output::note(help); + for (label, found) in [("MCP ", &server), ("Fmt ", &formatter)] { + println!("{label} {}", found.line()); + if let Some(help) = found.help() { + crate::output::note(help); + } } let mut paths = std::collections::BTreeSet::new(); let installed: Vec<_> = rows @@ -158,8 +179,8 @@ pub fn report(dir: &Path) { for agent in differing { crate::output::note(format!( "help: the {agent} instructions under your home directory differ from \ - this release's; `cargo teksilo agent install {agent} --user --force` \ - replaces them, local edits included" + this release's; `cargo teksilo update --user` replaces them, local \ + edits included" )); } if crate::output::verbose() { diff --git a/crates/cargo-teksilo/src/update.rs b/crates/cargo-teksilo/src/update.rs new file mode 100644 index 000000000..85edbf410 --- /dev/null +++ b/crates/cargo-teksilo/src/update.rs @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! `cargo teksilo update`: bring what this tool installed to this release. +//! +//! Nothing here decides anything new. The probe's state is [`probe::inspect`], +//! an agent copy's is [`setup::inspect`], a companion binary's is +//! [`companion::inspect`], and writing goes through the same functions +//! `probe install`, `agent install` and `cargo install` use. What this module +//! adds is the selection (only what is ours, present and not current) and the +//! consent: an agent copy that differs from this release cannot be told from +//! one somebody edited, so every replacement is listed before it happens. + +use std::path::{Path, PathBuf}; + +use crate::{companion, probe, setup}; + +/// Which installed tooling one run covers. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Scope { + /// The project's probe harness, agent copies and the companion binaries. + Project, + /// The project's agent copies only. + AgentsOnly, + /// The agent copies under the home directory, instead of the project's. + User, +} + +impl Scope { + pub fn from_flags(agent: bool, user: bool) -> Self { + match (agent, user) { + (_, true) => Scope::User, + (true, false) => Scope::AgentsOnly, + (false, false) => Scope::Project, + } + } +} + +/// One thing to bring up to date. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Item { + Probe { + recorded: Option, + /// Generated files with local edits, replaced only with consent. + edited: Vec, + }, + Agent { + target: setup::Target, + id: &'static str, + user: bool, + }, + Binary(companion::Found), +} + +impl Item { + /// The name in the closing `Updated:` line. + pub fn name(&self) -> String { + match self { + Item::Probe { .. } => "probe harness".into(), + Item::Agent { + target, user: true, .. + } => format!("user {}", target.agent), + Item::Agent { target, .. } => target.agent.into(), + Item::Binary(found) => found.companion.binary.into(), + } + } + + /// The listing lines, verb first, aligned like `init`'s. + pub fn describe(&self, root: &Path) -> Vec { + match self { + Item::Probe { recorded, edited } => { + let mut lines = vec![match recorded { + Some(v) => format!("Update probe harness (from {v})"), + None => "Update probe harness".into(), + }]; + if !edited.is_empty() { + lines.push(format!( + " replaces local edits: {}", + edited + .iter() + .map(|f| format!("scripts/teksilo_probe/{f}")) + .collect::>() + .join(", ") + )); + } + lines + } + Item::Agent { target, user, .. } => vec![format!( + "Update {}{} instructions ({})", + if *user { "user " } else { "" }, + target.agent, + relative(&target.path, root).display() + )], + Item::Binary(found) => vec![format!( + "Install {} {} (replaces {})", + found.companion.binary, + found.expected, + found.version().unwrap_or("a copy of unknown version") + )], + } + } + + /// The command that does the same thing by hand. + pub fn manual(&self) -> String { + match self { + Item::Probe { edited, .. } if !edited.is_empty() => { + "cargo teksilo probe install --force".into() + } + Item::Probe { .. } => "cargo teksilo probe install".into(), + Item::Agent { id, user, .. } => format!( + "cargo teksilo agent install {id}{} --force", + if *user { " --user" } else { "" } + ), + Item::Binary(found) => found.install().map(|i| i.display()).unwrap_or_default(), + } + } + + /// Do it. `consented` is whether the reader accepted this item having seen + /// its listing, which is what lets a probe file with local edits go. + pub fn apply(&self, root: &Path, version: &str, consented: bool) -> Result<(), String> { + match self { + Item::Probe { .. } => crate::install_probe(root, version, consented), + Item::Agent { target, .. } => setup::apply(target).map(drop).map_err(|e| e.to_string()), + Item::Binary(found) => match found.install() { + Some(install) => install.run(), + None => Ok(()), + }, + } + } + + /// Files before binaries: an install is slow and needs the network, and a + /// failed one should leave the files done. + fn order(&self) -> u8 { + match self { + Item::Probe { .. } => 0, + Item::Agent { user: false, .. } => 1, + Item::Agent { user: true, .. } => 2, + Item::Binary(_) => 3, + } + } +} + +/// What a run of `scope` at `root` would change, in the order it changes it. +/// +/// `root` is the project, or the home directory for [`Scope::User`]. +/// `companions` are only considered for [`Scope::Project`]; they are passed in +/// so a test never depends on what is on its PATH. +pub fn plan( + root: &Path, + scope: Scope, + version: &str, + companions: Vec, +) -> Result, String> { + let mut items = Vec::new(); + if scope == Scope::Project + && let probe::ProbeState::Stale { recorded, edited } = probe::inspect(root, version) + { + // Named only when it is another release's: "from 0.19.0" on a 0.19.0 + // harness with an edited file would read as an upgrade that is not. + let recorded = recorded.filter(|r| r != version); + items.push(Item::Probe { recorded, edited }); + } + let user = scope == Scope::User; + let mut seen: Vec = Vec::new(); + for agent in setup::AGENTS { + // An agent with no target in this scope (cursor at user scope) has + // nothing installed to update. + let Ok(mut targets) = setup::selected_targets(root, &[*agent], user) else { + continue; + }; + let target = targets.remove(0); + // Vibe and opencode share codex's `AGENTS.md` in a project; the first + // agent naming a path owns its line. + if seen.contains(&target.path) { + continue; + } + seen.push(target.path.clone()); + match setup::inspect(&target) { + setup::Presence::Stale => items.push(Item::Agent { + target, + id: agent.id(), + user, + }), + setup::Presence::Blocked(reason) => { + return Err(format!("{}: {reason}", target.path.display())); + } + setup::Presence::Current | setup::Presence::Absent => {} + } + } + if scope == Scope::Project { + items.extend( + companions + .into_iter() + .filter(|found| found.install().is_some()) + .map(Item::Binary), + ); + } + items.sort_by_key(Item::order); + Ok(items) +} + +fn relative<'a>(path: &'a Path, root: &Path) -> &'a Path { + path.strip_prefix(root).unwrap_or(path) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn project() -> tempfile::TempDir { + let t = tempfile::tempdir().unwrap(); + std::fs::write( + t.path().join("Cargo.toml"), + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2021\"\n", + ) + .unwrap(); + t + } + + fn target(root: &Path, agent: setup::Agent, user: bool) -> setup::Target { + setup::selected_targets(root, &[agent], user) + .unwrap() + .remove(0) + } + + fn found(binary: companion::Companion, version: Option<&str>) -> companion::Found { + companion::inspect( + binary, + "0.19.1", + || version.map(|_| PathBuf::from("/nowhere/bin-x")), + |_| version.map(String::from), + ) + } + + fn names(items: &[Item]) -> Vec { + items.iter().map(Item::name).collect() + } + + #[test] + fn nothing_installed_plans_nothing() { + let p = project(); + assert!( + plan(p.path(), Scope::Project, "0.19.1", vec![]) + .unwrap() + .is_empty() + ); + } + + #[test] + fn a_current_install_plans_nothing() { + let p = project(); + setup::apply(&target(p.path(), setup::Agent::Claude, false)).unwrap(); + crate::install_probe(p.path(), "0.19.1", false).unwrap(); + let companions = vec![ + found(companion::MCP, Some("0.19.1")), + found(companion::FMT, None), + ]; + assert!( + plan(p.path(), Scope::Project, "0.19.1", companions) + .unwrap() + .is_empty() + ); + } + + #[test] + fn an_edited_skill_is_planned_and_applying_it_clears_the_plan() { + let p = project(); + let claude = target(p.path(), setup::Agent::Claude, false); + setup::apply(&claude).unwrap(); + std::fs::write(claude.path.join("SKILL.md"), "an earlier release's skill").unwrap(); + + let items = plan(p.path(), Scope::Project, "0.19.1", vec![]).unwrap(); + assert_eq!(names(&items), ["Claude Code"]); + assert_eq!( + items[0].manual(), + "cargo teksilo agent install claude --force" + ); + assert_eq!( + items[0].describe(p.path()), + ["Update Claude Code instructions (.claude/skills/teksilo)"] + ); + items[0].apply(p.path(), "0.19.1", false).unwrap(); + assert!( + plan(p.path(), Scope::Project, "0.19.1", vec![]) + .unwrap() + .is_empty() + ); + } + + #[test] + fn agents_sharing_agents_md_are_one_item() { + let p = project(); + let codex = target(p.path(), setup::Agent::Codex, false); + setup::apply(&codex).unwrap(); + let text = std::fs::read_to_string(&codex.path).unwrap(); + std::fs::write( + &codex.path, + text.replace("## Commands", "## Older commands"), + ) + .unwrap(); + let items = plan(p.path(), Scope::Project, "0.19.1", vec![]).unwrap(); + assert_eq!(items.len(), 1); + assert_eq!( + items[0].manual(), + "cargo teksilo agent install codex --force" + ); + } + + #[test] + fn a_probe_recorded_for_another_release_is_planned() { + let p = project(); + crate::install_probe(p.path(), "0.19.0", false).unwrap(); + let items = plan(p.path(), Scope::Project, "0.19.1", vec![]).unwrap(); + assert_eq!( + items, + [Item::Probe { + recorded: Some("0.19.0".into()), + edited: vec![], + }] + ); + assert_eq!( + items[0].describe(p.path()), + ["Update probe harness (from 0.19.0)"] + ); + } + + #[test] + fn an_edited_probe_file_needs_consent() { + let p = project(); + crate::install_probe(p.path(), "0.19.1", false).unwrap(); + let file = p.path().join("scripts/teksilo_probe/session.py"); + std::fs::write(&file, "# mine\n").unwrap(); + + let items = plan(p.path(), Scope::Project, "0.19.1", vec![]).unwrap(); + assert_eq!(items[0].manual(), "cargo teksilo probe install --force"); + assert_eq!( + items[0].describe(p.path())[1], + " replaces local edits: scripts/teksilo_probe/session.py" + ); + assert!(items[0].apply(p.path(), "0.19.1", false).is_err()); + assert_eq!(std::fs::read_to_string(&file).unwrap(), "# mine\n"); + items[0].apply(p.path(), "0.19.1", true).unwrap(); + assert_ne!(std::fs::read_to_string(&file).unwrap(), "# mine\n"); + } + + #[test] + fn only_companions_that_need_it_are_installed() { + let p = project(); + let items = plan( + p.path(), + Scope::Project, + "0.19.1", + vec![ + found(companion::MCP, Some("0.19.0")), + found(companion::FMT, Some("0.19.2")), + ], + ) + .unwrap(); + assert_eq!(names(&items), ["teksilo-automation-mcp"]); + assert_eq!( + items[0].describe(p.path()), + ["Install teksilo-automation-mcp 0.19.1 (replaces 0.19.0)"] + ); + let items = plan( + p.path(), + Scope::Project, + "0.19.1", + vec![ + found(companion::MCP, None), + found(companion::FMT, Some("0.18.4")), + ], + ) + .unwrap(); + assert_eq!(names(&items), ["cargo-teksilo-fmt"]); + } + + #[test] + fn agents_only_leaves_the_probe_and_the_binaries() { + let p = project(); + crate::install_probe(p.path(), "0.19.0", false).unwrap(); + let claude = target(p.path(), setup::Agent::Claude, false); + setup::apply(&claude).unwrap(); + std::fs::write(claude.path.join("SKILL.md"), "older").unwrap(); + let items = plan( + p.path(), + Scope::AgentsOnly, + "0.19.1", + vec![found(companion::MCP, Some("0.19.0"))], + ) + .unwrap(); + assert_eq!(names(&items), ["Claude Code"]); + } + + #[test] + fn user_scope_covers_only_the_home_copies() { + // The home directory doubles as a project here, with a stale probe and + // a stale project-form agent, to show neither is picked up. + let home = project(); + crate::install_probe(home.path(), "0.19.0", false).unwrap(); + let claude = target(home.path(), setup::Agent::Claude, true); + setup::apply(&claude).unwrap(); + std::fs::write(claude.path.join("SKILL.md"), "older").unwrap(); + let mut items = plan( + home.path(), + Scope::User, + "0.19.1", + vec![found(companion::MCP, Some("0.19.0"))], + ) + .unwrap(); + // Only Claude's row: vibe and opencode follow `VIBE_HOME` and + // `XDG_CONFIG_HOME`, which a test cannot keep out of the temp home. + items.retain(|i| { + matches!(i, Item::Agent { id: "claude", .. }) || !matches!(i, Item::Agent { .. }) + }); + assert_eq!(names(&items), ["user Claude Code"]); + assert_eq!( + items[0].manual(), + "cargo teksilo agent install claude --user --force" + ); + } + + #[test] + fn flags_select_the_scope() { + assert_eq!(Scope::from_flags(false, false), Scope::Project); + assert_eq!(Scope::from_flags(true, false), Scope::AgentsOnly); + assert_eq!(Scope::from_flags(false, true), Scope::User); + assert_eq!(Scope::from_flags(true, true), Scope::User); + } +} diff --git a/crates/cargo-teksilo/tests/cli.rs b/crates/cargo-teksilo/tests/cli.rs index 4264f447b..cb447fee2 100644 --- a/crates/cargo-teksilo/tests/cli.rs +++ b/crates/cargo-teksilo/tests/cli.rs @@ -282,3 +282,182 @@ fn user_install_creates_missing_configuration_without_project_writes() { assert!(!root.path().join("scripts").exists()); assert!(!root.path().join("Cargo.lock").exists()); } + +/// Run with fake companion binaries ahead on PATH, each reporting `version`, +/// so `update` never reaches the real ones and never runs `cargo install`. +#[cfg(unix)] +fn run_with_companions(root: &Path, version: &str, args: &[&str]) -> Output { + use std::os::unix::fs::PermissionsExt; + let bin = root.join("fake-bin"); + std::fs::create_dir_all(&bin).unwrap(); + for name in ["teksilo-automation-mcp", "cargo-teksilo-fmt"] { + let script = bin.join(name); + std::fs::write(&script, format!("#!/bin/sh\necho '{name} {version}'\n")).unwrap(); + std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap(); + } + let path = std::env::join_paths( + std::iter::once(bin).chain(std::env::split_paths(&std::env::var_os("PATH").unwrap())), + ) + .unwrap(); + Command::new(env!("CARGO_BIN_EXE_cargo-teksilo")) + .arg("teksilo") + .args(args) + .current_dir(root) + .env("PATH", path) + .env("CARGO_NET_OFFLINE", "true") + .env("FASTEMBED_CACHE_DIR", root.join("model-cache")) + .output() + .unwrap() +} +#[cfg(unix)] +#[test] +fn update_refreshes_what_is_installed_and_nothing_else() { + let root = project(); + let tool = env!("CARGO_PKG_VERSION"); + ok(&run( + root.path(), + &["agent", "install", "claude", "--quiet"], + )); + let skill = root.path().join(".claude/skills/teksilo/SKILL.md"); + let shipped = std::fs::read_to_string(&skill).unwrap(); + std::fs::write(&skill, "an earlier release's skill").unwrap(); + + let listing = "Update Claude Code instructions (.claude/skills/teksilo)"; + let untouched = || { + assert_eq!( + std::fs::read_to_string(&skill).unwrap(), + "an earlier release's skill" + ) + }; + // Nobody to ask: the listing, then an error naming the flag, never a wait. + let result = run_with_companions(root.path(), tool, &["update"]); + assert!(!result.status.success()); + assert!(String::from_utf8_lossy(&result.stderr).contains("pass --yes once they agree")); + assert!(String::from_utf8_lossy(&result.stdout).contains(listing)); + untouched(); + // `-i` is only questions, so it refuses before printing any. + let result = run_with_companions(root.path(), tool, &["update", "-i"]); + assert!(!result.status.success()); + assert!(String::from_utf8_lossy(&result.stderr).contains("update --dry-run")); + assert!(result.stdout.is_empty()); + untouched(); + // A dry run lists and succeeds. + let result = run_with_companions(root.path(), tool, &["update", "--dry-run"]); + ok(&result); + assert!(String::from_utf8_lossy(&result.stdout).contains(listing)); + untouched(); + + let result = run_with_companions(root.path(), tool, &["update", "-y"]); + ok(&result); + let stdout = String::from_utf8_lossy(&result.stdout); + assert!(stdout.contains("Updated: Claude Code.")); + assert!(stdout.contains("help: restart the agent session")); + assert!(!stdout.contains("MCP server")); + assert_eq!(std::fs::read_to_string(&skill).unwrap(), shipped); + // Only what was there: no harness, no other agent. + assert!(!root.path().join("scripts").exists()); + assert!(!root.path().join("AGENTS.md").exists()); + + let manifest = std::fs::read_to_string(root.path().join("Cargo.toml")).unwrap(); + let result = run_with_companions(root.path(), tool, &["update", "-y"]); + ok(&result); + assert_eq!( + String::from_utf8_lossy(&result.stdout), + "Nothing to update.\n" + ); + assert_eq!( + std::fs::read_to_string(root.path().join("Cargo.toml")).unwrap(), + manifest + ); +} +#[cfg(unix)] +#[test] +fn status_names_the_command_for_an_older_formatter() { + // The app one patch ahead, the fakes at this release: one patch older. + let tool = env!("CARGO_PKG_VERSION"); + let (minor, patch) = tool.rsplit_once('.').unwrap(); + let app = format!("{minor}.{}", patch.parse::().unwrap() + 1); + let root = project_at(&app); + ok(&run( + root.path(), + &["show", "docs/agent-tooling.md", "--quiet"], + )); // writes Cargo.lock + let result = run_with_companions(root.path(), tool, &["status"]); + ok(&result); + let stdout = String::from_utf8_lossy(&result.stdout); + assert!( + stdout.contains(&format!("Fmt {tool} (older than {app})")), + "{stdout}" + ); + assert!( + stdout.contains(&format!("MCP {tool} (older than {app})")), + "{stdout}" + ); + let stderr = String::from_utf8_lossy(&result.stderr); + for name in ["cargo-teksilo-fmt", "teksilo-automation-mcp"] { + assert!( + stderr.contains(&format!("`cargo install {name} --version {app} --locked`")), + "{stderr}" + ); + } +} +#[test] +fn an_older_tool_says_so_and_does_not_update() { + let tool = env!("CARGO_PKG_VERSION"); + let (minor, patch) = tool.rsplit_once('.').unwrap(); + let app = format!("{minor}.{}", patch.parse::().unwrap() + 1); + let root = project_at(&app); + ok(&run( + root.path(), + &["agent", "install", "claude", "--quiet"], + )); + let skill = root.path().join(".claude/skills/teksilo/SKILL.md"); + std::fs::write(&skill, "a newer release's skill").unwrap(); + + let result = run(root.path(), &["status", "--quiet"]); + ok(&result); + assert!( + String::from_utf8_lossy(&result.stdout) + .contains(&format!("Tool {tool} (older than {app})")) + ); + let help = format!("`cargo install cargo-teksilo --version {app} --locked`"); + assert!(String::from_utf8_lossy(&result.stderr).contains(&help)); + + let result = run(root.path(), &["update", "-y"]); + assert!(!result.status.success()); + assert!(String::from_utf8_lossy(&result.stderr).contains(&help)); + assert_eq!( + std::fs::read_to_string(&skill).unwrap(), + "a newer release's skill" + ); +} +#[test] +fn update_user_refreshes_home_copies_outside_any_project() { + let outside = tempfile::tempdir().unwrap(); + let home = outside.path().join("home"); + let user_run = |args: &[&str]| { + Command::new(env!("CARGO_BIN_EXE_cargo-teksilo")) + .arg("teksilo") + .args(args) + .current_dir(outside.path()) + .env("HOME", &home) + .env("USERPROFILE", &home) + .env("VIBE_HOME", home.join("vibe")) + .env("XDG_CONFIG_HOME", home.join("config")) + .output() + .unwrap() + }; + ok(&user_run(&[ + "agent", "install", "claude", "--user", "--quiet", + ])); + let skill = home.join(".claude/skills/teksilo/SKILL.md"); + let shipped = std::fs::read_to_string(&skill).unwrap(); + std::fs::write(&skill, "an earlier release's skill").unwrap(); + + let result = user_run(&["update", "--user", "-y"]); + ok(&result); + assert!(String::from_utf8_lossy(&result.stdout).contains("Updated: user Claude Code.")); + assert_eq!(std::fs::read_to_string(&skill).unwrap(), shipped); + assert!(!home.join("vibe").exists()); + assert!(!outside.path().join("Cargo.lock").exists()); +} diff --git a/crates/teksilo-app/src/app.rs b/crates/teksilo-app/src/app.rs index a14780951..459dfa5b5 100644 --- a/crates/teksilo-app/src/app.rs +++ b/crates/teksilo-app/src/app.rs @@ -3353,6 +3353,12 @@ impl TeksiloAppHandler { // back. `refresh_accessibility_preferences` applies any change // to every window (marking them dirty for repaint). if focused { + // The same moment backstops the desktop watch: a + // configuration file written while no signal reached us + // (no bus, or a signal missed) is caught here. + if self.wm.recheck_os_theme_files() { + self.wm.request_redraw_all(); + } self.wm.refresh_accessibility_preferences(); } // The global native menu (macOS) follows window focus: make the @@ -3431,15 +3437,15 @@ impl TeksiloAppHandler { // is honoured here too. OS-following results carry the id "system". match self.wm.theme_mode() { ThemeMode::Manual => {} // ignore OS theme changes + // Trust winit's per-window signal (authoritative on + // macOS/Windows where OS-colour querying is unimplemented). Each + // window keeps its density, and an unchanged state sets nothing. ThemeMode::FollowSystem => { - // Trust winit's per-window signal (authoritative on - // macOS/Windows where OS-colour querying is unimplemented). - let theme = match winit_theme { - winit::window::Theme::Dark => teksilo_core::presets::intui::dark(), - winit::window::Theme::Light => teksilo_core::presets::intui::light(), - } - .with_id("system"); - self.wm.set_theme(theme); + self.wm + .apply_os_source(crate::native_theme::OsThemeSource::follow_system(matches!( + winit_theme, + winit::window::Theme::Dark + ))); } // Native adopts the OS's actual colours on Linux; on macOS/Windows // (no OS-colour query) it follows winit's authoritative light/dark @@ -3461,6 +3467,8 @@ impl ApplicationHandler for TeksiloAppHandler { // Before the first window, so that it is born knowing whether anything // on screen can be seen. A no-op once watching. self.wm.watch_session(); + // Likewise for the desktop's appearance, while the theme follows it. + self.wm.sync_os_theme_watch(); if !self.initial_created && let Some(config) = self.initial_window.take() @@ -3511,18 +3519,29 @@ impl ApplicationHandler for TeksiloAppHandler { self.wm.apply_session(Instant::now()); return; } - Err(payload) => { - // A web-view engine asking to run. Answered here, with - // no redraw: the frames it commits wake their windows - // through their pictures, and the events it posts - // arrive as payloads of their own. - #[cfg(feature = "web-view")] - if payload.is::() { - self.pump_web_views(); + // The desktop's appearance changed: read it again, and + // redraw every window when that set a theme or changed a + // preference. + Err(payload) => match self.wm.route_os_theme_payload(payload) { + Ok(changed) => { + if changed { + self.wm.request_redraw_all(); + } return; } - AppEvent::External(payload) - } + Err(payload) => { + // A web-view engine asking to run. Answered here, with + // no redraw: the frames it commits wake their windows + // through their pictures, and the events it posts + // arrive as payloads of their own. + #[cfg(feature = "web-view")] + if payload.is::() { + self.pump_web_views(); + return; + } + AppEvent::External(payload) + } + }, }, }, event => event, @@ -3981,6 +4000,10 @@ impl AppEventPoster for AppEventProxy { pub struct TeksiloAppBuilder { theme: Theme, theme_mode: ThemeMode, + /// The app's own `ThemeMode::Native` theme, set via + /// [`native_theme`](Self::native_theme) and handed to the window manager + /// at `run`. + native_theme: Option, pen_batching: teksilo_platform::PenBatching, typesetter: Option, font_registrars: Vec>, @@ -4044,6 +4067,7 @@ impl TeksiloAppBuilder { Self { theme: teksilo_core::presets::intui::light(), theme_mode: ThemeMode::Manual, + native_theme: None, pen_batching: teksilo_platform::PenBatching::default(), typesetter: None, font_registrars: Vec::new(), @@ -4125,6 +4149,13 @@ impl TeksiloAppBuilder { self.app_paths.as_ref() } + /// The native-theme factory installed with + /// [`native_theme`](Self::native_theme), if any. Builder-extension traits + /// and tests read it to see what an install hook put there. + pub fn configured_native_theme(&self) -> Option<&crate::NativeThemeFactory> { + self.native_theme.as_ref() + } + /// Configure the persistence bundle. When `run`/`build_headless` /// fires, the bundle is opened against the configured `AppPaths` /// and every active service is registered in `app_state`, where @@ -4420,6 +4451,33 @@ impl TeksiloAppBuilder { self } + /// Give `ThemeMode::Native` a theme of the app's own. + /// + /// Whenever Native mode needs a theme (a window opening, the desktop's + /// light or dark state changing, a switch to Native mode at runtime), `f` + /// is asked first with a [`NativeThemeRequest`](crate::NativeThemeRequest) + /// carrying what the desktop reports, whether the theme is to be dark and + /// the density wanted. Answer `Some(theme)` to use it, for example a + /// design-language preset rebuilt over the desktop's colours so Native + /// mode brings that preset's geometry and chrome; answer `None` to keep + /// the default, IntUI coloured by the desktop + /// ([`presets::intui::from_os`](teksilo_core::presets::intui::from_os)). + /// + /// The theme the app answers with is given the id `"system"`, like every + /// theme that follows the OS, so a `ThemeSwitcher` shows it as "follow + /// OS", and is projected onto the density it was asked at with + /// [`Theme::with_density`], which is the density of the window it is + /// for. Setting a factory does not select Native mode; combine it with + /// [`theme_mode`](Self::theme_mode). See + /// [`resolve_native_theme`](crate::resolve_native_theme). + pub fn native_theme( + mut self, + f: impl Fn(&crate::NativeThemeRequest<'_>) -> Option + 'static, + ) -> Self { + self.native_theme = Some(Box::new(f)); + self + } + /// Choose how a drained pen batch reaches the tree. /// /// A digitizer outruns the window's message rate, so one poll routinely @@ -5003,6 +5061,7 @@ impl TeksiloAppBuilder { // rather than threaded through `TeksiloAppHandler::new`'s already long // parameter list. app.wm.set_pen_batching(self.pen_batching); + app.wm.set_native_theme(self.native_theme); app.wm.set_records_announcements(self.records_announcements); app.external_ctx_handler = self.external_ctx_handler; // Hand over any registered loop-tick hook (e.g. the `teksilo-async` diff --git a/crates/teksilo-app/src/lib.rs b/crates/teksilo-app/src/lib.rs index 7a58c00e8..e0d9bc5d5 100644 --- a/crates/teksilo-app/src/lib.rs +++ b/crates/teksilo-app/src/lib.rs @@ -9,6 +9,7 @@ pub mod automation_bridge; pub mod default_post_root; pub(crate) mod input_loop; pub(crate) mod input_routing; +pub mod native_theme; pub(crate) mod redraw_gate; pub mod window_config; pub mod window_manager; @@ -22,6 +23,7 @@ pub use app_event_observers::AppEventObservers; #[cfg(feature = "automation")] pub use automation_bridge::TeksiloAppBuilderAutomationExt; pub use default_post_root::DefaultPostRoot; +pub use native_theme::{NativeThemeFactory, NativeThemeRequest, resolve_native_theme}; pub use window_config::{ ModalConfig, PostRootBuilder, RootBuilder, TeksiloWindowId, WindowConfig, WindowRemovedCallback, WindowRemovedEvent, diff --git a/crates/teksilo-app/src/native_theme.rs b/crates/teksilo-app/src/native_theme.rs new file mode 100644 index 000000000..4faa1f284 --- /dev/null +++ b/crates/teksilo-app/src/native_theme.rs @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The one place [`ThemeMode::Native`](crate::ThemeMode::Native) turns what +//! the desktop reports into a [`Theme`]. +//! +//! An app can hand the window manager a [`NativeThemeFactory`] through +//! [`TeksiloAppBuilder::native_theme`](crate::TeksiloAppBuilder::native_theme): +//! asked with a [`NativeThemeRequest`] whenever Native mode needs a theme (a +//! window opening, the desktop's light or dark state changing, a switch to +//! Native mode), it can answer with a theme of its own, for example a preset +//! that brings its own geometry and chrome along with the desktop's colours. +//! When there is no factory, or it answers `None`, the theme is IntUI +//! coloured by the desktop ([`presets::intui::from_os`]). +//! +//! [`resolve_native_theme`] sets no id and applies no density. The window +//! manager resolves every theme that follows the OS, under +//! [`ThemeMode::FollowSystem`](crate::ThemeMode::FollowSystem) as under Native +//! mode, through one function too: it gives the theme the id `"system"` and +//! the density of the window it is for, and a re-read of the desktop that +//! changes nothing a theme was resolved from sets no theme. The previewer +//! applies its own density. + +use teksilo_core::Theme; +use teksilo_core::presets; +use teksilo_platform::os_theme::NativeDesktop; +use teksilo_tokens::{ColorSchemePreference, TargetDensity}; + +/// What [`ThemeMode::Native`](crate::ThemeMode::Native) asks a +/// [`NativeThemeFactory`] for a theme with. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub struct NativeThemeRequest<'a> { + /// What the desktop reports: its colours and, on a KDE Plasma session, + /// its settings. + pub desktop: &'a NativeDesktop, + /// Whether the theme is to be dark. + pub dark: bool, + /// The target density the theme is wanted at. + pub density: TargetDensity, +} + +impl<'a> NativeThemeRequest<'a> { + /// A request for a theme over `desktop`, dark or light, at `density`. + pub fn new(desktop: &'a NativeDesktop, dark: bool, density: TargetDensity) -> Self { + NativeThemeRequest { + desktop, + dark, + density, + } + } +} + +/// An app's own answer to a [`NativeThemeRequest`]: `Some` theme to use, or +/// `None` to keep the default, IntUI coloured by the desktop. +/// +/// Installed with +/// [`TeksiloAppBuilder::native_theme`](crate::TeksiloAppBuilder::native_theme). +pub type NativeThemeFactory = Box) -> Option>; + +/// The theme [`ThemeMode::Native`](crate::ThemeMode::Native) uses for +/// `request`: the `factory`'s answer when there is a factory and it answers +/// `Some`, otherwise [`presets::intui::from_os`] over the desktop's colour +/// report with `request.dark`. +/// +/// The theme is returned as the factory or the preset built it: no id is set +/// and no density applied. +pub fn resolve_native_theme( + factory: Option<&NativeThemeFactory>, + request: &NativeThemeRequest<'_>, +) -> Theme { + factory + .and_then(|factory| factory(request)) + .unwrap_or_else(|| presets::intui::from_os(&request.desktop.os, request.dark)) +} + +/// The desktop reads a theme that follows the OS is made from, and the +/// accessibility preferences read again with it when the desktop changes. +/// The window manager reads the platform's own ([`OsQueries::PLATFORM`]); +/// its tests hand it fixed answers. +#[derive(Clone, Copy)] +pub(crate) struct OsQueries { + /// What the desktop reports for [`ThemeMode::Native`](crate::ThemeMode::Native). + pub(crate) native_desktop: fn() -> NativeDesktop, + /// The desktop's light or dark preference, for + /// [`ThemeMode::FollowSystem`](crate::ThemeMode::FollowSystem). + pub(crate) color_scheme: fn() -> ColorSchemePreference, + /// The accessibility preferences, among them reduced motion, read again + /// whenever the desktop's appearance is heard to change. + pub(crate) accessibility: fn() -> teksilo_platform::AccessibilityPreferences, +} + +impl OsQueries { + /// The platform's reads, + /// [`query_native_desktop`](teksilo_platform::os_theme::query_native_desktop), + /// [`query_color_scheme`](teksilo_platform::os_theme::query_color_scheme) + /// and + /// [`AccessibilityPreferences::query`](teksilo_platform::AccessibilityPreferences::query). + pub(crate) const PLATFORM: OsQueries = OsQueries { + native_desktop: teksilo_platform::os_theme::query_native_desktop, + color_scheme: teksilo_platform::os_theme::query_color_scheme, + accessibility: teksilo_platform::AccessibilityPreferences::query, + }; + + /// Whether [`ThemeMode::FollowSystem`](crate::ThemeMode::FollowSystem) + /// is dark: the desktop's light or dark preference when it states one, + /// otherwise `hint`, the window system's light or dark state, with no + /// hint meaning light. A window that opens and a change heard later both + /// decide here, so they cannot disagree. + pub(crate) fn follow_system_dark(self, hint: Option) -> bool { + match (self.color_scheme)() { + ColorSchemePreference::Dark => true, + ColorSchemePreference::Light => false, + ColorSchemePreference::NoPreference => hint.unwrap_or(false), + } + } +} + +/// What a theme that follows the OS is resolved from, apart from the density +/// it is wanted at. +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum OsThemeSource { + /// [`ThemeMode::FollowSystem`](crate::ThemeMode::FollowSystem): the IntUI + /// light or dark preset. + FollowSystem { + /// Whether the theme is dark. + dark: bool, + }, + /// [`ThemeMode::Native`](crate::ThemeMode::Native): the desktop as it was + /// read, and whether the theme is dark. + Native { + /// What the desktop reported, boxed: a KDE desktop carries its whole + /// colour scheme. + desktop: Box, + /// Whether the theme is dark. + dark: bool, + }, +} + +impl OsThemeSource { + /// The IntUI light or dark preset. + pub(crate) fn follow_system(dark: bool) -> Self { + OsThemeSource::FollowSystem { dark } + } + + /// The theme Native mode makes of `desktop`. A desktop that states a + /// light or dark preference decides `dark` itself; one that states none + /// (macOS, Windows, a Linux desktop the portal answers for with no + /// preference) follows `hint`, the window system's light or dark state, + /// and is light when there is none. + pub(crate) fn native(desktop: NativeDesktop, hint: Option) -> Self { + let dark = match desktop.os.color_scheme { + ColorSchemePreference::Dark => true, + ColorSchemePreference::Light => false, + ColorSchemePreference::NoPreference => hint.unwrap_or(false), + }; + OsThemeSource::Native { + desktop: Box::new(desktop), + dark, + } + } + + /// This source asked for at `density`. + pub(crate) fn at(self, density: TargetDensity) -> OsThemeInputs { + OsThemeInputs { + source: self, + density, + } + } +} + +/// Everything a theme that follows the OS is resolved from: equal inputs +/// resolve to equal themes, so a re-read that gives the inputs a theme was +/// last resolved from needs no new theme. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct OsThemeInputs { + /// The mode's reading of the desktop. + pub(crate) source: OsThemeSource, + /// The density the theme is wanted at. + pub(crate) density: TargetDensity, +} + +/// The theme that follows the OS for `inputs`, with the id `"system"`: the +/// theme the source resolves to, projected onto `inputs.density` by +/// [`Theme::with_density`]. +/// +/// Under [`OsThemeSource::Native`] the theme is [`resolve_native_theme`]'s. +/// A desktop that states no preference is asked for with its report +/// stating the light or dark preference `dark` chose, so the factory and the +/// colours of [`presets::intui::from_os`] are those of a light or dark +/// desktop. +pub(crate) fn resolve_os_theme( + factory: Option<&NativeThemeFactory>, + inputs: &OsThemeInputs, +) -> Theme { + let theme = match &inputs.source { + OsThemeSource::FollowSystem { dark: true } => presets::intui::dark(), + OsThemeSource::FollowSystem { dark: false } => presets::intui::light(), + OsThemeSource::Native { desktop, dark } => { + if desktop.os.color_scheme == ColorSchemePreference::NoPreference { + let mut stated = NativeDesktop::clone(desktop); + stated.os.color_scheme = if *dark { + ColorSchemePreference::Dark + } else { + ColorSchemePreference::Light + }; + resolve_native_theme( + factory, + &NativeThemeRequest::new(&stated, *dark, inputs.density), + ) + } else { + resolve_native_theme( + factory, + &NativeThemeRequest::new(desktop, *dark, inputs.density), + ) + } + } + }; + theme.with_density(inputs.density).with_id("system") +} + +/// One re-read of the OS appearance, brought to every theme that follows it: +/// each window's and the window manager's own. +/// +/// Each of those remembers the [`OsThemeInputs`] its theme was last resolved +/// from. [`theme_for`](Self::theme_for) answers `None` for one whose inputs, +/// at its own density, are unchanged, so it is not set again, and resolves +/// each density once however many windows share it. +pub(crate) struct OsThemeUpdate<'a> { + /// The app's own Native-mode theme, if any. + factory: Option<&'a NativeThemeFactory>, + /// What the OS was read as. + source: OsThemeSource, + /// The themes resolved so far, by density. + resolved: Vec<(TargetDensity, Theme)>, +} + +impl<'a> OsThemeUpdate<'a> { + /// An update to the themes `source` resolves to. + pub(crate) fn new(factory: Option<&'a NativeThemeFactory>, source: OsThemeSource) -> Self { + OsThemeUpdate { + factory, + source, + resolved: Vec::new(), + } + } + + /// The theme to set on a target whose theme is at `density` and was last + /// resolved from `applied` (`None` when it does not follow the OS), or + /// `None` when `applied` is already this update's inputs at `density`. + /// `applied` becomes those inputs. + pub(crate) fn theme_for( + &mut self, + applied: &mut Option, + density: TargetDensity, + ) -> Option { + let inputs = self.source.clone().at(density); + if applied.as_ref() == Some(&inputs) { + return None; + } + let theme = match self.resolved.iter().find(|(at, _)| *at == density) { + Some((_, theme)) => theme.clone(), + None => { + let theme = resolve_os_theme(self.factory, &inputs); + self.resolved.push((density, theme.clone())); + theme + } + }; + *applied = Some(inputs); + Some(theme) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::cell::RefCell; + use std::rc::Rc; + use teksilo_core::WidgetTree; + use teksilo_tokens::{Color, ColorTokens, InputTokens, OsThemeColors}; + + const DENSITIES: [TargetDensity; 3] = [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ]; + + /// The Native-mode theme the window manager made of a desktop stating a + /// light or dark preference before densities were kept: IntUI light or + /// dark under the report's colours, at the presets' Compact density. + fn old_stated(os: &OsThemeColors) -> Theme { + let base = if os.color_scheme.is_dark() { + presets::intui::dark() + } else { + presets::intui::light() + }; + Theme { + colors: ColorTokens::from_os_colors(os), + ..base + } + .with_id("system") + } + + /// Reports for every preference, with no colours, with the colours every + /// desktop reader fills, and with the view and semantic colours as well. + fn reports() -> Vec { + let mut out = Vec::new(); + for scheme in [ + ColorSchemePreference::Light, + ColorSchemePreference::Dark, + ColorSchemePreference::NoPreference, + ] { + let bare = OsThemeColors::new(scheme); + + let mut accent_only = OsThemeColors::new(scheme); + accent_only.accent = Some(Color::from_rgb(0.24, 0.68, 0.91)); + + let mut legacy = OsThemeColors::new(scheme); + legacy.accent = Some(Color::from_rgb(0.24, 0.68, 0.91)); + legacy.window_bg = Some(Color::from_rgb(0.94, 0.94, 0.95)); + legacy.window_fg = Some(Color::from_rgb(0.14, 0.15, 0.16)); + legacy.button_bg = Some(Color::from_rgb(0.99, 0.99, 0.99)); + legacy.button_fg = Some(Color::from_rgb(0.14, 0.15, 0.16)); + legacy.selection_bg = Some(Color::from_rgb(0.24, 0.68, 0.91)); + legacy.selection_fg = Some(Color::WHITE); + legacy.tooltip_bg = Some(Color::from_rgb(0.97, 0.97, 0.97)); + legacy.tooltip_fg = Some(Color::from_rgb(0.14, 0.15, 0.16)); + + let mut full = legacy.clone(); + full.view_bg = Some(Color::WHITE); + full.view_fg = Some(Color::from_rgb(0.12, 0.12, 0.12)); + full.alternate_bg = Some(Color::from_rgb(0.97, 0.97, 0.97)); + full.inactive_fg = Some(Color::from_rgb(0.44, 0.45, 0.46)); + full.link = Some(Color::from_rgb(0.16, 0.5, 0.73)); + full.link_visited = Some(Color::from_rgb(0.61, 0.35, 0.71)); + full.error = Some(Color::from_rgb(0.85, 0.27, 0.33)); + full.warning = Some(Color::from_rgb(0.96, 0.45, 0.0)); + full.success = Some(Color::from_rgb(0.15, 0.68, 0.38)); + full.focus = Some(Color::from_rgb(0.24, 0.68, 0.91)); + + out.extend([bare, accent_only, legacy, full]); + } + out + } + + /// Every source the window manager can read: both FollowSystem presets, + /// and Native over every report with no hint and both hint values. + fn sources() -> Vec { + let mut out = vec![ + OsThemeSource::follow_system(false), + OsThemeSource::follow_system(true), + ]; + for os in reports() { + for hint in [None, Some(false), Some(true)] { + out.push(OsThemeSource::native(NativeDesktop::new(os.clone()), hint)); + } + } + out + } + + fn assert_same(new: &Theme, old: &Theme, what: &str) { + assert_eq!(new.id, old.id, "{what}: id"); + assert_eq!(new.colors, old.colors, "{what}: colours"); + assert_eq!(new.input, old.input, "{what}: input tokens"); + assert_eq!(new, old, "{what}: theme"); + } + + #[test] + fn a_desktop_stating_a_preference_resolves_as_before_at_compact() { + for os in reports() { + if os.color_scheme == ColorSchemePreference::NoPreference { + continue; + } + for hint in [None, Some(false), Some(true)] { + let inputs = OsThemeSource::native(NativeDesktop::new(os.clone()), hint) + .at(TargetDensity::Compact); + assert_same( + &resolve_os_theme(None, &inputs), + &old_stated(&os), + &format!("{os:?} with hint {hint:?}"), + ); + } + } + } + + #[test] + fn follow_system_resolves_to_the_intui_presets() { + for dark in [false, true] { + let preset = if dark { + presets::intui::dark() + } else { + presets::intui::light() + }; + for density in DENSITIES { + assert_same( + &resolve_os_theme(None, &OsThemeSource::follow_system(dark).at(density)), + &preset.with_density(density).with_id("system"), + &format!("dark {dark} at {density:?}"), + ); + } + } + } + + #[test] + fn the_density_asked_for_is_the_density_given() { + for source in sources() { + let compact = resolve_os_theme(None, &source.clone().at(TargetDensity::Compact)); + assert_eq!( + compact.input, + InputTokens::for_density(TargetDensity::Compact) + ); + for density in DENSITIES { + let theme = resolve_os_theme(None, &source.clone().at(density)); + assert_eq!(theme.input.density, density, "{source:?} at {density:?}"); + assert_same( + &theme, + &compact.with_density(density), + &format!("{source:?} at {density:?}"), + ); + } + } + } + + /// A density projection that marks the theme it projects, so a test can + /// tell that it ran. + fn marking_projection(theme: &Theme, density: TargetDensity) -> Theme { + let mut colors = theme.colors.clone(); + colors.accent = match density { + TargetDensity::Compact => Color::from_rgb(1.0, 0.0, 0.0), + TargetDensity::Comfortable => Color::from_rgb(0.0, 1.0, 0.0), + TargetDensity::Touch => Color::from_rgb(0.0, 0.0, 1.0), + }; + Theme { + input: InputTokens::for_density(density), + colors, + ..theme.clone() + } + } + + #[test] + fn a_factory_theme_is_projected_by_its_own_density_projection() { + let asked = Rc::new(RefCell::new(Vec::new())); + let log = asked.clone(); + let factory: NativeThemeFactory = Box::new(move |request: &NativeThemeRequest<'_>| { + log.borrow_mut().push(request.density); + Some( + presets::intui::dark() + .with_id("app.native") + .with_density_projection(marking_projection), + ) + }); + let desktop = NativeDesktop::new(OsThemeColors::new(ColorSchemePreference::Dark)); + for density in DENSITIES { + let theme = resolve_os_theme( + Some(&factory), + &OsThemeSource::native(desktop.clone(), None).at(density), + ); + let want = presets::intui::dark() + .with_id("app.native") + .with_density_projection(marking_projection) + .with_density(density) + .with_id("system"); + assert_eq!(theme.colors, want.colors, "{density:?}"); + assert_eq!(theme, want, "{density:?}"); + } + assert_eq!(*asked.borrow(), DENSITIES.to_vec()); + } + + #[test] + fn a_desktop_with_no_preference_follows_the_hint() { + for os in reports() { + if os.color_scheme != ColorSchemePreference::NoPreference { + continue; + } + for hint in [None, Some(false), Some(true)] { + let dark = hint == Some(true); + let mut stated = os.clone(); + stated.color_scheme = if dark { + ColorSchemePreference::Dark + } else { + ColorSchemePreference::Light + }; + let inputs = OsThemeSource::native(NativeDesktop::new(os.clone()), hint) + .at(TargetDensity::Compact); + let theme = resolve_os_theme(None, &inputs); + assert_eq!(theme.is_dark(), dark, "{os:?} with hint {hint:?}"); + assert_same( + &theme, + &old_stated(&stated), + &format!("{os:?} with hint {hint:?}"), + ); + } + } + // With no colours, the bare presets. + let bare = NativeDesktop::new(OsThemeColors::new(ColorSchemePreference::NoPreference)); + let at = |hint| { + resolve_os_theme( + None, + &OsThemeSource::native(bare.clone(), hint).at(TargetDensity::Compact), + ) + }; + assert_eq!(at(Some(true)), presets::intui::dark().with_id("system")); + assert_eq!(at(Some(false)), presets::intui::light().with_id("system")); + assert_eq!(at(None), presets::intui::light().with_id("system")); + } + + #[test] + fn without_a_factory_the_theme_is_intui_from_the_report() { + for os in reports() { + for dark in [false, true] { + let desktop = NativeDesktop::new(os.clone()); + let request = NativeThemeRequest::new(&desktop, dark, TargetDensity::Touch); + assert_eq!( + resolve_native_theme(None, &request), + presets::intui::from_os(&os, dark), + "{os:?}, dark {dark}" + ); + } + } + } + + #[test] + fn a_factory_answering_some_wins() { + let seen = Rc::new(RefCell::new(Vec::new())); + let log = seen.clone(); + let factory: NativeThemeFactory = Box::new(move |request: &NativeThemeRequest<'_>| { + log.borrow_mut() + .push((request.desktop.clone(), request.dark, request.density)); + Some(presets::intui::dark().with_id("app.native")) + }); + + let mut os = OsThemeColors::new(ColorSchemePreference::Light); + os.accent = Some(Color::from_rgb(0.9, 0.2, 0.1)); + let desktop = NativeDesktop::new(os); + let request = NativeThemeRequest::new(&desktop, false, TargetDensity::Comfortable); + assert_eq!( + resolve_native_theme(Some(&factory), &request), + presets::intui::dark().with_id("app.native") + ); + assert_eq!( + *seen.borrow(), + vec![(desktop.clone(), false, TargetDensity::Comfortable)] + ); + + // The window manager stamps its id over the factory's, and gives the + // theme the density it asked for. + let theme = resolve_os_theme( + Some(&factory), + &OsThemeSource::native(desktop.clone(), Some(true)).at(TargetDensity::Touch), + ); + assert_eq!( + theme, + presets::intui::dark() + .with_density(TargetDensity::Touch) + .with_id("system") + ); + assert_eq!(seen.borrow().len(), 2); + // A desktop stating a light preference is light whatever the hint. + assert_eq!(seen.borrow()[1], (desktop, false, TargetDensity::Touch)); + } + + #[test] + fn a_factory_answering_none_falls_back() { + let calls = Rc::new(RefCell::new(0)); + let count = calls.clone(); + let factory: NativeThemeFactory = Box::new(move |_: &NativeThemeRequest<'_>| { + *count.borrow_mut() += 1; + None + }); + + for os in reports() { + let desktop = NativeDesktop::new(os.clone()); + for dark in [false, true] { + let request = NativeThemeRequest::new(&desktop, dark, TargetDensity::Compact); + assert_eq!( + resolve_native_theme(Some(&factory), &request), + resolve_native_theme(None, &request), + "{os:?}, dark {dark}" + ); + } + for hint in [None, Some(false), Some(true)] { + let inputs = OsThemeSource::native(desktop.clone(), hint).at(TargetDensity::Touch); + assert_eq!( + resolve_os_theme(Some(&factory), &inputs), + resolve_os_theme(None, &inputs), + "{os:?} with hint {hint:?}" + ); + } + } + assert_eq!(*calls.borrow(), reports().len() * (2 + 3)); + } + + #[test] + fn a_desktop_with_no_preference_is_asked_for_by_the_hint() { + let seen = Rc::new(RefCell::new(Vec::new())); + let log = seen.clone(); + let factory: NativeThemeFactory = Box::new(move |request: &NativeThemeRequest<'_>| { + log.borrow_mut() + .push((request.desktop.os.clone(), request.dark)); + None + }); + let mut os = OsThemeColors::new(ColorSchemePreference::NoPreference); + os.accent = Some(Color::from_rgb(0.9, 0.2, 0.1)); + let desktop = NativeDesktop::new(os.clone()); + + for hint in [Some(true), None] { + resolve_os_theme( + Some(&factory), + &OsThemeSource::native(desktop.clone(), hint).at(TargetDensity::Compact), + ); + } + let mut dark = os.clone(); + dark.color_scheme = ColorSchemePreference::Dark; + let mut light = os; + light.color_scheme = ColorSchemePreference::Light; + assert_eq!(*seen.borrow(), vec![(dark, true), (light, false)]); + } + + /// A window's tree and what its theme was last resolved from. + struct Target { + tree: WidgetTree, + applied: Option, + } + + impl Target { + fn at(density: TargetDensity) -> Self { + let mut tree = WidgetTree::new(); + tree.set_input_density(density); + Target { + tree, + applied: None, + } + } + + /// How many times the tree's theme has been set. + fn sets(&self) -> u64 { + self.tree.theme_signal().generation() + } + } + + /// Bring `targets` to `source` as the window manager does, and say how + /// many times the factory was asked. + fn apply(source: &OsThemeSource, targets: &mut [&mut Target]) -> usize { + let calls = Rc::new(RefCell::new(0)); + let count = calls.clone(); + let factory: NativeThemeFactory = Box::new(move |_: &NativeThemeRequest<'_>| { + *count.borrow_mut() += 1; + None + }); + let mut update = OsThemeUpdate::new(Some(&factory), source.clone()); + for target in targets.iter_mut() { + if let Some(theme) = update.theme_for(&mut target.applied, target.tree.input_density()) + { + target.tree.set_theme(theme); + } + } + *calls.borrow() + } + + #[test] + fn each_target_keeps_its_density() { + let mut desk = OsThemeColors::new(ColorSchemePreference::Dark); + desk.accent = Some(Color::from_rgb(0.9, 0.2, 0.1)); + let source = OsThemeSource::native(NativeDesktop::new(desk), None); + for density in DENSITIES { + let mut target = Target::at(density); + apply(&source, &mut [&mut target]); + assert_eq!(target.tree.input_density(), density); + assert_eq!( + *target.tree.theme(), + resolve_os_theme(None, &source.clone().at(TargetDensity::Compact)) + .with_density(density), + "{density:?}" + ); + } + } + + #[test] + fn an_unchanged_reading_sets_no_theme() { + let light = OsThemeSource::native( + NativeDesktop::new(OsThemeColors::new(ColorSchemePreference::Light)), + None, + ); + let mut compact = Target::at(TargetDensity::Compact); + let mut touch = Target::at(TargetDensity::Touch); + let mut touch_too = Target::at(TargetDensity::Touch); + + // The first reading sets every theme, resolving each density once. + let before = [compact.sets(), touch.sets(), touch_too.sets()]; + assert_eq!( + apply(&light, &mut [&mut compact, &mut touch, &mut touch_too]), + 2 + ); + assert_eq!( + [compact.sets(), touch.sets(), touch_too.sets()], + before.map(|sets| sets + 1) + ); + + // The same reading again sets nothing and asks the factory nothing. + let before = [compact.sets(), touch.sets(), touch_too.sets()]; + assert_eq!( + apply(&light, &mut [&mut compact, &mut touch, &mut touch_too]), + 0 + ); + assert_eq!([compact.sets(), touch.sets(), touch_too.sets()], before); + + // A changed desktop sets every theme. + let mut accented = OsThemeColors::new(ColorSchemePreference::Light); + accented.accent = Some(Color::from_rgb(0.9, 0.2, 0.1)); + let changed = OsThemeSource::native(NativeDesktop::new(accented), None); + apply(&changed, &mut [&mut compact, &mut touch]); + assert_eq!( + [compact.sets(), touch.sets()], + [before[0] + 1, before[1] + 1] + ); + + // A changed light or dark state sets every theme. + let no_preference = + NativeDesktop::new(OsThemeColors::new(ColorSchemePreference::NoPreference)); + let mut target = Target::at(TargetDensity::Compact); + apply( + &OsThemeSource::native(no_preference.clone(), Some(false)), + &mut [&mut target], + ); + let sets = target.sets(); + apply( + &OsThemeSource::native(no_preference.clone(), None), + &mut [&mut target], + ); + assert_eq!(target.sets(), sets, "no hint reads as light"); + apply( + &OsThemeSource::native(no_preference, Some(true)), + &mut [&mut target], + ); + assert_eq!(target.sets(), sets + 1); + assert!(target.tree.theme().is_dark()); + + let mut follow = Target::at(TargetDensity::Compact); + apply(&OsThemeSource::follow_system(false), &mut [&mut follow]); + let sets = follow.sets(); + apply(&OsThemeSource::follow_system(false), &mut [&mut follow]); + assert_eq!(follow.sets(), sets); + apply(&OsThemeSource::follow_system(true), &mut [&mut follow]); + assert_eq!(follow.sets(), sets + 1); + + // A changed density sets that target's theme alone, at its density. + let before = [compact.sets(), touch.sets()]; + compact.tree.set_input_density(TargetDensity::Comfortable); + let switched = compact.sets(); + assert_eq!(switched, before[0] + 1); + apply(&changed, &mut [&mut compact, &mut touch]); + assert_eq!([compact.sets(), touch.sets()], [switched + 1, before[1]]); + assert_eq!(compact.tree.input_density(), TargetDensity::Comfortable); + } +} diff --git a/crates/teksilo-app/src/window_manager.rs b/crates/teksilo-app/src/window_manager.rs index 744d2b1ed..d69b8ddd5 100644 --- a/crates/teksilo-app/src/window_manager.rs +++ b/crates/teksilo-app/src/window_manager.rs @@ -23,12 +23,14 @@ use teksilo_platform::AccessibilityPreferences; use teksilo_platform::PlatformWindow; use teksilo_platform::create_title_bar_host; use teksilo_platform::event_translation::TranslationState; -use teksilo_tokens::{ColorSchemePreference, ColorTokens}; #[allow(unused_imports)] use winit::raw_window_handle::HasWindowHandle; use winit::window::WindowLevel; use crate::app::{AppEventProxy, CloseWindowRequest, ThemeMode}; +use crate::native_theme::{ + NativeThemeFactory, OsQueries, OsThemeInputs, OsThemeSource, OsThemeUpdate, resolve_os_theme, +}; use crate::window_config::{TeksiloWindowId, WindowConfig}; @@ -220,6 +222,11 @@ pub(crate) struct ManagedWindow { /// observable on winit 0.30, so it can desync if Caps Lock was /// already on before the app gained focus. pub caps_lock_active: bool, + /// What the tree's theme was last resolved from while it followed the + /// OS ([`ThemeMode::FollowSystem`] or [`ThemeMode::Native`]); `None` + /// once any other theme was set. A re-read of the OS that gives the + /// same inputs at the tree's density sets no theme. + pub(crate) os_theme: Option, /// Last OS-IME enablement applied to the winit window (`None` = never /// set, forces the first apply). The post-dispatch reconcile compares /// the focused node's IME descriptor against this and calls @@ -454,6 +461,35 @@ pub(crate) fn wake_routes( (state, draw) } +/// What the manager keeps while it listens to the desktop's appearance: the +/// platform's [`OsThemeWatch`](teksilo_platform::os_theme::OsThemeWatch), +/// which stops when dropped. A trait so the manager's tests can watch a +/// watch start and stop without an event loop or a session bus. +pub(crate) trait DesktopWatch { + /// Whether a configuration file the theme is read from changed since the + /// last call; see + /// [`OsThemeWatch::files_changed`](teksilo_platform::os_theme::OsThemeWatch::files_changed). + fn files_changed(&mut self) -> bool; +} + +impl DesktopWatch for teksilo_platform::os_theme::OsThemeWatch { + fn files_changed(&mut self) -> bool { + teksilo_platform::os_theme::OsThemeWatch::files_changed(self) + } +} + +/// How the manager starts a [`DesktopWatch`] posting through `proxy`. +pub(crate) type DesktopWatchStart = fn(Option<&AppEventProxy>) -> Option>; + +/// The platform's watch, posting through `proxy`; `None` before the event +/// proxy is installed, and wherever +/// [`OsThemeWatch::new`](teksilo_platform::os_theme::OsThemeWatch::new) answers +/// `None`. +fn start_platform_desktop_watch(proxy: Option<&AppEventProxy>) -> Option> { + let watch = teksilo_platform::os_theme::OsThemeWatch::new(proxy?.clone())?; + Some(Box::new(watch)) +} + /// Manages multiple application windows. /// /// Each window owns its own `WidgetTree` and `PlatformWindow`. The manager @@ -490,6 +526,14 @@ pub struct WindowManager { /// How the app resolves its theme (Manual, FollowSystem, Native). theme_mode: ThemeMode, pen_batching: teksilo_platform::PenBatching, + /// The app's own [`ThemeMode::Native`] theme, asked before the default + /// (IntUI coloured by the desktop). See + /// [`TeksiloAppBuilder::native_theme`](crate::TeksiloAppBuilder::native_theme). + native_theme: Option, + /// What [`theme`](Self::theme) was last resolved from while it followed + /// the OS; `None` once any other theme was set. See + /// [`ManagedWindow::os_theme`]. + os_theme: Option, /// Whether each window's tree records what the platform adapters /// announce ([`WidgetTree::set_records_announcements`]). Off unless the /// automation bridge is installed: nothing else in a running application @@ -522,6 +566,14 @@ pub struct WindowManager { session_away: bool, /// While away, when the watch last reread the system's state. session_rechecked_at: Option, + /// Hears the desktop's appearance change while the theme follows the + /// OS; `None` under [`ThemeMode::Manual`], before + /// [`sync_os_theme_watch`](Self::sync_os_theme_watch) has run with the + /// event proxy installed, and on every platform but Linux. + os_theme_watch: Option>, + /// What starts [`os_theme_watch`](Self::os_theme_watch): + /// [`start_platform_desktop_watch`], except in tests. + start_os_theme_watch: DesktopWatchStart, } // Doc comments on **every** method here are enforced, not hoped for: a new item @@ -557,6 +609,8 @@ impl WindowManager { user_text_scale: 1.0, theme_mode: ThemeMode::Manual, pen_batching: teksilo_platform::PenBatching::default(), + native_theme: None, + os_theme: None, records_announcements: false, app_context_template: None, event_proxy: None, @@ -564,6 +618,8 @@ impl WindowManager { session: None, session_away: false, session_rechecked_at: None, + os_theme_watch: None, + start_os_theme_watch: start_platform_desktop_watch, } } @@ -592,6 +648,13 @@ impl WindowManager { self.pen_batching = mode; } + /// The app's own [`ThemeMode::Native`] theme, asked before the default + /// whenever Native mode resolves a theme. See + /// [`resolve_native_theme`](crate::resolve_native_theme). + pub(crate) fn set_native_theme(&mut self, factory: Option) { + self.native_theme = factory; + } + /// Whether the windows opened from now on record announcements. See /// [`WidgetTree::set_records_announcements`]. pub(crate) fn set_records_announcements(&mut self, record: bool) { @@ -611,8 +674,83 @@ impl WindowManager { } /// Set the theme mode (called by TeksiloAppHandler during initialization). + /// Once the event loop runs, the desktop's appearance is watched while + /// the mode follows the OS. pub fn set_theme_mode(&mut self, mode: ThemeMode) { self.theme_mode = mode; + self.sync_os_theme_watch(); + } + + /// Start the desktop's appearance watch while the theme follows the OS + /// ([`ThemeMode::Native`] or [`ThemeMode::FollowSystem`]), and stop it + /// under [`ThemeMode::Manual`]. Each change it hears posts an + /// [`OsThemeChanged`](teksilo_platform::os_theme::OsThemeChanged), which + /// the loop answers with + /// [`route_os_theme_payload`](Self::route_os_theme_payload). A no-op + /// before the event proxy is installed, and while already in step. + pub(crate) fn sync_os_theme_watch(&mut self) { + if self.theme_mode == ThemeMode::Manual { + self.os_theme_watch = None; + return; + } + if self.os_theme_watch.is_some() { + return; + } + self.os_theme_watch = (self.start_os_theme_watch)(self.event_proxy.as_ref()); + } + + /// Answer `payload` when it is an + /// [`OsThemeChanged`](teksilo_platform::os_theme::OsThemeChanged): read + /// the desktop again and give each window the theme it now resolves to + /// ([`reapply_os_theme_from`](Self::reapply_os_theme_from)), and refresh + /// the accessibility preferences, among them reduced motion from KDE's + /// animation speed. `Ok` with whether anything changed, so the caller + /// redraws; any other payload is handed back. + pub(crate) fn route_os_theme_payload( + &mut self, + payload: Box, + ) -> Result> { + self.route_os_theme_payload_from(payload, OsQueries::PLATFORM) + } + + /// [`route_os_theme_payload`](Self::route_os_theme_payload), reading the + /// desktop and its accessibility preferences through `queries`. + fn route_os_theme_payload_from( + &mut self, + payload: Box, + queries: OsQueries, + ) -> Result> { + payload.downcast::()?; + let themed = self.reapply_os_theme_from(queries); + let preferences = self.refresh_accessibility_preferences_from(queries.accessibility); + Ok(themed || preferences) + } + + /// Read the desktop again through `queries` and give each window the + /// theme it now resolves to, as [`apply_os_theme`](Self::apply_os_theme) + /// does with no winit hint; whether any theme was set. The watch's + /// file times are taken first, so the change that prompted this is not + /// found again when a window next takes focus. + fn reapply_os_theme_from(&mut self, queries: OsQueries) -> bool { + if let Some(watch) = &mut self.os_theme_watch { + watch.files_changed(); + } + self.apply_os_theme_from(None, queries) + } + + /// The backstop for a session with no bus or a change signal that never + /// came, for when a window takes focus: when a file of the `kdeglobals` + /// or `breezerc` cascade changed since the watch last looked, read the + /// desktop again and apply what it resolves to. Whether any theme was + /// set; `false` with no watch. + pub(crate) fn recheck_os_theme_files(&mut self) -> bool { + let Some(watch) = &mut self.os_theme_watch else { + return false; + }; + if !watch.files_changed() { + return false; + } + self.apply_os_theme_from(None, OsQueries::PLATFORM) } /// Install the event-loop proxy (called by TeksiloAppHandler once the @@ -714,19 +852,33 @@ impl WindowManager { /// Recompute and broadcast the theme from the **current OS appearance**, /// per the active theme mode. A no-op under `Manual`. Under `Native` it - /// adopts the OS's actual colours (GNOME/KDE/Cinnamon on Linux); under - /// `FollowSystem` it picks the built-in light/dark preset. Both results - /// carry the id `"system"`. + /// resolves the desktop's report through + /// [`resolve_native_theme`](crate::resolve_native_theme): the app's own + /// native theme, else IntUI in the OS's actual colours (GNOME/KDE/Cinnamon + /// on Linux); under `FollowSystem` it picks the built-in light/dark + /// preset. Both results carry the id `"system"`. + /// + /// Each window keeps its target density: its theme is resolved at the + /// density its tree is at, and the theme new windows are built with at + /// the density of the manager's current theme. A window whose desktop + /// report, light or dark state and density are those its theme was last + /// resolved from is not given its theme again. /// /// `os_dark_hint` is the OS light/dark state as reported by winit (e.g. - /// from a `WindowEvent::ThemeChanged`), used as the authoritative source on - /// platforms where Teksilo's own OS-colour query is unimplemented - /// (macOS / Windows, where `query_os_theme_colors()` returns - /// `NoPreference`). On Linux the query reports a real scheme and the hint - /// is unused. Pass `None` when no winit signal is available (e.g. a runtime - /// "follow system" request) — the current window's reported theme is used - /// where possible, otherwise it resolves to light. + /// from a `WindowEvent::ThemeChanged`), used where the desktop states no + /// light or dark preference itself (macOS / Windows, where + /// `query_os_theme_colors()` returns `NoPreference`). On a Linux desktop + /// that states one the hint is unused. Pass `None` when no winit signal is + /// available (e.g. a runtime "follow system" request): the current + /// window's reported theme is used where possible, otherwise it resolves + /// to light. pub fn apply_os_theme(&mut self, os_dark_hint: Option) { + self.apply_os_theme_from(os_dark_hint, OsQueries::PLATFORM); + } + + /// [`apply_os_theme`](Self::apply_os_theme), reading the desktop through + /// `queries`; whether any theme was set. + fn apply_os_theme_from(&mut self, os_dark_hint: Option, queries: OsQueries) -> bool { // Fall back to a window's currently-reported winit theme when the // caller didn't supply a hint (so picking "System" on macOS/Windows // adopts the right light/dark immediately, not just on the next toggle). @@ -737,47 +889,71 @@ impl WindowManager { .and_then(|m| m.platform_window.window().theme()) .map(|t| matches!(t, winit::window::Theme::Dark)) }); - let theme = match self.theme_mode { - ThemeMode::Manual => return, + let source = match self.theme_mode { + ThemeMode::Manual => return false, ThemeMode::FollowSystem => { - let dark = match teksilo_platform::os_theme::query_color_scheme() { - ColorSchemePreference::Dark => true, - ColorSchemePreference::Light => false, - ColorSchemePreference::NoPreference => hint.unwrap_or(false), - }; - if dark { - teksilo_core::presets::intui::dark() - } else { - teksilo_core::presets::intui::light() - } - .with_id("system") + OsThemeSource::follow_system(queries.follow_system_dark(hint)) } - ThemeMode::Native => { - let os = teksilo_platform::os_theme::query_os_theme_colors(); - match os.color_scheme { - // Real OS scheme (Linux): adopt the OS's actual colours. - ColorSchemePreference::Dark => Theme { - colors: ColorTokens::from_os_colors(&os), - ..teksilo_core::presets::intui::dark() - }, - ColorSchemePreference::Light => Theme { - colors: ColorTokens::from_os_colors(&os), - ..teksilo_core::presets::intui::light() - }, - // No OS-colour support (macOS/Windows): follow the winit - // light/dark hint using the built-in presets. - ColorSchemePreference::NoPreference => { - if hint.unwrap_or(false) { - teksilo_core::presets::intui::dark() - } else { - teksilo_core::presets::intui::light() - } - } - } - .with_id("system") + // A desktop stating a light or dark preference decides; one + // stating none (macOS, Windows) follows the winit hint. + ThemeMode::Native => OsThemeSource::native((queries.native_desktop)(), hint), + }; + self.apply_os_source(source) + } + + /// Give every window, and the manager's own theme, the theme `source` + /// resolves to at its density, except where that is the theme it was + /// last given; whether any theme was set. Does not look at the theme + /// mode: the caller has. + pub(crate) fn apply_os_source(&mut self, source: OsThemeSource) -> bool { + let mut update = OsThemeUpdate::new(self.native_theme.as_ref(), source); + let mut changed = false; + for managed in self.windows.values_mut() { + if let Some(theme) = + update.theme_for(&mut managed.os_theme, managed.tree.input_density()) + { + // As in `set_theme`: the translator holds the input tokens. + managed.translation_state.set_input_tokens(theme.input); + managed.tree.set_theme(theme); + changed = true; + } + } + if let Some(theme) = update.theme_for(&mut self.os_theme, self.theme.input.density) { + self.theme = theme; + changed = true; + } + changed + } + + /// The theme a window created now is built with, and, while the theme + /// follows the OS, what it was resolved from. `window_hint` is the new + /// window's winit light or dark state. + /// + /// Under `Manual` it is the manager's theme. Under `FollowSystem` it is + /// the IntUI light or dark preset the desktop's preference names, and + /// under `Native` the theme the desktop resolves to. Under either, a + /// desktop that states no preference follows the hint, as + /// [`apply_os_theme`](Self::apply_os_theme) does, so a window opened and + /// a change heard later resolve alike. The theme is resolved at the + /// density of the manager's theme and becomes + /// the manager's theme, which later windows start from. + fn new_window_theme( + &mut self, + window_hint: Option, + queries: OsQueries, + ) -> (Theme, Option) { + let source = match self.theme_mode { + ThemeMode::Manual => return (self.theme.clone(), None), + ThemeMode::FollowSystem => { + OsThemeSource::follow_system(queries.follow_system_dark(window_hint)) } + ThemeMode::Native => OsThemeSource::native((queries.native_desktop)(), window_hint), }; - self.set_theme(theme); + let inputs = source.at(self.theme.input.density); + let theme = resolve_os_theme(self.native_theme.as_ref(), &inputs); + self.theme = theme.clone(); + self.os_theme = Some(inputs.clone()); + (theme, Some(inputs)) } /// Install the shared typesetter every window's tree will shape text @@ -1072,34 +1248,15 @@ impl WindowManager { translation_state.set_scale_factor(scale_factor); translation_state.set_pen_batching(self.pen_batching); - // Resolve the initial theme from ThemeMode before building the tree - let initial_theme = match self.theme_mode { - ThemeMode::Manual => self.theme.clone(), - // OS-following modes carry the id "system" so a `ThemeSwitcher` - // recognizes the active theme as "follow OS", not a fixed pick. - ThemeMode::FollowSystem => match window.theme() { - Some(winit::window::Theme::Dark) => teksilo_core::presets::intui::dark(), - _ => teksilo_core::presets::intui::light(), - } - .with_id("system"), - ThemeMode::Native => { - let os = teksilo_platform::os_theme::query_os_theme_colors(); - let base = if os.color_scheme.is_dark() { - teksilo_core::presets::intui::dark() - } else { - teksilo_core::presets::intui::light() - }; - Theme { - colors: ColorTokens::from_os_colors(&os), - ..base - } - .with_id("system") - } - }; - // Update the shared theme so all subsequent windows use the same base - if self.theme_mode != ThemeMode::Manual { - self.theme = initial_theme.clone(); - } + // Resolve the initial theme from ThemeMode before building the tree. + // OS-following modes carry the id "system" so a `ThemeSwitcher` + // recognizes the active theme as "follow OS", not a fixed pick, and + // update the shared theme so all subsequent windows use the same base. + let window_hint = window + .theme() + .map(|theme| matches!(theme, winit::window::Theme::Dark)); + let (initial_theme, initial_os_theme) = + self.new_window_theme(window_hint, OsQueries::PLATFORM); // Create with AccessKit adapter (shows window after adapter is ready) let mut pw = pollster::block_on(PlatformWindow::new_with_a11y(window, target)); @@ -1373,6 +1530,7 @@ impl WindowManager { occlusion_probed_at: None, session_away: self.session_away, caps_lock_active: false, + os_theme: initial_os_theme, ime_allowed: None, ime_purpose: None, last_ime_preedit_empty: false, @@ -1903,7 +2061,9 @@ impl WindowManager { /// `Manual` (the default). pub fn set_theme(&mut self, theme: Theme) { self.theme = theme.clone(); + self.os_theme = None; for managed in self.windows.values_mut() { + managed.os_theme = None; managed.tree.set_theme(theme.clone()); // The translator holds the input tokens, not the theme, so a // theme swap has to re-install them or the touch kill switch and @@ -1933,16 +2093,33 @@ impl WindowManager { /// motion", text scale, screen reader) and, if they changed since startup / /// the last refresh, apply them to every open window's tree. /// - /// Cost note: on Linux each of these is a subprocess (`busctl`, - /// `gsettings`), and the screen-reader flag adds one more `busctl` to the - /// handful this already runs. It is on the window-focus path, not an idle - /// poll, so it costs a fraction of a focus change and nothing at rest. Lets a runtime toggle + /// Cost note: on Linux the contrast, reduced-motion and screen-reader + /// flags are read over the session bus with zbus (the desktop portal, and + /// AT-SPI's status object on `org.a11y.Bus`), on one connection the + /// process keeps, and the text scale and the GNOME fallbacks run the + /// `gsettings` tool as a subprocess. A bus that answers costs a few + /// milliseconds per read. A bus, or a service on it, that does not answer + /// costs up to the reader's 500 ms call timeout for the first read that + /// meets it, after which the reader leaves that bus or service alone for + /// a 5 s quiet period: on such a session a focus change can be held up by + /// about half a second for each silent service, once per quiet period, + /// and by nothing in between. It is on the window-focus path, not an idle + /// poll, so it costs nothing at rest. Lets a runtime toggle /// of these settings take effect without restarting the app (WCAG / EN /// 301 549 §11.7). Driven event-first — from `WindowEvent::Focused` when a /// window gains focus — so there is no idle polling wakeup. Returns `true` /// if anything changed (so the caller can request a redraw). pub fn refresh_accessibility_preferences(&mut self) -> bool { - let fresh = AccessibilityPreferences::query(); + self.refresh_accessibility_preferences_from(AccessibilityPreferences::query) + } + + /// [`refresh_accessibility_preferences`](Self::refresh_accessibility_preferences), + /// reading the preferences through `query`. + fn refresh_accessibility_preferences_from( + &mut self, + query: fn() -> AccessibilityPreferences, + ) -> bool { + let fresh = query(); if fresh == self.a11y_prefs { return false; } @@ -2341,6 +2518,15 @@ impl WindowManager { requests.push(theme); } } + self.apply_theme_requests(requests) + } + + /// Set each theme of `requests` in turn, the last winning, then, when + /// there was one, leave the OS-following modes for + /// [`ThemeMode::Manual`]; whether there was one. The half of + /// [`drain_pending_theme_requests`](Self::drain_pending_theme_requests) + /// after the requests are taken from the windows. + fn apply_theme_requests(&mut self, requests: Vec) -> bool { let had_requests = !requests.is_empty(); for theme in requests { self.set_theme(theme); @@ -2348,9 +2534,11 @@ impl WindowManager { if had_requests { // An explicit theme pick disables OS-following so a later OS // light/dark change won't override the chosen theme. (The - // internal `apply_os_theme` path calls `set_theme` directly, not - // through this drain, so it is unaffected.) + // internal `apply_os_theme` path sets its themes directly, not + // through this drain, so it is unaffected.) Nothing is left to + // listen to the desktop for. self.theme_mode = ThemeMode::Manual; + self.sync_os_theme_watch(); } had_requests } @@ -2368,14 +2556,22 @@ impl WindowManager { } } if requested { - self.theme_mode = ThemeMode::Native; - // No winit hint at request time; apply_os_theme falls back to the - // current window's reported theme. - self.apply_os_theme(None); + self.follow_system_theme(OsQueries::PLATFORM); } requested } + /// Switch the app to [`ThemeMode::Native`] and apply the theme the + /// desktop, read through `queries`, resolves to. + fn follow_system_theme(&mut self, queries: OsQueries) { + self.theme_mode = ThemeMode::Native; + // From now on the desktop's changes are heard as they happen. + self.sync_os_theme_watch(); + // No winit hint at request time; the current window's reported theme + // stands in for it. + self.apply_os_theme_from(None, queries); + } + /// Drain per-tree text-scale requests raised by handlers via /// [`EventContext::set_text_scale`](teksilo_core::widget::EventContext::set_text_scale) /// and route each through [`WindowManager::set_text_scale`] so the new @@ -3131,3 +3327,488 @@ mod announcement_recording_tests { assert!(wm.new_window_tree(theme).records_announcements()); } } + +#[cfg(test)] +mod os_theme_tests { + use super::*; + use crate::native_theme::NativeThemeRequest; + use std::cell::Cell; + use teksilo_core::presets::intui; + use teksilo_platform::os_theme::NativeDesktop; + use teksilo_tokens::ColorSchemePreference; + use teksilo_tokens::{Color, OsThemeColors, TargetDensity}; + + const DENSITIES: [TargetDensity; 2] = [TargetDensity::Compact, TargetDensity::Touch]; + + fn accented(scheme: ColorSchemePreference) -> NativeDesktop { + let mut os = OsThemeColors::new(scheme); + os.accent = Some(Color::from_rgb(0.9, 0.2, 0.1)); + os.window_bg = Some(Color::from_rgb(0.94, 0.94, 0.95)); + NativeDesktop::new(os) + } + fn light_desktop() -> NativeDesktop { + accented(ColorSchemePreference::Light) + } + fn dark_desktop() -> NativeDesktop { + accented(ColorSchemePreference::Dark) + } + fn silent_desktop() -> NativeDesktop { + NativeDesktop::new(OsThemeColors::new(ColorSchemePreference::NoPreference)) + } + fn says_light() -> ColorSchemePreference { + ColorSchemePreference::Light + } + fn says_dark() -> ColorSchemePreference { + ColorSchemePreference::Dark + } + fn says_nothing() -> ColorSchemePreference { + ColorSchemePreference::NoPreference + } + + fn queries( + native_desktop: fn() -> NativeDesktop, + color_scheme: fn() -> ColorSchemePreference, + ) -> OsQueries { + OsQueries { + native_desktop, + color_scheme, + accessibility: AccessibilityPreferences::default, + } + } + + /// Every pair of readings: each desktop with each light or dark answer. + fn all_queries() -> Vec { + let mut out = Vec::new(); + for desktop in [light_desktop, dark_desktop, silent_desktop] { + for scheme in [says_light, says_dark, says_nothing] { + out.push(queries(desktop, scheme)); + } + } + out + } + + /// A manager in `mode` whose theme is at `density`, holding the + /// accessibility preferences [`queries`] reads. + fn manager(mode: ThemeMode, density: TargetDensity) -> WindowManager { + let mut wm = WindowManager::new(intui::light().with_density(density)); + wm.a11y_prefs = AccessibilityPreferences::default(); + wm.set_theme_mode(mode); + wm + } + + /// The theme `source` resolves to at the presets' density, projected + /// onto `density`. + fn projected(source: OsThemeSource, density: TargetDensity) -> Theme { + resolve_os_theme(None, &source.at(TargetDensity::Compact)).with_density(density) + } + + #[test] + fn a_window_opened_keeps_the_density() { + for density in DENSITIES { + for hint in [None, Some(false), Some(true)] { + let mut wm = manager(ThemeMode::Native, density); + let (theme, inputs) = + wm.new_window_theme(hint, queries(dark_desktop, says_nothing)); + assert_eq!(theme.input.density, density); + assert_eq!( + theme, + projected(OsThemeSource::native(dark_desktop(), hint), density) + ); + assert_eq!(wm.theme, theme); + assert_eq!(inputs.as_ref().map(|i| i.density), Some(density)); + + let mut wm = manager(ThemeMode::FollowSystem, density); + let (theme, _) = wm.new_window_theme(hint, queries(dark_desktop, says_dark)); + assert_eq!(theme.input.density, density); + assert_eq!( + theme, + projected(OsThemeSource::follow_system(true), density) + ); + } + } + } + + #[test] + fn an_os_theme_change_keeps_the_density() { + for density in DENSITIES { + let mut wm = manager(ThemeMode::Native, density); + wm.apply_os_theme_from(Some(true), queries(light_desktop, says_light)); + assert_eq!(wm.theme.input.density, density); + assert_eq!( + wm.theme, + projected(OsThemeSource::native(light_desktop(), Some(true)), density) + ); + + let mut wm = manager(ThemeMode::FollowSystem, density); + wm.apply_os_theme_from(None, queries(light_desktop, says_dark)); + assert_eq!(wm.theme.input.density, density); + assert_eq!( + wm.theme, + projected(OsThemeSource::follow_system(true), density) + ); + } + } + + #[test] + fn following_the_system_theme_keeps_the_density() { + for density in DENSITIES { + let mut wm = manager(ThemeMode::Manual, density); + wm.follow_system_theme(queries(dark_desktop, says_light)); + assert_eq!(wm.theme_mode(), ThemeMode::Native); + assert_eq!(wm.theme.input.density, density); + assert_eq!( + wm.theme, + projected(OsThemeSource::native(dark_desktop(), None), density) + ); + assert!(wm.theme.is_dark()); + } + } + + #[test] + fn a_manual_theme_ignores_the_os() { + let theme = intui::dark().with_density(TargetDensity::Touch); + let mut wm = WindowManager::new(theme.clone()); + wm.apply_os_theme_from(None, queries(light_desktop, says_light)); + assert_eq!(wm.theme, theme); + assert_eq!( + wm.new_window_theme(Some(false), queries(light_desktop, says_light)), + (theme, None) + ); + } + + #[test] + fn a_window_opened_on_a_desktop_with_no_preference_follows_its_hint() { + for density in DENSITIES { + for (hint, dark) in [(Some(true), true), (Some(false), false), (None, false)] { + let mut wm = manager(ThemeMode::Native, density); + let (theme, _) = wm.new_window_theme(hint, queries(silent_desktop, says_nothing)); + assert_eq!(theme.is_dark(), dark, "hint {hint:?}"); + let preset = if dark { intui::dark() } else { intui::light() }; + assert_eq!(theme, preset.with_density(density).with_id("system")); + } + } + } + + #[test] + fn a_window_opened_under_follow_system_reads_the_desktops_preference() { + // On Linux winit gives a window no light or dark hint, so the + // desktop's own answer is all there is to open on. + for density in DENSITIES { + for (scheme, dark) in [ + (says_dark as fn() -> ColorSchemePreference, true), + (says_light, false), + ] { + for hint in [None, Some(false), Some(true)] { + let mut wm = manager(ThemeMode::FollowSystem, density); + let (theme, inputs) = + wm.new_window_theme(hint, queries(silent_desktop, scheme)); + assert_eq!(theme.is_dark(), dark, "hint {hint:?}"); + assert_eq!( + inputs, + Some(OsThemeSource::follow_system(dark).at(density)), + "hint {hint:?}" + ); + assert_eq!(wm.theme, theme, "later windows start from it"); + } + } + for (hint, dark) in [(None, false), (Some(false), false), (Some(true), true)] { + let mut wm = manager(ThemeMode::FollowSystem, density); + let (theme, _) = wm.new_window_theme(hint, queries(silent_desktop, says_nothing)); + assert_eq!(theme.is_dark(), dark, "no preference follows hint {hint:?}"); + } + } + } + + #[test] + fn a_second_window_keeps_the_dark_a_live_change_set() { + let mut wm = manager(ThemeMode::FollowSystem, TargetDensity::Compact); + let dark = queries(light_desktop, says_dark); + wm.new_window_theme(None, dark); + wm.apply_os_theme_from(None, dark); + let (second, _) = wm.new_window_theme(None, dark); + assert!(second.is_dark()); + assert!(wm.theme.is_dark()); + } + + #[test] + fn a_window_opened_and_a_live_change_agree() { + for mode in [ThemeMode::FollowSystem, ThemeMode::Native] { + for q in all_queries() { + for hint in [None, Some(false), Some(true)] { + for density in DENSITIES { + let mut opened = manager(mode, density); + let (theme, inputs) = opened.new_window_theme(hint, q); + let mut live = manager(mode, density); + live.apply_os_theme_from(hint, q); + assert_eq!(theme, live.theme, "{mode:?}, hint {hint:?}, {density:?}"); + assert_eq!(inputs, live.os_theme); + } + } + } + } + } + + #[test] + fn an_unchanged_reading_resolves_and_sets_nothing() { + let calls = Rc::new(Cell::new(0)); + let count = calls.clone(); + let mut wm = manager(ThemeMode::Native, TargetDensity::Touch); + wm.set_native_theme(Some(Box::new(move |_: &NativeThemeRequest<'_>| { + count.set(count.get() + 1); + None + }))); + + // A window opens, then the OS reports what it was built from. + let (opened, _) = wm.new_window_theme(None, queries(silent_desktop, says_nothing)); + assert_eq!(calls.get(), 1); + wm.apply_os_theme_from(None, queries(silent_desktop, says_nothing)); + wm.apply_os_theme_from(Some(false), queries(silent_desktop, says_nothing)); + assert_eq!(calls.get(), 1, "the same desktop, state and density"); + assert_eq!(wm.theme, opened); + + // A changed light or dark state. + wm.apply_os_theme_from(Some(true), queries(silent_desktop, says_nothing)); + assert_eq!(calls.get(), 2); + assert!(wm.theme.is_dark()); + wm.apply_os_theme_from(Some(true), queries(silent_desktop, says_nothing)); + assert_eq!(calls.get(), 2); + + // A changed desktop. + wm.apply_os_theme_from(Some(true), queries(dark_desktop, says_nothing)); + assert_eq!(calls.get(), 3); + wm.apply_os_theme_from(None, queries(dark_desktop, says_nothing)); + assert_eq!(calls.get(), 3, "a stated preference ignores the hint"); + + // A changed density: a theme the app set at another density. + wm.set_theme(intui::light().with_density(TargetDensity::Comfortable)); + wm.apply_os_theme_from(None, queries(dark_desktop, says_nothing)); + assert_eq!(calls.get(), 4); + assert_eq!(wm.theme.input.density, TargetDensity::Comfortable); + + // Any theme set in between is replaced, even at the same density. + wm.set_theme(intui::light().with_density(TargetDensity::Comfortable)); + wm.apply_os_theme_from(None, queries(dark_desktop, says_nothing)); + assert_eq!(calls.get(), 5); + assert!(wm.theme.is_dark()); + } + /// The light desktop with another accent. + fn reaccented_desktop() -> NativeDesktop { + let mut desktop = light_desktop(); + desktop.os.accent = Some(Color::from_rgb(0.1, 0.5, 0.3)); + desktop + } + + /// A change the watch posts once. + fn changed() -> Box { + Box::new(teksilo_platform::os_theme::OsThemeChanged) + } + + #[test] + fn a_desktop_change_rereads_the_desktop_and_applies_what_changed() { + let mut wm = manager(ThemeMode::Native, TargetDensity::Touch); + let route = |wm: &mut WindowManager, desktop: fn() -> NativeDesktop| { + wm.route_os_theme_payload_from(changed(), queries(desktop, says_nothing)) + .expect("the watch's payload is taken") + }; + assert!(route(&mut wm, light_desktop), "the first reading sets"); + assert_eq!( + wm.theme, + projected( + OsThemeSource::native(light_desktop(), None), + TargetDensity::Touch + ) + ); + assert!( + !route(&mut wm, light_desktop), + "an unchanged desktop sets nothing" + ); + + let before = wm.theme.clone(); + assert!(route(&mut wm, reaccented_desktop), "a new accent"); + assert_eq!( + wm.theme, + projected( + OsThemeSource::native(reaccented_desktop(), None), + TargetDensity::Touch + ) + ); + assert_ne!(wm.theme.colors, before.colors); + assert!(!wm.theme.is_dark()); + + assert!(route(&mut wm, dark_desktop), "a dark scheme"); + assert!(wm.theme.is_dark()); + assert_eq!(wm.theme.input.density, TargetDensity::Touch); + } + + #[test] + fn a_desktop_change_follows_the_light_or_dark_preference() { + let mut wm = manager(ThemeMode::FollowSystem, TargetDensity::Compact); + let route = |wm: &mut WindowManager, scheme: fn() -> ColorSchemePreference| { + wm.route_os_theme_payload_from(changed(), queries(light_desktop, scheme)) + .expect("the watch's payload is taken") + }; + assert!(route(&mut wm, says_light)); + assert!(!wm.theme.is_dark()); + assert!(route(&mut wm, says_dark)); + assert!(wm.theme.is_dark()); + assert!( + !route(&mut wm, says_dark), + "the same preference sets nothing" + ); + } + + #[test] + fn a_desktop_change_leaves_a_manual_theme_alone() { + let theme = intui::dark().with_density(TargetDensity::Touch); + let mut wm = WindowManager::new(theme.clone()); + wm.a11y_prefs = AccessibilityPreferences::default(); + assert_eq!( + wm.route_os_theme_payload_from(changed(), queries(light_desktop, says_light)) + .ok(), + Some(false) + ); + assert_eq!(wm.theme, theme); + assert!(!wm.recheck_os_theme_files(), "no watch, nothing to check"); + } + + #[test] + fn any_other_payload_is_handed_back_untouched() { + let mut wm = manager(ThemeMode::Native, TargetDensity::Compact); + let theme = wm.theme.clone(); + let back = wm + .route_os_theme_payload_from(Box::new(7_u8), queries(dark_desktop, says_dark)) + .expect_err("not the watch's payload"); + assert_eq!(back.downcast_ref::(), Some(&7)); + assert_eq!(wm.theme, theme, "and the desktop was not read"); + } + + thread_local! { + /// How many [`FakeWatch`]es this test thread started, and how many + /// are alive. + static WATCHES_STARTED: Cell = const { Cell::new(0) }; + static WATCHES_ALIVE: Cell = const { Cell::new(0) }; + } + + /// A watch that hears nothing and counts itself. + struct FakeWatch; + + impl DesktopWatch for FakeWatch { + fn files_changed(&mut self) -> bool { + false + } + } + + impl Drop for FakeWatch { + fn drop(&mut self) { + WATCHES_ALIVE.with(|alive| alive.set(alive.get() - 1)); + } + } + + /// Starts a [`FakeWatch`], with or without an event proxy. + fn start_fake_watch(_proxy: Option<&AppEventProxy>) -> Option> { + WATCHES_STARTED.with(|started| started.set(started.get() + 1)); + WATCHES_ALIVE.with(|alive| alive.set(alive.get() + 1)); + Some(Box::new(FakeWatch)) + } + + /// A manager in `mode` that starts [`FakeWatch`]es. + fn watching_manager(mode: ThemeMode) -> WindowManager { + WATCHES_STARTED.with(|started| started.set(0)); + WATCHES_ALIVE.with(|alive| alive.set(0)); + let mut wm = manager(ThemeMode::Manual, TargetDensity::Compact); + wm.start_os_theme_watch = start_fake_watch; + wm.set_theme_mode(mode); + wm + } + + fn watches() -> (usize, usize) { + ( + WATCHES_STARTED.with(Cell::get), + WATCHES_ALIVE.with(Cell::get), + ) + } + + #[test] + fn the_platform_watch_waits_for_the_event_proxy() { + assert!(start_platform_desktop_watch(None).is_none()); + let mut wm = manager(ThemeMode::Native, TargetDensity::Compact); + wm.sync_os_theme_watch(); + assert!(wm.os_theme_watch.is_none(), "no proxy, no watch"); + } + + #[test] + fn the_watch_runs_while_the_theme_follows_the_os_and_stops_under_manual() { + for mode in [ThemeMode::Native, ThemeMode::FollowSystem] { + let mut wm = watching_manager(mode); + assert!(wm.os_theme_watch.is_some(), "{mode:?}"); + assert_eq!(watches(), (1, 1), "{mode:?}"); + wm.sync_os_theme_watch(); + wm.set_theme_mode(ThemeMode::Native); + wm.set_theme_mode(ThemeMode::FollowSystem); + assert_eq!(watches(), (1, 1), "{mode:?}: one watch, kept"); + wm.set_theme_mode(ThemeMode::Manual); + assert!(wm.os_theme_watch.is_none(), "{mode:?}"); + assert_eq!(watches(), (1, 0), "{mode:?}: stopped"); + } + let wm = watching_manager(ThemeMode::Manual); + assert!(wm.os_theme_watch.is_none()); + assert_eq!(watches(), (0, 0), "Manual starts none"); + } + + #[test] + fn an_explicit_theme_pick_stops_the_watch() { + let mut wm = watching_manager(ThemeMode::Native); + assert!(!wm.apply_theme_requests(Vec::new()), "no request"); + assert_eq!(wm.theme_mode(), ThemeMode::Native); + assert_eq!(watches(), (1, 1), "no request, the watch runs on"); + + assert!(wm.apply_theme_requests(vec![intui::dark()])); + assert_eq!(wm.theme_mode(), ThemeMode::Manual); + assert!(wm.os_theme_watch.is_none()); + assert_eq!(watches(), (1, 0), "a pick stops it"); + assert!(wm.theme.is_dark()); + } + + #[test] + fn following_the_system_starts_the_watch() { + let mut wm = watching_manager(ThemeMode::Manual); + wm.follow_system_theme(queries(light_desktop, says_light)); + assert!(wm.os_theme_watch.is_some()); + assert_eq!(watches(), (1, 1)); + } + + /// The defaults with reduced motion asked for, as KDE's animation speed + /// set to instant reads. + fn reduced_motion() -> AccessibilityPreferences { + AccessibilityPreferences { + reduced_motion: true, + ..AccessibilityPreferences::default() + } + } + + #[test] + fn a_desktop_change_reads_the_accessibility_preferences_again() { + let mut wm = manager(ThemeMode::Native, TargetDensity::Compact); + let q = queries(light_desktop, says_nothing); + assert!(wm.route_os_theme_payload_from(changed(), q).expect("taken")); + assert!( + !wm.route_os_theme_payload_from(changed(), q).expect("taken"), + "nothing changed" + ); + + let slowed = OsQueries { + accessibility: reduced_motion, + ..q + }; + assert!( + wm.route_os_theme_payload_from(changed(), slowed) + .expect("taken"), + "the preferences alone changed" + ); + assert!(wm.a11y_prefs.reduced_motion); + assert!( + !wm.route_os_theme_payload_from(changed(), slowed) + .expect("taken") + ); + } +} diff --git a/crates/teksilo-core/src/accessibility/target_audit.rs b/crates/teksilo-core/src/accessibility/target_audit.rs index 4e26ab113..f0eedd392 100644 --- a/crates/teksilo-core/src/accessibility/target_audit.rs +++ b/crates/teksilo-core/src/accessibility/target_audit.rs @@ -367,7 +367,7 @@ pub struct TargetViolation { /// The density ladder this violation was found at. pub density: TargetDensity, /// The theme the tree was built with. Carried because a failure list over - /// four presets is unreadable without it, and because an allow-list pin + /// five presets is unreadable without it, and because an allow-list pin /// discriminates on it. pub theme: crate::styles::ThemeId, /// The SC 2.5.8 conformance floor this violation was judged against, in dp — @@ -643,7 +643,7 @@ pub fn measure_fixtures( /// not a one-line `if projection.is_some() { return empty }`. A projection /// rebuilds the slots *it* owns and leaves the app's alone — that is the /// contract `DensityProjection`'s own docs state — so an app style installed on -/// top of macOS, Fluent or Material 3 is exactly as frozen as one installed on +/// top of macOS, Fluent, Material 3 or KDE is exactly as frozen as one installed on /// top of Int UI, and refusing to look at a projected theme reported nothing in /// the common case. (An app slot in a field the preset *also* owns is a /// different story: the projection overwrites it at the next density change, so diff --git a/crates/teksilo-core/src/accessibility/target_audit/gate.rs b/crates/teksilo-core/src/accessibility/target_audit/gate.rs index d1523c650..5bf2b08e5 100644 --- a/crates/teksilo-core/src/accessibility/target_audit/gate.rs +++ b/crates/teksilo-core/src/accessibility/target_audit/gate.rs @@ -64,7 +64,7 @@ pub const ALL_DENSITIES: &[TargetDensity] = &[ /// `M3` is on the list because it is the prefix Material 3 actually gives its /// private widgets (`M3SwitchBody`); no `Widget` type in that crate starts with /// `Material3`, which stays here only to catch a future one written longhand. -const THEME_OWNED_PREFIXES: &[&str] = &["Fluent", "MacOs", "Material3", "M3"]; +const THEME_OWNED_PREFIXES: &[&str] = &["Fluent", "MacOs", "Material3", "M3", "Breeze"]; /// The id every theme built from raw tokens carries, so it names no preset and /// is never an admissible pin. diff --git a/crates/teksilo-core/src/accessibility/target_audit/gate/tests.rs b/crates/teksilo-core/src/accessibility/target_audit/gate/tests.rs index ee1b9ccdc..b56cf9dab 100644 --- a/crates/teksilo-core/src/accessibility/target_audit/gate/tests.rs +++ b/crates/teksilo-core/src/accessibility/target_audit/gate/tests.rs @@ -381,7 +381,7 @@ fn roster_with(allow: &'static [AllowedViolation]) -> Roster { /// The product is swept: every theme in the roster, at every density. /// /// A census taken under one theme, or at one rung, is the shape in which a -/// four-preset gate reports three presets' worth of nothing — and every question +/// five-preset gate reports four presets' worth of nothing, and every question /// below is asked *of the census*, so a sweep that lost an axis takes all four /// with it and every one of them still reads green. #[test] @@ -786,7 +786,7 @@ fn the_roster_lints() { // fixture-name-prefixed FIRST segment, which the lint has to strip the name // off before it can see, and one in an ordinary later segment. static THEME_OWNED_PATH: &[AllowedViolation] = &[AllowedViolation { - path: "row: FluentRowFrame > MacOsRowFrame > Grip", + path: "row: FluentRowFrame > MacOsRowFrame > BreezeRowFrame > Grip", measured: &[SIXTEEN], owner: Owner::Named("a fixture"), exception: None, @@ -932,7 +932,8 @@ fn the_roster_lints() { let both = roster_defects(&[], &roster_with(THEME_OWNED_PATH)); assert!( both.iter().any(|f| f.contains("`FluentRowFrame`")) - && both.iter().any(|f| f.contains("`MacOsRowFrame`")), + && both.iter().any(|f| f.contains("`MacOsRowFrame`")) + && both.iter().any(|f| f.contains("`BreezeRowFrame`")), "the lint must name the type behind the fixture name as well as the \ plain one; it reported {both:#?}", ); diff --git a/crates/teksilo-core/src/build_context.rs b/crates/teksilo-core/src/build_context.rs index b006b56db..56ad1a0cb 100644 --- a/crates/teksilo-core/src/build_context.rs +++ b/crates/teksilo-core/src/build_context.rs @@ -535,6 +535,31 @@ impl<'a> BuildContext<'a> { self.tree.window_active_signal() } + /// Whether the OS "increase contrast" preference is set. One-shot read for + /// build-time use; for a value that follows the preference, bind + /// [`high_contrast_signal`](Self::high_contrast_signal). + pub fn prefers_high_contrast(&self) -> bool { + self.tree.prefers_high_contrast() + } + + /// Reactive handle on the high-contrast preference, the companion of + /// [`window_active_signal`](Self::window_active_signal). A recipe that + /// bakes a colour at build time zips both into a + /// [`PaintState`](crate::styles::PaintState) and resolves against + /// [`Theme::for_paint_state`](crate::styles::Theme::for_paint_state), so + /// it paints what the paint walker paints. Starts `false`. + pub fn high_contrast_signal(&self) -> crate::signal::Signal { + self.tree.high_contrast_signal() + } + + /// The [`PaintState`](crate::styles::PaintState) the host window paints + /// under right now. One-shot read; see + /// [`high_contrast_signal`](Self::high_contrast_signal) for the reactive + /// form. + pub fn paint_state(&self) -> crate::styles::PaintState { + self.tree.paint_state() + } + /// Reactive handle on the current locale. Fires observers when /// `tree.set_locale(...)` is called. pub fn locale_signal(&self) -> crate::signal::Signal> { diff --git a/crates/teksilo-core/src/presets.rs b/crates/teksilo-core/src/presets.rs index e2d529a39..b74a1c9fb 100644 --- a/crates/teksilo-core/src/presets.rs +++ b/crates/teksilo-core/src/presets.rs @@ -12,7 +12,7 @@ //! ``` //! //! IntUI ships in core. Sibling preset crates (`teksilo-theme-material3`, -//! `teksilo-theme-macos`, `teksilo-theme-fluent`) live behind opt-in Cargo -//! features in the `teksilo` umbrella crate. +//! `teksilo-theme-macos`, `teksilo-theme-fluent`, `teksilo-theme-kde`) live +//! behind opt-in Cargo features in the `teksilo` umbrella crate. pub mod intui; diff --git a/crates/teksilo-core/src/presets/intui.rs b/crates/teksilo-core/src/presets/intui.rs index 102bd77cf..41d094e0b 100644 --- a/crates/teksilo-core/src/presets/intui.rs +++ b/crates/teksilo-core/src/presets/intui.rs @@ -16,7 +16,8 @@ //! `style_slots: ComponentStyleSlots`. use teksilo_tokens::{ - ColorTokens, InputTokens, LayoutTokens, MotionTokens, ShapeTokens, TypographyTokens, + ColorTokens, InputTokens, LayoutTokens, MotionTokens, OsThemeColors, ShapeTokens, + TypographyTokens, }; use crate::styles::{ComponentStyleSlots, Theme, ThemeAppearance, ThemeExtensions, ThemeId}; @@ -54,6 +55,27 @@ pub fn dark() -> Theme { } } +/// IntUI coloured by the desktop: the [`light`] or [`dark`] theme, chosen by +/// `dark`, with its colours replaced by +/// [`ColorTokens::from_os_colors`] over `os`. +/// +/// This is the theme `ThemeMode::Native` falls back to when the app gives it +/// no theme of its own. Everything but the colours (geometry, typography, +/// motion, input, style slots, extensions, id) is the chosen preset's, so the +/// id stays `intui.light` or `intui.dark` until the caller sets another. +/// +/// `dark` picks only the preset under the colours. The colours follow +/// `os.color_scheme`: [`ColorTokens::from_os_colors`] starts from the light +/// or dark defaults that preference names (light when it states none), so a +/// caller normally passes `dark` as `os.color_scheme.is_dark()`. +pub fn from_os(os: &OsThemeColors, dark: bool) -> Theme { + let base = if dark { self::dark() } else { light() }; + Theme { + colors: ColorTokens::from_os_colors(os), + ..base + } +} + #[cfg(test)] mod tests { use super::*; @@ -84,4 +106,25 @@ mod tests { let d = dark(); assert!(d.shape.shadow_lg.color.a() > l.shape.shadow_lg.color.a()); } + + #[test] + fn from_os_keeps_the_preset_and_takes_the_report_colours() { + use teksilo_tokens::{Color, ColorSchemePreference}; + + let mut os = OsThemeColors::new(ColorSchemePreference::Dark); + os.window_bg = Some(Color::from_rgb(0.1, 0.2, 0.3)); + let theme = from_os(&os, true); + assert_eq!(theme.colors, ColorTokens::from_os_colors(&os)); + assert_eq!( + Theme { + colors: dark().colors, + ..theme + }, + dark() + ); + + // A report with no colours is the preset itself. + let plain = OsThemeColors::new(ColorSchemePreference::Light); + assert_eq!(from_os(&plain, false), light()); + } } diff --git a/crates/teksilo-core/src/styles.rs b/crates/teksilo-core/src/styles.rs index be65f1057..0451baadf 100644 --- a/crates/teksilo-core/src/styles.rs +++ b/crates/teksilo-core/src/styles.rs @@ -39,6 +39,8 @@ mod dialog_style; mod drop_target_style; #[path = "styles/drop_zone_style.rs"] mod drop_zone_style; +#[path = "styles/focus_ring.rs"] +mod focus_ring; #[path = "styles/grid_view_style.rs"] mod grid_view_style; #[path = "styles/icon_button_style.rs"] @@ -139,6 +141,7 @@ pub use drop_target_style::{ pub use drop_zone_style::{ DropZoneStyle, DropZoneStyleConfig, DropZoneVisualState, SharedDropZoneStyle, }; +pub use focus_ring::FocusRingStroke; pub use grid_view_style::{ GridFocusRingRecipe, GridInsertionRecipe, GridMarqueeRecipe, GridViewStyle, SharedGridViewStyle, }; @@ -186,7 +189,7 @@ pub use spin_box_style::{ButtonLayout, SharedSpinBoxStyle, SpinBoxStyle, SpinBox pub use split_button_style::{SharedSplitButtonStyle, SplitButtonStyle, SplitButtonStyleConfig}; pub use splitter_style::{SharedSplitterStyle, SplitterStyle, SplitterStyleConfig}; pub use standard_item_style::{ - SharedStandardItemStyle, StandardItemStyle, StandardItemStyleConfig, + SelectionEmphasis, SharedStandardItemStyle, StandardItemStyle, StandardItemStyleConfig, }; pub use tab_style::{ SharedTabStyle, TabBarChromeConfig, TabBarOrientation, TabIndicatorPosition, TabStyle, @@ -203,7 +206,7 @@ pub use text_input_style::{ pub use text_selection_style::{ SharedTextSelectionStyle, TextMagnifierRecipe, TextSelectionHandleRecipe, TextSelectionStyle, }; -pub use theme::{DensityProjection, Theme, ThemeId}; +pub use theme::{DensityProjection, PaintProjection, PaintState, Theme, ThemeId}; pub use theme_appearance::ThemeAppearance; pub use theme_extension::ThemeExtensions; pub use toast_style::{SharedToastStyle, ToastPriority, ToastStyle, ToastStyleConfig}; diff --git a/crates/teksilo-core/src/styles/component_style_slots.rs b/crates/teksilo-core/src/styles/component_style_slots.rs index 17050f79c..d73eab1a0 100644 --- a/crates/teksilo-core/src/styles/component_style_slots.rs +++ b/crates/teksilo-core/src/styles/component_style_slots.rs @@ -94,12 +94,13 @@ pub struct ComponentStyleSlots { /// Every slot's name, written **once**, expanded through by each probe below. /// -/// Two hand-maintained copies of a forty-two-name list is how one of them loses -/// a slot, which for [`ComponentStyleSlots::installed`] and -/// [`ComponentStyleSlots::unchanged_against`] means an override that is silently -/// never reported. The `Self { .. }`-less destructure inside each probe still -/// makes a slot added to the struct a compile error; this makes a slot added -/// *here* reach both askers at once. +/// [`ComponentStyleSlots::NAMES`], [`ComponentStyleSlots::installed`], +/// [`ComponentStyleSlots::unchanged_against`] and the `Debug` and `PartialEq` +/// impls all expand this one roster, so none of them can be the copy that fell +/// behind: a hand-written list per asker is how one of them loses a slot, which +/// for `installed` means an override that is silently never reported. Every +/// probe except `NAMES` also destructures `Self` without a `..` rest pattern, so +/// a slot added to the struct and not to this roster is a compile error. macro_rules! for_every_slot { ($probe:ident) => { $probe!( @@ -150,6 +151,22 @@ macro_rules! for_every_slot { } impl ComponentStyleSlots { + /// Every slot's name, in declaration order: the roster that + /// [`installed`](Self::installed) and + /// [`unchanged_against`](Self::unchanged_against) answer from. + /// + /// For a test that wants to say which slots a preset fills and which it + /// deliberately leaves to the widget's default, as set equality against + /// this list rather than against a hand-copied one. + pub const NAMES: &'static [&'static str] = { + macro_rules! probe { + ($($name:ident),* $(,)?) => { + &[$(stringify!($name)),*] + }; + } + for_every_slot!(probe) + }; + /// The names of the slots that carry an override, in declaration order. /// /// The direct "what is installed here" question, kept for diagnostics and @@ -226,52 +243,18 @@ impl ComponentStyleSlots { impl std::fmt::Debug for ComponentStyleSlots { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // Hand-rolled because `Rc` doesn't impl Debug. - // Show which slots are populated (Some/None) — the actual + // Show which slots are populated (Some/None): the actual // chrome behaviour isn't introspectable. - f.debug_struct("ComponentStyleSlots") - .field("button", &self.button.is_some()) - .field("split_button", &self.split_button.is_some()) - .field("splitter", &self.splitter.is_some()) - .field("icon_button", &self.icon_button.is_some()) - .field("toggle", &self.toggle.is_some()) - .field("checkbox", &self.checkbox.is_some()) - .field("radio", &self.radio.is_some()) - .field("radio_tile", &self.radio_tile.is_some()) - .field("slider", &self.slider.is_some()) - .field("text_input", &self.text_input.is_some()) - .field("combo_box", &self.combo_box.is_some()) - .field("menu_item", &self.menu_item.is_some()) - .field("panel", &self.panel.is_some()) - .field("card", &self.card.is_some()) - .field("chart", &self.chart.is_some()) - .field("popover", &self.popover.is_some()) - .field("tooltip", &self.tooltip.is_some()) - .field("scroll_bar", &self.scroll_bar.is_some()) - .field("standard_item", &self.standard_item.is_some()) - .field("tab", &self.tab.is_some()) - .field("dialog", &self.dialog.is_some()) - .field("snackbar", &self.snackbar.is_some()) - .field("toast", &self.toast.is_some()) - .field("banner", &self.banner.is_some()) - .field("badge", &self.badge.is_some()) - .field("progress_bar", &self.progress_bar.is_some()) - .field("link", &self.link.is_some()) - .field("segmented_control", &self.segmented_control.is_some()) - .field("avatar", &self.avatar.is_some()) - .field("calendar", &self.calendar.is_some()) - .field("color_picker", &self.color_picker.is_some()) - .field("spin_box", &self.spin_box.is_some()) - .field("date_edit", &self.date_edit.is_some()) - .field("search_field", &self.search_field.is_some()) - .field("rich_text_editor", &self.rich_text_editor.is_some()) - .field("table", &self.table.is_some()) - .field("list_container", &self.list_container.is_some()) - .field("drop_zone", &self.drop_zone.is_some()) - .field("drop_target", &self.drop_target.is_some()) - .field("grid_view", &self.grid_view.is_some()) - .field("web_view", &self.web_view.is_some()) - .field("text_selection", &self.text_selection.is_some()) - .finish() + macro_rules! probe { + ($($name:ident),* $(,)?) => {{ + // No `..`: a slot missing from the roster fails to compile. + let Self { $($name),* } = self; + f.debug_struct("ComponentStyleSlots") + $(.field(stringify!($name), &$name.is_some()))* + .finish() + }}; + } + for_every_slot!(probe) } } @@ -287,47 +270,19 @@ impl PartialEq for ComponentStyleSlots { _ => false, } } - rc_eq(&self.button, &other.button) - && rc_eq(&self.radio_tile, &other.radio_tile) - && rc_eq(&self.split_button, &other.split_button) - && rc_eq(&self.splitter, &other.splitter) - && rc_eq(&self.icon_button, &other.icon_button) - && rc_eq(&self.toggle, &other.toggle) - && rc_eq(&self.checkbox, &other.checkbox) - && rc_eq(&self.radio, &other.radio) - && rc_eq(&self.slider, &other.slider) - && rc_eq(&self.text_input, &other.text_input) - && rc_eq(&self.combo_box, &other.combo_box) - && rc_eq(&self.menu_item, &other.menu_item) - && rc_eq(&self.panel, &other.panel) - && rc_eq(&self.card, &other.card) - && rc_eq(&self.chart, &other.chart) - && rc_eq(&self.popover, &other.popover) - && rc_eq(&self.tooltip, &other.tooltip) - && rc_eq(&self.scroll_bar, &other.scroll_bar) - && rc_eq(&self.standard_item, &other.standard_item) - && rc_eq(&self.tab, &other.tab) - && rc_eq(&self.dialog, &other.dialog) - && rc_eq(&self.snackbar, &other.snackbar) - && rc_eq(&self.toast, &other.toast) - && rc_eq(&self.banner, &other.banner) - && rc_eq(&self.badge, &other.badge) - && rc_eq(&self.progress_bar, &other.progress_bar) - && rc_eq(&self.link, &other.link) - && rc_eq(&self.segmented_control, &other.segmented_control) - && rc_eq(&self.avatar, &other.avatar) - && rc_eq(&self.calendar, &other.calendar) - && rc_eq(&self.color_picker, &other.color_picker) - && rc_eq(&self.spin_box, &other.spin_box) - && rc_eq(&self.date_edit, &other.date_edit) - && rc_eq(&self.search_field, &other.search_field) - && rc_eq(&self.rich_text_editor, &other.rich_text_editor) - && rc_eq(&self.table, &other.table) - && rc_eq(&self.list_container, &other.list_container) - && rc_eq(&self.drop_zone, &other.drop_zone) - && rc_eq(&self.drop_target, &other.drop_target) - && rc_eq(&self.grid_view, &other.grid_view) - && rc_eq(&self.web_view, &other.web_view) - && rc_eq(&self.text_selection, &other.text_selection) + macro_rules! probe { + ($($name:ident),* $(,)?) => {{ + // No `..`: a slot missing from the roster fails to compile. + let Self { $($name),* } = self; + true $(&& rc_eq($name, &other.$name))* + }}; + } + for_every_slot!(probe) } } + +// `styles.rs` declares this module with `#[path]`, so a child module resolves +// from `styles/`, not from a directory named after this file. +#[cfg(test)] +#[path = "component_style_slots/tests.rs"] +mod tests; diff --git a/crates/teksilo-core/src/styles/component_style_slots/tests.rs b/crates/teksilo-core/src/styles/component_style_slots/tests.rs new file mode 100644 index 000000000..c79a2dc58 --- /dev/null +++ b/crates/teksilo-core/src/styles/component_style_slots/tests.rs @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The roster [`ComponentStyleSlots::NAMES`] is checked against a value built +//! with a struct literal, which the compiler, not `for_every_slot!`, holds to +//! every field. + +use std::collections::BTreeSet; +use std::rc::Rc; + +use super::ComponentStyleSlots; +use crate::build_context::BuildContext; +use crate::styles::*; +use crate::widget_id::WidgetId; + +/// One type standing in for every Tier-3 style. Nothing here builds a widget: +/// the slots are only counted, compared and printed. +struct Stub; + +macro_rules! stub_make_body { + ($($trait:ident => $config:ident),* $(,)?) => { + $(impl $trait for Stub { + fn make_body(&self, _: &$config, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + })* + }; +} + +stub_make_body!( + AvatarStyle => AvatarStyleConfig, + BadgeStyle => BadgeStyleConfig, + BannerStyle => BannerStyleConfig, + ButtonStyle => ButtonStyleConfig, + CardStyle => CardStyleConfig, + CheckboxStyle => CheckboxStyleConfig, + ColorPickerStyle => ColorPickerStyleConfig, + ComboBoxStyle => ComboBoxStyleConfig, + DateEditStyle => DateEditStyleConfig, + DropTargetStyle => DropTargetStyleConfig, + DropZoneStyle => DropZoneStyleConfig, + IconButtonStyle => IconButtonStyleConfig, + LinkStyle => LinkStyleConfig, + MenuItemStyle => MenuItemStyleConfig, + PanelStyle => PanelStyleConfig, + PopoverStyle => PopoverStyleConfig, + ProgressBarStyle => ProgressBarStyleConfig, + RadioStyle => RadioStyleConfig, + RadioTileStyle => RadioTileStyleConfig, + RichTextEditorStyle => RichTextEditorStyleConfig, + ScrollBarStyle => ScrollBarStyleConfig, + SearchFieldStyle => SearchFieldStyleConfig, + SegmentedControlStyle => SegmentedControlStyleConfig, + SliderStyle => SliderStyleConfig, + SnackbarStyle => SnackbarStyleConfig, + SpinBoxStyle => SpinBoxStyleConfig, + SplitButtonStyle => SplitButtonStyleConfig, + StandardItemStyle => StandardItemStyleConfig, + TextInputStyle => TextInputStyleConfig, + ToastStyle => ToastStyleConfig, + ToggleStyle => ToggleStyleConfig, + TooltipStyle => TooltipStyleConfig, + WebViewStyle => WebViewStyleConfig, +); + +impl CalendarStyle for Stub { + fn make_day_cell(&self, _: &CalendarDayConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + fn make_zoom_cell(&self, _: &CalendarZoomCellConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + fn make_header(&self, _: &CalendarHeaderConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } +} + +impl ChartStyle for Stub { + fn bar_fill(&self, _: &ChartFillContext) -> FillRecipe { + unreachable!("the stub style is never built") + } + fn area_fill(&self, _: &ChartFillContext, _: f32) -> FillRecipe { + unreachable!("the stub style is never built") + } + fn donut_fill(&self, _: &ChartFillContext) -> FillRecipe { + unreachable!("the stub style is never built") + } + fn gridline(&self, _: &Theme) -> BorderRecipe { + unreachable!("the stub style is never built") + } +} + +impl DialogStyle for Stub { + fn make_panel(&self, _: &DialogStyleConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + fn make_scrim(&self, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } +} + +impl GridViewStyle for Stub {} + +impl ListContainerStyle for Stub { + fn make_insertion_indicator(&self, _: &ListInsertionConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + fn insertion(&self) -> ListInsertionRecipe { + unreachable!("the stub style is never built") + } +} + +impl SplitterStyle for Stub { + fn make_handle(&self, _: &SplitterStyleConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } +} + +impl TabStyle for Stub { + fn make_body(&self, _: &TabStyleConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + fn make_bar(&self, _: &TabBarChromeConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } +} + +impl TableStyle for Stub { + fn make_header_cell(&self, _: &TableHeaderCellConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + fn make_sort_indicator(&self, _: SortDirection, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + fn make_row_background(&self, _: &TableRowConfig, _: &mut BuildContext) -> WidgetId { + unreachable!("the stub style is never built") + } + fn grid(&self) -> TableGridRecipe { + unreachable!("the stub style is never built") + } +} + +impl TextSelectionStyle for Stub { + fn handle(&self, _: &Theme) -> TextSelectionHandleRecipe { + unreachable!("the stub style is never built") + } + fn magnifier(&self, _: &Theme) -> TextMagnifierRecipe { + unreachable!("the stub style is never built") + } +} + +/// Every slot filled, each with its own allocation. A struct literal with no +/// `..Default::default()`, so a slot added to the struct and not here fails to +/// compile, independently of the roster the code under test expands. +fn fully_populated() -> ComponentStyleSlots { + ComponentStyleSlots { + button: Some(Rc::new(Stub)), + split_button: Some(Rc::new(Stub)), + splitter: Some(Rc::new(Stub)), + icon_button: Some(Rc::new(Stub)), + toggle: Some(Rc::new(Stub)), + checkbox: Some(Rc::new(Stub)), + radio: Some(Rc::new(Stub)), + radio_tile: Some(Rc::new(Stub)), + slider: Some(Rc::new(Stub)), + text_input: Some(Rc::new(Stub)), + combo_box: Some(Rc::new(Stub)), + menu_item: Some(Rc::new(Stub)), + panel: Some(Rc::new(Stub)), + card: Some(Rc::new(Stub)), + chart: Some(Rc::new(Stub)), + popover: Some(Rc::new(Stub)), + tooltip: Some(Rc::new(Stub)), + scroll_bar: Some(Rc::new(Stub)), + standard_item: Some(Rc::new(Stub)), + tab: Some(Rc::new(Stub)), + dialog: Some(Rc::new(Stub)), + snackbar: Some(Rc::new(Stub)), + toast: Some(Rc::new(Stub)), + banner: Some(Rc::new(Stub)), + badge: Some(Rc::new(Stub)), + progress_bar: Some(Rc::new(Stub)), + link: Some(Rc::new(Stub)), + segmented_control: Some(Rc::new(Stub)), + avatar: Some(Rc::new(Stub)), + calendar: Some(Rc::new(Stub)), + color_picker: Some(Rc::new(Stub)), + spin_box: Some(Rc::new(Stub)), + date_edit: Some(Rc::new(Stub)), + search_field: Some(Rc::new(Stub)), + rich_text_editor: Some(Rc::new(Stub)), + table: Some(Rc::new(Stub)), + list_container: Some(Rc::new(Stub)), + drop_zone: Some(Rc::new(Stub)), + drop_target: Some(Rc::new(Stub)), + grid_view: Some(Rc::new(Stub)), + web_view: Some(Rc::new(Stub)), + text_selection: Some(Rc::new(Stub)), + } +} + +#[test] +fn names_has_no_duplicates() { + let unique: BTreeSet<&str> = ComponentStyleSlots::NAMES.iter().copied().collect(); + assert_eq!(unique.len(), ComponentStyleSlots::NAMES.len()); +} + +#[test] +fn names_is_what_a_fully_populated_value_reports_installed() { + assert_eq!(fully_populated().installed(), ComponentStyleSlots::NAMES); + assert!(ComponentStyleSlots::default().installed().is_empty()); +} + +#[test] +fn debug_shows_every_slot_and_whether_it_is_filled() { + let full = format!("{:?}", fully_populated()); + let empty = format!("{:?}", ComponentStyleSlots::default()); + for name in ComponentStyleSlots::NAMES { + assert!(full.contains(&format!("{name}: true")), "{name} in {full}"); + assert!( + empty.contains(&format!("{name}: false")), + "{name} in {empty}" + ); + } + assert!(full.starts_with("ComponentStyleSlots {")); +} + +#[test] +fn equality_is_identity_of_every_slot() { + let full = fully_populated(); + assert_eq!(full, full.clone(), "a clone shares every allocation"); + assert_eq!( + ComponentStyleSlots::default(), + ComponentStyleSlots::default() + ); + assert_ne!(full, ComponentStyleSlots::default()); + // Equal-looking but separately allocated styles are different styles. + assert_ne!(full, fully_populated()); + + // Each slot, on its own, takes part: one slot replaced by another + // allocation, or emptied, while every other slot stays shared, differs. + let mut flipped = Vec::new(); + for (name, flip) in one_slot_flips() { + let mut replaced = full.clone(); + flip(&mut replaced, Some(Rc::new(Stub))); + assert_ne!(full, replaced, "{name} replaced"); + let mut emptied = full.clone(); + flip(&mut emptied, None); + assert_ne!(full, emptied, "{name} emptied"); + flipped.push(name); + } + assert_eq!(flipped, ComponentStyleSlots::NAMES); +} + +/// A slot's name, and a function that puts `Some(Stub)` or nothing in that +/// one slot. Fields cannot be addressed by a string, so the roster writes one +/// function per field; `flipped == NAMES` above holds the list to every slot. +type Flip = fn(&mut ComponentStyleSlots, Option>); + +fn one_slot_flips() -> Vec<(&'static str, Flip)> { + macro_rules! probe { + ($($slot:ident),* $(,)?) => { + vec![$((stringify!($slot), { + fn flip(slots: &mut ComponentStyleSlots, style: Option>) { + slots.$slot = style.map(|style| style as _); + } + flip as Flip + })),*] + }; + } + for_every_slot!(probe) +} diff --git a/crates/teksilo-core/src/styles/focus_ring.rs b/crates/teksilo-core/src/styles/focus_ring.rs new file mode 100644 index 000000000..53a6b8308 --- /dev/null +++ b/crates/teksilo-core/src/styles/focus_ring.rs @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Focus-ring stroke geometry shared by every painter that reads +//! [`ShapeTokens::focus_ring_offset`]. +//! +//! A ring is a stroke centred on a rounded rectangle at a signed distance +//! from the control edge. The distance is negative when a theme draws the +//! ring partly or wholly inside the control (a negative +//! `focus_ring_offset`), so the arithmetic here floors the two quantities +//! that would otherwise invert: the rectangle's size, which stays centred +//! on the control and never drops below zero, and the corner radius, which +//! never drops below zero either. + +use teksilo_canvas::Rect; +use teksilo_tokens::ShapeTokens; + +/// The rounded rectangle a focus-ring stroke is centred on. +/// +/// Pass [`rect`](Self::rect) and [`corner_radius`](Self::corner_radius) to +/// `Canvas::stroke_rounded_rect` with the ring's width. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FocusRingStroke { + /// The stroke's centre line. Its width and height are never negative. + pub rect: Rect, + /// Corner radius of the centre line. Never negative. + pub corner_radius: f32, +} + +impl FocusRingStroke { + /// The centre line `distance` outside `control`, whose corners have + /// radius `control_radius`. A negative `distance` lies inside the + /// control. + /// + /// The rectangle grows (or, for a negative distance, shrinks) by + /// `distance` on every side and keeps the control's centre; a shrink + /// past the control's half-size stops at zero rather than inverting. + /// The radius follows the edge at the same distance, floored at zero. + pub fn at_distance(control: Rect, control_radius: f32, distance: f32) -> Self { + let width = (control.width + distance * 2.0).max(0.0); + let height = (control.height + distance * 2.0).max(0.0); + let cx = control.x + control.width * 0.5; + let cy = control.y + control.height * 0.5; + Self { + rect: Rect::new(cx - width * 0.5, cy - height * 0.5, width, height), + corner_radius: (control_radius + distance).max(0.0), + } + } + + /// The theme's ring around `control`: centred + /// [`ShapeTokens::focus_ring_stroke_distance`] outside it, so its inner + /// edge sits `focus_ring_offset` from the control edge. + pub fn around(control: Rect, control_radius: f32, shape: &ShapeTokens) -> Self { + Self::at_distance(control, control_radius, shape.focus_ring_stroke_distance()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn shape_with_offset(offset: f32) -> ShapeTokens { + ShapeTokens { + focus_ring_offset: offset, + focus_ring_width: 2.0, + ..ShapeTokens::default() + } + } + + #[test] + fn a_positive_offset_grows_the_ring_and_its_radius() { + let control = Rect::new(10.0, 10.0, 40.0, 20.0); + let ring = FocusRingStroke::around(control, 4.0, &shape_with_offset(2.0)); + assert_eq!(ring.rect, Rect::new(7.0, 7.0, 46.0, 26.0)); + assert_eq!(ring.corner_radius, 7.0); + } + + #[test] + fn a_negative_offset_straddles_the_edge_and_is_smaller_than_an_outer_ring() { + let control = Rect::new(10.0, 10.0, 40.0, 20.0); + let inner = FocusRingStroke::around(control, 4.0, &shape_with_offset(-1.0)); + let outer = FocusRingStroke::around(control, 4.0, &shape_with_offset(2.0)); + // Offset -1, width 2: centred on the control edge. + assert_eq!(inner.rect, control); + assert_eq!(inner.corner_radius, 4.0); + assert!(inner.rect.width < outer.rect.width); + assert!(inner.rect.height < outer.rect.height); + assert!(inner.corner_radius < outer.corner_radius); + } + + #[test] + fn a_deep_negative_offset_never_inverts() { + let control = Rect::new(10.0, 10.0, 6.0, 4.0); + let ring = FocusRingStroke::at_distance(control, 2.0, -10.0); + assert!(ring.rect.width >= 0.0); + assert!(ring.rect.height >= 0.0); + assert_eq!(ring.corner_radius, 0.0); + // Collapsed onto the control's centre, not thrown off it. + assert_eq!(ring.rect, Rect::new(13.0, 12.0, 0.0, 0.0)); + } +} diff --git a/crates/teksilo-core/src/styles/menu_item_style.rs b/crates/teksilo-core/src/styles/menu_item_style.rs index 0872dc05a..c14004387 100644 --- a/crates/teksilo-core/src/styles/menu_item_style.rs +++ b/crates/teksilo-core/src/styles/menu_item_style.rs @@ -19,6 +19,9 @@ pub struct MenuItemStyleConfig { /// Optional trailing slot (shortcut chip, submenu chevron). pub trailing: Option, pub is_hovered: Signal, + /// `true` while a pointer holds the row down (the framework press, from + /// the pointer-down until the release or until the press slides off), and + /// after an Enter or Space activation of a row that stays open. pub is_pressed: Signal, pub is_focused: Signal, pub is_disabled: Signal, @@ -98,6 +101,25 @@ pub trait MenuItemStyle: 'static { fn highlighted_label_role(&self) -> Option { None } + + /// The text role a menu row's label and shortcut take while the row is + /// **pressed**: held under the pointer, from the pointer-down until the + /// release, or activated from the keyboard. + /// `None` (the default) keeps whatever the row would read otherwise, + /// which for a pressed row is + /// [`highlighted_label_role`](Self::highlighted_label_role), since a + /// pressed row is still highlighted underneath the press. + /// + /// For a design language whose *pressed* row is a solid fill while its + /// hover is a pale wash: the style returns `None` from + /// `highlighted_label_role` and, say, [`TextRole::OnAccent`] here, so the + /// label keeps its contrast against the wash and flips only under the + /// press. + /// + /// Defaulted, so an existing style needs no change. + fn pressed_label_role(&self) -> Option { + None + } } pub type SharedMenuItemStyle = Rc; diff --git a/crates/teksilo-core/src/styles/progress_bar_style.rs b/crates/teksilo-core/src/styles/progress_bar_style.rs index 1395df0df..1469b8721 100644 --- a/crates/teksilo-core/src/styles/progress_bar_style.rs +++ b/crates/teksilo-core/src/styles/progress_bar_style.rs @@ -13,7 +13,7 @@ use std::rc::Rc; -use teksilo_tokens::Orientation; +use teksilo_tokens::{InputTokens, Orientation}; use crate::build_context::BuildContext; use crate::color_prop::ColorProp; @@ -45,6 +45,36 @@ pub struct ProgressBarStyleConfig { pub trait ProgressBarStyle: 'static { fn make_body(&self, cfg: &ProgressBarStyleConfig, ctx: &mut BuildContext) -> WidgetId; + + /// The bar's narrow dimension, in logical pixels, at the density `tokens` + /// describe, when the app gives it none (`ProgressBar::thickness`): the + /// height of a horizontal bar, the width of a vertical one. `None` (the + /// default) keeps the widget's own 4 px. + /// + /// A metrics accessor because the bar measures itself in + /// `layout_response`, where the chrome `make_body` built is out of reach. + /// A design language whose bar reserves room around a thinner groove (Qt + /// sizes a KDE Breeze bar for its text and centres a 6 px groove in it) + /// says so here and paints the groove inside the bounds it is given. + fn thickness(&self, _tokens: &InputTokens) -> Option { + None + } + + /// Whether this style paints an indeterminate bar's motion itself. + /// + /// `false` (the default): the bar draws its own sweep, a shader-driven + /// quad for a horizontal bar and a moving fill over + /// [`make_body`](Self::make_body)'s track for a vertical one, and the + /// style supplies only the stationary chrome. `true`: the bar mounts + /// `make_body` alone for an indeterminate bar in either orientation, with + /// [`ProgressKind::Indeterminate`], and the style draws the whole busy + /// indicator, animating it (and holding it still under the reduced-motion + /// preference, `BuildContext::prefers_reduced_motion`) itself. For a + /// design language whose busy bar is not a sweep, such as KDE Breeze's + /// moving stripes. + fn paints_busy_indicator(&self) -> bool { + false + } } pub type SharedProgressBarStyle = Rc; diff --git a/crates/teksilo-core/src/styles/scroll_bar_style.rs b/crates/teksilo-core/src/styles/scroll_bar_style.rs index 3633ca190..a1a8f1814 100644 --- a/crates/teksilo-core/src/styles/scroll_bar_style.rs +++ b/crates/teksilo-core/src/styles/scroll_bar_style.rs @@ -2,13 +2,20 @@ // SPDX-FileCopyrightText: 2026 FernTech //! Tier-3 style protocol for `ScrollBar`. See `docs/styling-system.md`. +//! +//! One defaulted metrics accessor, [`ScrollBarStyle::min_thumb_length`], says +//! how short the thumb may get. The bar sizes its thumb, and decides whether a +//! press lands on it, from that one number, so a preset's floor is the one laid +//! out. use std::rc::Rc; use serde::{Deserialize, Serialize}; +use teksilo_tokens::{InputTokens, TargetRole}; use crate::build_context::BuildContext; use crate::signal::Signal; +use crate::styles::density::dp; use crate::widget_id::WidgetId; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] @@ -53,9 +60,11 @@ pub struct ScrollBarStyleConfig { pub is_idle: Signal, pub orientation: ScrollBarOrientation, pub variant: ScrollBarVariant, - /// Minimum thumb length in logical pixels. Sourced from - /// `teksilo_widgets::styles::recipe_scroll_bar_style` constants by - /// default; apps override per-instance via `ScrollBar::min_thumb_length(...)`. + /// Minimum thumb length in logical pixels: the active style's + /// [`ScrollBarStyle::min_thumb_length`] at the tree's density, unless the + /// app named one per instance via `ScrollBar::min_thumb_length(...)`. The + /// widget resolves it before `make_body` runs and sizes its own thumb + /// geometry (hit test, drag, reported target) from the same value. pub min_thumb_length: f32, /// Optional thumb tint override (`ScrollBar::thumb_color` / /// `ScrollArea::scroll_bar_thumb_color`). `None` (the default) means the @@ -86,6 +95,30 @@ pub trait ScrollBarStyle: 'static { /// an RTL locale — the same obligation /// [`SliderStyle::make_body`](super::SliderStyle::make_body) carries. fn make_body(&self, cfg: &ScrollBarStyleConfig, ctx: &mut BuildContext) -> WidgetId; + + /// The shortest the thumb may get along the scroll axis, in logical + /// pixels, at the density `tokens` describe. A long document's thumb is + /// held at this length rather than shrinking with the viewport ratio, so it + /// stays something a pointer can land on. + /// + /// A metrics accessor because the bar, not the chrome, owns the thumb's + /// geometry: its hit test, its drag and the target it reports all need the + /// floor outside `make_body`. The widget resolves it once per build and + /// hands the answer to `make_body` as + /// [`ScrollBarStyleConfig::min_thumb_length`]. A bar's own + /// `min_thumb_length(..)` wins over it. + /// + /// **Defaulted**, so a `ScrollBarStyle` implemented outside this workspace + /// keeps compiling. The default is + /// `teksilo_widgets::styles::recipe_scroll_bar_style::SCROLLBAR_MIN_THUMB_LENGTH` + /// put through [`dp`] as a target (24 at Compact, 32 at Comfortable, 44 at + /// Touch), which is the shipped recipe's own answer at every density, + /// restated as a literal because `teksilo-core` cannot name a + /// `teksilo-widgets` constant, and pinned equal to it by + /// `the_scroll_bar_trait_default_restates_the_module_constant`. + fn min_thumb_length(&self, tokens: &InputTokens) -> f32 { + dp(24.0, TargetRole::Target, tokens) + } } pub type SharedScrollBarStyle = Rc; diff --git a/crates/teksilo-core/src/styles/standard_item_style.rs b/crates/teksilo-core/src/styles/standard_item_style.rs index 25bbd92f5..3be0932cf 100644 --- a/crates/teksilo-core/src/styles/standard_item_style.rs +++ b/crates/teksilo-core/src/styles/standard_item_style.rs @@ -6,10 +6,11 @@ use std::rc::Rc; -use teksilo_tokens::TextRole; +use teksilo_tokens::{InputTokens, TargetRole, TextRole}; use crate::build_context::BuildContext; use crate::signal::Signal; +use crate::styles::density::dp; use crate::widget_id::WidgetId; #[derive(Clone, Debug)] @@ -41,9 +42,57 @@ pub struct StandardItemStyleConfig { /// same desaturation a view-focus loss produces (macOS "unemphasized" /// selection serves both states). Populated from /// `BuildContext::window_active_signal`. + /// + /// Which of the two signals a style composes is its + /// [`StandardItemStyle::selection_emphasis`]; build the condition with + /// [`SelectionEmphasis::emphasised`] rather than by hand, so the chrome + /// and the row's label (which the row composes from the same hook) agree. pub is_window_active: Signal, } +/// When a selected row is drawn **emphasised**: the vivid `Selected` +/// surface (and [`StandardItemStyle::selected_label_role`] on its +/// foregrounds) rather than the muted `SelectedInactive` one. +/// +/// Design languages disagree on what mutes a selection. macOS, IntUI and +/// Fluent mute it whenever the view loses keyboard focus, as well as when +/// the window does. Qt picks a palette colour group per *window*: every +/// view in an active window paints its selection with the active +/// `Highlight`, whichever of them holds focus, so KDE's Breeze mutes a +/// selection only when the window goes inactive. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[non_exhaustive] +pub enum SelectionEmphasis { + /// Emphasised while the row's view holds keyboard focus **and** its + /// window is active. The default, and the rule every shipped style + /// followed before the choice existed. + #[default] + ViewFocusedAndWindowActive, + /// Emphasised while the window is active, whether or not the view holds + /// keyboard focus: Qt's per-window colour groups. + WindowActive, +} + +impl SelectionEmphasis { + /// The emphasis condition as a signal, from the view-focus signal (a + /// row's [`StandardItemStyleConfig::is_focused`]) and the window-active + /// signal ([`StandardItemStyleConfig::is_window_active`]). + /// + /// A selected row is emphasised while this reads `true`. Selection + /// itself is not part of it: the caller combines that, since the chrome + /// and the label each already hold it. + pub fn emphasised( + self, + view_focused: &Signal, + window_active: &Signal, + ) -> Signal { + match self { + Self::ViewFocusedAndWindowActive => view_focused.and(window_active), + Self::WindowActive => window_active.clone(), + } + } +} + pub trait StandardItemStyle: 'static { fn make_body(&self, cfg: &StandardItemStyleConfig, ctx: &mut BuildContext) -> WidgetId; @@ -67,6 +116,67 @@ pub trait StandardItemStyle: 'static { fn selected_label_role(&self) -> Option { None } + + /// When a selected row is drawn emphasised; see [`SelectionEmphasis`]. + /// + /// The row reads this for its foregrounds (the label, the subtitle and + /// the tree chevron take [`selected_label_role`](Self::selected_label_role) + /// only while the row is emphasised), and a style reads it for its own + /// chrome through [`SelectionEmphasis::emphasised`], so a solid + /// selection fill and the text on it stay in step. + /// + /// **Defaulted** to [`SelectionEmphasis::ViewFocusedAndWindowActive`], + /// the rule every style followed before this existed, so an existing + /// style needs no change. The shipped `RecipeStandardItemStyle` consults + /// its own value, which is always the default: a style that returns + /// another value and delegates its chrome to the recipe gets the + /// recipe's muting, and has to paint its selection itself to follow its + /// own rule. + fn selection_emphasis(&self) -> SelectionEmphasis { + SelectionEmphasis::ViewFocusedAndWindowActive + } + + /// The height floor of a row with no subtitle, in logical pixels, at the + /// density `tokens` describe. + /// + /// A metrics accessor rather than a `make_body` concern: the row measures + /// itself in `layout_response`, long after the chrome is built, and holds + /// an `Rc` that cannot reach a recipe field. + /// Without it a preset's row height is written and never rendered. + /// + /// **Defaulted**, so a `StandardItemStyle` implemented outside this + /// workspace keeps compiling and keeps the ladder it had. The default is + /// `teksilo_widgets::styles::recipe_standard_item_style::STANDARD_ITEM_MIN_HEIGHT_SINGLE_LINE` + /// put through [`dp`] as a target, restated as a literal because + /// `teksilo-core` cannot name a `teksilo-widgets` constant, and pinned + /// equal to it by + /// `the_standard_item_trait_defaults_restate_the_module_constants`. + fn min_height_single_line(&self, tokens: &InputTokens) -> f32 { + dp(28.0, TargetRole::Target, tokens) + } + + /// The height floor of a row with no subtitle that carries a leading + /// decoration, a check box or a leading slot (an icon, typically), in + /// logical pixels, at the density `tokens` describe. + /// + /// Some design languages make such a row taller than a row of text + /// alone: Qt adds 2 px for an item's icon, so a KDE Breeze row is 24 px + /// with text only and 26 px with an icon or a check box. + /// + /// **Defaulted** to [`min_height_single_line`](Self::min_height_single_line), + /// so a style that does not tell the two apart keeps one floor and an + /// existing style needs no change. + fn min_height_decorated(&self, tokens: &InputTokens) -> f32 { + self.min_height_single_line(tokens) + } + + /// The height floor of a row carrying a subtitle, in logical pixels. + /// Defaulted on the same terms as + /// [`min_height_single_line`](Self::min_height_single_line), from + /// `STANDARD_ITEM_MIN_HEIGHT_TWO_LINE`. + fn min_height_two_line(&self, tokens: &InputTokens) -> f32 { + dp(44.0, TargetRole::Target, tokens) + } } pub type SharedStandardItemStyle = Rc; diff --git a/crates/teksilo-core/src/styles/tab_style.rs b/crates/teksilo-core/src/styles/tab_style.rs index 1337ebf16..5a0febe3d 100644 --- a/crates/teksilo-core/src/styles/tab_style.rs +++ b/crates/teksilo-core/src/styles/tab_style.rs @@ -8,14 +8,22 @@ //! indicator, focus ring, …) and [`TabStyle::make_bar`] wraps the //! whole strip (backdrop fill, content-pane separator, drag-reorder //! drop indicator). A custom `impl TabStyle` provides both. +//! +//! Two defaulted metrics accessors, [`TabStyle::editor_height`] and +//! [`TabStyle::padding_horizontal`], say how tall a tab header is and how far +//! its content sits from its leading and trailing edges. The bar and its +//! headers measure themselves from them, so a preset's tab geometry is the one +//! laid out. use std::rc::Rc; use serde::{Deserialize, Serialize}; +use teksilo_tokens::{InputTokens, TargetRole}; use crate::build_context::BuildContext; use crate::color_prop::ColorProp; use crate::signal::Signal; +use crate::styles::density::{dp, spacing}; use crate::widget_id::WidgetId; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] @@ -84,6 +92,41 @@ pub trait TabStyle: 'static { fn make_body(&self, cfg: &TabStyleConfig, ctx: &mut BuildContext) -> WidgetId; /// Chrome for the whole bar strip — wraps [`TabBarChromeConfig::content`]. fn make_bar(&self, cfg: &TabBarChromeConfig, ctx: &mut BuildContext) -> WidgetId; + + /// The outer height of one tab header, in logical pixels, at the density + /// `tokens` describe: the strip height of a horizontal bar, and the pill + /// height of a vertical one. The focus-ring envelope is reserved inside it, + /// never added on top. + /// + /// A metrics accessor because the bar sizes its scroll viewport and its + /// header row from one number, and each header measures itself in + /// `layout_response`, where the chrome `make_body` built is out of reach. + /// Without it a preset's tab height is written and never rendered. A bar's + /// own `tab_bar_height(..)` wins over it. + /// + /// **Defaulted**, so a `TabStyle` implemented outside this workspace keeps + /// compiling. The default is + /// `teksilo_widgets::styles::recipe_tab_style::TAB_EDITOR_HEIGHT` put + /// through [`dp`] as a target, which is the shipped recipe's own answer at + /// every density, restated as a literal because `teksilo-core` cannot name + /// a `teksilo-widgets` constant, and pinned equal to it by + /// `the_tab_trait_defaults_restate_the_module_constants`. + fn editor_height(&self, tokens: &InputTokens) -> f32 { + dp(50.0, TargetRole::Target, tokens) + } + + /// The gap between a tab header's edges and its content (icon, label, + /// slots, close button) along the bar's main axis, in logical pixels, at + /// the density `tokens` describe. It counts twice in a header's natural + /// width. A pinned tab, which centres its icon in a fixed width, ignores + /// it. + /// + /// Defaulted on the same terms as [`editor_height`](Self::editor_height), + /// from `teksilo_widgets::styles::recipe_tab_style::TAB_PADDING_HORIZONTAL` + /// put through [`spacing`]. + fn padding_horizontal(&self, tokens: &InputTokens) -> f32 { + spacing(12.0, tokens) + } } pub type SharedTabStyle = Rc; diff --git a/crates/teksilo-core/src/styles/table_style.rs b/crates/teksilo-core/src/styles/table_style.rs index 636c8a45d..fd54fcbe6 100644 --- a/crates/teksilo-core/src/styles/table_style.rs +++ b/crates/teksilo-core/src/styles/table_style.rs @@ -19,7 +19,8 @@ //! `make_header_cell` — the shared `table_view::header::HeaderCell`, //! used by both views — and their gutter comes from //! `cell_padding_horizontal` / `cell_padding_vertical` rather than from -//! the module constants. Wiring `make_row_background` and +//! the module constants, and both views take their default row and header +//! heights from `row_height` / `header_height`. Wiring `make_row_background` and //! `make_sort_indicator` is intentionally deferred: the widgets still //! own their cell / row / grid-line chrome directly, and those //! dimensions live on `teksilo_widgets::styles::recipe_table_style` as @@ -27,11 +28,11 @@ use std::rc::Rc; -use teksilo_tokens::{BorderRole, InputTokens}; +use teksilo_tokens::{BorderRole, InputTokens, TargetRole}; use crate::build_context::BuildContext; use crate::signal::Signal; -use crate::styles::density::spacing; +use crate::styles::density::{dp, spacing}; use crate::widget_id::WidgetId; /// Sort direction for header cells. @@ -125,6 +126,36 @@ pub trait TableStyle: 'static { fn cell_padding_vertical(&self, tokens: &InputTokens) -> f32 { spacing(4.0, tokens) } + + /// The height of a body row in a uniform-height table, in logical pixels, + /// at the density `tokens` describe. + /// + /// A metrics accessor because the views lay their rows out arithmetically + /// (the virtualization offsets, the scroll step, the visible range) from + /// one number, long after the chrome is built, and hold an + /// `Rc` that cannot reach a recipe field. Without it a + /// preset's row height is written and never rendered. A view's own + /// `row_height(..)`, `row_height_fn(..)` or `auto_row_height(..)` wins over + /// it. + /// + /// **Defaulted**, so a `TableStyle` implemented outside this workspace + /// keeps compiling. The default is + /// `teksilo_widgets::styles::recipe_table_style::ROW_HEIGHT` put through + /// [`dp`] as a target, which is the shipped recipe's own answer at every + /// density, restated as a literal because `teksilo-core` cannot name a + /// `teksilo-widgets` constant, and pinned equal to it by + /// `the_table_trait_defaults_restate_the_module_constants`. + fn row_height(&self, tokens: &InputTokens) -> f32 { + dp(28.0, TargetRole::Target, tokens) + } + + /// The height of the column header row, in logical pixels, at the density + /// `tokens` describe. A view's own `header_height(..)` wins over it. + /// Defaulted on the same terms as [`row_height`](Self::row_height), from + /// `teksilo_widgets::styles::recipe_table_style::HEADER_HEIGHT`. + fn header_height(&self, tokens: &InputTokens) -> f32 { + dp(32.0, TargetRole::Target, tokens) + } } pub type SharedTableStyle = Rc; diff --git a/crates/teksilo-core/src/styles/theme.rs b/crates/teksilo-core/src/styles/theme.rs index e723d6ccf..d182c67d4 100644 --- a/crates/teksilo-core/src/styles/theme.rs +++ b/crates/teksilo-core/src/styles/theme.rs @@ -115,7 +115,7 @@ pub struct Theme { /// next build. /// /// A **preset that installs Tier-3 slots of its own** (Fluent, macOS, -/// Material 3) needs more: its slots are `Some(..)`, so they would ride across +/// Material 3, KDE) needs more: its slots are `Some(..)`, so they would ride across /// a density switch still carrying the dimensions they were built with, and a /// Fluent button would stay 32 dp tall under Touch. Such a preset registers /// this — a function taking the *current* theme, so it can recover the palette @@ -135,6 +135,53 @@ impl std::fmt::Debug for DensityProjection { } } +/// The window and accessibility state a frame is painted under: the inputs of +/// [`Theme::for_paint_state`]. +/// +/// A fresh tree paints with `window_active: true, high_contrast: false`, +/// under which [`Theme::for_paint_state`] returns the theme unchanged when no +/// [`PaintProjection`] is registered. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PaintState { + /// The host window is focused and not occluded. + pub window_active: bool, + /// The OS "increase contrast" preference is set. + pub high_contrast: bool, +} + +/// How a theme re-colours itself for a [`PaintState`]: registered in the +/// theme's [extension registry](ThemeExtensions) and consulted by +/// [`Theme::for_paint_state`]. +/// +/// Absent, which is the default and what every raw-token theme and the IntUI +/// preset get, means the generic chain: [`Theme::for_inactive_window`] when +/// the window is inactive, then [`Theme::for_high_contrast`] when the +/// high-contrast preference is set. That chain is a fixed transform of the +/// tokens (desaturate the accent family, then lift the accent, the ring, the +/// secondary text and the selection until they reach the high-contrast +/// floors), which is right for a palette that defines no inactive or +/// high-contrast colours of its own. +/// +/// A preset whose source palette **does** define them (a desktop colour +/// scheme with an inactive colour group, say) registers this to paint those +/// colours instead. The function receives the *active* theme and the whole +/// state at once, so it decides how the two flags combine; it replaces the +/// generic chain entirely, and calling [`Theme::for_inactive_window`] or +/// [`Theme::for_high_contrast`] from it is how it keeps a part of that chain. +/// Only colours should change: the paint walker swaps the result in without a +/// relayout, so geometry a hook changes is not honoured. +/// +/// It lives in the extension registry for the reason [`DensityProjection`] +/// does: it is optional, typed, non-serializable state only some themes carry. +#[derive(Clone, Copy)] +pub struct PaintProjection(pub fn(&Theme, PaintState) -> Theme); + +impl std::fmt::Debug for PaintProjection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("PaintProjection()") + } +} + impl Theme { /// Build a Theme from raw token data. Most apps go through a /// preset constructor (e.g. `teksilo_core::presets::intui::light`) @@ -169,6 +216,12 @@ impl Theme { self.with_extension(DensityProjection(project)) } + /// Register how this theme re-colours itself for an inactive window and + /// the high-contrast preference; see [`PaintProjection`]. + pub fn with_paint_projection(self, project: fn(&Theme, PaintState) -> Theme) -> Self { + self.with_extension(PaintProjection(project)) + } + /// Set this theme's [`ThemeId`] and return self for chaining. Used by /// preset constructors and apps building custom themes. pub fn with_id(mut self, id: impl Into>) -> Self { @@ -207,6 +260,39 @@ impl Theme { } } + /// The theme a frame is painted with under `state`: what the paint walker + /// swaps in, and what a recipe that bakes a colour at build time must + /// resolve against so it agrees with the paint-resolved controls. + /// + /// A theme that carries a [`PaintProjection`] gets that function's + /// answer. Otherwise this is the generic chain: + /// [`for_inactive_window`](Self::for_inactive_window) when + /// `!state.window_active`, then + /// [`for_high_contrast`](Self::for_high_contrast) when + /// `state.high_contrast` (so the desaturated accent and ring of an + /// inactive window are lifted to the high-contrast floors too), and an + /// unchanged copy when neither applies. + pub fn for_paint_state(&self, state: PaintState) -> Theme { + if let Some(PaintProjection(project)) = self.extension::().copied() { + return project(self, state); + } + Theme { + colors: generic_paint_colors(&self.colors, state), + ..self.clone() + } + } + + /// The colours of [`for_paint_state`](Self::for_paint_state), without + /// cloning the rest of the theme when no [`PaintProjection`] is + /// registered. For a colour resolved on every paint (a recipe's + /// `ColorProp::Bound`), where only the palette is read. + pub fn paint_colors(&self, state: PaintState) -> ColorTokens { + if let Some(PaintProjection(project)) = self.extension::().copied() { + return project(self, state).colors; + } + generic_paint_colors(&self.colors, state) + } + /// A copy of this theme projected onto another [`TargetDensity`]. /// /// This is a **token projection only**: it replaces [`Self::input`] with @@ -255,12 +341,219 @@ impl Theme { } } +/// The generic chain behind [`Theme::for_paint_state`] when no +/// [`PaintProjection`] is registered: the token halves of +/// [`Theme::for_inactive_window`] and [`Theme::for_high_contrast`], which +/// change only the colours. +fn generic_paint_colors(colors: &ColorTokens, state: PaintState) -> ColorTokens { + match (state.window_active, state.high_contrast) { + (true, false) => colors.clone(), + (false, false) => colors.for_inactive_window(), + (true, true) => colors.for_high_contrast(), + (false, true) => colors.for_inactive_window().for_high_contrast(), + } +} + #[cfg(test)] mod tests { use super::*; use crate::presets::intui; use teksilo_tokens::TargetDensity; + const ALL_PAINT_STATES: [PaintState; 4] = [ + PaintState { + window_active: true, + high_contrast: false, + }, + PaintState { + window_active: false, + high_contrast: false, + }, + PaintState { + window_active: true, + high_contrast: true, + }, + PaintState { + window_active: false, + high_contrast: true, + }, + ]; + + /// The paint walker's projection as it was written before + /// `for_paint_state` existed, kept verbatim as the oracle. + fn pre_hook_chain(theme: &Theme, state: PaintState) -> Theme { + let base = if state.window_active { + theme.clone() + } else { + theme.for_inactive_window() + }; + if state.high_contrast { + base.for_high_contrast() + } else { + base + } + } + + const HOOK_INACTIVE: &str = "#FF00FF"; + const HOOK_HIGH_CONTRAST: &str = "#00FF00"; + + /// A projection that marks each state with a colour no generic transform + /// produces, so a test can tell which path painted. + fn marking_projection(theme: &Theme, state: PaintState) -> Theme { + let mut out = theme.clone(); + if !state.window_active { + out.colors.accent = teksilo_tokens::Color::from_hex(HOOK_INACTIVE); + } + if state.high_contrast { + out.colors.accent = teksilo_tokens::Color::from_hex(HOOK_HIGH_CONTRAST); + } + out + } + + /// Without a hook, the default output is the old chain exactly, for every + /// state and both IntUI appearances. + #[test] + fn for_paint_state_without_a_hook_is_the_pre_hook_chain() { + for theme in [intui::light(), intui::dark()] { + assert!(theme.extension::().is_none()); + for state in ALL_PAINT_STATES { + let expected = pre_hook_chain(&theme, state); + assert_eq!(theme.for_paint_state(state), expected, "{state:?}"); + assert_eq!(theme.paint_colors(state), expected.colors, "{state:?}"); + } + // Active and not high-contrast is the identity. + assert_eq!(theme.for_paint_state(ALL_PAINT_STATES[0]), theme); + } + } + + /// A registered hook replaces the generic chain, in both entry points. + #[test] + fn a_paint_projection_replaces_the_generic_chain() { + let theme = intui::light().with_paint_projection(marking_projection); + for state in ALL_PAINT_STATES { + let expected = marking_projection(&theme, state); + assert_eq!(theme.for_paint_state(state), expected, "{state:?}"); + assert_eq!(theme.paint_colors(state), expected.colors, "{state:?}"); + } + assert_eq!( + format!("{:?}", theme.extension::().unwrap()), + "PaintProjection()" + ); + } + + /// `for_inactive_window` and `for_high_contrast` stay the documented + /// generic transforms: colours only, and a hook does not reach them. + #[test] + fn the_named_projections_keep_their_documented_behaviour() { + let plain = intui::light(); + let hooked = plain.clone().with_paint_projection(marking_projection); + for theme in [&plain, &hooked] { + let inactive = theme.for_inactive_window(); + assert_eq!(inactive.colors, theme.colors.for_inactive_window()); + assert_ne!(inactive.colors.accent, theme.colors.accent); + let high = theme.for_high_contrast(); + assert_eq!(high.colors, theme.colors.for_high_contrast()); + for projected in [inactive, high] { + assert_eq!(projected.id, theme.id); + assert_eq!(projected.appearance, theme.appearance); + assert_eq!(projected.layout, theme.layout); + assert_eq!(projected.typography, theme.typography); + assert_eq!(projected.shape, theme.shape); + assert_eq!(projected.motion, theme.motion); + assert_eq!(projected.input, theme.input); + assert_eq!( + projected.extension::().is_some(), + theme.extension::().is_some(), + ); + } + } + } + + /// A leaf painting the theme's accent, read from the paint context the + /// walker hands it. + #[derive(Debug)] + struct AccentProbe; + + impl crate::widget::Widget for AccentProbe { + fn layout_response( + &self, + proposal: teksilo_canvas::SizeProposal, + _ctx: &crate::widget::LayoutContext, + ) -> crate::widget::LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn paint( + &self, + bounds: teksilo_canvas::Rect, + canvas: &mut teksilo_canvas::Canvas, + ctx: &crate::widget::PaintContext, + ) { + canvas.fill_rounded_rect( + bounds, + teksilo_tokens::CornerRadius::ZERO, + ctx.theme.colors.accent, + ); + } + } + + fn painted_accent(tree: &mut crate::widget_tree::WidgetTree) -> [f32; 4] { + let frame = tree.render(); + assert_eq!(frame.shapes.len(), 1); + frame.shapes[0].color + } + + /// The paint walker asks the theme's hook, for window activation and for + /// the high-contrast preference. + #[test] + fn the_paint_walker_paints_the_hooks_colours() { + let theme = intui::light().with_paint_projection(marking_projection); + let active = theme.colors.accent.to_array(); + let mut tree = crate::widget_tree::WidgetTree::new().with_theme(theme); + tree.add(AccentProbe); + tree.layout(teksilo_canvas::SizeProposal::exact(40.0, 20.0)); + assert_eq!(painted_accent(&mut tree), active); + + tree.set_window_active(false); + assert_eq!( + painted_accent(&mut tree), + teksilo_tokens::Color::from_hex(HOOK_INACTIVE).to_array(), + "an inactive window paints the hook's colours, not the generic desaturation" + ); + + tree.set_window_active(true); + tree.set_accessibility_preferences(true, false, 1.0); + assert_eq!( + painted_accent(&mut tree), + teksilo_tokens::Color::from_hex(HOOK_HIGH_CONTRAST).to_array(), + ); + assert_eq!( + tree.paint_state(), + PaintState { + window_active: true, + high_contrast: true, + } + ); + } + + /// Without a hook the walker paints what it painted before the hook. + #[test] + fn the_paint_walker_without_a_hook_paints_the_generic_chain() { + let theme = intui::dark(); + let mut tree = crate::widget_tree::WidgetTree::new().with_theme(theme.clone()); + tree.add(AccentProbe); + tree.layout(teksilo_canvas::SizeProposal::exact(40.0, 20.0)); + tree.set_window_active(false); + tree.set_accessibility_preferences(true, false, 1.0); + assert_eq!( + painted_accent(&mut tree), + pre_hook_chain(&theme, ALL_PAINT_STATES[3]) + .colors + .accent + .to_array() + ); + } + /// The whole point of `#[serde(default)]` on `input`: a theme serialized /// before the field existed must still load. The fixture is built the only /// way that cannot go stale — serialize a real theme, then delete the key. diff --git a/crates/teksilo-core/src/widget_tree.rs b/crates/teksilo-core/src/widget_tree.rs index e1bf9910e..4df000408 100644 --- a/crates/teksilo-core/src/widget_tree.rs +++ b/crates/teksilo-core/src/widget_tree.rs @@ -570,7 +570,10 @@ pub struct WidgetTree { /// dormant while still holding its handlers). gesture_owners: std::collections::HashSet, /// OS-level accessibility preferences (high contrast, reduced motion, text scale). - prefers_high_contrast: bool, + /// High contrast is a signal so a colour baked at build time (a recipe's + /// `ColorProp::Bound`) can follow it the way it follows window activation; + /// mutated only by `set_accessibility_preferences`. + prefers_high_contrast: crate::signal::Signal, prefers_reduced_motion: bool, text_scale_factor: f64, /// Whether an assistive technology is reading the tree, as reported by the @@ -1048,7 +1051,7 @@ impl WidgetTree { pending_delayed_overlays: Vec::new(), active_ids_scratch: Vec::new(), gesture_owners: std::collections::HashSet::new(), - prefers_high_contrast: false, + prefers_high_contrast: crate::signal::Signal::new(false), prefers_reduced_motion: false, text_scale_factor: 1.0, screen_reader: crate::environment::ScreenReaderState::default(), @@ -3074,12 +3077,14 @@ impl WidgetTree { reduced_motion: bool, text_scale_factor: f64, ) { - let changed = self.prefers_high_contrast != high_contrast + let changed = self.prefers_high_contrast.get() != high_contrast || self.prefers_reduced_motion != reduced_motion || (self.text_scale_factor - text_scale_factor).abs() > f64::EPSILON; if changed { - self.prefers_high_contrast = high_contrast; + if self.prefers_high_contrast.get() != high_contrast { + self.prefers_high_contrast.set(high_contrast); + } self.prefers_reduced_motion = reduced_motion; self.text_scale_factor = text_scale_factor; // The OS factor feeds the effective text scale (multiplied with the @@ -3091,7 +3096,27 @@ impl WidgetTree { /// Whether the OS has requested high-contrast mode. pub fn prefers_high_contrast(&self) -> bool { - self.prefers_high_contrast + self.prefers_high_contrast.get() + } + + /// Reactive handle on the high-contrast preference. Fires when + /// [`set_accessibility_preferences`](Self::set_accessibility_preferences) + /// changes it. Bind at [`BindingLevel::RepaintOnly`]: the preference only + /// changes colours. Starts `false`. + /// + /// [`BindingLevel::RepaintOnly`]: crate::binding::BindingLevel::RepaintOnly + pub fn high_contrast_signal(&self) -> crate::signal::Signal { + self.prefers_high_contrast.clone() + } + + /// The [`PaintState`](crate::styles::PaintState) this tree paints under: + /// its window-active state and the high-contrast preference. The paint + /// walker hands it to [`Theme::for_paint_state`](crate::styles::Theme::for_paint_state). + pub fn paint_state(&self) -> crate::styles::PaintState { + crate::styles::PaintState { + window_active: self.is_window_active(), + high_contrast: self.prefers_high_contrast(), + } } /// Whether the OS has requested reduced motion. diff --git a/crates/teksilo-core/src/widget_tree/rendering_impl.rs b/crates/teksilo-core/src/widget_tree/rendering_impl.rs index 77778395a..68fad64c3 100644 --- a/crates/teksilo-core/src/widget_tree/rendering_impl.rs +++ b/crates/teksilo-core/src/widget_tree/rendering_impl.rs @@ -118,31 +118,21 @@ impl WidgetTree { let mut frame = RenderFrame::new(); // `effective_theme` carries the user/OS text-scale multiplier baked into - // its typography, so painted glyphs match the scaled layout sizes. When - // the window is inactive, paint against the accent-desaturated - // projection so every accent-coloured control (default button, Toggle, - // checked Checkbox/Radio, selected Tab/Segment, Slider fill, - // ProgressBar, focus rings) greys out — the Qt `QPalette::Inactive` - // model, resolved once theme-side instead of per widget. Colours only; - // typography is unchanged, so this is repaint-only (geometry stable). - let base_theme = if self.is_window_active() { - self.effective_theme.clone() - } else { - self.effective_theme.for_inactive_window() - }; - // When the OS "increase contrast" preference is set, project the - // (already active/inactive-adjusted) palette into its high-contrast - // variant (WCAG 1.4.6 Enhanced / EN 301 549 11.7). Colours only, so - // this stays repaint-only like the inactive-window swap; the HC accent - // / focus overrides win over the inactive desaturation while it's on. - let base_theme = if self.prefers_high_contrast { - base_theme.for_high_contrast() - } else { - base_theme - }; + // its typography, so painted glyphs match the scaled layout sizes. The + // palette is then projected for this window's paint state: an inactive + // window greys out every accent-coloured control (default button, + // Toggle, checked Checkbox/Radio, selected Tab/Segment, Slider fill, + // ProgressBar, focus rings), the Qt `QPalette::Inactive` model resolved + // once theme-side instead of per widget, and the OS "increase + // contrast" preference swaps in the high-contrast variant (WCAG 1.4.6 + // Enhanced / EN 301 549 11.7). A theme's own `PaintProjection` decides + // both when it registers one; see `Theme::for_paint_state`. Colours + // only; typography is unchanged, so this is repaint-only (geometry + // stable). + let base_theme = self.effective_theme.for_paint_state(self.paint_state()); let text_backend = self.text_backend.clone(); let a11y_prefs = A11yPaintPrefs { - high_contrast: self.prefers_high_contrast, + high_contrast: self.prefers_high_contrast(), reduced_motion: self.prefers_reduced_motion, large_text: self.text_scale_factor > 1.0, text_scale: self.effective_text_scale, diff --git a/crates/teksilo-kde-scheme/Cargo.toml b/crates/teksilo-kde-scheme/Cargo.toml new file mode 100644 index 000000000..7fa716d6b --- /dev/null +++ b/crates/teksilo-kde-scheme/Cargo.toml @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech + +[package] +name = "teksilo-kde-scheme" +edition.workspace = true +version.workspace = true +license.workspace = true +publish.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +description = "KDE Plasma colour schemes, configuration cascade and desktop settings for Teksilo, with no I/O." +documentation = "https://docs.rs/teksilo-kde-scheme" +keywords = ["kde", "plasma", "breeze", "color-scheme", "kconfig"] +categories = ["gui", "config", "parser-implementations"] +# The stock Breeze `.colors` files are LGPL-2.0-or-later test fixtures, +# vendored verbatim so the tests can compare against them. They are not part +# of the published crate, and neither are `tests/breeze_files.rs` and +# `tests/golden_accent.rs`, the two test targets that read them: left in, +# they would not compile from the package and `cargo test` there would +# fail. The same holds for the tests that read the golden vectors under +# `tools/kde_golden` (`tests/golden_accent.rs` among them) and the +# clean-room vocabulary scan, which reads the repository's source tree. +# NOTICE section 1.5 names the same two fixture readers, and a test in +# `tests/breeze_files.rs` checks both records against the test sources. +exclude = [ + "tests/fixtures/**", + "tests/breeze_files.rs", + "tests/golden_math.rs", + "tests/golden_resolve.rs", + "tests/golden_accent.rs", + "tests/clean_room_vocabulary.rs", +] + +[dependencies] +teksilo-tokens = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +# Property-based testing: `tests/prop_config.rs` checks that the parser never +# panics on arbitrary bytes. `cargo-fuzz` needs a nightly toolchain, which is +# not a guaranteed CI dependency; proptest gives the same coverage plus +# shrinking on stable (see the workspace `Cargo.toml`). +proptest = { workspace = true } +# Reads the golden vectors in `tools/kde_golden/vectors`. +serde_json = { workspace = true } diff --git a/crates/teksilo-kde-scheme/src/accent.rs b/crates/teksilo-kde-scheme/src/accent.rs new file mode 100644 index 000000000..41b6fa5af --- /dev/null +++ b/crates/teksilo-kde-scheme/src/accent.rs @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Applying an accent colour to a scheme, as Plasma's colour settings do. +//! +//! [`KdeColorScheme::with_accent`] writes, for one scheme and one accent, +//! every key Plasma writes into `kdeglobals` when the user picks an accent: +//! the seven colour groups and their `[Inactive]` subgroups, `[WM]`, +//! `[KDE] contrast` and `frameContrast`, and the two `[ColorEffects:*]` +//! groups. Colours are computed with [`crate::math`] and written as KDE +//! writes them, so each key reads back to the same 8-bit value; the keys +//! the procedure copies keep the scheme's own text. +//! +//! [`ACCENT_SWATCHES`] are the ten colours Plasma's settings page offers. + +use crate::color::KdeColor; +use crate::config::KConfig; +use crate::math::{accent_over, black_or_white_on, darken, lighten, luma, luma_ratio, oklab_tint}; +use crate::scheme::{ + ColorRole, ColorSet, ColorState, DEFAULT_CONTRAST, DEFAULT_FRAME_CONTRAST, DEFAULT_TINT_FACTOR, + KdeColorScheme, +}; + +/// The ten accent colours Plasma's colour settings page offers, in the +/// order it shows them: pink, red, orange, yellow, green, teal, blue, +/// purple, violet and gray. KDE gives them no names. The first is also the +/// page's custom colour until the user saves one, and the seventh, +/// 61,174,233, is Breeze's own accent. +pub const ACCENT_SWATCHES: [KdeColor; 10] = [ + KdeColor::rgb8(233, 58, 154), + KdeColor::rgb8(233, 61, 88), + KdeColor::rgb8(233, 100, 58), + KdeColor::rgb8(232, 203, 45), + KdeColor::rgb8(61, 212, 37), + KdeColor::rgb8(0, 211, 184), + KdeColor::rgb8(61, 174, 233), + KdeColor::rgb8(184, 117, 220), + KdeColor::rgb8(146, 110, 228), + KdeColor::rgb8(104, 107, 111), +]; + +/// The contrast against its background that keeps a link the plain +/// accent, in [`luma_ratio`]. +const READABLE_LINK_RATIO: f64 = 5.0; + +/// The roles that take the accent itself (the link made readable first). +const ACCENT_ROLES: [ColorRole; 4] = [ + ColorRole::ForegroundActive, + ColorRole::ForegroundLink, + ColorRole::DecorationFocus, + ColorRole::DecorationHover, +]; + +/// The keys of each `[ColorEffects:*]` group, in the order they are written. +const EFFECT_KEYS: [&str; 9] = [ + "Enable", + "ChangeSelectionColor", + "IntensityEffect", + "IntensityAmount", + "ColorEffect", + "ColorAmount", + "Color", + "ContrastEffect", + "ContrastAmount", +]; + +/// The accent as a link colour on `background`. +/// +/// The accent itself when its [`luma_ratio`] against `background` is at +/// least 5. Otherwise it is lightened (amount 0.5, chroma factor 1) when it +/// is lighter than the background and darkened by the same amounts when it +/// is not, once, without checking the result again. Breeze's accent +/// 61,174,233 on white becomes 45,127,170. +pub fn readable_link(accent: KdeColor, background: KdeColor) -> KdeColor { + let (accent_luma, background_luma) = (luma(accent), luma(background)); + if luma_ratio(background_luma, accent_luma) >= READABLE_LINK_RATIO { + accent + } else if accent_luma > background_luma { + lighten(accent, 0.5, 1.0) + } else { + darken(accent, 0.5, 1.0) + } +} + +impl KdeColorScheme { + /// The scheme with `accent` applied the way Plasma applies an accent + /// colour to it. + /// + /// The result is read from the configuration the application writes, + /// which is also its [`KdeColorScheme::source`]: what KDE writes into + /// `kdeglobals`, key for key and value for value, with three + /// differences. `[General] ColorSchemeHash` is not written (KDE writes + /// a digest of the scheme file's bytes); the scheme's `[General] Name` + /// and `ColorScheme` are copied, so the result keeps its identity; and, + /// as a documented Teksilo divergence, a colour the scheme does not set + /// is not tinted (KDE writes the tint of an undefined colour). Nothing + /// else of `[General]` is written, so the result has no `TintFactor`, + /// no accent title bar and no `AccentColor`. + /// + /// In outline, with tinting on when the scheme has a `[General] + /// TintFactor` key (its value, or 0.15 when it does not parse): + /// + /// - every colour group takes the accent for `ForegroundActive`, + /// `DecorationFocus` and `DecorationHover`, and for `ForegroundLink` + /// the accent made readable against the group's own `BackgroundNormal` + /// ([`readable_link`]); every other colour is tinted toward the + /// accent in OKLab when tinting is on, and copied as written + /// otherwise; + /// - the selection background (normal and alternate) becomes the accent + /// at 0.7 over the View background, with black or white text on it, + /// and the Button alternate background the accent at 0.7 over the + /// Button background; + /// - an `[Inactive]` subgroup is tinted when tinting is on and copied + /// otherwise, the Header one from the Window group; with an accent + /// title bar the Header backgrounds and text follow the accent; + /// - `[WM]` is the scheme's own or the resolved Header colours, then the + /// tinted or accented window background when tinting or the accent + /// title bar is on; + /// - `[KDE] contrast` and `frameContrast` are written with their + /// defaults (7 and 0.2) when the scheme lacks them, and each + /// `[ColorEffects:*]` key is copied, empty when the scheme lacks it + /// (an empty `Enable` reads as true, so effects the scheme left + /// unset are on). + /// + /// A fully transparent accent (all four 8-bit channels 0) is no accent: + /// the colour groups are copied, except that `[Inactive]` subgroups are + /// still tinted toward it when tinting is on. Where the procedure needs + /// a scheme colour the scheme does not set (a group's + /// `BackgroundNormal`), it uses black. + /// + /// **Which configuration is accented.** A scheme read with + /// [`KdeColorScheme::from_config`] or [`KdeColorScheme::parse`] is + /// accented from its source configuration, so copied keys keep their + /// text and a key the file lacks stays absent; fields changed after + /// reading are not seen. A built-in scheme ([`KdeColorScheme::breeze_light`] + /// and the others) has no source and is accented as the configuration + /// [`KdeColorScheme::to_kconfig_text`] writes for it. For the three + /// stock Breeze schemes that text holds the same keys with the same + /// values as the stock `.colors` file, as far as the accent + /// application reads it, so accenting the constant gives exactly what + /// accenting the file gives. + /// + /// **Applying it twice is not the same as applying it once.** The + /// procedure reads the scheme it is given: on an accented scheme the + /// Header `[Inactive]` group copies the already accented Window + /// colours, the selection link is checked against the accented + /// selection background, and a tinting scheme, whose result has no + /// tint factor, is not tinted the second time. KDE reads the scheme + /// file afresh for every accent, so to change the accent, apply the new + /// one to the original scheme. + pub fn with_accent(&self, accent: impl Into) -> KdeColorScheme { + KdeColorScheme::from_config(&self.accented_config(accent.into())) + } + + /// The configuration [`KdeColorScheme::with_accent`] writes. + pub(crate) fn accented_config(&self, accent: KdeColor) -> KConfig { + let written; + let source = match self.source() { + Some(source) => source, + None => { + written = KConfig::parse(&self.to_kconfig_text()).0; + &written + } + }; + Application::new(source, accent).run() + } +} + +/// One application of an accent to one scheme configuration. +struct Application<'a> { + /// The scheme configuration, read on its own. + source: &'a KConfig, + /// The same scheme with every default filled in. + scheme: KdeColorScheme, + accent: KdeColor, + /// Whether there is an accent: it is not fully transparent black. + present: bool, + /// The tint factor when tinting is on. + tint: Option, + /// The tint factor, 0.15 when the scheme has none. + factor: f64, + /// Whether the title bar is accent coloured. + titlebar: bool, + out: KConfig, +} + +impl<'a> Application<'a> { + fn new(source: &'a KConfig, accent: KdeColor) -> Application<'a> { + let scheme = KdeColorScheme::from_config(source); + Application { + source, + present: !accent.is_transparent_black(), + tint: scheme.tint_factor, + factor: scheme.tint_factor.unwrap_or(DEFAULT_TINT_FACTOR), + titlebar: scheme.titlebar_is_accent_colored, + scheme, + accent, + out: KConfig::default(), + } + } + + fn run(mut self) -> KConfig { + // ColorSet::ALL is the order the groups are processed in: View, + // Window, Button, Selection, Tooltip, Complementary, Header. + for set in ColorSet::ALL { + self.color_group(set); + } + self.window_manager(); + self.kde(); + self.effects(); + for key in ["Name", "ColorScheme"] { + self.copy(&["General"], &["General"], key); + } + self.out + } + + /// A colour of the scheme as written, black when the scheme lacks it + /// or it is not a colour. + fn scheme_color(&self, group: &[&str], key: &str) -> KdeColor { + self.source + .read_color(group, key) + .unwrap_or(KdeColor::BLACK) + } + + fn write(&mut self, group: &[&str], key: &str, color: KdeColor) { + self.out.set(group, key, color.to_kconfig_text()); + } + + /// Copies `key` as text from the scheme's `from` group to the output's + /// `to` group, when the scheme has it. + fn copy(&mut self, from: &[&str], to: &[&str], key: &str) { + if let Some(value) = self.source.get(from, key) { + self.out.set(to, key, value); + } + } + + /// Writes the tint of the scheme's `key` in `group`, when the scheme + /// has that colour. + fn tint_key(&mut self, group: &[&str], key: &str, factor: f64) { + if let Some(color) = self.source.read_color(group, key) { + self.write(group, key, oklab_tint(color, self.accent, factor)); + } + } + + fn color_group(&mut self, set: ColorSet) { + let name = set.group_name(); + let group: &[&str] = &[name]; + let inactive: &[&str] = &[name, "Inactive"]; + if set == ColorSet::Header && !self.source.group_exists(group) { + return; + } + + for role in ColorRole::ALL { + let key = role.key(); + if self.present && ACCENT_ROLES.contains(&role) { + let color = if role == ColorRole::ForegroundLink { + readable_link(self.accent, self.scheme_color(group, "BackgroundNormal")) + } else { + self.accent + }; + self.write(group, key, color); + } else if let (true, Some(factor)) = (self.present, self.tint) { + self.tint_key(group, key, factor); + } else { + self.copy(group, group, key); + } + } + + if self.present && set == ColorSet::Selection { + let view = self.scheme_color(&[ColorSet::View.group_name()], "BackgroundNormal"); + let fill = accent_over(self.accent, view); + let text = black_or_white_on(fill, true); + self.write(group, "BackgroundNormal", fill); + self.write(group, "BackgroundAlternate", fill); + self.write(group, "ForegroundNormal", text); + self.write(group, "ForegroundInactive", text); + } + if self.present && set == ColorSet::Button { + let button = self.scheme_color(group, "BackgroundNormal"); + self.write( + group, + "BackgroundAlternate", + accent_over(self.accent, button), + ); + } + + if self.source.group_exists(inactive) { + for role in ColorRole::ALL { + let key = role.key(); + if let Some(factor) = self.tint { + self.tint_key(inactive, key, factor); + } else if set == ColorSet::Header { + self.copy(&[ColorSet::Window.group_name()], inactive, key); + } else { + self.copy(inactive, inactive, key); + } + } + } + + if self.present && set == ColorSet::Header && self.titlebar { + let window = self.window_background(); + let accented = accent_over(self.accent, window); + let tinted = oklab_tint(window, self.accent, self.factor); + self.write(group, "BackgroundNormal", accented); + self.write(group, "ForegroundNormal", black_or_white_on(accented, true)); + self.write(inactive, "BackgroundNormal", tinted); + self.write( + inactive, + "ForegroundNormal", + black_or_white_on(tinted, false), + ); + } + } + + fn window_background(&self) -> KdeColor { + self.scheme_color(&[ColorSet::Window.group_name()], "BackgroundNormal") + } + + /// `[WM]`: the scheme's own colours, else the Header colours it + /// resolves to before the accent, then the title-bar override. + fn window_manager(&mut self) { + let group: &[&str] = &["WM"]; + let active = self + .scheme + .state_colors(ColorState::Active, ColorSet::Header); + let inactive = self + .scheme + .state_colors(ColorState::Inactive, ColorSet::Header); + for (key, default) in [ + ("activeBackground", active.background_normal), + ("activeForeground", active.foreground_normal), + ("inactiveBackground", inactive.background_normal), + ("inactiveForeground", inactive.foreground_normal), + ("activeBlend", active.background_normal), + ("inactiveBlend", inactive.background_normal), + ] { + let color = self.source.read_color(group, key).unwrap_or(default); + self.write(group, key, color); + } + + if !self.present { + return; + } + let window = self.window_background(); + if let Some(factor) = self.tint { + let tinted = oklab_tint(window, self.accent, factor); + if !self.titlebar { + self.write(group, "activeBackground", tinted); + self.write(group, "activeForeground", black_or_white_on(tinted, true)); + } + self.write(group, "inactiveBackground", tinted); + self.write( + group, + "inactiveForeground", + black_or_white_on(tinted, false), + ); + } + if self.titlebar { + let accented = accent_over(self.accent, window); + self.write(group, "activeBackground", accented); + self.write(group, "activeForeground", black_or_white_on(accented, true)); + } + } + + /// `[KDE]`: `frameContrast` as the scheme writes it (not clamped) or + /// 0.2, and `contrast` or 7, both written as numbers. + fn kde(&mut self) { + let group: &[&str] = &["KDE"]; + let frame = self + .source + .read_real(group, "frameContrast", DEFAULT_FRAME_CONTRAST); + let contrast = self + .source + .read_int(group, "contrast", i64::from(DEFAULT_CONTRAST)); + self.out.set(group, "frameContrast", frame.to_string()); + self.out.set(group, "contrast", contrast.to_string()); + } + + /// The effect groups, each key copied as text, empty when the scheme + /// lacks it. + fn effects(&mut self) { + for name in ["ColorEffects:Inactive", "ColorEffects:Disabled"] { + let group: &[&str] = &[name]; + for key in EFFECT_KEYS { + let value = self.source.get(group, key).unwrap_or_default(); + self.out.set(group, key, value); + } + } + } +} diff --git a/crates/teksilo-kde-scheme/src/color.rs b/crates/teksilo-kde-scheme/src/color.rs new file mode 100644 index 000000000..5144458ff --- /dev/null +++ b/crates/teksilo-kde-scheme/src/color.rs @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Colour values as KDE configuration files store them. +//! +//! A colour is held as four 16-bit channels, the precision KDE's own colour +//! arithmetic works in, so that a value read from a file and written back +//! is the same bytes. [`KdeColor::to_color`] converts to Teksilo's +//! [`Color`] at the crate boundary. +//! +//! The accepted text forms (all trimmed and escape-decoded by the +//! configuration parser first): +//! +//! | Text | Result | +//! |---|---| +//! | `r,g,b` or `r,g,b,a` | 8-bit channels, each a base-10 integer in 0..=255, optional sign and surrounding ASCII whitespace | +//! | `#rgb`, `#rrggbb`, `#aarrggbb` | 8-bit channels (a single digit `d` is `17 d`) | +//! | `#rrrgggbbb` | 12-bit channels | +//! | `#rrrrggggbbbb` | 16-bit channels, as written | +//! | an SVG 1.1 colour keyword, or `transparent` | that colour, ignoring ASCII case | +//! +//! Anything else (an empty value, `invalid`, a wrong component count, an +//! out-of-range component, another digit count after `#`, an unknown name) +//! is treated as absent, so the reader falls back to its default. KDE keeps +//! an empty or `invalid` value as an "invalid colour" that paints as black; +//! Teksilo treats it as absent instead, which is the one deliberate +//! difference. + +use std::fmt; + +use teksilo_tokens::Color; + +/// A colour with four 16-bit channels, red, green, blue and alpha, each in +/// `0..=65535`. +/// +/// An 8-bit channel `v` is stored as `257 v`, so every 8-bit colour is +/// represented exactly and [`KdeColor::to_rgba8`] gives it back. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct KdeColor { + /// Red, 0..=65535. + pub r: u16, + /// Green, 0..=65535. + pub g: u16, + /// Blue, 0..=65535. + pub b: u16, + /// Alpha, 0..=65535 (65535 is opaque). + pub a: u16, +} + +impl KdeColor { + /// Opaque black. + pub const BLACK: KdeColor = KdeColor::rgb8(0, 0, 0); + /// Opaque white. + pub const WHITE: KdeColor = KdeColor::rgb8(255, 255, 255); + /// Fully transparent black, the `transparent` keyword. + pub const TRANSPARENT: KdeColor = KdeColor::rgba8(0, 0, 0, 0); + + /// An opaque colour from 8-bit channels. + pub const fn rgb8(r: u8, g: u8, b: u8) -> KdeColor { + KdeColor::rgba8(r, g, b, 255) + } + + /// A colour from 8-bit channels, alpha included. + pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> KdeColor { + KdeColor { + r: widen8(r), + g: widen8(g), + b: widen8(b), + a: widen8(a), + } + } + + /// A colour from 16-bit channels. + pub const fn rgba16(r: u16, g: u16, b: u16, a: u16) -> KdeColor { + KdeColor { r, g, b, a } + } + + /// The four channels reduced to 8 bits, red, green, blue, alpha. + /// + /// Each channel `q` becomes `floor((q + 128) / 257)`, which is + /// `round(q / 257)` and never ties. + pub const fn to_rgba8(self) -> [u8; 4] { + [ + narrow16(self.r), + narrow16(self.g), + narrow16(self.b), + narrow16(self.a), + ] + } + + /// Whether every 8-bit channel, alpha included, is zero: the + /// `transparent` keyword, which KDE treats as "no colour" where a key + /// may name one. + pub const fn is_transparent_black(self) -> bool { + let [r, g, b, a] = self.to_rgba8(); + r == 0 && g == 0 && b == 0 && a == 0 + } + + /// Converts to Teksilo's [`Color`], each channel `q` becoming + /// `q / 65535`. + pub fn to_color(self) -> Color { + Color::new( + f32::from(self.r) / 65535.0, + f32::from(self.g) / 65535.0, + f32::from(self.b) / 65535.0, + f32::from(self.a) / 65535.0, + ) + } + + /// Converts from Teksilo's [`Color`], each channel `c` becoming + /// `round(65535 c)` after clamping to `0..=1`. A NaN channel becomes 0. + pub fn from_color(color: Color) -> KdeColor { + KdeColor { + r: quantise(color.r()), + g: quantise(color.g()), + b: quantise(color.b()), + a: quantise(color.a()), + } + } + + /// Parses a colour value as stored in a KDE configuration file. + /// + /// Returns `None` for every value the module documentation lists as + /// absent. + pub fn parse(text: &str) -> Option { + let text = text.trim_matches(is_ascii_space); + if text.is_empty() || text == "invalid" { + return None; + } + if let Some(digits) = text.strip_prefix('#') { + return parse_hex(digits); + } + if text.contains(',') { + return parse_components(text); + } + named_color(text) + } + + /// The text KDE writes for this colour: `r,g,b` when the 8-bit alpha + /// is 255, `r,g,b,a` otherwise, each component the 8-bit channel in + /// decimal. + pub fn to_kconfig_text(self) -> String { + let [r, g, b, a] = self.to_rgba8(); + if a == 255 { + format!("{r},{g},{b}") + } else { + format!("{r},{g},{b},{a}") + } + } +} + +impl From for Color { + fn from(color: KdeColor) -> Color { + color.to_color() + } +} + +impl From for KdeColor { + /// The same as [`KdeColor::from_color`]. + fn from(color: Color) -> KdeColor { + KdeColor::from_color(color) + } +} + +impl fmt::Display for KdeColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.to_kconfig_text()) + } +} + +const fn widen8(v: u8) -> u16 { + // 257 * 255 = 65535, so the product never overflows. + (v as u16) * 257 +} + +const fn narrow16(q: u16) -> u8 { + // (65535 + 128) / 257 = 255, so the quotient always fits. + ((q as u32 + 128) / 257) as u8 +} + +fn quantise(c: f32) -> u16 { + if c.is_nan() { + return 0; + } + (c.clamp(0.0, 1.0) * 65535.0).round() as u16 +} + +fn is_ascii_space(c: char) -> bool { + matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0b' | '\x0c') +} + +fn parse_hex(digits: &str) -> Option { + if !digits.bytes().all(|b| b.is_ascii_hexdigit()) { + return None; + } + let field = + |start: usize, len: usize| u16::from_str_radix(&digits[start..start + len], 16).ok(); + match digits.len() { + 3 => { + let r = field(0, 1)? * 17; + let g = field(1, 1)? * 17; + let b = field(2, 1)? * 17; + Some(KdeColor::rgba16(r * 257, g * 257, b * 257, u16::MAX)) + } + 6 => Some(KdeColor::rgba16( + field(0, 2)? * 257, + field(2, 2)? * 257, + field(4, 2)? * 257, + u16::MAX, + )), + 8 => Some(KdeColor::rgba16( + field(2, 2)? * 257, + field(4, 2)? * 257, + field(6, 2)? * 257, + field(0, 2)? * 257, + )), + 9 => { + let wide = |v: u16| 16 * v + v / 256; + Some(KdeColor::rgba16( + wide(field(0, 3)?), + wide(field(3, 3)?), + wide(field(6, 3)?), + u16::MAX, + )) + } + 12 => Some(KdeColor::rgba16( + field(0, 4)?, + field(4, 4)?, + field(8, 4)?, + u16::MAX, + )), + _ => None, + } +} + +fn parse_components(text: &str) -> Option { + let mut channels = [255_u8; 4]; + let mut count = 0; + for part in text.split(',') { + if count == 4 { + return None; + } + let part = part.trim_matches(is_ascii_space); + let value: i64 = part.parse().ok()?; + channels[count] = u8::try_from(value).ok()?; + count += 1; + } + if count < 3 { + return None; + } + let [r, g, b, a] = channels; + Some(KdeColor::rgba8(r, g, b, a)) +} + +fn named_color(text: &str) -> Option { + let lower = text.to_ascii_lowercase(); + if lower == "transparent" { + return Some(KdeColor::TRANSPARENT); + } + SVG_COLOR_KEYWORDS + .binary_search_by(|(name, _)| name.cmp(&lower.as_str())) + .ok() + .map(|i| { + let [r, g, b] = SVG_COLOR_KEYWORDS[i].1; + KdeColor::rgb8(r, g, b) + }) +} + +/// The SVG 1.1 colour keywords, sorted by name for binary search. +pub(crate) const SVG_COLOR_KEYWORDS: [(&str, [u8; 3]); 147] = [ + ("aliceblue", [240, 248, 255]), + ("antiquewhite", [250, 235, 215]), + ("aqua", [0, 255, 255]), + ("aquamarine", [127, 255, 212]), + ("azure", [240, 255, 255]), + ("beige", [245, 245, 220]), + ("bisque", [255, 228, 196]), + ("black", [0, 0, 0]), + ("blanchedalmond", [255, 235, 205]), + ("blue", [0, 0, 255]), + ("blueviolet", [138, 43, 226]), + ("brown", [165, 42, 42]), + ("burlywood", [222, 184, 135]), + ("cadetblue", [95, 158, 160]), + ("chartreuse", [127, 255, 0]), + ("chocolate", [210, 105, 30]), + ("coral", [255, 127, 80]), + ("cornflowerblue", [100, 149, 237]), + ("cornsilk", [255, 248, 220]), + ("crimson", [220, 20, 60]), + ("cyan", [0, 255, 255]), + ("darkblue", [0, 0, 139]), + ("darkcyan", [0, 139, 139]), + ("darkgoldenrod", [184, 134, 11]), + ("darkgray", [169, 169, 169]), + ("darkgreen", [0, 100, 0]), + ("darkgrey", [169, 169, 169]), + ("darkkhaki", [189, 183, 107]), + ("darkmagenta", [139, 0, 139]), + ("darkolivegreen", [85, 107, 47]), + ("darkorange", [255, 140, 0]), + ("darkorchid", [153, 50, 204]), + ("darkred", [139, 0, 0]), + ("darksalmon", [233, 150, 122]), + ("darkseagreen", [143, 188, 143]), + ("darkslateblue", [72, 61, 139]), + ("darkslategray", [47, 79, 79]), + ("darkslategrey", [47, 79, 79]), + ("darkturquoise", [0, 206, 209]), + ("darkviolet", [148, 0, 211]), + ("deeppink", [255, 20, 147]), + ("deepskyblue", [0, 191, 255]), + ("dimgray", [105, 105, 105]), + ("dimgrey", [105, 105, 105]), + ("dodgerblue", [30, 144, 255]), + ("firebrick", [178, 34, 34]), + ("floralwhite", [255, 250, 240]), + ("forestgreen", [34, 139, 34]), + ("fuchsia", [255, 0, 255]), + ("gainsboro", [220, 220, 220]), + ("ghostwhite", [248, 248, 255]), + ("gold", [255, 215, 0]), + ("goldenrod", [218, 165, 32]), + ("gray", [128, 128, 128]), + ("green", [0, 128, 0]), + ("greenyellow", [173, 255, 47]), + ("grey", [128, 128, 128]), + ("honeydew", [240, 255, 240]), + ("hotpink", [255, 105, 180]), + ("indianred", [205, 92, 92]), + ("indigo", [75, 0, 130]), + ("ivory", [255, 255, 240]), + ("khaki", [240, 230, 140]), + ("lavender", [230, 230, 250]), + ("lavenderblush", [255, 240, 245]), + ("lawngreen", [124, 252, 0]), + ("lemonchiffon", [255, 250, 205]), + ("lightblue", [173, 216, 230]), + ("lightcoral", [240, 128, 128]), + ("lightcyan", [224, 255, 255]), + ("lightgoldenrodyellow", [250, 250, 210]), + ("lightgray", [211, 211, 211]), + ("lightgreen", [144, 238, 144]), + ("lightgrey", [211, 211, 211]), + ("lightpink", [255, 182, 193]), + ("lightsalmon", [255, 160, 122]), + ("lightseagreen", [32, 178, 170]), + ("lightskyblue", [135, 206, 250]), + ("lightslategray", [119, 136, 153]), + ("lightslategrey", [119, 136, 153]), + ("lightsteelblue", [176, 196, 222]), + ("lightyellow", [255, 255, 224]), + ("lime", [0, 255, 0]), + ("limegreen", [50, 205, 50]), + ("linen", [250, 240, 230]), + ("magenta", [255, 0, 255]), + ("maroon", [128, 0, 0]), + ("mediumaquamarine", [102, 205, 170]), + ("mediumblue", [0, 0, 205]), + ("mediumorchid", [186, 85, 211]), + ("mediumpurple", [147, 112, 219]), + ("mediumseagreen", [60, 179, 113]), + ("mediumslateblue", [123, 104, 238]), + ("mediumspringgreen", [0, 250, 154]), + ("mediumturquoise", [72, 209, 204]), + ("mediumvioletred", [199, 21, 133]), + ("midnightblue", [25, 25, 112]), + ("mintcream", [245, 255, 250]), + ("mistyrose", [255, 228, 225]), + ("moccasin", [255, 228, 181]), + ("navajowhite", [255, 222, 173]), + ("navy", [0, 0, 128]), + ("oldlace", [253, 245, 230]), + ("olive", [128, 128, 0]), + ("olivedrab", [107, 142, 35]), + ("orange", [255, 165, 0]), + ("orangered", [255, 69, 0]), + ("orchid", [218, 112, 214]), + ("palegoldenrod", [238, 232, 170]), + ("palegreen", [152, 251, 152]), + ("paleturquoise", [175, 238, 238]), + ("palevioletred", [219, 112, 147]), + ("papayawhip", [255, 239, 213]), + ("peachpuff", [255, 218, 185]), + ("peru", [205, 133, 63]), + ("pink", [255, 192, 203]), + ("plum", [221, 160, 221]), + ("powderblue", [176, 224, 230]), + ("purple", [128, 0, 128]), + ("red", [255, 0, 0]), + ("rosybrown", [188, 143, 143]), + ("royalblue", [65, 105, 225]), + ("saddlebrown", [139, 69, 19]), + ("salmon", [250, 128, 114]), + ("sandybrown", [244, 164, 96]), + ("seagreen", [46, 139, 87]), + ("seashell", [255, 245, 238]), + ("sienna", [160, 82, 45]), + ("silver", [192, 192, 192]), + ("skyblue", [135, 206, 235]), + ("slateblue", [106, 90, 205]), + ("slategray", [112, 128, 144]), + ("slategrey", [112, 128, 144]), + ("snow", [255, 250, 250]), + ("springgreen", [0, 255, 127]), + ("steelblue", [70, 130, 180]), + ("tan", [210, 180, 140]), + ("teal", [0, 128, 128]), + ("thistle", [216, 191, 216]), + ("tomato", [255, 99, 71]), + ("turquoise", [64, 224, 208]), + ("violet", [238, 130, 238]), + ("wheat", [245, 222, 179]), + ("white", [255, 255, 255]), + ("whitesmoke", [245, 245, 245]), + ("yellow", [255, 255, 0]), + ("yellowgreen", [154, 205, 50]), +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_keyword_table_is_sorted_and_unique_for_binary_search() { + for pair in SVG_COLOR_KEYWORDS.windows(2) { + assert!(pair[0].0 < pair[1].0, "{} before {}", pair[0].0, pair[1].0); + } + } + + #[test] + fn every_eight_bit_value_round_trips_through_sixteen_bits() { + for v in 0..=255_u8 { + assert_eq!(narrow16(widen8(v)), v); + } + } + + #[test] + fn narrowing_rounds_to_the_nearest_eight_bit_value() { + assert_eq!(narrow16(7067), 27); + assert_eq!(narrow16(128), 0); + assert_eq!(narrow16(129), 1); + assert_eq!(narrow16(u16::MAX), 255); + } +} diff --git a/crates/teksilo-kde-scheme/src/config.rs b/crates/teksilo-kde-scheme/src/config.rs new file mode 100644 index 000000000..d97ab7ca7 --- /dev/null +++ b/crates/teksilo-kde-scheme/src/config.rs @@ -0,0 +1,807 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The KDE configuration text format and its cascade. +//! +//! [`KConfig::parse`] reads one file (one *layer*) and never fails: every +//! malformed line is skipped and reported as a [`ParseDiagnostic`]. +//! [`KConfig::cascade`] merges layers, lowest priority first, the way KDE +//! reads `kdeglobals` from the system directories and then the user's own +//! file. +//! +//! # Grammar +//! +//! - The file is split into lines at byte `0x0A` only, and each line is +//! trimmed of ASCII whitespace, so a CR before the LF goes away. +//! - An empty line, or one whose first byte is `#`, is ignored. `;` is not a +//! comment. +//! - A line starting with `[` is a group header: `[a][b]` names the group +//! `b` nested inside `a`. A nested group is a separate group; it inherits +//! nothing from its parent. A header with an unclosed segment is skipped +//! and the current group does not change. +//! - A trailing `[$i]` segment makes the group immutable; a `[$i]` line on +//! its own makes the rest of the file immutable, and no higher layer is +//! read at all. +//! - Any other line is `key=value`, both trimmed. The key may carry +//! bracketed suffixes: `[$i]` (immutable entry), `[$e]` (environment +//! expansion on string reads), `[$d]` (deleted entry, which hides the key +//! from lower layers), and a locale tag such as `[de]`. Locale-tagged +//! entries are ignored. +//! - `\s`, `\t`, `\n`, `\r`, `\\` and `\xHH` are decoded in keys, values and +//! group names. +//! - A UTF-8 byte order mark is not recognised: it stays part of the first +//! line, as KDE does, and a diagnostic names it. +//! - Text is decoded as UTF-8 with invalid sequences replaced by U+FFFD. +//! - Within one file the last occurrence of a key wins, unless an earlier +//! occurrence was immutable. +//! +//! `[$e]` expansion never runs a command: `$(cmd)` reads back as `(cmd)`. + +use std::collections::BTreeMap; +use std::fmt; + +use crate::color::KdeColor; + +/// A line longer than this many bytes is skipped. +const MAX_LINE_BYTES: usize = 1536 * 1024; + +/// After this many counted errors the rest of the file is not read. +const MAX_ERRORS: usize = 100; + +/// The UTF-8 byte order mark. +const BOM: &[u8] = b"\xEF\xBB\xBF"; + +/// Where a layer sits in a cascade, which changes how immutable groups +/// treat it. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum LayerKind { + /// A system file, or the user's own `system.kdeglobals`. + #[default] + Default, + /// The user's own file, the one a KDE program writes (`kdeglobals`, + /// `breezerc`). A group a lower layer made immutable ignores every + /// entry of this layer. + User, +} + +/// What a [`ParseDiagnostic`] reports. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +#[non_exhaustive] +pub enum DiagnosticKind { + /// The file starts with a UTF-8 byte order mark, which is kept as part + /// of the first line, so a group header there is not recognised. + #[error("the file starts with a UTF-8 byte order mark, which is part of the first line")] + ByteOrderMark, + /// A line longer than 1.5 MiB, skipped. + #[error("the line is longer than 1.5 MiB and was skipped")] + LineTooLong, + /// A group header segment has no closing `]`; the line was skipped and + /// the current group kept. + #[error("a group header segment has no closing `]`")] + UnclosedGroupHeader, + /// A key has a `[` with no `]` after it; the line was skipped. + #[error("a key has a `[` with no closing `]`")] + UnclosedKeyBracket, + /// A key has two locale tags; the line was skipped. + #[error("a key has more than one locale tag")] + SecondLocale, + /// An entry line has no `=` and is not a deletion; it was skipped. + #[error("the line has no `=`")] + MissingEquals, + /// An escape sequence that is not one of the recognised ones; the + /// backslash and the character after it were kept. + #[error("unknown escape sequence `\\{0}`")] + InvalidEscape(char), + /// The file reached the error limit; the rest of it was not read. + #[error("too many errors, the rest of the file was not read")] + TooManyErrors, +} + +impl DiagnosticKind { + /// Whether the diagnostic counts toward the error limit. + fn is_counted(&self) -> bool { + matches!( + self, + DiagnosticKind::UnclosedGroupHeader + | DiagnosticKind::UnclosedKeyBracket + | DiagnosticKind::SecondLocale + | DiagnosticKind::MissingEquals + | DiagnosticKind::InvalidEscape(_) + ) + } +} + +/// One problem found while parsing, with the 1-based line it is on. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseDiagnostic { + /// The 1-based line number. + pub line: usize, + /// What was wrong. + pub kind: DiagnosticKind, +} + +impl fmt::Display for ParseDiagnostic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "line {}: {}", self.line, self.kind) + } +} + +/// One stored entry. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Entry { + value: Option, + immutable: bool, + expand: bool, +} + +impl Entry { + /// The decoded value, or `None` for a deleted entry. + pub fn value(&self) -> Option<&str> { + self.value.as_deref() + } + + /// Whether the entry was deleted with `[$d]`. + pub fn is_deleted(&self) -> bool { + self.value.is_none() + } + + /// Whether the entry is immutable: no later occurrence and no higher + /// layer can change it. + pub fn is_immutable(&self) -> bool { + self.immutable + } + + /// Whether the entry carries `[$e]`, so a string read expands its + /// environment variables. + pub fn expands(&self) -> bool { + self.expand + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +struct Group { + entries: BTreeMap, + immutable: bool, +} + +/// One parsed configuration file, or the merge of several. +/// +/// Groups are addressed by their path: `&[]` is the root group (entries +/// before any header), `&["General"]` is `[General]`, and +/// `&["Colors:Header", "Inactive"]` is `[Colors:Header][Inactive]`. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct KConfig { + groups: BTreeMap, Group>, + file_immutable: bool, + kind: LayerKind, +} + +/// The variables a `[$e]` string read may expand. +/// +/// [`KdeConfigLocations::expansion_env`](crate::KdeConfigLocations::expansion_env) +/// builds one from the same environment the locations were read from. +pub struct ExpansionEnv<'a> { + var: &'a dyn Fn(&str) -> Option, + data_home: String, + config_home: String, + cache_home: String, +} + +impl<'a> ExpansionEnv<'a> { + /// An expansion environment that reads variables through `var` and + /// replaces an unset `QT_DATA_HOME`, `QT_CONFIG_HOME` or + /// `QT_CACHE_HOME` with the given directories. + pub fn new( + var: &'a dyn Fn(&str) -> Option, + data_home: impl Into, + config_home: impl Into, + cache_home: impl Into, + ) -> Self { + ExpansionEnv { + var, + data_home: data_home.into(), + config_home: config_home.into(), + cache_home: cache_home.into(), + } + } + + fn lookup(&self, name: &str) -> String { + match (self.var)(name) { + Some(value) => value, + None => match name { + "QT_DATA_HOME" => self.data_home.clone(), + "QT_CONFIG_HOME" => self.config_home.clone(), + "QT_CACHE_HOME" => self.cache_home.clone(), + _ => String::new(), + }, + } + } + + /// Expands the environment references in `value`. + /// + /// `$$` is a literal `$`; `${NAME}` and `$NAME` (the longest run of + /// letters, digits and `_`) are replaced by the variable's value, or by + /// nothing when it is unset or empty; a `$` followed by anything else is + /// removed, so `$(cmd)` becomes `(cmd)` and nothing is executed; a + /// trailing `$` is kept. + pub fn expand(&self, value: &str) -> String { + let mut out = String::with_capacity(value.len()); + let mut rest = value; + while let Some(at) = rest.find('$') { + out.push_str(&rest[..at]); + let after = &rest[at + 1..]; + let mut chars = after.chars(); + match chars.next() { + None => { + out.push('$'); + rest = after; + } + Some('$') => { + out.push('$'); + rest = &after[1..]; + } + Some('{') => { + let inner = &after[1..]; + let (name, next) = match inner.find('}') { + Some(end) => (&inner[..end], &inner[end + 1..]), + None => (inner, ""), + }; + if !name.is_empty() { + out.push_str(&self.lookup(name)); + } + rest = next; + } + Some(c) if is_name_char(c) => { + let end = after + .char_indices() + .find(|&(_, c)| !is_name_char(c)) + .map_or(after.len(), |(i, _)| i); + out.push_str(&self.lookup(&after[..end])); + rest = &after[end..]; + } + Some(_) => { + rest = after; + } + } + } + out.push_str(rest); + out + } +} + +impl fmt::Debug for ExpansionEnv<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExpansionEnv") + .field("data_home", &self.data_home) + .field("config_home", &self.config_home) + .field("cache_home", &self.cache_home) + .finish_non_exhaustive() + } +} + +fn is_name_char(c: char) -> bool { + c.is_alphanumeric() || c == '_' +} + +impl KConfig { + /// Parses one file's text. Never fails; every problem is reported in + /// the returned diagnostics and the offending line is skipped. + pub fn parse(text: &str) -> (KConfig, Vec) { + KConfig::parse_bytes(text.as_bytes()) + } + + /// Parses one file's bytes, decoding text as UTF-8 with invalid + /// sequences replaced by U+FFFD. + pub fn parse_bytes(bytes: &[u8]) -> (KConfig, Vec) { + Parser::default().run(bytes) + } + + /// The same layer, marked as the given [`LayerKind`]. + pub fn with_kind(mut self, kind: LayerKind) -> KConfig { + self.kind = kind; + self + } + + /// This layer's kind ([`LayerKind::Default`] unless set). + pub fn kind(&self) -> LayerKind { + self.kind + } + + /// Whether the layer contains a bare `[$i]` line, so no higher layer is + /// read on top of it. + pub fn is_file_immutable(&self) -> bool { + self.file_immutable + } + + /// The same merge with its whole-file lock lifted, so a file cascaded on + /// it is read again. + /// + /// The entries the lock made immutable stay immutable, and so do the + /// groups a `[$i]` header locked, so a file cascaded on the result still + /// cannot change them; it can only add keys they do not set and replace + /// entries that were never locked. Used for the colour-scheme file of + /// the no-colours fallback, which is not a layer of the `kdeglobals` + /// cascade that a whole-file lock suppresses. + pub(crate) fn without_file_lock(mut self) -> KConfig { + self.file_immutable = false; + self + } + + /// Merges `layers`, lowest priority first. + /// + /// For each entry of each layer, in order: + /// + /// 1. an entry the merge already holds as immutable is kept; + /// 2. a key the merge does not hold yet, in a group a lower layer made + /// immutable, is ignored; + /// 3. in a [`LayerKind::User`] layer, every entry of a group a lower + /// layer made immutable is ignored; + /// 4. otherwise the entry replaces the one held (a deleted entry hides + /// the key). + /// + /// A layer that is file-immutable stops the merge: no higher layer is + /// read. The result is a [`LayerKind::Default`] layer that can itself be + /// cascaded under another file. + pub fn cascade(layers: &[KConfig]) -> KConfig { + let mut merged = KConfig::default(); + for layer in layers { + if merged.file_immutable { + break; + } + for (path, group) in &layer.groups { + let target = merged.groups.entry(path.clone()).or_default(); + let locked_by_lower = target.immutable; + if locked_by_lower && layer.kind == LayerKind::User { + continue; + } + for (key, entry) in &group.entries { + match target.entries.get(key) { + Some(held) if held.immutable => {} + None if locked_by_lower => {} + _ => { + target.entries.insert(key.clone(), entry.clone()); + } + } + } + } + for (path, group) in &layer.groups { + if group.immutable { + merged.groups.entry(path.clone()).or_default().immutable = true; + } + } + if layer.file_immutable { + merged.file_immutable = true; + } + } + merged + .groups + .retain(|_, g| !g.entries.is_empty() || g.immutable); + merged + } + + /// The stored entry for `key` in `group`, deleted entries included. + pub fn entry(&self, group: &[&str], key: &str) -> Option<&Entry> { + self.groups.get(&owned_path(group))?.entries.get(key) + } + + /// The decoded value of `key` in `group`, or `None` when it is absent + /// or deleted. Not expanded, even when the entry carries `[$e]`. + pub fn get(&self, group: &[&str], key: &str) -> Option<&str> { + self.entry(group, key)?.value() + } + + /// Whether `key` is present (and not deleted) in `group`. + pub fn has_key(&self, group: &[&str], key: &str) -> bool { + self.get(group, key).is_some() + } + + /// Whether `group` was made immutable by a `[$i]` header. + pub fn is_group_immutable(&self, group: &[&str]) -> bool { + self.groups + .get(&owned_path(group)) + .is_some_and(|g| g.immutable) + } + + /// Whether `group` exists: it, or a group nested inside it, holds at + /// least one entry that is not deleted. + pub fn group_exists(&self, group: &[&str]) -> bool { + self.groups.iter().any(|(path, g)| { + path.len() >= group.len() + && path.iter().zip(group).all(|(a, b)| a == b) + && g.entries.values().any(|e| !e.is_deleted()) + }) + } + + /// Every group path that holds an entry, in sorted order. + pub fn groups(&self) -> impl Iterator> { + self.groups + .iter() + .filter(|(_, g)| !g.entries.is_empty()) + .map(|(path, _)| path.iter().map(String::as_str).collect()) + } + + /// The present (not deleted) keys of `group`, in sorted order. + pub fn keys(&self, group: &[&str]) -> Vec<&str> { + self.groups + .get(&owned_path(group)) + .map(|g| { + g.entries + .iter() + .filter(|(_, e)| !e.is_deleted()) + .map(|(k, _)| k.as_str()) + .collect() + }) + .unwrap_or_default() + } + + /// The value of `key` in `group` read as a string: expanded through + /// `env` when the entry carries `[$e]`, as it is otherwise. + pub fn read_string(&self, group: &[&str], key: &str, env: &ExpansionEnv<'_>) -> Option { + let entry = self.entry(group, key)?; + let value = entry.value()?; + Some(if entry.expand { + env.expand(value) + } else { + value.to_owned() + }) + } + + /// The value read as a boolean: `false`, `no`, `off` and `0` (ignoring + /// ASCII case) are false and every other value, an empty one included, + /// is true. `None` when the key is absent. + pub fn read_bool(&self, group: &[&str], key: &str) -> Option { + self.get(group, key).map(parse_bool) + } + + /// The value read as a base-10 integer, or `default` when the key is + /// absent or the value does not parse. + pub fn read_int(&self, group: &[&str], key: &str, default: i64) -> i64 { + self.get(group, key) + .and_then(|v| v.parse().ok()) + .unwrap_or(default) + } + + /// The value read as a finite real number in the C locale, or `default` + /// when the key is absent or the value does not parse (a non-finite + /// value does not parse). + pub fn read_real(&self, group: &[&str], key: &str, default: f64) -> f64 { + self.get(group, key).and_then(parse_real).unwrap_or(default) + } + + /// The value read as a colour, or `None` when the key is absent or the + /// value is not a colour (see [`KdeColor::parse`]). + pub fn read_color(&self, group: &[&str], key: &str) -> Option { + self.get(group, key).and_then(KdeColor::parse) + } + + /// Stores `value` for `key` in `group` as a plain entry (neither + /// immutable nor expanding), replacing whatever the group held for the + /// key. The group is created when it does not exist. + pub(crate) fn set(&mut self, group: &[&str], key: &str, value: impl Into) { + self.groups + .entry(owned_path(group)) + .or_default() + .entries + .insert( + key.to_owned(), + Entry { + value: Some(value.into()), + immutable: false, + expand: false, + }, + ); + } +} + +pub(crate) fn parse_bool(value: &str) -> bool { + !["false", "no", "off", "0"] + .iter() + .any(|f| value.eq_ignore_ascii_case(f)) +} + +pub(crate) fn parse_real(value: &str) -> Option { + value.parse::().ok().filter(|v| v.is_finite()) +} + +fn owned_path(group: &[&str]) -> Vec { + group.iter().map(|s| (*s).to_owned()).collect() +} + +fn is_trim_byte(b: u8) -> bool { + matches!(b, b' ' | b'\t' | b'\r' | b'\n' | 0x0b | 0x0c) +} + +fn trim_bytes(mut s: &[u8]) -> &[u8] { + while let [first, rest @ ..] = s { + if is_trim_byte(*first) { + s = rest; + } else { + break; + } + } + while let [rest @ .., last] = s { + if is_trim_byte(*last) { + s = rest; + } else { + break; + } + } + s +} + +/// The state of one file's parse. +#[derive(Default)] +struct Parser { + config: KConfig, + diagnostics: Vec, + errors: usize, + line: usize, + group: Vec, + /// Entries of the current group are immutable: its header carried + /// `[$i]`, or the file became immutable before it. + group_locked: bool, +} + +/// What one line asked for, so the parser can decide whether to stop. +enum Flow { + Continue, + Stop, +} + +impl Parser { + fn run(mut self, bytes: &[u8]) -> (KConfig, Vec) { + for (index, raw) in bytes.split(|&b| b == b'\n').enumerate() { + self.line = index + 1; + if index == 0 && raw.starts_with(BOM) { + self.note(DiagnosticKind::ByteOrderMark); + } + if raw.len() > MAX_LINE_BYTES { + self.note(DiagnosticKind::LineTooLong); + continue; + } + if let Flow::Stop = self.line_bytes(trim_bytes(raw)) { + break; + } + } + (self.config, self.diagnostics) + } + + /// Records a diagnostic; returns `Stop` once the error limit is hit. + fn note(&mut self, kind: DiagnosticKind) -> Flow { + let counted = kind.is_counted(); + self.diagnostics.push(ParseDiagnostic { + line: self.line, + kind, + }); + if counted { + self.errors += 1; + if self.errors >= MAX_ERRORS { + self.diagnostics.push(ParseDiagnostic { + line: self.line, + kind: DiagnosticKind::TooManyErrors, + }); + return Flow::Stop; + } + } + Flow::Continue + } + + fn line_bytes(&mut self, line: &[u8]) -> Flow { + match line.first() { + None | Some(b'#') => Flow::Continue, + Some(b'[') => self.header(line), + Some(_) => self.entry(line), + } + } + + fn header(&mut self, line: &[u8]) -> Flow { + // Split into segments `[..]`: each starts after a `[`, ends at the + // next `]`, and another follows only when the byte after `]` is `[`. + let mut segments: Vec<&[u8]> = Vec::new(); + let mut pos = 0; + let mut ends_at_line_end = false; + loop { + debug_assert_eq!(line[pos], b'['); + let start = pos + 1; + let Some(len) = line[start..].iter().position(|&b| b == b']') else { + return self.note(DiagnosticKind::UnclosedGroupHeader); + }; + segments.push(&line[start..start + len]); + let next = start + len + 1; + if next == line.len() { + ends_at_line_end = true; + break; + } + if line[next] != b'[' { + break; + } + pos = next; + } + + let mut lock_group = false; + if ends_at_line_end && segments.last() == Some(&&b"$i"[..]) { + segments.pop(); + if segments.is_empty() { + // A bare `[$i]`: the rest of the file is immutable, and the + // entries before the next header go to the empty-named group. + self.config.file_immutable = true; + self.group = vec![String::new()]; + self.group_locked = true; + return Flow::Continue; + } + lock_group = true; + } + + let mut path = Vec::with_capacity(segments.len()); + for segment in segments { + let (name, flow) = self.decode(segment); + if let Flow::Stop = flow { + return Flow::Stop; + } + path.push(name); + } + if lock_group { + self.config + .groups + .entry(path.clone()) + .or_default() + .immutable = true; + } + self.group = path; + self.group_locked = lock_group || self.config.file_immutable; + Flow::Continue + } + + fn entry(&mut self, line: &[u8]) -> Flow { + let (key_part, value_part) = match line.iter().position(|&b| b == b'=') { + Some(eq) => (trim_bytes(&line[..eq]), Some(trim_bytes(&line[eq + 1..]))), + None => (line, None), + }; + if key_part.is_empty() { + return Flow::Continue; + } + + // Peel the bracketed suffixes off the key, from the right. + let mut key = key_part; + let mut immutable = false; + let mut expand = false; + let mut delete = false; + let mut locale: Option<&[u8]> = None; + while key.last() == Some(&b']') { + let Some(open) = key.iter().rposition(|&b| b == b'[') else { + break; + }; + let inside = &key[open + 1..key.len() - 1]; + if let Some(options) = inside.strip_prefix(b"$") { + for &letter in options { + match letter { + b'i' => immutable = true, + b'e' => expand = true, + b'd' => delete = true, + _ => {} + } + } + } else if locale.is_some() { + return self.note(DiagnosticKind::SecondLocale); + } else { + locale = Some(inside); + } + key = trim_bytes(&key[..open]); + } + if let Some(open) = key.iter().rposition(|&b| b == b'[') + && !key[open..].contains(&b']') + { + return self.note(DiagnosticKind::UnclosedKeyBracket); + } + if value_part.is_none() && !delete { + return self.note(DiagnosticKind::MissingEquals); + } + + let (key, flow) = self.decode(key); + if let Flow::Stop = flow { + return Flow::Stop; + } + let value = if delete { + None + } else { + let (value, flow) = self.decode(value_part.unwrap_or_default()); + if let Flow::Stop = flow { + return Flow::Stop; + } + Some(value) + }; + if locale.is_some() || key.is_empty() { + return Flow::Continue; + } + + let entry = Entry { + value, + immutable: immutable || self.group_locked, + expand, + }; + let group = self.config.groups.entry(self.group.clone()).or_default(); + match group.entries.get(&key) { + Some(held) if held.immutable => {} + _ => { + group.entries.insert(key, entry); + } + } + Flow::Continue + } + + /// Decodes escape sequences in a key, value or group-name segment and + /// turns the bytes into text. + fn decode(&mut self, text: &[u8]) -> (String, Flow) { + if !text.contains(&b'\\') { + return (String::from_utf8_lossy(text).into_owned(), Flow::Continue); + } + let text = trim_bytes(text); + let mut out = Vec::with_capacity(text.len()); + let mut flow = Flow::Continue; + let mut i = 0; + while i < text.len() { + let b = text[i]; + if b != b'\\' { + out.push(b); + i += 1; + continue; + } + let Some(&next) = text.get(i + 1) else { + out.push(b'\\'); + break; + }; + i += 2; + match next { + b's' => out.push(b' '), + b't' => out.push(b'\t'), + b'n' => out.push(b'\n'), + b'r' => out.push(b'\r'), + b'\\' => out.push(b'\\'), + b';' | b',' => out.extend_from_slice(&[b'\\', next]), + b'x' => { + if text.len() - i < 2 { + out.push(b'x'); + break; + } + let pair = &text[i..i + 2]; + i += 2; + match std::str::from_utf8(pair) + .ok() + .and_then(|p| u8::from_str_radix(p, 16).ok()) + { + Some(byte) if pair.iter().all(u8::is_ascii_hexdigit) => out.push(byte), + _ => out.push(b'x'), + } + } + other => { + out.extend_from_slice(&[b'\\', other]); + let shown = char::from_u32(u32::from(other)).unwrap_or('\u{FFFD}'); + if let Flow::Stop = self.note(DiagnosticKind::InvalidEscape(shown)) { + flow = Flow::Stop; + } + } + } + } + (String::from_utf8_lossy(&out).into_owned(), flow) + } +} + +/// Escapes `value` so that [`KConfig::parse`] reads it back unchanged: +/// backslash, line feed, carriage return and tab are escaped, as are a +/// leading or trailing space and every other ASCII control character. +pub(crate) fn escape_value(value: &str) -> String { + let mut out = String::with_capacity(value.len()); + let last = value.chars().count().saturating_sub(1); + for (i, c) in value.chars().enumerate() { + match c { + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + ' ' if i == 0 || i == last => out.push_str("\\s"), + c if c.is_ascii_control() => out.push_str(&format!("\\x{:02x}", c as u32)), + c => out.push(c), + } + } + out +} diff --git a/crates/teksilo-kde-scheme/src/desktop.rs b/crates/teksilo-kde-scheme/src/desktop.rs new file mode 100644 index 000000000..648134b7b --- /dev/null +++ b/crates/teksilo-kde-scheme/src/desktop.rs @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The desktop settings a KDE session gives an application, read from +//! configuration layers the caller has already loaded. +//! +//! [`KdeDesktop::from_layers`] is pure: the caller finds the files with +//! [`KdeConfigLocations`](crate::KdeConfigLocations), parses the ones that +//! exist, and hands the layers over. + +use crate::color::KdeColor; +use crate::config::{ExpansionEnv, KConfig, LayerKind}; +use crate::fonts::{KdeFont, STYLE_HINT_MONOSPACE}; +use crate::scheme::KdeColorScheme; + +/// Where a desktop's colour scheme came from. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SchemeOrigin { + /// The colour groups of `kdeglobals` itself, which has a + /// `[Colors:View]` group. Any accent the user chose is already in them. + Kdeglobals, + /// A scheme file read because `kdeglobals` has no `[Colors:View]` + /// group: the look-and-feel package's colours or the named scheme, with + /// the user's accent applied to it when there is one. + SchemeFile, + /// Neither: `kdeglobals` has no colours and no scheme file was found, + /// so the built-in Breeze Light scheme was used, with the user's accent + /// applied to it when there is one. + BuiltIn, +} + +/// The five fonts a KDE desktop configures. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct KdeFonts { + /// `[General] font`, default Noto Sans 10 pt. + pub general: KdeFont, + /// `[General] fixed`, default Hack 10 pt with the monospace hint. + pub fixed: KdeFont, + /// `[General] smallestReadableFont`, default Noto Sans 8 pt. + pub small: KdeFont, + /// `[General] menuFont`, default Noto Sans 10 pt. + pub menu: KdeFont, + /// `[General] toolBarFont`, default Noto Sans 10 pt. + pub toolbar: KdeFont, +} + +impl Default for KdeFonts { + fn default() -> Self { + let regular = |family: &str, points: f64| { + let mut font = KdeFont::new(family, points); + font.style_name = Some("Regular".to_owned()); + font + }; + let mut fixed = regular("Hack", 10.0); + fixed.style_hint = STYLE_HINT_MONOSPACE; + KdeFonts { + general: regular("Noto Sans", 10.0), + fixed, + small: regular("Noto Sans", 8.0), + menu: regular("Noto Sans", 10.0), + toolbar: regular("Noto Sans", 10.0), + } + } +} + +impl KdeFonts { + /// Reads the font keys as strings, so a `[$e]` entry is expanded + /// before it is parsed (spec j.1 and b.11). + fn read(config: &KConfig, env: &ExpansionEnv<'_>) -> KdeFonts { + let defaults = KdeFonts::default(); + let font = |key: &str, default: KdeFont| { + config + .read_string(&["General"], key, env) + .filter(|v| !v.is_empty()) + .and_then(|v| KdeFont::parse_qfont(&v)) + .unwrap_or(default) + }; + KdeFonts { + general: font("font", defaults.general), + fixed: font("fixed", defaults.fixed), + small: font("smallestReadableFont", defaults.small), + menu: font("menuFont", defaults.menu), + toolbar: font("toolBarFont", defaults.toolbar), + } + } +} + +/// How large a Breeze window shadow is (`[Common] ShadowSize`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum ShadowSize { + /// `ShadowNone`, 0. + None, + /// `ShadowSmall`, 1. + Small, + /// `ShadowMedium`, 2. + Medium, + /// `ShadowLarge`, 3, the default. + #[default] + Large, + /// `ShadowVeryLarge`, 4. + VeryLarge, +} + +/// When Breeze underlines mnemonics (`[Style] MnemonicsMode`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum MnemonicsMode { + /// `MN_NEVER`, 0. + Never, + /// `MN_AUTO`, 1, the default: while Alt is held. + #[default] + Auto, + /// `MN_ALWAYS`, 2. + Always, +} + +/// How many arrow buttons a Breeze scroll bar has at one end. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum ScrollBarButtons { + /// 0, the default. + #[default] + None, + /// 1. + Single, + /// Any other value. + Double, +} + +impl ScrollBarButtons { + fn from_code(code: i64) -> ScrollBarButtons { + match code { + 0 => ScrollBarButtons::None, + 1 => ScrollBarButtons::Single, + _ => ScrollBarButtons::Double, + } + } +} + +/// The Breeze widget-style settings of `breezerc` that change how widgets +/// look. Keys that only concern the window decoration, window dragging, +/// splitter proxies or debugging are not read. +#[derive(Debug, Clone, Copy, PartialEq)] +#[non_exhaustive] +pub struct BreezeSettings { + /// `[Common] ShadowStrength`, clamped to 25..=255 (the strength is the + /// value divided by 255). Default 255. + pub shadow_strength: u8, + /// `[Common] ShadowSize`. Default large. + pub shadow_size: ShadowSize, + /// `[Common] ShadowColor`. Default black. + pub shadow_color: KdeColor, + /// `[Style] AnimationsEnabled`. Default true. + pub animations_enabled: bool, + /// `[Style] AnimationSteps`: animation progress is quantised to + /// multiples of one over this many steps. Default 10. + pub animation_steps: i64, + /// `[Style] AnimationsDuration` in milliseconds. Default 100. A + /// `kdeglobals [KDE] AnimationDurationFactor` overrides it. + pub animations_duration_ms: i64, + /// `[Style] StackedWidgetTransitionsEnabled`. Default false. + pub stacked_widget_transitions_enabled: bool, + /// `[Style] ProgressBarAnimated`: the busy animation. Default true. + pub progress_bar_animated: bool, + /// `[Style] ProgressBarBusyStepDuration` in milliseconds. Default 800. + pub progress_bar_busy_step_duration_ms: i64, + /// `[Style] ScrollBarAddLineButtons`. Default none. + pub scroll_bar_add_line_buttons: ScrollBarButtons, + /// `[Style] ScrollBarSubLineButtons`. Default none. + pub scroll_bar_sub_line_buttons: ScrollBarButtons, + /// `[Style] MnemonicsMode`. Default automatic. + pub mnemonics_mode: MnemonicsMode, + /// `[Style] ToolBarDrawItemSeparator`. Default true. + pub tool_bar_draw_item_separator: bool, + /// `[Style] ViewDrawFocusIndicator`: the item-view focus rectangle. + /// Default true. + pub view_draw_focus_indicator: bool, + /// `[Style] SliderDrawTickMarks`. Default true. + pub slider_draw_tick_marks: bool, + /// `[Style] ViewDrawTreeBranchLines`. Default true. + pub view_draw_tree_branch_lines: bool, + /// `[Style] ViewInvertSortIndicator`. Default true. + pub view_invert_sort_indicator: bool, + /// `[Style] TabBarDrawCenteredTabs`. Default false. + pub tab_bar_draw_centered_tabs: bool, + /// `[Style] DockWidgetDrawFrame`. Default false. + pub dock_widget_draw_frame: bool, + /// `[Style] SidePanelDrawFrame`. Default false. + pub side_panel_draw_frame: bool, + /// `[Style] MenuItemDrawStrongFocus`. Default true. + pub menu_item_draw_strong_focus: bool, + /// `[Style] MenuOpacity` in percent, clamped to 0..=100. Default 100. + pub menu_opacity: u8, +} + +impl Default for BreezeSettings { + fn default() -> Self { + BreezeSettings { + shadow_strength: 255, + shadow_size: ShadowSize::Large, + shadow_color: KdeColor::BLACK, + animations_enabled: true, + animation_steps: 10, + animations_duration_ms: 100, + stacked_widget_transitions_enabled: false, + progress_bar_animated: true, + progress_bar_busy_step_duration_ms: 800, + scroll_bar_add_line_buttons: ScrollBarButtons::None, + scroll_bar_sub_line_buttons: ScrollBarButtons::None, + mnemonics_mode: MnemonicsMode::Auto, + tool_bar_draw_item_separator: true, + view_draw_focus_indicator: true, + slider_draw_tick_marks: true, + view_draw_tree_branch_lines: true, + view_invert_sort_indicator: true, + tab_bar_draw_centered_tabs: false, + dock_widget_draw_frame: false, + side_panel_draw_frame: false, + menu_item_draw_strong_focus: true, + menu_opacity: 100, + } + } +} + +impl BreezeSettings { + /// Reads the settings from a merged `breezerc`. + pub fn from_config(config: &KConfig) -> BreezeSettings { + let d = BreezeSettings::default(); + let common = &["Common"]; + let style = &["Style"]; + let flag = |key: &str, default: bool| config.read_bool(style, key).unwrap_or(default); + let int = |key: &str, default: i64| config.read_int(style, key, default); + BreezeSettings { + shadow_strength: config + .read_int(common, "ShadowStrength", i64::from(d.shadow_strength)) + .clamp(25, 255) as u8, + shadow_size: config + .get(common, "ShadowSize") + .and_then(|v| { + enum_value( + v, + &[ + "ShadowNone", + "ShadowSmall", + "ShadowMedium", + "ShadowLarge", + "ShadowVeryLarge", + ], + ) + }) + .map_or(d.shadow_size, |i| { + [ + ShadowSize::None, + ShadowSize::Small, + ShadowSize::Medium, + ShadowSize::Large, + ShadowSize::VeryLarge, + ][i] + }), + shadow_color: config + .read_color(common, "ShadowColor") + .unwrap_or(d.shadow_color), + animations_enabled: flag("AnimationsEnabled", d.animations_enabled), + animation_steps: int("AnimationSteps", d.animation_steps), + animations_duration_ms: int("AnimationsDuration", d.animations_duration_ms), + stacked_widget_transitions_enabled: flag( + "StackedWidgetTransitionsEnabled", + d.stacked_widget_transitions_enabled, + ), + progress_bar_animated: flag("ProgressBarAnimated", d.progress_bar_animated), + progress_bar_busy_step_duration_ms: int( + "ProgressBarBusyStepDuration", + d.progress_bar_busy_step_duration_ms, + ), + scroll_bar_add_line_buttons: ScrollBarButtons::from_code(int( + "ScrollBarAddLineButtons", + 0, + )), + scroll_bar_sub_line_buttons: ScrollBarButtons::from_code(int( + "ScrollBarSubLineButtons", + 0, + )), + mnemonics_mode: config + .get(style, "MnemonicsMode") + .and_then(|v| enum_value(v, &["MN_NEVER", "MN_AUTO", "MN_ALWAYS"])) + .map_or(d.mnemonics_mode, |i| { + [ + MnemonicsMode::Never, + MnemonicsMode::Auto, + MnemonicsMode::Always, + ][i] + }), + tool_bar_draw_item_separator: flag( + "ToolBarDrawItemSeparator", + d.tool_bar_draw_item_separator, + ), + view_draw_focus_indicator: flag("ViewDrawFocusIndicator", d.view_draw_focus_indicator), + slider_draw_tick_marks: flag("SliderDrawTickMarks", d.slider_draw_tick_marks), + view_draw_tree_branch_lines: flag( + "ViewDrawTreeBranchLines", + d.view_draw_tree_branch_lines, + ), + view_invert_sort_indicator: flag( + "ViewInvertSortIndicator", + d.view_invert_sort_indicator, + ), + tab_bar_draw_centered_tabs: flag( + "TabBarDrawCenteredTabs", + d.tab_bar_draw_centered_tabs, + ), + dock_widget_draw_frame: flag("DockWidgetDrawFrame", d.dock_widget_draw_frame), + side_panel_draw_frame: flag("SidePanelDrawFrame", d.side_panel_draw_frame), + menu_item_draw_strong_focus: flag( + "MenuItemDrawStrongFocus", + d.menu_item_draw_strong_focus, + ), + menu_opacity: int("MenuOpacity", i64::from(d.menu_opacity)).clamp(0, 100) as u8, + } + } +} + +/// An enumeration value stored as one of `names` (ignoring ASCII case) or +/// as its index. +fn enum_value(value: &str, names: &[&str]) -> Option { + names + .iter() + .position(|n| n.eq_ignore_ascii_case(value)) + .or_else(|| value.parse::().ok().filter(|&i| i < names.len())) +} + +/// The configuration layers a [`KdeDesktop`] is built from, each list +/// lowest priority first, as +/// [`KdeConfigLocations`](crate::KdeConfigLocations) orders them. +#[derive(Debug, Clone, Copy, Default)] +pub struct KdeDesktopLayers<'a> { + /// The `kdeglobals` cascade. + pub kdeglobals: &'a [KConfig], + /// The `breezerc` cascade. + pub breezerc: &'a [KConfig], + /// The colour-scheme file to read when `kdeglobals` has no + /// `[Colors:View]` group: the first existing candidate of + /// [`KdeConfigLocations::scheme_fallback_candidates`](crate::KdeConfigLocations::scheme_fallback_candidates). + pub scheme_file: Option<&'a KConfig>, +} + +/// What a KDE desktop configures for an application. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct KdeDesktop { + /// The colour scheme in effect. + pub scheme: KdeColorScheme, + /// Where the scheme came from. + pub scheme_origin: SchemeOrigin, + /// `kdeglobals [General] AccentColor`, the accent the user chose, when + /// present, a colour, and not fully transparent black. + pub user_accent: Option, + /// The configured fonts. + pub fonts: KdeFonts, + /// `kdeglobals [KDE] AnimationDurationFactor`, `None` when absent (a + /// present value that does not parse reads as 1.0). + pub animation_duration_factor: Option, + /// `kdeglobals [KDE] widgetStyle` as stored, `None` when absent. + pub widget_style: Option, + /// The Breeze widget-style settings. + pub breeze: BreezeSettings, +} + +impl KdeDesktop { + /// A desktop with `scheme` and every other setting at its default, as a + /// KDE session with empty configuration files gives. + pub fn new(scheme: KdeColorScheme) -> KdeDesktop { + KdeDesktop { + scheme, + scheme_origin: SchemeOrigin::Kdeglobals, + user_accent: None, + fonts: KdeFonts::default(), + animation_duration_factor: None, + widget_style: None, + breeze: BreezeSettings::default(), + } + } + + /// Builds the desktop from already-read layers. + /// + /// When the merged `kdeglobals` has a `[Colors:View]` group the scheme + /// is read from it, and any accent the user chose is already in its + /// colours. Otherwise it is read from `layers.scheme_file`, or from the + /// built-in Breeze Light when there is none, with the `kdeglobals` + /// cascade below it, so a key the scheme file lacks still comes from + /// `kdeglobals`. String settings expand `[$e]` entries through `env`. + /// + /// In that fallback, a `[General] AccentColor` that is a colour and not + /// fully transparent is applied to the scheme file with + /// [`KdeColorScheme::with_accent`], and what that application writes + /// takes the scheme file's place over `kdeglobals`. It writes `[KDE] + /// contrast` and `frameContrast` (7 and 0.2 when the scheme file lacks + /// them), so those then come from the scheme rather than from + /// `kdeglobals`. KDE itself ignores the accent in this fallback; Teksilo + /// honours it so that the accent the user chose still shows on a profile + /// whose colour groups were never written. + /// + /// A `kdeglobals` layer with a bare `[$i]` line (a whole-file lock) + /// keeps every higher `kdeglobals` layer out, but not the scheme file, + /// which is not one of those layers: the file is still read and + /// accented, and [`KdeDesktop::scheme_origin`] still names it. Each + /// entry the lock made immutable wins over the file, so a locked + /// `[KDE] frameContrast` stays, while keys the locked layers do not set + /// come from the file as without the lock. + pub fn from_layers(layers: &KdeDesktopLayers<'_>, env: &ExpansionEnv<'_>) -> KdeDesktop { + let globals = KConfig::cascade(layers.kdeglobals); + let user_accent = globals + .read_color(&["General"], "AccentColor") + .filter(|c| !c.is_transparent_black()); + let (scheme, scheme_origin) = if globals.group_exists(&["Colors:View"]) { + ( + KdeColorScheme::from_config(&globals), + SchemeOrigin::Kdeglobals, + ) + } else { + let (file, origin) = match layers.scheme_file { + Some(file) => (file.clone(), SchemeOrigin::SchemeFile), + None => ( + KConfig::parse(&KdeColorScheme::breeze_light().to_kconfig_text()).0, + SchemeOrigin::BuiltIn, + ), + }; + let file = match user_accent { + Some(accent) => KdeColorScheme::from_config(&file).accented_config(accent), + None => file, + }; + // A whole-file lock in a `kdeglobals` layer keeps the higher + // `kdeglobals` layers out, not the scheme file: that lock is + // lifted here, and the locked entries still win over the file. + let merged = KConfig::cascade(&[ + globals.clone().without_file_lock(), + file.with_kind(LayerKind::Default), + ]); + (KdeColorScheme::from_config(&merged), origin) + }; + + let kde = &["KDE"]; + let animation_duration_factor = globals + .has_key(kde, "AnimationDurationFactor") + .then(|| globals.read_real(kde, "AnimationDurationFactor", 1.0)); + + KdeDesktop { + scheme, + scheme_origin, + user_accent, + fonts: KdeFonts::read(&globals, env), + animation_duration_factor, + widget_style: globals.read_string(kde, "widgetStyle", env), + breeze: BreezeSettings::from_config(&KConfig::cascade(layers.breezerc)), + } + } + + /// The animation duration factor, 1.0 when `kdeglobals` does not set + /// one. + pub fn animation_factor(&self) -> f64 { + self.animation_duration_factor.unwrap_or(1.0) + } + + /// Whether the desktop asks for reduced motion: the animation duration + /// factor is exactly 0, as the KDE desktop portal reports it. + pub fn prefers_reduced_motion(&self) -> bool { + self.animation_duration_factor == Some(0.0) + } + + /// Whether the widget style is Breeze: `widgetStyle` absent, empty, or + /// `breeze` ignoring ASCII case. + pub fn uses_breeze_style(&self) -> bool { + self.widget_style + .as_deref() + .is_none_or(|s| s.is_empty() || s.eq_ignore_ascii_case("breeze")) + } +} diff --git a/crates/teksilo-kde-scheme/src/effects.rs b/crates/teksilo-kde-scheme/src/effects.rs new file mode 100644 index 000000000..62ffb7046 --- /dev/null +++ b/crates/teksilo-kde-scheme/src/effects.rs @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Disabled and Inactive state effects: how a scheme's stored colours +//! become the colours of a disabled control or of a window without focus. +//! +//! Implemented from sections c.2, c.5 and e of Teksilo's specification +//! (`engineering/docs/kde-breeze-spec.md`). +//! +//! A state's effects ([`StateEffect`]) are three optional transformations: +//! an *intensity* change (shade, darken or lighten the luma), a *colour* +//! change (desaturate, fade toward a colour, or tint toward it) and, for +//! foregrounds only, a *contrast* change (fade or tint toward the +//! background the colour is drawn on). They run in this order: +//! +//! - a foreground or a decoration gets the contrast change first, against +//! the set's background as it was before any effect, then the intensity +//! change, then the colour change ([`foreground_chain`]); +//! - a stored background gets the intensity change, then the colour change +//! ([`background_chain`]). +//! +//! The Active state never has effects. When a state's effects are disabled +//! (`Enable=false`), every colour passes through untouched, with no colour +//! operation run on it, so it is not even re-quantised. +//! +//! [`KdeColorScheme::state_colors`] also chooses where each (state, set) +//! takes its colours from: a set's `[Inactive]` subgroup replaces it for the +//! Inactive state, and the Selection set of an inactive window is, by +//! default, the Window set with both backgrounds tinted toward the +//! selection colour ([`tint_toward_selection`]). The six computed +//! backgrounds and the palette are derived from the result in +//! [`crate::palette`]. + +use crate::color::KdeColor; +use crate::math::{darken, lighten, mix, shade, tint}; +use crate::scheme::{ + ColorEffect, ColorRole, ColorSet, ColorState, ContrastEffect, IntensityEffect, KdeColorScheme, + SetColors, StateEffect, +}; + +/// How far the backgrounds of an inactive window's selection are tinted +/// toward the selection colour when `ChangeSelectionColor` is on. +pub const SELECTION_TINT_AMOUNT: f64 = 0.4; + +/// The effects a stored background gets: the intensity change, then the +/// colour change on its result. +/// +/// - intensity: shade adds `intensity_amount` to the luma (chroma amount 0), +/// darken and lighten move the luma by `intensity_amount` toward black or +/// white (chroma factor 1); +/// - colour: desaturate scales the chroma by `1 - color_amount` (luma +/// amount 0), fade mixes toward `color` by `color_amount`, tint tints +/// toward it by `color_amount`. +/// +/// A disabled effect returns `color` unchanged. +pub fn background_chain(color: KdeColor, effect: &StateEffect) -> KdeColor { + if !effect.enabled { + return color; + } + let intensified = match effect.intensity_effect { + IntensityEffect::None => color, + IntensityEffect::Shade => shade(color, effect.intensity_amount, 0.0), + IntensityEffect::Darken => darken(color, effect.intensity_amount, 1.0), + IntensityEffect::Lighten => lighten(color, effect.intensity_amount, 1.0), + }; + match effect.color_effect { + ColorEffect::None => intensified, + ColorEffect::Desaturate => darken(intensified, 0.0, 1.0 - effect.color_amount), + ColorEffect::Fade => mix(intensified, effect.color, effect.color_amount), + ColorEffect::Tint => tint(intensified, effect.color, effect.color_amount), + } +} + +/// The effects a foreground or a decoration drawn on `background` gets: the +/// contrast change toward `background` (fade mixes, tint tints, each by +/// `contrast_amount`), then [`background_chain`] on its result. +/// +/// A disabled effect returns `color` unchanged. +pub fn foreground_chain(color: KdeColor, background: KdeColor, effect: &StateEffect) -> KdeColor { + if !effect.enabled { + return color; + } + let contrasted = match effect.contrast_effect { + ContrastEffect::None => color, + ContrastEffect::Fade => mix(color, background, effect.contrast_amount), + ContrastEffect::Tint => tint(color, background, effect.contrast_amount), + }; + background_chain(contrasted, effect) +} + +/// A set's twelve colours after `effect`. +/// +/// The eight foregrounds and two decorations each get [`foreground_chain`] +/// against the set's `BackgroundNormal` as given (before any effect); the +/// two backgrounds each get [`background_chain`]. A disabled effect returns +/// the set unchanged. +pub fn apply_state_effect(colors: &SetColors, effect: &StateEffect) -> SetColors { + if !effect.enabled { + return *colors; + } + let background = colors.background_normal; + let mut out = *colors; + for role in ColorRole::ALL { + let color = colors.get(role); + let changed = match role { + ColorRole::BackgroundNormal | ColorRole::BackgroundAlternate => { + background_chain(color, effect) + } + _ => foreground_chain(color, background, effect), + }; + out.set(role, changed); + } + out +} + +/// The Selection set of an inactive window when `ChangeSelectionColor` is +/// on: `window` with its two backgrounds tinted toward +/// `selection_background` by [`SELECTION_TINT_AMOUNT`], its foregrounds and +/// decorations kept. +pub fn tint_toward_selection(window: &SetColors, selection_background: KdeColor) -> SetColors { + let mut out = *window; + out.background_normal = tint( + window.background_normal, + selection_background, + SELECTION_TINT_AMOUNT, + ); + out.background_alternate = tint( + window.background_alternate, + selection_background, + SELECTION_TINT_AMOUNT, + ); + out +} + +impl KdeColorScheme { + /// The twelve colours of `set` in `state`, after the state's effects + /// and before the computed backgrounds. + /// + /// - Active: the stored set, unchanged. + /// - Inactive: the set's `[Inactive]` subgroup when it has one, else the + /// stored set, then the Inactive effects. The Selection set with + /// [`KdeColorScheme::change_selection_color`] on is instead the Window + /// set (its `[Inactive]` subgroup when present) passed through + /// [`tint_toward_selection`] with the stored Selection + /// `BackgroundNormal`, then the Inactive effects. + /// - Disabled: the stored set, the Selection set being the stored + /// Window set (with no tint), then the Disabled effects. + pub fn state_colors(&self, state: ColorState, set: ColorSet) -> SetColors { + match state { + ColorState::Active => *self.set(set), + ColorState::Inactive => { + let source = if set == ColorSet::Selection && self.change_selection_color { + let window = self + .inactive_set(ColorSet::Window) + .unwrap_or_else(|| self.set(ColorSet::Window)); + tint_toward_selection(window, self.set(ColorSet::Selection).background_normal) + } else { + *self.inactive_set(set).unwrap_or_else(|| self.set(set)) + }; + apply_state_effect(&source, &self.inactive_effect) + } + ColorState::Disabled => { + let source = if set == ColorSet::Selection { + self.set(ColorSet::Window) + } else { + self.set(set) + }; + apply_state_effect(source, &self.disabled_effect) + } + } + } +} diff --git a/crates/teksilo-kde-scheme/src/fonts.rs b/crates/teksilo-kde-scheme/src/fonts.rs new file mode 100644 index 000000000..f95d61205 --- /dev/null +++ b/crates/teksilo-kde-scheme/src/fonts.rs @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Fonts as KDE stores them: Qt's comma-separated font description. +//! +//! The fields, in order: family, point size, pixel size, style hint, +//! weight, style, underline, strike out, fixed pitch, a field that is +//! always 0, then either a style name (the Qt 5 form) or capitalisation, +//! letter-spacing type, letter spacing, word spacing, stretch, style +//! strategy and an optional style name (the Qt 6 form). +//! +//! [`KdeFont::parse_qfont`] accepts 2 fields (family and point size), 10 +//! or 11 (Qt 5) and 16 or 17 (Qt 6). Newer Qt versions append further +//! fields; only the first 17 are read. The weight is on the OpenType scale +//! (1 to 1000) in the Qt 6 form and on Qt 5's legacy 0 to 99 scale in the +//! Qt 5 form, which is mapped to the nearest named OpenType weight. + +/// A font's slant (field 5). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum FontStyle { + /// Upright, 0 (and any value without a meaning). + #[default] + Normal, + /// Italic, 1. + Italic, + /// Oblique, 2. + Oblique, +} + +/// The style hint Qt stores for a monospace font. +pub const STYLE_HINT_MONOSPACE: i32 = 7; + +/// The style hint Qt stores when no hint was asked for. +pub const STYLE_HINT_ANY: i32 = 5; + +/// The OpenType weight of normal text. +pub const WEIGHT_NORMAL: u16 = 400; + +/// A font read from a KDE configuration key. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct KdeFont { + /// The family name. + pub family: String, + /// The size in points, when set (a stored value at or below zero means + /// unset). + pub point_size: Option, + /// The size in pixels, when set (a stored value at or below zero means + /// unset). + pub pixel_size: Option, + /// Qt's style hint ([`STYLE_HINT_ANY`], [`STYLE_HINT_MONOSPACE`], ...). + pub style_hint: i32, + /// The OpenType weight, 1 to 1000 (400 is normal, 700 bold). + pub weight: u16, + /// The slant. + pub style: FontStyle, + /// Underlined. + pub underline: bool, + /// Struck out. + pub strike_out: bool, + /// Fixed pitch. + pub fixed_pitch: bool, + /// Qt's capitalisation mode (0 when absent). + pub capitalization: i32, + /// Qt's letter-spacing type (0 when absent). + pub letter_spacing_type: i32, + /// The letter spacing (0 when absent). + pub letter_spacing: f64, + /// The word spacing (0 when absent). + pub word_spacing: f64, + /// The stretch (0, "any", when absent). + pub stretch: i32, + /// Qt's style strategy (1, the default, when absent). + pub style_strategy: i32, + /// The style name, such as `Regular` or `Bold Italic`, when stored. + pub style_name: Option, +} + +/// The legacy (Qt 5) weights of the named weights, with their OpenType +/// values, lightest first. +const LEGACY_WEIGHTS: [(i32, u16); 9] = [ + (0, 100), + (12, 200), + (25, 300), + (50, 400), + (57, 500), + (63, 600), + (75, 700), + (81, 800), + (87, 900), +]; + +impl KdeFont { + /// A regular, upright font of `family` at `point_size` points, every + /// other field at its default. + pub fn new(family: impl Into, point_size: f64) -> KdeFont { + KdeFont { + family: family.into(), + point_size: Some(point_size), + pixel_size: None, + style_hint: STYLE_HINT_ANY, + weight: WEIGHT_NORMAL, + style: FontStyle::Normal, + underline: false, + strike_out: false, + fixed_pitch: false, + capitalization: 0, + letter_spacing_type: 0, + letter_spacing: 0.0, + word_spacing: 0.0, + stretch: 0, + style_strategy: 1, + style_name: None, + } + } + + /// Parses Qt's font description. Returns `None` for a field count + /// other than 2, 10, 11, 16, 17 or more, or when a numeric field does + /// not parse. + pub fn parse_qfont(text: &str) -> Option { + let fields: Vec<&str> = text.split(',').collect(); + let count = fields.len(); + let fields = &fields[..count.min(17)]; + let qt6 = match fields.len() { + 2 => None, + 10 | 11 => Some(false), + 16 | 17 => Some(true), + _ => return None, + }; + + let mut font = KdeFont::new(fields[0], 0.0); + font.point_size = Some(parse_f64(fields[1])?).filter(|&p| p > 0.0); + let Some(qt6) = qt6 else { + return Some(font); + }; + + font.pixel_size = Some(parse_i32(fields[2])?).filter(|&p| p > 0); + font.style_hint = parse_i32(fields[3])?; + let weight = parse_i32(fields[4])?; + font.weight = if qt6 { + weight.clamp(1, 1000) as u16 + } else { + legacy_weight(weight) + }; + font.style = match parse_i32(fields[5])? { + 1 => FontStyle::Italic, + 2 => FontStyle::Oblique, + _ => FontStyle::Normal, + }; + font.underline = parse_i32(fields[6])? != 0; + font.strike_out = parse_i32(fields[7])? != 0; + font.fixed_pitch = parse_i32(fields[8])? != 0; + parse_i32(fields[9])?; + + let style_name_field = if qt6 { + font.capitalization = parse_i32(fields[10])?; + font.letter_spacing_type = parse_i32(fields[11])?; + font.letter_spacing = parse_f64(fields[12])?; + font.word_spacing = parse_f64(fields[13])?; + font.stretch = parse_i32(fields[14])?; + font.style_strategy = parse_i32(fields[15])?; + fields.get(16) + } else { + fields.get(10) + }; + font.style_name = style_name_field + .filter(|name| !name.is_empty()) + .map(|name| (*name).to_owned()); + Some(font) + } + + /// The size in logical pixels: the pixel size when one is set, + /// otherwise the point size at 96 logical pixels per 72 points. `None` + /// when neither is set. + pub fn size_px(&self) -> Option { + match (self.pixel_size, self.point_size) { + (Some(px), _) => Some(f64::from(px)), + (None, Some(pt)) => Some(pt * 96.0 / 72.0), + (None, None) => None, + } + } +} + +/// Maps a Qt 5 legacy weight to the OpenType weight of the nearest named +/// weight, a tie going to the lighter one. Any `i32` is accepted: the +/// distances are taken in `i64`, so a weight a user wrote near `i32::MIN` +/// or `i32::MAX` maps to the lightest or the heaviest weight instead of +/// overflowing. +pub fn legacy_weight(weight: i32) -> u16 { + let distance = |named: i32| (i64::from(weight) - i64::from(named)).abs(); + let mut best = LEGACY_WEIGHTS[0]; + for candidate in LEGACY_WEIGHTS { + if distance(candidate.0) < distance(best.0) { + best = candidate; + } + } + best.1 +} + +fn parse_i32(field: &str) -> Option { + field.trim().parse().ok() +} + +fn parse_f64(field: &str) -> Option { + field.trim().parse::().ok().filter(|v| v.is_finite()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn legacy_weights_go_to_the_nearest_named_weight_and_ties_to_the_lighter() { + assert_eq!(legacy_weight(50), 400); + assert_eq!(legacy_weight(60), 500); + assert_eq!(legacy_weight(70), 700); + assert_eq!(legacy_weight(0), 100); + assert_eq!(legacy_weight(99), 900); + assert_eq!(legacy_weight(-5), 100); + // 6 is halfway between 0 and 12. + assert_eq!(legacy_weight(6), 100); + assert_eq!(legacy_weight(7), 200); + } + + #[test] + fn legacy_weights_at_the_ends_of_the_integer_range_do_not_overflow() { + assert_eq!(legacy_weight(i32::MIN), 100); + assert_eq!(legacy_weight(i32::MIN + 1), 100); + assert_eq!(legacy_weight(i32::MAX), 900); + assert_eq!(legacy_weight(i32::MAX - 1), 900); + } +} diff --git a/crates/teksilo-kde-scheme/src/lib.rs b/crates/teksilo-kde-scheme/src/lib.rs new file mode 100644 index 000000000..fdcca3f57 --- /dev/null +++ b/crates/teksilo-kde-scheme/src/lib.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Reading KDE Plasma's colour schemes and desktop settings. +//! +//! This crate understands the files a KDE Plasma desktop keeps its look +//! in: the configuration text format and its system-to-user cascade +//! ([`config`]), where those files live ([`locations`]), the colour values +//! they store ([`color`]), the colour-scheme model of a `.colors` file or of +//! `kdeglobals` ([`scheme`]), Qt's font descriptions ([`fonts`]) and the +//! desktop settings built from all of them ([`desktop`]). [`math`] holds +//! the colour arithmetic KDE derives its palettes with, reproduced to the +//! byte KDE writes; [`effects`] the Disabled and Inactive state effects +//! built on it, and [`palette`] the resolved palette of every state and set +//! ([`KdeColorScheme::resolve`]), the light or dark decision +//! ([`KdeColorScheme::is_dark`]) and the colour report Teksilo's native +//! theme reads ([`KdeColorScheme::os_theme_colors`]). [`accent`] applies +//! an accent colour to a scheme the way Plasma's colour settings do +//! ([`KdeColorScheme::with_accent`]) and lists Plasma's ten accent +//! swatches ([`ACCENT_SWATCHES`]). +//! +//! It does no I/O. The environment is passed in as a getter and files are +//! passed in as text, so everything here runs the same in a test as on a +//! desktop: +//! +//! ``` +//! use teksilo_kde_scheme::{ColorState, KConfig, KdeColorScheme, KdeConfigLocations, PaletteRole}; +//! +//! let env = |name: &str| match name { +//! "HOME" => Some("/home/ada".to_owned()), +//! "XDG_CURRENT_DESKTOP" => Some("KDE".to_owned()), +//! _ => None, +//! }; +//! let locations = KdeConfigLocations::from_env(env); +//! // The caller reads each existing file of the cascade. +//! let layers: Vec = locations +//! .kdeglobals_cascade() +//! .into_iter() +//! .filter(|layer| layer.path.ends_with("kdedefaults/kdeglobals")) +//! .map(|layer| KConfig::parse("[General]\nColorScheme=BreezeDark\n").0.with_kind(layer.kind)) +//! .collect(); +//! let globals = KConfig::cascade(&layers); +//! assert_eq!(globals.get(&["General"], "ColorScheme"), Some("BreezeDark")); +//! +//! let scheme = KdeColorScheme::breeze_dark(); +//! assert_eq!(scheme.contrast, 4); +//! assert!(scheme.is_dark()); +//! +//! let palette = scheme.resolve(); +//! let window = palette.role(ColorState::Active, PaletteRole::Window); +//! assert_eq!(window.to_rgba8(), [32, 35, 38, 255]); +//! ``` +//! +//! The behaviour is implemented from Teksilo's written specification of +//! KDE's formats (`engineering/docs/kde-breeze-spec.md` in the Teksilo +//! repository), not from KDE's code. The stock Breeze `.colors` files the +//! tests compare against are vendored as test fixtures only and are not +//! part of the published crate. + +pub mod accent; +pub mod color; +pub mod config; +pub mod desktop; +pub mod effects; +pub mod fonts; +pub mod locations; +pub mod math; +pub mod palette; +pub mod scheme; + +pub use accent::ACCENT_SWATCHES; +pub use color::KdeColor; +pub use config::{DiagnosticKind, Entry, ExpansionEnv, KConfig, LayerKind, ParseDiagnostic}; +pub use desktop::{ + BreezeSettings, KdeDesktop, KdeDesktopLayers, KdeFonts, MnemonicsMode, SchemeOrigin, + ScrollBarButtons, ShadowSize, +}; +pub use fonts::{FontStyle, KdeFont}; +pub use locations::{ConfigLayerPath, KdeConfigLocations, is_kde_session}; +pub use math::{FloatColor, Hcy, ShadeRole}; +pub use palette::{ + BackgroundRole, DecorationRole, ForegroundRole, PaletteRole, ResolvedPalette, ResolvedSet, +}; +pub use scheme::{ + ColorEffect, ColorRole, ColorSet, ColorState, ContrastEffect, IntensityEffect, KdeColorScheme, + SetColors, StateEffect, WmColors, +}; diff --git a/crates/teksilo-kde-scheme/src/locations.rs b/crates/teksilo-kde-scheme/src/locations.rs new file mode 100644 index 000000000..80b5d508a --- /dev/null +++ b/crates/teksilo-kde-scheme/src/locations.rs @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Where KDE keeps its configuration and colour schemes. +//! +//! Everything here is a pure function of an environment getter: nothing +//! reads the process environment and nothing touches the file system. The +//! caller decides which candidate files exist and reads them. + +use std::path::{Path, PathBuf}; + +use crate::config::{ExpansionEnv, KConfig, LayerKind}; + +/// One file of a cascade, with the kind of layer it is. +/// +/// A cascade lists its layers lowest priority first; only files that exist +/// count, and the caller skips the others. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConfigLayerPath { + /// The file. + pub path: PathBuf, + /// How the layer takes part in the cascade. + pub kind: LayerKind, +} + +/// The configuration and data directories KDE reads, resolved from the XDG +/// environment. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct KdeConfigLocations { + /// `$HOME`, when set to an absolute path. + pub home: Option, + /// `$XDG_CONFIG_HOME`, default `$HOME/.config`. + pub config_home: Option, + /// `$XDG_CONFIG_DIRS` in the order listed (default `/etc/xdg`), with + /// `$XDG_CONFIG_HOME/kdedefaults` prepended in a KDE session when it is + /// not already listed. + pub config_dirs: Vec, + /// `$XDG_DATA_HOME`, default `$HOME/.local/share`. + pub data_home: Option, + /// `$XDG_DATA_DIRS` in the order listed, default `/usr/local/share` + /// then `/usr/share`. + pub data_dirs: Vec, + /// `$XDG_CACHE_HOME`, default `$HOME/.cache`. + pub cache_home: Option, + /// Whether the session is a KDE session ([`is_kde_session`]). + pub kde_session: bool, + /// Whether `KDE_SKIP_KDERC` is set, which leaves `/etc/kde5rc` out of + /// the `kdeglobals` cascade. + pub skip_kderc: bool, +} + +/// Whether the environment describes a KDE session: `XDG_CURRENT_DESKTOP`, +/// split at `:`, contains `KDE` (ignoring ASCII case), or `KDE_FULL_SESSION` +/// is `true`. +pub fn is_kde_session(get: impl Fn(&str) -> Option) -> bool { + let desktop = get("XDG_CURRENT_DESKTOP") + .is_some_and(|v| v.split(':').any(|d| d.eq_ignore_ascii_case("KDE"))); + desktop || get("KDE_FULL_SESSION").as_deref() == Some("true") +} + +impl KdeConfigLocations { + /// Resolves the locations from `get`, which returns an environment + /// variable's value (`None` when unset). + /// + /// An empty or relative home variable is ignored in favour of its + /// default; empty and relative entries of a directory list are dropped, + /// and a list left empty takes its default. + pub fn from_env(get: impl Fn(&str) -> Option) -> KdeConfigLocations { + let home = get("HOME").filter(|v| is_absolute(v)); + // Joined with `/`, not `Path::join`: these are POSIX paths on every + // host, and on Windows `join` would insert a `\` that then reaches + // `$QT_DATA_HOME` and the other `[$e]` defaults. + let under_home = |rel: &str| { + home.as_ref() + .map(|h| PathBuf::from(format!("{}/{rel}", h.trim_end_matches('/')))) + }; + let config_home = absolute(get("XDG_CONFIG_HOME")).or_else(|| under_home(".config")); + let data_home = absolute(get("XDG_DATA_HOME")).or_else(|| under_home(".local/share")); + let cache_home = absolute(get("XDG_CACHE_HOME")).or_else(|| under_home(".cache")); + let kde_session = is_kde_session(&get); + + let mut config_dirs = dir_list(get("XDG_CONFIG_DIRS"), &["/etc/xdg"]); + if kde_session && let Some(home) = &config_home { + let kdedefaults = home.join("kdedefaults"); + if !config_dirs.contains(&kdedefaults) { + config_dirs.insert(0, kdedefaults); + } + } + let data_dirs = dir_list(get("XDG_DATA_DIRS"), &["/usr/local/share", "/usr/share"]); + + KdeConfigLocations { + home: home.map(PathBuf::from), + config_home, + config_dirs, + data_home, + data_dirs, + cache_home, + kde_session, + skip_kderc: get("KDE_SKIP_KDERC").is_some(), + } + } + + /// The `kdeglobals` cascade, lowest priority first: + /// + /// 1. `/etc/kde5rc`, unless `KDE_SKIP_KDERC` is set; + /// 2. `system.kdeglobals` in each configuration directory, the last + /// listed first; + /// 3. `kdeglobals` in each configuration directory, the last listed + /// first (so `kdedefaults`, listed first in a KDE session, is the + /// highest system layer); + /// 4. the user's `system.kdeglobals`, a default layer; + /// 5. the user's `kdeglobals`, the user layer. + pub fn kdeglobals_cascade(&self) -> Vec { + let mut layers = Vec::new(); + if !self.skip_kderc { + layers.push(default_layer(PathBuf::from("/etc/kde5rc"))); + } + for name in ["system.kdeglobals", "kdeglobals"] { + for dir in self.config_dirs.iter().rev() { + layers.push(default_layer(dir.join(name))); + } + } + if let Some(home) = &self.config_home { + layers.push(default_layer(home.join("system.kdeglobals"))); + layers.push(ConfigLayerPath { + path: home.join("kdeglobals"), + kind: LayerKind::User, + }); + } + layers + } + + /// The `breezerc` cascade, lowest priority first: `breezerc` in each + /// configuration directory, the last listed first, then the user's + /// `breezerc` as the user layer. + pub fn breezerc_cascade(&self) -> Vec { + let mut layers: Vec = self + .config_dirs + .iter() + .rev() + .map(|dir| default_layer(dir.join("breezerc"))) + .collect(); + if let Some(home) = &self.config_home { + layers.push(ConfigLayerPath { + path: home.join("breezerc"), + kind: LayerKind::User, + }); + } + layers + } + + /// The candidates for a relative data path, in order of preference: + /// the data home, then each data directory as listed. The first one + /// that exists is the file. + pub fn data_file_candidates(&self, relative: impl AsRef) -> Vec { + let relative = relative.as_ref(); + self.data_home + .iter() + .chain(&self.data_dirs) + .map(|dir| dir.join(relative)) + .collect() + } + + /// The candidates for the colour scheme named `name` + /// (`color-schemes/.colors`), in order of preference. + pub fn color_scheme_file(&self, name: &str) -> Vec { + self.data_file_candidates(format!("color-schemes/{name}.colors")) + } + + /// The candidate files a palette is read from when `kdeglobals` does + /// not carry the colours itself, in order of preference; `None` when + /// the merged `kdeglobals` has a `[Colors:View]` group, which means it + /// does. + /// + /// The candidates are, each through [`Self::data_file_candidates`]: + /// the colours of the look-and-feel package (`[KDE] + /// LookAndFeelPackage`, default `org.kde.breeze.desktop`), the scheme + /// named by `[General] ColorScheme` (default `BreezeLight`), then + /// `BreezeLight`. The first that exists is read with the `kdeglobals` + /// cascade below it. + pub fn scheme_fallback_candidates( + &self, + kdeglobals: &KConfig, + env: &ExpansionEnv<'_>, + ) -> Option> { + if kdeglobals.group_exists(&["Colors:View"]) { + return None; + } + let package = kdeglobals + .read_string(&["KDE"], "LookAndFeelPackage", env) + .unwrap_or_else(|| "org.kde.breeze.desktop".to_owned()); + let scheme = kdeglobals + .read_string(&["General"], "ColorScheme", env) + .unwrap_or_else(|| "BreezeLight".to_owned()); + let mut candidates = + self.data_file_candidates(format!("plasma/look-and-feel/{package}/contents/colors")); + candidates.extend(self.color_scheme_file(&scheme)); + candidates.extend(self.color_scheme_file("BreezeLight")); + Some(candidates) + } + + /// The environment `[$e]` entries expand in: variables come from `var`, + /// and an unset `QT_DATA_HOME`, `QT_CONFIG_HOME` or `QT_CACHE_HOME` + /// reads as the matching XDG home directory. + pub fn expansion_env<'a>(&self, var: &'a dyn Fn(&str) -> Option) -> ExpansionEnv<'a> { + let text = |p: &Option| { + p.as_ref() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_default() + }; + ExpansionEnv::new( + var, + text(&self.data_home), + text(&self.config_home), + text(&self.cache_home), + ) + } +} + +fn default_layer(path: PathBuf) -> ConfigLayerPath { + ConfigLayerPath { + path, + kind: LayerKind::Default, + } +} + +/// XDG paths are POSIX paths and must be absolute: a value that does not +/// start with `/` is ignored, on every host. +fn is_absolute(value: &str) -> bool { + value.starts_with('/') +} + +fn absolute(value: Option) -> Option { + value.filter(|v| is_absolute(v)).map(PathBuf::from) +} + +fn dir_list(value: Option, default: &[&str]) -> Vec { + let dirs: Vec = value + .as_deref() + .unwrap_or_default() + .split(':') + .filter(|d| is_absolute(d)) + .map(PathBuf::from) + .collect(); + if dirs.is_empty() { + default.iter().map(PathBuf::from).collect() + } else { + dirs + } +} diff --git a/crates/teksilo-kde-scheme/src/math.rs b/crates/teksilo-kde-scheme/src/math.rs new file mode 100644 index 000000000..910f87b0b --- /dev/null +++ b/crates/teksilo-kde-scheme/src/math.rs @@ -0,0 +1,1074 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The colour arithmetic KDE Plasma derives its palettes with. +//! +//! Every operation takes and returns [`KdeColor`]s, four 16-bit channels, +//! and follows the precision rules of section a.3 of Teksilo's +//! specification (`engineering/docs/kde-breeze-spec.md`) so that a result +//! reduced to 8 bits is the byte KDE itself writes: +//! +//! - a channel `q` is read as the single-precision quotient `q / 65535`, +//! then widened to `f64` ([`read_channel`]); +//! - a real result `v` is stored by rounding it to single precision, +//! forming the single-precision product `v x 65535`, and rounding that +//! half up ([`store_channel`]); +//! - everything in between is `f64`, evaluated in the order the +//! specification writes it, except where a step says single precision. +//! +//! The operations, in the specification's order (section d): +//! +//! | Operation | Function | +//! |---|---| +//! | HCY colour space (gamma 2.2, Rec. 709 weights) | [`Hcy::from_color`], [`Hcy::to_color`] | +//! | luma, and the luma contrast ratio | [`luma`], [`luma_ratio`], [`color_luma_ratio`] | +//! | lighten, darken, shade in HCY | [`lighten`], [`darken`], [`shade`] | +//! | premultiplied mix | [`mix`] | +//! | luma-preserving tint | [`tint`] | +//! | the five shade roles of a palette | [`shade_role`] | +//! | Qt's HSV lighter and darker | [`hsv_lighter`], [`hsv_darker`] | +//! | the 8-bit alpha blend | [`blend8`] | +//! | black or white on a background | [`black_or_white_on`] | +//! | an accent laid over a background | [`accent_over`] | +//! | the OKLab tint, and the sRGB curve under it | [`oklab_tint`], [`srgb_to_linear`], [`linear_to_srgb`] | +//! +//! The luma contrast ratio is KDE's own measure, built on a pure 2.2 power; +//! it is **not** WCAG contrast and must never be used to check +//! readability. [`lift_luma_until`] is Teksilo's helper for that: it keeps a +//! colour's HCY hue and chroma and moves its luma until the WCAG 2.x ratio +//! reaches a floor, measured with [`teksilo_tokens::contrast`], and +//! [`lift_luma_toward`] does the same in a direction the caller gives. + +use teksilo_tokens::contrast::composite_contrast; + +use crate::color::KdeColor; + +/// The red weight of the luma, ITU-R BT.709. +pub const LUMA_RED: f64 = 0.2126; +/// The green weight of the luma, ITU-R BT.709. +pub const LUMA_GREEN: f64 = 0.7152; +/// The blue weight of the luma, ITU-R BT.709. +pub const LUMA_BLUE: f64 = 0.0722; +/// The gamma exponent the HCY space linearises channels with. +pub const GAMMA: f64 = 2.2; + +// --------------------------------------------------------------------------- +// Channel conventions (section a.3) +// --------------------------------------------------------------------------- + +/// The real value of a stored channel: `q / 65535` in single precision, +/// widened to `f64`. +pub fn read_channel(q: u16) -> f64 { + f64::from(f32::from(q) / 65535.0_f32) +} + +/// Stores a real channel value in `0..=1` as a 16-bit channel. +/// +/// The value is rounded to single precision, multiplied by 65535 in single +/// precision, and the product rounded half up. A value below 0 stores 0 +/// and one above 1 stores 65535; a NaN stores 0. (Every operation of this +/// module clamps before it stores, so the last two cases are only reached +/// through a direct call.) +pub fn store_channel(v: f64) -> u16 { + let product = (v as f32) * 65535.0_f32; + let rounded = (f64::from(product) + 0.5).floor(); + if rounded.is_nan() || rounded <= 0.0 { + 0 + } else if rounded >= 65535.0 { + u16::MAX + } else { + rounded as u16 + } +} + +/// `N(x)` of the specification: `x` inside `(0, 1)`, 0 at or below 0, and 1 +/// at or above 1 **or for a NaN**. +fn unit(x: f64) -> f64 { + if x > 0.0 && x < 1.0 { + x + } else if x <= 0.0 { + 0.0 + } else { + 1.0 + } +} + +/// `L(a, b, t) = a + (b - a) t`, in this form. +fn lerp(a: f64, b: f64, t: f64) -> f64 { + a + (b - a) * t +} + +/// A channel read made linear: `N(x)^2.2`. +fn linearise(x: f64) -> f64 { + unit(x).powf(GAMMA) +} + +/// A linear value back to a channel: `N(x)^(1/2.2)`. +fn delinearise(x: f64) -> f64 { + unit(x).powf(1.0 / GAMMA) +} + +fn reads(c: KdeColor) -> [f64; 4] { + [ + read_channel(c.r), + read_channel(c.g), + read_channel(c.b), + read_channel(c.a), + ] +} + +// --------------------------------------------------------------------------- +// HCY and luma (sections d.2 to d.5) +// --------------------------------------------------------------------------- + +/// A colour in KDE's HCY space: hue, chroma, luma and alpha, each a real +/// number. +/// +/// The luma `y` is the Rec. 709 weighted sum of the gamma-2.2 linear +/// channels ([`luma`]). The hue `h` is in turns; [`Hcy::from_color`] does +/// **not** wrap it, so a colour whose red channel is the largest and whose +/// blue exceeds its green has a hue in `(-1/6, 0)`. [`Hcy::to_color`] +/// wraps any hue, and [`Hcy::wrapped_hue`] gives it in `[0, 1)`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Hcy { + /// Hue in turns, possibly negative (see the type documentation). + pub h: f64, + /// Chroma, 0 for a grey and 1 at the edge of the gamut. + pub c: f64, + /// Luma, 0 for black and 1 for white. + pub y: f64, + /// Alpha, the channel read of the colour's alpha. + pub a: f64, +} + +impl Hcy { + /// Converts a stored colour to HCY. + pub fn from_color(color: KdeColor) -> Hcy { + let [rr, gr, br, a] = reads(color); + let r = linearise(rr); + let g = linearise(gr); + let b = linearise(br); + let y = r * LUMA_RED + g * LUMA_GREEN + b * LUMA_BLUE; + let p = r.max(g).max(b); + let n = r.min(g).min(b); + let d = 6.0 * (p - n); + let h = if n == p { + 0.0 + } else if r == p { + (g - b) / d + } else if g == p { + (b - r) / d + 1.0 / 3.0 + } else { + (r - g) / d + 2.0 / 3.0 + }; + let c = if r == g && g == b { + 0.0 + } else { + ((y - n) / y).max((p - y) / (1.0 - y)) + }; + Hcy { h, c, y, a } + } + + /// The hue in `[0, 1)`: [`Hcy::h`] plus 1 when it is negative. + pub fn wrapped_hue(self) -> f64 { + if self.h < 0.0 { self.h + 1.0 } else { self.h } + } + + /// Converts back to a stored colour. + /// + /// The hue is wrapped into `[0, 1)` and the chroma and luma clamped to + /// `0..=1`; each linear component is clamped, raised to `1/2.2` and + /// stored, and so is the alpha. Converting an 8-bit colour to HCY and + /// back gives the same colour. + pub fn to_color(self) -> KdeColor { + let h = wrap_hue(self.h); + let c = unit(self.c); + let y = unit(self.y); + let s = 6.0 * h; + let (sector, t, m) = if s < 1.0 { + (0, s, LUMA_RED + LUMA_GREEN * s) + } else if s < 2.0 { + let t = 2.0 - s; + (1, t, LUMA_GREEN + LUMA_RED * t) + } else if s < 3.0 { + let t = s - 2.0; + (2, t, LUMA_GREEN + LUMA_BLUE * t) + } else if s < 4.0 { + let t = 4.0 - s; + (3, t, LUMA_BLUE + LUMA_GREEN * t) + } else if s < 5.0 { + let t = s - 4.0; + (4, t, LUMA_BLUE + LUMA_RED * t) + } else { + let t = 6.0 - s; + (5, t, LUMA_RED + LUMA_BLUE * t) + }; + let (top, middle, bottom) = if m >= y { + ( + y + y * c * (1.0 - m) / m, + y + y * c * (t - m) / m, + y - y * c, + ) + } else { + ( + y + (1.0 - y) * c, + y + (1.0 - y) * c * (t - m) / (1.0 - m), + y - (1.0 - y) * c * m / (1.0 - m), + ) + }; + let (r, g, b) = match sector { + 0 => (top, middle, bottom), + 1 => (middle, top, bottom), + 2 => (bottom, top, middle), + 3 => (bottom, middle, top), + 4 => (middle, bottom, top), + _ => (top, bottom, middle), + }; + KdeColor::rgba16( + store_channel(delinearise(r)), + store_channel(delinearise(g)), + store_channel(delinearise(b)), + store_channel(self.a), + ) + } +} + +/// The remainder of `h` by 1 with `h`'s sign, plus 1 when negative; a zero +/// or NaN remainder gives 0. +fn wrap_hue(h: f64) -> f64 { + let r = h % 1.0; + if r.is_nan() || r == 0.0 { + 0.0 + } else if r < 0.0 { + r + 1.0 + } else { + r + } +} + +/// The luma of a colour, in `0..=1`: the Rec. 709 weighted sum of its +/// channels raised to the power 2.2. Alpha is ignored. +/// +/// This is not WCAG relative luminance, which uses the sRGB transfer curve. +pub fn luma(color: KdeColor) -> f64 { + let [r, g, b, _] = reads(color); + linearise(r) * LUMA_RED + linearise(g) * LUMA_GREEN + linearise(b) * LUMA_BLUE +} + +/// KDE's contrast ratio between two lumas: `(Y1 + 0.05) / (Y2 + 0.05)` with +/// the larger luma on top, so the result is at least 1. +/// +/// It has the shape of WCAG's ratio but is computed on [`luma`], so its +/// values differ from WCAG contrast. Use it only to reproduce KDE's +/// decisions, never to judge readability. +pub fn luma_ratio(y1: f64, y2: f64) -> f64 { + if y1 > y2 { + (y1 + 0.05) / (y2 + 0.05) + } else { + (y2 + 0.05) / (y1 + 0.05) + } +} + +/// [`luma_ratio`] of the lumas of two colours. +pub fn color_luma_ratio(a: KdeColor, b: KdeColor) -> f64 { + luma_ratio(luma(a), luma(b)) +} + +// --------------------------------------------------------------------------- +// Luma operations, mix and tint (sections d.6 to d.9) +// --------------------------------------------------------------------------- + +/// Moves a colour's luma toward white, keeping its hue and alpha. +/// +/// `amount` 1 gives white, 0.5 halves the distance to white and 0 keeps +/// the luma: `y' = 1 - N((1 - y)(1 - amount))`. The chroma becomes +/// `1 - N((1 - c) chroma_factor)`, so the usual factor 1 keeps it. KDE's +/// defaults are `amount` 0.5 and `chroma_factor` 1. +/// +/// The colour is converted through HCY even when nothing changes, so the +/// result may differ from the input by one 16-bit step. +pub fn lighten(color: KdeColor, amount: f64, chroma_factor: f64) -> KdeColor { + let mut hcy = Hcy::from_color(color); + hcy.y = 1.0 - unit((1.0 - hcy.y) * (1.0 - amount)); + hcy.c = 1.0 - unit((1.0 - hcy.c) * chroma_factor); + hcy.to_color() +} + +/// Moves a colour's luma toward black, keeping its hue and alpha. +/// +/// `y' = N(y (1 - amount))` and `c' = N(c chroma_factor)`; KDE's defaults +/// are `amount` 0.5 and `chroma_factor` 1. An `amount` of 0 with a +/// `chroma_factor` below 1 desaturates. +pub fn darken(color: KdeColor, amount: f64, chroma_factor: f64) -> KdeColor { + let mut hcy = Hcy::from_color(color); + hcy.y = unit(hcy.y * (1.0 - amount)); + hcy.c = unit(hcy.c * chroma_factor); + hcy.to_color() +} + +/// Adds `luma_amount` to a colour's luma and `chroma_amount` to its +/// chroma, each clamped to `0..=1`, keeping its hue and alpha. A negative +/// amount darkens or desaturates. +pub fn shade(color: KdeColor, luma_amount: f64, chroma_amount: f64) -> KdeColor { + let mut hcy = Hcy::from_color(color); + hcy.y = unit(hcy.y + luma_amount); + hcy.c = unit(hcy.c + chroma_amount); + hcy.to_color() +} + +/// Interpolates from `a` (at `t` 0) to `b` (at `t` 1) with premultiplied +/// alpha. +/// +/// At or below 0 the result is `a` exactly, at or above 1 it is `b` +/// exactly, and a NaN `t` gives `a`. Otherwise the alpha is interpolated +/// and each channel is interpolated premultiplied (the products in single +/// precision) and divided back by that alpha; a result with no alpha left +/// is transparent black. +pub fn mix(a: KdeColor, b: KdeColor, t: f64) -> KdeColor { + if t <= 0.0 { + return a; + } + if t >= 1.0 { + return b; + } + if t.is_nan() { + return a; + } + let ra = reads(a); + let rb = reads(b); + let alpha = lerp(ra[3], rb[3], t); + if alpha <= 0.0 { + return KdeColor::TRANSPARENT; + } + let channel = |i: usize| { + let pa = f64::from((ra[i] as f32) * (ra[3] as f32)); + let pb = f64::from((rb[i] as f32) * (rb[3] as f32)); + store_channel(lerp(pa, pb, t).clamp(0.0, 1.0) / alpha) + }; + KdeColor::rgba16(channel(0), channel(1), channel(2), store_channel(alpha)) +} + +/// Tints `base` toward `color` by `amount` while keeping `base`'s luma +/// within a bounded contrast of where it was. +/// +/// At or below 0 the result is `base`, at or above 1 it is `color`, and a +/// NaN amount gives `base`. Otherwise the mix factor is found by twelve +/// bisection steps against a luma-ratio budget that grows with the cube of +/// `amount`, and the result is that mix with its luma pulled back toward +/// `base`'s. KDE's default amount is 0.3. +pub fn tint(base: KdeColor, color: KdeColor, amount: f64) -> KdeColor { + if amount <= 0.0 { + return base; + } + if amount >= 1.0 { + return color; + } + if amount.is_nan() { + return base; + } + let base_luma = luma(base); + let initial_ratio = luma_ratio(base_luma, luma(color)); + let budget = 1.0 + (initial_ratio + 1.0) * amount * amount * amount; + let mut low = 0.0_f64; + let mut high = 1.0_f64; + let mut s = 0.5; + for _ in 0..12 { + s = (low + high) / 2.0; + let mixed = mix(base, color, s.powf(0.3)); + let mixed_luma = lerp(base_luma, luma(mixed), s); + if luma_ratio(base_luma, mixed_luma) > budget { + high = s; + } else { + low = s; + } + } + let mut hcy = Hcy::from_color(mix(base, color, s.powf(0.3))); + hcy.y = lerp(base_luma, hcy.y, s); + hcy.to_color() +} + +// --------------------------------------------------------------------------- +// Shade roles (section d.10) +// --------------------------------------------------------------------------- + +/// The five 3D shades a palette derives from its window background. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ShadeRole { + /// The lightest shade, a bevel's lit edge. + Light, + /// Between the background and [`ShadeRole::Light`]. + Midlight, + /// Between the background and [`ShadeRole::Dark`]. + Mid, + /// A bevel's shadowed edge. + Dark, + /// The darkest shade, a drop shadow. + Shadow, +} + +impl ShadeRole { + /// The five roles, lightest first. + pub const ALL: [ShadeRole; 5] = [ + ShadeRole::Light, + ShadeRole::Midlight, + ShadeRole::Mid, + ShadeRole::Dark, + ShadeRole::Shadow, + ]; +} + +/// The shade of `color` for `role`, at contrast value `contrast`. +/// +/// `contrast` is the scheme's `[KDE] contrast` divided by 10 (0.4 for +/// Breeze), clamped to `-1..=1` (a NaN counts as 1). Very dark colours +/// (luma below 0.006) shade upward and very light ones (above 0.93) +/// downward; anything between gets a lighter Light and Midlight and a +/// darker Mid, Dark and Shadow, scaled by its luma. `chroma_amount` is +/// added to the chroma of every shade (KDE's default is 0). +pub fn shade_role(color: KdeColor, role: ShadeRole, contrast: f64, chroma_amount: f64) -> KdeColor { + let k = if contrast >= 1.0 || contrast.is_nan() { + 1.0 + } else if contrast <= -1.0 { + -1.0 + } else { + contrast + }; + let y = luma(color); + let yi = 1.0 - y; + let shaded = |amount: f64| shade(color, amount, chroma_amount); + if y < 0.006 { + match role { + ShadeRole::Light => shaded(0.05 + 0.95 * k), + ShadeRole::Mid => shaded(0.01 + 0.20 * k), + ShadeRole::Dark => shaded(0.02 + 0.40 * k), + ShadeRole::Midlight | ShadeRole::Shadow => shaded(0.03 + 0.60 * k), + } + } else if y > 0.93 { + match role { + ShadeRole::Midlight => shaded(-0.02 - 0.20 * k), + ShadeRole::Dark => shaded(-0.06 - 0.60 * k), + ShadeRole::Shadow => shaded(-0.10 - 0.90 * k), + ShadeRole::Light | ShadeRole::Mid => shaded(-0.04 - 0.40 * k), + } + } else { + let light_amount = (0.05 + 0.55 * y) * (0.25 + 0.75 * k); + let dark_amount = (-y) * (0.55 + 0.35 * k); + match role { + ShadeRole::Light => shaded(light_amount), + ShadeRole::Midlight => shaded((0.15 + 0.35 * yi) * light_amount), + ShadeRole::Mid => shaded((0.35 + 0.15 * y) * dark_amount), + ShadeRole::Dark => shaded(dark_amount), + ShadeRole::Shadow => darken(shaded(dark_amount), 0.5 + 0.3 * y, 1.0), + } + } +} + +// --------------------------------------------------------------------------- +// HSV lighter and darker (section d.11) +// --------------------------------------------------------------------------- + +/// A colour in Qt's integer HSV form: hue in hundredths of a degree +/// (`None` for a grey), saturation and value in `0..=65535`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Hsv16 { + hue: Option, + saturation: u32, + value: u32, +} + +/// `R(x) = floor(x + 1/2)` of a non-negative single-precision value. +fn round_half_up(x: f32) -> u32 { + (f64::from(x) + 0.5).floor() as u32 +} + +fn to_hsv16(color: KdeColor) -> Hsv16 { + let (r, g, b) = (u32::from(color.r), u32::from(color.g), u32::from(color.b)); + let max = r.max(g).max(b); + let min = r.min(g).min(b); + if max == min { + return Hsv16 { + hue: None, + saturation: 0, + value: max, + }; + } + let k = (max - min) as f32; + let saturation = round_half_up((k / max as f32) * 65535.0_f32); + let diff = |a: u32, b: u32| (i64::from(a) - i64::from(b)) as f32; + let position = if max == r { + let p = diff(g, b) / k; + if p < 0.0 { p + 6.0_f32 } else { p } + } else if max == g { + 2.0_f32 + diff(b, r) / k + } else { + 4.0_f32 + diff(r, g) / k + }; + Hsv16 { + hue: Some(round_half_up(position * 6000.0_f32)), + saturation, + value: max, + } +} + +fn from_hsv16(hsv: Hsv16, alpha: u16) -> KdeColor { + let v16 = hsv.value as u16; + let Some(hue) = hsv.hue.filter(|_| hsv.saturation != 0) else { + return KdeColor::rgba16(v16, v16, v16, alpha); + }; + let h = if hue == 36000 { + 0.0_f32 + } else { + hue as f32 / 6000.0_f32 + }; + let s = hsv.saturation as f32 / 65535.0_f32; + let v = hsv.value as f32 / 65535.0_f32; + let i = h.trunc(); + let f = h - i; + let p = v * (1.0_f32 - s); + let q = v * (1.0_f32 - s * f); + let t = v * (1.0_f32 - s * (1.0_f32 - f)); + let (r, g, b) = match i as u32 { + 0 => (v, t, p), + 1 => (q, v, p), + 2 => (p, v, t), + 3 => (p, q, v), + 4 => (t, p, v), + _ => (v, p, q), + }; + let store = |x: f32| round_half_up(x * 65535.0_f32).min(65535) as u16; + KdeColor::rgba16(store(r), store(g), store(b), alpha) +} + +/// Qt's documented `lighter(factor)`: the colour with its HSV value +/// multiplied by `factor / 100`. +/// +/// A factor of 0 or less returns the colour unchanged, and a factor below +/// 100 is [`hsv_darker`] by `10000 / factor`. When the value would pass the +/// top of the range, the excess is taken off the saturation instead, so a +/// strong factor moves toward white. The colour goes through Qt's 16-bit +/// HSV form even at factor 100, and keeps its alpha. +pub fn hsv_lighter(color: KdeColor, factor: i32) -> KdeColor { + if factor <= 0 { + return color; + } + if factor < 100 { + return hsv_darker(color, 10000 / factor); + } + let mut hsv = to_hsv16(color); + let value = i64::from(factor) * i64::from(hsv.value) / 100; + if value > 65535 { + let excess = value - 65535; + hsv.saturation = (i64::from(hsv.saturation) - excess).max(0) as u32; + hsv.value = 65535; + } else { + hsv.value = value as u32; + } + from_hsv16(hsv, color.a) +} + +/// Qt's documented `darker(factor)`: the colour with its HSV value divided +/// by `factor / 100`. +/// +/// A factor of 0 or less returns the colour unchanged, and a factor below +/// 100 is [`hsv_lighter`] by `10000 / factor`. The saturation and alpha are +/// kept. +pub fn hsv_darker(color: KdeColor, factor: i32) -> KdeColor { + if factor <= 0 { + return color; + } + if factor < 100 { + return hsv_lighter(color, 10000 / factor); + } + let mut hsv = to_hsv16(color); + hsv.value = (i64::from(hsv.value) * 100 / i64::from(factor)) as u32; + from_hsv16(hsv, color.a) +} + +// --------------------------------------------------------------------------- +// 8-bit blending (sections d.12 to d.14) +// --------------------------------------------------------------------------- + +/// `foreground` composited over `background` the way KDE's accent code +/// does it, in 8-bit channels. +/// +/// A fully transparent foreground returns `background` unchanged. Over an +/// opaque background each channel is `trunc(af f + (1 - af) g)`, with `af` +/// the foreground's alpha, `f` and `g` the 8-bit channels, and the first +/// product in single precision; the result is opaque. Over a translucent +/// background the colour channels are weighted by the background's alpha +/// as well, without un-premultiplying, and the alpha is the integer part +/// of the summed coverages (so 0, or 1 out of 255): KDE writes that nearly +/// transparent value and this function reproduces it. +pub fn blend8(foreground: KdeColor, background: KdeColor) -> KdeColor { + let af = f32::from(foreground.a) / 65535.0_f32; + if af == 0.0 { + return background; + } + let inverse = 1.0 - f64::from(af); + let [fr, fg, fb, _] = foreground.to_rgba8(); + let [gr, gg, gb, _] = background.to_rgba8(); + let background_alpha = read_channel(background.a); + let weight = if background_alpha == 1.0 { + inverse + } else { + background_alpha * inverse + }; + let channel = |f: u8, g: u8| { + let painted = f64::from(af * f32::from(f)); + (painted + weight * f64::from(g)).trunc().clamp(0.0, 255.0) as u8 + }; + let alpha = if background_alpha == 1.0 { + 255 + } else { + (f64::from(af) + weight).trunc().clamp(0.0, 255.0) as u8 + }; + KdeColor::rgba8(channel(fr, gr), channel(fg, gg), channel(fb, gb), alpha) +} + +/// Black or white text for `background`, as KDE picks it for an accent +/// colour. +/// +/// Black when white would reach less than 4.5 in [`luma_ratio`] (KDE's +/// measure, not WCAG's: black exactly when the luma is above about +/// 0.183), white otherwise. An active colour is that colour, opaque; an +/// inactive one is it at alpha 0.6 laid over `background` with +/// [`blend8`]. +pub fn black_or_white_on(background: KdeColor, active: bool) -> KdeColor { + let chosen = if color_luma_ratio(background, KdeColor::WHITE) < 4.5 { + KdeColor::BLACK + } else { + KdeColor::WHITE + }; + let alpha = if active { 1.0 } else { 0.6 }; + blend8(with_alpha(chosen, alpha), background) +} + +/// An accent laid over a background at alpha 0.7 with [`blend8`]: the +/// selection fill KDE derives from an accent. Only the accent's 8-bit +/// channels are used; its own alpha is replaced. +pub fn accent_over(accent: KdeColor, background: KdeColor) -> KdeColor { + let [r, g, b, _] = accent.to_rgba8(); + blend8(with_alpha(KdeColor::rgb8(r, g, b), 0.7), background) +} + +/// `color` with its alpha set to the fraction `alpha`, stored like any +/// channel. +fn with_alpha(color: KdeColor, alpha: f64) -> KdeColor { + KdeColor { + a: store_channel(alpha), + ..color + } +} + +// --------------------------------------------------------------------------- +// sRGB curve and OKLab tint (section d.15) +// --------------------------------------------------------------------------- + +/// A colour held as floating-point channels, the way Qt keeps the result +/// of a colour-space conversion. +/// +/// A result whose three colour channels all lie in `0..=1` is stored in 16 +/// bits like any colour. One with a channel outside that range is held in +/// half precision instead: each channel, alpha included, is rounded to the +/// nearest IEEE 754 binary16 value (ties to even), so it can carry the +/// out-of-gamut value into the next step. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FloatColor { + /// Every colour channel in range: 16-bit storage. + Rgb16(KdeColor), + /// Some colour channel out of range: red, green, blue and alpha, each + /// an exact binary16 value written as an `f32`. + Half([f32; 4]), +} + +impl FloatColor { + /// Stores single-precision channels by the rule of the type + /// documentation. The range test is made on these `f32` values. + pub fn from_f32(rgb: [f32; 3], alpha: f32) -> FloatColor { + if rgb.iter().all(|c| (0.0..=1.0).contains(c)) { + FloatColor::Rgb16(KdeColor::rgba16( + store_channel(f64::from(rgb[0])), + store_channel(f64::from(rgb[1])), + store_channel(f64::from(rgb[2])), + store_channel(f64::from(alpha)), + )) + } else { + FloatColor::Half([ + round_to_half(rgb[0]), + round_to_half(rgb[1]), + round_to_half(rgb[2]), + round_to_half(alpha), + ]) + } + } + + /// The channel reads: `q / 65535` in single precision for a 16-bit + /// colour, the binary16 values for a half-precision one. + pub fn channels(self) -> [f32; 4] { + match self { + FloatColor::Rgb16(c) => [c.r, c.g, c.b, c.a].map(|q| f32::from(q) / 65535.0_f32), + FloatColor::Half(channels) => channels, + } + } + + /// The colour in 16-bit channels: a half-precision colour has each + /// channel clamped to `0..=1` and stored. + pub fn to_rgb16(self) -> KdeColor { + match self { + FloatColor::Rgb16(c) => c, + FloatColor::Half([r, g, b, a]) => { + let store = |x: f32| store_channel(f64::from(x.clamp(0.0, 1.0))); + KdeColor::rgba16(store(r), store(g), store(b), store(a)) + } + } + } +} + +/// Rounds a single-precision value to the nearest binary16 value, ties to +/// even, overflowing to infinity past the largest finite one. +fn round_to_half(x: f32) -> f32 { + if x.is_nan() || x == 0.0 || x.is_infinite() { + return x; + } + let magnitude = f64::from(x.abs()); + // 65520 is halfway between 65504, the largest finite binary16 value, + // and 65536; the tie goes to the even neighbour, which is infinity. + let rounded = if magnitude >= 65520.0 { + f64::INFINITY + } else { + // Below 2^-14 the binary16 values are the subnormals, multiples of + // 2^-24; above, ten fraction bits under the leading one. + let exponent = ((magnitude.to_bits() >> 52) & 0x7ff) as i32 - 1023; + let quantum = 2.0_f64.powi(exponent.max(-14) - 10); + (magnitude / quantum).round_ties_even() * quantum + }; + (rounded as f32).copysign(x) +} + +/// The constants of Qt's sRGB transfer curve, each computed in single +/// precision exactly as section d.15 writes it. +struct SrgbCurve { + a: f32, + b: f32, + k: f32, + d: f32, + g: f32, + inv_d: f32, + inv_k: f32, + inv_a: f32, + inv_e: f32, + inv_g: f32, +} + +/// A single-precision power, correctly rounded: computed in `f64` and +/// rounded once. +fn powf32(x: f32, y: f32) -> f32 { + f64::from(x).powf(f64::from(y)) as f32 +} + +impl SrgbCurve { + fn new() -> SrgbCurve { + let a = 1.0_f32 / 1.055_f32; + let b = 0.055_f32 / 1.055_f32; + let k = 1.0_f32 / 12.92_f32; + let d = 0.04045_f32; + let g = 2.4_f32; + SrgbCurve { + a, + b, + k, + d, + g, + inv_d: k * d, + inv_k: 1.0_f32 / k, + inv_a: powf32(1.0_f32 / a, g), + inv_e: -b / a, + inv_g: 1.0_f32 / g, + } + } + + /// The sRGB curve, channel to linear light, sign kept. + fn forward(&self, x: f32) -> f32 { + let u = x.abs(); + let t = if u < self.d { + self.k * u + } else { + powf32(self.a * u + self.b, self.g) + }; + t.copysign(x) + } + + /// The inverse curve, linear light to channel, sign kept. + fn inverse(&self, y: f32) -> f32 { + let u = y.abs(); + let t = if u < self.inv_d { + self.inv_k * u + } else { + powf32(self.inv_a * u, self.inv_g) + self.inv_e + }; + t.copysign(y) + } +} + +/// The matrix Qt applies between its sRGB and linear sRGB spaces: the +/// identity up to single-precision rounding. Rows are red, green, blue; +/// the values are binary32 numbers given by their bit patterns. +const SRGB_MATRIX: [[u32; 3]; 3] = [ + // 0x1.fffffep-1, 0x1.4p-25, -0x1p-25 + [0x3f7f_ffff, 0x3320_0000, 0xb300_0000], + // 0x1.928p-26, 1, 0 + [0x32c9_4000, 0x3f80_0000, 0x0000_0000], + // 0, 0, 0x1.fffffcp-1 + [0x0000_0000, 0x0000_0000, 0x3f7f_fffe], +]; + +fn apply_srgb_matrix([r, g, b]: [f32; 3]) -> [f32; 3] { + SRGB_MATRIX.map(|[m1, m2, m3]| { + (r * f32::from_bits(m1) + g * f32::from_bits(m2)) + b * f32::from_bits(m3) + }) +} + +/// Converts a colour from sRGB to linear sRGB as Qt's colour-space +/// transform does: the sRGB curve on each channel read, then the +/// near-identity matrix, all in single precision, stored as a +/// [`FloatColor`]. Alpha is carried through. +pub fn srgb_to_linear(color: FloatColor) -> FloatColor { + let curve = SrgbCurve::new(); + let [r, g, b, a] = color.channels(); + let linear = apply_srgb_matrix([curve.forward(r), curve.forward(g), curve.forward(b)]); + FloatColor::from_f32(linear, a) +} + +/// Converts a colour from linear sRGB to sRGB as Qt's colour-space +/// transform does: the near-identity matrix, then the inverse sRGB curve +/// on each channel, all in single precision, stored as a [`FloatColor`]. +/// Alpha is carried through. +pub fn linear_to_srgb(color: FloatColor) -> FloatColor { + let curve = SrgbCurve::new(); + let [r, g, b, a] = color.channels(); + let [lr, lg, lb] = apply_srgb_matrix([r, g, b]); + FloatColor::from_f32([curve.inverse(lr), curve.inverse(lg), curve.inverse(lb)], a) +} + +/// Linear sRGB to OKLab with Ottosson's published matrices, in `f64`. +fn linear_to_oklab([r, g, b]: [f64; 3]) -> [f64; 3] { + let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b; + let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b; + let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b; + let (l, m, s) = (l.powf(1.0 / 3.0), m.powf(1.0 / 3.0), s.powf(1.0 / 3.0)); + [ + 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s, + 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s, + 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s, + ] +} + +/// OKLab back to linear sRGB with Ottosson's published matrices, in `f64`. +fn oklab_to_linear([lightness, a, b]: [f64; 3]) -> [f64; 3] { + let l = lightness + 0.3963377774 * a + 0.2158037573 * b; + let m = lightness - 0.1055613458 * a - 0.0638541728 * b; + let s = lightness - 0.0894841775 * a - 1.2914855480 * b; + let (l, m, s) = (l * l * l, m * m * m, s * s * s); + [ + 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s, + ] +} + +/// `x (1 - f) + y f` with the mixed precision of section d.15 step 3: the +/// inputs rounded to single precision, `y f` in single precision, the rest +/// in double, the result rounded to single precision. +fn tint_lerp(x: f64, y: f64, f: f64) -> f32 { + let (x, y, f) = (x as f32, y as f32, f as f32); + let kept = f64::from(x) * (1.0 - f64::from(f)); + (kept + f64::from(y * f)) as f32 +} + +/// Tints `base` toward `color` in OKLab by `factor`, keeping `base`'s +/// perceptual lightness: the accent tint Plasma applies to every colour of +/// a scheme that sets a tint factor. +/// +/// Both colours are taken to linear sRGB ([`srgb_to_linear`]) and to +/// OKLab; the result has `base`'s lightness and its `a` and `b` moved +/// `factor` of the way toward `color`'s. It is taken back to linear sRGB, +/// stored, reduced to 16 bits and converted to sRGB ([`linear_to_srgb`]); +/// a result outside the gamut is clamped. The result is opaque. +pub fn oklab_tint(base: KdeColor, color: KdeColor, factor: f64) -> KdeColor { + let oklab_of = |c: KdeColor| { + let [r, g, b, _] = srgb_to_linear(FloatColor::Rgb16(c)).channels(); + linear_to_oklab([f64::from(r), f64::from(g), f64::from(b)]) + }; + let [lightness, base_a, base_b] = oklab_of(base); + let [_, color_a, color_b] = oklab_of(color); + let a = f64::from(tint_lerp(base_a, color_a, factor)); + let b = f64::from(tint_lerp(base_b, color_b, factor)); + let linear = oklab_to_linear([lightness, a, b]).map(|c| c as f32); + let stored = FloatColor::from_f32(linear, 1.0).to_rgb16(); + let srgb = linear_to_srgb(FloatColor::Rgb16(KdeColor { + a: u16::MAX, + ..stored + })); + KdeColor { + a: u16::MAX, + ..srgb.to_rgb16() + } +} + +// --------------------------------------------------------------------------- +// Teksilo's WCAG lift +// --------------------------------------------------------------------------- + +/// The number of equal luma steps [`lift_luma_until`] tries between the +/// colour and black or white. A step of 1/256 of the way moves an 8-bit +/// channel by about one level, so the lift overshoots its floor by very +/// little. +const LUMA_LIFT_STEPS: u32 = 256; + +/// The end of the luma scale [`lift_luma_toward`] moves a colour toward. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LumaDirection { + /// Toward black, luma 0. + Darker, + /// Toward white, luma 1. + Lighter, +} + +impl LumaDirection { + /// The luma this direction ends at. + fn end(self) -> f64 { + match self { + LumaDirection::Darker => 0.0, + LumaDirection::Lighter => 1.0, + } + } +} + +/// `color` with its HCY luma moved toward more contrast until its WCAG 2.x +/// contrast ratio reaches `floor` against every colour in `backdrops`. +/// +/// This is Teksilo's helper, not KDE's: KDE never lifts a colour for +/// readability. The hue, chroma and alpha are kept, so a lifted accent +/// stays recognisably the same hue; only its luma moves, toward black or +/// toward white, whichever reaches the higher minimum contrast over +/// `backdrops`. Contrast is measured with +/// [`teksilo_tokens::contrast::composite_contrast`], so a translucent +/// `color` is measured as painted, and each backdrop is taken as opaque. +/// +/// Returns `color` unchanged when it already passes, or when `backdrops` is +/// empty. Otherwise returns the first luma step that passes, or, when no +/// step does (a floor above what black or white can reach, a very +/// translucent `color`, or backdrops on both sides of the middle), the step +/// with the highest minimum contrast. +pub fn lift_luma_until(color: KdeColor, backdrops: &[KdeColor], floor: f32) -> KdeColor { + if backdrops.is_empty() || worst_on(color, backdrops) >= floor { + return color; + } + let hcy = Hcy::from_color(color); + let at_luma = |y: f64| Hcy { y, ..hcy }.to_color(); + let direction = if worst_on(at_luma(0.0), backdrops) >= worst_on(at_luma(1.0), backdrops) { + LumaDirection::Darker + } else { + LumaDirection::Lighter + }; + lift_luma_toward(color, backdrops, floor, direction) +} + +/// [`lift_luma_until`] with the direction given: `color`'s luma moves only +/// toward `direction`'s end of the scale, never the other way, until its +/// WCAG 2.x contrast reaches `floor` against every colour in `backdrops`. +/// +/// For a colour that must stay on one side of the scale, such as a fill +/// that must stay as light as the surfaces around it whatever the text on +/// it needs. Hue, chroma and alpha are kept, and contrast is measured as in +/// [`lift_luma_until`]. +/// +/// Returns `color` unchanged when it already passes, or when `backdrops` is +/// empty. Otherwise returns the first luma step toward the end that passes, +/// or, when none does, the step with the highest minimum contrast, which +/// may be `color` itself when moving that way only lowers it. +pub fn lift_luma_toward( + color: KdeColor, + backdrops: &[KdeColor], + floor: f32, + direction: LumaDirection, +) -> KdeColor { + let start = worst_on(color, backdrops); + if backdrops.is_empty() || start >= floor { + return color; + } + let hcy = Hcy::from_color(color); + let at_luma = |y: f64| Hcy { y, ..hcy }.to_color(); + let mut best = (start, color); + for step in 1..=LUMA_LIFT_STEPS { + let candidate = at_luma(lerp( + hcy.y, + direction.end(), + f64::from(step) / f64::from(LUMA_LIFT_STEPS), + )); + let score = worst_on(candidate, backdrops); + if score >= floor { + return candidate; + } + if score > best.0 { + best = (score, candidate); + } + } + best.1 +} + +/// The lowest WCAG 2.x contrast `color` reaches, painted over each of +/// `backdrops`; infinite for no backdrop. +fn worst_on(color: KdeColor, backdrops: &[KdeColor]) -> f32 { + let fg = color.to_color(); + backdrops + .iter() + .map(|bg| composite_contrast(fg, bg.to_color())) + .fold(f32::INFINITY, f32::min) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_matrix_bit_patterns_are_the_published_values() { + // 0x1.fffffep-1, 0x1.4p-25, -0x1p-25 / 0x1.928p-26, 1, 0 / + // 0, 0, 0x1.fffffcp-1, written as exact powers of two. + let p = |e: i32| 2.0_f64.powi(e); + let expected = [ + [1.0 - p(-24), 1.25 * p(-25), -p(-25)], + [(1.0 + 2344.0 / 4096.0) * p(-26), 1.0, 0.0], + [0.0, 0.0, 1.0 - p(-23)], + ]; + for (row, expected) in SRGB_MATRIX.iter().zip(expected) { + for (&bits, value) in row.iter().zip(expected) { + assert_eq!(f64::from(f32::from_bits(bits)), value); + } + } + } + + #[test] + fn half_precision_rounding_keeps_ties_even_and_subnormals() { + let p = |e: i32| 2.0_f32.powi(e); + assert_eq!(round_to_half(1.0), 1.0); + // Ten fraction bits: 1.05 becomes 1075 / 1024. + assert_eq!(round_to_half(1.05), 1075.0 * p(-10)); + // Below 2^-14 the step is the subnormal 2^-24. + assert_eq!(round_to_half(-1e-7), -2.0 * p(-24)); + // Exactly half the smallest subnormal ties to zero, keeping the sign. + assert_eq!(round_to_half(-p(-25)).to_bits(), (-0.0_f32).to_bits()); + // A tie between two normal values goes to the even one. + assert_eq!(round_to_half(1.0 + p(-11)), 1.0); + assert_eq!(round_to_half(1.0 + 3.0 * p(-11)), 1.0 + p(-9)); + assert_eq!(round_to_half(65504.0), 65504.0); + assert_eq!(round_to_half(65520.0), f32::INFINITY); + } + + #[test] + fn channel_storage_round_trips_every_16_bit_value() { + for q in 0..=u16::MAX { + assert_eq!(store_channel(read_channel(q)), q); + } + } +} diff --git a/crates/teksilo-kde-scheme/src/palette.rs b/crates/teksilo-kde-scheme/src/palette.rs new file mode 100644 index 000000000..124657ed7 --- /dev/null +++ b/crates/teksilo-kde-scheme/src/palette.rs @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! A scheme resolved into the colours an application draws with. +//! +//! Implemented from sections f and i of Teksilo's specification +//! (`engineering/docs/kde-breeze-spec.md`). +//! +//! [`KdeColorScheme::resolve`] gives a [`ResolvedPalette`]: for each of the +//! three states and seven sets, a [`ResolvedSet`] of +//! +//! - 8 foregrounds ([`ForegroundRole`]), as stored and after the state's +//! effects ([`crate::effects`]); +//! - 8 backgrounds ([`BackgroundRole`]): the two stored ones after the +//! effects, and six computed ones, each the `Normal` background tinted +//! toward one of the foregrounds by [`COMPUTED_BACKGROUND_TINT`]; +//! - 2 decorations ([`DecorationRole`]); +//! - 5 shades ([`ShadeRole`]) of the `Normal` background, at the scheme's +//! contrast value ([`KdeColorScheme::contrast_value`]). +//! +//! [`PaletteRole`] names the twenty colours an application palette takes +//! from those sets, read with [`ResolvedPalette::role`]. +//! +//! [`KdeColorScheme::is_dark`] is the light or dark decision KDE reports to +//! applications and portals, and [`KdeColorScheme::os_theme_colors`] the +//! report Teksilo's native theme builds its tokens from. + +use teksilo_tokens::{ColorSchemePreference, OsThemeColors}; + +use crate::color::KdeColor; +use crate::math::{ShadeRole, shade_role, tint}; +use crate::scheme::{ColorRole, ColorSet, ColorState, KdeColorScheme, SetColors}; + +/// How far each computed background is tinted from the `Normal` background +/// toward its foreground. +pub const COMPUTED_BACKGROUND_TINT: f64 = 0.3; + +/// One of the eight foregrounds of a resolved set. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ForegroundRole { + /// Ordinary text, `ForegroundNormal`. + Normal, + /// Secondary text, `ForegroundInactive`. + Inactive, + /// Active or hot text, `ForegroundActive`. + Active, + /// An unvisited link, `ForegroundLink`. + Link, + /// A visited link, `ForegroundVisited`. + Visited, + /// Errors, `ForegroundNegative`. + Negative, + /// Warnings, `ForegroundNeutral`. + Neutral, + /// Success, `ForegroundPositive`. + Positive, +} + +impl ForegroundRole { + /// The eight foregrounds, in declaration order. + pub const ALL: [ForegroundRole; 8] = [ + ForegroundRole::Normal, + ForegroundRole::Inactive, + ForegroundRole::Active, + ForegroundRole::Link, + ForegroundRole::Visited, + ForegroundRole::Negative, + ForegroundRole::Neutral, + ForegroundRole::Positive, + ]; + + /// The stored key the foreground comes from. + pub const fn stored_role(self) -> ColorRole { + match self { + ForegroundRole::Normal => ColorRole::ForegroundNormal, + ForegroundRole::Inactive => ColorRole::ForegroundInactive, + ForegroundRole::Active => ColorRole::ForegroundActive, + ForegroundRole::Link => ColorRole::ForegroundLink, + ForegroundRole::Visited => ColorRole::ForegroundVisited, + ForegroundRole::Negative => ColorRole::ForegroundNegative, + ForegroundRole::Neutral => ColorRole::ForegroundNeutral, + ForegroundRole::Positive => ColorRole::ForegroundPositive, + } + } + + const fn index(self) -> usize { + self as usize + } +} + +/// One of the eight backgrounds of a resolved set. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BackgroundRole { + /// The ordinary background, `BackgroundNormal`. + Normal, + /// Alternate rows, `BackgroundAlternate`. + Alternate, + /// Computed: `Normal` tinted toward [`ForegroundRole::Active`]. + Active, + /// Computed: `Normal` tinted toward [`ForegroundRole::Link`]. + Link, + /// Computed: `Normal` tinted toward [`ForegroundRole::Visited`]. + Visited, + /// Computed: `Normal` tinted toward [`ForegroundRole::Negative`]. + Negative, + /// Computed: `Normal` tinted toward [`ForegroundRole::Neutral`]. + Neutral, + /// Computed: `Normal` tinted toward [`ForegroundRole::Positive`]. + Positive, +} + +impl BackgroundRole { + /// The eight backgrounds, in declaration order. + pub const ALL: [BackgroundRole; 8] = [ + BackgroundRole::Normal, + BackgroundRole::Alternate, + BackgroundRole::Active, + BackgroundRole::Link, + BackgroundRole::Visited, + BackgroundRole::Negative, + BackgroundRole::Neutral, + BackgroundRole::Positive, + ]; + + /// The foreground a computed background is tinted toward, `None` for + /// the two stored backgrounds. + pub const fn tinted_toward(self) -> Option { + match self { + BackgroundRole::Normal | BackgroundRole::Alternate => None, + BackgroundRole::Active => Some(ForegroundRole::Active), + BackgroundRole::Link => Some(ForegroundRole::Link), + BackgroundRole::Visited => Some(ForegroundRole::Visited), + BackgroundRole::Negative => Some(ForegroundRole::Negative), + BackgroundRole::Neutral => Some(ForegroundRole::Neutral), + BackgroundRole::Positive => Some(ForegroundRole::Positive), + } + } + + const fn index(self) -> usize { + self as usize + } +} + +/// One of the two decorations of a resolved set. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum DecorationRole { + /// The keyboard focus indicator, `DecorationFocus`. + Focus, + /// The hover indicator, `DecorationHover`. + Hover, +} + +impl DecorationRole { + /// Both decorations. + pub const ALL: [DecorationRole; 2] = [DecorationRole::Focus, DecorationRole::Hover]; + + const fn index(self) -> usize { + self as usize + } +} + +const fn shade_index(role: ShadeRole) -> usize { + match role { + ShadeRole::Light => 0, + ShadeRole::Midlight => 1, + ShadeRole::Mid => 2, + ShadeRole::Dark => 3, + ShadeRole::Shadow => 4, + } +} + +/// The colours of one set in one state, every effect applied and every +/// derived colour computed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ResolvedSet { + foregrounds: [KdeColor; 8], + backgrounds: [KdeColor; 8], + decorations: [KdeColor; 2], + shades: [KdeColor; 5], +} + +impl ResolvedSet { + /// Completes a set's twelve colours, already through the state's + /// effects ([`KdeColorScheme::state_colors`]): the six computed + /// backgrounds and the five shades of `BackgroundNormal` at + /// `contrast_value` (the scheme's `contrast` divided by 10). + pub fn from_state_colors(colors: &SetColors, contrast_value: f64) -> ResolvedSet { + let foregrounds = ForegroundRole::ALL.map(|role| colors.get(role.stored_role())); + let normal = colors.background_normal; + let backgrounds = BackgroundRole::ALL.map(|role| match (role, role.tinted_toward()) { + (BackgroundRole::Normal, _) => normal, + (_, Some(foreground)) => tint( + normal, + foregrounds[foreground.index()], + COMPUTED_BACKGROUND_TINT, + ), + (_, None) => colors.background_alternate, + }); + ResolvedSet { + foregrounds, + backgrounds, + decorations: [colors.decoration_focus, colors.decoration_hover], + shades: ShadeRole::ALL.map(|role| shade_role(normal, role, contrast_value, 0.0)), + } + } + + /// The foreground of `role`. + pub const fn foreground(&self, role: ForegroundRole) -> KdeColor { + self.foregrounds[role.index()] + } + + /// The background of `role`. + pub const fn background(&self, role: BackgroundRole) -> KdeColor { + self.backgrounds[role.index()] + } + + /// The decoration of `role`. + pub const fn decoration(&self, role: DecorationRole) -> KdeColor { + self.decorations[role.index()] + } + + /// The shade of `BackgroundNormal` for `role`. + pub const fn shade(&self, role: ShadeRole) -> KdeColor { + self.shades[shade_index(role)] + } +} + +/// A colour of an application palette, and the resolved set it is read +/// from. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum PaletteRole { + /// Window background: Window `BackgroundNormal`. + Window, + /// Text on the window: Window `ForegroundNormal`. + WindowText, + /// Content-view background: View `BackgroundNormal`. + Base, + /// Content-view text: View `ForegroundNormal`. + Text, + /// Alternate rows: View `BackgroundAlternate`. + AlternateBase, + /// Unvisited links: View `ForegroundLink`. + Link, + /// Visited links: View `ForegroundVisited`. + LinkVisited, + /// Placeholder text in an empty field: View `ForegroundInactive`. + PlaceholderText, + /// Button background: Button `BackgroundNormal`. + Button, + /// Button text: Button `ForegroundNormal`. + ButtonText, + /// Selection background: Selection `BackgroundNormal`. + Highlight, + /// Selected text: Selection `ForegroundNormal`. + HighlightedText, + /// The accent colour, the same as [`PaletteRole::Highlight`]. + Accent, + /// Tooltip background: Tooltip `BackgroundNormal` of the Active state, + /// whatever state is asked for. + TooltipBase, + /// Tooltip text: Tooltip `ForegroundNormal` of the Active state, + /// whatever state is asked for. + TooltipText, + /// The light shade of the Window `BackgroundNormal`. + Light, + /// The midlight shade of the Window `BackgroundNormal`. + Midlight, + /// The mid shade of the Window `BackgroundNormal`. + Mid, + /// The dark shade of the Window `BackgroundNormal`. + Dark, + /// The shadow shade of the Window `BackgroundNormal`. + Shadow, +} + +impl PaletteRole { + /// Every palette role, in declaration order. + pub const ALL: [PaletteRole; 20] = [ + PaletteRole::Window, + PaletteRole::WindowText, + PaletteRole::Base, + PaletteRole::Text, + PaletteRole::AlternateBase, + PaletteRole::Link, + PaletteRole::LinkVisited, + PaletteRole::PlaceholderText, + PaletteRole::Button, + PaletteRole::ButtonText, + PaletteRole::Highlight, + PaletteRole::HighlightedText, + PaletteRole::Accent, + PaletteRole::TooltipBase, + PaletteRole::TooltipText, + PaletteRole::Light, + PaletteRole::Midlight, + PaletteRole::Mid, + PaletteRole::Dark, + PaletteRole::Shadow, + ]; +} + +/// Every colour of a scheme, resolved for the three states and seven sets. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ResolvedPalette { + /// Indexed by [`ColorState::index`], then [`ColorSet::index`]. + sets: [[ResolvedSet; 7]; 3], +} + +impl ResolvedPalette { + /// The resolved colours of `set` in `state`. + pub const fn set(&self, state: ColorState, set: ColorSet) -> &ResolvedSet { + &self.sets[state.index()][set.index()] + } + + /// The palette colour of `role` in `state`. + pub const fn role(&self, state: ColorState, role: PaletteRole) -> KdeColor { + let window = self.set(state, ColorSet::Window); + let view = self.set(state, ColorSet::View); + let button = self.set(state, ColorSet::Button); + let selection = self.set(state, ColorSet::Selection); + let tooltip = self.set(ColorState::Active, ColorSet::Tooltip); + match role { + PaletteRole::Window => window.background(BackgroundRole::Normal), + PaletteRole::WindowText => window.foreground(ForegroundRole::Normal), + PaletteRole::Base => view.background(BackgroundRole::Normal), + PaletteRole::Text => view.foreground(ForegroundRole::Normal), + PaletteRole::AlternateBase => view.background(BackgroundRole::Alternate), + PaletteRole::Link => view.foreground(ForegroundRole::Link), + PaletteRole::LinkVisited => view.foreground(ForegroundRole::Visited), + PaletteRole::PlaceholderText => view.foreground(ForegroundRole::Inactive), + PaletteRole::Button => button.background(BackgroundRole::Normal), + PaletteRole::ButtonText => button.foreground(ForegroundRole::Normal), + PaletteRole::Highlight | PaletteRole::Accent => { + selection.background(BackgroundRole::Normal) + } + PaletteRole::HighlightedText => selection.foreground(ForegroundRole::Normal), + PaletteRole::TooltipBase => tooltip.background(BackgroundRole::Normal), + PaletteRole::TooltipText => tooltip.foreground(ForegroundRole::Normal), + PaletteRole::Light => window.shade(ShadeRole::Light), + PaletteRole::Midlight => window.shade(ShadeRole::Midlight), + PaletteRole::Mid => window.shade(ShadeRole::Mid), + PaletteRole::Dark => window.shade(ShadeRole::Dark), + PaletteRole::Shadow => window.shade(ShadeRole::Shadow), + } + } +} + +/// The 8-bit grey level below which a window background counts as dark. +pub const DARK_GRAY_THRESHOLD: u32 = 192; + +impl KdeColorScheme { + /// The contrast value the shades are computed with: `[KDE] contrast` + /// times 0.1, unclamped here (0.4 for Breeze, 0.7 by default). + pub fn contrast_value(&self) -> f64 { + 0.1 * f64::from(self.contrast) + } + + /// Resolves every set in every state: the state's colour source and + /// effects ([`KdeColorScheme::state_colors`]), then the computed + /// backgrounds and shades ([`ResolvedSet::from_state_colors`]). + /// + /// The Tooltip set is resolved in every state too, although the palette + /// roles read only its Active colours. + pub fn resolve(&self) -> ResolvedPalette { + let k = self.contrast_value(); + ResolvedPalette { + sets: ColorState::ALL.map(|state| { + ColorSet::ALL + .map(|set| ResolvedSet::from_state_colors(&self.state_colors(state, set), k)) + }), + } + } + + /// Whether the scheme is dark, as KDE reports it to applications and + /// to the desktop portal. + /// + /// The Window `BackgroundNormal` of the Active state (which no effect + /// changes) is reduced to 8-bit channels `r`, `g`, `b`, and its grey + /// level `(11 r + 16 g + 5 b) / 32` (integer division) is compared with + /// [`DARK_GRAY_THRESHOLD`]: below it, the scheme is dark. Only the + /// window decides: a dark window with light content views is dark. + pub fn is_dark(&self) -> bool { + let [r, g, b, _] = self.set(ColorSet::Window).background_normal.to_rgba8(); + let gray = (11 * u32::from(r) + 16 * u32::from(g) + 5 * u32::from(b)) / 32; + gray < DARK_GRAY_THRESHOLD + } + + /// The scheme as the colour report Teksilo's native theme is built + /// from, every colour taken from the Active state: + /// + /// | Field | Source | + /// |---|---| + /// | `color_scheme` | [`KdeColorScheme::is_dark`] | + /// | `accent`, `selection_bg` | Selection `BackgroundNormal` | + /// | `selection_fg` | Selection `ForegroundNormal` | + /// | `window_bg`, `window_fg` | Window `BackgroundNormal`, `ForegroundNormal` | + /// | `button_bg`, `button_fg` | Button `BackgroundNormal`, `ForegroundNormal` | + /// | `tooltip_bg`, `tooltip_fg` | Tooltip `BackgroundNormal`, `ForegroundNormal` | + /// | `view_bg`, `view_fg` | View `BackgroundNormal`, `ForegroundNormal` | + /// | `alternate_bg` | View `BackgroundAlternate` | + /// | `inactive_fg` | View `ForegroundInactive` | + /// | `link`, `link_visited` | View `ForegroundLink`, `ForegroundVisited` | + /// | `error`, `warning`, `success` | View `ForegroundNegative`, `ForegroundNeutral`, `ForegroundPositive` | + /// | `focus` | View `DecorationFocus` | + /// + /// The accent is the selection colour, as KDE's application palette + /// and the portal's accent colour both report it. + pub fn os_theme_colors(&self) -> OsThemeColors { + let scheme = if self.is_dark() { + ColorSchemePreference::Dark + } else { + ColorSchemePreference::Light + }; + let some = |color: KdeColor| Some(color.to_color()); + let view = self.set(ColorSet::View); + let window = self.set(ColorSet::Window); + let button = self.set(ColorSet::Button); + let selection = self.set(ColorSet::Selection); + let tooltip = self.set(ColorSet::Tooltip); + + let mut os = OsThemeColors::new(scheme); + os.accent = some(selection.background_normal); + os.window_bg = some(window.background_normal); + os.window_fg = some(window.foreground_normal); + os.button_bg = some(button.background_normal); + os.button_fg = some(button.foreground_normal); + os.selection_bg = some(selection.background_normal); + os.selection_fg = some(selection.foreground_normal); + os.tooltip_bg = some(tooltip.background_normal); + os.tooltip_fg = some(tooltip.foreground_normal); + os.view_bg = some(view.background_normal); + os.view_fg = some(view.foreground_normal); + os.alternate_bg = some(view.background_alternate); + os.inactive_fg = some(view.foreground_inactive); + os.link = some(view.foreground_link); + os.link_visited = some(view.foreground_visited); + os.error = some(view.foreground_negative); + os.warning = some(view.foreground_neutral); + os.success = some(view.foreground_positive); + os.focus = some(view.decoration_focus); + os + } +} diff --git a/crates/teksilo-kde-scheme/src/scheme.rs b/crates/teksilo-kde-scheme/src/scheme.rs new file mode 100644 index 000000000..dfaef8f5e --- /dev/null +++ b/crates/teksilo-kde-scheme/src/scheme.rs @@ -0,0 +1,1248 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The KDE colour-scheme model: what a `.colors` file, or the colour groups +//! of `kdeglobals`, says. +//! +//! A scheme has seven colour sets ([`ColorSet`]), each with twelve stored +//! roles ([`ColorRole`], gathered in [`SetColors`]), an optional +//! replacement of each set for inactive windows (an `[Inactive]` subgroup), +//! the settings of the Disabled and Inactive state effects +//! ([`StateEffect`]), the title-bar colours ([`WmColors`]) and a few +//! scalars. +//! +//! [`KdeColorScheme::from_config`] fills every key a configuration lacks +//! from KDE's compiled defaults, so a scheme is always complete: +//! +//! - every set falls back to its own default table; +//! - the Header set falls back, key by key, to the same key of the Window +//! group when that group has it, and to the Header table otherwise; +//! - an `[Inactive]` subgroup replaces its set as a whole: a key it lacks +//! takes the same default the set would, never the parent group's value; +//! - `[KDE] contrast` defaults to 7 and `[KDE] frameContrast` to 0.2, +//! clamped to `0..=1`. + +use std::fmt; +use std::sync::Arc; + +use crate::color::KdeColor; +use crate::config::{KConfig, ParseDiagnostic, escape_value}; + +/// One of the seven colour sets of a scheme. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ColorSet { + /// Content views: lists, text fields, documents. + View, + /// Window backgrounds and the controls drawn on them. + Window, + /// Push buttons. + Button, + /// Selected items and the highlight. + Selection, + /// Tooltips. + Tooltip, + /// Areas drawn in the opposite light/dark sense, such as a dark panel + /// in a light scheme. + Complementary, + /// Headers: title bars, toolbars, column headers. + Header, +} + +impl ColorSet { + /// Every set, in the order the model stores them. + pub const ALL: [ColorSet; 7] = [ + ColorSet::View, + ColorSet::Window, + ColorSet::Button, + ColorSet::Selection, + ColorSet::Tooltip, + ColorSet::Complementary, + ColorSet::Header, + ]; + + /// The configuration group the set is stored in, such as + /// `Colors:View`. + pub const fn group_name(self) -> &'static str { + match self { + ColorSet::View => "Colors:View", + ColorSet::Window => "Colors:Window", + ColorSet::Button => "Colors:Button", + ColorSet::Selection => "Colors:Selection", + ColorSet::Tooltip => "Colors:Tooltip", + ColorSet::Complementary => "Colors:Complementary", + ColorSet::Header => "Colors:Header", + } + } + + /// The set's position in [`ColorSet::ALL`]. + pub const fn index(self) -> usize { + self as usize + } +} + +/// One of the three states (colour groups) a palette is resolved for. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ColorState { + /// A focused window's normal controls. + Active, + /// Controls in a window that does not have focus. + Inactive, + /// Disabled controls. + Disabled, +} + +impl ColorState { + /// Every state. + pub const ALL: [ColorState; 3] = [ + ColorState::Active, + ColorState::Inactive, + ColorState::Disabled, + ]; + + /// The state's position in [`ColorState::ALL`]. + pub const fn index(self) -> usize { + self as usize + } +} + +/// One of the twelve colours a set stores. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ColorRole { + /// `BackgroundNormal`. + BackgroundNormal, + /// `BackgroundAlternate`, alternate rows. + BackgroundAlternate, + /// `ForegroundNormal`, ordinary text. + ForegroundNormal, + /// `ForegroundInactive`, secondary text. + ForegroundInactive, + /// `ForegroundActive`. + ForegroundActive, + /// `ForegroundLink`. + ForegroundLink, + /// `ForegroundVisited`. + ForegroundVisited, + /// `ForegroundNegative`, errors. + ForegroundNegative, + /// `ForegroundNeutral`, warnings. + ForegroundNeutral, + /// `ForegroundPositive`, success. + ForegroundPositive, + /// `DecorationFocus`, the focus indicator. + DecorationFocus, + /// `DecorationHover`, the hover indicator. + DecorationHover, +} + +impl ColorRole { + /// Every role, in the order [`SetColors`] declares them. + pub const ALL: [ColorRole; 12] = [ + ColorRole::BackgroundNormal, + ColorRole::BackgroundAlternate, + ColorRole::ForegroundNormal, + ColorRole::ForegroundInactive, + ColorRole::ForegroundActive, + ColorRole::ForegroundLink, + ColorRole::ForegroundVisited, + ColorRole::ForegroundNegative, + ColorRole::ForegroundNeutral, + ColorRole::ForegroundPositive, + ColorRole::DecorationFocus, + ColorRole::DecorationHover, + ]; + + /// The configuration key, such as `BackgroundNormal`. + pub const fn key(self) -> &'static str { + match self { + ColorRole::BackgroundNormal => "BackgroundNormal", + ColorRole::BackgroundAlternate => "BackgroundAlternate", + ColorRole::ForegroundNormal => "ForegroundNormal", + ColorRole::ForegroundInactive => "ForegroundInactive", + ColorRole::ForegroundActive => "ForegroundActive", + ColorRole::ForegroundLink => "ForegroundLink", + ColorRole::ForegroundVisited => "ForegroundVisited", + ColorRole::ForegroundNegative => "ForegroundNegative", + ColorRole::ForegroundNeutral => "ForegroundNeutral", + ColorRole::ForegroundPositive => "ForegroundPositive", + ColorRole::DecorationFocus => "DecorationFocus", + ColorRole::DecorationHover => "DecorationHover", + } + } +} + +/// The twelve stored colours of one set. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SetColors { + /// `BackgroundNormal`. + pub background_normal: KdeColor, + /// `BackgroundAlternate`. + pub background_alternate: KdeColor, + /// `ForegroundNormal`. + pub foreground_normal: KdeColor, + /// `ForegroundInactive`. + pub foreground_inactive: KdeColor, + /// `ForegroundActive`. + pub foreground_active: KdeColor, + /// `ForegroundLink`. + pub foreground_link: KdeColor, + /// `ForegroundVisited`. + pub foreground_visited: KdeColor, + /// `ForegroundNegative`. + pub foreground_negative: KdeColor, + /// `ForegroundNeutral`. + pub foreground_neutral: KdeColor, + /// `ForegroundPositive`. + pub foreground_positive: KdeColor, + /// `DecorationFocus`. + pub decoration_focus: KdeColor, + /// `DecorationHover`. + pub decoration_hover: KdeColor, +} + +impl SetColors { + /// A set from its twelve colours as 8-bit triples, in + /// [`ColorRole::ALL`] order. + pub const fn from_rgb8(rows: [[u8; 3]; 12]) -> SetColors { + const fn c(rgb: [u8; 3]) -> KdeColor { + KdeColor::rgb8(rgb[0], rgb[1], rgb[2]) + } + SetColors { + background_normal: c(rows[0]), + background_alternate: c(rows[1]), + foreground_normal: c(rows[2]), + foreground_inactive: c(rows[3]), + foreground_active: c(rows[4]), + foreground_link: c(rows[5]), + foreground_visited: c(rows[6]), + foreground_negative: c(rows[7]), + foreground_neutral: c(rows[8]), + foreground_positive: c(rows[9]), + decoration_focus: c(rows[10]), + decoration_hover: c(rows[11]), + } + } + + /// The colour of `role`. + pub const fn get(&self, role: ColorRole) -> KdeColor { + match role { + ColorRole::BackgroundNormal => self.background_normal, + ColorRole::BackgroundAlternate => self.background_alternate, + ColorRole::ForegroundNormal => self.foreground_normal, + ColorRole::ForegroundInactive => self.foreground_inactive, + ColorRole::ForegroundActive => self.foreground_active, + ColorRole::ForegroundLink => self.foreground_link, + ColorRole::ForegroundVisited => self.foreground_visited, + ColorRole::ForegroundNegative => self.foreground_negative, + ColorRole::ForegroundNeutral => self.foreground_neutral, + ColorRole::ForegroundPositive => self.foreground_positive, + ColorRole::DecorationFocus => self.decoration_focus, + ColorRole::DecorationHover => self.decoration_hover, + } + } + + /// Replaces the colour of `role`. + pub fn set(&mut self, role: ColorRole, color: KdeColor) { + let slot = match role { + ColorRole::BackgroundNormal => &mut self.background_normal, + ColorRole::BackgroundAlternate => &mut self.background_alternate, + ColorRole::ForegroundNormal => &mut self.foreground_normal, + ColorRole::ForegroundInactive => &mut self.foreground_inactive, + ColorRole::ForegroundActive => &mut self.foreground_active, + ColorRole::ForegroundLink => &mut self.foreground_link, + ColorRole::ForegroundVisited => &mut self.foreground_visited, + ColorRole::ForegroundNegative => &mut self.foreground_negative, + ColorRole::ForegroundNeutral => &mut self.foreground_neutral, + ColorRole::ForegroundPositive => &mut self.foreground_positive, + ColorRole::DecorationFocus => &mut self.decoration_focus, + ColorRole::DecorationHover => &mut self.decoration_hover, + }; + *slot = color; + } + + /// The same set with both decoration colours replaced. + const fn with_decorations(mut self, focus: KdeColor, hover: KdeColor) -> SetColors { + self.decoration_focus = focus; + self.decoration_hover = hover; + self + } +} + +/// The intensity part of a state effect (`IntensityEffect`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum IntensityEffect { + /// 0, or any value without a meaning. + None, + /// 1. + Shade, + /// 2. + Darken, + /// 3. + Lighten, +} + +impl IntensityEffect { + /// The effect a stored integer selects; an unknown value is `None`. + pub const fn from_code(code: i64) -> IntensityEffect { + match code { + 1 => IntensityEffect::Shade, + 2 => IntensityEffect::Darken, + 3 => IntensityEffect::Lighten, + _ => IntensityEffect::None, + } + } + + /// The integer stored for the effect. + pub const fn code(self) -> i64 { + match self { + IntensityEffect::None => 0, + IntensityEffect::Shade => 1, + IntensityEffect::Darken => 2, + IntensityEffect::Lighten => 3, + } + } +} + +/// The colour part of a state effect (`ColorEffect`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ColorEffect { + /// 0, or any value without a meaning. + None, + /// 1. + Desaturate, + /// 2. + Fade, + /// 3. + Tint, +} + +impl ColorEffect { + /// The effect a stored integer selects; an unknown value is `None`. + pub const fn from_code(code: i64) -> ColorEffect { + match code { + 1 => ColorEffect::Desaturate, + 2 => ColorEffect::Fade, + 3 => ColorEffect::Tint, + _ => ColorEffect::None, + } + } + + /// The integer stored for the effect. + pub const fn code(self) -> i64 { + match self { + ColorEffect::None => 0, + ColorEffect::Desaturate => 1, + ColorEffect::Fade => 2, + ColorEffect::Tint => 3, + } + } +} + +/// The contrast part of a state effect (`ContrastEffect`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ContrastEffect { + /// 0, or any value without a meaning. + None, + /// 1. + Fade, + /// 2. + Tint, +} + +impl ContrastEffect { + /// The effect a stored integer selects; an unknown value is `None`. + pub const fn from_code(code: i64) -> ContrastEffect { + match code { + 1 => ContrastEffect::Fade, + 2 => ContrastEffect::Tint, + _ => ContrastEffect::None, + } + } + + /// The integer stored for the effect. + pub const fn code(self) -> i64 { + match self { + ContrastEffect::None => 0, + ContrastEffect::Fade => 1, + ContrastEffect::Tint => 2, + } + } +} + +/// The settings of one state's effects, `[ColorEffects:Disabled]` or +/// `[ColorEffects:Inactive]`. +/// +/// When `enabled` is false the other fields are kept as read but have no +/// effect. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct StateEffect { + /// `Enable`. + pub enabled: bool, + /// `IntensityEffect`. + pub intensity_effect: IntensityEffect, + /// `IntensityAmount`. + pub intensity_amount: f64, + /// `ColorEffect`. + pub color_effect: ColorEffect, + /// `ColorAmount`. + pub color_amount: f64, + /// `Color`, the colour the colour effect uses. KDE reads it only when + /// a colour effect is set, so with [`ColorEffect::None`] it is the + /// state's default colour. + pub color: KdeColor, + /// `ContrastEffect`. + pub contrast_effect: ContrastEffect, + /// `ContrastAmount`. + pub contrast_amount: f64, +} + +impl StateEffect { + /// The Disabled state's settings when its group is absent: enabled, + /// darken by 0.1, no colour effect, contrast fade 0.65, colour + /// 56,56,56. + pub const DISABLED_DEFAULT: StateEffect = StateEffect { + enabled: true, + intensity_effect: IntensityEffect::Darken, + intensity_amount: 0.1, + color_effect: ColorEffect::None, + color_amount: 0.0, + color: KdeColor::rgb8(56, 56, 56), + contrast_effect: ContrastEffect::Fade, + contrast_amount: 0.65, + }; + + /// The Inactive state's settings when its group is absent: disabled, + /// no intensity effect, desaturate by -0.9, contrast tint 0.25, colour + /// 112,111,110. + pub const INACTIVE_DEFAULT: StateEffect = StateEffect { + enabled: false, + intensity_effect: IntensityEffect::None, + intensity_amount: 0.0, + color_effect: ColorEffect::Desaturate, + color_amount: -0.9, + color: KdeColor::rgb8(112, 111, 110), + contrast_effect: ContrastEffect::Tint, + contrast_amount: 0.25, + }; + + fn read(config: &KConfig, group: &str, default: StateEffect) -> StateEffect { + let g = &[group]; + let color_effect = + ColorEffect::from_code(config.read_int(g, "ColorEffect", default.color_effect.code())); + let color = if color_effect == ColorEffect::None { + default.color + } else { + config.read_color(g, "Color").unwrap_or(default.color) + }; + StateEffect { + enabled: config.read_bool(g, "Enable").unwrap_or(default.enabled), + intensity_effect: IntensityEffect::from_code(config.read_int( + g, + "IntensityEffect", + default.intensity_effect.code(), + )), + intensity_amount: config.read_real(g, "IntensityAmount", default.intensity_amount), + color_effect, + color_amount: config.read_real(g, "ColorAmount", default.color_amount), + color, + contrast_effect: ContrastEffect::from_code(config.read_int( + g, + "ContrastEffect", + default.contrast_effect.code(), + )), + contrast_amount: config.read_real(g, "ContrastAmount", default.contrast_amount), + } + } + + /// Writes the group's keys in sorted order. `Enable` is left out when + /// it equals `omitted_enable`, the value a reader assumes for an absent + /// key that the stock files rely on. + fn write(&self, out: &mut String, omitted_enable: Option) { + push_entry(out, "Color", &self.color.to_kconfig_text()); + push_entry(out, "ColorAmount", &self.color_amount.to_string()); + push_entry(out, "ColorEffect", &self.color_effect.code().to_string()); + push_entry(out, "ContrastAmount", &self.contrast_amount.to_string()); + push_entry( + out, + "ContrastEffect", + &self.contrast_effect.code().to_string(), + ); + if omitted_enable != Some(self.enabled) { + push_entry(out, "Enable", bool_text(self.enabled)); + } + push_entry(out, "IntensityAmount", &self.intensity_amount.to_string()); + push_entry( + out, + "IntensityEffect", + &self.intensity_effect.code().to_string(), + ); + } +} + +/// The title-bar colours of `[WM]`. Each is `None` when the scheme does not +/// set it. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct WmColors { + /// `activeBackground`. + pub active_background: Option, + /// `activeForeground`. + pub active_foreground: Option, + /// `inactiveBackground`. + pub inactive_background: Option, + /// `inactiveForeground`. + pub inactive_foreground: Option, + /// `activeBlend`. + pub active_blend: Option, + /// `inactiveBlend`. + pub inactive_blend: Option, +} + +impl WmColors { + /// The six keys with a reference to their field, in key order. + fn fields(&self) -> [(&'static str, Option); 6] { + [ + ("activeBackground", self.active_background), + ("activeBlend", self.active_blend), + ("activeForeground", self.active_foreground), + ("inactiveBackground", self.inactive_background), + ("inactiveBlend", self.inactive_blend), + ("inactiveForeground", self.inactive_foreground), + ] + } + + fn read(config: &KConfig) -> WmColors { + let c = |key| config.read_color(&["WM"], key); + WmColors { + active_background: c("activeBackground"), + active_foreground: c("activeForeground"), + inactive_background: c("inactiveBackground"), + inactive_foreground: c("inactiveForeground"), + active_blend: c("activeBlend"), + inactive_blend: c("inactiveBlend"), + } + } +} + +/// The default `[KDE] contrast`. +pub const DEFAULT_CONTRAST: i32 = 7; + +/// The default `[KDE] frameContrast`. +pub const DEFAULT_FRAME_CONTRAST: f64 = 0.2; + +/// The default value of `[General] TintFactor` when the key is present but +/// does not parse. +pub const DEFAULT_TINT_FACTOR: f64 = 0.15; + +/// A complete KDE colour scheme. +/// +/// Every field holds a value: keys a configuration lacks take KDE's +/// compiled defaults (see the module documentation). +/// +/// A scheme read with [`KdeColorScheme::from_config`] also keeps the +/// configuration it was read from ([`KdeColorScheme::source`]), for an +/// operation that must reproduce what the file said rather than what it +/// means. Equality compares the scheme's fields only, never that source. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct KdeColorScheme { + /// `[General] Name`, the display name (localised variants are + /// ignored). + pub name: Option, + /// `[General] ColorScheme`, the scheme's file name without extension. + pub scheme_id: Option, + /// The seven sets, indexed by [`ColorSet::index`]. + pub sets: [SetColors; 7], + /// The `[Inactive]` subgroup of each set when it has one, indexed by + /// [`ColorSet::index`]. It replaces the set for inactive windows. + pub inactive_sets: [Option; 7], + /// Whether the configuration has a `[Colors:Header]` group (or a group + /// nested in it). Without one, the Header set is the Window group's + /// keys, each falling back to the Header table where Window lacks it. + pub header_group_present: bool, + /// `[ColorEffects:Disabled]`. + pub disabled_effect: StateEffect, + /// `[ColorEffects:Inactive]`. + pub inactive_effect: StateEffect, + /// `[ColorEffects:Inactive] ChangeSelectionColor`: whether the + /// selection of an inactive window is drawn from the Window set tinted + /// toward the selection colour. Defaults to the Inactive group's + /// `Enable`, itself read with a default of true here. + pub change_selection_color: bool, + /// `[WM]`. + pub wm: WmColors, + /// `[KDE] contrast`, not clamped. + pub contrast: i32, + /// `[KDE] frameContrast`, clamped to `0..=1`. + pub frame_contrast: f64, + /// `[General] AccentColor`, when present and a colour. + pub accent: Option, + /// `[General] TintFactor`: `Some` when the key is present (0.15 when it + /// does not parse), which turns accent tinting on. + pub tint_factor: Option, + /// `[General] TitlebarIsAccentColored`, falling back to the legacy + /// `accentActiveTitlebar`, then false. + pub titlebar_is_accent_colored: bool, + source: SchemeSource, +} + +/// The configuration a scheme was read from. It takes no part in equality: +/// two schemes with the same fields are equal whatever text they came from. +#[derive(Clone, Default)] +struct SchemeSource(Option>); + +impl PartialEq for SchemeSource { + fn eq(&self, _: &SchemeSource) -> bool { + true + } +} + +impl fmt::Debug for SchemeSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Some(config) => write!(f, "SchemeSource({} groups)", config.groups().count()), + None => f.write_str("SchemeSource(none)"), + } + } +} + +const FOCUS: KdeColor = KdeColor::rgb8(61, 174, 233); + +/// The compiled default tables, in [`ColorSet::ALL`] order. The last one is +/// the Header table, used only where `[Colors:Window]` lacks the key. +const DEFAULT_SETS: [SetColors; 7] = [ + // View + SetColors::from_rgb8([ + [255, 255, 255], + [247, 247, 247], + [35, 38, 41], + [112, 125, 138], + [61, 174, 233], + [41, 128, 185], + [155, 89, 182], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), + // Window + SetColors::from_rgb8([ + [239, 240, 241], + [227, 229, 231], + [35, 38, 41], + [112, 125, 138], + [61, 174, 233], + [41, 128, 185], + [155, 89, 182], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), + // Button + SetColors::from_rgb8([ + [252, 252, 252], + [163, 212, 250], + [35, 38, 41], + [112, 125, 138], + [61, 174, 233], + [41, 128, 185], + [155, 89, 182], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), + // Selection + SetColors::from_rgb8([ + [61, 174, 233], + [163, 212, 250], + [255, 255, 255], + [112, 125, 138], + [255, 255, 255], + [253, 188, 75], + [155, 89, 182], + [176, 55, 69], + [198, 92, 0], + [23, 104, 57], + [61, 174, 233], + [147, 206, 233], + ]), + // Tooltip + SetColors::from_rgb8([ + [247, 247, 247], + [239, 240, 241], + [35, 38, 41], + [112, 125, 138], + [61, 174, 233], + [41, 128, 185], + [155, 89, 182], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), + // Complementary + SetColors::from_rgb8([ + [42, 46, 50], + [27, 30, 32], + [252, 252, 252], + [161, 169, 177], + [61, 174, 233], + [29, 153, 243], + [155, 89, 182], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), + // Header + SetColors::from_rgb8([ + [222, 224, 226], + [239, 240, 241], + [35, 38, 41], + [112, 125, 138], + [61, 174, 233], + [41, 128, 185], + [155, 89, 182], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), +]; + +/// Breeze Light's sets: the compiled defaults with every hover decoration +/// set to the focus colour. +const BREEZE_LIGHT_SETS: [SetColors; 7] = [ + DEFAULT_SETS[0].with_decorations(FOCUS, FOCUS), + DEFAULT_SETS[1].with_decorations(FOCUS, FOCUS), + DEFAULT_SETS[2].with_decorations(FOCUS, FOCUS), + DEFAULT_SETS[3].with_decorations(FOCUS, FOCUS), + DEFAULT_SETS[4].with_decorations(FOCUS, FOCUS), + DEFAULT_SETS[5].with_decorations(FOCUS, FOCUS), + DEFAULT_SETS[6].with_decorations(FOCUS, FOCUS), +]; + +/// Breeze Light's `[Colors:Header][Inactive]`. +const BREEZE_LIGHT_HEADER_INACTIVE: SetColors = SetColors::from_rgb8([ + [239, 240, 241], + [227, 229, 231], + [35, 38, 41], + [112, 125, 138], + [61, 174, 233], + [41, 128, 185], + [155, 89, 182], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [61, 174, 233], +]); + +/// A Breeze Dark set from its two backgrounds; every Breeze Dark set except +/// Selection shares the same foregrounds and decorations. +const fn dark_set(normal: [u8; 3], alternate: [u8; 3]) -> SetColors { + SetColors::from_rgb8([ + normal, + alternate, + [252, 252, 252], + [161, 169, 177], + [61, 174, 233], + [29, 153, 243], + [155, 89, 182], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [61, 174, 233], + ]) +} + +const BREEZE_DARK_SETS: [SetColors; 7] = [ + dark_set([20, 22, 24], [29, 31, 34]), + dark_set([32, 35, 38], [41, 44, 48]), + dark_set([41, 44, 48], [30, 87, 116]), + SetColors::from_rgb8([ + [61, 174, 233], + [30, 87, 116], + [252, 252, 252], + [161, 169, 177], + [252, 252, 252], + [253, 188, 75], + [155, 89, 182], + [176, 55, 69], + [198, 92, 0], + [23, 104, 57], + [61, 174, 233], + [61, 174, 233], + ]), + dark_set([41, 44, 48], [32, 35, 38]), + dark_set([32, 35, 38], [30, 87, 116]), + dark_set([41, 44, 48], [32, 35, 38]), +]; + +const BREEZE_DARK_HEADER_INACTIVE: SetColors = dark_set([32, 35, 38], [41, 44, 48]); + +/// Breeze Classic's Window set, which its Header set follows key by key. +const BREEZE_CLASSIC_WINDOW: SetColors = SetColors::from_rgb8([ + [239, 240, 241], + [189, 195, 199], + [35, 38, 39], + [127, 140, 141], + [61, 174, 233], + [41, 128, 185], + [127, 140, 141], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], +]); + +const BREEZE_CLASSIC_SETS: [SetColors; 7] = [ + SetColors::from_rgb8([ + [252, 252, 252], + [239, 240, 241], + [35, 38, 39], + [127, 140, 141], + [61, 174, 233], + [41, 128, 185], + [127, 140, 141], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), + BREEZE_CLASSIC_WINDOW, + SetColors::from_rgb8([ + [239, 240, 241], + [189, 195, 199], + [35, 38, 39], + [127, 140, 141], + [61, 174, 233], + [41, 128, 185], + [127, 140, 141], + [218, 68, 83], + [198, 92, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), + SetColors::from_rgb8([ + [61, 174, 233], + [29, 153, 243], + [252, 252, 252], + [239, 240, 241], + [252, 252, 252], + [253, 188, 75], + [189, 195, 199], + [176, 55, 69], + [246, 116, 0], + [23, 104, 57], + [61, 174, 233], + [147, 206, 233], + ]), + SetColors::from_rgb8([ + [35, 38, 39], + [77, 77, 77], + [252, 252, 252], + [189, 195, 199], + [61, 174, 233], + [41, 128, 185], + [127, 140, 141], + [218, 68, 83], + [246, 116, 0], + [39, 174, 96], + [61, 174, 233], + [147, 206, 233], + ]), + SetColors::from_rgb8([ + [49, 54, 59], + [59, 64, 69], + [239, 240, 241], + [175, 176, 179], + [147, 206, 233], + [61, 174, 230], + [61, 174, 230], + [231, 76, 60], + [253, 188, 75], + [46, 204, 113], + [30, 146, 255], + [61, 174, 230], + ]), + BREEZE_CLASSIC_WINDOW, +]; + +/// The `[ColorEffects:Inactive]` group all three stock Breeze schemes +/// share. Their `[ColorEffects:Disabled]` group equals +/// [`StateEffect::DISABLED_DEFAULT`]. +const BREEZE_INACTIVE_EFFECT: StateEffect = StateEffect { + enabled: false, + intensity_effect: IntensityEffect::None, + intensity_amount: 0.0, + color_effect: ColorEffect::Fade, + color_amount: 0.025, + color: KdeColor::rgb8(112, 111, 110), + contrast_effect: ContrastEffect::Tint, + contrast_amount: 0.1, +}; + +const fn wm(colors: [[u8; 3]; 6]) -> WmColors { + const fn c(rgb: [u8; 3]) -> Option { + Some(KdeColor::rgb8(rgb[0], rgb[1], rgb[2])) + } + WmColors { + active_background: c(colors[0]), + active_blend: c(colors[1]), + active_foreground: c(colors[2]), + inactive_background: c(colors[3]), + inactive_blend: c(colors[4]), + inactive_foreground: c(colors[5]), + } +} + +impl Default for KdeColorScheme { + fn default() -> Self { + KdeColorScheme::kcolorscheme_defaults() + } +} + +impl KdeColorScheme { + /// The scheme a configuration with no colour keys at all describes: + /// KDE's compiled defaults. They equal Breeze Light except that every + /// `DecorationHover` is 147,206,233, there is no `[Inactive]` subgroup, + /// `contrast` is 7 and there is no `[WM]` colour. + pub fn kcolorscheme_defaults() -> KdeColorScheme { + KdeColorScheme { + name: None, + scheme_id: None, + sets: DEFAULT_SETS, + inactive_sets: [None; 7], + header_group_present: false, + disabled_effect: StateEffect::DISABLED_DEFAULT, + inactive_effect: StateEffect::INACTIVE_DEFAULT, + change_selection_color: true, + wm: WmColors::default(), + contrast: DEFAULT_CONTRAST, + frame_contrast: DEFAULT_FRAME_CONTRAST, + accent: None, + tint_factor: None, + titlebar_is_accent_colored: false, + source: SchemeSource::default(), + } + } + + /// The stock Breeze Light scheme (`BreezeLight.colors`, breeze 6.8.0). + pub fn breeze_light() -> KdeColorScheme { + let mut inactive_sets = [None; 7]; + inactive_sets[ColorSet::Header.index()] = Some(BREEZE_LIGHT_HEADER_INACTIVE); + KdeColorScheme { + name: Some("Breeze Light".to_owned()), + scheme_id: Some("BreezeLight".to_owned()), + sets: BREEZE_LIGHT_SETS, + inactive_sets, + header_group_present: true, + inactive_effect: BREEZE_INACTIVE_EFFECT, + wm: wm([ + [227, 229, 231], + [227, 229, 231], + [35, 38, 41], + [239, 240, 241], + [239, 240, 241], + [112, 125, 138], + ]), + contrast: 4, + ..KdeColorScheme::kcolorscheme_defaults() + } + } + + /// The stock Breeze Dark scheme (`BreezeDark.colors`, breeze 6.8.0). + pub fn breeze_dark() -> KdeColorScheme { + let mut inactive_sets = [None; 7]; + inactive_sets[ColorSet::Header.index()] = Some(BREEZE_DARK_HEADER_INACTIVE); + KdeColorScheme { + name: Some("Breeze Dark".to_owned()), + scheme_id: Some("BreezeDark".to_owned()), + sets: BREEZE_DARK_SETS, + inactive_sets, + header_group_present: true, + inactive_effect: BREEZE_INACTIVE_EFFECT, + wm: wm([ + [39, 44, 49], + [252, 252, 252], + [252, 252, 252], + [32, 36, 40], + [161, 169, 177], + [161, 169, 177], + ]), + contrast: 4, + ..KdeColorScheme::kcolorscheme_defaults() + } + } + + /// The stock Breeze Classic scheme (`BreezeClassic.colors`, breeze + /// 6.8.0). It has no Header group, so its Header set is its Window set, + /// and it colours the active title bar with the accent. + pub fn breeze_classic() -> KdeColorScheme { + KdeColorScheme { + name: Some("Breeze Classic".to_owned()), + scheme_id: Some("BreezeClassic".to_owned()), + sets: BREEZE_CLASSIC_SETS, + inactive_effect: BREEZE_INACTIVE_EFFECT, + wm: wm([ + [71, 80, 87], + [252, 252, 252], + [252, 252, 252], + [239, 240, 241], + [75, 71, 67], + [189, 195, 199], + ]), + contrast: 4, + titlebar_is_accent_colored: true, + ..KdeColorScheme::kcolorscheme_defaults() + } + } + + /// The stored colours of `set`. + pub fn set(&self, set: ColorSet) -> &SetColors { + &self.sets[set.index()] + } + + /// The `[Inactive]` replacement of `set`, when the scheme has one. + pub fn inactive_set(&self, set: ColorSet) -> Option<&SetColors> { + self.inactive_sets[set.index()].as_ref() + } + + /// Parses a `.colors` file (or any configuration text) and reads the + /// scheme from it. + pub fn parse(text: &str) -> (KdeColorScheme, Vec) { + let (config, diagnostics) = KConfig::parse(text); + (KdeColorScheme::from_config(&config), diagnostics) + } + + /// Reads the scheme a configuration describes, a single file or a + /// merged cascade. Names are read as stored, without `[$e]` expansion. + pub fn from_config(config: &KConfig) -> KdeColorScheme { + let header_defaults = { + let mut defaults = DEFAULT_SETS[ColorSet::Header.index()]; + for role in ColorRole::ALL { + if let Some(c) = config.read_color(&[ColorSet::Window.group_name()], role.key()) { + defaults.set(role, c); + } + } + defaults + }; + + let mut sets = DEFAULT_SETS; + let mut inactive_sets = [None; 7]; + for set in ColorSet::ALL { + let defaults = if set == ColorSet::Header { + header_defaults + } else { + DEFAULT_SETS[set.index()] + }; + let group = set.group_name(); + sets[set.index()] = read_set(config, &[group], defaults); + if config.group_exists(&[group, "Inactive"]) { + inactive_sets[set.index()] = Some(read_set(config, &[group, "Inactive"], defaults)); + } + } + + let inactive = "ColorEffects:Inactive"; + let change_selection_color = config + .read_bool(&[inactive], "ChangeSelectionColor") + .unwrap_or_else(|| config.read_bool(&[inactive], "Enable").unwrap_or(true)); + + let general = &["General"]; + let titlebar_is_accent_colored = config + .read_bool(general, "TitlebarIsAccentColored") + .or_else(|| config.read_bool(general, "accentActiveTitlebar")) + .unwrap_or(false); + let tint_factor = config + .has_key(general, "TintFactor") + .then(|| config.read_real(general, "TintFactor", DEFAULT_TINT_FACTOR)); + + let contrast = config.read_int(&["KDE"], "contrast", i64::from(DEFAULT_CONTRAST)); + + KdeColorScheme { + name: config.get(general, "Name").map(str::to_owned), + scheme_id: config.get(general, "ColorScheme").map(str::to_owned), + sets, + inactive_sets, + header_group_present: config.group_exists(&[ColorSet::Header.group_name()]), + disabled_effect: StateEffect::read( + config, + "ColorEffects:Disabled", + StateEffect::DISABLED_DEFAULT, + ), + inactive_effect: StateEffect::read(config, inactive, StateEffect::INACTIVE_DEFAULT), + change_selection_color, + wm: WmColors::read(config), + contrast: i32::try_from(contrast).unwrap_or(DEFAULT_CONTRAST), + frame_contrast: config + .read_real(&["KDE"], "frameContrast", DEFAULT_FRAME_CONTRAST) + .clamp(0.0, 1.0), + accent: config.read_color(general, "AccentColor"), + tint_factor, + titlebar_is_accent_colored, + source: SchemeSource(Some(Arc::new(config.clone()))), + } + } + + /// The configuration the scheme was read from, `None` for a built-in + /// scheme. It holds what the fields do not: which keys were present, + /// each value as written, `frameContrast` before clamping. + pub fn source(&self) -> Option<&KConfig> { + self.source.0.as_deref() + } + + /// The scheme as configuration text that [`KdeColorScheme::parse`] + /// reads back to an equal scheme. + /// + /// Groups and keys are written in sorted order, colours as KDE writes + /// them, `r,g,b` (`r,g,b,a` when translucent) in 8 bits, so a colour + /// read from a 9- or 12-digit `#` form comes back rounded to 8 bits. `[KDE] contrast` and `frameContrast` + /// are written only when they differ from their defaults, so a key the + /// scheme leaves at its default still falls back to `kdeglobals` when + /// the text is cascaded over it, as a stock `.colors` file does. For the + /// same reason `[ColorEffects:Disabled] Enable` is written only when it + /// is false: the stock files leave it out, and it reads as true when + /// absent. (`[ColorEffects:Inactive] Enable` is always written, as the + /// stock files write it.) + /// + /// A scheme without a Header group is written without one. Its Header + /// colours follow the Window group key by key, falling back to the + /// Header table where Window lacks a key; to keep that, a Window key + /// whose Header colour differs from the Window colour is left out, so it + /// reads back as absent from both. + pub fn to_kconfig_text(&self) -> String { + let mut out = String::new(); + + push_group(&mut out, &["ColorEffects:Disabled"]); + self.disabled_effect + .write(&mut out, Some(StateEffect::DISABLED_DEFAULT.enabled)); + push_group(&mut out, &["ColorEffects:Inactive"]); + push_entry( + &mut out, + "ChangeSelectionColor", + bool_text(self.change_selection_color), + ); + self.inactive_effect.write(&mut out, None); + + let mut ordered = ColorSet::ALL; + ordered.sort_by_key(|s| s.group_name()); + for set in ordered { + if set == ColorSet::Header && !self.header_group_present { + continue; + } + let colors = self.set(set); + push_group(&mut out, &[set.group_name()]); + for (key, role) in sorted_roles() { + let follows_window_default = set == ColorSet::Window + && !self.header_group_present + && self.set(ColorSet::Header).get(role) != colors.get(role); + if !follows_window_default { + push_entry(&mut out, key, &colors.get(role).to_kconfig_text()); + } + } + if let Some(inactive) = self.inactive_set(set) { + push_group(&mut out, &[set.group_name(), "Inactive"]); + for (key, role) in sorted_roles() { + push_entry(&mut out, key, &inactive.get(role).to_kconfig_text()); + } + } + } + + let general: Vec<(&str, String)> = [ + ("AccentColor", self.accent.map(KdeColor::to_kconfig_text)), + ("ColorScheme", self.scheme_id.as_deref().map(escape_value)), + ("Name", self.name.as_deref().map(escape_value)), + ("TintFactor", self.tint_factor.map(|f| f.to_string())), + ( + "TitlebarIsAccentColored", + self.titlebar_is_accent_colored.then(|| "true".to_owned()), + ), + ] + .into_iter() + .filter_map(|(k, v)| v.map(|v| (k, v))) + .collect(); + if !general.is_empty() { + push_group(&mut out, &["General"]); + for (key, value) in &general { + push_entry(&mut out, key, value); + } + } + + let contrast = (self.contrast != DEFAULT_CONTRAST).then(|| self.contrast.to_string()); + let frame = (self.frame_contrast != DEFAULT_FRAME_CONTRAST) + .then(|| self.frame_contrast.to_string()); + if contrast.is_some() || frame.is_some() { + push_group(&mut out, &["KDE"]); + if let Some(contrast) = contrast { + push_entry(&mut out, "contrast", &contrast); + } + if let Some(frame) = frame { + push_entry(&mut out, "frameContrast", &frame); + } + } + + let wm: Vec<_> = self + .wm + .fields() + .into_iter() + .filter_map(|(k, v)| v.map(|v| (k, v))) + .collect(); + if !wm.is_empty() { + push_group(&mut out, &["WM"]); + for (key, color) in wm { + push_entry(&mut out, key, &color.to_kconfig_text()); + } + } + out + } +} + +fn read_set(config: &KConfig, group: &[&str], defaults: SetColors) -> SetColors { + let mut colors = defaults; + for role in ColorRole::ALL { + if let Some(c) = config.read_color(group, role.key()) { + colors.set(role, c); + } + } + colors +} + +fn sorted_roles() -> impl Iterator { + let mut roles: Vec<_> = ColorRole::ALL.iter().map(|r| (r.key(), *r)).collect(); + roles.sort_by_key(|(k, _)| *k); + roles.into_iter() +} + +fn push_group(out: &mut String, path: &[&str]) { + if !out.is_empty() { + out.push('\n'); + } + for segment in path { + out.push('['); + out.push_str(segment); + out.push(']'); + } + out.push('\n'); +} + +fn push_entry(out: &mut String, key: &str, value: &str) { + out.push_str(key); + out.push('='); + out.push_str(value); + out.push('\n'); +} + +fn bool_text(value: bool) -> &'static str { + if value { "true" } else { "false" } +} diff --git a/crates/teksilo-kde-scheme/tests/accent.rs b/crates/teksilo-kde-scheme/tests/accent.rs new file mode 100644 index 000000000..af35e2171 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/accent.rs @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Accent application: the specification's worked examples (g.10), the ten +//! swatches through the selection rule (g.11), and the rules of section g +//! that the golden schemes do not exercise. + +use teksilo_kde_scheme::accent::readable_link; +use teksilo_kde_scheme::math::{accent_over, black_or_white_on, luma, oklab_tint}; +use teksilo_kde_scheme::{ + ACCENT_SWATCHES, ColorSet, ColorState, KConfig, KdeColor, KdeColorScheme, +}; + +fn rgb(r: u8, g: u8, b: u8) -> KdeColor { + KdeColor::rgb8(r, g, b) +} + +fn rgb8(c: KdeColor) -> [u8; 3] { + let [r, g, b, _] = c.to_rgba8(); + [r, g, b] +} + +fn parse_clean(text: &str) -> KdeColorScheme { + let (scheme, diagnostics) = KdeColorScheme::parse(text); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + scheme +} + +fn written(scheme: &KdeColorScheme) -> &KConfig { + scheme.source().expect("an accented scheme has a source") +} + +const BREEZE_BLUE: KdeColor = KdeColor::rgb8(61, 174, 233); + +// ---- Swatches (section h) ------------------------------------------------- + +#[test] +fn the_swatches_are_plasma_s_ten_in_order() { + let hex: Vec = ACCENT_SWATCHES + .iter() + .map(|c| { + let [r, g, b] = rgb8(*c); + format!("#{r:02x}{g:02x}{b:02x}") + }) + .collect(); + assert_eq!( + hex, + [ + "#e93a9a", "#e93d58", "#e9643a", "#e8cb2d", "#3dd425", "#00d3b8", "#3daee9", "#b875dc", + "#926ee4", "#686b6f", + ] + ); + assert!(ACCENT_SWATCHES.iter().all(|c| c.to_rgba8()[3] == 255)); +} + +#[test] +fn the_ten_swatches_give_the_specified_selection_through_with_accent() { + // (light selection, black text, dark selection, black text), spec g.11. + let table: [([u8; 3], bool, [u8; 3], bool); 10] = [ + ([239, 117, 184], true, [169, 47, 115], false), + ([239, 119, 138], true, [169, 49, 68], false), + ([239, 146, 117], true, [169, 76, 47], false), + ([238, 218, 107], true, [168, 148, 38], true), + ([119, 224, 102], true, [48, 155, 33], true), + ([76, 224, 205], true, [5, 154, 136], true), + ([119, 198, 239], true, [48, 128, 170], true), + ([205, 158, 230], true, [134, 88, 161], false), + ([178, 153, 236], true, [108, 83, 166], false), + ([149, 151, 154], true, [78, 81, 84], false), + ]; + let text = |black: bool| if black { [0, 0, 0] } else { [255, 255, 255] }; + for (swatch, (light, light_black, dark, dark_black)) in ACCENT_SWATCHES.into_iter().zip(table) { + for (scheme, fill, black) in [ + (KdeColorScheme::breeze_light(), light, light_black), + (KdeColorScheme::breeze_dark(), dark, dark_black), + ] { + let selection = *scheme.with_accent(swatch).set(ColorSet::Selection); + assert_eq!(rgb8(selection.background_normal), fill, "swatch {swatch}"); + assert_eq!( + rgb8(selection.background_alternate), + fill, + "swatch {swatch}" + ); + assert_eq!( + rgb8(selection.foreground_normal), + text(black), + "swatch {swatch}" + ); + assert_eq!( + rgb8(selection.foreground_inactive), + text(black), + "swatch {swatch}" + ); + } + } +} + +// ---- Worked examples (section g.10) --------------------------------------- + +#[test] +fn breeze_light_with_breeze_blue_matches_the_worked_example() { + let accented = KdeColorScheme::breeze_light().with_accent(BREEZE_BLUE); + let selection = accented.set(ColorSet::Selection); + assert_eq!(selection.background_normal, rgb(119, 198, 239)); + assert_eq!(selection.background_alternate, rgb(119, 198, 239)); + assert_eq!(selection.foreground_normal, rgb(0, 0, 0)); + assert_eq!(selection.foreground_inactive, rgb(0, 0, 0)); + assert_eq!(selection.foreground_active, BREEZE_BLUE); + assert_eq!(selection.decoration_focus, BREEZE_BLUE); + assert_eq!(selection.decoration_hover, BREEZE_BLUE); + + for set in [ + ColorSet::View, + ColorSet::Window, + ColorSet::Button, + ColorSet::Tooltip, + ColorSet::Header, + // Checked against its own 61,174,233: ratio 1, so darkened too. + ColorSet::Selection, + ] { + assert_eq!( + accented.set(set).foreground_link, + rgb(45, 127, 170), + "{set:?}" + ); + } + // 5.88 against 42,46,50: the accent itself. + assert_eq!( + accented.set(ColorSet::Complementary).foreground_link, + BREEZE_BLUE + ); + assert_eq!( + accented.set(ColorSet::Button).background_alternate, + rgb(118, 197, 238) + ); +} + +#[test] +fn breeze_dark_with_breeze_blue_matches_the_worked_example() { + let accented = KdeColorScheme::breeze_dark().with_accent(BREEZE_BLUE); + let selection = accented.set(ColorSet::Selection); + assert_eq!(selection.background_normal, rgb(48, 128, 170)); + assert_eq!(selection.foreground_normal, rgb(0, 0, 0)); + for set in [ + ColorSet::View, + ColorSet::Window, + ColorSet::Button, + ColorSet::Tooltip, + ColorSet::Header, + ] { + assert_eq!(accented.set(set).foreground_link, BREEZE_BLUE, "{set:?}"); + } + assert_eq!( + accented.set(ColorSet::Button).background_alternate, + rgb(55, 135, 177) + ); +} + +#[test] +fn breeze_light_with_its_own_accent_is_not_breeze_light() { + let light = KdeColorScheme::breeze_light(); + let accented = light.with_accent(BREEZE_BLUE); + assert_ne!(accented, light); + assert_ne!( + accented.set(ColorSet::Selection).background_normal, + light.set(ColorSet::Selection).background_normal + ); +} + +#[test] +fn the_link_is_made_readable_in_one_step() { + assert_eq!( + rgb8(readable_link(BREEZE_BLUE, rgb(255, 255, 255))), + [45, 127, 170] + ); + assert_eq!(readable_link(BREEZE_BLUE, rgb(20, 22, 24)), BREEZE_BLUE); + // A light accent on a lighter-than-black but darker background is + // lightened. + let lightened = readable_link(rgb(120, 120, 200), rgb(90, 90, 90)); + assert!(luma(lightened) > luma(rgb(120, 120, 200))); +} + +// ---- Rules of section g -------------------------------------------------- + +#[test] +fn applying_an_accent_twice_is_not_applying_it_once() { + let accent = ACCENT_SWATCHES[0]; + let once = KdeColorScheme::breeze_light().with_accent(accent); + let twice = once.with_accent(accent); + assert_ne!(once, twice); + // The first pass copies the stock Window colours into the Header + // [Inactive] group; the second copies the accented ones. + let inactive_focus = + |s: &KdeColorScheme| s.inactive_set(ColorSet::Header).unwrap().decoration_focus; + assert_eq!(inactive_focus(&once), BREEZE_BLUE); + assert_eq!(inactive_focus(&twice), accent); + // Applying to the original scheme again gives the same result. + assert_eq!(KdeColorScheme::breeze_light().with_accent(accent), once); +} + +#[test] +fn a_transparent_accent_copies_the_scheme() { + let light = KdeColorScheme::breeze_light(); + let same = light.with_accent(KdeColor::TRANSPARENT); + assert_eq!(same, light); + let out = written(&same); + assert_eq!( + out.get(&["Colors:Selection"], "BackgroundNormal"), + Some("61,174,233") + ); + // The effect groups are still written, with empty values where the + // scheme has no key. + assert_eq!(out.get(&["ColorEffects:Disabled"], "Enable"), Some("")); +} + +#[test] +fn a_transparent_accent_still_tints_inactive_subgroups_when_tinting_is_on() { + let scheme = parse_clean( + "[General]\nTintFactor=0.5\n[Colors:View]\nBackgroundNormal=200,40,40\n\ + [Colors:View][Inactive]\nBackgroundNormal=200,40,40\n", + ); + let out = scheme.with_accent(KdeColor::TRANSPARENT); + let out = written(&out); + // Not tinted outside [Inactive]: there is no accent. + assert_eq!( + out.get(&["Colors:View"], "BackgroundNormal"), + Some("200,40,40") + ); + let tinted = oklab_tint(rgb(200, 40, 40), KdeColor::TRANSPARENT, 0.5).to_kconfig_text(); + assert_ne!(tinted, "200,40,40"); + assert_eq!( + out.get(&["Colors:View", "Inactive"], "BackgroundNormal"), + Some(tinted.as_str()) + ); +} + +#[test] +fn copied_keys_keep_their_text_and_missing_ones_stay_missing() { + let scheme = parse_clean( + "[Colors:View]\nBackgroundNormal=#ffffff\nForegroundNormal=#232629\n[Colors:Window]\nForegroundVisited=#9b59b6\n", + ); + let out = scheme.with_accent(BREEZE_BLUE); + let out = written(&out); + assert_eq!( + out.get(&["Colors:View"], "ForegroundNormal"), + Some("#232629") + ); + assert_eq!(out.get(&["Colors:View"], "BackgroundAlternate"), None); + assert_eq!( + out.get(&["Colors:Window"], "ForegroundVisited"), + Some("#9b59b6") + ); + // The accent keys are written in every group, missing or not. + assert_eq!( + out.get(&["Colors:Tooltip"], "DecorationHover"), + Some("61,174,233") + ); + // A group without BackgroundNormal checks the link against black. + assert_eq!( + out.get(&["Colors:Tooltip"], "ForegroundLink"), + Some( + readable_link(BREEZE_BLUE, KdeColor::BLACK) + .to_kconfig_text() + .as_str() + ) + ); + // No Header group in the scheme: none written. + assert!(!out.group_exists(&["Colors:Header"])); +} + +#[test] +fn tinting_skips_keys_the_scheme_lacks() { + let scheme = parse_clean( + "[General]\nTintFactor=0.3\n[Colors:View]\nBackgroundNormal=255,255,255\nForegroundNormal=35,38,41\n", + ); + let accent = ACCENT_SWATCHES[4]; + let out = scheme.with_accent(accent); + let out = written(&out); + let tinted = |c: KdeColor| oklab_tint(c, accent, 0.3).to_kconfig_text(); + assert_eq!( + out.get(&["Colors:View"], "BackgroundNormal"), + Some(tinted(rgb(255, 255, 255)).as_str()) + ); + assert_eq!( + out.get(&["Colors:View"], "ForegroundNormal"), + Some(tinted(rgb(35, 38, 41)).as_str()) + ); + assert_eq!(out.get(&["Colors:View"], "BackgroundAlternate"), None); + assert_eq!(out.get(&["Colors:Window"], "BackgroundNormal"), None); + // The result carries no tint factor. + assert!(!out.has_key(&["General"], "TintFactor")); +} + +#[test] +fn breeze_classic_has_no_header_and_an_accent_title_bar() { + let classic = KdeColorScheme::breeze_classic(); + let accent = ACCENT_SWATCHES[1]; + let accented = classic.with_accent(accent); + let out = written(&accented); + assert!(!out.group_exists(&["Colors:Header"])); + assert!(!accented.header_group_present); + // The accent title bar without tinting: only the active title bar. + let window = rgb(239, 240, 241); + let fill = accent_over(accent, window); + assert_eq!(accented.wm.active_background, Some(fill)); + assert_eq!( + accented.wm.active_foreground, + Some(black_or_white_on(fill, true)) + ); + assert_eq!(accented.wm.inactive_background, Some(rgb(239, 240, 241))); + assert_eq!(accented.wm.active_blend, Some(rgb(252, 252, 252))); + assert!(!accented.titlebar_is_accent_colored); +} + +#[test] +fn a_scheme_without_wm_colours_gets_its_resolved_header_colours() { + let scheme = parse_clean( + "[Colors:Window]\nBackgroundNormal=200,201,202\nForegroundNormal=1,2,3\n\ + [ColorEffects:Inactive]\nEnable=true\nIntensityEffect=2\nIntensityAmount=0.5\n", + ); + let accented = scheme.with_accent(BREEZE_BLUE); + let active = scheme.state_colors(ColorState::Active, ColorSet::Header); + let inactive = scheme.state_colors(ColorState::Inactive, ColorSet::Header); + assert_eq!(active.background_normal, rgb(200, 201, 202)); + assert_ne!(inactive.background_normal, active.background_normal); + // Written as text, so compared in 8 bits. + let wm = accented.wm; + let eight = |c: Option| c.map(KdeColor::to_rgba8); + let want = |c: KdeColor| Some(c.to_rgba8()); + assert_eq!(eight(wm.active_background), want(active.background_normal)); + assert_eq!(eight(wm.active_foreground), want(active.foreground_normal)); + assert_eq!( + eight(wm.inactive_background), + want(inactive.background_normal) + ); + assert_eq!( + eight(wm.inactive_foreground), + want(inactive.foreground_normal) + ); + assert_eq!(eight(wm.active_blend), want(active.background_normal)); + assert_eq!(eight(wm.inactive_blend), want(inactive.background_normal)); +} + +#[test] +fn tinting_without_an_accent_title_bar_tints_both_title_bars() { + let scheme = parse_clean( + "[General]\nTintFactor=0.2\n[Colors:Window]\nBackgroundNormal=239,240,241\n[WM]\nactiveBackground=1,1,1\n", + ); + let accent = ACCENT_SWATCHES[2]; + let wm = scheme.with_accent(accent).wm; + // Written as text, so compared in 8 bits. + let eight = |c: Option| c.map(KdeColor::to_rgba8); + let tinted = oklab_tint(rgb(239, 240, 241), accent, 0.2); + let want = |c: KdeColor| Some(c.to_rgba8()); + assert_eq!(eight(wm.active_background), want(tinted)); + assert_eq!( + eight(wm.active_foreground), + want(black_or_white_on(tinted, true)) + ); + assert_eq!(eight(wm.inactive_background), want(tinted)); + assert_eq!( + eight(wm.inactive_foreground), + want(black_or_white_on(tinted, false)) + ); +} + +#[test] +fn the_kde_group_is_written_as_read_or_with_its_defaults() { + let out = parse_clean("").with_accent(BREEZE_BLUE); + let out = written(&out); + assert_eq!(out.get(&["KDE"], "contrast"), Some("7")); + assert_eq!(out.get(&["KDE"], "frameContrast"), Some("0.2")); + + let raw = parse_clean("[KDE]\ncontrast=3\nframeContrast=1.5\n"); + let out = raw.with_accent(BREEZE_BLUE); + assert_eq!(written(&out).get(&["KDE"], "contrast"), Some("3")); + // Not clamped when written; clamped when read back. + assert_eq!(written(&out).get(&["KDE"], "frameContrast"), Some("1.5")); + assert_eq!(out.frame_contrast, 1.0); +} + +#[test] +fn every_effect_key_is_written_and_empty_ones_read_as_enabled() { + let accented = parse_clean("").with_accent(BREEZE_BLUE); + let out = written(&accented); + for group in ["ColorEffects:Inactive", "ColorEffects:Disabled"] { + assert_eq!(out.keys(&[group]).len(), 9, "{group}"); + assert_eq!(out.get(&[group], "Enable"), Some(""), "{group}"); + } + // A scheme with no Inactive effects group gets them switched on, as + // KDE's empty Enable= reads. + assert!(accented.inactive_effect.enabled); + assert!(accented.disabled_effect.enabled); +} + +#[test] +fn the_accented_scheme_keeps_its_name() { + let accented = KdeColorScheme::breeze_dark().with_accent(BREEZE_BLUE); + assert_eq!(accented.name.as_deref(), Some("Breeze Dark")); + assert_eq!(accented.scheme_id.as_deref(), Some("BreezeDark")); + assert_eq!(accented.accent, None); +} + +#[test] +fn a_teksilo_color_is_accepted() { + let from_tokens = KdeColorScheme::breeze_light().with_accent(BREEZE_BLUE.to_color()); + assert_eq!( + from_tokens, + KdeColorScheme::breeze_light().with_accent(BREEZE_BLUE) + ); +} diff --git a/crates/teksilo-kde-scheme/tests/breeze_files.rs b/crates/teksilo-kde-scheme/tests/breeze_files.rs new file mode 100644 index 000000000..bd68d0018 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/breeze_files.rs @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The stock Breeze constants against the vendored breeze 6.8.0 `.colors` +//! files, and the source a parsed file keeps. +//! +//! The fixtures are LGPL-2.0-or-later and are left out of the published +//! crate, and so is this file (the `exclude` list in `Cargo.toml`): a test +//! target that reads them could not compile from the package. Every test +//! that needs no fixture lives in `scheme.rs`, which is published. The +//! last test checks that every test target reading the fixtures is +//! excluded and named in NOTICE section 1.5 and in the `exclude` comment. + +use teksilo_kde_scheme::KdeColorScheme; + +const BREEZE_LIGHT: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/breeze-v6.8.0/BreezeLight.colors" +)); +const BREEZE_DARK: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/breeze-v6.8.0/BreezeDark.colors" +)); +const BREEZE_CLASSIC: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/breeze-v6.8.0/BreezeClassic.colors" +)); + +fn parse_clean(text: &str) -> KdeColorScheme { + let (scheme, diagnostics) = KdeColorScheme::parse(text); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + scheme +} + +#[test] +fn the_breeze_light_constant_equals_the_breeze_light_file() { + assert_eq!(parse_clean(BREEZE_LIGHT), KdeColorScheme::breeze_light()); +} + +#[test] +fn the_breeze_dark_constant_equals_the_breeze_dark_file() { + assert_eq!(parse_clean(BREEZE_DARK), KdeColorScheme::breeze_dark()); +} + +#[test] +fn the_breeze_classic_constant_equals_the_breeze_classic_file() { + assert_eq!( + parse_clean(BREEZE_CLASSIC), + KdeColorScheme::breeze_classic() + ); +} + +// ---- Source --------------------------------------------------------------- + +#[test] +fn a_parsed_scheme_keeps_its_source_and_equality_ignores_it() { + let scheme = parse_clean(BREEZE_LIGHT); + let source = scheme.source().unwrap(); + assert_eq!( + source.get(&["Colors:View"], "BackgroundNormal"), + Some("255,255,255") + ); + assert!(!source.has_key(&["KDE"], "frameContrast")); + assert!(!source.has_key(&["ColorEffects:Disabled"], "Enable")); + assert!(KdeColorScheme::breeze_light().source().is_none()); + assert_eq!(scheme, KdeColorScheme::breeze_light()); +} + +// ---- Licensing record ----------------------------------------------------- + +/// The test targets of this crate that read the vendored fixtures: every +/// `tests/*.rs` whose text names the fixtures directory. +fn fixture_readers() -> Vec { + let tests = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests"); + let mut readers: Vec = std::fs::read_dir(&tests) + .expect("tests/ lists") + .map(|entry| entry.expect("tests/ entry").path()) + .filter(|path| path.extension().is_some_and(|ext| ext == "rs")) + .filter(|path| { + std::fs::read_to_string(path) + .expect("test file reads") + .contains("tests/fixtures/breeze-v6.8.0/") + }) + .map(|path| { + format!( + "tests/{}", + path.file_name().unwrap().to_str().expect("UTF-8 name") + ) + }) + .collect(); + readers.sort(); + readers +} + +/// `text` with every run of whitespace collapsed to one space, so a path +/// that a wrapped paragraph puts at a line start still matches. +fn collapse_whitespace(text: &str) -> String { + text.split_whitespace().collect::>().join(" ") +} + +/// Every test target that reads the LGPL fixtures is excluded from the +/// published crate, and is named both in the comment above that `exclude` +/// list and in NOTICE section 1.5, the licensing record of where the +/// fixtures are used. +#[test] +fn the_licensing_record_names_every_test_that_reads_the_fixtures() { + let readers = fixture_readers(); + assert!( + readers.contains(&"tests/breeze_files.rs".to_owned()), + "the scan finds this file: {readers:?}" + ); + + let manifest = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml")) + .expect("Cargo.toml reads"); + let exclude_at = manifest.find("\nexclude = [").expect("an exclude list"); + let exclude_end = exclude_at + manifest[exclude_at..].find(']').expect("the list closes"); + let exclude_list = &manifest[exclude_at..exclude_end]; + let comment: String = manifest[..exclude_at] + .lines() + .rev() + .take_while(|line| line.starts_with('#')) + .collect::>() + .into_iter() + .rev() + .map(|line| line.trim_start_matches('#')) + .collect::>() + .join(" "); + let comment = collapse_whitespace(&comment); + + let notice = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/../../NOTICE")) + .expect("NOTICE reads"); + let section_at = notice.find("\n1.5 KDE").expect("NOTICE section 1.5"); + let section_end = section_at + + notice[section_at..] + .find("\n2. ") + .expect("NOTICE section 2 follows"); + let section = collapse_whitespace(¬ice[section_at..section_end]); + + for reader in &readers { + assert!( + exclude_list.contains(&format!("\"{reader}\"")), + "{reader} reads the LGPL fixtures but is not in the exclude list" + ); + assert!( + comment.contains(reader.as_str()), + "{reader} reads the LGPL fixtures but the comment above the exclude \ + list does not name it: {comment}" + ); + assert!( + section.contains(reader.as_str()), + "{reader} reads the LGPL fixtures but NOTICE section 1.5 does not name it" + ); + } +} diff --git a/crates/teksilo-kde-scheme/tests/cascade.rs b/crates/teksilo-kde-scheme/tests/cascade.rs new file mode 100644 index 000000000..ac82da977 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/cascade.rs @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The cascade, the file locations and `[$e]` expansion. The environment +//! is always injected as a closure; nothing here reads or changes the +//! process environment. + +use std::collections::HashMap; +use std::path::PathBuf; + +use teksilo_kde_scheme::{ + ConfigLayerPath, KConfig, KdeConfigLocations, KdeDesktop, KdeDesktopLayers, LayerKind, + SchemeOrigin, is_kde_session, +}; + +/// An environment getter over a fixed table. +fn env(vars: &[(&str, &str)]) -> impl Fn(&str) -> Option + use<> { + let table: HashMap = vars + .iter() + .map(|(k, v)| ((*k).to_owned(), (*v).to_owned())) + .collect(); + move |name| table.get(name).cloned() +} + +fn layer(text: &str) -> KConfig { + let (config, diagnostics) = KConfig::parse(text); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + config +} + +fn user(text: &str) -> KConfig { + layer(text).with_kind(LayerKind::User) +} + +fn paths(layers: &[ConfigLayerPath]) -> Vec<(String, LayerKind)> { + layers + .iter() + .map(|l| (l.path.to_string_lossy().replace('\\', "/"), l.kind)) + .collect() +} + +// ---- Precedence ------------------------------------------------------- + +#[test] +fn a_higher_layer_wins_over_a_lower_one() { + let merged = KConfig::cascade(&[layer("[G]\nA=low\nB=low\n"), user("[G]\nA=high\n")]); + assert_eq!(merged.get(&["G"], "A"), Some("high")); + assert_eq!(merged.get(&["G"], "B"), Some("low")); +} + +#[test] +fn a_deleted_entry_hides_the_lower_value_until_a_higher_layer_sets_it_again() { + let hidden = KConfig::cascade(&[layer("[G]\nA=low\n"), layer("[G]\nA[$d]\n")]); + assert_eq!(hidden.get(&["G"], "A"), None); + let back = KConfig::cascade(&[ + layer("[G]\nA=low\n"), + layer("[G]\nA[$d]\n"), + user("[G]\nA=again\n"), + ]); + assert_eq!(back.get(&["G"], "A"), Some("again")); +} + +#[test] +fn an_immutable_entry_blocks_every_higher_layer() { + let merged = KConfig::cascade(&[ + layer("[KDE]\nwidgetStyle[$i]=Breeze\n"), + layer("[KDE]\nwidgetStyle=Fusion\n"), + user("[KDE]\nwidgetStyle=Oxygen\n"), + ]); + assert_eq!(merged.get(&["KDE"], "widgetStyle"), Some("Breeze")); +} + +#[test] +fn an_immutable_group_ignores_every_entry_of_the_user_layer() { + let merged = KConfig::cascade(&[ + layer("[General][$i]\nColorScheme=BreezeLight\n"), + user("[General]\nColorScheme=BreezeDark\nNew=1\n"), + ]); + assert_eq!(merged.get(&["General"], "ColorScheme"), Some("BreezeLight")); + assert_eq!(merged.get(&["General"], "New"), None); +} + +#[test] +fn an_immutable_group_takes_no_new_keys_from_a_higher_default_layer() { + let merged = KConfig::cascade(&[ + layer("[General][$i]\nColorScheme=BreezeLight\n"), + layer("[General]\nNew=1\n"), + ]); + assert_eq!(merged.get(&["General"], "New"), None); + assert_eq!(merged.get(&["General"], "ColorScheme"), Some("BreezeLight")); +} + +#[test] +fn a_lower_immutable_group_does_not_lock_its_nested_groups() { + let merged = KConfig::cascade(&[ + layer("[Colors:Header][$i]\nA=1\n[Colors:Header][Inactive]\nB=1\n"), + user("[Colors:Header][Inactive]\nB=2\n"), + ]); + assert_eq!(merged.get(&["Colors:Header", "Inactive"], "B"), Some("2")); +} + +#[test] +fn a_file_immutable_layer_stops_every_higher_layer() { + let merged = KConfig::cascade(&[ + layer("[General]\nA=1\n[$i]\n[KDE]\nB=1\n"), + layer("[General]\nA=2\nC=2\n"), + user("[Other]\nD=3\n"), + ]); + assert_eq!(merged.get(&["General"], "A"), Some("1")); + assert_eq!(merged.get(&["General"], "C"), None); + assert_eq!(merged.get(&["Other"], "D"), None); + assert!(merged.is_file_immutable()); + // The merge stays a lower layer that blocks anything cascaded on it. + let again = KConfig::cascade(&[merged, user("[General]\nC=3\n")]); + assert_eq!(again.get(&["General"], "C"), None); +} + +#[test] +fn a_file_can_add_to_its_own_immutable_group_later() { + let merged = KConfig::cascade(&[layer("[G][$i]\nA=1\n[Other]\nX=1\n[G]\nB=2\n")]); + assert_eq!(merged.get(&["G"], "B"), Some("2")); + assert!(merged.is_group_immutable(&["G"])); +} + +// ---- This machine's layout ------------------------------------------- + +/// A Plasma session where `ColorScheme` lives only in +/// `~/.config/kdedefaults/kdeglobals`, `/etc/xdg/kdeglobals` names the +/// look-and-feel package, and the user's own `kdeglobals` holds the colour +/// groups but no `ColorScheme`. +#[test] +fn the_kdedefaults_layer_supplies_the_scheme_name_under_the_user_file() { + let locations = KdeConfigLocations::from_env(env(&[ + ("HOME", "/home/ada"), + ("XDG_CONFIG_DIRS", "/home/ada/.config/kdedefaults:/etc/xdg"), + ("XDG_CURRENT_DESKTOP", "KDE"), + ("KDE_FULL_SESSION", "true"), + ])); + assert_eq!( + paths(&locations.kdeglobals_cascade()), + vec![ + ("/etc/kde5rc".to_owned(), LayerKind::Default), + ("/etc/xdg/system.kdeglobals".to_owned(), LayerKind::Default), + ( + "/home/ada/.config/kdedefaults/system.kdeglobals".to_owned(), + LayerKind::Default + ), + ("/etc/xdg/kdeglobals".to_owned(), LayerKind::Default), + ( + "/home/ada/.config/kdedefaults/kdeglobals".to_owned(), + LayerKind::Default + ), + ( + "/home/ada/.config/system.kdeglobals".to_owned(), + LayerKind::Default + ), + ("/home/ada/.config/kdeglobals".to_owned(), LayerKind::User), + ] + ); + + // Only three of those files exist. + let files: HashMap<&str, &str> = [ + ( + "/etc/xdg/kdeglobals", + "[General]\nXftHintStyle=hintslight\n\n[KDE]\nLookAndFeelPackage=org.example.desktop\nSingleClick=false\n", + ), + ( + "/home/ada/.config/kdedefaults/kdeglobals", + "[General]\nColorScheme=BreezeLight\n\n[Icons]\nTheme=breeze\n\n[KDE]\nwidgetStyle=Breeze\n", + ), + ( + "/home/ada/.config/kdeglobals", + "[Colors:View]\nBackgroundNormal=255,255,255\n\n[KDE]\nLookAndFeelPackage=org.kde.breeze.desktop\ncontrast=4\nframeContrast=0.2\n", + ), + ] + .into_iter() + .collect(); + let layers: Vec = locations + .kdeglobals_cascade() + .into_iter() + .filter_map(|l| { + let key = l.path.to_string_lossy().replace('\\', "/"); + files + .get(key.as_str()) + .map(|text| layer(text).with_kind(l.kind)) + }) + .collect(); + assert_eq!(layers.len(), 3); + + let merged = KConfig::cascade(&layers); + assert_eq!(merged.get(&["General"], "ColorScheme"), Some("BreezeLight")); + assert_eq!( + merged.get(&["KDE"], "LookAndFeelPackage"), + Some("org.kde.breeze.desktop") + ); + assert_eq!(merged.get(&["KDE"], "widgetStyle"), Some("Breeze")); + assert_eq!(merged.get(&["General"], "XftHintStyle"), Some("hintslight")); + assert!(merged.group_exists(&["Colors:View"])); +} + +#[test] +fn a_kde_session_prepends_kdedefaults_when_the_environment_lacks_it() { + let locations = KdeConfigLocations::from_env(env(&[ + ("HOME", "/home/ada"), + ("XDG_CURRENT_DESKTOP", "KDE"), + ])); + assert_eq!( + locations.config_dirs, + vec![ + PathBuf::from("/home/ada/.config/kdedefaults"), + PathBuf::from("/etc/xdg") + ] + ); +} + +#[test] +fn outside_a_kde_session_kdedefaults_is_not_added() { + let locations = KdeConfigLocations::from_env(env(&[ + ("HOME", "/home/ada"), + ("XDG_CURRENT_DESKTOP", "GNOME"), + ])); + assert_eq!(locations.config_dirs, vec![PathBuf::from("/etc/xdg")]); +} + +#[test] +fn kdedefaults_is_not_listed_twice() { + let locations = KdeConfigLocations::from_env(env(&[ + ("HOME", "/home/ada"), + ("XDG_CONFIG_DIRS", "/etc/xdg:/home/ada/.config/kdedefaults/"), + ("KDE_FULL_SESSION", "true"), + ])); + assert_eq!(locations.config_dirs.len(), 2); + assert_eq!(locations.config_dirs[0], PathBuf::from("/etc/xdg")); +} + +// ---- Locations ------------------------------------------------------- + +#[test] +fn xdg_defaults_apply_when_the_variables_are_unset_empty_or_relative() { + let locations = KdeConfigLocations::from_env(env(&[ + ("HOME", "/home/ada"), + ("XDG_CONFIG_HOME", ""), + ("XDG_DATA_HOME", "relative/data"), + ("XDG_CONFIG_DIRS", "relative:"), + ])); + assert_eq!( + locations.config_home, + Some(PathBuf::from("/home/ada/.config")) + ); + assert_eq!( + locations.data_home, + Some(PathBuf::from("/home/ada/.local/share")) + ); + assert_eq!( + locations.cache_home, + Some(PathBuf::from("/home/ada/.cache")) + ); + assert_eq!(locations.config_dirs, vec![PathBuf::from("/etc/xdg")]); + assert_eq!( + locations.data_dirs, + vec![ + PathBuf::from("/usr/local/share"), + PathBuf::from("/usr/share") + ] + ); + assert!(!locations.kde_session); +} + +#[test] +fn explicit_xdg_variables_are_used_in_the_order_listed() { + let locations = KdeConfigLocations::from_env(env(&[ + ("XDG_CONFIG_HOME", "/cfg"), + ("XDG_CONFIG_DIRS", "/a::relative:/b"), + ("XDG_DATA_HOME", "/data"), + ("XDG_DATA_DIRS", "/d1:/d2"), + ])); + assert_eq!(locations.home, None); + assert_eq!( + locations.config_dirs, + vec![PathBuf::from("/a"), PathBuf::from("/b")] + ); + assert_eq!( + paths(&locations.breezerc_cascade()), + vec![ + ("/b/breezerc".to_owned(), LayerKind::Default), + ("/a/breezerc".to_owned(), LayerKind::Default), + ("/cfg/breezerc".to_owned(), LayerKind::User), + ] + ); + let candidates: Vec = locations + .color_scheme_file("BreezeDark") + .iter() + .map(|p| p.to_string_lossy().replace('\\', "/")) + .collect(); + assert_eq!( + candidates, + vec![ + "/data/color-schemes/BreezeDark.colors", + "/d1/color-schemes/BreezeDark.colors", + "/d2/color-schemes/BreezeDark.colors", + ] + ); +} + +#[test] +fn kde_skip_kderc_leaves_out_etc_kde5rc() { + let locations = KdeConfigLocations::from_env(env(&[("HOME", "/h"), ("KDE_SKIP_KDERC", "1")])); + assert!( + !paths(&locations.kdeglobals_cascade()) + .iter() + .any(|(p, _)| p == "/etc/kde5rc") + ); +} + +#[test] +fn without_a_home_there_is_no_user_layer() { + let locations = KdeConfigLocations::from_env(env(&[])); + assert!( + locations + .kdeglobals_cascade() + .iter() + .all(|l| l.kind == LayerKind::Default) + ); + assert_eq!(locations.data_file_candidates("x").len(), 2); +} + +#[test] +fn a_kde_color_scheme_path_variable_is_not_read() { + let with = KdeConfigLocations::from_env(env(&[ + ("HOME", "/home/ada"), + ("KDE_COLOR_SCHEME_PATH", "/tmp/Evil.colors"), + ])); + let without = KdeConfigLocations::from_env(env(&[("HOME", "/home/ada")])); + assert_eq!(with, without); + assert!( + with.color_scheme_file("BreezeLight") + .iter() + .all(|p| !p.to_string_lossy().contains("Evil")) + ); +} + +#[test] +fn kde_session_detection_reads_the_desktop_list_and_the_full_session_flag() { + assert!(is_kde_session(env(&[("XDG_CURRENT_DESKTOP", "KDE")]))); + assert!(is_kde_session(env(&[( + "XDG_CURRENT_DESKTOP", + "ubuntu:KDE" + )]))); + assert!(is_kde_session(env(&[("KDE_FULL_SESSION", "true")]))); + assert!(!is_kde_session(env(&[("XDG_CURRENT_DESKTOP", "KDEx")]))); + assert!(is_kde_session(env(&[("XDG_CURRENT_DESKTOP", "kde")]))); + assert!(is_kde_session(env(&[( + "XDG_CURRENT_DESKTOP", + "X-Generic:Kde" + )]))); + assert!(!is_kde_session(env(&[("XDG_CURRENT_DESKTOP", "X-KDE")]))); + assert!(!is_kde_session(env(&[("KDE_FULL_SESSION", "1")]))); + assert!(!is_kde_session(env(&[]))); +} + +#[test] +fn the_scheme_fallback_is_needed_only_without_a_view_group() { + let locations = + KdeConfigLocations::from_env(env(&[("HOME", "/h"), ("XDG_DATA_DIRS", "/usr/share")])); + let var = env(&[]); + let expansion = locations.expansion_env(&var); + + let with_colors = layer("[Colors:View]\nBackgroundNormal=1,2,3\n"); + assert_eq!( + locations.scheme_fallback_candidates(&with_colors, &expansion), + None + ); + + let named = + layer("[General]\nColorScheme=BreezeDark\n[KDE]\nLookAndFeelPackage=org.example.desktop\n"); + let candidates: Vec = locations + .scheme_fallback_candidates(&named, &expansion) + .unwrap() + .iter() + .map(|p| p.to_string_lossy().replace('\\', "/")) + .collect(); + assert_eq!( + candidates, + vec![ + "/h/.local/share/plasma/look-and-feel/org.example.desktop/contents/colors", + "/usr/share/plasma/look-and-feel/org.example.desktop/contents/colors", + "/h/.local/share/color-schemes/BreezeDark.colors", + "/usr/share/color-schemes/BreezeDark.colors", + "/h/.local/share/color-schemes/BreezeLight.colors", + "/usr/share/color-schemes/BreezeLight.colors", + ] + ); + + let defaults: Vec = locations + .scheme_fallback_candidates(&KConfig::default(), &expansion) + .unwrap() + .iter() + .map(|p| p.to_string_lossy().replace('\\', "/")) + .collect(); + assert_eq!( + defaults[0], + "/h/.local/share/plasma/look-and-feel/org.kde.breeze.desktop/contents/colors" + ); + assert_eq!( + defaults[2], + "/h/.local/share/color-schemes/BreezeLight.colors" + ); +} + +// ---- [$e] expansion ---------------------------------------------------- + +#[test] +fn dollar_e_entries_expand_only_through_the_injected_environment() { + let locations = KdeConfigLocations::from_env(env(&[("HOME", "/home/ada")])); + let var = env(&[("HOME", "/home/ada"), ("FONT", "Hack"), ("EMPTY", "")]); + let expansion = locations.expansion_env(&var); + let config = layer( + "[G]\nPlain=$HOME/x\nHome[$e]=$HOME/x\nBraced[$e]=${FONT},10\nDollars[$e]=a$$b\nUnset[$e]=[$NOPE]\n\ + Empty[$e]=[$EMPTY]\nEmptyBraces[$e]=a${}b\nUnclosed[$e]=a${FONT\nTrailing[$e]=cost 5$\n\ + Qt[$e]=$QT_DATA_HOME/x\nUnicode[$e]=$FONTé\n", + ); + let g = &["G"]; + let read = |key| config.read_string(g, key, &expansion); + assert_eq!(read("Plain").as_deref(), Some("$HOME/x")); + assert_eq!(read("Home").as_deref(), Some("/home/ada/x")); + assert_eq!(read("Braced").as_deref(), Some("Hack,10")); + assert_eq!(read("Dollars").as_deref(), Some("a$b")); + assert_eq!(read("Unset").as_deref(), Some("[]")); + assert_eq!(read("Empty").as_deref(), Some("[]")); + assert_eq!(read("EmptyBraces").as_deref(), Some("ab")); + assert_eq!(read("Unclosed").as_deref(), Some("aHack")); + assert_eq!(read("Trailing").as_deref(), Some("cost 5$")); + assert_eq!(read("Qt").as_deref(), Some("/home/ada/.local/share/x")); + // Letters in the Unicode sense continue the name. + assert_eq!(read("Unicode").as_deref(), Some("")); + // The raw value is never expanded. + assert_eq!(config.get(g, "Home"), Some("$HOME/x")); +} + +#[test] +fn a_command_substitution_is_never_executed() { + let locations = KdeConfigLocations::from_env(env(&[])); + let asked = std::cell::RefCell::new(Vec::new()); + let var = |name: &str| { + asked.borrow_mut().push(name.to_owned()); + None + }; + let expansion = locations.expansion_env(&var); + let config = layer("[G]\nCmd[$e]=$(touch /tmp/pwned)x\nBack[$e]=`id`\n"); + assert_eq!( + config.read_string(&["G"], "Cmd", &expansion).as_deref(), + Some("(touch /tmp/pwned)x") + ); + assert_eq!( + config.read_string(&["G"], "Back", &expansion).as_deref(), + Some("`id`") + ); + // Only variable names are looked up, and `(` starts none. + assert!(asked.borrow().is_empty(), "{:?}", asked.borrow()); +} + +// ---- A desktop built from these layers -------------------------------- + +#[test] +fn a_desktop_without_colour_groups_reads_the_scheme_file_over_kdeglobals() { + let var = env(&[]); + let locations = KdeConfigLocations::from_env(env(&[])); + let expansion = locations.expansion_env(&var); + // No accent: an accent would be applied to the scheme file, and the + // application writes its own frameContrast (see the next test). + let kdeglobals = [user("[KDE]\nframeContrast=0.5\n")]; + let scheme_file = layer("[Colors:Window]\nBackgroundNormal=10,20,30\n[KDE]\ncontrast=4\n"); + let desktop = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + breezerc: &[], + scheme_file: Some(&scheme_file), + }, + &expansion, + ); + assert_eq!(desktop.scheme_origin, SchemeOrigin::SchemeFile); + assert_eq!(desktop.scheme.contrast, 4); + // frameContrast is not in the scheme file, so kdeglobals supplies it. + assert_eq!(desktop.scheme.frame_contrast, 0.5); + assert_eq!( + desktop.scheme.sets[1].background_normal, + teksilo_kde_scheme::KdeColor::rgb8(10, 20, 30) + ); + assert_eq!(desktop.user_accent, None); + + let builtin = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + breezerc: &[], + scheme_file: None, + }, + &expansion, + ); + assert_eq!(builtin.scheme_origin, SchemeOrigin::BuiltIn); + assert_eq!( + builtin.scheme.sets, + teksilo_kde_scheme::KdeColorScheme::breeze_light().sets + ); + assert_eq!(builtin.scheme.frame_contrast, 0.5); +} + +#[test] +fn a_desktop_without_colour_groups_applies_the_user_accent_to_the_scheme_file() { + use teksilo_kde_scheme::{ColorSet, KdeColor, KdeColorScheme}; + + let var = env(&[]); + let locations = KdeConfigLocations::from_env(env(&[])); + let expansion = locations.expansion_env(&var); + let accent = KdeColor::rgb8(233, 61, 61); + let kdeglobals = [user( + "[KDE]\nframeContrast=0.5\n[General]\nAccentColor=233,61,61\n", + )]; + let file_text = "[Colors:Window]\nBackgroundNormal=10,20,30\n[KDE]\ncontrast=4\n"; + let scheme_file = layer(file_text); + let desktop = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + breezerc: &[], + scheme_file: Some(&scheme_file), + }, + &expansion, + ); + assert_eq!(desktop.scheme_origin, SchemeOrigin::SchemeFile); + assert_eq!(desktop.user_accent, Some(accent)); + let accented = KdeColorScheme::parse(file_text).0.with_accent(accent); + assert_eq!(desktop.scheme.sets, accented.sets); + assert_eq!( + desktop.scheme.set(ColorSet::Window).decoration_focus, + accent + ); + assert_eq!(desktop.scheme.contrast, 4); + // The accent application writes frameContrast, 0.2 when the scheme file + // has none, over kdeglobals' 0.5. + assert_eq!(desktop.scheme.frame_contrast, 0.2); + + let builtin = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + breezerc: &[], + scheme_file: None, + }, + &expansion, + ); + assert_eq!(builtin.scheme_origin, SchemeOrigin::BuiltIn); + let light = KdeColorScheme::breeze_light().with_accent(accent); + assert_eq!(builtin.scheme.sets, light.sets); + assert_eq!(builtin.scheme.inactive_sets, light.inactive_sets); + assert_eq!(builtin.scheme.wm, light.wm); +} + +#[test] +fn a_file_immutable_kdeglobals_still_reads_the_scheme_file_under_its_locked_entries() { + use teksilo_kde_scheme::{ColorSet, KdeColor, KdeColorScheme}; + + let var = env(&[]); + let locations = KdeConfigLocations::from_env(env(&[])); + let expansion = locations.expansion_env(&var); + let accent = KdeColor::rgb8(233, 61, 61); + // A whole-file lock in a system kdeglobals that names a scheme and an + // accent but writes no colour groups. The user layer above it is not + // read, so its accent is not the one applied. + let kdeglobals = [ + layer( + "[$i]\n[General]\nColorScheme=BreezeDark\nAccentColor=233,61,61\n\ + [KDE]\nframeContrast=0.5\n", + ), + user("[General]\nAccentColor=1,2,3\n"), + ]; + let scheme_file = layer(&KdeColorScheme::breeze_dark().to_kconfig_text()); + let desktop = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + breezerc: &[], + scheme_file: Some(&scheme_file), + }, + &expansion, + ); + assert_eq!(desktop.scheme_origin, SchemeOrigin::SchemeFile); + assert_eq!(desktop.user_accent, Some(accent)); + // The scheme file is read, and the accent is applied to it. + let accented = KdeColorScheme::breeze_dark().with_accent(accent); + assert_eq!(desktop.scheme.sets, accented.sets); + assert_eq!( + desktop.scheme.set(ColorSet::Window).background_normal, + KdeColor::rgb8(32, 35, 38) + ); + assert_eq!( + desktop.scheme.set(ColorSet::Window).decoration_focus, + accent + ); + // A locked kdeglobals entry still wins over what the accented scheme + // file writes (0.2 without the lock). + assert_eq!(desktop.scheme.frame_contrast, 0.5); + + let builtin = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + breezerc: &[], + scheme_file: None, + }, + &expansion, + ); + assert_eq!(builtin.scheme_origin, SchemeOrigin::BuiltIn); + assert_eq!( + builtin.scheme.sets, + KdeColorScheme::breeze_light().with_accent(accent).sets + ); +} + +#[test] +fn colour_groups_in_kdeglobals_are_not_accented_again() { + use teksilo_kde_scheme::{ColorSet, KdeColor}; + + let var = env(&[]); + let locations = KdeConfigLocations::from_env(env(&[])); + let expansion = locations.expansion_env(&var); + let kdeglobals = [user( + "[Colors:View]\nDecorationFocus=1,2,3\n[General]\nAccentColor=233,61,61\n", + )]; + let desktop = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + breezerc: &[], + scheme_file: None, + }, + &expansion, + ); + assert_eq!(desktop.scheme_origin, SchemeOrigin::Kdeglobals); + assert_eq!( + desktop.scheme.set(ColorSet::View).decoration_focus, + KdeColor::rgb8(1, 2, 3) + ); +} + +#[test] +fn a_transparent_user_accent_is_not_applied_in_the_fallback() { + let var = env(&[]); + let locations = KdeConfigLocations::from_env(env(&[])); + let expansion = locations.expansion_env(&var); + let kdeglobals = [user("[General]\nAccentColor=transparent\n")]; + let desktop = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + breezerc: &[], + scheme_file: None, + }, + &expansion, + ); + assert_eq!(desktop.user_accent, None); + let light = teksilo_kde_scheme::KdeColorScheme::breeze_light(); + assert_eq!(desktop.scheme.sets, light.sets); + assert_eq!(desktop.scheme.wm, light.wm); +} diff --git a/crates/teksilo-kde-scheme/tests/clean_room_vocabulary.rs b/crates/teksilo-kde-scheme/tests/clean_room_vocabulary.rs new file mode 100644 index 000000000..94f45476d --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/clean_room_vocabulary.rs @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The clean-room vocabulary guard. +//! +//! The KDE crates are written from Teksilo's specification +//! (`engineering/docs/kde-breeze-spec.md`), never from KDE's or Qt's code. +//! An identifier that only exists upstream (a KDE function or class name, a +//! Qt type) appearing in the source is a sign that the code was shaped +//! after upstream rather than after the specification, so this test fails +//! on any of them, naming the file and line. +//! +//! The words are matched whole and case-sensitively. A `//` comment line +//! that cites `doc.qt.io`, Qt's public documentation, may name the Qt words +//! ([`QT_WORDS`]) it cites; every KDE word is still reported there, since +//! Qt's documentation cannot be where a KDE identifier comes from. +//! Test fixtures (verbatim upstream files) are not scanned, and neither is +//! the word list below. +//! +//! To guard another crate, add its directories to [`SCANNED`]. The paths are +//! relative to the repository root, and a missing directory fails the test +//! rather than passing it unscanned, as does a directory with no file in it +//! or a scan that misses `src/math.rs` or this file. Every file found is +//! read, a non-UTF-8 one through a lossy decoding. This file reads the +//! repository tree, so it is left out of the published package. + +use std::fs; +use std::path::{Path, PathBuf}; + +/// The directories scanned, recursively, relative to the repository root. +const SCANNED: &[&str] = &[ + "crates/teksilo-kde-scheme/src", + "crates/teksilo-kde-scheme/tests", + "crates/teksilo-theme-kde/src", + "crates/teksilo-theme-kde/tests", +]; + +/// Directories inside [`SCANNED`] that hold verbatim upstream files. +const SKIPPED: &[&str] = &["crates/teksilo-kde-scheme/tests/fixtures"]; + +const LIST_BEGIN: &str = "// clean-room word list: begin"; +const LIST_END: &str = "// clean-room word list: end"; + +// clean-room word list: begin +const FORBIDDEN: &[&str] = &[ + "KHCY", + "lumag", + "igamma", + "mixQreal", + "mix_qreal", + "tintHelper", + "tint_helper", + "contrastRatioForLuma", + "SerializedColors", + "StateEffects", + "applyScheme", + "accentForeground", + "accentBackground", + "QColor", + "KColorScheme", + "qreal", +]; + +/// The words of [`FORBIDDEN`] that Qt's public documentation names, the only +/// ones a comment citing `doc.qt.io` may carry. +const QT_WORDS: &[&str] = &["QColor", "qreal"]; +// clean-room word list: end + +fn repository_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") +} + +fn is_word_byte(b: u8) -> bool { + b.is_ascii_alphanumeric() || b == b'_' +} + +/// The forbidden words that appear whole in `line`. +fn whole_words_in(line: &str) -> Vec<&'static str> { + let bytes = line.as_bytes(); + FORBIDDEN + .iter() + .copied() + .filter(|word| { + line.match_indices(word).any(|(start, _)| { + let end = start + word.len(); + let before = start == 0 || !is_word_byte(bytes[start - 1]); + let after = end == bytes.len() || !is_word_byte(bytes[end]); + before && after + }) + }) + .collect() +} + +/// Whether `line` is a `//` comment (including `///` and `//!`) that cites +/// Qt's public documentation. +fn cites_qt_documentation(line: &str) -> bool { + line.trim_start().starts_with("//") && line.contains("doc.qt.io") +} + +/// The forbidden words reported on `line`: every whole word, except a Qt +/// word on a comment line that cites `doc.qt.io`. +fn reported_words(line: &str) -> Vec<&'static str> { + let cites_qt = cites_qt_documentation(line); + whole_words_in(line) + .into_iter() + .filter(|word| !(cites_qt && QT_WORDS.contains(word))) + .collect() +} + +fn collect_files(dir: &Path, root: &Path, out: &mut Vec) { + let entries = + fs::read_dir(dir).unwrap_or_else(|e| panic!("cannot read {}: {e}", dir.display())); + for entry in entries { + let path = entry.expect("a directory entry").path(); + let relative = path.strip_prefix(root).unwrap_or(&path); + if SKIPPED.iter().any(|skip| relative == Path::new(skip)) { + continue; + } + if path.is_dir() { + collect_files(&path, root, out); + } else { + out.push(path); + } + } +} + +#[test] +fn no_upstream_identifier_appears_in_the_kde_crates() { + let root = repository_root(); + let this_file = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/clean_room_vocabulary.rs"); + let mut files = Vec::new(); + for dir in SCANNED { + let path = root.join(dir); + assert!( + path.is_dir(), + "{dir} is listed in SCANNED but does not exist" + ); + let before = files.len(); + collect_files(&path, &root, &mut files); + assert!( + files.len() > before, + "{dir} is listed in SCANNED but holds no file to scan" + ); + } + let canonical: Vec = files + .iter() + .map(|file| { + fs::canonicalize(file) + .unwrap_or_else(|e| panic!("cannot resolve {}: {e}", file.display())) + }) + .collect(); + let this_file = fs::canonicalize(&this_file).expect("this test file resolves"); + let math = fs::canonicalize(root.join("crates/teksilo-kde-scheme/src/math.rs")) + .expect("the colour arithmetic module resolves"); + assert!( + canonical.contains(&math), + "the scan did not reach crates/teksilo-kde-scheme/src/math.rs" + ); + assert!( + canonical.contains(&this_file), + "the scan did not reach this test file" + ); + let mut findings = Vec::new(); + let mut lines_read = 0usize; + for (file, resolved) in files.iter().zip(&canonical) { + // Every file is read: one that is not UTF-8 is scanned through a + // lossy decoding, so an identifier in it is still found. + let bytes = + fs::read(file).unwrap_or_else(|e| panic!("cannot read {}: {e}", file.display())); + let text = String::from_utf8_lossy(&bytes); + let is_this_file = *resolved == this_file; + let mut in_list = false; + for (index, line) in text.lines().enumerate() { + lines_read += 1; + if is_this_file && line.trim() == LIST_BEGIN { + in_list = true; + } + if is_this_file && line.trim() == LIST_END { + in_list = false; + continue; + } + if in_list { + continue; + } + for word in reported_words(line) { + let shown = file.strip_prefix(&root).unwrap_or(file); + findings.push(format!("{}:{}: {word}", shown.display(), index + 1)); + } + } + } + assert!( + lines_read > files.len(), + "the scan read {lines_read} lines from {} files", + files.len() + ); + assert!( + findings.is_empty(), + "upstream identifiers in clean-room code:\n{}", + findings.join("\n") + ); +} + +#[test] +fn words_match_whole_and_case_sensitively() { + let word = FORBIDDEN[0]; + assert_eq!(whole_words_in(&format!("let x = {word};")), vec![word]); + assert!(whole_words_in(&format!("{word}x")).is_empty()); + assert!(whole_words_in(&format!("x{word}")).is_empty()); + assert!(whole_words_in(&format!("_{word}")).is_empty()); + assert_ne!(word, word.to_lowercase()); + assert!(whole_words_in(&word.to_lowercase()).is_empty()); +} + +#[test] +fn a_qt_documentation_comment_exempts_only_the_qt_words() { + for &qt in QT_WORDS { + assert!(FORBIDDEN.contains(&qt), "{qt} is a forbidden word"); + for comment in ["//", "///", "//!"] { + let line = format!(" {comment} {qt}: see https://doc.qt.io/qt-6/qcolor.html"); + assert!(reported_words(&line).is_empty(), "{line}"); + } + // Code before the comment is not a citation, and neither is an + // attribute, even one that names doc.qt.io. + let code = format!("let x: {qt} = 1; // https://doc.qt.io/"); + assert_eq!(reported_words(&code), vec![qt]); + let attribute = format!("#[doc = \"{qt}, see doc.qt.io\"]"); + assert_eq!(reported_words(&attribute), vec![qt]); + } + // A KDE word is reported next to a doc.qt.io citation, alone or beside + // a Qt word the citation does exempt. + for &kde in FORBIDDEN.iter().filter(|w| !QT_WORDS.contains(w)) { + let alone = format!("// port of {kde}, see https://doc.qt.io/qt-6/qcolor.html"); + assert_eq!(reported_words(&alone), vec![kde], "{alone}"); + let beside = format!("/// {kde} / {}::lightness, doc.qt.io", QT_WORDS[0]); + assert_eq!(reported_words(&beside), vec![kde], "{beside}"); + } +} diff --git a/crates/teksilo-kde-scheme/tests/desktop.rs b/crates/teksilo-kde-scheme/tests/desktop.rs new file mode 100644 index 000000000..ee87f5c7b --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/desktop.rs @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Desktop settings built from already-read layers. + +use teksilo_kde_scheme::{ + BreezeSettings, ColorSet, KConfig, KdeColor, KdeColorScheme, KdeConfigLocations, KdeDesktop, + KdeDesktopLayers, KdeFont, KdeFonts, LayerKind, MnemonicsMode, SchemeOrigin, ScrollBarButtons, + ShadowSize, +}; + +fn no_env(_: &str) -> Option { + None +} + +fn desktop(kdeglobals: &str, breezerc: &str) -> KdeDesktop { + desktop_with_env(kdeglobals, breezerc, &no_env) +} + +fn desktop_with_env( + kdeglobals: &str, + breezerc: &str, + var: &dyn Fn(&str) -> Option, +) -> KdeDesktop { + let locations = KdeConfigLocations::from_env(|name: &str| match name { + "HOME" => Some("/home/ada".to_owned()), + _ => None, + }); + let expansion = locations.expansion_env(var); + let globals = [KConfig::parse(kdeglobals).0.with_kind(LayerKind::User)]; + let breeze = [KConfig::parse(breezerc).0.with_kind(LayerKind::User)]; + KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &globals, + breezerc: &breeze, + scheme_file: None, + }, + &expansion, + ) +} + +#[test] +fn an_empty_session_gives_breeze_light_and_every_default() { + let d = desktop("", ""); + assert_eq!(d.scheme_origin, SchemeOrigin::BuiltIn); + assert_eq!(d.scheme, KdeColorScheme::breeze_light()); + assert_eq!(d.user_accent, None); + assert_eq!(d.fonts, KdeFonts::default()); + assert_eq!(d.animation_duration_factor, None); + assert_eq!(d.widget_style, None); + assert_eq!(d.breeze, BreezeSettings::default()); + assert!(d.uses_breeze_style()); + assert!(!d.prefers_reduced_motion()); + + let mut expected = KdeDesktop::new(KdeColorScheme::breeze_light()); + expected.scheme_origin = SchemeOrigin::BuiltIn; + assert_eq!(d, expected); +} + +#[test] +fn the_default_fonts_are_noto_sans_and_hack() { + let fonts = KdeFonts::default(); + assert_eq!(fonts.general.family, "Noto Sans"); + assert_eq!(fonts.general.point_size, Some(10.0)); + assert_eq!(fonts.small.point_size, Some(8.0)); + assert_eq!(fonts.fixed.family, "Hack"); + assert_eq!(fonts.fixed.style_hint, 7); + assert_eq!(fonts.menu.style_name.as_deref(), Some("Regular")); +} + +#[test] +fn colour_groups_in_kdeglobals_are_the_scheme() { + let d = desktop( + "[Colors:View]\nBackgroundNormal=1,2,3\n[General]\nAccentColor=233,61,61\n[KDE]\ncontrast=2\n", + "", + ); + assert_eq!(d.scheme_origin, SchemeOrigin::Kdeglobals); + assert_eq!( + d.scheme.set(ColorSet::View).background_normal, + KdeColor::rgb8(1, 2, 3) + ); + assert_eq!(d.scheme.contrast, 2); + assert_eq!(d.user_accent, Some(KdeColor::rgb8(233, 61, 61))); + assert_eq!(d.scheme.accent, Some(KdeColor::rgb8(233, 61, 61))); +} + +#[test] +fn a_transparent_accent_is_no_accent() { + assert_eq!( + desktop("[General]\nAccentColor=0,0,0,0\n", "").user_accent, + None + ); + assert_eq!( + desktop("[General]\nAccentColor=transparent\n", "").user_accent, + None + ); + assert_eq!( + desktop("[General]\nAccentColor=nonsense\n", "").user_accent, + None + ); +} + +#[test] +fn animation_duration_factor_absent() { + let d = desktop("[KDE]\nwidgetStyle=breeze\n", ""); + assert_eq!(d.animation_duration_factor, None); + assert_eq!(d.animation_factor(), 1.0); + assert!(!d.prefers_reduced_motion()); +} + +#[test] +fn animation_duration_factor_zero_asks_for_reduced_motion() { + let d = desktop("[KDE]\nAnimationDurationFactor=0\n", ""); + assert_eq!(d.animation_duration_factor, Some(0.0)); + assert_eq!(d.animation_factor(), 0.0); + assert!(d.prefers_reduced_motion()); +} + +#[test] +fn animation_duration_factor_two_doubles_the_durations() { + let d = desktop("[KDE]\nAnimationDurationFactor=2\n", ""); + assert_eq!(d.animation_duration_factor, Some(2.0)); + assert_eq!(d.animation_factor(), 2.0); + assert!(!d.prefers_reduced_motion()); +} + +#[test] +fn an_unparseable_animation_duration_factor_is_present_and_reads_as_one() { + let d = desktop("[KDE]\nAnimationDurationFactor=fast\n", ""); + assert_eq!(d.animation_duration_factor, Some(1.0)); +} + +#[test] +fn the_widget_style_is_breeze_only_when_absent_empty_or_named_breeze() { + assert!(desktop("[KDE]\nwidgetStyle=\n", "").uses_breeze_style()); + assert!(desktop("[KDE]\nwidgetStyle=BREEZE\n", "").uses_breeze_style()); + let fusion = desktop("[KDE]\nwidgetStyle=Fusion\n", ""); + assert_eq!(fusion.widget_style.as_deref(), Some("Fusion")); + assert!(!fusion.uses_breeze_style()); +} + +#[test] +fn fonts_are_read_as_strings_expanded_and_unparseable_ones_keep_the_default() { + let var = |name: &str| (name == "MONO").then(|| "Fira Mono".to_owned()); + let d = desktop_with_env( + "[General]\nfont=Inter,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1\nfixed[$e]=$MONO,9,-1,7,50,0,0,0,1,0\n\ + menuFont=garbage\ntoolBarFont=\nsmallestReadableFont=Noto Sans,7\n", + "", + &var, + ); + assert_eq!(d.fonts.general.family, "Inter"); + assert_eq!(d.fonts.general.point_size, Some(11.0)); + assert_eq!(d.fonts.fixed.family, "Fira Mono"); + assert_eq!(d.fonts.fixed.weight, 400); + assert!(d.fonts.fixed.fixed_pitch); + assert_eq!(d.fonts.menu, KdeFonts::default().menu); + assert_eq!(d.fonts.toolbar, KdeFonts::default().toolbar); + assert_eq!(d.fonts.small, KdeFont::parse_qfont("Noto Sans,7").unwrap()); +} + +#[test] +fn breezerc_settings_are_read_with_their_ranges_and_enumerations() { + let d = desktop( + "", + "[Common]\nShadowStrength=10\nShadowSize=ShadowSmall\nShadowColor=10,0,0\n\ + [Style]\nAnimationsEnabled=false\nAnimationsDuration=250\nScrollBarAddLineButtons=1\n\ + ScrollBarSubLineButtons=5\nMnemonicsMode=MN_ALWAYS\nMenuOpacity=140\nMenuItemDrawStrongFocus=false\n\ + ViewDrawFocusIndicator=0\n", + ); + let b = d.breeze; + assert_eq!(b.shadow_strength, 25); + assert_eq!(b.shadow_size, ShadowSize::Small); + assert_eq!(b.shadow_color, KdeColor::rgb8(10, 0, 0)); + assert!(!b.animations_enabled); + assert_eq!(b.animations_duration_ms, 250); + assert_eq!(b.scroll_bar_add_line_buttons, ScrollBarButtons::Single); + assert_eq!(b.scroll_bar_sub_line_buttons, ScrollBarButtons::Double); + assert_eq!(b.mnemonics_mode, MnemonicsMode::Always); + assert_eq!(b.menu_opacity, 100); + assert!(!b.menu_item_draw_strong_focus); + assert!(!b.view_draw_focus_indicator); + assert_eq!(b.progress_bar_busy_step_duration_ms, 800); +} + +#[test] +fn breezerc_enumerations_accept_indices_and_fall_back_on_unknown_names() { + let d = desktop( + "", + "[Common]\nShadowSize=4\n[Style]\nMnemonicsMode=sometimes\n", + ); + assert_eq!(d.breeze.shadow_size, ShadowSize::VeryLarge); + assert_eq!(d.breeze.mnemonics_mode, MnemonicsMode::Auto); + let unknown = desktop("", "[Common]\nShadowSize=ShadowHuge\n"); + assert_eq!(unknown.breeze.shadow_size, ShadowSize::Large); +} + +#[test] +fn an_immutable_system_breezerc_wins_over_the_user_file() { + let locations = KdeConfigLocations::from_env(|_: &str| None); + let expansion = locations.expansion_env(&no_env); + let breezerc = [ + KConfig::parse("[Style][$i]\nMenuOpacity=80\n").0, + KConfig::parse("[Style]\nMenuOpacity=50\n") + .0 + .with_kind(LayerKind::User), + ]; + let d = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &[], + breezerc: &breezerc, + scheme_file: None, + }, + &expansion, + ); + assert_eq!(d.breeze.menu_opacity, 80); +} diff --git a/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeClassic.colors b/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeClassic.colors new file mode 100644 index 000000000..881f8c16c --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeClassic.colors @@ -0,0 +1,170 @@ +[ColorEffects:Disabled] +Color=56,56,56 +ColorAmount=0 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +IntensityAmount=0.1 +IntensityEffect=2 + +[ColorEffects:Inactive] +ChangeSelectionColor=true +Color=112,111,110 +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.1 +ContrastEffect=2 +Enable=false +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate=189,195,199 +BackgroundNormal=239,240,241 +DecorationFocus=61,174,233 +DecorationHover=147,206,233 +ForegroundActive=61,174,233 +ForegroundInactive=127,140,141 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=198,92,0 +ForegroundNormal=35,38,39 +ForegroundPositive=39,174,96 +ForegroundVisited=127,140,141 + +[Colors:Selection] +BackgroundAlternate=29,153,243 +BackgroundNormal=61,174,233 +DecorationFocus=61,174,233 +DecorationHover=147,206,233 +ForegroundActive=252,252,252 +ForegroundInactive=239,240,241 +ForegroundLink=253,188,75 +ForegroundNegative=176,55,69 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=23,104,57 +ForegroundVisited=189,195,199 + +[Colors:Tooltip] +BackgroundAlternate=77,77,77 +BackgroundNormal=35,38,39 +DecorationFocus=61,174,233 +DecorationHover=147,206,233 +ForegroundActive=61,174,233 +ForegroundInactive=189,195,199 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=127,140,141 + +[Colors:View] +BackgroundAlternate=239,240,241 +BackgroundNormal=252,252,252 +DecorationFocus=61,174,233 +DecorationHover=147,206,233 +ForegroundActive=61,174,233 +ForegroundInactive=127,140,141 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=35,38,39 +ForegroundPositive=39,174,96 +ForegroundVisited=127,140,141 + +[Colors:Window] +BackgroundAlternate=189,195,199 +BackgroundNormal=239,240,241 +DecorationFocus=61,174,233 +DecorationHover=147,206,233 +ForegroundActive=61,174,233 +ForegroundInactive=127,140,141 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=35,38,39 +ForegroundPositive=39,174,96 +ForegroundVisited=127,140,141 + +[Colors:Complementary] +BackgroundAlternate=59,64,69 +BackgroundNormal=49,54,59 +DecorationFocus=30,146,255 +DecorationHover=61,174,230 +ForegroundActive=147,206,233 +ForegroundInactive=175,176,179 +ForegroundLink=61,174,230 +ForegroundNegative=231,76,60 +ForegroundNeutral=253,188,75 +ForegroundNormal=239,240,241 +ForegroundPositive=46,204,113 +ForegroundVisited=61,174,230 + +[General] +ColorScheme=BreezeClassic +Name=Breeze Classic +Name[ar]=نسيم التقليدي +Name[az]=Breeze klassik +Name[bg]=Breeze Класически +Name[ca]=Brisa clàssica +Name[ca@valencia]=Brisa clàssica +Name[cs]=Breeze Classic +Name[da]=Breeze Classic +Name[de]=Breeze Klassisch +Name[el]=Breeze κλασικό +Name[en_GB]=Breeze Classic +Name[eo]=Breeze Classic +Name[es]=Brisa clásico +Name[eu]=Breeze klasikoa +Name[fi]=Perinteinen Breeze +Name[fr]=Breeze classique +Name[gl]=Brisa clásica +Name[he]=בריזה קלסית +Name[hi]=ब्रीज़ क्लासिक +Name[hu]=Breeze Classic +Name[ia]=Brisa Classic +Name[id]=Breeze Klasik +Name[is]=Breeze klassík +Name[it]=Brezza classico +Name[ja]=Breeze クラシック +Name[ka]=Breeze კლასიკური +Name[ko]=Breeze 고전 +Name[lt]=Breeze klasikinis +Name[lv]=„Breeze“ klasiskais +Name[nl]=Breeze Classic +Name[nn]=Breeze klassisk +Name[pa]=ਬਰੀਜ਼ ਟਕਸਾਲੀ +Name[pl]=Klasyczna Bryza +Name[pt]=Brisa Clássico +Name[pt_BR]=Breeze Classic +Name[ro]=Briză, clasic +Name[ru]=Breeze, классический вариант +Name[sa]=हवा क्लासिक +Name[sk]=Klasický vánok +Name[sl]=Sapica, klasična +Name[sr]=Поветарац класични +Name[sr@ijekavian]=Поветарац класични +Name[sr@ijekavianlatin]=Povetarac klasični +Name[sr@latin]=Povetarac klasični +Name[sv]=Breeze klassisk +Name[ta]=செந்தர பிரீஸ் +Name[tr]=Esinti Klasik +Name[uk]=Класична Breeze +Name[zh_CN]=Breeze 微风经典 +Name[zh_TW]=Breeze Classic +accentActiveTitlebar=true +accentInactiveTitlebar=false +shadeSortColumn=true + +[KDE] +contrast=4 + +[WM] +activeBackground=71,80,87 +activeBlend=252,252,252 +activeForeground=252,252,252 +inactiveBackground=239,240,241 +inactiveBlend=75,71,67 +inactiveForeground=189,195,199 diff --git a/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeDark.colors b/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeDark.colors new file mode 100644 index 000000000..a759d5459 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeDark.colors @@ -0,0 +1,210 @@ +# SPDX-FileCopyrightText: Andrew Lake +# SPDX-FileCopyrightText: Marco Martin +# SPDX-FileCopyrightText: Nate Graham +# SPDX-FileCopyrightText: Noah Davis +# SPDX-FileCopyrightText: Neal Gompa +# SPDX-FileCopyrightText: David Redondo +# SPDX-FileCopyrightText: Thomas Duckworth +# SPDX-License-Identifier: LGPL-2.0-or-later + +[ColorEffects:Disabled] +Color=56,56,56 +ColorAmount=0 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +IntensityAmount=0.1 +IntensityEffect=2 + +[ColorEffects:Inactive] +ChangeSelectionColor=true +Color=112,111,110 +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.1 +ContrastEffect=2 +Enable=false +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate=30,87,116 +BackgroundNormal=41,44,48 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Complementary] +BackgroundAlternate=30,87,116 +BackgroundNormal=32,35,38 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Header] +BackgroundAlternate=32,35,38 +BackgroundNormal=41,44,48 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Header][Inactive] +BackgroundAlternate=41,44,48 +BackgroundNormal=32,35,38 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Selection] +BackgroundAlternate=30,87,116 +BackgroundNormal=61,174,233 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=252,252,252 +ForegroundInactive=161,169,177 +ForegroundLink=253,188,75 +ForegroundNegative=176,55,69 +ForegroundNeutral=198,92,0 +ForegroundNormal=252,252,252 +ForegroundPositive=23,104,57 +ForegroundVisited=155,89,182 + +[Colors:Tooltip] +BackgroundAlternate=32,35,38 +BackgroundNormal=41,44,48 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:View] +BackgroundAlternate=29,31,34 +BackgroundNormal=20,22,24 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Window] +BackgroundAlternate=41,44,48 +BackgroundNormal=32,35,38 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[General] +ColorScheme=BreezeDark +Name=Breeze Dark +Name[ar]=نسيم داكن +Name[az]=Breeze - Tünd +Name[bg]=Breeze Тъмен +Name[bs]=Breeze tamna +Name[ca]=Brisa fosca +Name[ca@valencia]=Brisa fosca +Name[cs]=Breeze Tmavé +Name[da]=Breeze Dark +Name[de]=Breeze Dunkel +Name[el]=Breeze σκούρο +Name[en_GB]=Breeze Dark +Name[eo]=Breeze Dark +Name[es]=Brisa oscuro +Name[et]=Breeze tume +Name[eu]=Breeze iluna +Name[fi]=Tumma Breeze +Name[fr]=Brise sombre +Name[gl]=Brisa escura +Name[he]=בריזה כהה +Name[hi]=ब्रीज़ गहरा +Name[hu]=Breeze Dark +Name[ia]=Brisa obscure +Name[id]=Breeze Gelap +Name[is]=Breeze dökkt +Name[it]=Brezza scuro +Name[ja]=Breeze Dark +Name[ka]=Breeze მუქი +Name[ko]=어두운 Breeze +Name[lt]=Breeze tamsus +Name[lv]=„Breeze“ tumšais +Name[nb]=Breeze mørk +Name[nl]=Breeze Dark +Name[nn]=Breeze mørk +Name[pa]=ਬਰੀਜ਼ ਗੂੜ੍ਹਾ +Name[pl]=Ciemna Bryza +Name[pt]=Brisa Escuro +Name[pt_BR]=Breeze Dark +Name[ro]=Briză, întunecat +Name[ru]=Breeze, тёмный вариант +Name[sa]=वायुः अन्धकारः +Name[sk]=Tmavý vánok +Name[sl]=Sapica, temna +Name[sr]=Поветарац тамни +Name[sr@ijekavian]=Поветарац тамни +Name[sr@ijekavianlatin]=Povetarac tamni +Name[sr@latin]=Povetarac tamni +Name[sv]=Breeze mörk +Name[ta]=கரும் பிரீஸ் +Name[tg]=Насими торик +Name[tr]=Esinti Koyu +Name[ug]=مەيىن شامال قاراڭغۇ +Name[uk]=Темна Breeze +Name[zh_CN]=Breeze 微风深色 +Name[zh_TW]=Breeze Dark +shadeSortColumn=true + +[KDE] +contrast=4 + +[WM] +activeBackground=39,44,49 +activeBlend=252,252,252 +activeForeground=252,252,252 +inactiveBackground=32,36,40 +inactiveBlend=161,169,177 +inactiveForeground=161,169,177 diff --git a/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeLight.colors b/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeLight.colors new file mode 100644 index 000000000..b3c2fca60 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/BreezeLight.colors @@ -0,0 +1,207 @@ +# SPDX-FileCopyrightText: Andrew Lake +# SPDX-FileCopyrightText: Marco Martin +# SPDX-FileCopyrightText: Nate Graham +# SPDX-FileCopyrightText: Noah Davis +# SPDX-FileCopyrightText: Neal Gompa +# SPDX-FileCopyrightText: David Redondo +# SPDX-License-Identifier: LGPL-2.0-or-later + +[ColorEffects:Disabled] +Color=56,56,56 +ColorAmount=0 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +IntensityAmount=0.1 +IntensityEffect=2 + +[ColorEffects:Inactive] +ChangeSelectionColor=true +Color=112,111,110 +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.1 +ContrastEffect=2 +Enable=false +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate=163,212,250 +BackgroundNormal=252,252,252 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=112,125,138 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=35,38,41 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Complementary] +BackgroundAlternate=27,30,32 +BackgroundNormal=42,46,50 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Header] +BackgroundAlternate=239,240,241 +BackgroundNormal=222,224,226 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=112,125,138 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=35,38,41 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Header][Inactive] +BackgroundAlternate=227,229,231 +BackgroundNormal=239,240,241 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=112,125,138 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=35,38,41 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Selection] +BackgroundAlternate=163,212,250 +BackgroundNormal=61,174,233 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=255,255,255 +ForegroundInactive=112,125,138 +ForegroundLink=253,188,75 +ForegroundNegative=176,55,69 +ForegroundNeutral=198,92,0 +ForegroundNormal=255,255,255 +ForegroundPositive=23,104,57 +ForegroundVisited=155,89,182 + +[Colors:Tooltip] +BackgroundAlternate=239,240,241 +BackgroundNormal=247,247,247 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=112,125,138 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=35,38,41 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:View] +BackgroundAlternate=247,247,247 +BackgroundNormal=255,255,255 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=112,125,138 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=35,38,41 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Window] +BackgroundAlternate=227,229,231 +BackgroundNormal=239,240,241 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=112,125,138 +ForegroundLink=41,128,185 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=35,38,41 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[General] +ColorScheme=BreezeLight +Name=Breeze Light +Name[ar]=نسيم فاتح +Name[az]=Breeze - İşıqlı +Name[bg]=Breeze Светъл +Name[ca]=Brisa clara +Name[ca@valencia]=Brisa clara +Name[cs]=Breeze Světlé +Name[da]=Breeze Light +Name[de]=Breeze Hell +Name[el]=Breeze φωτεινό +Name[en_GB]=Breeze Light +Name[eo]=Breeze Light +Name[es]=Brisa claro +Name[et]=Breeze hele +Name[eu]=Breeze argia +Name[fi]=Vaalea Breeze +Name[fr]=Brise clair +Name[gl]=Brisa clara +Name[he]=בריזה בהירה +Name[hi]=ब्रीज़ हलका +Name[hu]=Breeze Light +Name[ia]=Brisa Ligier +Name[id]=Breeze Terang +Name[is]=Breeze ljóst +Name[it]=Brezza chiaro +Name[ja]=Breeze Light +Name[ka]=Breeze ღია +Name[ko]=밝은 Breeze +Name[lt]=Breeze šviesus +Name[lv]=„Breeze“ gaišais +Name[nl]=Breeze Light +Name[nn]=Breeze lys +Name[pa]=ਬਰੀਜ਼ ਹਲਕਾ +Name[pl]=Jasna Bryza +Name[pt]=Brisa Claro +Name[pt_BR]=Breeze Light +Name[ro]=Briză, luminos +Name[ru]=Breeze, светлый вариант +Name[sa]=वायु प्रकाश +Name[sk]=Svetlý vánok +Name[sl]=Sapica, svetla +Name[sr]=Поветарац светли +Name[sr@ijekavian]=Поветарац свијетли +Name[sr@ijekavianlatin]=Povetarac svijetli +Name[sr@latin]=Povetarac svetli +Name[sv]=Breeze ljus +Name[ta]=வெளிர் பிரீஸ் +Name[tg]=Насими сабук +Name[tr]=Esinti Açık +Name[ug]=مەيىن شامال يورۇق +Name[uk]=Світла Breeze +Name[zh_CN]=Breeze 微风浅色 +Name[zh_TW]=Breeze Light +shadeSortColumn=true + +[KDE] +contrast=4 + +[WM] +activeBackground=227,229,231 +activeBlend=227,229,231 +activeForeground=35,38,41 +inactiveBackground=239,240,241 +inactiveBlend=239,240,241 +inactiveForeground=112,125,138 diff --git a/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/LICENSE.txt b/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/LICENSE.txt new file mode 100644 index 000000000..12735e6c2 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/LICENSE.txt @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/crates/teksilo-kde-scheme/tests/golden_accent.rs b/crates/teksilo-kde-scheme/tests/golden_accent.rs new file mode 100644 index 000000000..6ad5406a0 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/golden_accent.rs @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! `KdeColorScheme::with_accent` against the golden vectors +//! `tools/kde_golden/vectors/accent.json`: the `kdeglobals` Plasma 6.8 +//! wrote for four schemes and the ten swatches, read back key by key. +//! +//! It also checks that accenting a built-in Breeze constant gives exactly +//! what accenting the vendored stock file gives. The vectors and the +//! fixtures live outside the published crate, so this file is left out of +//! the package (the `exclude` list in `Cargo.toml`). + +use std::collections::BTreeSet; + +use serde_json::{Map, Value}; +use teksilo_kde_scheme::{ACCENT_SWATCHES, KConfig, KdeColor, KdeColorScheme}; + +const ACCENT_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tools/kde_golden/vectors/accent.json" +)); +const BREEZE_LIGHT: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/breeze-v6.8.0/BreezeLight.colors" +)); +const BREEZE_DARK: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/breeze-v6.8.0/BreezeDark.colors" +)); +const BREEZE_CLASSIC: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/fixtures/breeze-v6.8.0/BreezeClassic.colors" +)); + +fn vectors() -> Value { + serde_json::from_str(ACCENT_JSON).expect("accent.json parses") +} + +fn cases(all: &Value) -> &[Value] { + let list = all["cases"] + .as_array() + .expect("accent.json has a case list"); + assert!(!list.is_empty(), "accent.json has no case"); + list +} + +/// A group name as the vectors write it (`Colors:Header][Inactive`) as a +/// group path. +fn path(name: &str) -> Vec<&str> { + name.split("][").collect() +} + +fn object<'a>(value: &'a Value, what: &str) -> &'a Map { + value + .as_object() + .unwrap_or_else(|| panic!("{what} is an object")) +} + +fn text(value: &Value) -> &str { + value.as_str().expect("a configuration value is a string") +} + +/// The scheme file the vectors record as the input of a scheme, as +/// configuration text. +fn scheme_text(input: &Value) -> String { + let mut out = String::new(); + for (group, keys) in object(input, "a scheme input") { + out.push('['); + out.push_str(group); + out.push_str("]\n"); + for (key, value) in object(keys, "a group") { + out.push_str(key); + out.push('='); + out.push_str(text(value)); + out.push('\n'); + } + } + out +} + +fn parse_clean(text: &str) -> KdeColorScheme { + let (scheme, diagnostics) = KdeColorScheme::parse(text); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + scheme +} + +fn accent_of(case: &Value) -> KdeColor { + KdeColor::parse(text(&case["accent"])).expect("the accent is a colour") +} + +/// The groups `[General]` aside: the accent procedure writes only a digest +/// there, which Teksilo leaves out (spec g.2), and the scheme name the +/// vectors hold comes from applying the scheme, not from the accent. +fn compared(group: &[&str]) -> bool { + group != ["General"] +} + +#[test] +fn every_accent_case_writes_exactly_what_plasma_wrote() { + let all = vectors(); + let mut failures = Vec::new(); + let mut checked = 0; + for case in cases(&all) { + let name = text(&case["scheme"]); + let input = &all["schemes"][name]["input"]; + let label = format!("{name} {}", text(&case["accent"])); + let accented = parse_clean(&scheme_text(input)).with_accent(accent_of(case)); + let written = accented.source().expect("an accented scheme has a source"); + + let mut expected_keys = BTreeSet::new(); + for (group, keys) in object(&case["kdeglobals"], "kdeglobals") { + let group = path(group); + if !compared(&group) { + continue; + } + for (key, value) in object(keys, "a group") { + expected_keys.insert((group.join("]["), key.clone())); + checked += 1; + let got = written.get(&group, key); + if got != Some(text(value)) { + failures.push(format!( + "{label} [{}] {key}: wrote {got:?}, Plasma wrote {value}", + group.join("][") + )); + } + } + } + for group in written.groups() { + if !compared(&group) { + continue; + } + for key in written.keys(&group) { + if !expected_keys.contains(&(group.join("]["), key.to_owned())) { + failures.push(format!( + "{label} [{}] {key}: written, but Plasma did not write it", + group.join("][") + )); + } + } + } + // The accent option writes no AccentColor (spec g.2). + if written.has_key(&["General"], "AccentColor") { + failures.push(format!("{label}: AccentColor written")); + } + } + assert!(checked > 0, "no key was checked"); + assert!( + failures.is_empty(), + "{} differences:\n{}", + failures.len(), + failures.join("\n") + ); +} + +#[test] +fn the_cases_cover_every_swatch_on_every_scheme() { + let all = vectors(); + let schemes = object(&all["schemes"], "schemes"); + for name in schemes.keys() { + let accents: Vec = cases(&all) + .iter() + .filter(|c| c["scheme"] == name.as_str()) + .map(accent_of) + .collect(); + assert_eq!(accents, ACCENT_SWATCHES, "the swatches accented on {name}"); + } +} + +#[test] +fn the_vector_inputs_of_the_stock_schemes_are_the_vendored_files() { + let all = vectors(); + for (name, file) in [("BreezeLight", BREEZE_LIGHT), ("BreezeDark", BREEZE_DARK)] { + let from_vectors = KConfig::parse(&scheme_text(&all["schemes"][name]["input"])).0; + let from_file = KConfig::parse(file).0; + assert_eq!(from_vectors, from_file, "{name}"); + } +} + +#[test] +fn accenting_a_breeze_constant_is_accenting_its_stock_file() { + for (constant, file) in [ + (KdeColorScheme::breeze_light(), BREEZE_LIGHT), + (KdeColorScheme::breeze_dark(), BREEZE_DARK), + // No [Colors:Header] group, the legacy title-bar key and its own + // [WM] colours: the paths of the written text the other two miss. + (KdeColorScheme::breeze_classic(), BREEZE_CLASSIC), + ] { + let file = parse_clean(file); + for accent in ACCENT_SWATCHES.into_iter().chain([ + KdeColor::rgb8(0, 0, 0), + KdeColor::rgb8(255, 255, 255), + KdeColor::rgba8(10, 200, 90, 128), + KdeColor::TRANSPARENT, + ]) { + let from_constant = constant.with_accent(accent); + let from_file = file.with_accent(accent); + let name = constant.name.clone(); + assert_eq!(from_constant, from_file, "{name:?}, accent {accent}"); + assert_eq!( + from_constant.source(), + from_file.source(), + "the written configuration of {name:?} for accent {accent}" + ); + } + } +} diff --git a/crates/teksilo-kde-scheme/tests/golden_math.rs b/crates/teksilo-kde-scheme/tests/golden_math.rs new file mode 100644 index 000000000..a0c6b3a36 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/golden_math.rs @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The colour arithmetic of `teksilo_kde_scheme::math` against the golden +//! vectors `tools/kde_golden/vectors/math.json`, measured from KDE's own +//! libraries and Qt 6.11.1. +//! +//! Every record is checked: real results within 1e-6, stored colours equal +//! in all four 16-bit channels and in the 8-bit hex. The one member left +//! out is `overlay`, which records a painter composition that Teksilo's +//! specification does not describe and no Teksilo code performs. +//! +//! The vectors live outside the crate, so this file is left out of the +//! published package (the `exclude` list in `Cargo.toml`). + +use serde_json::Value; +use teksilo_kde_scheme::KdeColor; +use teksilo_kde_scheme::math::{ + self, FloatColor, Hcy, ShadeRole, color_luma_ratio, darken, hsv_darker, hsv_lighter, lighten, + luma, mix, shade, shade_role, tint, +}; + +const MATH_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tools/kde_golden/vectors/math.json" +)); + +fn vectors() -> Value { + serde_json::from_str(MATH_JSON).expect("math.json parses") +} + +fn records<'a>(all: &'a Value, member: &str) -> &'a [Value] { + let list = all[member] + .as_array() + .unwrap_or_else(|| panic!("math.json member {member} is a list")); + assert!(!list.is_empty(), "math.json member {member} is empty"); + list +} + +/// A `#rrggbbaa` golden colour, entering the model from 8 bits. +fn color(value: &Value) -> KdeColor { + let text = value.as_str().expect("a colour is a string"); + let digits = text.strip_prefix('#').expect("a colour starts with #"); + assert_eq!(digits.len(), 8, "{text} is #rrggbbaa"); + let byte = |i: usize| u8::from_str_radix(&digits[2 * i..2 * i + 2], 16).expect("hex digits"); + KdeColor::rgba8(byte(0), byte(1), byte(2), byte(3)) +} + +fn hex(c: KdeColor) -> String { + let [r, g, b, a] = c.to_rgba8(); + format!("#{r:02x}{g:02x}{b:02x}{a:02x}") +} + +fn real(value: &Value) -> f64 { + value.as_f64().expect("a number") +} + +fn int(value: &Value) -> i32 { + i32::try_from(value.as_i64().expect("an integer")).expect("in range") +} + +/// Records a mismatch between a stored colour and a golden `{hex, u16}`. +fn check_color(failures: &mut Vec, what: String, got: KdeColor, expected: &Value) { + let channels: Vec = expected["u16"] + .as_array() + .expect("a u16 list") + .iter() + .map(|v| u16::try_from(v.as_u64().expect("an integer")).expect("16 bits")) + .collect(); + let want = KdeColor::rgba16(channels[0], channels[1], channels[2], channels[3]); + let want_hex = expected["hex"].as_str().expect("a hex string"); + if got != want || hex(got) != want_hex { + failures.push(format!( + "{what}: got {got:?} {}, expected {want:?} {want_hex}", + hex(got) + )); + } +} + +fn check_real(failures: &mut Vec, what: String, got: f64, expected: f64) { + if (got - expected).abs() > 1e-6 { + failures.push(format!("{what}: got {got}, expected {expected}")); + } +} + +fn assert_none(member: &str, failures: Vec) { + assert!( + failures.is_empty(), + "{} of math.json {member} differ; first ones:\n{}", + failures.len(), + failures + .iter() + .take(20) + .cloned() + .collect::>() + .join("\n") + ); +} + +#[test] +fn single_colours_have_the_golden_luma_hue_chroma_and_round_trip() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "single") { + let c = color(&r["in"]); + let hcy = Hcy::from_color(c); + let name = r["in"].as_str().unwrap_or_default(); + check_real( + &mut failures, + format!("{name} luma"), + luma(c), + real(&r["luma"]), + ); + check_real( + &mut failures, + format!("{name} hue"), + hcy.wrapped_hue(), + real(&r["hue"]), + ); + check_real( + &mut failures, + format!("{name} chroma"), + hcy.c, + real(&r["chroma"]), + ); + let expected = r["hcy"].as_array().expect("hcy is a list"); + for (i, got) in [hcy.wrapped_hue(), hcy.c, hcy.y, hcy.a] + .into_iter() + .enumerate() + { + check_real( + &mut failures, + format!("{name} hcy[{i}]"), + got, + real(&expected[i]), + ); + } + check_color( + &mut failures, + format!("{name} round trip"), + hcy.to_color(), + &r["roundtrip"], + ); + } + assert_none("single", failures); +} + +#[test] +fn luma_contrast_ratios_match() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "contrast_ratio") { + let got = color_luma_ratio(color(&r["a"]), color(&r["b"])); + check_real(&mut failures, format!("{r}"), got, real(&r["out"])); + } + assert_none("contrast_ratio", failures); +} + +#[test] +fn lighten_darken_and_shade_match() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "lighten") { + let got = lighten(color(&r["in"]), real(&r["amount"]), real(&r["gain"])); + check_color(&mut failures, format!("lighten {r}"), got, &r["out"]); + } + for r in records(&all, "darken") { + let got = darken(color(&r["in"]), real(&r["amount"]), real(&r["gain"])); + check_color(&mut failures, format!("darken {r}"), got, &r["out"]); + } + for r in records(&all, "shade") { + let got = shade(color(&r["in"]), real(&r["luma"]), real(&r["chroma"])); + check_color(&mut failures, format!("shade {r}"), got, &r["out"]); + } + assert_none("lighten, darken and shade", failures); +} + +#[test] +fn mix_and_tint_match() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "mix") { + let got = mix(color(&r["c1"]), color(&r["c2"]), real(&r["bias"])); + check_color(&mut failures, format!("mix {r}"), got, &r["out"]); + } + for r in records(&all, "tint") { + let got = tint(color(&r["base"]), color(&r["color"]), real(&r["amount"])); + check_color(&mut failures, format!("tint {r}"), got, &r["out"]); + } + assert_none("mix and tint", failures); +} + +#[test] +fn hcy_values_convert_to_the_golden_colours() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "hcy_color") { + let hcy = Hcy { + h: real(&r["h"]), + c: real(&r["c"]), + y: real(&r["y"]), + a: real(&r["a"]), + }; + check_color(&mut failures, format!("{r}"), hcy.to_color(), &r["out"]); + } + assert_none("hcy_color", failures); +} + +#[test] +fn shade_roles_match() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "scheme_shade") { + let role = match r["role"].as_str() { + Some("Light") => ShadeRole::Light, + Some("Midlight") => ShadeRole::Midlight, + Some("Mid") => ShadeRole::Mid, + Some("Dark") => ShadeRole::Dark, + Some("Shadow") => ShadeRole::Shadow, + other => panic!("unknown shade role {other:?}"), + }; + let got = shade_role( + color(&r["in"]), + role, + real(&r["contrast"]), + real(&r["chroma"]), + ); + check_color(&mut failures, format!("{r}"), got, &r["out"]); + } + assert_none("scheme_shade", failures); +} + +#[test] +fn hsv_lighter_and_darker_match() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "qt_lighter_darker") { + let c = color(&r["in"]); + let factor = int(&r["factor"]); + let got = match r["op"].as_str() { + Some("lighter") => hsv_lighter(c, factor), + Some("darker") => hsv_darker(c, factor), + other => panic!("unknown operation {other:?}"), + }; + check_color(&mut failures, format!("{r}"), got, &r["out"]); + } + assert_none("qt_lighter_darker", failures); +} + +/// Checks a float colour against a golden `{hex, u16}`, which also carries +/// `f` exactly when the colour is held in half precision. +fn check_float(failures: &mut Vec, what: String, got: FloatColor, expected: &Value) { + match (&expected["f"], got) { + (Value::Array(f), FloatColor::Half(channels)) => { + // Each value is a binary16 number, so exactly an `f32`; reading + // it as `f32` absorbs the last-digit error of the JSON decimal. + let want: Vec = f.iter().map(|v| real(v) as f32).collect(); + if want != channels { + let have = channels; + failures.push(format!("{what}: half channels {have:?}, expected {want:?}")); + } + } + (Value::Null, FloatColor::Rgb16(_)) => {} + (_, got) => failures.push(format!("{what}: storage {got:?} disagrees with {expected}")), + } + check_color(failures, what, got.to_rgb16(), expected); +} + +#[test] +fn srgb_and_linear_conversions_match() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "qt_colorspace") { + let c = FloatColor::Rgb16(color(&r["in"])); + check_float( + &mut failures, + format!("to linear {r}"), + math::srgb_to_linear(c), + &r["to_linear"], + ); + check_float( + &mut failures, + format!("from linear {r}"), + math::linear_to_srgb(c), + &r["from_linear"], + ); + } + assert_none("qt_colorspace", failures); +} + +#[test] +fn out_of_range_float_colours_are_held_in_half_precision() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "qt_extended") { + let input: Vec = r["in"] + .as_array() + .expect("a channel list") + .iter() + .map(|v| real(v) as f32) + .collect(); + let stored = FloatColor::from_f32([input[0], input[1], input[2]], 1.0); + check_float(&mut failures, format!("stored {r}"), stored, &r["stored"]); + check_color( + &mut failures, + format!("to rgb {r}"), + stored.to_rgb16(), + &r["to_rgb"], + ); + } + assert_none("qt_extended", failures); +} + +#[test] +fn colour_text_parses_as_qt_reads_it() { + let all = vectors(); + let mut failures = Vec::new(); + for r in records(&all, "qt_color_text") { + let text = r["in"].as_str().expect("a string"); + // Section b.9: a configuration value is trimmed before it is read + // as a colour, so the reader accepts the surrounding whitespace Qt's + // own parser rejects. Every other text must agree. + if text != text.trim() { + continue; + } + let got = KdeColor::parse(text); + match (got, r["out"]["valid"].as_bool()) { + (None, Some(false)) => {} + (Some(c), None) => check_color(&mut failures, format!("{text:?}"), c, &r["out"]), + (got, _) => failures.push(format!("{text:?}: got {got:?}, expected {}", r["out"])), + } + } + assert_none("qt_color_text", failures); +} + +const ACCENT_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tools/kde_golden/vectors/accent.json" +)); + +/// The OKLab tint on its own, through the accent oracle's tinting scheme: +/// every colour Plasma tinted (section g.3 step 3: not an accent key, not +/// one the selection or button accent overwrites) must be +/// `oklab_tint(scheme value, accent, TintFactor)`. +#[test] +fn oklab_tint_reproduces_the_tinted_scheme_colours() { + let all: Value = serde_json::from_str(ACCENT_JSON).expect("accent.json parses"); + let scheme = &all["schemes"]["KdeGoldenTintLight"]; + let factor: f64 = scheme["changes"]["General"]["TintFactor"] + .as_str() + .expect("the scheme sets a tint factor") + .parse() + .expect("a real tint factor"); + let accent_keys = [ + "ForegroundActive", + "ForegroundLink", + "DecorationFocus", + "DecorationHover", + ]; + let parse = + |v: &Value| KdeColor::parse(v.as_str().expect("a colour string")).expect("a colour"); + let mut failures = Vec::new(); + let mut checked = 0; + let cases = all["cases"].as_array().expect("a case list"); + for case in cases.iter().filter(|c| c["scheme"] == "KdeGoldenTintLight") { + let accent = parse(&case["accent"]); + for group in [ + "View", + "Window", + "Button", + "Tooltip", + "Complementary", + "Header", + ] { + let name = format!("Colors:{group}"); + let Some(input) = scheme["input"][&name].as_object() else { + continue; + }; + for (key, value) in input { + if accent_keys.contains(&key.as_str()) + || (group == "Button" && key == "BackgroundAlternate") + { + continue; + } + let written = &case["kdeglobals"][&name][key]; + let got = math::oklab_tint(parse(value), accent, factor); + checked += 1; + if got.to_rgba8() != parse(written).to_rgba8() { + failures.push(format!( + "accent {} [{name}] {key}: {value} tinted to {got}, Plasma wrote {written}", + case["accent"] + )); + } + } + } + } + assert!(checked > 0, "no tinted colour was checked"); + assert_none("accent.json tinted colours", failures); +} diff --git a/crates/teksilo-kde-scheme/tests/golden_resolve.rs b/crates/teksilo-kde-scheme/tests/golden_resolve.rs new file mode 100644 index 000000000..032300b16 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/golden_resolve.rs @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Resolved palettes against the golden vectors +//! `tools/kde_golden/vectors/resolve.json`, measured from KDE's own +//! libraries and Qt 6.11.1. +//! +//! Each scheme of the vectors carries its input (the configuration text of +//! a synthetic scheme, the parsed groups of an installed one), so the +//! scheme is read from that input here and resolved, and every colour is +//! compared at 8 bits: for every state and set, the 8 foregrounds, the 8 +//! backgrounds, the 2 decorations and the 5 shades, and for every state +//! the 20 palette roles. +//! +//! One documented divergence is left out of the comparison: where a scheme +//! stores an empty or unknown colour, KDE resolves an invalid colour and +//! Teksilo uses the key's default instead (section b.9 of the +//! specification). The vectors record such a colour as `{"valid": false}`. +//! The test pins the exact list of those stored colours, checks that +//! Teksilo gives their default, and skips only what derives from them: that +//! foreground in each state, the background tinted toward it and the +//! palette role read from it. +//! +//! The vectors live outside the crate, so this file is left out of the +//! published package (the `exclude` list in `Cargo.toml`). + +use serde_json::Value; +use teksilo_kde_scheme::{ + BackgroundRole, ColorSet, ColorState, DecorationRole, ForegroundRole, KdeColor, KdeColorScheme, + PaletteRole, ShadeRole, +}; + +const RESOLVE_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tools/kde_golden/vectors/resolve.json" +)); + +fn vectors() -> Value { + serde_json::from_str(RESOLVE_JSON).expect("resolve.json parses") +} + +fn schemes(all: &Value) -> &[Value] { + let list = all["schemes"] + .as_array() + .expect("resolve.json has a list of schemes"); + assert!(!list.is_empty(), "resolve.json lists no scheme"); + list +} + +fn name(scheme: &Value) -> &str { + scheme["name"].as_str().expect("a scheme has a name") +} + +fn find<'a>(all: &'a Value, wanted: &str) -> &'a Value { + schemes(all) + .iter() + .find(|s| name(s) == wanted) + .unwrap_or_else(|| panic!("resolve.json has no scheme {wanted}")) +} + +/// The configuration text of a scheme's input: the verbatim text of a +/// synthetic scheme, or the groups of an installed one written back as +/// text. A nested group is recorded with its path joined by `][`, so +/// bracketing the recorded name gives the header line. +fn input_text(scheme: &Value) -> String { + if let Some(text) = scheme["source_text"].as_str() { + return text.to_owned(); + } + let groups = scheme["input"].as_object().expect("an input object"); + assert!(!groups.is_empty(), "{} has an empty input", name(scheme)); + let mut out = String::new(); + for (group, entries) in groups { + out.push('['); + out.push_str(group); + out.push_str("]\n"); + for (key, value) in entries.as_object().expect("a group object") { + out.push_str(key); + out.push('='); + out.push_str(value.as_str().expect("a value is a string")); + out.push('\n'); + } + out.push('\n'); + } + out +} + +fn read_scheme(scheme: &Value) -> KdeColorScheme { + KdeColorScheme::parse(&input_text(scheme)).0 +} + +fn hex(c: KdeColor) -> String { + let [r, g, b, a] = c.to_rgba8(); + format!("#{r:02x}{g:02x}{b:02x}{a:02x}") +} + +/// A golden `{hex, u16}` colour's 8-bit hex, checked to be the 8-bit +/// projection of its 16-bit channels. +fn golden_hex(value: &Value) -> String { + let want = value["hex"].as_str().expect("a hex string").to_owned(); + let channels: Vec = value["u16"] + .as_array() + .expect("a u16 list") + .iter() + .map(|v| u16::try_from(v.as_u64().expect("an integer")).expect("16 bits")) + .collect(); + let from_u16 = KdeColor::rgba16(channels[0], channels[1], channels[2], channels[3]); + assert_eq!(hex(from_u16), want, "golden hex and u16 disagree"); + want +} + +fn state_key(state: ColorState) -> &'static str { + match state { + ColorState::Active => "Active", + ColorState::Inactive => "Inactive", + ColorState::Disabled => "Disabled", + } +} + +fn set_key(set: ColorSet) -> &'static str { + match set { + ColorSet::View => "View", + ColorSet::Window => "Window", + ColorSet::Button => "Button", + ColorSet::Selection => "Selection", + ColorSet::Tooltip => "Tooltip", + ColorSet::Complementary => "Complementary", + ColorSet::Header => "Header", + } +} + +fn foreground_key(role: ForegroundRole) -> &'static str { + match role { + ForegroundRole::Normal => "Normal", + ForegroundRole::Inactive => "Inactive", + ForegroundRole::Active => "Active", + ForegroundRole::Link => "Link", + ForegroundRole::Visited => "Visited", + ForegroundRole::Negative => "Negative", + ForegroundRole::Neutral => "Neutral", + ForegroundRole::Positive => "Positive", + } +} + +fn background_key(role: BackgroundRole) -> &'static str { + match role { + BackgroundRole::Normal => "Normal", + BackgroundRole::Alternate => "Alternate", + BackgroundRole::Active => "Active", + BackgroundRole::Link => "Link", + BackgroundRole::Visited => "Visited", + BackgroundRole::Negative => "Negative", + BackgroundRole::Neutral => "Neutral", + BackgroundRole::Positive => "Positive", + } +} + +fn decoration_key(role: DecorationRole) -> &'static str { + match role { + DecorationRole::Focus => "Focus", + DecorationRole::Hover => "Hover", + } +} + +fn shade_key(role: ShadeRole) -> &'static str { + match role { + ShadeRole::Light => "Light", + ShadeRole::Midlight => "Midlight", + ShadeRole::Mid => "Mid", + ShadeRole::Dark => "Dark", + ShadeRole::Shadow => "Shadow", + } +} + +fn palette_key(role: PaletteRole) -> &'static str { + match role { + PaletteRole::Window => "Window", + PaletteRole::WindowText => "WindowText", + PaletteRole::Base => "Base", + PaletteRole::Text => "Text", + PaletteRole::AlternateBase => "AlternateBase", + PaletteRole::Link => "Link", + PaletteRole::LinkVisited => "LinkVisited", + PaletteRole::PlaceholderText => "PlaceholderText", + PaletteRole::Button => "Button", + PaletteRole::ButtonText => "ButtonText", + PaletteRole::Highlight => "Highlight", + PaletteRole::HighlightedText => "HighlightedText", + PaletteRole::Accent => "Accent", + PaletteRole::TooltipBase => "ToolTipBase", + PaletteRole::TooltipText => "ToolTipText", + PaletteRole::Light => "Light", + PaletteRole::Midlight => "Midlight", + PaletteRole::Mid => "Mid", + PaletteRole::Dark => "Dark", + PaletteRole::Shadow => "Shadow", + } +} + +fn check(failures: &mut Vec, what: String, got: KdeColor, expected: &Value) { + let want = golden_hex(expected); + if hex(got) != want { + failures.push(format!("{what}: got {}, expected {want}", hex(got))); + } +} + +fn is_invalid(value: &Value) -> bool { + value["valid"] == Value::Bool(false) +} + +/// The stored foregrounds KDE resolves as an invalid colour, as (set, +/// foreground) pairs: the Active colour of the set is recorded invalid. +/// Any other invalid Active colour (a background or a decoration) fails, +/// since the skipping rule below would not cover what derives from it. +fn divergent_foregrounds(scheme: &Value) -> Vec<(ColorSet, ForegroundRole)> { + let mut out = Vec::new(); + for set in ColorSet::ALL { + let active = &scheme["sets"][set_key(set)]["Active"]; + for role in ForegroundRole::ALL { + if is_invalid(&active["foreground"][foreground_key(role)]) { + out.push((set, role)); + } + } + for role in BackgroundRole::ALL { + assert!( + !is_invalid(&active["background"][background_key(role)]), + "{} {} background {} is invalid", + name(scheme), + set_key(set), + background_key(role) + ); + } + for role in DecorationRole::ALL { + assert!( + !is_invalid(&active["decoration"][decoration_key(role)]), + "{} {} decoration {} is invalid", + name(scheme), + set_key(set), + decoration_key(role) + ); + } + } + out +} + +/// The set and foreground a palette role reads, for the roles read from a +/// foreground. +fn palette_foreground(role: PaletteRole) -> Option<(ColorSet, ForegroundRole)> { + match role { + PaletteRole::WindowText => Some((ColorSet::Window, ForegroundRole::Normal)), + PaletteRole::Text => Some((ColorSet::View, ForegroundRole::Normal)), + PaletteRole::Link => Some((ColorSet::View, ForegroundRole::Link)), + PaletteRole::LinkVisited => Some((ColorSet::View, ForegroundRole::Visited)), + PaletteRole::PlaceholderText => Some((ColorSet::View, ForegroundRole::Inactive)), + PaletteRole::ButtonText => Some((ColorSet::Button, ForegroundRole::Normal)), + PaletteRole::HighlightedText => Some((ColorSet::Selection, ForegroundRole::Normal)), + PaletteRole::TooltipText => Some((ColorSet::Tooltip, ForegroundRole::Normal)), + _ => None, + } +} + +#[test] +fn the_invalid_colours_are_exactly_the_known_ones_and_take_their_default() { + let all = vectors(); + let mut found = Vec::new(); + for scheme in schemes(&all) { + for (set, role) in divergent_foregrounds(scheme) { + found.push((name(scheme).to_owned(), set, role)); + } + } + assert_eq!( + found, + vec![ + // `ForegroundLink=` (an empty value). + ( + "SyntheticMalformed".to_owned(), + ColorSet::View, + ForegroundRole::Link + ), + // `ForegroundVisited=rebeccapurple`, not an SVG 1.1 keyword. + ( + "SyntheticValueForms".to_owned(), + ColorSet::View, + ForegroundRole::Visited + ), + ] + ); + let view_default = KdeColorScheme::kcolorscheme_defaults(); + let view_default = view_default.set(ColorSet::View); + let malformed = read_scheme(find(&all, "SyntheticMalformed")); + assert_eq!( + malformed.set(ColorSet::View).foreground_link, + view_default.foreground_link + ); + let forms = read_scheme(find(&all, "SyntheticValueForms")); + assert_eq!( + forms.set(ColorSet::View).foreground_visited, + view_default.foreground_visited + ); +} + +#[test] +fn every_scheme_resolves_to_the_golden_colours() { + let all = vectors(); + let mut failures = Vec::new(); + let mut compared = 0_usize; + for scheme in schemes(&all) { + let n = name(scheme); + let palette = read_scheme(scheme).resolve(); + let divergent = divergent_foregrounds(scheme); + for state in ColorState::ALL { + let sk = state_key(state); + for set in ColorSet::ALL { + let golden = &scheme["sets"][set_key(set)][sk]; + assert!(golden.is_object(), "{n} has no {} {sk} set", set_key(set)); + let got = palette.set(state, set); + let at = |kind: &str, key: &str| format!("{n} {sk} {} {kind} {key}", set_key(set)); + for role in ForegroundRole::ALL { + if divergent.contains(&(set, role)) { + continue; + } + let key = foreground_key(role); + check( + &mut failures, + at("foreground", key), + got.foreground(role), + &golden["foreground"][key], + ); + compared += 1; + } + for role in BackgroundRole::ALL { + if role + .tinted_toward() + .is_some_and(|f| divergent.contains(&(set, f))) + { + continue; + } + let key = background_key(role); + check( + &mut failures, + at("background", key), + got.background(role), + &golden["background"][key], + ); + compared += 1; + } + for role in DecorationRole::ALL { + let key = decoration_key(role); + check( + &mut failures, + at("decoration", key), + got.decoration(role), + &golden["decoration"][key], + ); + compared += 1; + } + for role in ShadeRole::ALL { + let key = shade_key(role); + check( + &mut failures, + at("shade", key), + got.shade(role), + &golden["shade"][key], + ); + compared += 1; + } + } + let golden = &scheme["palette"][sk]; + for role in PaletteRole::ALL { + if palette_foreground(role).is_some_and(|pair| divergent.contains(&pair)) { + continue; + } + let key = palette_key(role); + check( + &mut failures, + format!("{n} {sk} palette {key}"), + palette.role(state, role), + &golden[key], + ); + compared += 1; + } + // The one role the palette does not take from the scheme: KDE + // leaves it at the toolkit's opaque white. + assert_eq!(golden_hex(&golden["BrightText"]), "#ffffffff"); + } + } + assert!(compared > 0, "nothing was compared"); + assert!( + failures.is_empty(), + "{} of {compared} colours differ:\n{}", + failures.len(), + failures.join("\n") + ); +} + +#[test] +fn every_golden_set_and_palette_role_is_compared() { + let all = vectors(); + let scheme = &schemes(&all)[0]; + let set = &scheme["sets"]["Window"]["Active"]; + let count = |v: &Value| v.as_object().expect("an object").len(); + assert_eq!(count(&scheme["sets"]), ColorSet::ALL.len()); + assert_eq!(count(&scheme["sets"]["Window"]), ColorState::ALL.len()); + assert_eq!(count(&set["foreground"]), ForegroundRole::ALL.len()); + assert_eq!(count(&set["background"]), BackgroundRole::ALL.len()); + assert_eq!(count(&set["decoration"]), DecorationRole::ALL.len()); + assert_eq!(count(&set["shade"]), ShadeRole::ALL.len()); + // The twenty roles and the white BrightText. + assert_eq!( + count(&scheme["palette"]["Active"]), + PaletteRole::ALL.len() + 1 + ); +} + +#[test] +fn the_contrast_value_is_the_recorded_one() { + let all = vectors(); + for scheme in schemes(&all) { + let want = scheme["contrastF"].as_f64().expect("contrastF is a number"); + let got = read_scheme(scheme).contrast_value(); + assert_eq!(got, want, "{}", name(scheme)); + } +} + +#[test] +fn the_stock_constants_resolve_like_the_vectors() { + let all = vectors(); + for (wanted, constant) in [ + ("BreezeLight", KdeColorScheme::breeze_light()), + ("BreezeDark", KdeColorScheme::breeze_dark()), + ("BreezeClassic", KdeColorScheme::breeze_classic()), + ] { + assert_eq!( + constant.resolve(), + read_scheme(find(&all, wanted)).resolve(), + "{wanted}" + ); + } + assert_eq!( + KdeColorScheme::kcolorscheme_defaults().resolve(), + read_scheme(find(&all, "SyntheticEmpty")).resolve() + ); +} + +/// Whether each scheme is dark. Only the Active window background decides. +#[test] +fn is_dark_follows_the_window_background() { + let all = vectors(); + let expected = [ + ("BreezeLight", false), + ("BreezeDark", true), + ("BreezeClassic", false), + ("Oxygen", false), + ("OxygenCold", false), + ("OxygenDark", true), + ("SyntheticNoHeader", false), + ("SyntheticInactiveEffects", true), + ("SyntheticInactiveSubgroups", false), + ("SyntheticAccentKeys", false), + ("SyntheticEmpty", false), + ("SyntheticMalformed", false), + // Window 200,190,180: grey (2200 + 3040 + 900) / 32 = 191, just + // below the threshold. + ("SyntheticValueForms", true), + ]; + assert_eq!(expected.len(), schemes(&all).len(), "a scheme is unlisted"); + for (wanted, dark) in expected { + assert_eq!(read_scheme(find(&all, wanted)).is_dark(), dark, "{wanted}"); + } +} + +/// The input of `base` with one group replaced by the same group of +/// `donor`. +fn with_group_from(base: &Value, donor: &Value, group: &str) -> KdeColorScheme { + let mut mixed = base.clone(); + mixed["input"][group] = donor["input"][group].clone(); + read_scheme(&mixed) +} + +/// A Twilight-like scheme (Breeze Light with Breeze Dark's window) is dark +/// although its content views are light, and the reverse is light. +#[test] +fn is_dark_ignores_the_content_views() { + let all = vectors(); + let light = find(&all, "BreezeLight"); + let dark = find(&all, "BreezeDark"); + + let twilight = with_group_from(light, dark, "Colors:Window"); + assert_eq!( + twilight.set(ColorSet::Window).background_normal.to_rgba8(), + [32, 35, 38, 255] + ); + assert_eq!( + twilight.set(ColorSet::View).background_normal.to_rgba8(), + [255, 255, 255, 255] + ); + assert!(twilight.is_dark()); + + let reverse = with_group_from(dark, light, "Colors:Window"); + assert_eq!( + reverse.set(ColorSet::View).background_normal.to_rgba8(), + [20, 22, 24, 255] + ); + assert!(!reverse.is_dark()); + + // SyntheticMalformed's window is a light grey under a saturated orange + // view. + let malformed = read_scheme(find(&all, "SyntheticMalformed")); + assert_eq!( + malformed.set(ColorSet::View).background_normal.to_rgba8(), + [255, 136, 0, 255] + ); + assert!(!malformed.is_dark()); +} diff --git a/crates/teksilo-kde-scheme/tests/grammar.rs b/crates/teksilo-kde-scheme/tests/grammar.rs new file mode 100644 index 000000000..ea7be96d2 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/grammar.rs @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The configuration grammar, one rule per test, including the malformed +//! inputs the parser must survive without failing. + +use teksilo_kde_scheme::{DiagnosticKind, KConfig, ParseDiagnostic}; + +fn parse(text: &str) -> (KConfig, Vec) { + KConfig::parse(text) +} + +fn kinds(diagnostics: &[ParseDiagnostic]) -> Vec { + diagnostics.iter().map(|d| d.kind.clone()).collect() +} + +#[test] +fn an_empty_file_contributes_nothing() { + let (config, diagnostics) = parse(""); + assert_eq!(config, KConfig::default()); + assert!(diagnostics.is_empty()); +} + +#[test] +fn entries_before_any_header_belong_to_the_root_group() { + let (config, _) = parse("Key=Value\n[G]\nOther=1\n"); + assert_eq!(config.get(&[], "Key"), Some("Value")); + assert_eq!(config.get(&["G"], "Other"), Some("1")); + assert_eq!(config.get(&["G"], "Key"), None); +} + +#[test] +fn keys_and_values_are_trimmed_and_the_value_may_contain_equals_signs() { + let (config, _) = parse("[G]\n Key = a=b = c \n"); + assert_eq!(config.get(&["G"], "Key"), Some("a=b = c")); +} + +#[test] +fn hash_lines_and_blank_lines_are_comments_but_semicolons_are_not() { + let (config, diagnostics) = + parse("# comment\n\n \n # indented comment\n[G]\n;Key=1\nValue=has # inside\n"); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + assert_eq!(config.get(&["G"], ";Key"), Some("1")); + assert_eq!(config.get(&["G"], "Value"), Some("has # inside")); +} + +#[test] +fn crlf_line_endings_read_like_lf() { + let (crlf, d1) = parse("[G]\r\nKey=Value\r\n"); + let (lf, d2) = parse("[G]\nKey=Value\n"); + assert_eq!(crlf, lf); + assert!(d1.is_empty() && d2.is_empty()); +} + +#[test] +fn a_file_with_lone_cr_separators_is_a_single_line() { + let (config, _) = parse("[G]\rKey=Value\r"); + // The whole file is one header line: `[G]` followed by ignored text. + assert_eq!(config.get(&["G"], "Key"), None); + assert!(config.groups().next().is_none()); +} + +#[test] +fn a_byte_order_mark_hides_the_first_header_and_is_reported() { + let (config, diagnostics) = parse("\u{FEFF}[General]\nColorScheme=BreezeDark\n"); + assert_eq!( + kinds(&diagnostics), + vec![DiagnosticKind::ByteOrderMark, DiagnosticKind::MissingEquals] + ); + assert_eq!(config.get(&["General"], "ColorScheme"), None); + assert_eq!(config.get(&[], "ColorScheme"), Some("BreezeDark")); +} + +#[test] +fn nested_groups_are_separate_from_their_parent() { + let (config, _) = parse("[Colors:Header]\nA=1\n[Colors:Header][Inactive]\nB=2\n"); + assert_eq!(config.get(&["Colors:Header"], "A"), Some("1")); + assert_eq!(config.get(&["Colors:Header", "Inactive"], "B"), Some("2")); + assert_eq!(config.get(&["Colors:Header", "Inactive"], "A"), None); + assert_eq!(config.get(&["Colors:Header"], "B"), None); +} + +#[test] +fn text_after_the_last_closing_bracket_of_a_header_is_ignored() { + let (config, _) = parse("[G] trailing\nKey=1\n[A][B]x[C]\nOther=2\n"); + assert_eq!(config.get(&["G"], "Key"), Some("1")); + assert_eq!(config.get(&["A", "B"], "Other"), Some("2")); +} + +#[test] +fn an_unclosed_header_is_skipped_and_the_current_group_is_kept() { + let (config, diagnostics) = parse("[G]\nA=1\n[Broken\nB=2\n"); + assert_eq!( + kinds(&diagnostics), + vec![DiagnosticKind::UnclosedGroupHeader] + ); + assert_eq!(diagnostics[0].line, 3); + assert_eq!(config.get(&["G"], "B"), Some("2")); +} + +#[test] +fn a_line_without_equals_is_an_error_and_skipped() { + let (config, diagnostics) = parse("[G]\nJustText\nKey=1\n"); + assert_eq!(kinds(&diagnostics), vec![DiagnosticKind::MissingEquals]); + assert_eq!(config.keys(&["G"]), vec!["Key"]); +} + +#[test] +fn a_line_with_an_empty_key_is_ignored_without_an_error() { + let (config, diagnostics) = parse("[G]\n=value\n = other\n"); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + assert!(config.keys(&["G"]).is_empty()); +} + +#[test] +fn the_last_occurrence_of_a_key_in_a_file_wins() { + let (config, _) = parse("[G]\nKey=first\nKey=second\n[Other]\n[G]\nKey=third\n"); + assert_eq!(config.get(&["G"], "Key"), Some("third")); +} + +#[test] +fn an_immutable_occurrence_freezes_the_key_for_the_rest_of_the_file() { + let (config, _) = parse("[G]\nKey=first\nKey[$i]=frozen\nKey=ignored\n"); + let entry = config.entry(&["G"], "Key").unwrap(); + assert_eq!(entry.value(), Some("frozen")); + assert!(entry.is_immutable()); +} + +#[test] +fn inside_an_immutable_group_the_first_occurrence_wins() { + let (config, _) = parse("[G][$i]\nKey=first\nKey=second\n"); + assert_eq!(config.get(&["G"], "Key"), Some("first")); + assert!(config.is_group_immutable(&["G"])); + assert!(config.entry(&["G"], "Key").unwrap().is_immutable()); +} + +#[test] +fn group_immutability_applies_to_that_group_only() { + let (config, _) = parse("[G][$i]\nA=1\n[G][Sub]\nB=2\n"); + assert!(config.is_group_immutable(&["G"])); + assert!(!config.is_group_immutable(&["G", "Sub"])); + assert!(!config.entry(&["G", "Sub"], "B").unwrap().is_immutable()); +} + +#[test] +fn a_dollar_i_segment_that_is_not_last_is_part_of_the_name() { + let (config, _) = parse("[G][$i][H]\nA=1\n[K][$i]x\nB=2\n"); + assert_eq!(config.get(&["G", "$i", "H"], "A"), Some("1")); + assert_eq!(config.get(&["K", "$i"], "B"), Some("2")); + assert!(!config.is_group_immutable(&["K"])); +} + +#[test] +fn a_bare_dollar_i_line_makes_the_rest_of_the_file_immutable() { + let (config, _) = parse("[Before]\nA=1\n[$i]\nB=2\n[After]\nC=3\n"); + assert!(config.is_file_immutable()); + assert!(!config.entry(&["Before"], "A").unwrap().is_immutable()); + // Entries right after the bare line go to the empty-named group. + assert_eq!(config.get(&[""], "B"), Some("2")); + assert!(config.entry(&[""], "B").unwrap().is_immutable()); + assert!(config.entry(&["After"], "C").unwrap().is_immutable()); +} + +#[test] +fn key_options_are_read_from_trailing_brackets_in_either_order_with_a_locale() { + let (config, diagnostics) = parse("[G]\nA[$ie]=1\nB[$i][de]=2\nC[de][$i]=3\nD[$xz]=4\n"); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + let a = config.entry(&["G"], "A").unwrap(); + assert!(a.is_immutable() && a.expands()); + // Locale-tagged entries are ignored, whatever their options. + assert_eq!(config.get(&["G"], "B"), None); + assert_eq!(config.get(&["G"], "C"), None); + // Unknown option letters are ignored. + let d = config.entry(&["G"], "D").unwrap(); + assert!(!d.is_immutable() && !d.expands()); +} + +#[test] +fn locale_tagged_entries_are_ignored() { + let (config, _) = parse("[General]\nName=Breeze Light\nName[fr]=Breeze clair\n"); + assert_eq!(config.get(&["General"], "Name"), Some("Breeze Light")); + assert_eq!(config.keys(&["General"]), vec!["Name"]); +} + +#[test] +fn a_second_locale_tag_is_an_error() { + let (config, diagnostics) = parse("[G]\nName[fr][de]=x\nOk=1\n"); + assert_eq!(kinds(&diagnostics), vec![DiagnosticKind::SecondLocale]); + assert_eq!(config.keys(&["G"]), vec!["Ok"]); +} + +#[test] +fn an_unclosed_key_bracket_is_an_error() { + let (config, diagnostics) = parse("[G]\nKey[de=x\nKe[y]x=literal\n"); + assert_eq!( + kinds(&diagnostics), + vec![DiagnosticKind::UnclosedKeyBracket] + ); + assert_eq!(config.get(&["G"], "Ke[y]x"), Some("literal")); +} + +#[test] +fn a_deleted_entry_needs_no_equals_and_reads_as_absent() { + let (config, diagnostics) = parse("[G]\nKey[$d]\nOther[$d]=ignored\n"); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + assert!(config.entry(&["G"], "Key").unwrap().is_deleted()); + assert_eq!(config.get(&["G"], "Key"), None); + assert_eq!(config.get(&["G"], "Other"), None); + assert!(!config.has_key(&["G"], "Other")); + assert!(!config.group_exists(&["G"])); +} + +#[test] +fn escapes_are_decoded_in_values_keys_and_group_names() { + let (config, diagnostics) = + parse("[Sp\\sace]\nTab\\tKey=\\sa\\tb\\nc\\rd\\\\e\\x41\\x6a\\s\nList=a\\,b\\;c\n"); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + assert_eq!( + config.get(&["Sp ace"], "Tab\tKey"), + Some(" a\tb\nc\rd\\eAj ") + ); + assert_eq!(config.get(&["Sp ace"], "List"), Some("a\\,b\\;c")); +} + +#[test] +fn a_trailing_backslash_is_kept() { + let (config, _) = parse("[G]\nKey=path\\\n"); + assert_eq!(config.get(&["G"], "Key"), Some("path\\")); +} + +#[test] +fn hex_escapes_with_too_few_or_non_hex_digits_yield_x() { + let (config, diagnostics) = parse("[G]\nShort=ab\\x4\nBad=a\\xzzb\nEnd=a\\x\n"); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + assert_eq!(config.get(&["G"], "Short"), Some("abx")); + assert_eq!(config.get(&["G"], "Bad"), Some("axb")); + assert_eq!(config.get(&["G"], "End"), Some("ax")); +} + +#[test] +fn an_unknown_escape_is_kept_verbatim_and_reported() { + let (config, diagnostics) = parse("[G]\nKey=a\\qb\\sc\n"); + assert_eq!( + kinds(&diagnostics), + vec![DiagnosticKind::InvalidEscape('q')] + ); + assert_eq!(config.get(&["G"], "Key"), Some("a\\qb c")); +} + +#[test] +fn invalid_utf8_is_replaced_and_hex_escapes_can_produce_it_too() { + let (config, diagnostics) = KConfig::parse_bytes(b"[G]\nKey=a\xffb\nEsc=\\xc3\\xa9\\xff\n"); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + assert_eq!(config.get(&["G"], "Key"), Some("a\u{FFFD}b")); + assert_eq!(config.get(&["G"], "Esc"), Some("\u{e9}\u{FFFD}")); +} + +#[test] +fn a_line_longer_than_one_and_a_half_mebibytes_is_skipped() { + let long = format!("[G]\nLong={}\nShort=1\n", "x".repeat(1536 * 1024)); + let (config, diagnostics) = parse(&long); + assert_eq!(kinds(&diagnostics), vec![DiagnosticKind::LineTooLong]); + assert_eq!(config.get(&["G"], "Long"), None); + assert_eq!(config.get(&["G"], "Short"), Some("1")); +} + +#[test] +fn reading_stops_after_the_hundredth_counted_error() { + let mut text = String::from("[G]\nBefore=1\n"); + for _ in 0..100 { + text.push_str("broken\n"); + } + text.push_str("After=2\n"); + let (config, diagnostics) = parse(&text); + assert_eq!(config.get(&["G"], "Before"), Some("1")); + assert_eq!(config.get(&["G"], "After"), None); + assert_eq!(diagnostics.len(), 101); + assert_eq!( + diagnostics.last().unwrap().kind, + DiagnosticKind::TooManyErrors + ); +} + +#[test] +fn ninety_nine_errors_do_not_stop_the_file() { + let mut text = String::from("[G]\n"); + for _ in 0..99 { + text.push_str("broken\n"); + } + text.push_str("After=2\n"); + let (config, diagnostics) = parse(&text); + assert_eq!(config.get(&["G"], "After"), Some("2")); + assert_eq!(diagnostics.len(), 99); +} + +#[test] +fn a_group_exists_only_with_a_live_entry_in_it_or_below_it() { + let (config, _) = parse("[Empty]\n[Gone]\nK[$d]\n[Parent][Child]\nK=1\n"); + assert!(!config.group_exists(&["Empty"])); + assert!(!config.group_exists(&["Gone"])); + assert!(config.group_exists(&["Parent"])); + assert!(config.group_exists(&["Parent", "Child"])); + assert!(!config.group_exists(&["Parent", "Other"])); +} + +#[test] +fn typed_reads_follow_kde_rules() { + let (config, _) = parse( + "[G]\nT=yes\nF=Off\nZero=0\nEmpty=\nInt= 12\nBadInt=1.5\nReal=0.15\nBadReal=abc\nInf=inf\n", + ); + let g = &["G"]; + assert_eq!(config.read_bool(g, "T"), Some(true)); + assert_eq!(config.read_bool(g, "F"), Some(false)); + assert_eq!(config.read_bool(g, "Zero"), Some(false)); + // An empty value is present, and every value but the four false words + // reads as true. + assert_eq!(config.read_bool(g, "Empty"), Some(true)); + assert_eq!(config.read_bool(g, "Missing"), None); + // Values are trimmed when the entry is read, so " 12" is "12". + assert_eq!(config.read_int(g, "Int", 7), 12); + assert_eq!(config.read_int(g, "BadInt", 7), 7); + assert_eq!(config.read_int(g, "Empty", 7), 7); + assert_eq!(config.read_real(g, "Real", 0.2), 0.15); + assert_eq!(config.read_real(g, "BadReal", 0.2), 0.2); + assert_eq!(config.read_real(g, "Inf", 0.2), 0.2); +} + +#[test] +fn diagnostics_carry_one_based_line_numbers_and_display_them() { + let (_, diagnostics) = parse("[G]\nok=1\n\nbroken\n"); + assert_eq!(diagnostics[0].line, 4); + assert_eq!(diagnostics[0].to_string(), "line 4: the line has no `=`"); +} diff --git a/crates/teksilo-kde-scheme/tests/math.rs b/crates/teksilo-kde-scheme/tests/math.rs new file mode 100644 index 000000000..0b1686bf5 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/math.rs @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The worked examples of section d of Teksilo's KDE specification, the +//! 8-bit blends the golden vectors reach only through the accent oracle, +//! and Teksilo's own WCAG lifts, [`lift_luma_until`] and +//! [`lift_luma_toward`]. +//! +//! `tests/golden_math.rs` checks the same functions against every measured +//! record; this file needs no vector and is published with the crate. + +use teksilo_kde_scheme::KdeColor; +use teksilo_kde_scheme::math::{ + self, Hcy, LumaDirection, ShadeRole, accent_over, black_or_white_on, blend8, darken, + lift_luma_toward, lift_luma_until, luma, shade_role, tint, +}; +use teksilo_tokens::contrast::composite_contrast; + +fn rgb(r: u8, g: u8, b: u8) -> KdeColor { + KdeColor::rgb8(r, g, b) +} + +fn rgb8(c: KdeColor) -> [u8; 3] { + let [r, g, b, _] = c.to_rgba8(); + [r, g, b] +} + +fn wcag(fg: KdeColor, bg: KdeColor) -> f32 { + composite_contrast(fg.to_color(), bg.to_color()) +} + +#[test] +fn the_breeze_accent_has_the_specified_hcy_coordinates_and_returns() { + let accent = rgb(61, 174, 233); + let hcy = Hcy::from_color(accent); + assert!((hcy.h - 0.583_362_32).abs() < 1e-8, "{hcy:?}"); + assert!((hcy.c - 0.885_926_16).abs() < 1e-8, "{hcy:?}"); + assert!((hcy.y - 0.376_834_96).abs() < 1e-8, "{hcy:?}"); + assert_eq!(hcy.to_color(), accent); +} + +#[test] +fn a_red_dominant_colour_with_more_blue_than_green_has_a_negative_raw_hue() { + let hcy = Hcy::from_color(rgb(233, 58, 154)); + assert!(hcy.h < 0.0 && hcy.h > -1.0 / 6.0, "{hcy:?}"); + assert!((hcy.wrapped_hue() - (hcy.h + 1.0)).abs() < 1e-15); + assert_eq!(hcy.to_color(), rgb(233, 58, 154)); +} + +#[test] +fn breeze_light_window_shades_match_the_worked_example() { + let window = rgb(239, 240, 241); + let expected = [ + (ShadeRole::Light, [255, 255, 255]), + (ShadeRole::Midlight, [246, 247, 247]), + (ShadeRole::Mid, [196, 200, 204]), + (ShadeRole::Dark, [136, 142, 147]), + (ShadeRole::Shadow, [71, 74, 76]), + ]; + for (role, want) in expected { + assert_eq!(rgb8(shade_role(window, role, 0.4, 0.0)), want, "{role:?}"); + } +} + +#[test] +fn the_selection_tints_of_breeze_light_match_the_worked_examples() { + let accent = rgb(61, 174, 233); + assert_eq!(rgb8(tint(rgb(239, 240, 241), accent, 0.4)), [194, 224, 245]); + assert_eq!(rgb8(tint(rgb(227, 229, 231), accent, 0.4)), [176, 216, 243]); +} + +#[test] +fn the_link_darkening_matches_the_worked_example() { + let accent = rgb(61, 174, 233); + assert!(math::color_luma_ratio(accent, KdeColor::WHITE) < 5.0); + assert_eq!(rgb8(darken(accent, 0.5, 1.0)), [45, 127, 170]); +} + +#[test] +fn blend8_truncates_rather_than_rounds() { + // Section d.12: white at 0.6 over white paints 254, not 255. + let white_at_06 = KdeColor { + a: math::store_channel(0.6), + ..KdeColor::WHITE + }; + assert_eq!(white_at_06.a, 39321); + assert_eq!( + blend8(white_at_06, KdeColor::WHITE).to_rgba8(), + [254, 254, 254, 255] + ); +} + +#[test] +fn blend8_returns_the_background_for_a_transparent_foreground() { + let background = KdeColor::rgba16(1000, 2000, 3000, 40000); + assert_eq!(blend8(KdeColor::TRANSPARENT, background), background); +} + +#[test] +fn blend8_over_a_translucent_background_keeps_kde_s_nearly_transparent_alpha() { + let fg = KdeColor::rgba8(200, 100, 50, 128); + let bg = KdeColor::rgba8(10, 20, 30, 128); + let out = blend8(fg, bg); + let [_, _, _, a] = out.to_rgba8(); + assert!(a <= 1, "alpha {a} of {out:?}"); +} + +#[test] +fn the_accent_over_a_background_matches_the_worked_examples() { + let accent = rgb(61, 174, 233); + assert_eq!(rgb8(accent_over(accent, KdeColor::WHITE)), [119, 198, 239]); + assert_eq!(rgb8(accent_over(accent, rgb(20, 22, 24))), [48, 128, 170]); + assert_eq!( + rgb8(accent_over(accent, rgb(252, 252, 252))), + [118, 197, 238] + ); + // Only the accent's 8-bit channels count: its own alpha is replaced. + let translucent = KdeColor::rgba8(61, 174, 233, 10); + assert_eq!( + accent_over(translucent, KdeColor::WHITE), + accent_over(accent, KdeColor::WHITE) + ); +} + +#[test] +fn the_ten_swatches_give_the_specified_selection_and_text() { + let light = KdeColor::WHITE; + let dark = rgb(20, 22, 24); + // (swatch, light selection, black text on it, dark selection, black text on it) + let table = [ + ([233, 58, 154], [239, 117, 184], true, [169, 47, 115], false), + ([233, 61, 88], [239, 119, 138], true, [169, 49, 68], false), + ([233, 100, 58], [239, 146, 117], true, [169, 76, 47], false), + ([232, 203, 45], [238, 218, 107], true, [168, 148, 38], true), + ([61, 212, 37], [119, 224, 102], true, [48, 155, 33], true), + ([0, 211, 184], [76, 224, 205], true, [5, 154, 136], true), + ([61, 174, 233], [119, 198, 239], true, [48, 128, 170], true), + ( + [184, 117, 220], + [205, 158, 230], + true, + [134, 88, 161], + false, + ), + ( + [146, 110, 228], + [178, 153, 236], + true, + [108, 83, 166], + false, + ), + ([104, 107, 111], [149, 151, 154], true, [78, 81, 84], false), + ]; + for ([r, g, b], on_light, light_black, on_dark, dark_black) in table { + let swatch = rgb(r, g, b); + for (backdrop, selection, black) in + [(light, on_light, light_black), (dark, on_dark, dark_black)] + { + let fill = accent_over(swatch, backdrop); + assert_eq!(rgb8(fill), selection, "swatch {swatch}"); + let text = black_or_white_on(fill, true); + let want = if black { + KdeColor::BLACK + } else { + KdeColor::WHITE + }; + assert_eq!(text, want, "text on {fill} for swatch {swatch}"); + } + } +} + +#[test] +fn inactive_text_is_the_chosen_colour_at_60_percent_over_the_background() { + let fill = rgb(119, 198, 239); + let text = black_or_white_on(fill, false); + // Black at 0.6 over the fill: each channel truncated to 40 % of it. + assert_eq!(rgb8(text), [47, 79, 95]); + assert_eq!(text.to_rgba8()[3], 255); +} + +#[test] +fn black_text_starts_just_above_the_luma_threshold() { + let threshold = 1.05 / 4.5 - 0.05; + for v in 0..=255_u8 { + let grey = rgb(v, v, v); + let black = black_or_white_on(grey, true) == KdeColor::BLACK; + assert_eq!(black, luma(grey) > threshold, "grey {v}"); + } +} + +#[test] +fn an_identical_tint_target_returns_the_base_colour_s_luma() { + let base = rgb(35, 38, 41); + assert_eq!(rgb8(math::oklab_tint(base, base, 0.25)), [35, 38, 41]); +} + +// Teksilo's WCAG lift. + +#[test] +fn a_colour_that_already_passes_is_unchanged() { + let fg = rgb(35, 38, 41); + assert_eq!(lift_luma_until(fg, &[KdeColor::WHITE], 4.5), fg); +} + +#[test] +fn no_backdrop_means_no_change() { + let fg = rgb(200, 200, 200); + assert_eq!(lift_luma_until(fg, &[], 4.5), fg); +} + +#[test] +fn a_light_accent_on_white_darkens_to_the_floor_and_keeps_its_hue() { + let accent = rgb(61, 174, 233); + assert!(wcag(accent, KdeColor::WHITE) < 4.5); + let lifted = lift_luma_until(accent, &[KdeColor::WHITE], 4.5); + let contrast = wcag(lifted, KdeColor::WHITE); + assert!(contrast >= 4.5, "{lifted} reaches {contrast}"); + // The first passing step, so not much past the floor. + assert!(contrast < 4.7, "{lifted} overshoots to {contrast}"); + assert!(luma(lifted) < luma(accent)); + let (before, after) = (Hcy::from_color(accent), Hcy::from_color(lifted)); + assert!( + (before.wrapped_hue() - after.wrapped_hue()).abs() < 0.01, + "{before:?} {after:?}" + ); + assert!((before.c - after.c).abs() < 0.02, "{before:?} {after:?}"); +} + +#[test] +fn a_dark_colour_on_a_dark_backdrop_lightens() { + let fg = rgb(41, 128, 185); + let bg = rgb(20, 22, 24); + let lifted = lift_luma_until(fg, &[bg], 7.0); + assert!(wcag(lifted, bg) >= 7.0, "{lifted}"); + assert!(luma(lifted) > luma(fg)); +} + +#[test] +fn the_floor_holds_against_every_backdrop() { + let fg = rgb(150, 150, 150); + let backdrops = [KdeColor::WHITE, rgb(239, 240, 241), rgb(222, 224, 226)]; + let lifted = lift_luma_until(fg, &backdrops, 4.5); + for bg in backdrops { + assert!(wcag(lifted, bg) >= 4.5, "{lifted} on {bg}"); + } +} + +#[test] +fn an_unreachable_floor_returns_the_best_reachable_colour() { + let fg = rgb(61, 174, 233); + let grey = rgb(119, 119, 119); + let lifted = lift_luma_until(fg, &[grey], 21.0); + let best = wcag(lifted, grey); + assert!(best < 21.0); + // Black is the strongest the lift can reach on this grey, and it gets + // there: its luma end is black whatever the chroma. + assert!( + (best - wcag(KdeColor::BLACK, grey)).abs() < 1e-4, + "{lifted} reaches {best}" + ); +} + +#[test] +fn the_lift_keeps_alpha() { + let fg = KdeColor::rgba8(61, 174, 233, 200); + let lifted = lift_luma_until(fg, &[KdeColor::WHITE], 3.0); + assert_eq!(lifted.a, fg.a); + assert!(wcag(lifted, KdeColor::WHITE) >= 3.0, "{lifted}"); +} + +#[test] +fn a_directed_lift_never_crosses_to_the_other_end() { + // A light fill under a mid-grey text: black is the stronger end, so the + // free lift crosses the text and ends near black, while the lift held + // to the lighter end ends as light as it can. + let fill = rgb(204, 233, 249); + let text = rgb(125, 125, 125); + let free = lift_luma_until(fill, &[text], 4.5); + assert!(luma(free) < luma(text), "{free}"); + let held = lift_luma_toward(fill, &[text], 4.5, LumaDirection::Lighter); + assert!(luma(held) >= luma(fill), "{held}"); + // White falls short too (4.1:1), so the end of the scale is the best it + // reaches. + assert!(wcag(text, KdeColor::WHITE) < 4.5); + assert!((wcag(held, text) - wcag(KdeColor::WHITE, text)).abs() < 1e-4); +} + +#[test] +fn a_directed_lift_stops_at_its_first_passing_step() { + let fill = rgb(61, 174, 233); + let text = rgb(35, 38, 41); + assert!(wcag(fill, text) < 7.0); + let held = lift_luma_toward(fill, &[text], 7.0, LumaDirection::Lighter); + let contrast = wcag(held, text); + assert!(contrast >= 7.0, "{held}"); + assert!(contrast < 7.3, "{held} overshoots to {contrast}"); + // The other way only lowers the contrast: the colour comes back as it + // was. + let wrong = lift_luma_toward(fill, &[text], 7.0, LumaDirection::Darker); + assert_eq!(wrong, fill); +} + +#[test] +fn a_directed_lift_keeps_a_passing_colour_and_needs_a_backdrop() { + let fg = rgb(35, 38, 41); + assert_eq!( + lift_luma_toward(fg, &[KdeColor::WHITE], 4.5, LumaDirection::Lighter), + fg + ); + assert_eq!(lift_luma_toward(fg, &[], 4.5, LumaDirection::Darker), fg); +} diff --git a/crates/teksilo-kde-scheme/tests/palette.rs b/crates/teksilo-kde-scheme/tests/palette.rs new file mode 100644 index 000000000..6911d8417 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/palette.rs @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! State effects, the resolved palette, the dark predicate and the colour +//! report, on the stock Breeze schemes and on schemes built to exercise one +//! rule each. The expected 8-bit values are the specification's worked +//! examples (sections e.4, f.1 and d.10); `golden_resolve.rs` checks every +//! colour against the measured vectors. + +use teksilo_kde_scheme::effects::{ + SELECTION_TINT_AMOUNT, apply_state_effect, background_chain, foreground_chain, + tint_toward_selection, +}; +use teksilo_kde_scheme::math::{darken, lighten, mix, tint}; +use teksilo_kde_scheme::{ + BackgroundRole, ColorEffect, ColorRole, ColorSet, ColorState, ContrastEffect, ForegroundRole, + IntensityEffect, KdeColor, KdeColorScheme, PaletteRole, StateEffect, +}; +use teksilo_tokens::ColorSchemePreference; + +fn rgb(c: KdeColor) -> [u8; 3] { + let [r, g, b, _] = c.to_rgba8(); + [r, g, b] +} + +#[test] +fn breeze_disabled_darkens_a_fade_toward_the_background() { + let scheme = KdeColorScheme::breeze_light(); + let disabled = scheme.state_colors(ColorState::Disabled, ColorSet::Window); + assert_eq!(rgb(disabled.foreground_normal), [160, 161, 163]); + assert_eq!(rgb(disabled.background_normal), [227, 229, 231]); + + // Every foreground and decoration is the chain darken(mix(X, BG0, 0.65), + // 0.1) against the background as it was before the effect; every + // background is darken(X, 0.1). + let window = scheme.set(ColorSet::Window); + let background = window.background_normal; + for role in ColorRole::ALL { + let stored = window.get(role); + let expected = match role { + ColorRole::BackgroundNormal | ColorRole::BackgroundAlternate => { + darken(stored, 0.1, 1.0) + } + _ => darken(mix(stored, background, 0.65), 0.1, 1.0), + }; + assert_eq!(disabled.get(role), expected, "{role:?}"); + } +} + +#[test] +fn the_chains_apply_contrast_then_intensity_then_colour() { + let effect = StateEffect { + enabled: true, + intensity_effect: IntensityEffect::Lighten, + intensity_amount: 0.2, + color_effect: ColorEffect::Tint, + color_amount: 0.5, + color: KdeColor::rgb8(56, 56, 56), + contrast_effect: ContrastEffect::Tint, + contrast_amount: 0.6, + }; + let x = KdeColor::rgb8(200, 60, 40); + let bg = KdeColor::rgb8(20, 30, 40); + let lightened = lighten(x, 0.2, 1.0); + assert_eq!( + background_chain(x, &effect), + tint(lightened, effect.color, 0.5) + ); + let contrasted = tint(x, bg, 0.6); + assert_eq!( + foreground_chain(x, bg, &effect), + background_chain(contrasted, &effect) + ); +} + +#[test] +fn a_disabled_effect_passes_colours_through_without_requantising() { + // A 16-bit colour that an HCY round trip would move. + let odd = KdeColor::rgba16(12345, 23456, 34567, 65535); + let mut effect = StateEffect::DISABLED_DEFAULT; + effect.enabled = false; + assert_eq!(background_chain(odd, &effect), odd); + assert_eq!(foreground_chain(odd, KdeColor::WHITE, &effect), odd); + let mut set = *KdeColorScheme::breeze_light().set(ColorSet::View); + set.foreground_normal = odd; + assert_eq!(apply_state_effect(&set, &effect), set); +} + +#[test] +fn breeze_inactive_selection_is_the_window_tinted_toward_the_selection() { + let scheme = KdeColorScheme::breeze_light(); + let inactive = scheme.state_colors(ColorState::Inactive, ColorSet::Selection); + assert_eq!(rgb(inactive.background_normal), [194, 224, 245]); + assert_eq!(rgb(inactive.background_alternate), [176, 216, 243]); + assert_eq!(rgb(inactive.foreground_normal), [35, 38, 41]); + assert_eq!( + inactive, + tint_toward_selection( + scheme.set(ColorSet::Window), + scheme.set(ColorSet::Selection).background_normal + ) + ); + assert_eq!(SELECTION_TINT_AMOUNT, 0.4); + + let palette = scheme.resolve(); + assert_eq!( + rgb(palette.role(ColorState::Inactive, PaletteRole::Highlight)), + [194, 224, 245] + ); + assert_eq!( + rgb(palette.role(ColorState::Inactive, PaletteRole::HighlightedText)), + [35, 38, 41] + ); + assert_eq!( + palette.role(ColorState::Inactive, PaletteRole::Accent), + palette.role(ColorState::Inactive, PaletteRole::Highlight) + ); +} + +#[test] +fn the_selection_tint_follows_the_inactive_window_subgroup() { + let (scheme, _) = KdeColorScheme::parse( + "[Colors:Window]\nBackgroundNormal=239,240,241\n\ + [Colors:Window][Inactive]\nBackgroundNormal=200,200,200\n\ + [Colors:Selection]\nBackgroundNormal=200,40,120\n", + ); + let inactive = scheme.state_colors(ColorState::Inactive, ColorSet::Selection); + let window = scheme.inactive_set(ColorSet::Window).expect("a subgroup"); + assert_eq!( + inactive.background_normal, + tint(window.background_normal, KdeColor::rgb8(200, 40, 120), 0.4) + ); +} + +#[test] +fn without_the_selection_change_an_inactive_selection_keeps_its_colours() { + let mut scheme = KdeColorScheme::breeze_light(); + scheme.change_selection_color = false; + assert_eq!( + scheme.state_colors(ColorState::Inactive, ColorSet::Selection), + *scheme.set(ColorSet::Selection) + ); +} + +#[test] +fn a_disabled_selection_is_the_disabled_window() { + let scheme = KdeColorScheme::breeze_dark(); + assert_eq!( + scheme.state_colors(ColorState::Disabled, ColorSet::Selection), + scheme.state_colors(ColorState::Disabled, ColorSet::Window) + ); +} + +#[test] +fn an_inactive_subgroup_replaces_the_set_and_the_effects_still_run() { + let (scheme, _) = KdeColorScheme::parse( + "[ColorEffects:Inactive]\nEnable=true\nIntensityEffect=2\nIntensityAmount=0.2\n\ + ColorEffect=0\nContrastEffect=0\n\ + [Colors:View][Inactive]\nBackgroundNormal=250,250,250\n", + ); + let inactive = scheme.state_colors(ColorState::Inactive, ColorSet::View); + let source = scheme.inactive_set(ColorSet::View).expect("a subgroup"); + assert_eq!( + inactive.background_normal, + darken(KdeColor::rgb8(250, 250, 250), 0.2, 1.0) + ); + assert_eq!( + inactive.foreground_normal, + darken(source.foreground_normal, 0.2, 1.0) + ); +} + +#[test] +fn breeze_inactive_colours_pass_through() { + let scheme = KdeColorScheme::breeze_light(); + for set in ColorSet::ALL { + if set == ColorSet::Selection { + continue; + } + let expected = *scheme.inactive_set(set).unwrap_or_else(|| scheme.set(set)); + assert_eq!( + scheme.state_colors(ColorState::Inactive, set), + expected, + "{set:?}" + ); + } +} + +#[test] +fn breeze_light_computed_backgrounds() { + let palette = KdeColorScheme::breeze_light().resolve(); + let view = palette.set(ColorState::Active, ColorSet::View); + assert_eq!( + rgb(view.background(BackgroundRole::Active)), + [236, 246, 252] + ); + assert_eq!( + rgb(view.background(BackgroundRole::Negative)), + [250, 236, 237] + ); + assert_eq!( + rgb(view.background(BackgroundRole::Neutral)), + [254, 241, 234] + ); + assert_eq!( + rgb(view.background(BackgroundRole::Positive)), + [221, 251, 232] + ); + // Each computed background tints Normal toward its foreground by 0.3. + let normal = view.background(BackgroundRole::Normal); + for role in BackgroundRole::ALL { + if let Some(foreground) = role.tinted_toward() { + assert_eq!( + view.background(role), + tint(normal, view.foreground(foreground), 0.3), + "{role:?}" + ); + } + } + assert_eq!( + rgb(view.background(BackgroundRole::Alternate)), + [247, 247, 247] + ); + assert_eq!(rgb(view.foreground(ForegroundRole::Link)), [41, 128, 185]); +} + +#[test] +fn computed_backgrounds_follow_the_effects() { + let scheme = KdeColorScheme::breeze_light(); + let palette = scheme.resolve(); + let disabled = palette.set(ColorState::Disabled, ColorSet::View); + let colors = scheme.state_colors(ColorState::Disabled, ColorSet::View); + assert_eq!( + disabled.background(BackgroundRole::Link), + tint(colors.background_normal, colors.foreground_link, 0.3) + ); +} + +#[test] +fn breeze_light_palette_roles() { + let palette = KdeColorScheme::breeze_light().resolve(); + let active = |role| rgb(palette.role(ColorState::Active, role)); + assert_eq!(active(PaletteRole::Window), [239, 240, 241]); + assert_eq!(active(PaletteRole::WindowText), [35, 38, 41]); + assert_eq!(active(PaletteRole::Base), [255, 255, 255]); + assert_eq!(active(PaletteRole::AlternateBase), [247, 247, 247]); + assert_eq!(active(PaletteRole::PlaceholderText), [112, 125, 138]); + assert_eq!(active(PaletteRole::Button), [252, 252, 252]); + assert_eq!(active(PaletteRole::Highlight), [61, 174, 233]); + assert_eq!(active(PaletteRole::HighlightedText), [255, 255, 255]); + assert_eq!(active(PaletteRole::LinkVisited), [155, 89, 182]); + // The shades of the window at contrast 4 (k = 0.4). + assert_eq!(active(PaletteRole::Light), [255, 255, 255]); + assert_eq!(active(PaletteRole::Midlight), [246, 247, 247]); + assert_eq!(active(PaletteRole::Mid), [196, 200, 204]); + assert_eq!(active(PaletteRole::Dark), [136, 142, 147]); + assert_eq!(active(PaletteRole::Shadow), [71, 74, 76]); + // Tooltips use the Active colours in every state. + for state in ColorState::ALL { + assert_eq!( + rgb(palette.role(state, PaletteRole::TooltipBase)), + [247, 247, 247] + ); + assert_eq!( + rgb(palette.role(state, PaletteRole::TooltipText)), + [35, 38, 41] + ); + } + assert_eq!( + rgb(palette.role(ColorState::Disabled, PaletteRole::WindowText)), + [160, 161, 163] + ); +} + +#[test] +fn the_contrast_value_is_a_tenth_of_the_contrast() { + assert_eq!(KdeColorScheme::breeze_light().contrast_value(), 0.1 * 4.0); + assert_eq!( + KdeColorScheme::kcolorscheme_defaults().contrast_value(), + 0.1 * 7.0 + ); +} + +#[test] +fn breeze_light_and_classic_are_light_and_breeze_dark_is_dark() { + assert!(!KdeColorScheme::breeze_light().is_dark()); + assert!(!KdeColorScheme::breeze_classic().is_dark()); + assert!(KdeColorScheme::breeze_dark().is_dark()); + assert!(!KdeColorScheme::kcolorscheme_defaults().is_dark()); +} + +#[test] +fn is_dark_is_the_grey_level_below_192() { + let with_window = |r: u8, g: u8, b: u8| { + KdeColorScheme::parse(&format!("[Colors:Window]\nBackgroundNormal={r},{g},{b}\n")) + .0 + .is_dark() + }; + // (11 r + 16 g + 5 b) / 32 with integer division. + assert!(with_window(191, 191, 191)); + assert!(!with_window(192, 192, 192)); + // 11 x 200 + 16 x 190 + 5 x 180 = 6140, / 32 = 191.875 -> 191: dark, + // although a rounded grey would be 192. + assert!(with_window(200, 190, 180)); + // A dark window under light content views is dark. + let (twilight, _) = KdeColorScheme::parse( + "[Colors:Window]\nBackgroundNormal=49,54,59\n\ + [Colors:View]\nBackgroundNormal=252,252,252\n", + ); + assert!(twilight.is_dark()); +} + +#[test] +fn breeze_light_colour_report() { + let os = KdeColorScheme::breeze_light().os_theme_colors(); + let c = |r, g, b| Some(KdeColor::rgb8(r, g, b).to_color()); + assert_eq!(os.color_scheme, ColorSchemePreference::Light); + assert_eq!(os.accent, c(61, 174, 233)); + assert_eq!(os.window_bg, c(239, 240, 241)); + assert_eq!(os.window_fg, c(35, 38, 41)); + assert_eq!(os.button_bg, c(252, 252, 252)); + assert_eq!(os.button_fg, c(35, 38, 41)); + assert_eq!(os.selection_bg, c(61, 174, 233)); + assert_eq!(os.selection_fg, c(255, 255, 255)); + assert_eq!(os.tooltip_bg, c(247, 247, 247)); + assert_eq!(os.tooltip_fg, c(35, 38, 41)); + assert_eq!(os.view_bg, c(255, 255, 255)); + assert_eq!(os.view_fg, c(35, 38, 41)); + assert_eq!(os.alternate_bg, c(247, 247, 247)); + assert_eq!(os.inactive_fg, c(112, 125, 138)); + assert_eq!(os.link, c(41, 128, 185)); + assert_eq!(os.link_visited, c(155, 89, 182)); + assert_eq!(os.error, c(218, 68, 83)); + assert_eq!(os.warning, c(246, 116, 0)); + assert_eq!(os.success, c(39, 174, 96)); + assert_eq!(os.focus, c(61, 174, 233)); +} + +#[test] +fn breeze_dark_colour_report() { + let os = KdeColorScheme::breeze_dark().os_theme_colors(); + let c = |r, g, b| Some(KdeColor::rgb8(r, g, b).to_color()); + assert_eq!(os.color_scheme, ColorSchemePreference::Dark); + assert_eq!(os.accent, c(61, 174, 233)); + assert_eq!(os.window_bg, c(32, 35, 38)); + assert_eq!(os.window_fg, c(252, 252, 252)); + assert_eq!(os.button_bg, c(41, 44, 48)); + assert_eq!(os.button_fg, c(252, 252, 252)); + assert_eq!(os.selection_bg, c(61, 174, 233)); + assert_eq!(os.selection_fg, c(252, 252, 252)); + assert_eq!(os.tooltip_bg, c(41, 44, 48)); + assert_eq!(os.tooltip_fg, c(252, 252, 252)); + assert_eq!(os.view_bg, c(20, 22, 24)); + assert_eq!(os.view_fg, c(252, 252, 252)); + assert_eq!(os.alternate_bg, c(29, 31, 34)); + assert_eq!(os.inactive_fg, c(161, 169, 177)); + assert_eq!(os.link, c(29, 153, 243)); + assert_eq!(os.link_visited, c(155, 89, 182)); + assert_eq!(os.error, c(218, 68, 83)); + assert_eq!(os.warning, c(246, 116, 0)); + assert_eq!(os.success, c(39, 174, 96)); + assert_eq!(os.focus, c(61, 174, 233)); +} + +#[test] +fn the_colour_report_reads_the_active_palette() { + // The report and the resolved Active palette agree on every shared + // colour, for a scheme whose sets all differ. + let (scheme, _) = KdeColorScheme::parse( + "[Colors:Window]\nBackgroundNormal=10,20,30\nForegroundNormal=200,201,202\n\ + [Colors:View]\nBackgroundNormal=240,241,242\nDecorationFocus=1,2,3\n\ + [Colors:Selection]\nBackgroundNormal=90,10,200\n", + ); + let os = scheme.os_theme_colors(); + let palette = scheme.resolve(); + let role = |r| Some(palette.role(ColorState::Active, r).to_color()); + assert_eq!(os.color_scheme, ColorSchemePreference::Dark); + assert_eq!(os.window_bg, role(PaletteRole::Window)); + assert_eq!(os.window_fg, role(PaletteRole::WindowText)); + assert_eq!(os.view_bg, role(PaletteRole::Base)); + assert_eq!(os.accent, role(PaletteRole::Accent)); + assert_eq!(os.selection_bg, role(PaletteRole::Highlight)); + assert_eq!(os.focus, Some(KdeColor::rgb8(1, 2, 3).to_color())); +} diff --git a/crates/teksilo-kde-scheme/tests/prop_config.proptest-regressions b/crates/teksilo-kde-scheme/tests/prop_config.proptest-regressions new file mode 100644 index 000000000..a14afbf86 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/prop_config.proptest-regressions @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 8e347f92a35f5399869f64e9dcd1e4e35014ca47f0bd6cd44e61516e2e37d082 # shrinks to count = 10, ints = ["-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648", "-2147483648"], floats = ["10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10", "10"] diff --git a/crates/teksilo-kde-scheme/tests/prop_config.rs b/crates/teksilo-kde-scheme/tests/prop_config.rs new file mode 100644 index 000000000..97174e832 --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/prop_config.rs @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Property tests for the configuration parser and the readers built on it +//! (crates/teksilo-kde-scheme/src/config.rs, color.rs, fonts.rs and +//! scheme.rs). +//! +//! The parser reads files a user, a distribution or a theme package wrote, +//! so it must return rather than unwind on any input at all. `cargo-fuzz` +//! needs nightly and libfuzzer-sys, which is not assumed here; proptest +//! with 1024 cases per property gives the "never panics on weird input" +//! coverage a fuzz corpus would, plus shrinking to a minimal +//! counterexample. Manual override knob: `PROPTEST_CASES=N cargo test -p +//! teksilo-kde-scheme --test prop_config`. +//! +//! Safe run (engineering/docs/property-testing.md): +//! +//! ```text +//! cargo test -p teksilo-kde-scheme --test prop_config --no-run +//! BIN=$(cargo test -p teksilo-kde-scheme --test prop_config --no-run --message-format=json 2>/dev/null \ +//! | jq -r 'select(.executable != null) | .executable' | tail -1) +//! ( ulimit -v 6000000 -t 300; "$BIN" --test-threads=1 ) +//! ``` + +use proptest::prelude::*; +use teksilo_kde_scheme::{KConfig, KdeColor, KdeColorScheme, KdeFont}; + +/// Bytes biased toward the characters the grammar gives a meaning to, so +/// most cases exercise headers, suffixes and escapes rather than plain +/// text. Cost: at most 400 bytes per case, parsed once (linear), so the +/// worst case is a few microseconds and a few kilobytes. +fn arb_config_bytes() -> impl Strategy> { + let byte = prop_oneof![ + 4 => prop::sample::select(b"[]$=\\\n\r#ideftxs0123456789abcdefABCDEF,;: \t".to_vec()), + 1 => any::(), + ]; + prop::collection::vec(byte, 0..400) +} + +/// Lines assembled from grammar fragments and the keys a scheme reads, so +/// headers, nested groups, key suffixes and colour keys appear far more +/// often than random bytes would make them. Cost: at most 40 lines of up to +/// 5 fragments each, well under 4 KB, parsed, cascaded, written and parsed +/// again: a few hundred microseconds per case. +fn arb_config_text() -> impl Strategy { + let fragment = prop::sample::select(vec![ + "[", + "]", + "[$i]", + "[$d]", + "[$e]", + "[$ie]", + "[de]", + "[Colors:View]", + "[Colors:Window]", + "[Colors:Header]", + "[Inactive]", + "[General]", + "[KDE]", + "[ColorEffects:Inactive]", + "Key", + "BackgroundNormal", + "BackgroundAlternate", + "AccentColor", + "Name", + "contrast", + "TitlebarIsAccentColored", + "ColorEffect", + "Color", + "Enable", + "true", + "2", + "=", + "\\", + "\\s", + "\\x4", + "\\xzz", + "\\q", + "$", + "${HOME}", + "$(echo)", + "#", + " ", + "\r", + "\t", + "61,174,233", + "#3daee9", + "\u{FEFF}", + "é", + ]); + let line = prop::collection::vec(fragment, 0..6).prop_map(|parts| parts.concat()); + prop::collection::vec(line, 0..40).prop_map(|lines| lines.join("\n")) +} + +// ── 1. parsing arbitrary bytes never panics, every diagnostic is on a line +// the input has, and the error limit is respected ── +proptest! { + // 1024 cases: cheap per case, and a panic-freedom sweep over input a + // user or package controls is worth the extra depth. + #![proptest_config(ProptestConfig { cases: 1024, ..ProptestConfig::default() })] + #[test] + fn parse_never_panics_on_arbitrary_bytes(bytes in arb_config_bytes()) { + let (_, diagnostics) = KConfig::parse_bytes(&bytes); + let lines = bytes.split(|&b| b == b'\n').count(); + for d in &diagnostics { + prop_assert!(d.line >= 1 && d.line <= lines, "diagnostic {d:?} outside 1..={lines} for {bytes:?}"); + } + prop_assert!(diagnostics.len() <= 101 + lines, "{} diagnostics for {} lines", diagnostics.len(), lines); + } +} + +// ── 2. parsing, cascading and reading a scheme, fonts and colours from +// grammar-shaped text never panics ── +proptest! { + #![proptest_config(ProptestConfig { cases: 1024, ..ProptestConfig::default() })] + #[test] + fn every_reader_survives_grammar_shaped_text(a in arb_config_text(), b in arb_config_text()) { + let (low, _) = KConfig::parse(&a); + let (high, _) = KConfig::parse(&b); + let merged = KConfig::cascade(&[low, high.with_kind(teksilo_kde_scheme::LayerKind::User)]); + let scheme = KdeColorScheme::from_config(&merged); + let text = scheme.to_kconfig_text(); + let (again, diagnostics) = KdeColorScheme::parse(&text); + prop_assert!(diagnostics.is_empty(), "written text has diagnostics {diagnostics:?}:\n{text}"); + prop_assert_eq!(&again, &scheme, "round trip changed the scheme read from {:?} over {:?}", a, b); + for line in a.lines().chain(b.lines()) { + let _ = KdeColor::parse(line); + let _ = KdeFont::parse_qfont(line); + } + } +} + +/// An integer field of a font string: mostly the ends of the `i32` range +/// and their neighbours, where arithmetic on a field overflows, plus the +/// small values Qt writes and an arbitrary `i32`. +fn arb_font_int() -> impl Strategy { + prop_oneof![ + 2 => prop::sample::select(vec![ + i32::MIN, + i32::MIN + 1, + i32::MIN / 2, + -1, + 0, + 1, + 50, + 400, + 1000, + i32::MAX / 2, + i32::MAX - 1, + i32::MAX, + ]) + .prop_map(|v| v.to_string()), + 1 => any::().prop_map(|v| v.to_string()), + 1 => prop::sample::select(vec![ + "2147483648".to_owned(), + "-2147483649".to_owned(), + "99999999999999999999".to_owned(), + String::new(), + "x".to_owned(), + ]), + ] +} + +/// A floating-point field of a font string: the extremes of `f64`, values +/// that do not parse as finite, and ordinary sizes. +fn arb_font_float() -> impl Strategy { + prop_oneof![ + 2 => prop::sample::select(vec![ + "10", "9.5", "-1", "0", "1e308", "-1e308", "1e309", "inf", "NaN", "", + ]) + .prop_map(str::to_owned), + 1 => any::().prop_map(|v| v.to_string()), + ] +} + +// ── 3. a font string of any accepted length with extreme numeric fields +// parses without panicking, and its weight is on the OpenType scale ── +proptest! { + // 1024 cases: a string of at most 18 short fields, parsed once, costs + // a few microseconds; the extra depth goes into the integer extremes. + #![proptest_config(ProptestConfig { cases: 1024, ..ProptestConfig::default() })] + #[test] + fn font_strings_with_extreme_numbers_never_panic( + count in prop::sample::select(vec![2usize, 10, 11, 16, 17, 18]), + ints in prop::collection::vec(arb_font_int(), 18), + floats in prop::collection::vec(arb_font_float(), 18), + ) { + let mut fields = vec!["Noto Sans".to_owned()]; + for index in 1..count { + let float_field = matches!(index, 1 | 12 | 13); + fields.push(if float_field { floats[index].clone() } else { ints[index].clone() }); + } + let text = fields.join(","); + if let Some(font) = KdeFont::parse_qfont(&text) { + prop_assert!((1..=1000).contains(&font.weight), "weight {} from {text:?}", font.weight); + let _ = font.size_px(); + } + } +} diff --git a/crates/teksilo-kde-scheme/tests/prop_math.rs b/crates/teksilo-kde-scheme/tests/prop_math.rs new file mode 100644 index 000000000..3f52511bc --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/prop_math.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Property tests for the colour arithmetic +//! (crates/teksilo-kde-scheme/src/math.rs). +//! +//! Section d.5 of Teksilo's KDE specification requires the HCY conversion +//! to give back every 8-bit colour exactly; the golden vectors pin 62 +//! colours, and this suite covers the rest of the space. Each case is a +//! handful of floating-point operations, so 1024 cases per property cost +//! well under a second. Manual override knob: `PROPTEST_CASES=N cargo test +//! -p teksilo-kde-scheme --test prop_math`. +//! +//! Safe run (engineering/docs/property-testing.md): +//! +//! ```text +//! cargo test -p teksilo-kde-scheme --test prop_math --no-run +//! BIN=$(cargo test -p teksilo-kde-scheme --test prop_math --no-run --message-format=json 2>/dev/null \ +//! | jq -r 'select(.executable != null) | .executable' | tail -1) +//! ( ulimit -v 6000000 -t 300; "$BIN" --test-threads=1 ) +//! ``` + +use proptest::prelude::*; +use teksilo_kde_scheme::KdeColor; +use teksilo_kde_scheme::math::{Hcy, lift_luma_until}; +use teksilo_tokens::contrast::composite_contrast; + +/// Any 8-bit colour, alpha included. Cost: four bytes, no allocation. +fn arb_color8() -> impl Strategy { + any::<[u8; 4]>().prop_map(|[r, g, b, a]| KdeColor::rgba8(r, g, b, a)) +} + +/// Up to four opaque 8-bit backdrops. Cost: at most four colours, each +/// measured once per lift step (256 steps), so about a thousand contrast +/// computations per case. +fn arb_backdrops() -> impl Strategy> { + prop::collection::vec( + any::<[u8; 3]>().prop_map(|[r, g, b]| KdeColor::rgb8(r, g, b)), + 0..4, + ) +} + +fn worst(fg: KdeColor, backdrops: &[KdeColor]) -> f32 { + backdrops + .iter() + .map(|bg| composite_contrast(fg.to_color(), bg.to_color())) + .fold(f32::INFINITY, f32::min) +} + +// ── 1. converting an 8-bit colour to HCY and back gives the same colour, +// in all four 16-bit channels ── +proptest! { + #![proptest_config(ProptestConfig { cases: 1024, ..ProptestConfig::default() })] + + #[test] + fn every_eight_bit_colour_survives_the_hcy_round_trip(color in arb_color8()) { + let hcy = Hcy::from_color(color); + let back = hcy.to_color(); + prop_assert_eq!(back, color, "{:?} went to {:?} and came back as {:?}", color, hcy, back); + } +} + +// ── 2. the WCAG lift never makes the worst contrast over the backdrops +// lower than it was, and keeps the alpha ── +proptest! { + #[test] + fn the_luma_lift_never_lowers_the_worst_contrast( + color in arb_color8(), + backdrops in arb_backdrops(), + floor in 1.0_f32..21.0, + ) { + let lifted = lift_luma_until(color, &backdrops, floor); + let (before, after) = (worst(color, &backdrops), worst(lifted, &backdrops)); + prop_assert!( + after >= before, + "{:?} on {:?} at floor {}: worst contrast {} fell to {} ({:?})", + color, backdrops, floor, before, after, lifted, + ); + prop_assert_eq!(lifted.a, color.a, "alpha changed: {:?} became {:?}", color, lifted); + } +} diff --git a/crates/teksilo-kde-scheme/tests/scheme.rs b/crates/teksilo-kde-scheme/tests/scheme.rs new file mode 100644 index 000000000..b8d8956bf --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/scheme.rs @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The colour-scheme model: compiled defaults, the Header fallback, the +//! `[Inactive]` replacement, the stock Breeze constants and the text round +//! trip. The comparisons against the vendored breeze 6.8.0 files are in +//! `breeze_files.rs`, which is not published with the crate. + +use teksilo_kde_scheme::{ + ColorEffect, ColorRole, ColorSet, ContrastEffect, IntensityEffect, KdeColor, KdeColorScheme, + SetColors, StateEffect, +}; + +fn parse_clean(text: &str) -> KdeColorScheme { + let (scheme, diagnostics) = KdeColorScheme::parse(text); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + scheme +} + +fn rgb(r: u8, g: u8, b: u8) -> KdeColor { + KdeColor::rgb8(r, g, b) +} + +// ---- The stock schemes ------------------------------------------------- + +#[test] +fn the_stock_schemes_carry_the_values_the_specification_lists() { + let light = KdeColorScheme::breeze_light(); + assert_eq!(light.name.as_deref(), Some("Breeze Light")); + assert_eq!( + light.set(ColorSet::Window).background_normal, + rgb(239, 240, 241) + ); + assert_eq!( + light.set(ColorSet::Selection).background_normal, + rgb(61, 174, 233) + ); + assert_eq!( + light.set(ColorSet::View).decoration_hover, + rgb(61, 174, 233) + ); + assert_eq!( + light + .inactive_set(ColorSet::Header) + .unwrap() + .background_normal, + rgb(239, 240, 241) + ); + assert_eq!(light.contrast, 4); + assert_eq!(light.frame_contrast, 0.2); + assert_eq!(light.disabled_effect, StateEffect::DISABLED_DEFAULT); + assert!(!light.inactive_effect.enabled); + assert_eq!(light.inactive_effect.color_effect, ColorEffect::Fade); + assert_eq!(light.inactive_effect.color_amount, 0.025); + assert!(light.change_selection_color); + assert_eq!(light.wm.active_background, Some(rgb(227, 229, 231))); + + let dark = KdeColorScheme::breeze_dark(); + assert_eq!( + dark.set(ColorSet::Window).background_normal, + rgb(32, 35, 38) + ); + assert_eq!(dark.set(ColorSet::View).background_normal, rgb(20, 22, 24)); + assert_eq!(dark.wm.inactive_background, Some(rgb(32, 36, 40))); + + let classic = KdeColorScheme::breeze_classic(); + assert!(!classic.header_group_present); + assert_eq!(classic.set(ColorSet::Header), classic.set(ColorSet::Window)); + assert!(classic.titlebar_is_accent_colored); + assert_eq!( + classic.set(ColorSet::Complementary).decoration_focus, + rgb(30, 146, 255) + ); +} + +#[test] +fn the_stock_schemes_and_the_defaults_round_trip_through_text() { + for scheme in [ + KdeColorScheme::breeze_light(), + KdeColorScheme::breeze_dark(), + KdeColorScheme::breeze_classic(), + KdeColorScheme::kcolorscheme_defaults(), + ] { + let text = scheme.to_kconfig_text(); + assert_eq!(parse_clean(&text), scheme, "round trip of:\n{text}"); + } +} + +#[test] +fn a_stock_scheme_is_written_like_the_file_it_came_from() { + let text = KdeColorScheme::breeze_dark().to_kconfig_text(); + assert!( + text.starts_with("[ColorEffects:Disabled]\nColor=56,56,56\n"), + "{text}" + ); + assert!(text.contains("\n[Colors:Header][Inactive]\nBackgroundAlternate=41,44,48\n")); + assert!(text.contains("\n[KDE]\ncontrast=4\n")); + assert!(!text.contains("frameContrast")); + assert!(text.ends_with("inactiveForeground=161,169,177\n")); +} + +// ---- Defaults ---------------------------------------------------------- + +#[test] +fn an_empty_configuration_gives_the_compiled_defaults() { + let scheme = parse_clean(""); + assert_eq!(scheme, KdeColorScheme::kcolorscheme_defaults()); + assert_eq!(scheme, KdeColorScheme::default()); + // The defaults equal Breeze Light except for the hover decoration. + let light = KdeColorScheme::breeze_light(); + for set in ColorSet::ALL { + let mut expected = *light.set(set); + expected.decoration_hover = rgb(147, 206, 233); + assert_eq!(*scheme.set(set), expected, "{set:?}"); + } + assert_eq!(scheme.contrast, 7); + assert_eq!(scheme.frame_contrast, 0.2); + assert_eq!(scheme.disabled_effect, StateEffect::DISABLED_DEFAULT); + assert_eq!(scheme.inactive_effect, StateEffect::INACTIVE_DEFAULT); + // No [ColorEffects:Inactive] at all: effects off, selection change on. + assert!(scheme.change_selection_color); + assert_eq!(scheme.wm, Default::default()); + assert_eq!(scheme.accent, None); + assert_eq!(scheme.tint_factor, None); + assert!(!scheme.titlebar_is_accent_colored); + assert!(scheme.inactive_sets.iter().all(Option::is_none)); +} + +#[test] +fn absent_and_unreadable_keys_take_the_default_of_their_own_set() { + let scheme = parse_clean( + "[Colors:Button]\nBackgroundNormal=1,2,3\nForegroundNormal=invalid\nForegroundLink=\nForegroundActive=300,0,0\n", + ); + let button = scheme.set(ColorSet::Button); + assert_eq!(button.background_normal, rgb(1, 2, 3)); + assert_eq!(button.background_alternate, rgb(163, 212, 250)); + assert_eq!(button.foreground_normal, rgb(35, 38, 41)); + assert_eq!(button.foreground_link, rgb(41, 128, 185)); + assert_eq!(button.foreground_active, rgb(61, 174, 233)); +} + +#[test] +fn contrast_defaults_to_seven_and_is_not_clamped() { + assert_eq!(parse_clean("[KDE]\ncontrast=abc\n").contrast, 7); + assert_eq!(parse_clean("[KDE]\ncontrast=15\n").contrast, 15); + assert_eq!(parse_clean("[KDE]\ncontrast=-3\n").contrast, -3); +} + +#[test] +fn frame_contrast_defaults_to_one_fifth_and_is_clamped_to_the_unit_interval() { + assert_eq!( + parse_clean("[KDE]\nframeContrast=junk\n").frame_contrast, + 0.2 + ); + assert_eq!( + parse_clean("[KDE]\nframeContrast=0.35\n").frame_contrast, + 0.35 + ); + assert_eq!(parse_clean("[KDE]\nframeContrast=4\n").frame_contrast, 1.0); + assert_eq!(parse_clean("[KDE]\nframeContrast=-1\n").frame_contrast, 0.0); +} + +// ---- Header ----------------------------------------------------------- + +#[test] +fn without_a_header_group_the_header_set_is_the_window_group_key_by_key() { + let scheme = + parse_clean("[Colors:Window]\nBackgroundNormal=10,10,10\nForegroundNormal=20,20,20\n"); + assert!(!scheme.header_group_present); + let header = scheme.set(ColorSet::Header); + assert_eq!(header.background_normal, rgb(10, 10, 10)); + assert_eq!(header.foreground_normal, rgb(20, 20, 20)); + // Window lacks BackgroundAlternate, so the Header table applies, not + // the Window table. + assert_eq!(header.background_alternate, rgb(239, 240, 241)); + assert_eq!( + scheme.set(ColorSet::Window).background_alternate, + rgb(227, 229, 231) + ); +} + +#[test] +fn a_partial_header_group_takes_its_other_keys_from_window() { + let scheme = parse_clean( + "[Colors:Window]\nBackgroundNormal=10,10,10\nDecorationHover=5,5,5\n[Colors:Header]\nForegroundNormal=1,1,1\n", + ); + assert!(scheme.header_group_present); + let header = scheme.set(ColorSet::Header); + assert_eq!(header.foreground_normal, rgb(1, 1, 1)); + assert_eq!(header.background_normal, rgb(10, 10, 10)); + assert_eq!(header.decoration_hover, rgb(5, 5, 5)); + assert_eq!(header.decoration_focus, rgb(61, 174, 233)); + assert_eq!(header.background_alternate, rgb(239, 240, 241)); +} + +#[test] +fn a_header_without_its_own_group_keeps_its_table_fallback_through_text() { + let scheme = parse_clean("[Colors:Window]\nBackgroundNormal=10,10,10\n"); + let text = scheme.to_kconfig_text(); + assert!(!text.contains("[Colors:Header]"), "{text}"); + assert_eq!(parse_clean(&text), scheme, "{text}"); +} + +// ---- [Inactive] -------------------------------------------------------- + +#[test] +fn an_inactive_subgroup_replaces_its_set_and_falls_back_to_the_defaults_not_the_parent() { + let scheme = parse_clean( + "[Colors:View]\nBackgroundNormal=1,1,1\nForegroundNormal=2,2,2\n[Colors:View][Inactive]\nBackgroundNormal=9,9,9\n", + ); + let inactive = scheme.inactive_set(ColorSet::View).unwrap(); + assert_eq!(inactive.background_normal, rgb(9, 9, 9)); + // Not the parent's 2,2,2: the View table. + assert_eq!(inactive.foreground_normal, rgb(35, 38, 41)); + assert_eq!(scheme.set(ColorSet::View).foreground_normal, rgb(2, 2, 2)); + assert_eq!(scheme.inactive_set(ColorSet::Window), None); +} + +#[test] +fn a_header_inactive_subgroup_falls_back_to_window_then_the_header_table() { + let scheme = parse_clean( + "[Colors:Window]\nForegroundNormal=3,3,3\n[Colors:Header][Inactive]\nBackgroundNormal=9,9,9\n", + ); + // A nested group alone makes the Header group exist. + assert!(scheme.header_group_present); + let inactive = scheme.inactive_set(ColorSet::Header).unwrap(); + assert_eq!(inactive.background_normal, rgb(9, 9, 9)); + assert_eq!(inactive.foreground_normal, rgb(3, 3, 3)); + assert_eq!(inactive.background_alternate, rgb(239, 240, 241)); +} + +#[test] +fn an_inactive_subgroup_with_only_deleted_keys_does_not_exist() { + let scheme = parse_clean("[Colors:View][Inactive]\nBackgroundNormal[$d]\n"); + assert_eq!(scheme.inactive_set(ColorSet::View), None); +} + +#[test] +fn schemes_with_inactive_subgroups_round_trip() { + let scheme = parse_clean( + "[Colors:Selection][Inactive]\nBackgroundNormal=1,2,3,128\n[Colors:Window][Inactive]\nForegroundNormal=#102030\n", + ); + let text = scheme.to_kconfig_text(); + assert_eq!(parse_clean(&text), scheme, "{text}"); +} + +// ---- Effects ------------------------------------------------------------ + +#[test] +fn effects_read_every_key_and_unknown_codes_mean_none() { + let scheme = parse_clean( + "[ColorEffects:Disabled]\nEnable=false\nIntensityEffect=3\nIntensityAmount=0.5\nColorEffect=3\n\ + ColorAmount=0.4\nColor=10,20,30\nContrastEffect=9\nContrastAmount=0.3\n", + ); + assert_eq!( + scheme.disabled_effect, + StateEffect { + enabled: false, + intensity_effect: IntensityEffect::Lighten, + intensity_amount: 0.5, + color_effect: ColorEffect::Tint, + color_amount: 0.4, + color: rgb(10, 20, 30), + contrast_effect: ContrastEffect::None, + contrast_amount: 0.3, + } + ); +} + +#[test] +fn the_effect_colour_is_read_only_when_a_colour_effect_is_set() { + let none = parse_clean("[ColorEffects:Disabled]\nColorEffect=0\nColor=1,2,3\n"); + assert_eq!(none.disabled_effect.color, rgb(56, 56, 56)); + let unknown = parse_clean("[ColorEffects:Inactive]\nColorEffect=7\nColor=1,2,3\n"); + assert_eq!(unknown.inactive_effect.color_effect, ColorEffect::None); + assert_eq!(unknown.inactive_effect.color, rgb(112, 111, 110)); +} + +#[test] +fn change_selection_color_defaults_to_the_inactive_enable_flag() { + assert!(!parse_clean("[ColorEffects:Inactive]\nEnable=false\n").change_selection_color); + assert!(parse_clean("[ColorEffects:Inactive]\nEnable=true\n").change_selection_color); + assert!( + parse_clean("[ColorEffects:Inactive]\nEnable=false\nChangeSelectionColor=true\n") + .change_selection_color + ); + // An empty value is present and reads as true. + assert!( + parse_clean("[ColorEffects:Inactive]\nEnable=\n") + .inactive_effect + .enabled + ); +} + +// ---- [General] and [WM] ------------------------------------------------- + +#[test] +fn general_keys_follow_their_fallbacks() { + let scheme = + parse_clean("[General]\nTintFactor=oops\naccentActiveTitlebar=true\nAccentColor=#3daee9\n"); + assert_eq!(scheme.tint_factor, Some(0.15)); + assert!(scheme.titlebar_is_accent_colored); + assert_eq!(scheme.accent, Some(rgb(61, 174, 233))); + + let newer = parse_clean( + "[General]\nTitlebarIsAccentColored=false\naccentActiveTitlebar=true\nTintFactor=0.3\n", + ); + assert!(!newer.titlebar_is_accent_colored); + assert_eq!(newer.tint_factor, Some(0.3)); +} + +#[test] +fn every_written_field_round_trips() { + let mut scheme = KdeColorScheme::breeze_dark(); + scheme.name = Some(" Odd \\ name\twith=signs ".to_owned()); + scheme.accent = Some(KdeColor::rgba8(1, 2, 3, 4)); + scheme.tint_factor = Some(0.125); + scheme.titlebar_is_accent_colored = true; + scheme.frame_contrast = 0.75; + scheme.contrast = 7; + scheme.wm.active_blend = None; + scheme.change_selection_color = false; + let mut set = SetColors::from_rgb8([[7, 7, 7]; 12]); + set.set(ColorRole::DecorationHover, KdeColor::rgba8(9, 9, 9, 0)); + scheme.inactive_sets[ColorSet::Tooltip.index()] = Some(set); + let text = scheme.to_kconfig_text(); + assert_eq!(parse_clean(&text), scheme, "{text}"); +} + +// ---- Source --------------------------------------------------------------- + +#[test] +fn a_parsed_scheme_keeps_a_value_as_written_even_when_it_reads_it_clamped() { + let raw = parse_clean("[KDE]\nframeContrast=4\n"); + assert_eq!(raw.frame_contrast, 1.0); + assert_eq!( + raw.source().unwrap().get(&["KDE"], "frameContrast"), + Some("4") + ); +} diff --git a/crates/teksilo-kde-scheme/tests/values.rs b/crates/teksilo-kde-scheme/tests/values.rs new file mode 100644 index 000000000..97409dace --- /dev/null +++ b/crates/teksilo-kde-scheme/tests/values.rs @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Colour values and Qt font descriptions. + +use teksilo_kde_scheme::{FontStyle, KdeColor, KdeFont}; +use teksilo_tokens::Color; + +// ---- Colours ------------------------------------------------------------- + +#[test] +fn comma_forms_give_eight_bit_channels_with_optional_alpha_signs_and_spaces() { + assert_eq!( + KdeColor::parse("61,174,233"), + Some(KdeColor::rgb8(61, 174, 233)) + ); + assert_eq!( + KdeColor::parse(" 61 , 174 ,233 "), + Some(KdeColor::rgb8(61, 174, 233)) + ); + assert_eq!( + KdeColor::parse("+1,-0,2,128"), + Some(KdeColor::rgba8(1, 0, 2, 128)) + ); + assert_eq!(KdeColor::parse("1,2,3").unwrap().a, u16::MAX); +} + +#[test] +fn malformed_comma_forms_are_absent() { + for text in [ + "1,2", + "1,2,3,4,5", + "256,0,0", + "-1,0,0", + "1.5,2,3", + "a,b,c", + "1,,3", + "1,2,3,", + ] { + assert_eq!(KdeColor::parse(text), None, "{text}"); + } +} + +#[test] +fn hex_forms_follow_the_documented_widths() { + assert_eq!( + KdeColor::parse("#3daee9"), + Some(KdeColor::rgb8(61, 174, 233)) + ); + assert_eq!( + KdeColor::parse("#3DAEE9"), + Some(KdeColor::rgb8(61, 174, 233)) + ); + assert_eq!(KdeColor::parse("#fa0"), Some(KdeColor::rgb8(255, 170, 0))); + assert_eq!( + KdeColor::parse("#803daee9"), + Some(KdeColor::rgba8(61, 174, 233, 128)) + ); + // 12-bit channels: v becomes 16 v + floor(v / 256). + assert_eq!( + KdeColor::parse("#fff800000"), + Some(KdeColor::rgba16(65535, 32776, 0, u16::MAX)) + ); + assert_eq!( + KdeColor::parse("#123456789abc"), + Some(KdeColor::rgba16(0x1234, 0x5678, 0x9abc, u16::MAX)) + ); + for text in ["#", "#12", "#12345", "#1234567", "#12345g", "# 3daee9"] { + assert_eq!(KdeColor::parse(text), None, "{text}"); + } +} + +#[test] +fn svg_names_and_transparent_are_recognised_ignoring_case() { + assert_eq!(KdeColor::parse("red"), Some(KdeColor::rgb8(255, 0, 0))); + assert_eq!( + KdeColor::parse("CornflowerBlue"), + Some(KdeColor::rgb8(100, 149, 237)) + ); + assert_eq!(KdeColor::parse("grey"), KdeColor::parse("gray")); + assert_eq!(KdeColor::parse("Transparent"), Some(KdeColor::TRANSPARENT)); + assert!(KdeColor::TRANSPARENT.is_transparent_black()); + // CSS-only, not SVG 1.1. + assert_eq!(KdeColor::parse("rebeccapurple"), None); + assert_eq!(KdeColor::parse("notacolour"), None); +} + +#[test] +fn empty_and_invalid_values_are_absent() { + assert_eq!(KdeColor::parse(""), None); + assert_eq!(KdeColor::parse("invalid"), None); +} + +#[test] +fn colours_are_written_as_kde_writes_them() { + assert_eq!(KdeColor::rgb8(61, 174, 233).to_kconfig_text(), "61,174,233"); + assert_eq!( + KdeColor::rgba8(61, 174, 233, 178).to_string(), + "61,174,233,178" + ); + assert_eq!( + KdeColor::rgba16(7067, 0, 65535, 65535).to_kconfig_text(), + "27,0,255" + ); +} + +#[test] +fn colours_cross_the_boundary_to_teksilo_colors_channel_by_channel() { + let c = KdeColor::rgba8(255, 0, 51, 255).to_color(); + assert_eq!(c, Color::new(1.0, 0.0, 0.2, 1.0)); + let back = KdeColor::from_color(c); + assert_eq!(back, KdeColor::rgba8(255, 0, 51, 255)); + assert_eq!( + KdeColor::from_color(Color::new(f32::NAN, 2.0, -1.0, 0.5)).to_rgba8(), + [0, 255, 0, 128] + ); + let converted: Color = KdeColor::WHITE.into(); + assert_eq!(converted, Color::new(1.0, 1.0, 1.0, 1.0)); +} + +// ---- Fonts -------------------------------------------------------------- + +#[test] +fn a_plasma_six_font_string_reads_the_opentype_weight() { + let font = KdeFont::parse_qfont("Noto Sans,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1").unwrap(); + assert_eq!(font.family, "Noto Sans"); + assert_eq!(font.point_size, Some(10.0)); + assert_eq!(font.pixel_size, None); + assert_eq!(font.style_hint, 5); + assert_eq!(font.weight, 400); + assert_eq!(font.style, FontStyle::Normal); + assert_eq!(font.style_strategy, 1); + assert_eq!(font.style_name, None); + assert_eq!(font.size_px(), Some(10.0 * 96.0 / 72.0)); +} + +#[test] +fn a_qt_six_string_with_a_style_name_and_spacing() { + let font = + KdeFont::parse_qfont("Hack,9.5,-1,7,700,1,1,1,1,0,2,1,1.5,2.5,100,1,Bold Italic").unwrap(); + assert_eq!(font.weight, 700); + assert_eq!(font.style, FontStyle::Italic); + assert!(font.underline && font.strike_out && font.fixed_pitch); + assert_eq!(font.capitalization, 2); + assert_eq!(font.letter_spacing_type, 1); + assert_eq!(font.letter_spacing, 1.5); + assert_eq!(font.word_spacing, 2.5); + assert_eq!(font.stretch, 100); + assert_eq!(font.style_name.as_deref(), Some("Bold Italic")); +} + +#[test] +fn a_newer_qt_string_is_cut_to_its_first_seventeen_fields() { + let font = KdeFont::parse_qfont( + "Noto Sans,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,Regular,feature=1,axis=2", + ) + .unwrap(); + assert_eq!(font.style_name.as_deref(), Some("Regular")); +} + +#[test] +fn a_plasma_five_font_string_maps_the_legacy_weight() { + let font = KdeFont::parse_qfont("Noto Sans,10,-1,5,50,0,0,0,0,0").unwrap(); + assert_eq!(font.weight, 400); + let bold = KdeFont::parse_qfont("Noto Sans,10,-1,5,75,2,0,0,0,0,Bold Oblique").unwrap(); + assert_eq!(bold.weight, 700); + assert_eq!(bold.style, FontStyle::Oblique); + assert_eq!(bold.style_name.as_deref(), Some("Bold Oblique")); + assert_eq!( + KdeFont::parse_qfont("X,10,-1,5,60,0,0,0,0,0") + .unwrap() + .weight, + 500 + ); + assert_eq!( + KdeFont::parse_qfont("X,10,-1,5,70,0,0,0,0,0") + .unwrap() + .weight, + 700 + ); +} + +#[test] +fn extreme_weights_in_a_font_string_are_read_without_overflow() { + let weight = |text: &str| KdeFont::parse_qfont(text).unwrap().weight; + assert_eq!(weight("A,10,-1,5,-2147483648,0,0,0,0,0"), 100); + assert_eq!(weight("A,10,-1,5,2147483647,0,0,0,0,0"), 900); + assert_eq!(weight("A,10,-1,5,-2147483648,0,0,0,0,0,0,0,0,0,0,1"), 1); + assert_eq!(weight("A,10,-1,5,2147483647,0,0,0,0,0,0,0,0,0,0,1"), 1000); +} + +#[test] +fn a_two_field_string_and_a_pixel_size() { + let short = KdeFont::parse_qfont("Hack,11").unwrap(); + assert_eq!(short.point_size, Some(11.0)); + assert_eq!(short.weight, 400); + let pixels = KdeFont::parse_qfont("Noto Sans,-1,14,5,400,0,0,0,0,0,0,0,0,0,0,1").unwrap(); + assert_eq!(pixels.point_size, None); + assert_eq!(pixels.pixel_size, Some(14)); + assert_eq!(pixels.size_px(), Some(14.0)); +} + +#[test] +fn other_field_counts_and_unparseable_fields_are_rejected() { + for text in [ + "", + "Noto Sans", + "Noto Sans,10,-1", + "Noto Sans,10,-1,5,400,0,0,0,0,0,0,0", + "Noto Sans,ten", + "Noto Sans,10,-1,5,heavy,0,0,0,0,0", + ] { + assert_eq!(KdeFont::parse_qfont(text), None, "{text}"); + } +} diff --git a/crates/teksilo-platform/Cargo.toml b/crates/teksilo-platform/Cargo.toml index 91c6fd022..d29678063 100644 --- a/crates/teksilo-platform/Cargo.toml +++ b/crates/teksilo-platform/Cargo.toml @@ -17,6 +17,12 @@ categories = ["gui", "os", "accessibility"] [dependencies] teksilo-tokens = { workspace = true } +# The KDE configuration cascade, colour schemes and desktop settings the KDE +# reader in `os_theme/kde.rs` hands its file contents to. Pure (no I/O, no +# environment), so it is a dependency on every target; nothing of it is +# re-exported at the crate root, though `os_theme::NativeDesktop` carries its +# `KdeDesktop`. +teksilo-kde-scheme = { workspace = true } teksilo-core = { workspace = true } teksilo-canvas = { workspace = true } teksilo-render = { workspace = true } @@ -210,9 +216,22 @@ libc = "0.2" # the CI job of the same name) fails the build if it ever forks. x11rb = "0.14" # Reporting each key to the AT-SPI registry (`key_report/atspi.rs`), which is -# how Orca's legacy keyboard device hears keys. The same zbus, with the same -# features, that `accesskit_unix` already links, so it adds no crate. +# how Orca's legacy keyboard device hears keys, and reading the XDG desktop +# portal's settings and the AT-SPI screen-reader flag (`linux_portal.rs`). +# The same zbus, with the same features, that `accesskit_unix` already links, +# so it adds no crate. `async-io` is the one feature needed: it is zbus's +# executor and reactor (zbus runs nothing without one, and tokio would add a +# runtime). `blocking-api` is not enabled: the reads drive the async API with +# `async_io::block_on`, so the blocking proxies would be dead code. zbus = { version = "5.19", default-features = false, features = ["async-io"] } +# The timer that bounds connecting to the session bus (`linux_portal.rs`), +# which zbus's method timeout does not cover, and `block_on` for its reads. +# The async-io zbus already links, so it adds no crate. +async-io = "2.6" + +[dev-dependencies] +# A scratch configuration directory for the theme watch's file-time check. +tempfile = { workspace = true } [target.'cfg(all(unix, not(target_os = "macos")))'.dev-dependencies] # The X keysym constants, to check `key_report`'s hand-written keysym table diff --git a/crates/teksilo-platform/src/accessibility_prefs.rs b/crates/teksilo-platform/src/accessibility_prefs.rs index 33b40858c..7ef7a4218 100644 --- a/crates/teksilo-platform/src/accessibility_prefs.rs +++ b/crates/teksilo-platform/src/accessibility_prefs.rs @@ -12,7 +12,7 @@ //! | Preference | Linux (XDG portal / gsettings) | macOS (NSWorkspace) | Windows (SystemParametersInfo / UISettings) | //! |------------------|-------------------------------|-------------------------|---------------------------------------------| //! | High contrast | portal `contrast` key + GTK theme check | `accessibilityDisplayShouldIncreaseContrast` | `SPI_GETHIGHCONTRAST` | -//! | Reduced motion | portal `reduced-motion` key + `enable-animations` | `accessibilityDisplayShouldReduceMotion` | `UISettings.AnimationsEnabled` | +//! | Reduced motion | portal `reduced-motion` key + KDE `AnimationDurationFactor` + `enable-animations` | `accessibilityDisplayShouldReduceMotion` | `UISettings.AnimationsEnabled` | //! | Text scale | gsettings `text-scaling-factor` | N/A (uses DPI scaling) | `UISettings.TextScaleFactor` | //! | Screen reader | AT-SPI `org.a11y.Status.ScreenReaderEnabled` | `NSWorkspace.isVoiceOverEnabled` | `SPI_GETSCREENREADER` | //! @@ -73,15 +73,18 @@ impl AccessibilityPreferences { } } -// ── Linux: XDG Desktop Portal via busctl + gsettings subprocess ───────────── +// ── Linux: XDG Desktop Portal and AT-SPI over zbus, gsettings subprocess ─── // -// Uses subprocess calls (`busctl`, `gsettings`) which are present on all major -// Linux desktops. This runs once at startup so subprocess overhead is negligible, -// and it avoids adding zbus as a direct dependency. +// The portal settings and the AT-SPI status flag are read over the session +// bus (`linux_portal`), each read bounded by a timeout. The GNOME fallbacks +// still run the `gsettings` tool. #[cfg(target_os = "linux")] mod platform { use super::{AccessibilityPreferences, ScreenReaderState}; - use crate::linux_helpers::{read_dbus_bool_property, read_gsettings, read_portal_u32}; + use crate::linux_helpers::{Desktop, detect_desktop, read_gsettings}; + use crate::linux_portal::{read_bool_property, read_portal_u32}; + use crate::os_theme::kde; + use teksilo_kde_scheme::KdeConfigLocations; pub(super) fn query() -> AccessibilityPreferences { let mut prefs = AccessibilityPreferences::default(); @@ -91,9 +94,11 @@ mod platform { if let Some(v) = read_portal_u32("org.freedesktop.appearance", "contrast") { prefs.high_contrast = v == 1; } - if let Some(v) = read_portal_u32("org.freedesktop.appearance", "reduced-motion") { - prefs.reduced_motion = v == 1; - } + prefs.reduced_motion = reduced_motion( + read_portal_u32("org.freedesktop.appearance", "reduced-motion"), + kde_reduced_motion, + || read_gsettings("org.gnome.desktop.interface", "enable-animations"), + ); // High contrast fallback: check GTK theme name for "HighContrast" if !prefs.high_contrast @@ -109,13 +114,6 @@ mod platform { prefs.high_contrast = val == "true"; } - // Reduced motion fallback: GNOME enable-animations (false → reduced motion) - if !prefs.reduced_motion - && let Some(val) = read_gsettings("org.gnome.desktop.interface", "enable-animations") - { - prefs.reduced_motion = val == "false"; - } - // Text scaling (not in the portal, must use gsettings) if let Some(val) = read_gsettings("org.gnome.desktop.interface", "text-scaling-factor") && let Ok(scale) = val.parse::() @@ -128,6 +126,36 @@ mod platform { prefs } + /// Whether reduced motion is asked for. Any one source asking is + /// enough, read in this order and each read only while no earlier one + /// has asked: + /// + /// 1. the desktop portal's `reduced-motion`, when it is 1; + /// 2. on a KDE session, `kdeglobals [KDE] AnimationDurationFactor` of + /// exactly 0, the value KDE's portal reports as reduced motion + /// (`kde`, `None` when the session is not KDE or its `kdeglobals` + /// cannot be read); + /// 3. GNOME's `enable-animations`, when it is `false`. + /// + /// A source that answers "no", or does not answer, leaves the next one + /// to decide. + pub(super) fn reduced_motion( + portal: Option, + kde: impl FnOnce() -> Option, + enable_animations: impl FnOnce() -> Option, + ) -> bool { + portal == Some(1) || kde() == Some(true) || enable_animations().as_deref() == Some("false") + } + + /// The KDE desktop's reduced-motion answer, on a KDE session only. + fn kde_reduced_motion() -> Option { + if !matches!(detect_desktop(), Desktop::Kde) { + return None; + } + let locations = KdeConfigLocations::from_env(kde::process_env); + kde::kde_prefers_reduced_motion(&locations, &kde::process_env, kde::read_file) + } + /// The AT-SPI status object's `ScreenReaderEnabled` flag, read off the /// same object `accesskit_unix` watches — `atspi-proxies`' `StatusProxy` /// on `org.a11y.Bus` at `/org/a11y/bus`. (AccessKit itself watches the @@ -135,11 +163,12 @@ mod platform { /// tree; this is the narrower question.) /// /// Advisory: Orca sets it, and an assistive technology that never touches - /// the status object will not. A machine with no a11y bus at all — no - /// `busctl`, a sandbox with no session bus, a desktop that does not run - /// one — answers `Unknown`, which behaves as "no". + /// the status object will not. A machine with no a11y bus at all (a + /// sandbox with no session bus, a desktop that does not run one), or a + /// bus that does not answer within `linux_portal::CALL_TIMEOUT`, answers + /// `Unknown`, which behaves as "no". fn query_screen_reader() -> ScreenReaderState { - match read_dbus_bool_property( + match read_bool_property( "org.a11y.Bus", "/org/a11y/bus", "org.a11y.Status", @@ -344,6 +373,41 @@ mod tests { )); } + #[cfg(target_os = "linux")] + #[test] + fn reduced_motion_is_asked_for_by_any_one_source() { + use platform::reduced_motion; + // No KDE session, KDE asking, KDE not asking; GNOME not answering. + let no_kde = || None; + let yes = || Some(true); + let kde_no = || Some(false); + let no_gnome = || None; + let animations = |value: &'static str| move || Some(value.to_owned()); + + // The portal alone. + assert!(reduced_motion(Some(1), no_kde, no_gnome)); + assert!(!reduced_motion(Some(0), no_kde, no_gnome)); + assert!(!reduced_motion(None, no_kde, no_gnome)); + // A KDE animation factor of 0, whatever the portal said. + assert!(reduced_motion(None, yes, no_gnome)); + assert!(reduced_motion(Some(0), yes, animations("true"))); + assert!(!reduced_motion(None, kde_no, no_gnome)); + // GNOME's switch, when nothing earlier asked. + assert!(reduced_motion(None, kde_no, animations("false"))); + assert!(reduced_motion(Some(0), no_kde, animations("false"))); + assert!(!reduced_motion(None, kde_no, animations("true"))); + + // A source that already asked stops the later reads. + assert!(reduced_motion( + Some(1), + || panic!("KDE is not read once the portal asked"), + || panic!("GNOME is not read once the portal asked"), + )); + assert!(reduced_motion(None, yes, || panic!( + "GNOME is not read once KDE asked" + ))); + } + #[test] fn preferences_compare_on_the_screen_reader_too() { // `refresh_accessibility_preferences` early-returns on `==`, so a diff --git a/crates/teksilo-platform/src/lib.rs b/crates/teksilo-platform/src/lib.rs index 1a3e00fad..aa942bed6 100644 --- a/crates/teksilo-platform/src/lib.rs +++ b/crates/teksilo-platform/src/lib.rs @@ -12,6 +12,8 @@ pub mod file_dialog; pub mod key_report; #[cfg(target_os = "linux")] pub(crate) mod linux_helpers; +#[cfg(target_os = "linux")] +pub(crate) mod linux_portal; #[cfg(feature = "native-menu")] pub mod native_menu; pub mod os_theme; diff --git a/crates/teksilo-platform/src/linux_helpers.rs b/crates/teksilo-platform/src/linux_helpers.rs index 7744ac2da..d49f76520 100644 --- a/crates/teksilo-platform/src/linux_helpers.rs +++ b/crates/teksilo-platform/src/linux_helpers.rs @@ -1,39 +1,12 @@ // SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2026 FernTech -//! Shared helpers for querying Linux desktop settings via subprocess calls. +//! Shared helpers for querying Linux desktop settings: the desktop in use, +//! and GNOME's settings through the `gsettings` tool. //! //! Used by both `accessibility_prefs` and `os_theme` modules. -//! These are `pub(crate)` — internal to teksilo-platform. - -/// Read a u32 value from the XDG Desktop Portal Settings via `busctl`. -/// -/// The portal method `org.freedesktop.portal.Settings.ReadOne` returns -/// a `Variant>`. `busctl` prints this as e.g. `v u 1`. -pub(crate) fn read_portal_u32(namespace: &str, key: &str) -> Option { - let output = std::process::Command::new("busctl") - .args([ - "--user", - "call", - "org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", - "org.freedesktop.portal.Settings", - "ReadOne", - "ss", - namespace, - key, - ]) - .output() - .ok()?; - - if !output.status.success() { - return None; - } - - // Output format: "v u \n" — extract the last whitespace-separated token - let stdout = String::from_utf8_lossy(&output.stdout); - stdout.split_whitespace().last()?.parse::().ok() -} +//! These are `pub(crate)`, internal to teksilo-platform. The D-Bus reads (the +//! XDG desktop portal and AT-SPI) are in `linux_portal`. /// Read a gsettings value via the `gsettings` CLI tool. /// @@ -53,50 +26,6 @@ pub(crate) fn read_gsettings(schema: &str, key: &str) -> Option { Some(value.trim_matches('\'').to_string()) } -/// Read an RGB tuple from the XDG Desktop Portal via `busctl`. -/// -/// The portal `accent-color` key returns `(ddd)` — a struct of three doubles. -/// `busctl` prints this as e.g. `v "(ddd)" 0.2078 0.5176 0.8941`. -pub(crate) fn read_portal_rgb(namespace: &str, key: &str) -> Option<(f64, f64, f64)> { - let output = std::process::Command::new("busctl") - .args([ - "--user", - "call", - "org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", - "org.freedesktop.portal.Settings", - "ReadOne", - "ss", - namespace, - key, - ]) - .output() - .ok()?; - - if !output.status.success() { - return None; - } - - let stdout = String::from_utf8_lossy(&output.stdout); - // Parse the three doubles from the output. - // busctl wraps in variant layers: could be `v "(ddd)" R G B` - // or `v v "(ddd)" R G B`. Parse from the end to be robust. - let parts: Vec<&str> = stdout.split_whitespace().collect(); - if parts.len() >= 3 { - let b = parts[parts.len() - 1].parse::().ok()?; - let g = parts[parts.len() - 2].parse::().ok()?; - let r = parts[parts.len() - 3].parse::().ok()?; - // Sanity check: RGB doubles should be in 0.0..=1.0 - if (0.0..=1.0).contains(&r) && (0.0..=1.0).contains(&g) && (0.0..=1.0).contains(&b) { - Some((r, g, b)) - } else { - None - } - } else { - None - } -} - /// Detect the current desktop environment from `$XDG_CURRENT_DESKTOP`. #[allow(dead_code)] pub(crate) enum Desktop { @@ -107,13 +36,24 @@ pub(crate) enum Desktop { } pub(crate) fn detect_desktop() -> Desktop { - let xdg = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); + detect_desktop_in(|name| std::env::var(name).ok()) +} + +/// The desktop the environment `var` describes. A KDE session is +/// recognised first, by KDE's own rule +/// ([`teksilo_kde_scheme::is_kde_session`]: `KDE` among the +/// colon-separated `XDG_CURRENT_DESKTOP` names, ignoring ASCII case, or +/// `KDE_FULL_SESSION=true`); the other desktops by a case-insensitive +/// substring of `XDG_CURRENT_DESKTOP`. +pub(crate) fn detect_desktop_in(var: impl Fn(&str) -> Option) -> Desktop { + if teksilo_kde_scheme::is_kde_session(&var) { + return Desktop::Kde; + } + let xdg = var("XDG_CURRENT_DESKTOP").unwrap_or_default(); // XDG_CURRENT_DESKTOP can contain colon-separated values, e.g. "ubuntu:GNOME" let upper = xdg.to_uppercase(); if upper.contains("GNOME") { Desktop::Gnome - } else if upper.contains("KDE") { - Desktop::Kde } else if upper.contains("CINNAMON") || upper.contains("X-CINNAMON") { Desktop::Cinnamon } else { @@ -121,82 +61,57 @@ pub(crate) fn detect_desktop() -> Desktop { } } -/// Parse the stdout of `busctl get-property` for a `b` (boolean) property. -/// -/// `busctl` prints a boolean property as the type code and the value on one -/// line: `b true` or `b false`. Anything else — a different type code, an error -/// string, an empty read — is `None` rather than a guess, because every caller -/// treats "cannot tell" and "no" differently. -/// -/// Split out from [`read_dbus_bool_property`] so the format this depends on can -/// be pinned by a test without a session bus. -pub(crate) fn parse_busctl_bool(stdout: &str) -> Option { - let mut tokens = stdout.split_whitespace(); - if tokens.next()? != "b" { - return None; - } - let value = match tokens.next()? { - "true" => true, - "false" => false, - _ => return None, - }; - // A well-formed boolean reply is exactly two tokens. More means the reply - // was something else that happens to start `b true`. - if tokens.next().is_some() { - return None; - } - Some(value) -} - -/// Read a boolean D-Bus property from the session bus via `busctl`. -/// -/// A different verb from [`read_portal_u32`], which *calls* -/// `org.freedesktop.portal.Settings.ReadOne`; this reads a property directly -/// off an interface, which is how the AT-SPI status object exposes its flags. -/// -/// `None` when `busctl` is absent, the service is not running, the sandbox -/// cannot reach the session bus, or the reply is not a boolean. Never panics. -pub(crate) fn read_dbus_bool_property( - service: &str, - path: &str, - interface: &str, - property: &str, -) -> Option { - let output = std::process::Command::new("busctl") - .args(["--user", "get-property", service, path, interface, property]) - .output() - .ok()?; - - if !output.status.success() { - return None; - } - - parse_busctl_bool(&String::from_utf8_lossy(&output.stdout)) -} - #[cfg(test)] mod tests { - use super::parse_busctl_bool; + use super::*; + + fn desktop(vars: &[(&str, &str)]) -> Desktop { + detect_desktop_in(|name| { + vars.iter() + .find(|(k, _)| *k == name) + .map(|(_, v)| (*v).to_owned()) + }) + } #[test] - fn busctl_bool_replies_parse() { - assert_eq!(parse_busctl_bool("b true\n"), Some(true)); - assert_eq!(parse_busctl_bool("b false\n"), Some(false)); - // Leading/trailing whitespace is `busctl`'s, not ours. - assert_eq!(parse_busctl_bool(" b true "), Some(true)); + fn a_kde_session_is_kde_in_the_desktop_list_or_a_full_session() { + for vars in [ + &[("XDG_CURRENT_DESKTOP", "KDE")][..], + &[("XDG_CURRENT_DESKTOP", "kde")], + &[("XDG_CURRENT_DESKTOP", "neon:KDE")], + &[("XDG_CURRENT_DESKTOP", "Kde:Plasma")], + &[("KDE_FULL_SESSION", "true")], + &[ + ("XDG_CURRENT_DESKTOP", "GNOME"), + ("KDE_FULL_SESSION", "true"), + ], + ] { + assert!(matches!(desktop(vars), Desktop::Kde), "{vars:?}"); + } + for vars in [ + &[("XDG_CURRENT_DESKTOP", "X-KDE-Other")][..], + &[("XDG_CURRENT_DESKTOP", "KDEx")], + &[("KDE_FULL_SESSION", "1")], + &[("KDE_FULL_SESSION", "false")], + &[], + ] { + assert!(!matches!(desktop(vars), Desktop::Kde), "{vars:?}"); + } } #[test] - fn non_boolean_replies_are_unknown_not_false() { - // A different type code: the property exists but is not a boolean. - assert_eq!(parse_busctl_bool("u 1\n"), None); - assert_eq!(parse_busctl_bool("s \"true\"\n"), None); - // An empty or truncated read. - assert_eq!(parse_busctl_bool(""), None); - assert_eq!(parse_busctl_bool("b\n"), None); - // A value that is neither of D-Bus's two boolean spellings. - assert_eq!(parse_busctl_bool("b 1\n"), None); - // A longer reply that merely begins like a boolean one. - assert_eq!(parse_busctl_bool("b true extra\n"), None); + fn the_other_desktops_are_read_from_the_desktop_list() { + assert!(matches!( + desktop(&[("XDG_CURRENT_DESKTOP", "ubuntu:GNOME")]), + Desktop::Gnome + )); + assert!(matches!( + desktop(&[("XDG_CURRENT_DESKTOP", "X-Cinnamon")]), + Desktop::Cinnamon + )); + assert!(matches!( + desktop(&[("XDG_CURRENT_DESKTOP", "XFCE")]), + Desktop::Other(name) if name == "XFCE" + )); } } diff --git a/crates/teksilo-platform/src/linux_portal.rs b/crates/teksilo-platform/src/linux_portal.rs new file mode 100644 index 000000000..1b82ae8f0 --- /dev/null +++ b/crates/teksilo-platform/src/linux_portal.rs @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Desktop settings read over the D-Bus session bus with zbus: the XDG +//! desktop portal's `org.freedesktop.portal.Settings` and a boolean property +//! such as the AT-SPI `ScreenReaderEnabled` flag. +//! +//! Every read is blocking, bounded by [`CALL_TIMEOUT`], and answers `None` +//! on any failure: no bus, no portal, an error reply, a reply of the wrong +//! type, or no reply in time. Callers treat `None` as "cannot tell" and fall +//! back to the desktop's own configuration. +//! +//! One session connection per process is made on the first read and reused +//! by every later one. A connection that breaks is dropped and made again on +//! the next read. +//! +//! The decoding is pure ([`decode_u32`], [`decode_rgb`], [`decode_bool`], +//! [`read_reply_value`]) so its rules are tested without a bus. + +use std::future::{Future, poll_fn}; +use std::pin::pin; +use std::sync::{Mutex, PoisonError}; +use std::task::Poll; +use std::time::{Duration, Instant}; + +use zbus::zvariant::{OwnedValue, Value}; + +/// The portal's bus name, object path and settings interface. +const PORTAL: &str = "org.freedesktop.portal.Desktop"; +const PORTAL_PATH: &str = "/org/freedesktop/portal/desktop"; +const SETTINGS_INTERFACE: &str = "org.freedesktop.portal.Settings"; +const PROPERTIES_INTERFACE: &str = "org.freedesktop.DBus.Properties"; +/// The error a service answers for a method its interface does not have: a +/// portal older than version 2 of the settings interface, which has `Read` +/// and not `ReadOne`. +const UNKNOWN_METHOD: &str = "org.freedesktop.DBus.Error.UnknownMethod"; + +/// How long one read waits: for the connection to the bus (socket, the +/// authentication handshake and `Hello`) and then for each reply. +/// +/// A running portal answers a settings read in a few milliseconds. The slow +/// case is the first read of a session, when the bus starts the portal by +/// D-Bus activation; that usually completes within a few hundred +/// milliseconds. A read that misses its reply costs one value, which the +/// caller then reads from the desktop's configuration instead, whereas +/// waiting longer would hold up the first window: the `busctl` process this +/// replaces waited up to sd-bus's 25 s default on a hung portal. 500 ms +/// keeps a cold activation inside the bound on an ordinary machine and caps +/// what a hung bus can cost. +pub(crate) const CALL_TIMEOUT: Duration = Duration::from_millis(500); + +/// How long a destination that let a read time out, or a bus that could not +/// be connected to, is left alone. Startup reads several settings within a +/// few milliseconds of each other; without this, a hung portal would cost +/// [`CALL_TIMEOUT`] once per setting instead of once. A read made after the +/// quiet period (a re-query when the desktop's settings change) tries again. +const QUIET_PERIOD: Duration = Duration::from_secs(5); + +/// The session bus named by the environment, shared by every read in the +/// process. +static SESSION: Bus = Bus::session(); + +/// A `u32` setting of the portal (`color-scheme`, `contrast`, +/// `reduced-motion` in `org.freedesktop.appearance`). +pub(crate) fn read_portal_u32(namespace: &str, key: &str) -> Option { + SESSION.read_portal_u32(namespace, key) +} + +/// A `(ddd)` colour setting of the portal (`accent-color` in +/// `org.freedesktop.appearance`), as red, green and blue in `0.0..=1.0`. +pub(crate) fn read_portal_rgb(namespace: &str, key: &str) -> Option<(f64, f64, f64)> { + SESSION.read_portal_rgb(namespace, key) +} + +/// A boolean property read with `org.freedesktop.DBus.Properties.Get`. +pub(crate) fn read_bool_property( + service: &str, + path: &str, + interface: &str, + property: &str, +) -> Option { + SESSION.read_bool_property(service, path, interface, property) +} + +// ── Decoding ──────────────────────────────────────────────────────────────── + +/// A `u32` value, and nothing else: a portal that answered with another +/// integer type is not guessed at. +pub(crate) fn decode_u32(value: &Value<'_>) -> Option { + match value { + Value::U32(v) => Some(*v), + _ => None, + } +} + +/// A `(ddd)` structure whose three components are in `0.0..=1.0`. The portal +/// specification says an accent colour out of that range is to be treated as +/// unset, so one component outside it (or NaN) rejects the whole colour. +pub(crate) fn decode_rgb(value: &Value<'_>) -> Option<(f64, f64, f64)> { + let Value::Structure(structure) = value else { + return None; + }; + let [Value::F64(r), Value::F64(g), Value::F64(b)] = structure.fields() else { + return None; + }; + let in_range = |c: f64| (0.0..=1.0).contains(&c); + (in_range(*r) && in_range(*g) && in_range(*b)).then_some((*r, *g, *b)) +} + +/// A `b` value. +pub(crate) fn decode_bool(value: &Value<'_>) -> Option { + match value { + Value::Bool(v) => Some(*v), + _ => None, + } +} + +/// The setting inside a reply of the deprecated `Read`, which wraps the +/// value in one more variant than `ReadOne` does. A reply without that layer +/// is not a `Read` reply and gives `None`. +pub(crate) fn read_reply_value<'v>(value: &'v Value<'v>) -> Option<&'v Value<'v>> { + match value { + Value::Value(inner) => Some(inner), + _ => None, + } +} + +// ── Quiet periods ─────────────────────────────────────────────────────────── + +/// Destinations (and the bus itself, under the empty name) that are not to +/// be called before an instant. +#[derive(Debug, Default)] +struct QuietList { + entries: Vec<(String, Instant)>, +} + +impl QuietList { + const fn new() -> Self { + Self { + entries: Vec::new(), + } + } + + fn is_quiet(&self, name: &str, now: Instant) -> bool { + self.entries + .iter() + .any(|(entry, until)| entry == name && now < *until) + } + + fn silence(&mut self, name: &str, until: Instant) { + match self.entries.iter_mut().find(|(entry, _)| entry == name) { + Some((_, entry_until)) => *entry_until = until, + None => self.entries.push((name.to_owned(), until)), + } + } +} + +/// The key under which a failed connection is recorded. +const BUS_ITSELF: &str = ""; + +// ── The bus ───────────────────────────────────────────────────────────────── + +/// A session bus and its lazily made connection. +pub(crate) struct Bus { + /// `None` for the session bus the environment names; an address for a + /// bus named explicitly (the tests' private bus). + address: Option, + state: Mutex, +} + +struct BusState { + connection: Option, + quiet: QuietList, +} + +/// Why a call gave no value. +#[derive(Debug, PartialEq, Eq)] +enum CallError { + /// The service has the interface but not the method. + UnknownMethod, + /// Anything else: no bus, no service, another error, a timeout. + Failed, +} + +impl Bus { + const fn session() -> Self { + Self::with_address(None) + } + + /// A bus at an explicit address, so a test passes its private bus in + /// rather than through the environment. + #[cfg(test)] + pub(crate) fn at(address: String) -> Self { + Self::with_address(Some(address)) + } + + const fn with_address(address: Option) -> Self { + Self { + address, + state: Mutex::new(BusState { + connection: None, + quiet: QuietList::new(), + }), + } + } + + pub(crate) fn read_portal_u32(&self, namespace: &str, key: &str) -> Option { + self.read_setting(namespace, key, decode_u32) + } + + pub(crate) fn read_portal_rgb(&self, namespace: &str, key: &str) -> Option<(f64, f64, f64)> { + self.read_setting(namespace, key, decode_rgb) + } + + pub(crate) fn read_bool_property( + &self, + service: &str, + path: &str, + interface: &str, + property: &str, + ) -> Option { + let value = self + .call( + service, + path, + PROPERTIES_INTERFACE, + "Get", + &(interface, property), + ) + .ok()?; + decode_bool(&value) + } + + /// `ReadOne`, or `Read` when the portal does not have `ReadOne`. + fn read_setting( + &self, + namespace: &str, + key: &str, + decode: impl Fn(&Value<'_>) -> Option, + ) -> Option { + let body = (namespace, key); + match self.call(PORTAL, PORTAL_PATH, SETTINGS_INTERFACE, "ReadOne", &body) { + Ok(value) => decode(&value), + Err(CallError::UnknownMethod) => { + let value = self + .call(PORTAL, PORTAL_PATH, SETTINGS_INTERFACE, "Read", &body) + .ok()?; + decode(read_reply_value(&value)?) + } + Err(CallError::Failed) => None, + } + } + + fn lock(&self) -> std::sync::MutexGuard<'_, BusState> { + self.state.lock().unwrap_or_else(PoisonError::into_inner) + } + + /// The shared connection, made on first use. The lock is held while + /// connecting, so concurrent first reads make one connection; the wait + /// is bounded by [`CALL_TIMEOUT`]. + fn connection(&self) -> Option { + let mut state = self.lock(); + if let Some(connection) = &state.connection { + return Some(connection.clone()); + } + let now = Instant::now(); + if state.quiet.is_quiet(BUS_ITSELF, now) { + return None; + } + let address = self.address.as_deref(); + let connected = async_io::block_on(within(CALL_TIMEOUT, async move { + let builder = match address { + Some(address) => zbus::connection::Builder::address(address), + None => zbus::connection::Builder::session(), + }; + builder + .ok()? + .method_timeout(CALL_TIMEOUT) + .build() + .await + .ok() + })) + .flatten(); + match connected { + Some(connection) => { + state.connection = Some(connection.clone()); + Some(connection) + } + None => { + state + .quiet + .silence(BUS_ITSELF, Instant::now() + QUIET_PERIOD); + None + } + } + } + + /// One method call whose reply body is a single variant. + fn call( + &self, + destination: &str, + path: &str, + interface: &str, + method: &str, + body: &B, + ) -> Result + where + B: zbus::export::serde::Serialize + zbus::zvariant::DynamicType, + { + if self.lock().quiet.is_quiet(destination, Instant::now()) { + return Err(CallError::Failed); + } + let connection = self.connection().ok_or(CallError::Failed)?; + // The connection carries the method timeout, so a reply that does + // not come back in time is a `TimedOut` error rather than a wait. + let reply = async_io::block_on(connection.call_method( + Some(destination), + path, + Some(interface), + method, + body, + )); + match reply { + Ok(message) => message + .body() + .deserialize::() + .map_err(|_| CallError::Failed), + Err(zbus::Error::MethodError(name, _, _)) if name.as_str() == UNKNOWN_METHOD => { + Err(CallError::UnknownMethod) + } + Err(zbus::Error::MethodError(..) | zbus::Error::FDO(_)) => Err(CallError::Failed), + Err(zbus::Error::InputOutput(error)) + if error.kind() == std::io::ErrorKind::TimedOut => + { + self.lock() + .quiet + .silence(destination, Instant::now() + QUIET_PERIOD); + Err(CallError::Failed) + } + Err(zbus::Error::InputOutput(_)) => { + // The connection itself failed: make a new one next time. + self.lock().connection = None; + Err(CallError::Failed) + } + // A name or body zbus refused before sending, or a reply it + // could not read: the connection is still good. + Err(_) => Err(CallError::Failed), + } + } +} + +/// `future`'s output, or `None` when `limit` passes first. +pub(crate) async fn within(limit: Duration, future: impl Future) -> Option { + let mut future = pin!(future); + let mut timer = pin!(async_io::Timer::after(limit)); + poll_fn(|cx| { + if let Poll::Ready(output) = future.as_mut().poll(cx) { + return Poll::Ready(Some(output)); + } + if timer.as_mut().poll(cx).is_ready() { + return Poll::Ready(None); + } + Poll::Pending + }) + .await +} + +#[cfg(test)] +pub(crate) mod tests; diff --git a/crates/teksilo-platform/src/linux_portal/tests.rs b/crates/teksilo-platform/src/linux_portal/tests.rs new file mode 100644 index 000000000..56eeaaeb8 --- /dev/null +++ b/crates/teksilo-platform/src/linux_portal/tests.rs @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +use std::time::{Duration, Instant}; + +use zbus::zvariant::Value; + +use super::{QuietList, decode_bool, decode_rgb, decode_u32, read_reply_value}; + +fn rgb(r: f64, g: f64, b: f64) -> Value<'static> { + Value::from((r, g, b)) +} + +#[test] +fn a_u32_setting_decodes() { + assert_eq!(decode_u32(&Value::U32(0)), Some(0)); + assert_eq!(decode_u32(&Value::U32(1)), Some(1)); + assert_eq!(decode_u32(&Value::U32(2)), Some(2)); +} + +#[test] +fn a_u32_setting_of_another_type_is_unknown() { + assert_eq!(decode_u32(&Value::I32(1)), None); + assert_eq!(decode_u32(&Value::U8(1)), None); + assert_eq!(decode_u32(&Value::Bool(true)), None); + assert_eq!(decode_u32(&Value::from("1")), None); + // A `Read` reply handed to the decoder without its extra layer removed. + assert_eq!(decode_u32(&Value::Value(Box::new(Value::U32(1)))), None); +} + +#[test] +fn a_read_reply_loses_exactly_one_variant() { + let reply = Value::Value(Box::new(Value::U32(1))); + let inner = read_reply_value(&reply).expect("one variant layer"); + assert_eq!(decode_u32(inner), Some(1)); + + let colour = Value::Value(Box::new(rgb(0.25, 0.5, 1.0))); + assert_eq!( + decode_rgb(read_reply_value(&colour).expect("one variant layer")), + Some((0.25, 0.5, 1.0)) + ); + + // Two extra layers leave one, which no decoder accepts. + let doubled = Value::Value(Box::new(Value::Value(Box::new(Value::U32(1))))); + assert_eq!(read_reply_value(&doubled).and_then(decode_u32), None); +} + +#[test] +fn a_read_reply_without_its_extra_variant_is_unknown() { + assert!(read_reply_value(&Value::U32(1)).is_none()); + assert!(read_reply_value(&rgb(0.0, 0.0, 0.0)).is_none()); +} + +#[test] +fn an_accent_colour_in_range_decodes() { + assert_eq!( + decode_rgb(&rgb(0.2078, 0.5176, 0.8941)), + Some((0.2078, 0.5176, 0.8941)) + ); + // Both ends of the range are in it. + assert_eq!(decode_rgb(&rgb(0.0, 0.0, 0.0)), Some((0.0, 0.0, 0.0))); + assert_eq!(decode_rgb(&rgb(1.0, 1.0, 1.0)), Some((1.0, 1.0, 1.0))); +} + +#[test] +fn an_accent_colour_out_of_range_is_unset() { + assert_eq!(decode_rgb(&rgb(-0.01, 0.5, 0.5)), None); + assert_eq!(decode_rgb(&rgb(0.5, 1.01, 0.5)), None); + assert_eq!(decode_rgb(&rgb(0.5, 0.5, 255.0)), None); + assert_eq!(decode_rgb(&rgb(f64::NAN, 0.5, 0.5)), None); + assert_eq!(decode_rgb(&rgb(0.5, f64::INFINITY, 0.5)), None); +} + +#[test] +fn an_accent_colour_of_another_shape_is_unknown() { + // Two or four components. + assert_eq!(decode_rgb(&Value::from((0.5, 0.5))), None); + assert_eq!(decode_rgb(&Value::from((0.5, 0.5, 0.5, 1.0))), None); + // Integers where doubles belong. + assert_eq!(decode_rgb(&Value::from((128_u32, 128_u32, 128_u32))), None); + assert_eq!(decode_rgb(&Value::from((0.5, 0.5, 1_u32))), None); + // Not a structure at all. + assert_eq!(decode_rgb(&Value::F64(0.5)), None); + assert_eq!(decode_rgb(&Value::U32(1)), None); + assert_eq!(decode_rgb(&Value::from(vec![0.5, 0.5, 0.5])), None); +} + +#[test] +fn a_boolean_property_decodes_and_nothing_else_does() { + assert_eq!(decode_bool(&Value::Bool(true)), Some(true)); + assert_eq!(decode_bool(&Value::Bool(false)), Some(false)); + assert_eq!(decode_bool(&Value::U32(1)), None); + assert_eq!(decode_bool(&Value::from("true")), None); + assert_eq!( + decode_bool(&Value::Value(Box::new(Value::Bool(true)))), + None + ); +} + +#[test] +fn a_silenced_name_is_quiet_until_its_instant_and_no_other_is() { + let start = Instant::now(); + let mut quiet = QuietList::new(); + assert!(!quiet.is_quiet("org.freedesktop.portal.Desktop", start)); + + quiet.silence( + "org.freedesktop.portal.Desktop", + start + Duration::from_secs(5), + ); + assert!(quiet.is_quiet("org.freedesktop.portal.Desktop", start)); + assert!(quiet.is_quiet( + "org.freedesktop.portal.Desktop", + start + Duration::from_millis(4999) + )); + assert!(!quiet.is_quiet( + "org.freedesktop.portal.Desktop", + start + Duration::from_secs(5) + )); + assert!(!quiet.is_quiet("org.a11y.Bus", start)); + + // Silencing again moves the instant rather than adding a second entry. + quiet.silence( + "org.freedesktop.portal.Desktop", + start + Duration::from_secs(1), + ); + assert!(!quiet.is_quiet( + "org.freedesktop.portal.Desktop", + start + Duration::from_secs(2) + )); + assert_eq!(quiet.entries.len(), 1); +} + +/// Reads against a private `dbus-daemon` serving a fake portal and a fake +/// AT-SPI status object. Opt-in, since it needs `dbus-daemon` installed: +/// run with `TEKSILO_DBUS_TESTS=1`. The bus itself is shared with the +/// theme watch's real-bus test. +pub(crate) mod real_bus { + use std::io::{BufRead, BufReader}; + use std::path::{Path, PathBuf}; + use std::process::{Child, Command, Stdio}; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::time::{Duration, Instant}; + + use zbus::zvariant::{OwnedValue, Value}; + + use super::super::{Bus, CALL_TIMEOUT, PORTAL, PORTAL_PATH}; + + const APPEARANCE: &str = "org.freedesktop.appearance"; + /// A namespace whose reads the fake portal never answers in time. + const HANG: &str = "test.hang"; + + pub(crate) fn enabled() -> bool { + if std::env::var("TEKSILO_DBUS_TESTS").as_deref() == Ok("1") { + return true; + } + println!("skipped: set TEKSILO_DBUS_TESTS=1 to run the private dbus-daemon tests"); + false + } + + /// A `dbus-daemon` of our own, on a configuration that activates no + /// service (so nothing on the machine is started on it) and listens in + /// a directory of its own. Killed and cleaned up on drop. + pub(crate) struct PrivateBus { + child: Child, + dir: PathBuf, + config: PathBuf, + pub(crate) address: String, + } + + impl PrivateBus { + pub(crate) fn start() -> Self { + Self::listening(false) + } + + /// A bus on a socket at a fixed path in its directory, so that + /// [`restart`](Self::restart) brings it back at the same address. The + /// address names the path alone, without the daemon's `guid`, which a + /// restarted daemon does not keep (as a session's own bus address + /// usually does). + pub(crate) fn start_restartable() -> Self { + Self::listening(true) + } + + /// Kill the daemon, closing every connection to it. + pub(crate) fn kill(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + } + + /// Kill the daemon and start another on the same configuration. On a + /// bus from [`start_restartable`](Self::start_restartable) it listens + /// at the same address. + pub(crate) fn restart(&mut self) { + self.kill(); + let (child, printed) = Self::spawn(&self.config); + self.child = child; + assert!( + printed.starts_with(&self.address), + "the bus came back elsewhere: {printed}, not {}", + self.address + ); + } + + fn listening(fixed_path: bool) -> Self { + static NEXT: AtomicUsize = AtomicUsize::new(0); + let dir = std::env::temp_dir().join(format!( + "teksilo-dbus-test-{}-{}", + std::process::id(), + NEXT.fetch_add(1, Ordering::Relaxed) + )); + std::fs::create_dir_all(&dir).expect("create the bus directory"); + let config = dir.join("bus.conf"); + let listen = if fixed_path { + format!("unix:path={}", dir.join("bus").display()) + } else { + format!("unix:dir={}", dir.display()) + }; + std::fs::write( + &config, + format!( + r#" + + session + {listen} + EXTERNAL + + + + + + +"# + ), + ) + .expect("write the bus configuration"); + let (child, printed) = Self::spawn(&config); + let address = if fixed_path { listen } else { printed }; + Self { + child, + dir, + config, + address, + } + } + + /// A daemon on `config`, and the address it printed. + fn spawn(config: &Path) -> (Child, String) { + let mut child = Command::new("dbus-daemon") + .arg(format!("--config-file={}", config.display())) + .args(["--nofork", "--print-address"]) + .env_clear() + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .expect("start dbus-daemon (is it installed?)"); + let mut address = String::new(); + BufReader::new(child.stdout.take().expect("piped stdout")) + .read_line(&mut address) + .expect("read the bus address"); + let address = address.trim().to_owned(); + assert!(!address.is_empty(), "dbus-daemon printed no address"); + (child, address) + } + } + + impl Drop for PrivateBus { + fn drop(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + let _ = std::fs::remove_dir_all(&self.dir); + } + } + + fn setting(namespace: &str, key: &str) -> Option> { + match (namespace, key) { + (APPEARANCE, "color-scheme") => Some(Value::U32(1)), + (APPEARANCE, "contrast") => Some(Value::U32(0)), + (APPEARANCE, "reduced-motion") => Some(Value::U32(1)), + (APPEARANCE, "accent-color") => Some(Value::from((0.25, 0.5, 0.75))), + ("test.wrong", "type") => Some(Value::from("dark")), + ("test.out-of-range", "accent-color") => Some(Value::from((0.25, 1.5, 0.75))), + _ => None, + } + } + + fn owned(value: Value<'static>) -> OwnedValue { + OwnedValue::try_from(value).expect("no file descriptors") + } + + fn not_found(namespace: &str, key: &str) -> zbus::fdo::Error { + zbus::fdo::Error::Failed(format!("{namespace} {key} not found")) + } + + /// A version 2 portal: `ReadOne` and `Read`. + struct CurrentPortal; + + #[zbus::interface(name = "org.freedesktop.portal.Settings")] + impl CurrentPortal { + async fn read_one(&self, namespace: &str, key: &str) -> zbus::fdo::Result { + if namespace == HANG { + async_io::Timer::after(CALL_TIMEOUT * 6).await; + } + setting(namespace, key) + .map(owned) + .ok_or_else(|| not_found(namespace, key)) + } + + /// Present, so a test can tell a reader that wrongly fell back from + /// one that read `ReadOne`: this answers a different value. + async fn read(&self, _namespace: &str, _key: &str) -> zbus::fdo::Result { + Ok(owned(Value::Value(Box::new(Value::U32(7))))) + } + } + + /// A portal older than version 2: only `Read`, whose reply wraps the + /// value in one more variant. + struct LegacyPortal; + + #[zbus::interface(name = "org.freedesktop.portal.Settings")] + impl LegacyPortal { + async fn read(&self, namespace: &str, key: &str) -> zbus::fdo::Result { + setting(namespace, key) + .map(|value| owned(Value::Value(Box::new(value)))) + .ok_or_else(|| not_found(namespace, key)) + } + } + + struct FakeStatus; + + #[zbus::interface(name = "org.a11y.Status")] + impl FakeStatus { + #[zbus(property)] + fn screen_reader_enabled(&self) -> bool { + true + } + + #[zbus(property)] + fn is_enabled(&self) -> u32 { + 1 + } + } + + fn serve(bus: &PrivateBus, portal: I) -> zbus::Connection { + async_io::block_on(async { + zbus::connection::Builder::address(bus.address.as_str())? + .name(PORTAL)? + .serve_at(PORTAL_PATH, portal)? + .name("org.a11y.Bus")? + .serve_at("/org/a11y/bus", FakeStatus)? + .build() + .await + }) + .expect("serve the fake portal") + } + + fn screen_reader(reader: &Bus) -> Option { + reader.read_bool_property( + "org.a11y.Bus", + "/org/a11y/bus", + "org.a11y.Status", + "ScreenReaderEnabled", + ) + } + + #[test] + fn reads_a_current_portal_with_read_one() { + if !enabled() { + return; + } + let bus = PrivateBus::start(); + let _server = serve(&bus, CurrentPortal); + let reader = Bus::at(bus.address.clone()); + + assert_eq!(reader.read_portal_u32(APPEARANCE, "color-scheme"), Some(1)); + assert_eq!(reader.read_portal_u32(APPEARANCE, "contrast"), Some(0)); + assert_eq!( + reader.read_portal_u32(APPEARANCE, "reduced-motion"), + Some(1) + ); + assert_eq!( + reader.read_portal_rgb(APPEARANCE, "accent-color"), + Some((0.25, 0.5, 0.75)) + ); + // An error reply and a reply of the wrong type or range are unknown, + // and none of them falls back to `Read`. + assert_eq!(reader.read_portal_u32(APPEARANCE, "no-such-key"), None); + assert_eq!(reader.read_portal_u32("test.wrong", "type"), None); + assert_eq!(reader.read_portal_rgb(APPEARANCE, "color-scheme"), None); + assert_eq!( + reader.read_portal_rgb("test.out-of-range", "accent-color"), + None + ); + + assert_eq!(screen_reader(&reader), Some(true)); + // A property of another type. + assert_eq!( + reader.read_bool_property( + "org.a11y.Bus", + "/org/a11y/bus", + "org.a11y.Status", + "IsEnabled" + ), + None + ); + // A service nobody owns on this bus. + assert_eq!( + reader.read_bool_property("org.example.Absent", "/", "org.example.Absent", "Flag"), + None + ); + + // A portal that does not answer costs one timeout, and the reads made + // during the quiet period after it do not wait at all. + let started = Instant::now(); + assert_eq!(reader.read_portal_u32(HANG, "anything"), None); + let waited = started.elapsed(); + assert!( + waited >= CALL_TIMEOUT && waited < CALL_TIMEOUT * 4, + "the hung read took {waited:?}" + ); + let started = Instant::now(); + assert_eq!(reader.read_portal_u32(APPEARANCE, "color-scheme"), None); + assert!(started.elapsed() < CALL_TIMEOUT / 2); + // Another destination on the same bus is still read. + assert_eq!(screen_reader(&reader), Some(true)); + } + + #[test] + fn falls_back_to_read_on_a_legacy_portal() { + if !enabled() { + return; + } + let bus = PrivateBus::start(); + let _server = serve(&bus, LegacyPortal); + let reader = Bus::at(bus.address.clone()); + + assert_eq!(reader.read_portal_u32(APPEARANCE, "color-scheme"), Some(1)); + assert_eq!(reader.read_portal_u32(APPEARANCE, "contrast"), Some(0)); + assert_eq!( + reader.read_portal_rgb(APPEARANCE, "accent-color"), + Some((0.25, 0.5, 0.75)) + ); + assert_eq!(reader.read_portal_u32(APPEARANCE, "no-such-key"), None); + assert_eq!(reader.read_portal_u32("test.wrong", "type"), None); + } + + #[test] + fn a_bus_that_is_not_there_reads_nothing_promptly() { + if !enabled() { + return; + } + let dir = std::env::temp_dir().join(format!("teksilo-dbus-absent-{}", std::process::id())); + let reader = Bus::at(format!("unix:path={}", dir.join("no-bus").display())); + let started = Instant::now(); + assert_eq!(reader.read_portal_u32(APPEARANCE, "color-scheme"), None); + assert_eq!(screen_reader(&reader), None); + assert!(started.elapsed() < Duration::from_secs(1)); + } +} diff --git a/crates/teksilo-platform/src/os_theme.rs b/crates/teksilo-platform/src/os_theme.rs index 9d2c7ba29..660ad71dd 100644 --- a/crates/teksilo-platform/src/os_theme.rs +++ b/crates/teksilo-platform/src/os_theme.rs @@ -6,30 +6,89 @@ //! //! Supports GNOME, KDE, and Cinnamon on Linux. macOS and Windows return //! only the light/dark preference (via winit), with no color reading. +//! +//! On a KDE Plasma session ([`teksilo_kde_scheme::is_kde_session`]) the +//! colours are KDE's own: the `kdeglobals` cascade, with its system layers +//! and `kdedefaults`, and the colour scheme in effect with the user's +//! accent, read through `teksilo-kde-scheme`. [`query_native_desktop`] +//! also hands over the KDE desktop's settings (fonts, animation speed, +//! widget style, Breeze style settings) as a [`KdeDesktop`]. +//! +//! An [`OsThemeWatch`] hears these change while the application runs; see +//! [`watch`]. + +#[cfg(target_os = "linux")] +pub(crate) mod kde; +pub mod watch; + +pub use watch::{OsThemeChanged, OsThemeWatch}; +use teksilo_kde_scheme::KdeDesktop; #[cfg(target_os = "linux")] use teksilo_tokens::Color; use teksilo_tokens::{ColorSchemePreference, OsThemeColors}; +/// What the desktop reports for `ThemeMode::Native`: its colours, and on a +/// KDE Plasma session its desktop settings. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct NativeDesktop { + /// The desktop's colour report. + pub os: OsThemeColors, + /// The KDE desktop's colour scheme and settings, on a KDE session whose + /// `kdeglobals` could be read; `None` elsewhere. + /// + /// When present, `os` is its scheme's colour report + /// ([`KdeColorScheme::os_theme_colors`](teksilo_kde_scheme::KdeColorScheme::os_theme_colors)). + pub kde: Option, +} + +impl NativeDesktop { + /// A desktop reporting `os` and no KDE settings. + pub fn new(os: OsThemeColors) -> Self { + NativeDesktop { os, kde: None } + } +} + /// Query only the OS light/dark preference (lightweight). /// Used by `ThemeMode::FollowSystem`. +/// +/// On Linux the desktop portal is asked first. When it does not answer, a +/// KDE session answers from its colour scheme (dark when the window +/// background is dark, the rule KDE reports to applications), and GNOME and +/// Cinnamon from their GTK theme's name. pub fn query_color_scheme() -> ColorSchemePreference { platform::query_color_scheme() } +/// Query the desktop's colours and, on a KDE Plasma session, its settings. +/// Used by `ThemeMode::Native`. +/// +/// On a KDE session the colours are the KDE colour scheme's +/// ([`KdeColorScheme::os_theme_colors`](teksilo_kde_scheme::KdeColorScheme::os_theme_colors)), +/// accent and light or dark preference included; only when no +/// `kdeglobals` file can be read does the report fall back to the desktop +/// portal's preference and accent colour, with no [`KdeDesktop`]. Elsewhere +/// the report is the one [`query_os_theme_colors`] has always given. +pub fn query_native_desktop() -> NativeDesktop { + platform::query_native_desktop() +} + /// Query full OS theme colors from desktop config files. /// Used by `ThemeMode::Native`. +/// +/// The colour report of [`query_native_desktop`]. pub fn query_os_theme_colors() -> OsThemeColors { - platform::query_os_theme_colors() + query_native_desktop().os } // ── Linux ─────────────────────────────────────────────────────────────────── #[cfg(target_os = "linux")] mod platform { use super::*; - use crate::linux_helpers::{ - Desktop, detect_desktop, read_gsettings, read_portal_rgb, read_portal_u32, - }; + use crate::linux_helpers::{Desktop, detect_desktop, read_gsettings}; + use crate::linux_portal::{read_portal_rgb, read_portal_u32}; + use teksilo_kde_scheme::KdeConfigLocations; pub(super) fn query_color_scheme() -> ColorSchemePreference { // XDG portal: 0 = no preference, 1 = dark, 2 = light. @@ -52,20 +111,17 @@ mod platform { // dark-mode KDE users to a light theme). match detect_desktop() { Desktop::Kde => { - // KDE stores the active scheme in `kdeglobals` — - // `[General] ColorScheme=BreezeDark` for dark, - // `BreezeLight` (or any name without "dark") for - // light. Reading the file is cheap; we already - // do it under `ThemeMode::Native` for the full - // colour palette. - let path = dirs_kdeglobals(); - if let Ok(content) = std::fs::read_to_string(&path) - && let Some(scheme) = ini_value(&content, "General", "ColorScheme") + // The scheme in effect decides, by its window background, + // as KDE reports it to applications and to the portal. + let locations = KdeConfigLocations::from_env(kde::process_env); + if let Some(dark) = + kde::kde_prefers_dark(&locations, &kde::process_env, kde::read_file) { - if scheme.to_lowercase().contains("dark") { - return ColorSchemePreference::Dark; - } - return ColorSchemePreference::Light; + return if dark { + ColorSchemePreference::Dark + } else { + ColorSchemePreference::Light + }; } } Desktop::Cinnamon => { @@ -89,28 +145,41 @@ mod platform { ColorSchemePreference::NoPreference } - pub(super) fn query_os_theme_colors() -> OsThemeColors { - let mut colors = OsThemeColors { - color_scheme: query_color_scheme(), - ..Default::default() - }; - + pub(super) fn query_native_desktop() -> NativeDesktop { match detect_desktop() { - Desktop::Kde => query_kde(&mut colors), - Desktop::Cinnamon => query_cinnamon(&mut colors), - Desktop::Gnome | Desktop::Other(_) => query_gnome(&mut colors), + Desktop::Kde => { + let locations = KdeConfigLocations::from_env(kde::process_env); + let desktop = kde::discover_kde(&locations, &kde::process_env, kde::read_file); + kde::native_desktop(desktop, || { + let mut colors = OsThemeColors::new(query_color_scheme()); + colors.accent = portal_accent(); + colors + }) + } + Desktop::Cinnamon => { + let mut colors = OsThemeColors::new(query_color_scheme()); + query_cinnamon(&mut colors); + NativeDesktop::new(colors) + } + Desktop::Gnome | Desktop::Other(_) => { + let mut colors = OsThemeColors::new(query_color_scheme()); + query_gnome(&mut colors); + NativeDesktop::new(colors) + } } + } - colors + /// The desktop portal's accent colour (RGB doubles). + fn portal_accent() -> Option { + read_portal_rgb("org.freedesktop.appearance", "accent-color") + .map(|(r, g, b)| Color::from_rgb(r as f32, g as f32, b as f32)) } // ── GNOME ──────────────────────────────────────────────────────────── fn query_gnome(colors: &mut OsThemeColors) { // Accent color from XDG portal (RGB doubles) - if let Some((r, g, b)) = read_portal_rgb("org.freedesktop.appearance", "accent-color") { - colors.accent = Some(Color::from_rgb(r as f32, g as f32, b as f32)); - } + colors.accent = portal_accent(); // Fallback: GNOME 47 named accent if colors.accent.is_none() @@ -142,93 +211,6 @@ mod platform { Some(Color::from_hex(hex)) } - // ── KDE ────────────────────────────────────────────────────────────── - - fn query_kde(colors: &mut OsThemeColors) { - let path = dirs_kdeglobals(); - let content = match std::fs::read_to_string(&path) { - Ok(c) => c, - Err(_) => return, - }; - - // Infer dark/light from color scheme name if portal didn't provide it - if colors.color_scheme == ColorSchemePreference::NoPreference - && let Some(scheme) = ini_value(&content, "General", "ColorScheme") - { - if scheme.to_lowercase().contains("dark") { - colors.color_scheme = ColorSchemePreference::Dark; - } else { - colors.color_scheme = ColorSchemePreference::Light; - } - } - - // Window colors - colors.window_bg = ini_color(&content, "Colors:Window", "BackgroundNormal"); - colors.window_fg = ini_color(&content, "Colors:Window", "ForegroundNormal"); - colors.accent = ini_color(&content, "Colors:Window", "DecorationFocus"); - - // Button colors - colors.button_bg = ini_color(&content, "Colors:Button", "BackgroundNormal"); - colors.button_fg = ini_color(&content, "Colors:Button", "ForegroundNormal"); - - // Selection colors - colors.selection_bg = ini_color(&content, "Colors:Selection", "BackgroundNormal"); - colors.selection_fg = ini_color(&content, "Colors:Selection", "ForegroundNormal"); - - // Tooltip colors - colors.tooltip_bg = ini_color(&content, "Colors:Tooltip", "BackgroundNormal"); - colors.tooltip_fg = ini_color(&content, "Colors:Tooltip", "ForegroundNormal"); - } - - fn dirs_kdeglobals() -> std::path::PathBuf { - if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") { - std::path::PathBuf::from(config_home).join("kdeglobals") - } else if let Ok(home) = std::env::var("HOME") { - std::path::PathBuf::from(home).join(".config/kdeglobals") - } else { - std::path::PathBuf::from("/dev/null") - } - } - - /// Read a value from a simple INI file (section + key). - fn ini_value<'a>(content: &'a str, section: &str, key: &str) -> Option<&'a str> { - let section_header = format!("[{}]", section); - let mut in_section = false; - - for line in content.lines() { - let trimmed = line.trim(); - if trimmed.starts_with('[') { - in_section = trimmed == section_header; - continue; - } - if in_section - && let Some((k, v)) = trimmed.split_once('=') - && k.trim() == key - { - return Some(v.trim()); - } - } - None - } - - /// Parse a KDE "R,G,B" color value (0-255 integers). - fn ini_color(content: &str, section: &str, key: &str) -> Option { - let value = ini_value(content, section, key)?; - let parts: Vec<&str> = value.split(',').collect(); - if parts.len() >= 3 { - let r = parts[0].trim().parse::().ok()?; - let g = parts[1].trim().parse::().ok()?; - let b = parts[2].trim().parse::().ok()?; - Some(Color::from_rgb( - r as f32 / 255.0, - g as f32 / 255.0, - b as f32 / 255.0, - )) - } else { - None - } - } - // ── Cinnamon ───────────────────────────────────────────────────────── fn query_cinnamon(colors: &mut OsThemeColors) { @@ -310,6 +292,7 @@ mod platform { if colors.tooltip_fg.is_none() { colors.tooltip_fg = resolve(&["tooltip_fg_color"]); } + fill_gtk_view_colors(colors, resolve); // Derive accent from selection color if not already set if colors.accent.is_none() @@ -319,6 +302,35 @@ mod platform { } } + /// Fill the content-view and semantic colours from the GTK names that + /// carry them, leaving any a desktop reader already set. GTK themes + /// publish no alternate-row or secondary-text colour (its + /// `insensitive_fg_color` is disabled text, not secondary), so + /// `alternate_bg` and `inactive_fg` stay `None` here. Breeze GTK names its + /// links and its focus decoration only with a `_breeze` suffix, hence the + /// second name in those lists. + fn fill_gtk_view_colors( + colors: &mut OsThemeColors, + resolve: impl Fn(&[&str]) -> Option, + ) { + let fill = |slot: &mut Option, names: &[&str]| { + if slot.is_none() { + *slot = resolve(names); + } + }; + fill(&mut colors.view_bg, &["theme_base_color", "view_bg_color"]); + fill(&mut colors.view_fg, &["theme_text_color", "view_fg_color"]); + fill(&mut colors.link, &["link_color", "link_color_breeze"]); + fill( + &mut colors.link_visited, + &["link_visited_color", "link_visited_color_breeze"], + ); + fill(&mut colors.error, &["error_color"]); + fill(&mut colors.warning, &["warning_color"]); + fill(&mut colors.success, &["success_color"]); + fill(&mut colors.focus, &["theme_button_decoration_focus_breeze"]); + } + /// Load GTK CSS for a theme. Tries gtk-4.0 first, falls back to gtk-3.0. /// Also checks user override directories. fn load_gtk_css(theme_name: &str) -> String { @@ -476,6 +488,52 @@ mod platform { assert!((c.r() - 0x35 as f32 / 255.0).abs() < 0.01); } + #[test] + fn gtk_view_colors_come_from_their_css_names() { + // A cut-down Breeze GTK theme: the bare names reference the + // `_breeze` ones, and links and focus exist only suffixed. + let css = r#" +@define-color theme_base_color_breeze #ffffff; +@define-color theme_text_color_breeze #232629; +@define-color link_color_breeze #2980b9; +@define-color link_visited_color_breeze #9b59b6; +@define-color error_color_breeze #da4453; +@define-color warning_color_breeze #f67400; +@define-color success_color_breeze #27ae60; +@define-color theme_button_decoration_focus_breeze #3daee9; +@define-color theme_base_color @theme_base_color_breeze; +@define-color theme_text_color @theme_text_color_breeze; +@define-color error_color @error_color_breeze; +@define-color warning_color @warning_color_breeze; +@define-color success_color @success_color_breeze; +"#; + let defined = parse_define_colors(css); + let resolve = |names: &[&str]| names.iter().find_map(|n| resolve_color(&defined, n)); + let mut colors = OsThemeColors::new(ColorSchemePreference::Light); + // A colour a desktop reader already set is kept. + colors.success = Some(Color::BLACK); + fill_gtk_view_colors(&mut colors, resolve); + assert_eq!(colors.view_bg, Some(Color::from_hex("#ffffff"))); + assert_eq!(colors.view_fg, Some(Color::from_hex("#232629"))); + assert_eq!(colors.link, Some(Color::from_hex("#2980b9"))); + assert_eq!(colors.link_visited, Some(Color::from_hex("#9b59b6"))); + assert_eq!(colors.error, Some(Color::from_hex("#da4453"))); + assert_eq!(colors.warning, Some(Color::from_hex("#f67400"))); + assert_eq!(colors.success, Some(Color::BLACK)); + assert_eq!(colors.focus, Some(Color::from_hex("#3daee9"))); + // GTK carries neither of these. + assert_eq!(colors.alternate_bg, None); + assert_eq!(colors.inactive_fg, None); + + // A theme without the names leaves every field absent. + let empty = std::collections::HashMap::new(); + let mut colors = OsThemeColors::new(ColorSchemePreference::Dark); + fill_gtk_view_colors(&mut colors, |names: &[&str]| { + names.iter().find_map(|n| resolve_color(&empty, n)) + }); + assert_eq!(colors, OsThemeColors::new(ColorSchemePreference::Dark)); + } + #[test] fn parse_css_color_hex() { let c = parse_css_color("#3584e4").unwrap(); @@ -496,27 +554,6 @@ mod platform { assert_eq!(parse_css_color("black").unwrap(), Color::BLACK); } - #[test] - fn kde_ini_parsing() { - let content = r#" -[General] -ColorScheme=Breeze-Dark - -[Colors:Window] -BackgroundNormal=49,54,59 -ForegroundNormal=239,240,241 -DecorationFocus=61,174,233 -"#; - let bg = ini_color(content, "Colors:Window", "BackgroundNormal").unwrap(); - assert!((bg.r() - 49.0 / 255.0).abs() < 0.01); - - let fg = ini_color(content, "Colors:Window", "ForegroundNormal").unwrap(); - assert!((fg.r() - 239.0 / 255.0).abs() < 0.01); - - let scheme = ini_value(content, "General", "ColorScheme").unwrap(); - assert!(scheme.contains("Dark")); - } - #[test] fn gnome_named_accent_mapping() { assert!(gnome_named_accent("blue").is_some()); @@ -560,11 +597,8 @@ mod platform { ColorSchemePreference::NoPreference } - pub(super) fn query_os_theme_colors() -> OsThemeColors { - OsThemeColors { - color_scheme: query_color_scheme(), - ..Default::default() - } + pub(super) fn query_native_desktop() -> NativeDesktop { + NativeDesktop::new(OsThemeColors::new(query_color_scheme())) } } @@ -595,11 +629,8 @@ mod platform { ColorSchemePreference::NoPreference } - pub(super) fn query_os_theme_colors() -> OsThemeColors { - OsThemeColors { - color_scheme: query_color_scheme(), - ..Default::default() - } + pub(super) fn query_native_desktop() -> NativeDesktop { + NativeDesktop::new(OsThemeColors::new(query_color_scheme())) } } @@ -612,7 +643,7 @@ mod platform { ColorSchemePreference::NoPreference } - pub(super) fn query_os_theme_colors() -> OsThemeColors { - OsThemeColors::default() + pub(super) fn query_native_desktop() -> NativeDesktop { + NativeDesktop::new(OsThemeColors::default()) } } diff --git a/crates/teksilo-platform/src/os_theme/kde.rs b/crates/teksilo-platform/src/os_theme/kde.rs new file mode 100644 index 000000000..ad04957be --- /dev/null +++ b/crates/teksilo-platform/src/os_theme/kde.rs @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The KDE Plasma reader behind `ThemeMode::Native` and the light or dark +//! preference. +//! +//! Everything KDE-specific is in `teksilo-kde-scheme`: where the files are +//! ([`KdeConfigLocations`]), how they cascade ([`KConfig::cascade`]), which +//! colour scheme is in effect and how the user's accent applies to it +//! ([`KdeDesktop::from_layers`]). This module only finds which of the +//! listed files exist, reads them through the `read` closure it is given, +//! and hands their contents over. +//! +//! The readers are pure: the environment arrives as a getter and the files +//! through `read`, so the tests run over fixture trees held in memory. The +//! two impure helpers at the end ([`process_env`], [`read_file`]) are what +//! the real queries pass in. +//! +//! What is read, per `engineering/docs/kde-breeze-spec.md`: +//! +//! - the `kdeglobals` cascade (b.7): `/etc/kde5rc`, every `system.kdeglobals` +//! then every `kdeglobals` of `$XDG_CONFIG_DIRS` (with +//! `$XDG_CONFIG_HOME/kdedefaults` first in a KDE session), then the user's +//! own files, with Kiosk immutability (b.6); +//! - the colours: the `[Colors:*]` groups of `kdeglobals` when it has a +//! `[Colors:View]` group, which already carry any accent the user chose; +//! otherwise the look-and-feel package's colours, else the scheme named +//! by `[General] ColorScheme`, else Breeze Light (b.8), with `[General] +//! AccentColor` then applied to it when it is a colour and not +//! transparent (n.13); +//! - the fonts, `[KDE] AnimationDurationFactor` and `[KDE] widgetStyle` +//! (j.1, j.2, j.4), and the `breezerc` cascade (j.6). +//! +//! `forceFontDPI` is not read: no Plasma 6.8 component reads it (j.3). + +use std::path::Path; + +use teksilo_kde_scheme::{ + ConfigLayerPath, KConfig, KdeConfigLocations, KdeDesktop, KdeDesktopLayers, +}; +use teksilo_tokens::OsThemeColors; + +use super::NativeDesktop; + +/// An environment getter: a variable's value, `None` when unset. +pub(crate) type EnvVar<'a> = &'a dyn Fn(&str) -> Option; + +/// The KDE desktop's settings, or `None` when no file of the `kdeglobals` +/// cascade can be read. +/// +/// `var` gives the environment `[$e]` entries expand in; `read` returns a +/// file's bytes, `None` for a file that does not exist or cannot be read. +pub(crate) fn discover_kde( + locations: &KdeConfigLocations, + var: EnvVar<'_>, + read: impl Fn(&Path) -> Option>, +) -> Option { + let colours = ColourFiles::read(locations, var, &read)?; + let breezerc = read_cascade(&locations.breezerc_cascade(), &read); + Some(KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &colours.kdeglobals, + breezerc: &breezerc, + scheme_file: colours.scheme_file.as_ref(), + }, + &locations.expansion_env(var), + )) +} + +/// Whether the KDE colour scheme in effect is dark +/// ([`KdeColorScheme::is_dark`](teksilo_kde_scheme::KdeColorScheme::is_dark)), +/// reading only the `kdeglobals` cascade and the scheme file; `None` when no +/// file of the `kdeglobals` cascade can be read. +/// +/// The scheme is the one [`discover_kde`] finds, accent included, so the +/// two never disagree. +pub(crate) fn kde_prefers_dark( + locations: &KdeConfigLocations, + var: EnvVar<'_>, + read: impl Fn(&Path) -> Option>, +) -> Option { + let colours = ColourFiles::read(locations, var, &read)?; + let desktop = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &colours.kdeglobals, + breezerc: &[], + scheme_file: colours.scheme_file.as_ref(), + }, + &locations.expansion_env(var), + ); + Some(desktop.scheme.is_dark()) +} + +/// Whether `kdeglobals [KDE] AnimationDurationFactor` is 0, which KDE +/// reports as reduced motion +/// ([`KdeDesktop::prefers_reduced_motion`]), reading only the `kdeglobals` +/// cascade; `None` when no file of it can be read. +pub(crate) fn kde_prefers_reduced_motion( + locations: &KdeConfigLocations, + var: EnvVar<'_>, + read: impl Fn(&Path) -> Option>, +) -> Option { + let kdeglobals = read_cascade(&locations.kdeglobals_cascade(), &read); + if kdeglobals.is_empty() { + return None; + } + let desktop = KdeDesktop::from_layers( + &KdeDesktopLayers { + kdeglobals: &kdeglobals, + ..KdeDesktopLayers::default() + }, + &locations.expansion_env(var), + ); + Some(desktop.prefers_reduced_motion()) +} + +/// The native desktop of a KDE session. +/// +/// With a readable `kdeglobals` the colour report is the scheme's own +/// ([`KdeColorScheme::os_theme_colors`](teksilo_kde_scheme::KdeColorScheme::os_theme_colors)), +/// accent included, and `without_kdeglobals` is not called. Without one, +/// the report is whatever `without_kdeglobals` gives (the portal's light or +/// dark preference and accent colour), and the desktop carries no +/// [`KdeDesktop`]. +pub(crate) fn native_desktop( + kde: Option, + without_kdeglobals: impl FnOnce() -> OsThemeColors, +) -> NativeDesktop { + match kde { + Some(desktop) => NativeDesktop { + os: desktop.scheme.os_theme_colors(), + kde: Some(desktop), + }, + None => NativeDesktop::new(without_kdeglobals()), + } +} + +/// The files a colour scheme is read from: the `kdeglobals` cascade, and +/// the scheme file when `kdeglobals` does not carry the colours itself. +struct ColourFiles { + kdeglobals: Vec, + scheme_file: Option, +} + +impl ColourFiles { + /// `None` when no file of the `kdeglobals` cascade can be read. + fn read( + locations: &KdeConfigLocations, + var: EnvVar<'_>, + read: &impl Fn(&Path) -> Option>, + ) -> Option { + let kdeglobals = read_cascade(&locations.kdeglobals_cascade(), read); + if kdeglobals.is_empty() { + return None; + } + let merged = KConfig::cascade(&kdeglobals); + // The first candidate that can be read is the scheme file. + let scheme_file = locations + .scheme_fallback_candidates(&merged, &locations.expansion_env(var)) + .and_then(|candidates| candidates.iter().find_map(|path| read(path))) + .map(|bytes| KConfig::parse_bytes(&bytes).0); + Some(ColourFiles { + kdeglobals, + scheme_file, + }) + } +} + +/// Every readable file of a cascade, parsed and marked with its layer +/// kind, lowest priority first. A file that cannot be read is skipped, as +/// KDE skips a file that does not exist; parse diagnostics are dropped, the +/// offending lines having been skipped. +fn read_cascade( + layers: &[ConfigLayerPath], + read: &impl Fn(&Path) -> Option>, +) -> Vec { + layers + .iter() + .filter_map(|layer| { + read(&layer.path).map(|bytes| KConfig::parse_bytes(&bytes).0.with_kind(layer.kind)) + }) + .collect() +} + +/// The process environment, for the real queries. +pub(crate) fn process_env(name: &str) -> Option { + std::env::var(name).ok() +} + +/// Reads a file of the real file system; any failure reads as absent. +pub(crate) fn read_file(path: &Path) -> Option> { + std::fs::read(path).ok() +} + +#[cfg(test)] +mod tests { + //! Fixture trees held in memory: no test reads the real home, the real + //! `/etc/xdg` or the process environment. + + use std::collections::HashMap; + use std::path::PathBuf; + + use teksilo_kde_scheme::{KdeColor, KdeColorScheme, SchemeOrigin}; + use teksilo_tokens::{Color, ColorSchemePreference}; + + use super::*; + + /// A file tree in memory. + #[derive(Default)] + struct Tree(HashMap>); + + impl Tree { + fn file(mut self, path: &str, text: &str) -> Tree { + self.0.insert(PathBuf::from(path), text.as_bytes().to_vec()); + self + } + + fn read(&self) -> impl Fn(&Path) -> Option> + '_ { + |path| self.0.get(path).cloned() + } + } + + /// An environment getter over a fixed table. + fn env(vars: &[(&str, &str)]) -> impl Fn(&str) -> Option + use<> { + let table: HashMap = vars + .iter() + .map(|(k, v)| ((*k).to_owned(), (*v).to_owned())) + .collect(); + move |name| table.get(name).cloned() + } + + /// A KDE session of user ada, with the default XDG directories. + fn kde_env() -> impl Fn(&str) -> Option + use<> { + env(&[("HOME", "/home/ada"), ("XDG_CURRENT_DESKTOP", "KDE")]) + } + + fn discover(tree: &Tree, var: EnvVar<'_>) -> Option { + discover_kde(&KdeConfigLocations::from_env(var), var, tree.read()) + } + + fn prefers_dark(tree: &Tree) -> Option { + let var = kde_env(); + kde_prefers_dark(&KdeConfigLocations::from_env(&var), &var, tree.read()) + } + + fn reduced_motion(tree: &Tree) -> Option { + let var = kde_env(); + kde_prefers_reduced_motion(&KdeConfigLocations::from_env(&var), &var, tree.read()) + } + + const USER_KDEGLOBALS: &str = "/home/ada/.config/kdeglobals"; + const KDEDEFAULTS: &str = "/home/ada/.config/kdedefaults/kdeglobals"; + const ETC_KDEGLOBALS: &str = "/etc/xdg/kdeglobals"; + const LIGHT_FILE: &str = "/usr/share/color-schemes/BreezeLight.colors"; + const DARK_FILE: &str = "/usr/share/color-schemes/BreezeDark.colors"; + const LOOK_AND_FEEL: &str = + "/usr/share/plasma/look-and-feel/org.kde.breeze.desktop/contents/colors"; + + fn light_text() -> String { + KdeColorScheme::breeze_light().to_kconfig_text() + } + + fn dark_text() -> String { + KdeColorScheme::breeze_dark().to_kconfig_text() + } + + fn rgb(r: u8, g: u8, b: u8) -> Option { + Some(KdeColor::rgb8(r, g, b).to_color()) + } + + // ---- kdeglobals carries the colours -------------------------------- + + #[test] + fn a_full_kdeglobals_gives_its_colours_fonts_and_settings() { + let kdeglobals = format!( + "{}\n[General]\nColorScheme=BreezeDark\nAccentColor=233,61,0\n\ + font=Inter,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1\n\ + fixed=JetBrains Mono,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1\n\ + [KDE]\nAnimationDurationFactor=0.5\nwidgetStyle=Breeze\nforceFontDPI=144\n", + dark_text() + ); + let tree = Tree::default() + .file(USER_KDEGLOBALS, &kdeglobals) + .file("/home/ada/.config/breezerc", "[Style]\nMenuOpacity=80\n") + // A scheme file is not read when kdeglobals has the colours. + .file(DARK_FILE, &light_text()); + let desktop = discover(&tree, &kde_env()).expect("a readable kdeglobals"); + + assert_eq!(desktop.scheme_origin, SchemeOrigin::Kdeglobals); + let os = desktop.scheme.os_theme_colors(); + assert_eq!(os, KdeColorScheme::breeze_dark().os_theme_colors()); + assert_eq!(os.color_scheme, ColorSchemePreference::Dark); + // The accent is the selection colour already in the groups; the + // stored AccentColor is recorded but not applied again. + assert_eq!(os.accent, rgb(61, 174, 233)); + assert_eq!(desktop.user_accent, Some(KdeColor::rgb8(233, 61, 0))); + + assert_eq!(desktop.fonts.general.family, "Inter"); + assert_eq!(desktop.fonts.general.point_size, Some(11.0)); + assert_eq!(desktop.fonts.fixed.family, "JetBrains Mono"); + assert_eq!(desktop.fonts.small.family, "Noto Sans"); + assert_eq!(desktop.animation_duration_factor, Some(0.5)); + assert_eq!(desktop.widget_style.as_deref(), Some("Breeze")); + assert!(desktop.uses_breeze_style()); + assert_eq!(desktop.breeze.menu_opacity, 80); + } + + #[test] + fn kdeglobals_colours_are_read_as_written_and_decide_dark() { + // The fixture of the reader this one replaced: a scheme name that + // says dark, and the colours to go with it. + let tree = Tree::default().file( + USER_KDEGLOBALS, + "[General]\nColorScheme=Breeze-Dark\n\n\ + [Colors:View]\nBackgroundNormal=35,38,41\n\n\ + [Colors:Window]\nBackgroundNormal=49,54,59\nForegroundNormal=239,240,241\n\ + DecorationFocus=61,174,233\n", + ); + let os = discover(&tree, &kde_env()) + .unwrap() + .scheme + .os_theme_colors(); + assert_eq!(os.window_bg, rgb(49, 54, 59)); + assert_eq!(os.window_fg, rgb(239, 240, 241)); + assert_eq!(os.view_bg, rgb(35, 38, 41)); + assert_eq!(os.color_scheme, ColorSchemePreference::Dark); + // The accent is the selection colour, not the window's focus + // decoration: the Selection set is absent, so KDE's default. + let defaults = KdeColorScheme::kcolorscheme_defaults(); + let selection = defaults.set(teksilo_kde_scheme::ColorSet::Selection); + assert_eq!(os.accent, Some(selection.background_normal.to_color())); + assert_eq!(prefers_dark(&tree), Some(true)); + } + + #[test] + fn no_kdeglobals_at_all_is_no_kde_desktop() { + let tree = Tree::default().file(LIGHT_FILE, &light_text()); + assert_eq!(discover(&tree, &kde_env()), None); + assert_eq!(prefers_dark(&tree), None); + assert_eq!(reduced_motion(&tree), None); + } + + // ---- The cascade --------------------------------------------------- + + #[test] + fn this_machines_layout_reads_kdedefaults_over_etc_xdg() { + // The scheme is named only in kdedefaults and /etc/xdg; the user's + // kdeglobals has fonts but no colours. + let tree = Tree::default() + .file(KDEDEFAULTS, "[General]\nColorScheme=BreezeDark\n") + .file( + ETC_KDEGLOBALS, + "[General]\nColorScheme=BreezeLight\n[KDE]\nwidgetStyle=Breeze\n", + ) + .file( + USER_KDEGLOBALS, + "[General]\nfont=Noto Sans,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1\n", + ) + .file(LIGHT_FILE, &light_text()) + .file(DARK_FILE, &dark_text()); + + let desktop = discover(&tree, &kde_env()).unwrap(); + assert_eq!(desktop.scheme_origin, SchemeOrigin::SchemeFile); + assert!(desktop.scheme.is_dark()); + assert_eq!( + desktop.scheme.os_theme_colors(), + KdeColorScheme::breeze_dark().os_theme_colors() + ); + assert_eq!(desktop.fonts.general.point_size, Some(12.0)); + assert_eq!(desktop.widget_style.as_deref(), Some("Breeze")); + assert_eq!(prefers_dark(&tree), Some(true)); + + // Outside a KDE session kdedefaults is not prepended, so /etc/xdg + // names the scheme. + let outside = env(&[("HOME", "/home/ada")]); + let desktop = discover(&tree, &outside).unwrap(); + assert!(!desktop.scheme.is_dark()); + + // A session that lists XDG_CONFIG_DIRS itself is followed exactly, + // kdedefaults still first. + let listed = env(&[ + ("HOME", "/home/ada"), + ("XDG_CURRENT_DESKTOP", "KDE"), + ("XDG_CONFIG_DIRS", "/opt/xdg:/etc/xdg"), + ]); + let tree = tree.file( + "/opt/xdg/kdeglobals", + "[General]\nColorScheme=BreezeLight\n", + ); + assert!(discover(&tree, &listed).unwrap().scheme.is_dark()); + } + + #[test] + fn the_scheme_file_is_looked_for_in_the_data_home_first() { + let mine = dark_text().replace("BackgroundNormal=32,35,38", "BackgroundNormal=10,20,30"); + let tree = Tree::default() + .file(USER_KDEGLOBALS, "[General]\nColorScheme=BreezeDark\n") + .file(DARK_FILE, &dark_text()) + .file( + "/home/ada/.local/share/color-schemes/BreezeDark.colors", + &mine, + ); + let os = discover(&tree, &kde_env()) + .unwrap() + .scheme + .os_theme_colors(); + assert_eq!(os.window_bg, rgb(10, 20, 30)); + } + + #[test] + fn the_look_and_feel_colours_come_before_the_named_scheme() { + let tree = Tree::default() + .file(USER_KDEGLOBALS, "[General]\nColorScheme=BreezeLight\n") + .file(LIGHT_FILE, &light_text()) + .file(LOOK_AND_FEEL, &dark_text()); + let desktop = discover(&tree, &kde_env()).unwrap(); + assert_eq!(desktop.scheme_origin, SchemeOrigin::SchemeFile); + assert!(desktop.scheme.is_dark()); + } + + #[test] + fn without_any_scheme_file_breeze_light_is_built_in() { + let tree = Tree::default().file(USER_KDEGLOBALS, "[General]\nColorScheme=Missing\n"); + let desktop = discover(&tree, &kde_env()).unwrap(); + assert_eq!(desktop.scheme_origin, SchemeOrigin::BuiltIn); + assert_eq!( + desktop.scheme.os_theme_colors(), + KdeColorScheme::breeze_light().os_theme_colors() + ); + assert_eq!(prefers_dark(&tree), Some(false)); + } + + #[test] + fn an_immutable_group_in_a_lower_layer_freezes_it_for_the_user() { + let tree = Tree::default() + .file( + ETC_KDEGLOBALS, + "[KDE][$i]\nwidgetStyle=Fusion\n\n[General]\nColorScheme[$i]=BreezeLight\n", + ) + .file( + USER_KDEGLOBALS, + "[KDE]\nwidgetStyle=Oxygen\nAnimationDurationFactor=0\n\n\ + [General]\nColorScheme=BreezeDark\nfixed=Hack,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1\n", + ) + .file(LIGHT_FILE, &light_text()) + .file(DARK_FILE, &dark_text()); + let desktop = discover(&tree, &kde_env()).unwrap(); + // The user's whole [KDE] group is ignored, keys the lower layer did + // not set included. + assert_eq!(desktop.widget_style.as_deref(), Some("Fusion")); + assert!(!desktop.uses_breeze_style()); + assert_eq!(desktop.animation_duration_factor, None); + assert!(!desktop.prefers_reduced_motion()); + // Only the immutable ColorScheme entry is frozen in [General]. + assert!(!desktop.scheme.is_dark()); + assert_eq!(desktop.fonts.fixed.point_size, Some(12.0)); + assert_eq!(prefers_dark(&tree), Some(false)); + assert_eq!(reduced_motion(&tree), Some(false)); + } + + // ---- The accent when kdeglobals has no colours ---------------------- + + fn accent_tree(accent_line: &str) -> Tree { + Tree::default() + .file( + USER_KDEGLOBALS, + &format!("[General]\nColorScheme=BreezeLight\n{accent_line}\n"), + ) + .file(LIGHT_FILE, &light_text()) + } + + #[test] + fn a_valid_accent_colour_is_applied_to_the_scheme_file() { + let accent = KdeColor::rgb8(233, 61, 0); + let desktop = discover(&accent_tree("AccentColor=233,61,0"), &kde_env()).unwrap(); + assert_eq!(desktop.scheme_origin, SchemeOrigin::SchemeFile); + assert_eq!(desktop.user_accent, Some(accent)); + let os = desktop.scheme.os_theme_colors(); + assert_eq!( + os, + KdeColorScheme::breeze_light() + .with_accent(accent) + .os_theme_colors() + ); + assert_ne!(os, KdeColorScheme::breeze_light().os_theme_colors()); + // Focus decorations take the accent itself. + assert_eq!(os.focus, Some(accent.to_color())); + + // The hex form is the same colour. + let hex = discover(&accent_tree("AccentColor=#e93d00"), &kde_env()).unwrap(); + assert_eq!(hex.scheme.os_theme_colors(), os); + } + + #[test] + fn a_valid_accent_colour_is_applied_to_built_in_breeze_light() { + let accent = KdeColor::rgb8(146, 110, 228); + let tree = Tree::default().file(USER_KDEGLOBALS, "[General]\nAccentColor=146,110,228\n"); + let desktop = discover(&tree, &kde_env()).unwrap(); + assert_eq!(desktop.scheme_origin, SchemeOrigin::BuiltIn); + assert_eq!( + desktop.scheme.os_theme_colors(), + KdeColorScheme::breeze_light() + .with_accent(accent) + .os_theme_colors() + ); + } + + #[test] + fn a_transparent_invalid_or_absent_accent_colour_is_no_accent() { + let plain = KdeColorScheme::breeze_light().os_theme_colors(); + for line in [ + "AccentColor=transparent", + "AccentColor=0,0,0,0", + "AccentColor=invalid", + "AccentColor=", + "AccentColor=12,34", + "AccentColor=300,0,0", + "AccentColor=notacolour", + "", + ] { + let desktop = discover(&accent_tree(line), &kde_env()).unwrap(); + assert_eq!(desktop.user_accent, None, "{line:?}"); + assert_eq!(desktop.scheme.os_theme_colors(), plain, "{line:?}"); + } + } + + // ---- Accent precedence --------------------------------------------- + + #[test] + fn kdeglobals_wins_over_the_portal_accent() { + let portal = || { + let mut os = OsThemeColors::new(ColorSchemePreference::Dark); + os.accent = Some(Color::from_rgb(1.0, 0.0, 0.0)); + os + }; + + let desktop = discover(&accent_tree(""), &kde_env()); + let native = native_desktop(desktop.clone(), || { + panic!("the portal is not asked when kdeglobals can be read") + }); + assert_eq!(native.kde, desktop); + assert_eq!(native.os, KdeColorScheme::breeze_light().os_theme_colors()); + assert_eq!(native.os.accent, rgb(61, 174, 233)); + + // Without a readable kdeglobals the portal's report is the one. + let native = native_desktop(None, portal); + assert_eq!(native.kde, None); + assert_eq!(native.os, portal()); + } + + // ---- Dark detection -------------------------------------------------- + + #[test] + fn dark_is_decided_by_the_colours_not_the_scheme_name() { + let light = accent_tree(""); + assert_eq!(prefers_dark(&light), Some(false)); + + let dark = Tree::default() + .file(USER_KDEGLOBALS, "[General]\nColorScheme=BreezeDark\n") + .file(DARK_FILE, &dark_text()); + assert_eq!(prefers_dark(&dark), Some(true)); + + // A light scheme whose name says dark is light. + let misnamed = Tree::default() + .file(USER_KDEGLOBALS, "[General]\nColorScheme=MyDarkScheme\n") + .file( + "/usr/share/color-schemes/MyDarkScheme.colors", + &light_text(), + ); + assert_eq!(prefers_dark(&misnamed), Some(false)); + + // Twilight-like: Breeze Light's views under Breeze Dark's window. The + // window decides, so it is dark. + let twilight = format!( + "{}\n[General]\nColorScheme=BreezeTwilight\n\n\ + [Colors:Window]\nBackgroundNormal=32,35,38\nForegroundNormal=252,252,252\n", + light_text() + ); + let twilight = Tree::default().file(USER_KDEGLOBALS, &twilight); + assert_eq!(prefers_dark(&twilight), Some(true)); + let os = discover(&twilight, &kde_env()) + .unwrap() + .scheme + .os_theme_colors(); + assert_eq!(os.color_scheme, ColorSchemePreference::Dark); + assert_eq!(os.view_bg, rgb(255, 255, 255)); + } + + // ---- Reduced motion -------------------------------------------------- + + #[test] + fn reduced_motion_follows_an_animation_factor_of_zero() { + let with = |factor: &str| { + Tree::default().file( + USER_KDEGLOBALS, + &format!("[KDE]\nAnimationDurationFactor={factor}\n"), + ) + }; + assert_eq!(reduced_motion(&with("0")), Some(true)); + assert_eq!(reduced_motion(&with("0.0")), Some(true)); + assert_eq!(reduced_motion(&with("1")), Some(false)); + assert_eq!(reduced_motion(&with("0.25")), Some(false)); + let absent = Tree::default().file(USER_KDEGLOBALS, "[KDE]\nwidgetStyle=Breeze\n"); + assert_eq!(reduced_motion(&absent), Some(false)); + + // The same answer as the full reader's. + let var = kde_env(); + let desktop = discover(&with("0"), &var).unwrap(); + assert!(desktop.prefers_reduced_motion()); + } +} diff --git a/crates/teksilo-platform/src/os_theme/watch.rs b/crates/teksilo-platform/src/os_theme/watch.rs new file mode 100644 index 000000000..f8d35e5aa --- /dev/null +++ b/crates/teksilo-platform/src/os_theme/watch.rs @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Hearing the desktop's appearance change while the application runs: its +//! accent, its colour scheme, its light or dark preference, its fonts and +//! its animation speed. +//! +//! An [`OsThemeWatch`] listens on the D-Bus session bus, on a thread of its +//! own with a connection of its own, for the four signals a Linux desktop +//! sends when one of those changes: +//! +//! - the desktop portal's `org.freedesktop.portal.Settings.SettingChanged` +//! on `/org/freedesktop/portal/desktop`, for a setting of the +//! `org.freedesktop.appearance` namespace (colour scheme, accent colour, +//! contrast, reduced motion) or of an `org.kde.kdeglobals.` +//! namespace (KDE's palette, font and widget style); a setting of any +//! other namespace is not heard; +//! - KDE's `org.kde.KGlobalSettings.notifyChange` on `/KGlobalSettings`; +//! - KDE's `org.kde.kconfig.notify.ConfigChanged` on `/kdeglobals` and on +//! `/breezerc`, sent when either file is written. +//! +//! A signal is matched on its interface, member and path, from whichever +//! sender sends it. What a KDE signal says changed is not decoded: any one +//! of them means everything is read again. +//! +//! One change on the desktop usually arrives as several signals within a +//! few milliseconds (a new colour scheme is a `ConfigChanged`, a +//! `notifyChange` and several `SettingChanged`). The watch therefore +//! debounces: the first signal opens a window of [`DEBOUNCE`], and when the +//! window ends one [`OsThemeChanged`] is posted through +//! [`AppEventPoster::post_external`], whatever was heard during it. A signal +//! heard after the window ended opens another. Every signal is thus +//! followed by a payload posted no earlier than it, so the reading the +//! payload prompts sees every change signalled before it. +//! +//! The connection can close while the application runs, when the bus daemon +//! exits or restarts. A window still open then is posted at once rather than +//! dropped, and the watch connects again every [`RECONNECT_EVERY`] until it +//! reaches the bus or is dropped. A signal sent while it was not connected +//! was heard by no one, so on reaching the bus again the watch opens a window +//! as if it had heard one, and its payload reads the desktop again for +//! whatever changed in between. A bus that cannot be reached when the watch +//! starts is not tried again: that session has none. +//! +//! The watch reads nothing itself; the payload's receiver queries the +//! desktop again ([`query_native_desktop`](super::query_native_desktop), +//! [`query_color_scheme`](super::query_color_scheme)). +//! +//! A session with no bus, or a signal that never came, is covered by +//! [`OsThemeWatch::files_changed`]: it compares the modification times of +//! the files of the `kdeglobals` and `breezerc` cascades with those it last +//! saw, for a caller to ask when a window takes focus. +//! +//! There is no watch elsewhere than on Linux: [`OsThemeWatch::new`] answers +//! `None`, and the window system's own light or dark notification is all +//! there is. + +// Elsewhere than on Linux nothing starts a watch; the machinery is still +// compiled there, so its tests run on every platform. +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] + +#[cfg(target_os = "linux")] +mod bus; +#[cfg(test)] +mod tests; + +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Condvar, Mutex, PoisonError}; +use std::task::{Context, Waker}; +use std::thread::JoinHandle; +use std::time::{Duration, Instant, SystemTime}; + +use teksilo_core::AppEventPoster; +use teksilo_kde_scheme::KdeConfigLocations; + +/// How long after the first signal of a change the watch posts its payload. +pub const DEBOUNCE: Duration = Duration::from_millis(150); + +/// How often a watch whose connection closed tries to connect again. An +/// attempt on a bus whose socket is gone fails at once, and one on a bus +/// that does not answer is bounded by the reader's call timeout, on the +/// watch's own thread either way. +pub const RECONNECT_EVERY: Duration = Duration::from_secs(1); + +/// The portal's settings interface and object path, and its change signal. +const PORTAL_SETTINGS: &str = "org.freedesktop.portal.Settings"; +const PORTAL_PATH: &str = "/org/freedesktop/portal/desktop"; +const SETTING_CHANGED: &str = "SettingChanged"; +/// KDE's global settings change signal. +const GLOBAL_SETTINGS: &str = "org.kde.KGlobalSettings"; +const GLOBAL_SETTINGS_PATH: &str = "/KGlobalSettings"; +const NOTIFY_CHANGE: &str = "notifyChange"; +/// KDE's configuration-file change signal, and the files it is heard for. +const KCONFIG_NOTIFY: &str = "org.kde.kconfig.notify"; +const CONFIG_CHANGED: &str = "ConfigChanged"; +const KDEGLOBALS_PATH: &str = "/kdeglobals"; +const BREEZERC_PATH: &str = "/breezerc"; + +/// Every signal the watch listens to: interface, member and object path. +const LISTENED: [(&str, &str, &str); 4] = [ + (PORTAL_SETTINGS, SETTING_CHANGED, PORTAL_PATH), + (GLOBAL_SETTINGS, NOTIFY_CHANGE, GLOBAL_SETTINGS_PATH), + (KCONFIG_NOTIFY, CONFIG_CHANGED, KDEGLOBALS_PATH), + (KCONFIG_NOTIFY, CONFIG_CHANGED, BREEZERC_PATH), +]; + +/// What an [`OsThemeWatch`] posts through +/// [`AppEventPoster::post_external`] once a change on the desktop has been +/// heard and its debounce window has ended. It carries nothing: the +/// receiver reads the desktop again, so posting one by any other means asks +/// for the same reading. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OsThemeChanged; + +/// Watches the desktop's appearance; see the [module](self). Dropping it +/// stops the watch: its thread is told to stop and joined, and nothing is +/// posted once the drop has returned. +pub struct OsThemeWatch { + /// The listening thread. + _running: Running, + /// The configuration files [`files_changed`](Self::files_changed) + /// compares, as last seen. + files: ConfigStamps, +} + +impl std::fmt::Debug for OsThemeWatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OsThemeWatch") + .field("files", &self.files.files.len()) + .finish() + } +} + +impl OsThemeWatch { + /// Start watching, posting each [`OsThemeChanged`] through `poster` + /// from the watch's own thread. `None` on every platform but Linux, and + /// when the thread cannot be started. + /// + /// The bus is connected to on the watch's thread, so this returns at + /// once. A session with no bus leaves a watch that hears nothing, whose + /// [`files_changed`](Self::files_changed) still answers. A connection + /// that closes later is made again; see the [module](self). + pub fn new(poster: impl AppEventPoster) -> Option { + #[cfg(target_os = "linux")] + { + Self::on_bus(None, poster, |_| {}) + } + #[cfg(not(target_os = "linux"))] + { + let _ = poster; + None + } + } + + /// [`new`](Self::new) on the bus at `address` (`None`: the session bus + /// the environment names). `opened` is told on the watch's thread, at + /// each attempt to connect, whether the bus was reached and every signal + /// subscribed to, before any is heard. + #[cfg(target_os = "linux")] + fn on_bus( + address: Option, + poster: impl AppEventPoster, + mut opened: impl FnMut(bool) + Send + 'static, + ) -> Option { + let locations = KdeConfigLocations::from_env(super::kde::process_env); + let files = ConfigStamps::read(config_files(&locations), modified); + let running = Running::start( + DEBOUNCE, + RECONNECT_EVERY, + move |stop| { + let source = bus::BusSource::connect(address.as_deref(), stop); + opened(source.is_some()); + source + }, + move || poster.post_external(Box::new(OsThemeChanged)), + )?; + Some(Self { + _running: running, + files, + }) + } + + /// Whether a file of the `kdeglobals` or `breezerc` cascade was + /// written, made or removed since the watch started or since the last + /// call, by its modification time; the times read become the ones the + /// next call compares with. For the main thread, when a window takes + /// focus, and after an [`OsThemeChanged`] so that the change it was + /// posted for is not counted twice. + pub fn files_changed(&mut self) -> bool { + self.files.refresh(modified) + } +} + +// ── Signals ───────────────────────────────────────────────────────────────── + +/// Whether a signal with this interface, member and object path is one the +/// watch acts on. `namespace` is the first argument of a portal +/// `SettingChanged`, `None` when it could not be read. +fn signal_matters(interface: &str, member: &str, path: &str, namespace: Option<&str>) -> bool { + match (interface, member, path) { + (PORTAL_SETTINGS, SETTING_CHANGED, PORTAL_PATH) => { + namespace.is_some_and(portal_namespace_matters) + } + (GLOBAL_SETTINGS, NOTIFY_CHANGE, GLOBAL_SETTINGS_PATH) => true, + (KCONFIG_NOTIFY, CONFIG_CHANGED, KDEGLOBALS_PATH | BREEZERC_PATH) => true, + _ => false, + } +} + +/// Whether a portal setting of `namespace` can change the theme or the +/// animation speed: `org.freedesktop.appearance`, and the +/// `org.kde.kdeglobals.` namespaces KDE's portal serves `kdeglobals` +/// under. +fn portal_namespace_matters(namespace: &str) -> bool { + namespace == "org.freedesktop.appearance" + || namespace + .strip_prefix("org.kde.kdeglobals.") + .is_some_and(|group| !group.is_empty()) +} + +// ── Debounce ──────────────────────────────────────────────────────────────── + +/// The debounce, on instants it is given: the first change opens a window +/// of `window`, later changes inside it do not move its end, and the window +/// is due once its end is reached. +#[derive(Debug, Clone, PartialEq, Eq)] +struct Debounce { + window: Duration, + due: Option, +} + +impl Debounce { + fn new(window: Duration) -> Self { + Self { window, due: None } + } + + /// A change heard at `now`. + fn heard(&mut self, now: Instant) { + if self.due.is_none() { + self.due = Some(now + self.window); + } + } + + /// When the open window ends; `None` while none is open. + fn deadline(&self) -> Option { + self.due + } + + /// Close the open window before its end; whether one was open. + fn take_open(&mut self) -> bool { + self.due.take().is_some() + } + + /// Whether the open window has ended by `now`. A window that has is + /// closed, so it is due once. + fn take_due(&mut self, now: Instant) -> bool { + match self.due { + Some(due) if now >= due => { + self.due = None; + true + } + _ => false, + } + } +} + +/// What waiting on a [`ChangeSource`] ended with. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Heard { + /// A change was heard. + Change, + /// The deadline passed first. + Deadline, + /// The source has closed, or the watch is stopping. + Closed, +} + +/// Where changes are heard from: the session bus, or a test's fake. +trait ChangeSource { + /// Wait for the next change, until `deadline` (`None`: for as long as + /// it takes), or until the source closes or the watch stops. + fn wait(&mut self, deadline: Option) -> Heard; +} + +/// Hear changes from `source` and call `post` once at the end of each +/// debounce window, reading the time from `clock`, until the source closes +/// or `stop` is set. A window still open when the source closes is posted +/// then, before its end, so a change heard is never dropped. Nothing is +/// posted once `stop` is set. +fn run( + source: &mut impl ChangeSource, + stop: &Stop, + debounce: &mut Debounce, + clock: &impl Fn() -> Instant, + post: &impl Fn(), +) { + loop { + match source.wait(debounce.deadline()) { + Heard::Change => debounce.heard(clock()), + Heard::Deadline => {} + Heard::Closed => { + if !stop.is_stopped() && debounce.take_open() { + post(); + } + return; + } + } + if stop.is_stopped() { + return; + } + if debounce.take_due(clock()) { + post(); + } + } +} + +// ── The thread ────────────────────────────────────────────────────────────── + +/// The flag a watch is stopped by, with the waker of the future waiting on +/// it and the condition a thread pausing on it waits on. +#[derive(Debug, Default)] +struct Stop { + stopped: AtomicBool, + waker: Mutex>, + /// Paired with `waker`'s lock, for [`pause`](Self::pause). + parked: Condvar, +} + +impl Stop { + /// Set the flag and wake whatever waits on it. + fn stop(&self) { + self.stopped.store(true, Ordering::SeqCst); + let waker = { + let mut slot = self.waker.lock().unwrap_or_else(PoisonError::into_inner); + // Under the lock, so a pause that read the flag before it was set + // is already waiting and hears this. + self.parked.notify_all(); + slot.take() + }; + if let Some(waker) = waker { + waker.wake(); + } + } + + /// Wait for `duration`, or until the flag is set; whether it is still + /// unset. The flag is read under the lock [`stop`](Self::stop) notifies + /// under, so a stop is never missed. + fn pause(&self, duration: Duration) -> bool { + let slot = self.waker.lock().unwrap_or_else(PoisonError::into_inner); + let _ = self + .parked + .wait_timeout_while(slot, duration, |_| !self.is_stopped()) + .unwrap_or_else(PoisonError::into_inner); + !self.is_stopped() + } + + fn is_stopped(&self) -> bool { + self.stopped.load(Ordering::SeqCst) + } + + /// Whether the flag is set, registering `cx`'s waker to be woken when it + /// is. The waker is stored before the flag is read, so a stop between + /// the two is either seen here or wakes it. + fn poll_stopped(&self, cx: &Context<'_>) -> bool { + *self.waker.lock().unwrap_or_else(PoisonError::into_inner) = Some(cx.waker().clone()); + self.is_stopped() + } +} + +/// The watch's thread; dropping it stops the thread and joins it. +struct Running { + stop: Arc, + thread: Option>, +} + +impl Running { + /// Start the thread: it opens its source with `open` (`None`: nothing + /// to listen to, and the thread ends), then [`run`]s it with a debounce + /// window of `window`, calling `post` at the end of each. When the + /// source closes, `open` is called again every `retry` until it gives a + /// source or the watch stops, and the source it gives is run with a + /// window already open, for what was not heard meanwhile. + fn start( + window: Duration, + retry: Duration, + mut open: impl FnMut(Arc) -> Option + Send + 'static, + post: impl Fn() + Send + 'static, + ) -> Option { + let stop = Arc::new(Stop::default()); + let thread_stop = Arc::clone(&stop); + let thread = std::thread::Builder::new() + .name("teksilo-os-theme-watch".to_owned()) + .spawn(move || { + let Some(mut source) = open(Arc::clone(&thread_stop)) else { + return; + }; + let mut debounce = Debounce::new(window); + loop { + run( + &mut source, + &thread_stop, + &mut debounce, + &Instant::now, + &post, + ); + drop(source); + let Some(again) = reopen(&mut open, &thread_stop, retry) else { + return; + }; + source = again; + debounce.heard(Instant::now()); + } + }) + .ok()?; + Some(Running { + stop, + thread: Some(thread), + }) + } +} + +/// A source from `open`, tried every `retry`, the first try one `retry` +/// from now; `None` once `stop` is set. +fn reopen( + open: &mut impl FnMut(Arc) -> Option, + stop: &Arc, + retry: Duration, +) -> Option { + loop { + if !stop.pause(retry) { + return None; + } + if let Some(source) = open(Arc::clone(stop)) { + return Some(source); + } + } +} + +impl Drop for Running { + fn drop(&mut self) { + self.stop.stop(); + if let Some(thread) = self.thread.take() { + let _ = thread.join(); + } + } +} + +// ── The configuration files ───────────────────────────────────────────────── + +/// Files and their modification times as last read, `None` for a file +/// that did not exist or could not be read. +#[derive(Debug, Clone, PartialEq, Eq)] +struct ConfigStamps { + files: Vec<(PathBuf, Option)>, +} + +impl ConfigStamps { + /// The times of `paths`, read with `modified`. + fn read( + paths: impl IntoIterator, + modified: impl Fn(&Path) -> Option, + ) -> Self { + let files = paths + .into_iter() + .map(|path| { + let stamp = modified(&path); + (path, stamp) + }) + .collect(); + Self { files } + } + + /// Read every time again with `modified`; whether any differs from the + /// one read before, a file made or removed included. + fn refresh(&mut self, modified: impl Fn(&Path) -> Option) -> bool { + let mut changed = false; + for (path, stamp) in &mut self.files { + let now = modified(path); + if now != *stamp { + *stamp = now; + changed = true; + } + } + changed + } +} + +/// Every file of the `kdeglobals` and `breezerc` cascades, existing or not. +fn config_files(locations: &KdeConfigLocations) -> Vec { + locations + .kdeglobals_cascade() + .into_iter() + .chain(locations.breezerc_cascade()) + .map(|layer| layer.path) + .collect() +} + +/// A file's modification time; `None` when it does not exist or the time +/// cannot be read. +fn modified(path: &Path) -> Option { + std::fs::metadata(path).and_then(|m| m.modified()).ok() +} diff --git a/crates/teksilo-platform/src/os_theme/watch/bus.rs b/crates/teksilo-platform/src/os_theme/watch/bus.rs new file mode 100644 index 000000000..326aa964d --- /dev/null +++ b/crates/teksilo-platform/src/os_theme/watch/bus.rs @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The watch's source on Linux: its own connection to the session bus, with +//! one match rule per listened signal. + +use std::future::{Future, poll_fn}; +use std::pin::{Pin, pin}; +use std::sync::Arc; +use std::task::Poll; +use std::time::Instant; + +use zbus::export::futures_core::Stream; +use zbus::zvariant::OwnedValue; +use zbus::{MatchRule, Message, MessageStream}; + +use super::{ChangeSource, Heard, LISTENED, PORTAL_SETTINGS, Stop, signal_matters}; +use crate::linux_portal::{CALL_TIMEOUT, within}; + +/// How many signals of one rule may wait unread before the oldest is +/// dropped. The watch reads them as they come; one is enough to act on. +const QUEUED: usize = 64; + +/// The session bus, as a [`ChangeSource`]. +pub(crate) struct BusSource { + /// Held so the streams' connection stays open. + _connection: zbus::Connection, + streams: Vec, + stop: Arc, +} + +impl BusSource { + /// Connect to the bus at `address` (`None`: the session bus the + /// environment names) and subscribe to every listened signal. `None` + /// when the bus cannot be reached within + /// [`CALL_TIMEOUT`], refuses a match + /// rule, or the watch stops meanwhile. + pub(crate) fn connect(address: Option<&str>, stop: Arc) -> Option { + let subscribed = async_io::block_on(until_stopped(&stop, async { + let builder = match address { + Some(address) => zbus::connection::Builder::address(address), + None => zbus::connection::Builder::session(), + }; + let connection = within( + CALL_TIMEOUT, + builder.ok()?.method_timeout(CALL_TIMEOUT).build(), + ) + .await? + .ok()?; + let mut streams = Vec::with_capacity(LISTENED.len()); + for (interface, member, path) in LISTENED { + let rule = MatchRule::builder() + .msg_type(zbus::message::Type::Signal) + .interface(interface) + .ok()? + .member(member) + .ok()? + .path(path) + .ok()? + .build(); + streams.push( + MessageStream::for_match_rule(rule, &connection, Some(QUEUED)) + .await + .ok()?, + ); + } + Some((connection, streams)) + })) + .flatten(); + let (connection, streams) = subscribed?; + Some(BusSource { + _connection: connection, + streams, + stop, + }) + } +} + +impl ChangeSource for BusSource { + fn wait(&mut self, deadline: Option) -> Heard { + let mut timer = deadline.map(async_io::Timer::at); + let stop = &self.stop; + let streams = &mut self.streams; + async_io::block_on(poll_fn(|cx| { + if stop.poll_stopped(cx) { + return Poll::Ready(Heard::Closed); + } + for stream in streams.iter_mut() { + loop { + match Pin::new(&mut *stream).poll_next(cx) { + Poll::Ready(Some(Ok(message))) => { + if message_matters(&message) { + return Poll::Ready(Heard::Change); + } + } + // A message zbus could not read: the next one may + // be fine. + Poll::Ready(Some(Err(_))) => {} + // The connection is gone. + Poll::Ready(None) => return Poll::Ready(Heard::Closed), + Poll::Pending => break, + } + } + } + if let Some(timer) = timer.as_mut() + && Pin::new(timer).poll(cx).is_ready() + { + return Poll::Ready(Heard::Deadline); + } + Poll::Pending + })) + } +} + +/// Whether `message` is a signal the watch acts on +/// ([`signal_matters`]), reading a portal +/// `SettingChanged`'s namespace from its body. +fn message_matters(message: &Message) -> bool { + let header = message.header(); + let (Some(interface), Some(member), Some(path)) = + (header.interface(), header.member(), header.path()) + else { + return false; + }; + let namespace = if interface.as_str() == PORTAL_SETTINGS { + message + .body() + .deserialize::<(String, String, OwnedValue)>() + .ok() + .map(|(namespace, _, _)| namespace) + } else { + None + }; + signal_matters( + interface.as_str(), + member.as_str(), + path.as_str(), + namespace.as_deref(), + ) +} + +/// `future`'s output, or `None` once `stop` is set. +async fn until_stopped(stop: &Stop, future: impl Future) -> Option { + let mut future = pin!(future); + poll_fn(|cx| { + if stop.poll_stopped(cx) { + return Poll::Ready(None); + } + future.as_mut().poll(cx).map(Some) + }) + .await +} diff --git a/crates/teksilo-platform/src/os_theme/watch/tests.rs b/crates/teksilo-platform/src/os_theme/watch/tests.rs new file mode 100644 index 000000000..9713e25a7 --- /dev/null +++ b/crates/teksilo-platform/src/os_theme/watch/tests.rs @@ -0,0 +1,898 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +use std::cell::Cell; +use std::collections::VecDeque; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::mpsc::{self, Receiver, RecvTimeoutError}; +use std::time::{Duration, Instant}; + +use super::{ + BREEZERC_PATH, CONFIG_CHANGED, ChangeSource, Debounce, GLOBAL_SETTINGS, GLOBAL_SETTINGS_PATH, + Heard, KCONFIG_NOTIFY, KDEGLOBALS_PATH, LISTENED, NOTIFY_CHANGE, PORTAL_PATH, PORTAL_SETTINGS, + Running, SETTING_CHANGED, Stop, run, signal_matters, +}; + +const MS: Duration = Duration::from_millis(1); + +// ── Signals ───────────────────────────────────────────────────────────────── + +#[test] +fn a_portal_setting_matters_by_its_namespace() { + let portal = + |namespace| signal_matters(PORTAL_SETTINGS, SETTING_CHANGED, PORTAL_PATH, namespace); + assert!(portal(Some("org.freedesktop.appearance"))); + assert!(portal(Some("org.kde.kdeglobals.General"))); + assert!(portal(Some("org.kde.kdeglobals.KDE"))); + assert!(portal(Some("org.kde.kdeglobals.Colors:View"))); + // KDE's other namespaces, GNOME's, a bare prefix and an unreadable body. + assert!(!portal(Some("org.kde.VirtualKeyboard"))); + assert!(!portal(Some("org.kde.TabletMode"))); + assert!(!portal(Some("org.gnome.desktop.interface"))); + assert!(!portal(Some("org.kde.kdeglobals."))); + assert!(!portal(Some("org.kde.kdeglobals"))); + assert!(!portal(Some("org.freedesktop.appearance.extra"))); + assert!(!portal(None)); +} + +#[test] +fn the_kde_signals_matter_on_their_paths_only() { + assert!(signal_matters( + GLOBAL_SETTINGS, + NOTIFY_CHANGE, + GLOBAL_SETTINGS_PATH, + None + )); + assert!(signal_matters( + KCONFIG_NOTIFY, + CONFIG_CHANGED, + KDEGLOBALS_PATH, + None + )); + assert!(signal_matters( + KCONFIG_NOTIFY, + CONFIG_CHANGED, + BREEZERC_PATH, + None + )); + // Another configuration file, another path, another member. + assert!(!signal_matters( + KCONFIG_NOTIFY, + CONFIG_CHANGED, + "/kwinrc", + None + )); + assert!(!signal_matters( + GLOBAL_SETTINGS, + NOTIFY_CHANGE, + "/KGlobalSettings/other", + None + )); + assert!(!signal_matters( + GLOBAL_SETTINGS, + "slotNotifyChange", + GLOBAL_SETTINGS_PATH, + None + )); + // The right member and path under another interface. + assert!(!signal_matters( + "org.example.Other", + CONFIG_CHANGED, + KDEGLOBALS_PATH, + None + )); + assert!(!signal_matters( + "org.example.Other", + SETTING_CHANGED, + PORTAL_PATH, + Some("org.freedesktop.appearance") + )); +} + +#[test] +fn every_listened_signal_matters() { + for (interface, member, path) in LISTENED { + assert!( + signal_matters(interface, member, path, Some("org.freedesktop.appearance")), + "{interface}.{member} on {path}" + ); + } +} + +// ── Debounce ──────────────────────────────────────────────────────────────── + +#[test] +fn a_window_opens_on_the_first_change_and_is_due_once() { + let start = Instant::now(); + let mut debounce = Debounce::new(150 * MS); + assert_eq!(debounce.deadline(), None); + assert!(!debounce.take_due(start + 1000 * MS), "nothing heard"); + + debounce.heard(start); + debounce.heard(start + 100 * MS); + assert_eq!( + debounce.deadline(), + Some(start + 150 * MS), + "a later change does not move the end" + ); + assert!(!debounce.take_due(start + 149 * MS)); + assert!(debounce.take_due(start + 150 * MS)); + assert!(!debounce.take_due(start + 151 * MS), "due once"); + assert_eq!(debounce.deadline(), None); + + debounce.heard(start + 200 * MS); + assert_eq!(debounce.deadline(), Some(start + 350 * MS)); +} + +/// A source whose changes come at fixed instants, on a clock it moves +/// itself: to each change it reports, and to the deadline when that comes +/// first. It closes when its changes are spent and no window is open, or, +/// when `closes_early`, as soon as they are spent. +struct Scripted { + now: Rc>, + changes: VecDeque, + closes_early: bool, +} + +impl ChangeSource for Scripted { + fn wait(&mut self, deadline: Option) -> Heard { + if self.closes_early && self.changes.is_empty() { + return Heard::Closed; + } + match (self.changes.front().copied(), deadline) { + (Some(at), Some(due)) if at > due => { + self.now.set(due); + Heard::Deadline + } + (Some(at), _) => { + self.changes.pop_front(); + self.now.set(at); + Heard::Change + } + (None, Some(due)) => { + self.now.set(due); + Heard::Deadline + } + (None, None) => Heard::Closed, + } + } +} + +/// The milliseconds after the start at which [`run`] posts, for changes +/// at `changes` milliseconds with a window of `window` milliseconds. +fn posts_for(window: u64, changes: &[u64]) -> Vec { + posts_for_source(window, changes, false) +} + +/// [`posts_for`] on a source that closes right after its last change. +fn posts_for_closing(window: u64, changes: &[u64]) -> Vec { + posts_for_source(window, changes, true) +} + +fn posts_for_source(window: u64, changes: &[u64], closes_early: bool) -> Vec { + let start = Instant::now(); + let now = Rc::new(Cell::new(start)); + let mut source = Scripted { + now: Rc::clone(&now), + changes: changes.iter().map(|ms| start + *ms as u32 * MS).collect(), + closes_early, + }; + let posted = std::cell::RefCell::new(Vec::new()); + run( + &mut source, + &Stop::default(), + &mut Debounce::new(window as u32 * MS), + &|| now.get(), + &|| { + posted + .borrow_mut() + .push((now.get() - start).as_millis() as u64) + }, + ); + posted.into_inner() +} + +#[test] +fn a_burst_collapses_to_one_payload() { + assert_eq!(posts_for(150, &[0, 5, 10, 60, 149]), vec![150]); +} + +#[test] +fn separate_bursts_give_one_payload_each() { + assert_eq!(posts_for(150, &[0, 10, 500, 510, 520]), vec![150, 650]); +} + +#[test] +fn a_change_after_its_window_ends_opens_another() { + // A change at the very end of a window is covered by its payload; one + // just after opens the next window. + assert_eq!(posts_for(150, &[0, 150, 151]), vec![150, 301]); +} + +#[test] +fn nothing_heard_posts_nothing() { + assert_eq!(posts_for(150, &[]), Vec::::new()); +} + +#[test] +fn every_change_is_followed_by_a_payload_no_earlier_than_it() { + // A fixed pseudo-random sequence of gaps, from 0 to 400 ms. + let mut state: u64 = 0x2545_f491_4f6c_dd1d; + let mut at = 0; + let mut changes = Vec::new(); + for _ in 0..200 { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + at += state % 401; + changes.push(at); + } + let posts = posts_for(150, &changes); + for change in &changes { + assert!( + posts + .iter() + .any(|post| post >= change && post - change <= 150), + "the change at {change} ms is followed by a payload within the window" + ); + } + for pair in posts.windows(2) { + assert!( + pair[1] - pair[0] >= 150, + "payloads {pair:?} closer than a window" + ); + } + assert!( + posts.len() < changes.len(), + "nearby changes share a payload" + ); +} + +#[test] +fn a_stopped_watch_posts_nothing() { + let start = Instant::now(); + let now = Rc::new(Cell::new(start)); + let mut source = Scripted { + now: Rc::clone(&now), + changes: [start, start + 10 * MS].into(), + closes_early: false, + }; + let stop = Stop::default(); + stop.stop(); + let posted = Cell::new(0); + run( + &mut source, + &stop, + &mut Debounce::new(150 * MS), + &|| now.get(), + &|| posted.set(posted.get() + 1), + ); + assert_eq!(posted.get(), 0); +} + +#[test] +fn a_window_open_when_the_source_closes_is_posted_then() { + // The source closes 10 ms into a window of 150: the changes it heard + // are posted for at the close, not dropped. + assert_eq!(posts_for_closing(150, &[0, 10]), vec![10]); + // A window that already ended was posted for at its end, and the close + // after it posts nothing more. + assert_eq!(posts_for_closing(150, &[0, 200]), vec![150, 200]); + assert_eq!(posts_for_closing(150, &[]), Vec::::new()); +} + +#[test] +fn a_window_open_when_a_stopped_watch_closes_posts_nothing() { + let start = Instant::now(); + let now = Rc::new(Cell::new(start)); + let mut source = Scripted { + now: Rc::clone(&now), + changes: VecDeque::new(), + closes_early: true, + }; + let stop = Stop::default(); + stop.stop(); + let mut debounce = Debounce::new(150 * MS); + debounce.heard(start); + let posted = Cell::new(0); + run(&mut source, &stop, &mut debounce, &|| now.get(), &|| { + posted.set(posted.get() + 1) + }); + assert_eq!(posted.get(), 0); +} + +#[test] +fn a_pause_ends_at_once_when_the_watch_stops() { + let stop = Arc::new(Stop::default()); + assert!(stop.pause(MS), "a pause nothing stops runs out"); + let stopper = Arc::clone(&stop); + let thread = std::thread::spawn(move || { + std::thread::sleep(20 * MS); + stopper.stop(); + }); + let paused = Instant::now(); + assert!(!stop.pause(Duration::from_secs(30)), "the stop ends it"); + assert!(paused.elapsed() < Duration::from_secs(5)); + thread.join().unwrap(); + assert!(!stop.pause(Duration::from_secs(30)), "and every later one"); +} + +// ── The thread ────────────────────────────────────────────────────────────── + +/// A source fed through a channel, watching the stop flag between short +/// waits. +struct Channel { + changes: Receiver<()>, + stop: Arc, +} + +impl ChangeSource for Channel { + fn wait(&mut self, deadline: Option) -> Heard { + loop { + if self.stop.is_stopped() { + return Heard::Closed; + } + let slice = deadline.map_or(5 * MS, |due| { + due.saturating_duration_since(Instant::now()).min(5 * MS) + }); + match self.changes.recv_timeout(slice) { + Ok(()) => return Heard::Change, + Err(RecvTimeoutError::Timeout) => { + if deadline.is_some_and(|due| Instant::now() >= due) { + return Heard::Deadline; + } + } + Err(RecvTimeoutError::Disconnected) => return Heard::Closed, + } + } + } +} + +/// A running watch over a [`Channel`] with a window of `window`, the +/// sender feeding it, and how many times it has posted. +fn channel_watch(window: Duration) -> (Running, mpsc::Sender<()>, Arc) { + let (send, changes) = mpsc::channel(); + let posted = Arc::new(AtomicUsize::new(0)); + let counter = Arc::clone(&posted); + let mut changes = Some(changes); + let running = Running::start( + window, + 10 * MS, + move |stop| { + Some(Channel { + changes: changes.take()?, + stop, + }) + }, + move || { + counter.fetch_add(1, Ordering::SeqCst); + }, + ) + .expect("start the watch thread"); + (running, send, posted) +} + +/// Wait until `count` reaches `at_least`, for at most two seconds. +fn wait_for(count: &AtomicUsize, at_least: usize) { + let until = Instant::now() + Duration::from_secs(2); + while count.load(Ordering::SeqCst) < at_least && Instant::now() < until { + std::thread::sleep(5 * MS); + } +} + +#[test] +fn the_thread_posts_once_per_burst() { + let window = 40 * MS; + let (_running, send, posted) = channel_watch(window); + for _ in 0..5 { + send.send(()).unwrap(); + } + wait_for(&posted, 1); + std::thread::sleep(window * 3); + assert_eq!(posted.load(Ordering::SeqCst), 1, "one burst, one payload"); + + send.send(()).unwrap(); + wait_for(&posted, 2); + std::thread::sleep(window * 3); + assert_eq!(posted.load(Ordering::SeqCst), 2, "a second burst, a second"); +} + +#[test] +fn nothing_is_posted_after_drop() { + let window = 40 * MS; + let (running, send, posted) = channel_watch(window); + send.send(()).unwrap(); + let dropped = Instant::now(); + drop(running); + assert!( + dropped.elapsed() < Duration::from_secs(1), + "the drop joins promptly" + ); + let _ = send.send(()); + std::thread::sleep(window * 4); + assert_eq!( + posted.load(Ordering::SeqCst), + 0, + "the window still open at the drop posts nothing" + ); +} + +/// A running watch with a window of `window` that opens a [`Channel`] on +/// `first`, then one for each receiver sent on the returned sender, trying +/// every `retry`, and counts its attempts to open and its posts. +fn reopening_watch( + window: Duration, + retry: Duration, + first: Receiver<()>, +) -> ( + Running, + mpsc::Sender>, + Arc, + Arc, +) { + let (sources, next) = mpsc::channel::>(); + sources.send(first).unwrap(); + let opens = Arc::new(AtomicUsize::new(0)); + let posted = Arc::new(AtomicUsize::new(0)); + let (open_count, post_count) = (Arc::clone(&opens), Arc::clone(&posted)); + let running = Running::start( + window, + retry, + move |stop| { + open_count.fetch_add(1, Ordering::SeqCst); + let changes = next.try_recv().ok()?; + Some(Channel { changes, stop }) + }, + move || { + post_count.fetch_add(1, Ordering::SeqCst); + }, + ) + .expect("start the watch thread"); + (running, sources, opens, posted) +} + +#[test] +fn a_closed_source_is_posted_for_and_opened_again() { + let window = 40 * MS; + let (first_send, first) = mpsc::channel(); + let (running, sources, opens, posted) = reopening_watch(window, 10 * MS, first); + wait_for(&opens, 1); + + // A change, then the source closes inside its window. + first_send.send(()).unwrap(); + drop(first_send); + wait_for(&posted, 1); + assert_eq!( + posted.load(Ordering::SeqCst), + 1, + "the open window is posted" + ); + + // With nothing to open, the watch keeps trying, and posts nothing. + wait_for(&opens, 4); + assert!(opens.load(Ordering::SeqCst) >= 4, "it tries again"); + assert_eq!(posted.load(Ordering::SeqCst), 1); + + // Opened again: one payload for what nobody heard, then one per burst. + let (second_send, second) = mpsc::channel(); + sources.send(second).unwrap(); + wait_for(&posted, 2); + std::thread::sleep(window * 3); + assert_eq!( + posted.load(Ordering::SeqCst), + 2, + "the reopening reads again" + ); + let opened = opens.load(Ordering::SeqCst); + second_send.send(()).unwrap(); + wait_for(&posted, 3); + std::thread::sleep(window * 3); + assert_eq!(posted.load(Ordering::SeqCst), 3, "the new source is heard"); + assert_eq!(opens.load(Ordering::SeqCst), opened, "and kept"); + + let dropped = Instant::now(); + drop(running); + assert!(dropped.elapsed() < Duration::from_secs(1)); + assert_eq!(posted.load(Ordering::SeqCst), 3); +} + +#[test] +fn a_watch_waiting_to_reopen_is_dropped_promptly() { + let (first_send, first) = mpsc::channel(); + let (running, _sources, opens, posted) = + reopening_watch(10 * MS, Duration::from_secs(30), first); + wait_for(&opens, 1); + drop(first_send); + // Let the thread see the close and start its pause. + std::thread::sleep(50 * MS); + let dropped = Instant::now(); + drop(running); + assert!( + dropped.elapsed() < Duration::from_secs(5), + "the drop ends the pause" + ); + assert_eq!(opens.load(Ordering::SeqCst), 1); + assert_eq!(posted.load(Ordering::SeqCst), 0, "nothing was heard"); +} + +#[test] +fn a_source_that_cannot_open_ends_the_thread() { + let running = Running::start( + 10 * MS, + 10 * MS, + |_stop| None::, + || panic!("nothing to hear"), + ) + .expect("start the watch thread"); + let dropped = Instant::now(); + drop(running); + assert!(dropped.elapsed() < Duration::from_secs(1)); +} + +// ── The configuration files ───────────────────────────────────────────────── + +/// Unix only: XDG paths must start with `/`, so a Windows scratch directory +/// (`C:\...`) is not one and the cascade falls back to `/etc/xdg`. The watch +/// reads these files on Linux alone (`OsThemeWatch::new` is `None` elsewhere). +#[cfg(unix)] +mod cascade_files { + use std::path::{Path, PathBuf}; + use std::time::{Duration, SystemTime}; + + use teksilo_kde_scheme::KdeConfigLocations; + + use super::super::{ConfigStamps, config_files, modified}; + + fn set_modified(path: &Path, time: SystemTime) { + std::fs::File::options() + .write(true) + .open(path) + .expect("open the file") + .set_modified(time) + .expect("set its modification time"); + } + + #[test] + fn a_file_of_either_cascade_written_made_or_removed_is_a_change() { + let dir = tempfile::tempdir().expect("make a scratch directory"); + let root = dir.path().to_owned(); + let config = root.join("config"); + let etc = root.join("etc"); + for d in [&config, &etc, &config.join("kdedefaults")] { + std::fs::create_dir_all(d).expect("make a configuration directory"); + } + let vars: Vec<(&str, String)> = vec![ + ("HOME", root.display().to_string()), + ("XDG_CONFIG_HOME", config.display().to_string()), + ("XDG_CONFIG_DIRS", etc.display().to_string()), + ("XDG_CURRENT_DESKTOP", "KDE".to_owned()), + ("KDE_SKIP_KDERC", "1".to_owned()), + ]; + let locations = KdeConfigLocations::from_env(|name: &str| { + vars.iter() + .find(|(key, _)| *key == name) + .map(|(_, value)| value.clone()) + }); + let paths = config_files(&locations); + assert!( + paths.iter().all(|path| path.starts_with(&root)), + "every file is in the scratch directory: {paths:?}" + ); + let has = |path: PathBuf| assert!(paths.contains(&path), "{} is watched", path.display()); + has(config.join("kdeglobals")); + has(config.join("breezerc")); + has(config.join("kdedefaults").join("kdeglobals")); + has(etc.join("kdeglobals")); + has(etc.join("breezerc")); + + let user = config.join("kdeglobals"); + let system_breezerc = etc.join("breezerc"); + std::fs::write(&user, "[General]\nColorScheme=BreezeLight\n").unwrap(); + std::fs::write(&system_breezerc, "[Style]\n").unwrap(); + let base = SystemTime::now() - Duration::from_secs(3600); + set_modified(&user, base); + set_modified(&system_breezerc, base); + + let mut stamps = ConfigStamps::read(paths, modified); + assert!(!stamps.refresh(modified), "nothing changed"); + + set_modified(&user, base + Duration::from_secs(10)); + assert!( + stamps.refresh(modified), + "the user's kdeglobals was written" + ); + assert!(!stamps.refresh(modified), "and is counted once"); + + std::fs::write(config.join("breezerc"), "[Common]\n").unwrap(); + assert!(stamps.refresh(modified), "the user's breezerc was made"); + + std::fs::write(config.join("kdedefaults").join("kdeglobals"), "[KDE]\n").unwrap(); + assert!(stamps.refresh(modified), "the kdedefaults layer was made"); + + std::fs::remove_file(&system_breezerc).unwrap(); + assert!(stamps.refresh(modified), "the system breezerc was removed"); + assert!(!stamps.refresh(modified)); + + // A file outside both cascades is not watched. + std::fs::write(config.join("kwinrc"), "[Windows]\n").unwrap(); + assert!(!stamps.refresh(modified)); + } +} + +// ── The real bus ──────────────────────────────────────────────────────────── + +/// The watch on a private `dbus-daemon`, posting through a recording +/// poster. Opt-in: run with `TEKSILO_DBUS_TESTS=1`. +#[cfg(target_os = "linux")] +mod real_bus { + use std::collections::HashMap; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::{Arc, mpsc}; + use std::time::{Duration, Instant}; + + use teksilo_core::{AppEventPoster, SubscriptionId}; + use zbus::zvariant::Value; + + use super::super::{DEBOUNCE, OsThemeChanged, OsThemeWatch, RECONNECT_EVERY}; + use crate::linux_portal::tests::real_bus::{PrivateBus, enabled}; + + /// Counts the [`OsThemeChanged`] payloads posted to it, and every other + /// payload apart. + #[derive(Clone, Default)] + struct Recorder { + changes: Arc, + others: Arc, + } + + impl AppEventPoster for Recorder { + fn post_subscription_event(&self, _: SubscriptionId, _: Box) { + self.others.fetch_add(1, Ordering::SeqCst); + } + + fn post_external(&self, payload: Box) { + if payload.is::() { + self.changes.fetch_add(1, Ordering::SeqCst); + } else { + self.others.fetch_add(1, Ordering::SeqCst); + } + } + } + + impl Recorder { + fn changes(&self) -> usize { + self.changes.load(Ordering::SeqCst) + } + + /// Wait for `expected` payloads, then a little more to see that no + /// further one comes, and return the count. + fn settle(&self, expected: usize) -> usize { + let until = Instant::now() + Duration::from_secs(3); + while self.changes() < expected && Instant::now() < until { + std::thread::sleep(Duration::from_millis(5)); + } + std::thread::sleep(DEBOUNCE * 2); + self.changes() + } + } + + /// Something that sends one signal. + type Sender = Box zbus::Result<()>>; + + fn emit( + connection: &zbus::Connection, + path: &str, + interface: &str, + member: &str, + body: &B, + ) -> zbus::Result<()> + where + B: zbus::export::serde::Serialize + zbus::zvariant::DynamicType, + { + async_io::block_on(connection.emit_signal( + None::>, + path, + interface, + member, + body, + )) + } + + fn portal( + namespace: &'static str, + key: &'static str, + ) -> impl Fn(&zbus::Connection) -> zbus::Result<()> { + move |c| { + emit( + c, + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings", + "SettingChanged", + &(namespace, key, Value::U32(1)), + ) + } + } + + fn config_changed(c: &zbus::Connection, path: &str) -> zbus::Result<()> { + let groups: HashMap>> = + HashMap::from([("General".to_owned(), vec![b"ColorScheme".to_vec()])]); + emit(c, path, "org.kde.kconfig.notify", "ConfigChanged", &groups) + } + + fn notify_change(c: &zbus::Connection) -> zbus::Result<()> { + emit( + c, + "/KGlobalSettings", + "org.kde.KGlobalSettings", + "notifyChange", + &(0_i32, 0_i32), + ) + } + + #[test] + fn hears_each_listened_signal_once_per_debounce_window() { + if !enabled() { + return; + } + let bus = PrivateBus::start(); + let recorder = Recorder::default(); + let (opened, is_open) = mpsc::channel(); + let watch = OsThemeWatch::on_bus(Some(bus.address.clone()), recorder.clone(), move |ok| { + let _ = opened.send(ok); + }) + .expect("start the watch"); + assert_eq!( + is_open.recv_timeout(Duration::from_secs(5)), + Ok(true), + "the watch reached the bus and subscribed" + ); + let sender = async_io::block_on( + zbus::connection::Builder::address(bus.address.as_str()) + .expect("the bus address") + .build(), + ) + .expect("connect the sender"); + + // Each listened signal alone: one payload each. + let listened: Vec<(&str, Sender)> = vec![ + ( + "appearance color-scheme", + Box::new(portal("org.freedesktop.appearance", "color-scheme")), + ), + ( + "kdeglobals General ColorScheme", + Box::new(portal("org.kde.kdeglobals.General", "ColorScheme")), + ), + ("notifyChange", Box::new(notify_change)), + ( + "ConfigChanged /kdeglobals", + Box::new(|c| config_changed(c, "/kdeglobals")), + ), + ( + "ConfigChanged /breezerc", + Box::new(|c| config_changed(c, "/breezerc")), + ), + ]; + let mut expected = 0; + for (name, send) in &listened { + send(&sender).expect("send the signal"); + expected += 1; + assert_eq!(recorder.settle(expected), expected, "{name}"); + } + + // A burst of every one of them: one payload. + for _ in 0..3 { + for (_, send) in &listened { + send(&sender).expect("send the signal"); + } + } + expected += 1; + assert_eq!(recorder.settle(expected), expected, "a burst"); + + // Signals the watch does not listen to, or ignores. + let ignored: Vec = vec![ + Box::new(portal("org.kde.VirtualKeyboard", "active")), + Box::new(portal("org.gnome.desktop.interface", "gtk-theme")), + Box::new(|c| config_changed(c, "/kwinrc")), + Box::new(|c| { + emit( + c, + "/kdeglobals", + "org.example.Other", + "ConfigChanged", + &(0_i32,), + ) + }), + ]; + for send in &ignored { + send(&sender).expect("send the signal"); + } + assert_eq!(recorder.settle(expected), expected, "nothing it ignores"); + + // Nothing once the watch is dropped. + drop(watch); + for (_, send) in &listened { + send(&sender).expect("send the signal"); + } + assert_eq!(recorder.settle(expected), expected, "nothing after drop"); + assert_eq!(recorder.others.load(Ordering::SeqCst), 0); + } + + /// A connection to the bus, for sending. + fn sender_on(address: &str) -> zbus::Connection { + async_io::block_on( + zbus::connection::Builder::address(address) + .expect("the bus address") + .build(), + ) + .expect("connect the sender") + } + + #[test] + fn a_bus_that_goes_away_is_posted_for_and_reached_again() { + if !enabled() { + return; + } + let mut bus = PrivateBus::start_restartable(); + let recorder = Recorder::default(); + let (opened, is_open) = mpsc::channel(); + let watch = OsThemeWatch::on_bus(Some(bus.address.clone()), recorder.clone(), move |ok| { + let _ = opened.send(ok); + }) + .expect("start the watch"); + assert_eq!(is_open.recv_timeout(Duration::from_secs(5)), Ok(true)); + + // A change, and the bus killed inside its debounce window. + let sender = sender_on(&bus.address); + config_changed(&sender, "/kdeglobals").expect("send the signal"); + std::thread::sleep(Duration::from_millis(20)); + bus.kill(); + drop(sender); + assert_eq!( + recorder.settle(1), + 1, + "the change heard before the bus went" + ); + + // The bus back at its address: the watch reaches it again and reads + // the desktop once for what it could not hear meanwhile. + bus.restart(); + let reached = Instant::now() + RECONNECT_EVERY * 5; + while Instant::now() < reached { + if is_open.recv_timeout(Duration::from_millis(50)) == Ok(true) { + break; + } + } + assert!(Instant::now() < reached, "the watch reached the bus again"); + assert_eq!(recorder.settle(2), 2, "one payload for the gap"); + + // And it hears the new bus. + let sender = sender_on(&bus.address); + notify_change(&sender).expect("send the signal"); + assert_eq!(recorder.settle(3), 3, "a signal on the new bus"); + drop(watch); + assert_eq!(recorder.others.load(Ordering::SeqCst), 0); + } + + #[test] + fn a_bus_that_is_not_there_leaves_a_silent_watch() { + if !enabled() { + return; + } + let dir = std::env::temp_dir().join(format!("teksilo-watch-absent-{}", std::process::id())); + let (opened, is_open) = mpsc::channel(); + let recorder = Recorder::default(); + let watch = OsThemeWatch::on_bus( + Some(format!("unix:path={}", dir.join("no-bus").display())), + recorder.clone(), + move |ok| { + let _ = opened.send(ok); + }, + ) + .expect("start the watch"); + assert_eq!(is_open.recv_timeout(Duration::from_secs(5)), Ok(false)); + drop(watch); + assert_eq!(recorder.changes(), 0); + } +} diff --git a/crates/teksilo-preview-ui/src/app_state.rs b/crates/teksilo-preview-ui/src/app_state.rs index 0589a0d7e..48009c0ae 100644 --- a/crates/teksilo-preview-ui/src/app_state.rs +++ b/crates/teksilo-preview-ui/src/app_state.rs @@ -109,26 +109,19 @@ impl CanvasTheme { /// pickers used to — silently drops the previewer back to `Compact` on the /// next theme click. pub fn theme_at(self, density: teksilo_tokens::TargetDensity) -> teksilo_core::Theme { - self.theme().with_density(density) + self.resolve(density).with_density(density) } - /// Resolve to a concrete theme. For `Native`, builds a theme that - /// *adopts* the OS palette rather than picking between Teksilo's - /// built-in Int UI light/dark — this is what gives a visibly - /// distinct result from clicking Light/Dark on most desktops. - pub fn theme(self) -> teksilo_core::Theme { + /// Resolve to a concrete theme, before `density` is applied. For + /// `Native`, builds a theme that *adopts* the OS palette rather than + /// picking between Teksilo's built-in Int UI light/dark, which is what + /// gives a visibly distinct result from clicking Light/Dark on most + /// desktops. It goes through the framework's one Native-mode resolution, + /// with no app factory, asking at `density`. + fn resolve(self, density: teksilo_tokens::TargetDensity) -> teksilo_core::Theme { match self { CanvasTheme::Native => { - let os = teksilo_platform::os_theme::query_os_theme_colors(); - let base = if os.color_scheme.is_dark() { - teksilo_core::presets::intui::dark() - } else { - teksilo_core::presets::intui::light() - }; - teksilo_core::Theme { - colors: teksilo_tokens::ColorTokens::from_os_colors(&os), - ..base - } + native_canvas_theme(&teksilo_platform::os_theme::query_native_desktop(), density) } CanvasTheme::Light => teksilo_core::presets::intui::light(), CanvasTheme::Dark => teksilo_core::presets::intui::dark(), @@ -136,6 +129,18 @@ impl CanvasTheme { } } +/// The `Native` canvas theme over `desktop`: dark exactly when the desktop +/// states a dark preference, with no id or density applied. +fn native_canvas_theme( + desktop: &teksilo_platform::os_theme::NativeDesktop, + density: teksilo_tokens::TargetDensity, +) -> teksilo_core::Theme { + teksilo_app::resolve_native_theme( + None, + &teksilo_app::NativeThemeRequest::new(desktop, desktop.os.color_scheme.is_dark(), density), + ) +} + /// Cache key — `(widget_id, variant_name)`. pub type CacheKey = (&'static str, &'static str); @@ -475,3 +480,66 @@ impl Widget for PreviewerRoot { builder.set_name("Teksilo Widget Previewer"); } } + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_platform::os_theme::NativeDesktop; + use teksilo_tokens::{Color, ColorSchemePreference, ColorTokens, OsThemeColors, TargetDensity}; + + /// `CanvasTheme::Native` as it was resolved before the framework's + /// Native-mode resolution existed. + fn old_native(os: &OsThemeColors) -> teksilo_core::Theme { + let base = if os.color_scheme.is_dark() { + teksilo_core::presets::intui::dark() + } else { + teksilo_core::presets::intui::light() + }; + teksilo_core::Theme { + colors: ColorTokens::from_os_colors(os), + ..base + } + } + + #[test] + fn the_native_canvas_theme_resolves_as_before() { + for scheme in [ + ColorSchemePreference::Light, + ColorSchemePreference::Dark, + ColorSchemePreference::NoPreference, + ] { + let bare = OsThemeColors::new(scheme); + let mut legacy = OsThemeColors::new(scheme); + legacy.accent = Some(Color::from_rgb(0.24, 0.68, 0.91)); + legacy.window_bg = Some(Color::from_rgb(0.94, 0.94, 0.95)); + legacy.window_fg = Some(Color::from_rgb(0.14, 0.15, 0.16)); + legacy.selection_bg = Some(Color::from_rgb(0.24, 0.68, 0.91)); + let mut full = legacy.clone(); + full.view_bg = Some(Color::WHITE); + full.view_fg = Some(Color::from_rgb(0.12, 0.12, 0.12)); + full.link = Some(Color::from_rgb(0.16, 0.5, 0.73)); + full.error = Some(Color::from_rgb(0.85, 0.27, 0.33)); + full.focus = Some(Color::from_rgb(0.24, 0.68, 0.91)); + + for os in [bare, legacy, full] { + let desktop = NativeDesktop::new(os.clone()); + for density in [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ] { + let new = native_canvas_theme(&desktop, density).with_density(density); + let old = old_native(&os).with_density(density); + assert_eq!(new.id, old.id, "{os:?} at {density:?}: id"); + assert_eq!(new.colors, old.colors, "{os:?} at {density:?}: colours"); + assert_eq!(new, old, "{os:?} at {density:?}"); + } + assert_eq!( + native_canvas_theme(&desktop, TargetDensity::default()), + old_native(&os), + "{os:?}" + ); + } + } + } +} diff --git a/crates/teksilo-preview-ui/src/png_export.rs b/crates/teksilo-preview-ui/src/png_export.rs index ab681d183..f83452324 100644 --- a/crates/teksilo-preview-ui/src/png_export.rs +++ b/crates/teksilo-preview-ui/src/png_export.rs @@ -40,42 +40,43 @@ pub fn export_current(state: &AppState) -> Result { // Compact ladder, which is a different picture. let pass = teksilo_preview::PreviewPass::new(state.density.get()); let theme = canvas_theme.theme_at(pass.density()); + let label = theme_label(canvas_theme, &theme); let mut shooter = Shooter::new(EXPORT_SCALE)?; let shot = shooter.capture(widget, theme, &ShotOptions::default().with_pass(pass))?; - let out_path = output_path(widget_id, variant_name, canvas_theme, pass)?; + let out_path = output_path(widget_id, variant_name, label, pass)?; write_png(&out_path, &shot.rgba, shot.width, shot.height)?; Ok(out_path) } +/// The theme part of an export's file name for `canvas_theme`, which +/// resolved to `theme`. A `Native` canvas is named for what it rendered, +/// "native-light" or "native-dark", read from `theme` itself: the desktop +/// is read once, by [`CanvasTheme::theme_at`](crate::app_state::CanvasTheme::theme_at), +/// so the name cannot disagree with the picture. +fn theme_label( + canvas_theme: crate::app_state::CanvasTheme, + theme: &teksilo_core::Theme, +) -> &'static str { + match canvas_theme { + crate::app_state::CanvasTheme::Light => "light", + crate::app_state::CanvasTheme::Dark => "dark", + crate::app_state::CanvasTheme::Native if theme.is_dark() => "native-dark", + crate::app_state::CanvasTheme::Native => "native-light", + } +} + fn output_path( widget_id: &'static str, variant_name: &'static str, - canvas_theme: crate::app_state::CanvasTheme, + theme_label: &'static str, pass: teksilo_preview::PreviewPass, ) -> Result { let mut out_dir = home_dir().ok_or_else(|| "couldn't resolve home directory".to_string())?; out_dir.push(".teksilo-previewer"); out_dir.push("exports"); std::fs::create_dir_all(&out_dir).map_err(|e| format!("create_dir_all: {}", e))?; - // Resolve the chosen theme to its concrete light/dark identity - // for the filename. `Native` resolves at click-time via - // `theme()`; we mirror that resolution here so an export taken - // while "Native" is selected still gets a meaningful suffix - // ("native-light" / "native-dark") rather than a bare "native" - // that drops information about what was actually rendered. - let theme_label: &'static str = match canvas_theme { - crate::app_state::CanvasTheme::Light => "light", - crate::app_state::CanvasTheme::Dark => "dark", - crate::app_state::CanvasTheme::Native => { - if teksilo_platform::os_theme::query_color_scheme().is_dark() { - "native-dark" - } else { - "native-light" - } - } - }; // The density rides in the stem the same way it does for the catalog // images, so a Touch export cannot silently overwrite a Compact one. Ok(out_dir.join(format!( @@ -98,3 +99,24 @@ fn home_dir() -> Option { } None } + +#[cfg(test)] +mod tests { + use super::*; + use crate::app_state::CanvasTheme; + use teksilo_core::presets::intui; + + #[test] + fn a_native_export_is_named_for_the_theme_it_rendered() { + assert_eq!( + theme_label(CanvasTheme::Native, &intui::dark()), + "native-dark" + ); + assert_eq!( + theme_label(CanvasTheme::Native, &intui::light()), + "native-light" + ); + assert_eq!(theme_label(CanvasTheme::Light, &intui::light()), "light"); + assert_eq!(theme_label(CanvasTheme::Dark, &intui::dark()), "dark"); + } +} diff --git a/crates/teksilo-scene/tests/target_conformance.rs b/crates/teksilo-scene/tests/target_conformance.rs index 149e3bb21..89f693a61 100644 --- a/crates/teksilo-scene/tests/target_conformance.rs +++ b/crates/teksilo-scene/tests/target_conformance.rs @@ -272,7 +272,7 @@ fn fixtures() -> Vec { /// /// **Int UI only**, and that is a coverage boundary rather than an oversight: a /// scene's heavyweight tier is whatever widget the app puts in it, measured by -/// the widget catalog's own gate under all four presets, and its lightweight +/// the widget catalog's own gate under all five presets, and its lightweight /// tier has no arena node for any preset to style. Naming the theme on the pin /// rather than leaving it implicit is what makes the boundary visible. const INTUI: &str = "intui.light"; diff --git a/crates/teksilo-target-conformance/Cargo.toml b/crates/teksilo-target-conformance/Cargo.toml index 2a2f55584..c12d1b0ca 100644 --- a/crates/teksilo-target-conformance/Cargo.toml +++ b/crates/teksilo-target-conformance/Cargo.toml @@ -12,7 +12,7 @@ homepage.workspace = true description = "The named fixture list the SC 2.5.8 target-conformance gates measure, and the gate for every shipped preset." # Not published: it exists to fail this workspace's build, and a published # copy would put a test scaffold on docs.rs as library API. It also -# dev-depends on all three preset crates, which a consumer of the fixture +# dev-depends on all four preset crates, which a consumer of the fixture # list has no reason to pull in. publish = false @@ -34,3 +34,4 @@ teksilo-text = { path = "../teksilo-text", version = "0.19.0", default-features teksilo-theme-macos = { workspace = true } teksilo-theme-fluent = { workspace = true } teksilo-theme-material3 = { workspace = true } +teksilo-theme-kde = { workspace = true } diff --git a/crates/teksilo-target-conformance/src/lib.rs b/crates/teksilo-target-conformance/src/lib.rs index 8ccb29790..332cd253c 100644 --- a/crates/teksilo-target-conformance/src/lib.rs +++ b/crates/teksilo-target-conformance/src/lib.rs @@ -45,8 +45,8 @@ //! preset lives *above* teksilo-widgets in the crate graph, so no test binary //! inside teksilo-widgets can install one; and four copies of one list is how //! two of them drift. So the list, the allow-list and every gate live here, in -//! an unpublished crate that dev-depends on all three presets — and nothing in -//! a published manifest changes at all. +//! an unpublished crate that dev-depends on all four sibling presets, and +//! nothing in a published manifest changes at all. //! //! **What that costs, recorded rather than papered over:** `cargo test -p //! teksilo-widgets` no longer catches an undersized control. `cargo test @@ -58,7 +58,7 @@ //! # A widget author can redden three other crates' tests //! //! That is the package, not an accident: a control that falls under the floor -//! under macOS, Fluent or Material 3 is a failure whoever last touched it. The +//! under macOS, Fluent, Material 3 or KDE is a failure whoever last touched it. The //! correct response is an [`AllowedViolation`] naming the preset's owner, never //! deleting the fixture. //! diff --git a/crates/teksilo-target-conformance/tests/conformance.rs b/crates/teksilo-target-conformance/tests/conformance.rs index 1243ebd5b..6332461d4 100644 --- a/crates/teksilo-target-conformance/tests/conformance.rs +++ b/crates/teksilo-target-conformance/tests/conformance.rs @@ -42,17 +42,24 @@ const ABOVE_COMPACT: &[TargetDensity] = &[TargetDensity::Comfortable, TargetDens /// The presets the gate measures, one **family representative** each. /// -/// Four ids, not eight: light and dark are geometrically identical in every +/// Five ids, not ten: light and dark are geometrically identical in every /// shipped preset — a claim [`light_and_dark_are_the_same_geometry_in_every_preset`] /// turns from an assumption into an assertion, and which halves the sweep. const INTUI: &str = "intui.light"; const MACOS: &str = "macos.light"; const FLUENT: &str = "fluent.light"; const M3: &str = "material3.light"; +const KDE: &str = "kde.light"; /// Every theme id in the roster — what a pin writes when a geometry is /// preset-independent, which most of them are. -const ALL_THEMES: &[&str] = &[INTUI, MACOS, FLUENT, M3]; +const ALL_THEMES: &[&str] = &[INTUI, MACOS, FLUENT, M3, KDE]; + +/// The presets whose `SpinBox` comes out at the shipped recipe's geometry. KDE +/// is not one of them: its Breeze spin box is a line-edit frame with its own +/// arrow column, so its halves are pinned on their own and its field clears +/// the floor. +const RECIPE_SPIN_BOX: &[&str] = &[INTUI, MACOS, FLUENT, M3]; static ROSTER: gate::Roster = gate::Roster { themes: &[ @@ -72,6 +79,10 @@ static ROSTER: gate::Roster = gate::Roster { id: M3, theme: teksilo_theme_material3::light, }, + gate::ThemeSubject { + id: KDE, + theme: teksilo_theme_kde::light, + }, ], allow: ALLOW_LIST, }; @@ -79,7 +90,7 @@ static ROSTER: gate::Roster = gate::Roster { /// Every conformance failure the fixture list produces under every preset, /// computed **once** per test binary. /// -/// The sweep is four presets x three densities x the whole fixture list, so a +/// The sweep is five presets x three densities x the whole fixture list, so a /// gate that recomputed it per test would pay for it four times over. The memo /// is a `OnceLock` rather than a `lazy_static`-style cell because the tests run /// as threads in one process and each of them wants the same answer. @@ -115,12 +126,29 @@ fn census() -> &'static [TargetViolation] { const ALLOW_LIST: &[AllowedViolation] = &[ AllowedViolation { path: "StepButton", - measured: &[PinnedGeometry { - densities: ALL_DENSITIES, - themes: ALL_THEMES, - paints: (Is(18.0), Is(13.0)), - reaches: (Is(18.0), Is(13.0)), - }], + measured: &[ + PinnedGeometry { + densities: ALL_DENSITIES, + themes: RECIPE_SPIN_BOX, + paints: (Is(18.0), Is(13.0)), + reaches: (Is(18.0), Is(13.0)), + }, + // KDE: Breeze's own halves, the same at every density. + PinnedGeometry { + densities: ALL_DENSITIES, + themes: &[KDE], + paints: (Is(20.0), Is(10.0)), + reaches: (Is(20.0), Is(10.0)), + }, + // KDE, two halves of the ColorPicker's spin boxes at Comfortable: + // the miss-only pass widens them, and the height stays 10. + PinnedGeometry { + densities: AT_COMFORTABLE, + themes: &[KDE], + paints: (Is(20.0), Is(10.0)), + reaches: (Is(28.0), Is(10.0)), + }, + ], owner: Owner::Named("whoever revisits SpinBox's step geometry"), exception: Some("Equivalent"), why: "A SpinBox's two step buttons are stacked halves of the field's \ @@ -135,7 +163,13 @@ const ALLOW_LIST: &[AllowedViolation] = &[ partition_targets as this row's mechanism, which cannot be right \ -- the step buttons are their own nodes beside their own sibling, \ the same error P25 corrected for the SplitButton chevron; that \ - row is corrected.", + row is corrected. Under KDE the halves are Breeze's own: an arrow \ + column 20 dp wide at the field's trailing edge and at most 20 dp \ + tall whatever the field's height, split into two 10 dp halves \ + with no button chrome, at every density. They reach their paint, \ + except two of the ColorPicker's at Comfortable, which the \ + miss-only pass widens to 28 dp and leaves 10 dp tall; the same \ + *Equivalent* stands for them.", }, AllowedViolation { path: "ColorSwatch", @@ -196,7 +230,7 @@ const ALLOW_LIST: &[AllowedViolation] = &[ path: "Expand > Padding > TextInputField", measured: &[PinnedGeometry { densities: AT_COMPACT, - themes: ALL_THEMES, + themes: RECIPE_SPIN_BOX, paints: (ClearsFloor, Is(20.0)), reaches: (ClearsFloor, Is(20.0)), }], @@ -216,7 +250,8 @@ const ALLOW_LIST: &[AllowedViolation] = &[ inset from. A real AA shortfall with no exception behind it -- \ the field IS the target for placing a caret, and no conforming \ control does that job. The remedy is a taller field at Compact, \ - which the invariant forbids.", + which the invariant forbids. Not under KDE, whose Breeze spin box \ + is 32 dp tall at Compact and whose field clears the floor.", }, AllowedViolation { // Scoped to the fixture, deliberately: the POPULATED field's clear slot @@ -225,7 +260,7 @@ const ALLOW_LIST: &[AllowedViolation] = &[ // signature `an_outsets_claim_survives_the_slop_pass_in_the_shipped_controls` // exists to refuse — that test runs under Int UI alone, so an unscoped // entry would pre-excuse the populated slot's regression under the - // other three presets. + // other four presets. path: "search_field/empty:", measured: &[PinnedGeometry { densities: AT_COMPACT, @@ -269,10 +304,10 @@ const ALLOW_LIST: &[AllowedViolation] = &[ // not literal equality. The walk reports an extent up to two // refinement quanta (0.031 dp each) short of itself, and these rows // land there: 15.96875 under every theme and density except Fluent - // at Touch, where it is 15.9375. `PIN_TOLERANCE` is three quanta - // wide for exactly this, and `PROBE_EPSILON` is why the Fluent Touch - // four are judged at all rather than filed as shadowed by something - // on top of them. + // at Touch and KDE at Comfortable and Touch, where it is 15.9375. + // `PIN_TOLERANCE` is three quanta wide for exactly this, and + // `PROBE_EPSILON` is why those twelve are judged at all rather than + // filed as shadowed by something on top of them. PinnedGeometry { densities: ALL_DENSITIES, themes: ALL_THEMES, @@ -322,7 +357,7 @@ const ALLOW_LIST: &[AllowedViolation] = &[ densities: AT_COMFORTABLE, themes: ALL_THEMES, paints: (Is(12.0), Is(12.0)), - reaches: (Is(22.0), Is(28.0)), + reaches: (Is(22.0), Is(32.0)), }, ], owner: Owner::Named( @@ -515,7 +550,7 @@ const ALLOW_LIST: &[AllowedViolation] = &[ reaches: (ClearsFloor, Is(22.0)), }, // Material 3: the stock recipe under M3 typography, whose nominal - // line box is the widest of the four. + // line box is the widest of the five. PinnedGeometry { densities: AT_COMPACT, themes: &[M3], @@ -577,7 +612,7 @@ const ALLOW_LIST: &[AllowedViolation] = &[ It is NOT a Material 3 defect. The identical geometry is present \ under every preset -- Int UI's button in this fixture reaches \ 44.7 x 24.0 of its own 124 x 44, Fluent's 44.8 x 24.0 of 116 x \ - 44 -- and the audit files those two as \ + 44, KDE's 44.9 x 24.0 of 109 x 44 -- and the audit files those as \ `ShadowedByAnotherTarget` rather than judging them, because a \ target whose reach falls short of its own paint is limited by \ another target rather than by its size. Material 3 is the preset \ @@ -585,7 +620,7 @@ const ALLOW_LIST: &[AllowedViolation] = &[ both the ring's reach and the button's centre, and the centre \ ends up inside the ring, which is the point at which the audit \ stops calling it shadowed and starts calling it unreachable. So \ - the gate reports one preset and the defect belongs to all four. \ + the gate reports one preset and the defect belongs to all five. \ No SC 2.5.8 exception is claimed: a button a finger cannot press \ has no equivalent.", }, @@ -610,8 +645,8 @@ const ALLOW_LIST: &[AllowedViolation] = &[ ends at 11.2 dp. This is NOT the fixture's viewport, which is the \ first thing it looks like -- the same 11.2 dp is measured at 440, \ 480 and 520 dp, because none of that width reaches the calendar. \ - Under Int UI and Fluent the same node is 27.2 dp, clearing the \ - floor by 3.2, which is how close the other three presets are to \ + Under Int UI, Fluent and KDE the same node is 27.2 dp, clearing \ + the floor by 3.2, which is how close the other presets are to \ the same failure. Neither hit mechanism helps: the arrows beside \ it are targets of their own kind, so the miss-only pass is denied \ at distance zero. The remedies are all header-layout decisions -- \ @@ -619,6 +654,41 @@ const ALLOW_LIST: &[AllowedViolation] = &[ two-row header, or arrows that do not follow the target ladder -- \ and each of them changes what a calendar looks like.", }, + AllowedViolation { + path: "TableView > ScrollBar", + measured: &[ + PinnedGeometry { + densities: AT_COMPACT, + themes: &[MACOS], + paints: (Is(12.0), Is(11.5)), + reaches: (Is(31.0), Is(11.5)), + }, + PinnedGeometry { + densities: AT_COMPACT, + themes: &[KDE], + paints: (Is(12.0), Is(16.875)), + reaches: (Is(31.0), Is(16.875)), + }, + ], + owner: Owner::Named("whoever revisits ScrollBar's track regions"), + exception: Some("Equivalent"), + why: "The part of a table's vertical scroll-bar track below the thumb, \ + which `ScrollBar::target_regions` reports as a paging target. It \ + is as tall as the content overflow leaves it: the fixture's twelve \ + rows at macOS's 24 dp `NSTableView` row height overflow the 276 dp \ + viewport by 12 dp, so the thumb fills all but 11.5 dp of the track \ + at the top of the range. KDE's rows are 24 dp as well and its \ + 30 dp header leaves a 270 dp viewport, which they overflow by \ + 18 dp: 16.875 dp of track. Under the other presets the same twelve \ + rows are taller and the remainder clears the floor, which is all \ + that separates them; any scroll bar whose content barely \ + overflows, or whose thumb sits near an end of its range, leaves a \ + remainder this short. The width earns its ring, the height cannot, \ + because the thumb is flush above it. WCAG 2.2 SC 2.5.8 \ + *Equivalent*: the page a tap on the track scrolls is reached by \ + dragging the thumb, which reaches far past the floor, by the \ + wheel, and by PageDown on the focused table.", + }, ]; #[test] @@ -697,12 +767,12 @@ fn the_allow_lists_roster_is_what_it_says_it_is() { let findings = gate::roster_defects(census(), &ROSTER); assert!(findings.is_empty(), "{}", findings.join("\n")); - assert_eq!(ALLOW_LIST.len(), 14, "fourteen written findings"); + assert_eq!(ALLOW_LIST.len(), 15, "fifteen written findings"); let on_an_exception = ALLOW_LIST.iter().filter(|e| e.exception.is_some()).count(); assert_eq!( on_an_exception, - 5, - "five rest on an SC 2.5.8 exception: {:?}", + 6, + "six rest on an SC 2.5.8 exception: {:?}", ALLOW_LIST .iter() .filter(|e| e.exception.is_some()) @@ -725,12 +795,12 @@ fn the_allow_lists_roster_is_what_it_says_it_is() { ); } -/// **The roster's four ids stand for eight.** Light and dark are the same +/// **The roster's five ids stand for ten.** Light and dark are the same /// geometry in every shipped preset. /// /// This is a **guard**, not a discriminator: it exists to turn the assumption /// that halves the sweep into an assertion. If it ever reddens, the answer is -/// not to widen a tolerance — it is that the gate must run all eight ids, and +/// not to widen a tolerance: it is that the gate must run all ten ids, and /// every pin in the list has to be re-read against the appearance it was /// measured under. /// @@ -757,6 +827,10 @@ fn light_and_dark_are_the_same_geometry_in_every_preset() { id: "material3.dark", theme: teksilo_theme_material3::dark, }, + gate::ThemeSubject { + id: "kde.dark", + theme: teksilo_theme_kde::dark, + }, ], allow: &[], }; @@ -853,16 +927,17 @@ fn no_entry_names_a_theme_owned_wrapper_in_its_path() { /// really produces it — so the assertion is not a restatement of the matcher. #[test] fn the_allow_list_excuses_only_the_geometry_it_pinned() { - // Per preset: four size seeds and three theme seeds for each covered - // violation — so seven times that preset's own census, and the figure below - // is the smallest of the four **exactly**. Exactly, not comfortably under: - // this number is the only thing standing between "every geometry in the list - // was seeded against" and "the census quietly collapsed and the seeding - // proved nothing", and a slack figure buys a silent collapse the room to - // hide in. It reddens when the census shrinks — re-measure with `zz_census` - // (the CENSUS-TOTAL lines), take the smallest preset's total, and multiply - // by seven. - let findings = gate::non_narrowing(census(), &ROSTER, 7 * 116); + // Per preset: four size seeds, and one theme seed per other preset in the + // roster, for each covered violation. So the floor is that many times the + // preset's own census, and the figure below is the smallest preset's + // **exactly**. Exactly, not comfortably under: this number is the only + // thing standing between "every geometry in the list was seeded against" + // and "the census quietly collapsed and the seeding proved nothing", and a + // slack figure buys a silent collapse the room to hide in. It reddens when + // the census shrinks: re-measure with `zz_census` (the CENSUS-TOTAL lines) + // and take the smallest preset's total, KDE's 107 today. + let seeds_per_violation = 4 + (ROSTER.themes.len() - 1); + let findings = gate::non_narrowing(census(), &ROSTER, seeds_per_violation * 107); assert!(findings.is_empty(), "{}", findings.join("\n")); // And the seed floor above is a floor: it cannot see a census that GREW, @@ -875,7 +950,7 @@ fn the_allow_list_excuses_only_the_geometry_it_pinned() { // region the new rows fall into. assert_eq!( census().len(), - 481, + 584, "the (theme x density) census moved — this test is proving something \ else until the figure, the per-preset seed floor above, and the \ entries covering any new rows are re-derived together", @@ -1062,7 +1137,7 @@ fn a_deliberately_undersized_fixture_fails() { } // The second half. The same fixture, through the gate's own path: the - // census it computes over all four presets, and the matcher it hands that + // census it computes over all five presets, and the matcher it hands that // census to. Nothing in the real allow-list names this fixture, so every // row of it must come back out. let reported = gate::reported_failures(&undersized, &ROSTER); @@ -1114,8 +1189,8 @@ fn an_app_installed_style_slot_is_reported() { // And the same under every preset in the roster, which is the leg that used // to be Int UI's alone: the function early-returned empty for any theme - // carrying a `DensityProjection`, and all three sibling presets carry one, - // so an app style installed on top of macOS, Fluent or Material 3 was + // carrying a `DensityProjection`, and all four sibling presets carry one, + // so an app style installed on top of macOS, Fluent, Material 3 or KDE was // reported as following a ladder it does not follow. `grid_view` is the // probe because no shipped preset installs it — a slot a preset DOES own is // overwritten by that preset's own projection at the next density change, @@ -1660,6 +1735,14 @@ fn the_allow_lists_discriminators_narrow_by_size_and_by_density() { /// because `up_to` 44 turns a 28 dp table row into a candidate that a 24 dp /// `up_to` left inert. A test that measured only one of them would report half /// the mechanism. +/// +/// The table half builds its own fixture with an app-chosen +/// `row_height(28.0)` and `header_height(32.0)`, the geometry every table had +/// before its heights came from its style. The shared `table_view` fixture +/// takes them from the active `TableStyle`, which every shipped preset raises +/// to 44 dp at Touch, and there the bar reaches 32 dp with and without the +/// mechanism (measured), so it would pass the mechanism's deletion. An app +/// that sets its own 28 dp rows is the configuration the mechanism protects. #[test] fn an_outsets_claim_survives_the_slop_pass_in_the_shipped_controls() { let clear = measurements_of("search_field", "HitTarget", TargetDensity::Compact); @@ -1679,7 +1762,27 @@ fn an_outsets_claim_survives_the_slop_pass_in_the_shipped_controls() { assert!(clear[0].sources.outset, "and the outset must be credited"); assert_eq!(clear[0].rule, None, "so it carries no verdict"); - let bar = measurements_of("table_view", "ScrollBar", TargetDensity::Touch); + let rows_28 = TargetFixture::sized("table_view_28dp_rows", 500.0, 300.0, |t| { + let model = teksilo_data::ListModel::from_vec( + (0..12) + .map(|i| format!("Item {i}")) + .collect::>(), + ); + t.add( + TableView::new(model) + .add_column(teksilo_widgets::table_view::Column::new( + "name", + lit!("Name"), + |item: &String, _cx| Box::new(TextWidget::new(lit!(item.clone()))), + )) + .row_height(28.0) + .header_height(32.0), + ) + }); + let bar: Vec<_> = measure_fixtures(&[rows_28], TargetDensity::Touch) + .into_iter() + .filter(|m| measured_leaf(&m.path) == "ScrollBar") + .collect(); let node = bar .iter() .find(|m| m.part.is_none() && m.size.width > 0.0) diff --git a/crates/teksilo-text/src/embedded_fonts.rs b/crates/teksilo-text/src/embedded_fonts.rs index e8c186b22..d65364137 100644 --- a/crates/teksilo-text/src/embedded_fonts.rs +++ b/crates/teksilo-text/src/embedded_fonts.rs @@ -11,16 +11,52 @@ //! them. //! //! [`TypesetterBridge`]: crate::TypesetterBridge +//! +//! Every bundled face is also held here as one `static`, and every other site in the crate +//! names that `static` instead of writing its own `include_bytes!`. Each `include_bytes!` is a +//! separate allocation, and only link-time optimisation merges identical ones, so a build +//! without LTO (a debug build, or an application with a default release profile) carried each +//! default face once per site. There were three sites, so 4.8 MB of Inter and JetBrains Mono +//! was duplicate. /// The bytes of the bundled Noto Sans Arabic variable font — the RTL face used for Arabic, /// Persian, Urdu and related scripts. #[cfg(feature = "fonts-arabic")] pub fn noto_sans_arabic_bytes() -> &'static [u8] { - include_bytes!("../fonts/NotoSansArabic-VariableFont_wdth,wght.ttf") + NOTO_SANS_ARABIC } /// The bytes of the bundled Noto Sans Hebrew variable font. #[cfg(feature = "fonts-hebrew")] pub fn noto_sans_hebrew_bytes() -> &'static [u8] { - include_bytes!("../fonts/NotoSansHebrew-VariableFont_wdth,wght.ttf") + NOTO_SANS_HEBREW } + +// The default UI and monospace families, always embedded. +pub(crate) static INTER: &[u8] = include_bytes!("../fonts/InterVariable.ttf"); +pub(crate) static INTER_ITALIC: &[u8] = include_bytes!("../fonts/InterVariable-Italic.ttf"); +pub(crate) static JETBRAINS_MONO: &[u8] = include_bytes!("../fonts/JetBrainsMono.ttf"); +pub(crate) static JETBRAINS_MONO_ITALIC: &[u8] = + include_bytes!("../fonts/JetBrainsMono-Italic.ttf"); + +// Script fallbacks. Arabic and Hebrew follow their feature, as the public accessors above do +// (both files are committed); the others follow the cfg `build.rs` sets only when the file +// is present, so a missing face stays a build warning. +#[cfg(feature = "fonts-arabic")] +pub(crate) static NOTO_SANS_ARABIC: &[u8] = + include_bytes!("../fonts/NotoSansArabic-VariableFont_wdth,wght.ttf"); +#[cfg(feature = "fonts-hebrew")] +pub(crate) static NOTO_SANS_HEBREW: &[u8] = + include_bytes!("../fonts/NotoSansHebrew-VariableFont_wdth,wght.ttf"); +#[cfg(font_thai)] +pub(crate) static NOTO_SANS_THAI: &[u8] = + include_bytes!("../fonts/NotoSansThai-VariableFont_wdth,wght.ttf"); +#[cfg(font_devanagari)] +pub(crate) static NOTO_SANS_DEVANAGARI: &[u8] = + include_bytes!("../fonts/NotoSansDevanagari-VariableFont_wdth,wght.ttf"); +#[cfg(font_cjk_sc)] +pub(crate) static NOTO_SANS_SC: &[u8] = include_bytes!("../fonts/NotoSansSC-VariableFont_wght.ttf"); +#[cfg(font_cjk_jp)] +pub(crate) static NOTO_SANS_JP: &[u8] = include_bytes!("../fonts/NotoSansJP-VariableFont_wght.ttf"); +#[cfg(font_cjk_kr)] +pub(crate) static NOTO_SANS_KR: &[u8] = include_bytes!("../fonts/NotoSansKR-VariableFont_wght.ttf"); diff --git a/crates/teksilo-text/src/font_registrar.rs b/crates/teksilo-text/src/font_registrar.rs index f8641d212..f9538d96c 100644 --- a/crates/teksilo-text/src/font_registrar.rs +++ b/crates/teksilo-text/src/font_registrar.rs @@ -90,20 +90,20 @@ pub struct EmbeddedInterRegistrar { impl EmbeddedInterRegistrar { pub const fn new() -> Self { Self { - data: include_bytes!("../fonts/InterVariable.ttf"), - data_italic: include_bytes!("../fonts/InterVariable-Italic.ttf"), - mono_data: include_bytes!("../fonts/JetBrainsMono.ttf"), - mono_data_italic: include_bytes!("../fonts/JetBrainsMono-Italic.ttf"), + data: crate::embedded_fonts::INTER, + data_italic: crate::embedded_fonts::INTER_ITALIC, + mono_data: crate::embedded_fonts::JETBRAINS_MONO, + mono_data_italic: crate::embedded_fonts::JETBRAINS_MONO_ITALIC, default_size_px: 14.0, } } pub const fn with_size(size_px: f32) -> Self { Self { - data: include_bytes!("../fonts/InterVariable.ttf"), - data_italic: include_bytes!("../fonts/InterVariable-Italic.ttf"), - mono_data: include_bytes!("../fonts/JetBrainsMono.ttf"), - mono_data_italic: include_bytes!("../fonts/JetBrainsMono-Italic.ttf"), + data: crate::embedded_fonts::INTER, + data_italic: crate::embedded_fonts::INTER_ITALIC, + mono_data: crate::embedded_fonts::JETBRAINS_MONO, + mono_data_italic: crate::embedded_fonts::JETBRAINS_MONO_ITALIC, default_size_px: size_px, } } diff --git a/crates/teksilo-text/src/typesetter_bridge.rs b/crates/teksilo-text/src/typesetter_bridge.rs index 46e325939..dd55fd6f3 100644 --- a/crates/teksilo-text/src/typesetter_bridge.rs +++ b/crates/teksilo-text/src/typesetter_bridge.rs @@ -10,6 +10,7 @@ use teksilo_canvas::text_backend::{ }; use teksilo_tokens::TextStyle; +use crate::embedded_fonts; use crate::font_registrar::shared_static; use text_typeset::atlas::cache::GlyphCacheKey; use text_typeset::{ @@ -267,11 +268,11 @@ impl TypesetterBridge { // face as the default, then the italic variable face so italic // UI text shapes from real italic outlines instead of falling // back to the upright (an italic request would otherwise miss). - let inter_data = include_bytes!("../fonts/InterVariable.ttf"); + let inter_data = embedded_fonts::INTER; let face_id = self.service.register_font_shared(shared_static(inter_data)); self.service.set_default_font(face_id, 14.0); self.default_font = Some(face_id); - let inter_italic = include_bytes!("../fonts/InterVariable-Italic.ttf"); + let inter_italic = embedded_fonts::INTER_ITALIC; let _ = self .service .register_font_shared(shared_static(inter_italic)); @@ -290,46 +291,46 @@ impl TypesetterBridge { // upright and italic variable faces (both under the family name // "JetBrains Mono"), so the theme's request — upright or italic — // resolves directly. - let mono_data = include_bytes!("../fonts/JetBrainsMono.ttf"); + let mono_data = embedded_fonts::JETBRAINS_MONO; let _ = self.service.register_font_shared(shared_static(mono_data)); - let mono_italic = include_bytes!("../fonts/JetBrainsMono-Italic.ttf"); + let mono_italic = embedded_fonts::JETBRAINS_MONO_ITALIC; let _ = self .service .register_font_shared(shared_static(mono_italic)); #[cfg(font_arabic)] { - let data = include_bytes!("../fonts/NotoSansArabic-VariableFont_wdth,wght.ttf"); + let data = embedded_fonts::NOTO_SANS_ARABIC; let _ = self.service.register_font_shared(shared_static(data)); } #[cfg(font_hebrew)] { - let data = include_bytes!("../fonts/NotoSansHebrew-VariableFont_wdth,wght.ttf"); + let data = embedded_fonts::NOTO_SANS_HEBREW; let _ = self.service.register_font_shared(shared_static(data)); } #[cfg(font_thai)] { - let data = include_bytes!("../fonts/NotoSansThai-VariableFont_wdth,wght.ttf"); + let data = embedded_fonts::NOTO_SANS_THAI; let _ = self.service.register_font_shared(shared_static(data)); } #[cfg(font_devanagari)] { - let data = include_bytes!("../fonts/NotoSansDevanagari-VariableFont_wdth,wght.ttf"); + let data = embedded_fonts::NOTO_SANS_DEVANAGARI; let _ = self.service.register_font_shared(shared_static(data)); } #[cfg(font_cjk_sc)] { - let data = include_bytes!("../fonts/NotoSansSC-VariableFont_wght.ttf"); + let data = embedded_fonts::NOTO_SANS_SC; let _ = self.service.register_font_shared(shared_static(data)); } #[cfg(font_cjk_jp)] { - let data = include_bytes!("../fonts/NotoSansJP-VariableFont_wght.ttf"); + let data = embedded_fonts::NOTO_SANS_JP; let _ = self.service.register_font_shared(shared_static(data)); } #[cfg(font_cjk_kr)] { - let data = include_bytes!("../fonts/NotoSansKR-VariableFont_wght.ttf"); + let data = embedded_fonts::NOTO_SANS_KR; let _ = self.service.register_font_shared(shared_static(data)); } diff --git a/crates/teksilo-theme-fluent/Cargo.toml b/crates/teksilo-theme-fluent/Cargo.toml index e9f418d2b..c6444f786 100644 --- a/crates/teksilo-theme-fluent/Cargo.toml +++ b/crates/teksilo-theme-fluent/Cargo.toml @@ -41,6 +41,12 @@ teksilo-widgets = { workspace = true } # `tests/mount.rs` builds a real `WidgetTree` per widget; `lit!` supplies # the untranslated labels those constructors take. teksilo-i18n = { workspace = true } +# `tests/preset_metrics.rs` reuses the conformance harness's `in_tappable_row`, +# the row geometry that denies the miss-only slop pass, instead of keeping a +# copy that could drift from the gate's. Path-only (no version): the crate is +# `publish = false`, and cargo strips versionless path dev-dependencies at +# publish time, so this never reaches a published manifest. +teksilo-target-conformance = { path = "../teksilo-target-conformance" } # `tests/preset_metrics.rs` mounts the data views this preset restyles, and # those take a `ListModel` rather than a plain slice. teksilo-data = { workspace = true } diff --git a/crates/teksilo-theme-fluent/src/lib.rs b/crates/teksilo-theme-fluent/src/lib.rs index 8085ef017..1bc3991ea 100644 --- a/crates/teksilo-theme-fluent/src/lib.rs +++ b/crates/teksilo-theme-fluent/src/lib.rs @@ -203,7 +203,9 @@ fn install_styles(theme: &mut Theme) { slots.radio = Some(Rc::new(styles::radio::FluentRadioStyle)); slots.text_input = Some(Rc::new(styles::text_input::FluentTextInputStyle)); slots.slider = Some(Rc::new(styles::slider::FluentSliderStyle)); - slots.menu_item = Some(Rc::new(styles::menu_item::FluentMenuItemStyle)); + slots.menu_item = Some(Rc::new(styles::menu_item::FluentMenuItemStyle::for_tokens( + &tokens, + ))); slots.standard_item = Some(Rc::new(styles::standard_item::FluentStandardItemStyle)); // Fluent metrics over the shipped composition. @@ -240,6 +242,8 @@ fn install_styles(theme: &mut Theme) { mod tests { use super::*; use crate::palette::argb; + use std::collections::BTreeSet; + use teksilo_core::styles::ComponentStyleSlots; #[test] fn light_reports_light_appearance() { @@ -329,39 +333,111 @@ mod tests { assert_eq!(d.control_fill_input_active, argb("#B31E1E1E")); } + /// The slots Fluent deliberately leaves to the widget's default + /// `Recipe*Style`, each with the reason it is inherited rather than filled. + const FLUENT_UNFILLED: &[(&str, &str)] = &[ + ( + "split_button", + "WinUI SplitButton is not transcribed; the recipe frame paints from the theme surface and border roles", + ), + ( + "splitter", + "WinUI ships no splitter; the recipe's thin resting divider reads the theme border role", + ), + ( + "radio_tile", + "WinUI ships no selectable card; the recipe surface reads the preset's surface and accent roles", + ), + ( + "chart", + "WinUI ships no chart; the teksilo-charts default reads the series colours and the theme border role", + ), + ( + "color_picker", + "WinUI ColorPicker is not transcribed; the recipe panel reads the theme roles", + ), + ( + "spin_box", + "WinUI NumberBox spin buttons are not transcribed; the recipe frames the widget's field and step buttons in theme roles", + ), + ( + "date_edit", + "the recipe is a passthrough around the TextInput, which the text_input slot already styles", + ), + ( + "rich_text_editor", + "the recipe frames the viewport in theme border and surface roles; the text is document content", + ), + ( + "list_container", + "the recipe is only the drag-insertion line, drawn in the accent role this preset maps", + ), + ( + "drop_zone", + "WinUI ships no drop-zone control; the recipe tints its region from the theme roles", + ), + ( + "drop_target", + "WinUI ships no drop-target control; the recipe border reads the theme roles and its hints are cards, which the card slot styles", + ), + ( + "grid_view", + "the recipe's focus ring, marquee and insertion bar use the focused and accent roles this preset maps", + ), + ( + "web_view", + "the page paints itself; the recipe only tints the overlay shown over it, from theme roles", + ), + ( + "text_selection", + "its geometry is teksilo_core::text_touch's, shared with hit testing, and its colours follow the accent role", + ), + ( + "calendar", + "WinUI CalendarView is not transcribed; the recipe's day cells read the accent and selection roles this preset maps", + ), + ( + "search_field", + "its field is a TextInput, which resolves the text_input slot, and its suggestion popup resolves the popover slot, both filled", + ), + ( + "avatar", + "WinUI PersonPicture is not transcribed; the recipe's initials disc and focus ring read the theme roles", + ), + ]; + + /// Whether the installed slots and [`FLUENT_UNFILLED`] are exactly + /// [`ComponentStyleSlots::NAMES`], with no slot in both: a slot added to the + /// framework must be either filled by this preset or listed with a reason. #[test] - fn every_installed_style_slot_is_populated() { - for t in [light(), dark()] { - let s = &t.style_slots; - for (name, present) in [ - ("button", s.button.is_some()), - ("toggle", s.toggle.is_some()), - ("checkbox", s.checkbox.is_some()), - ("radio", s.radio.is_some()), - ("text_input", s.text_input.is_some()), - ("slider", s.slider.is_some()), - ("menu_item", s.menu_item.is_some()), - ("standard_item", s.standard_item.is_some()), - ("card", s.card.is_some()), - ("panel", s.panel.is_some()), - ("popover", s.popover.is_some()), - ("tooltip", s.tooltip.is_some()), - ("dialog", s.dialog.is_some()), - ("snackbar", s.snackbar.is_some()), - ("toast", s.toast.is_some()), - ("banner", s.banner.is_some()), - ("combo_box", s.combo_box.is_some()), - ("icon_button", s.icon_button.is_some()), - ("link", s.link.is_some()), - ("segmented_control", s.segmented_control.is_some()), - ("badge", s.badge.is_some()), - ("progress_bar", s.progress_bar.is_some()), - ("scroll_bar", s.scroll_bar.is_some()), - ("tab", s.tab.is_some()), - ("table", s.table.is_some()), - ] { - assert!(present, "{name} slot not installed"); - } + fn installed_and_unfilled_slots_partition_the_roster() { + let unfilled: BTreeSet<&str> = FLUENT_UNFILLED.iter().map(|(name, _)| *name).collect(); + assert_eq!( + unfilled.len(), + FLUENT_UNFILLED.len(), + "a slot is listed twice" + ); + for (name, reason) in FLUENT_UNFILLED { + assert!( + !reason.trim().is_empty(), + "{name} is listed without a reason" + ); + } + let names: BTreeSet<&str> = ComponentStyleSlots::NAMES.iter().copied().collect(); + for t in [light(), dark(), light().with_density(TargetDensity::Touch)] { + let installed: BTreeSet<&str> = t.style_slots.installed().into_iter().collect(); + let both: Vec<_> = installed.intersection(&unfilled).collect(); + assert!( + both.is_empty(), + "installed yet listed as unfilled: {both:?}" + ); + let covered: BTreeSet<&str> = installed.union(&unfilled).copied().collect(); + let missing: Vec<_> = names.difference(&covered).collect(); + let unknown: Vec<_> = covered.difference(&names).collect(); + assert!( + missing.is_empty() && unknown.is_empty(), + "neither installed nor listed: {missing:?}; not a slot: {unknown:?}" + ); } } @@ -455,4 +531,46 @@ mod tests { assert_eq!(touch.colors, base.colors); assert!(touch.extension::().is_some()); } + + /// The menu slot is rebuilt for the density too, so the metrics + /// `MenuItem`, `MenuSeparator` and `MenuList` read follow it, and + /// switching back to Compact gives the Compact metrics again. + #[test] + fn a_density_switch_reprojects_the_menu_metrics() { + use teksilo_core::styles::MenuItemStyle; + + let base = light(); + let metrics = |t: &Theme| { + t.style_slots + .menu_item + .as_ref() + .expect("slot installed") + .metrics() + }; + for density in [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ] { + let t = base.with_density(density); + assert_eq!( + metrics(&t), + styles::menu_item::FluentMenuItemStyle::for_tokens(&InputTokens::for_density( + density + )) + .metrics(), + "{density:?}", + ); + } + let compact = metrics(&base); + let touch = metrics(&base.with_density(TargetDensity::Touch)); + assert_eq!(compact.item_height, 37.0, "WinUI's own 37 dp row"); + assert_eq!(touch.item_height, 44.0, "the Touch target"); + assert!(touch.separator_height > compact.separator_height); + assert!(touch.trailing_column_width > compact.trailing_column_width); + let back = base + .with_density(TargetDensity::Touch) + .with_density(TargetDensity::Compact); + assert_eq!(metrics(&back), compact); + } } diff --git a/crates/teksilo-theme-fluent/src/styles/menu_item.rs b/crates/teksilo-theme-fluent/src/styles/menu_item.rs index 15f098f76..961b9ee13 100644 --- a/crates/teksilo-theme-fluent/src/styles/menu_item.rs +++ b/crates/teksilo-theme-fluent/src/styles/menu_item.rs @@ -21,6 +21,12 @@ //! delegated body is stacked *over* a Fluent background rect and the //! delegate's own tint is suppressed by giving it interaction signals that //! never fire. That keeps the slot/gap/padding arithmetic in one place. +//! +//! The style stores the recipe it lays out, built from the theme's input +//! tokens ([`FluentMenuItemStyle::for_tokens`]), so +//! [`MenuItemStyle::metrics`], which `MenuItem` and `MenuSeparator` read for +//! the parts they build themselves and `MenuList` reads to cap its height, +//! follows the density the theme was projected to, as the painted row does. use teksilo_core::build_context::BuildContext; use teksilo_core::signal::Signal; @@ -72,8 +78,33 @@ pub fn fluent_menu_item_recipe_for(tokens: &InputTokens) -> MenuItemRecipe { } /// Fluent `MenuItemStyle`. -#[derive(Debug, Default, Clone, Copy)] -pub struct FluentMenuItemStyle; +/// +/// [`Default`] is the Compact density; the preset installs +/// [`FluentMenuItemStyle::for_tokens`] of the theme it builds, and builds it +/// again when the theme is projected to another density. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FluentMenuItemStyle { + /// The row dimensions this style lays out and reports. + pub recipe: MenuItemRecipe, +} + +impl FluentMenuItemStyle { + /// The style with `recipe`'s dimensions. + pub fn new(recipe: MenuItemRecipe) -> Self { + Self { recipe } + } + + /// The style at the density of `tokens`: [`fluent_menu_item_recipe_for`]. + pub fn for_tokens(tokens: &InputTokens) -> Self { + Self::new(fluent_menu_item_recipe_for(tokens)) + } +} + +impl Default for FluentMenuItemStyle { + fn default() -> Self { + Self::new(fluent_menu_item_recipe()) + } +} impl MenuItemStyle for FluentMenuItemStyle { fn make_body(&self, cfg: &MenuItemStyleConfig, ctx: &mut BuildContext) -> WidgetId { @@ -104,18 +135,19 @@ impl MenuItemStyle for FluentMenuItemStyle { is_disabled: cfg.is_disabled.clone(), is_highlighted: quiet, }; - let row = RecipeMenuItemStyle::new(fluent_menu_item_recipe_for(&ctx.theme().input)) - .make_body(&inner_cfg, ctx); + let row = RecipeMenuItemStyle::new(self.recipe).make_body(&inner_cfg, ctx); ctx.add(ZStack::new().child(backdrop).child(row)) } - /// Delegate the metrics alongside the row. `MenuItem` builds the icon and - /// chevron columns before any style runs and `MenuSeparator` is a - /// different widget entirely, so without this the slots would keep the - /// IntUI sizes while the row around them used ours. + /// Delegate the metrics alongside the row, from the same recipe. + /// `MenuItem` builds the icon and chevron columns before any style runs + /// and `MenuSeparator` is a different widget entirely, so without this + /// the slots would keep the IntUI sizes while the row around them used + /// ours, and a metric read at Compact would sit in a row laid out at + /// another density. fn metrics(&self) -> MenuItemMetrics { - RecipeMenuItemStyle::new(fluent_menu_item_recipe()).metrics() + RecipeMenuItemStyle::new(self.recipe).metrics() } } @@ -166,6 +198,37 @@ mod tests { assert_eq!(r.icon_column_width + r.icon_label_gap, 28.0); } + /// The metrics `MenuItem`, `MenuSeparator` and `MenuList` read are the + /// stored recipe's, at the density it was built for: the row rises to + /// the Touch target, the gutters and the separator follow the spacing + /// factor, and Compact is the recipe this module documents. + #[test] + fn the_metrics_are_the_recipes_at_every_density() { + use teksilo_tokens::TargetDensity; + + assert_eq!( + FluentMenuItemStyle::default().recipe, + fluent_menu_item_recipe() + ); + for (density, item, separator, trailing) in [ + (TargetDensity::Compact, 37.0, 3.0, 11.0), + (TargetDensity::Comfortable, 37.0, 3.45, 12.65), + (TargetDensity::Touch, 44.0, 3.9, 14.3), + ] { + let tokens = InputTokens::for_density(density); + let style = FluentMenuItemStyle::for_tokens(&tokens); + assert_eq!(style.recipe, fluent_menu_item_recipe_for(&tokens)); + let m = style.metrics(); + assert_eq!(m.item_height, item, "{density:?}"); + assert!((m.separator_height - separator).abs() < 1e-4, "{density:?}"); + assert!( + (m.trailing_column_width - trailing).abs() < 1e-4, + "{density:?}" + ); + assert_eq!(m.icon_column_width, ICON_COLUMN, "{density:?}"); + } + } + #[test] fn rows_are_taller_than_the_intui_default() { // Fluent menus are deliberately roomy; a 24 dp row would read as a diff --git a/crates/teksilo-theme-fluent/src/styles/metrics.rs b/crates/teksilo-theme-fluent/src/styles/metrics.rs index 8ee888a20..d917db3ce 100644 --- a/crates/teksilo-theme-fluent/src/styles/metrics.rs +++ b/crates/teksilo-theme-fluent/src/styles/metrics.rs @@ -228,11 +228,11 @@ pub fn fluent_scroll_bar_style_for(tokens: &InputTokens) -> RecipeScrollBarStyle /// `TabBar` — `TabViewItemMinHeight` 32, `TabViewItemHeaderPadding` 8, and /// a 3 dp active indicator (the `NavigationViewSelectionIndicator` -/// thickness Fluent uses wherever a selection underline appears). +/// thickness Fluent uses wherever a selection underline appears). The +/// height is raised to the density's target size, as macOS's tab strip is. pub fn fluent_tab_style_for(tokens: &InputTokens) -> RecipeTabStyle { RecipeTabStyle::new(TabRecipe { - editor_height: FLUENT_CONTROL_HEIGHT, - tool_window_height: FLUENT_CONTROL_HEIGHT, + editor_height: dp(FLUENT_CONTROL_HEIGHT, TargetRole::Target, tokens), padding_horizontal: 8.0, underline_active: 3.0, close_button_size: 16.0, @@ -241,11 +241,13 @@ pub fn fluent_tab_style_for(tokens: &InputTokens) -> RecipeTabStyle { } /// `TableView` / `TreeTableView` — rows and header on the `ListViewItem` -/// 40 dp rhythm, 12 dp cell gutters, 4 dp corners. +/// 40 dp rhythm, 12 dp cell gutters, 4 dp corners. Both heights are raised +/// to the density's target size, as the list row they share a rhythm with +/// is. pub fn fluent_table_style_for(tokens: &InputTokens) -> RecipeTableStyle { RecipeTableStyle::new(TableRecipe { - row_height: 40.0, - header_height: 40.0, + row_height: dp(40.0, TargetRole::Target, tokens), + header_height: dp(40.0, TargetRole::Target, tokens), cell_padding_horizontal: 12.0, corner_radius: R_CONTROL, tree_indent_per_level: 16.0, @@ -378,5 +380,18 @@ mod tests { let t = fluent_table_style().recipe; assert_eq!(t.row_height, 40.0); assert_eq!(t.header_height, t.row_height); + for density in [ + teksilo_tokens::TargetDensity::Compact, + teksilo_tokens::TargetDensity::Comfortable, + teksilo_tokens::TargetDensity::Touch, + ] { + let tokens = InputTokens::for_density(density); + assert_eq!( + fluent_table_style_for(&tokens).recipe.row_height, + crate::styles::standard_item::fluent_standard_item_recipe_for(&tokens) + .min_height_single_line, + "{density:?}: a TableView row and a ListView row must share one rhythm" + ); + } } } diff --git a/crates/teksilo-theme-fluent/src/styles/slider.rs b/crates/teksilo-theme-fluent/src/styles/slider.rs index fe2c34edc..91b1a77ec 100644 --- a/crates/teksilo-theme-fluent/src/styles/slider.rs +++ b/crates/teksilo-theme-fluent/src/styles/slider.rs @@ -20,11 +20,20 @@ //! //! The track is 4 dp, filled with the accent up to the value and //! `ControlStrongFillColorDefault` beyond it. +//! +//! Right to left, a horizontal slider runs from the right, as WinUI's +//! `Slider` does under `FlowDirection="RightToLeft"`: its minimum is at the +//! leading edge, so the thumb, the fill and the ticks are mirrored, as the +//! `Slider` mirrors its pointer and arrow keys. A vertical slider is laid out +//! the same way in either direction, with its minimum at the bottom, as WinUI +//! lays out a vertical `Slider` and as the `Slider` reads its pointer and +//! `ArrowUp` raises the value: the fill rises from the bottom to the thumb. use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal}; use teksilo_core::accessibility::AccessNodeBuilder; use teksilo_core::binding::BindingLevel; use teksilo_core::build_context::BuildContext; +use teksilo_core::environment::LayoutDirection; use teksilo_core::focus::FocusOrigin; use teksilo_core::signal::Signal; use teksilo_core::styles::{SliderOrientation, SliderStyle, SliderStyleConfig}; @@ -133,25 +142,39 @@ struct SliderAxis { /// Resolve a slider's track geometry and value axis. /// -/// **The vertical axis runs top → bottom**: `value == 0` is at the top and -/// `value == 1` at the bottom, with the fill growing downward. That is not -/// the visual convention one might reach for (a "level" that rises), and -/// getting it backwards is invisible until you grab the thumb — it then -/// travels *away* from the pointer, because a style does not own the -/// mapping. `Slider`'s own hit-testing computes -/// `t = (y - thumb_radius) / usable` from the widget-local pointer position, -/// and the shipped `RecipeSliderStyle` paints to match. A style that -/// disagrees is simply wrong, however it looks standing still. +/// **The vertical axis runs bottom to top**: `value == 0` is at the bottom +/// and `value == 1` at the top, with the fill rising from the bottom. +/// Getting it backwards is invisible until you grab the thumb: it then sits +/// opposite to where the press put the value, and `ArrowUp` moves it down, +/// because a style does not own the mapping. `Slider`'s own hit-testing +/// computes `t = 1 - (y - thumb_radius) / usable` from the widget-local +/// pointer position, and its `thumb_rect` and the shipped +/// `RecipeSliderStyle` put the thumb there. A style that disagrees is +/// simply wrong, however it looks standing still. /// /// The track is inset by the thumb's radius at both ends so the thumb's -/// travel never overhangs the control — the same inset `Slider` subtracts +/// travel never overhangs the control: the same inset `Slider` subtracts /// (`bounds.height - thumb_radius * 2.0`) when it maps a position to a /// value, which is what keeps the two in step. -fn slider_axis(orientation: SliderOrientation, bounds: Rect) -> SliderAxis { +/// +/// **A horizontal axis follows `direction`**: its minimum is at the leading +/// edge, the right one under right to left, so there `start` is the right +/// end and `end` the left one, as `Slider` reads its pointer and arrow keys. +/// The vertical axis does not depend on the direction. +fn slider_axis( + orientation: SliderOrientation, + direction: LayoutDirection, + bounds: Rect, +) -> SliderAxis { let half = THUMB * 0.5; match orientation { SliderOrientation::Horizontal => { let cy = bounds.y + bounds.height * 0.5; + let (left, right) = (bounds.x + half, bounds.x + bounds.width - half); + let (start, end) = match direction { + LayoutDirection::LeftToRight => (left, right), + LayoutDirection::RightToLeft => (right, left), + }; SliderAxis { track: Rect::new( bounds.x + half, @@ -159,8 +182,8 @@ fn slider_axis(orientation: SliderOrientation, bounds: Rect) -> SliderAxis { (bounds.width - THUMB).max(0.0), TRACK, ), - start: bounds.x + half, - end: bounds.x + bounds.width - half, + start, + end, centre_cross: cy, } } @@ -173,8 +196,8 @@ fn slider_axis(orientation: SliderOrientation, bounds: Rect) -> SliderAxis { TRACK, (bounds.height - THUMB).max(0.0), ), - start: bounds.y + half, - end: bounds.y + bounds.height - half, + start: bounds.y + bounds.height - half, + end: bounds.y + half, centre_cross: cx, } } @@ -299,33 +322,25 @@ impl Widget for FluentSliderBody { start, end, centre_cross, - } = slider_axis(self.orientation, bounds); + } = slider_axis(self.orientation, ctx.layout_direction, bounds); let pill = CornerRadius::uniform(TRACK * 0.5); canvas.fill_rounded_rect(track, pill, rail); - // Filled portion, from the track's origin to the thumb — rightward - // when horizontal, *downward* when vertical (see `slider_axis`). - match self.orientation { - SliderOrientation::Horizontal => { - let w = track.width * t; - if w > 0.0 { - canvas.fill_rounded_rect( - Rect::new(track.x, track.y, w, track.height), - pill, - fill, - ); + // Filled portion, from the minimum's end of the track to the thumb: + // from the leading edge when horizontal, from the bottom when + // vertical (see `slider_axis`). + let knob = lerp(start, end, t); + let extent = (knob - start).abs(); + if extent > 0.0 { + let filled = match self.orientation { + SliderOrientation::Horizontal => { + Rect::new(start.min(knob), track.y, extent, track.height) } - } - SliderOrientation::Vertical => { - let h = track.height * t; - if h > 0.0 { - canvas.fill_rounded_rect( - Rect::new(track.x, track.y, track.width, h), - pill, - fill, - ); + SliderOrientation::Vertical => { + Rect::new(track.x, start.min(knob), track.width, extent) } - } + }; + canvas.fill_rounded_rect(filled, pill, fill); } // Discrete ticks, drawn on the far side of the track. @@ -347,10 +362,9 @@ impl Widget for FluentSliderBody { // Thumb: outer disc + hairline + accent inner dot. let half = THUMB * 0.5; - let pos = lerp(start, end, t); let centre = match self.orientation { - SliderOrientation::Horizontal => Point::new(pos, centre_cross), - SliderOrientation::Vertical => Point::new(centre_cross, pos), + SliderOrientation::Horizontal => Point::new(knob, centre_cross), + SliderOrientation::Vertical => Point::new(centre_cross, knob), }; canvas.fill_circle(centre, half, thumb_face); if thumb_ring.a() > 0.0 { @@ -380,26 +394,89 @@ impl Widget for FluentSliderBody { mod tests { use super::*; - /// The bug this guards: a vertical slider whose paint ran bottom-to-top - /// while `Slider`'s hit-testing ran top-to-bottom. Everything looked - /// plausible until you grabbed the thumb, which then slid the opposite - /// way to the pointer. + const LTR: LayoutDirection = LayoutDirection::LeftToRight; + const RTL: LayoutDirection = LayoutDirection::RightToLeft; + + /// The bug this guards: a vertical slider painted with its minimum at + /// the top while `Slider` puts it at the bottom, so the thumb sat + /// opposite to where a press put the value and `ArrowUp` moved it down. /// - /// `Slider::set_value_from_position` computes - /// `t = (pos - thumb_radius) / (extent - thumb_radius * 2)` from the - /// widget-local coordinate on *both* axes — so on both axes `t` must - /// increase as the coordinate increases. + /// This mounts the real `Slider` under this style at several values, in + /// both orientations and both directions, and asks it where its thumb is + /// (`target_regions`, from the same `thumb_rect` its value readout uses + /// and that agrees with its pointer mapping). The axis has to put the + /// thumb's centre there. #[test] fn the_value_axis_runs_the_same_way_the_widget_maps_the_pointer() { + use teksilo_tokens::Orientation; + use teksilo_widgets::Slider; + use teksilo_widgets::slider::SLIDER_PART_THUMB; + + for (orientation, widget_orientation) in [ + (SliderOrientation::Horizontal, Orientation::Horizontal), + (SliderOrientation::Vertical, Orientation::Vertical), + ] { + for direction in [LTR, RTL] { + for value in [0.0_f32, 0.2, 0.5, 0.9, 1.0] { + let mut tree = teksilo_core::widget_tree::WidgetTree::new() + .with_theme(crate::light()) + .with_text_backend(std::rc::Rc::new(std::cell::RefCell::new( + teksilo_canvas::MockTextBackend::new(), + ))); + tree.set_layout_direction(direction); + let slider = tree.add( + Slider::new(Signal::new(value), 0.0, 1.0) + .orientation(widget_orientation) + .style(FluentSliderStyle), + ); + tree.layout(SizeProposal::exact(200.0, 200.0)); + + let bounds = tree.bounds(slider); + let thumb = tree + .widget_target_regions(slider) + .into_iter() + .find(|r| r.part == SLIDER_PART_THUMB) + .expect("the slider reports its thumb") + .rect; + let axis = slider_axis(orientation, direction, bounds); + let painted = lerp(axis.start, axis.end, value); + let reported = match orientation { + SliderOrientation::Horizontal => thumb.x + thumb.width * 0.5, + SliderOrientation::Vertical => thumb.y + thumb.height * 0.5, + }; + assert!( + (painted - reported).abs() < 1e-3, + "{orientation:?} {direction:?} at {value}: the thumb is \ + painted at {painted}, the widget's thumb is at {reported}" + ); + } + } + } + } + + /// Right to left, a horizontal axis runs from the right end to the left + /// one over the same track, as `Slider` reads its pointer from the other + /// end; a vertical axis is the same in both directions. + #[test] + fn right_to_left_mirrors_the_horizontal_axis_only() { let bounds = Rect::new(10.0, 20.0, 200.0, 300.0); - for orientation in [SliderOrientation::Horizontal, SliderOrientation::Vertical] { - let axis = slider_axis(orientation, bounds); + let l = slider_axis(SliderOrientation::Horizontal, LTR, bounds); + let r = slider_axis(SliderOrientation::Horizontal, RTL, bounds); + assert_eq!((r.start, r.end), (l.end, l.start)); + assert_eq!(r.track, l.track); + assert_eq!(r.centre_cross, l.centre_cross); + let mid = bounds.x + bounds.width * 0.5; + for t in [0.0, 0.25, 0.5, 1.0] { + let (a, b) = (lerp(l.start, l.end, t), lerp(r.start, r.end, t)); assert!( - axis.end > axis.start, - "{orientation:?}: value must grow with the coordinate, as \ - Slider's own position→value mapping does" + (a - mid + (b - mid)).abs() < 1e-4, + "the thumb at {t} is mirrored about the centre: {a} and {b}" ); } + + let lv = slider_axis(SliderOrientation::Vertical, LTR, bounds); + let rv = slider_axis(SliderOrientation::Vertical, RTL, bounds); + assert_eq!((rv.start, rv.end, rv.track), (lv.start, lv.end, lv.track)); } /// `Slider` subtracts exactly one thumb diameter from the extent when it @@ -409,12 +486,12 @@ mod tests { fn painted_travel_matches_the_widgets_usable_extent() { let bounds = Rect::new(10.0, 20.0, 200.0, 300.0); - let h = slider_axis(SliderOrientation::Horizontal, bounds); + let h = slider_axis(SliderOrientation::Horizontal, LTR, bounds); assert!((h.end - h.start - (bounds.width - THUMB)).abs() < 1e-4); assert!((h.track.width - (bounds.width - THUMB)).abs() < 1e-4); - let v = slider_axis(SliderOrientation::Vertical, bounds); - assert!((v.end - v.start - (bounds.height - THUMB)).abs() < 1e-4); + let v = slider_axis(SliderOrientation::Vertical, LTR, bounds); + assert!((v.start - v.end - (bounds.height - THUMB)).abs() < 1e-4); assert!((v.track.height - (bounds.height - THUMB)).abs() < 1e-4); } @@ -422,11 +499,11 @@ mod tests { fn the_track_is_centred_on_the_cross_axis() { let bounds = Rect::new(10.0, 20.0, 200.0, 300.0); - let h = slider_axis(SliderOrientation::Horizontal, bounds); + let h = slider_axis(SliderOrientation::Horizontal, LTR, bounds); assert_eq!(h.centre_cross, bounds.y + bounds.height * 0.5); assert!((h.track.y + h.track.height * 0.5 - h.centre_cross).abs() < 1e-4); - let v = slider_axis(SliderOrientation::Vertical, bounds); + let v = slider_axis(SliderOrientation::Vertical, LTR, bounds); assert_eq!(v.centre_cross, bounds.x + bounds.width * 0.5); assert!((v.track.x + v.track.width * 0.5 - v.centre_cross).abs() < 1e-4); } @@ -436,7 +513,7 @@ mod tests { fn a_slot_smaller_than_the_thumb_clamps_to_zero() { let tiny = Rect::new(0.0, 0.0, 4.0, 4.0); for orientation in [SliderOrientation::Horizontal, SliderOrientation::Vertical] { - let axis = slider_axis(orientation, tiny); + let axis = slider_axis(orientation, LTR, tiny); assert!(axis.track.width >= 0.0 && axis.track.height >= 0.0); } } diff --git a/crates/teksilo-theme-fluent/src/styles/standard_item.rs b/crates/teksilo-theme-fluent/src/styles/standard_item.rs index 9924b5da4..841540f98 100644 --- a/crates/teksilo-theme-fluent/src/styles/standard_item.rs +++ b/crates/teksilo-theme-fluent/src/styles/standard_item.rs @@ -108,6 +108,18 @@ impl StandardItemStyle for FluentStandardItemStyle { ); ctx.add(FluentRowFrame { row, pill }) } + + /// `ListViewItemMinHeight`, 40 dp at Compact, raised to the density's + /// target size above it. The row measures itself against this, so it is + /// the height a Fluent list row renders. + fn min_height_single_line(&self, tokens: &InputTokens) -> f32 { + fluent_standard_item_recipe_for(tokens).min_height_single_line + } + + /// The two-line row height of [`fluent_standard_item_recipe_for`]. + fn min_height_two_line(&self, tokens: &InputTokens) -> f32 { + fluent_standard_item_recipe_for(tokens).min_height_two_line + } } /// Stacks the selection pill over the delegated row, stretching **both** to diff --git a/crates/teksilo-theme-fluent/tests/high_contrast.rs b/crates/teksilo-theme-fluent/tests/high_contrast.rs new file mode 100644 index 000000000..10404f19d --- /dev/null +++ b/crates/teksilo-theme-fluent/tests/high_contrast.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Fluent light and dark under the OS "increase contrast" preference. +//! +//! The preset registers no `PaintProjection`, so it is painted through +//! `ColorTokens::for_high_contrast`, which lifts its own accent, ring, +//! secondary text and selection. Every pair that projection documents is +//! measured on both appearances, with the default accent and four others, for an active and an inactive window. + +use teksilo_core::styles::{PaintState, Theme}; +use teksilo_theme_fluent as fluent; +use teksilo_tokens::contrast::composite_contrast; +use teksilo_tokens::{Color, ColorTokens}; + +/// The default accent and four of Windows' accent swatches. +const ACCENTS: [&str; 4] = ["#B146C2", "#E74856", "#00B294", "#FFB900"]; + +fn themes() -> Vec<(String, Theme)> { + let mut out = vec![ + ("Fluent light".to_owned(), fluent::light()), + ("Fluent dark".to_owned(), fluent::dark()), + ]; + for hex in ACCENTS { + let accent = Color::from_hex(hex); + out.push(( + format!("Fluent light, {hex}"), + fluent::light_with_accent(accent), + )); + out.push(( + format!("Fluent dark, {hex}"), + fluent::dark_with_accent(accent), + )); + } + out +} + +/// `[wcag]` SC 1.4.6 for text, and the 4.5:1 the projection holds every +/// non-text pair it changes to; 1.5:1 is how far it moves the selection off +/// `surface_content`. +const TEXT: f32 = 7.0; +const NON_TEXT: f32 = 4.5; +const SELECTION: f32 = 1.5; + +fn high_contrast(theme: &Theme, window_active: bool) -> ColorTokens { + theme + .for_paint_state(PaintState { + window_active, + high_contrast: true, + }) + .colors +} + +/// Every pair `ColorTokens::for_high_contrast` documents, below its floor in +/// `c`, named with its ratio. +fn shortfalls(c: &ColorTokens) -> Vec { + let surfaces = [ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ("surface_raised", c.surface_raised), + ]; + let mut pairs: Vec<(String, Color, Color, f32)> = Vec::new(); + for (fill, bg) in [ + ("accent", c.accent), + ("accent_hover", c.accent_hover), + ("accent_pressed", c.accent_pressed), + ] { + pairs.push(( + format!("text_on_accent on {fill}"), + c.text_on_accent, + bg, + TEXT, + )); + // White text in a light palette, black in a dark one: the accent + // clears the surfaces as well. + let text_light = c.text_on_accent.relative_luminance() > 0.5; + let surfaces_light = c.surface_main.relative_luminance() > 0.5; + if text_light == surfaces_light { + for (s, s_bg) in surfaces { + pairs.push((format!("{fill} on {s}"), bg, s_bg, NON_TEXT)); + } + } + } + for (s, bg) in surfaces { + pairs.push((format!("focus_ring on {s}"), c.focus_ring, bg, NON_TEXT)); + pairs.push(( + format!("border_focused on {s}"), + c.border_focused, + bg, + NON_TEXT, + )); + pairs.push((format!("text_secondary on {s}"), c.text_secondary, bg, TEXT)); + } + // The focused selection, and the one a row keeps while its view is + // unfocused or its window inactive. + for (fill, bg) in [ + ("surface_selected", c.surface_selected), + ("surface_selected_inactive", c.surface_selected_inactive), + ] { + pairs.push(( + format!("text_secondary on {fill}"), + c.text_secondary, + bg, + TEXT, + )); + pairs.push((format!("text_primary on {fill}"), c.text_primary, bg, TEXT)); + pairs.push(( + format!("{fill} on surface_content"), + bg, + c.surface_content, + SELECTION, + )); + } + pairs + .into_iter() + .filter_map(|(name, fg, bg, floor)| { + let r = composite_contrast(fg, bg); + (r < floor).then(|| format!("{name} = {r:.2}, floor {floor}")) + }) + .collect() +} + +#[test] +fn every_high_contrast_pair_meets_its_floor() { + for (name, theme) in themes() { + for window_active in [true, false] { + let short = shortfalls(&high_contrast(&theme, window_active)); + assert!( + short.is_empty(), + "{name}, window active {window_active}: {short:#?}" + ); + } + } +} + +#[test] +fn high_contrast_keeps_the_accent_hue() { + // The projection used to swap IntUI's teal into every dark palette. + for (name, theme) in themes() { + for window_active in [true, false] { + let before = theme + .for_paint_state(PaintState { + window_active, + high_contrast: false, + }) + .colors + .accent; + let after = high_contrast(&theme, window_active).accent; + let (h0, s0, _) = before.to_hsv(); + let (h1, s1, _) = after.to_hsv(); + if s0 > 0.05 && s1 > 0.05 { + let d = (h0 - h1).abs(); + let d = d.min(360.0 - d); + assert!( + d < 1.0, + "{name}, window active {window_active}: hue {h0} became {h1}" + ); + } + } + } +} diff --git a/crates/teksilo-theme-fluent/tests/preset_metrics.rs b/crates/teksilo-theme-fluent/tests/preset_metrics.rs index 362562ad2..0c1c087ef 100644 --- a/crates/teksilo-theme-fluent/tests/preset_metrics.rs +++ b/crates/teksilo-theme-fluent/tests/preset_metrics.rs @@ -20,6 +20,7 @@ use teksilo_core::widget_id::WidgetId; use teksilo_core::widget_tree::WidgetTree; use teksilo_data::ListModel; use teksilo_i18n::lit; +use teksilo_tokens::TargetDensity; use teksilo_widgets::TableView; use teksilo_widgets::table_view::{Column, ColumnWidth}; @@ -123,3 +124,510 @@ fn the_installed_table_slot_reports_the_fluent_gutter() { "Fluent takes the shipped vertical gutter", ); } + +// ── StandardListItem row height ──────────────────────────────────────── + +/// The laid-out `(single-line, two-line)` heights of a `StandardListItem` row +/// in a plain column, built at `density` under `theme`. +/// +/// Built *at* the density, never switched into it: a row resolves its style +/// in `build()`, so switching after the fact would measure a mixture. +fn standard_row_heights(theme: &Theme, density: TargetDensity) -> (f32, f32) { + use teksilo_widgets::StandardListItem; + use teksilo_widgets::primitives::VStack; + + let (tree, root) = mounted( + theme.with_density(density), + VStack::new() + .child(StandardListItem::new(lit!("Documents"))) + .child(StandardListItem::new(lit!("Downloads")).subtitle(lit!("12 items"))), + ); + let rows = nodes_named(&tree, root, "StandardListItem"); + assert_eq!(rows.len(), 2, "one single-line row, one two-line row"); + (tree.bounds(rows[0]).height, tree.bounds(rows[1]).height) +} + +/// `ListViewItemMinHeight` is 40 dp, which is what the Fluent recipe writes, +/// and a laid-out row must measure it. The row used to floor itself on the +/// shipped 28 dp constant whatever the style declared. +/// +/// The Fluent heights already clear the Comfortable target, so only Touch +/// raises the single-line row (to 44); the 60 dp two-line row clears every +/// rung. The IntUI arm is the control and must be unchanged; its two-line row +/// at Touch is content-driven (44.4 under the mock text metrics), because a +/// floor only ever raises a row. +#[test] +fn a_standard_list_row_measures_the_fluent_row_height_at_every_density() { + let expected = [ + (TargetDensity::Compact, (40.0, 60.0), (28.0, 44.0)), + (TargetDensity::Comfortable, (40.0, 60.0), (32.0, 44.0)), + (TargetDensity::Touch, (44.0, 60.0), (44.0, 44.4)), + ]; + for (density, fluent, intui) in expected { + assert_eq!( + standard_row_heights(&teksilo_core::presets::intui::light(), density), + intui, + "{density:?}: IntUI must be unchanged", + ); + for theme in [teksilo_theme_fluent::light(), teksilo_theme_fluent::dark()] { + assert_eq!( + standard_row_heights(&theme, density), + fluent, + "{density:?}: the Fluent recipe's row heights must reach the row", + ); + } + // The accessor half: what the installed slot tells a row asking it. + let theme = teksilo_theme_fluent::light().with_density(density); + let slot = theme + .style_slots + .standard_item + .clone() + .expect("the preset installs a standard-item slot"); + assert_eq!( + ( + slot.min_height_single_line(&theme.input), + slot.min_height_two_line(&theme.input), + ), + fluent, + "{density:?}: the installed slot reports the Fluent row heights", + ); + } +} + +// ── TableView / TreeTableView row and header heights ─────────────────── + +/// The laid-out `(header height, row height)` of a `TableView` and of a +/// `TreeTableView` over the same three rows, built at `density` under `theme` +/// with no row or header height of their own. Each pair is measured on the +/// hosted `TableHeader` and on the first realized `BodyRow`, and the first +/// row must start where the header ends, so the band the virtualization +/// arithmetic steps by is the one painted. +/// +/// Built *at* the density, never switched into it: a view reads its style's +/// heights in `build()`. +fn table_bands(theme: &Theme, density: TargetDensity) -> [(f32, f32); 2] { + use teksilo_data::TreeModel; + use teksilo_widgets::TreeTableView; + + fn bands(tree: &WidgetTree, root: WidgetId) -> (f32, f32) { + let header = nodes_named(tree, root, "TableHeader"); + assert_eq!(header.len(), 1, "one hosted header"); + let mut rows = nodes_named(tree, root, "BodyRow"); + rows.sort_by(|a, b| tree.bounds(*a).y.total_cmp(&tree.bounds(*b).y)); + assert!(rows.len() >= 2, "at least two realized rows"); + let h = tree.bounds(header[0]); + let first = tree.bounds(rows[0]); + assert_eq!( + first.y, + h.y + h.height, + "the first row starts under the header" + ); + assert_eq!( + tree.bounds(rows[1]).y - first.y, + first.height, + "the row pitch is the row height", + ); + (h.height, first.height) + } + fn column(label: fn(&T) -> String) -> Column { + Column::new("name", lit!("Name"), move |row: &T, _| { + Box::new(teksilo_widgets::primitives::TextWidget::new(lit!(label( + row + )))) + }) + .width(ColumnWidth::Fixed(200.0)) + } + let theme = theme.with_density(density); + + let model = ListModel::from_vec(vec!["alpha".to_string(), "beta".into(), "gamma".into()]); + let (tree, root) = mounted( + theme.clone(), + TableView::new(model).add_column(column(|s: &String| s.clone())), + ); + let flat = bands(&tree, root); + + let model: TreeModel = TreeModel::new(); + for (i, name) in ["alpha", "beta", "gamma"].into_iter().enumerate() { + model.insert_root(i, name.to_string()); + } + let (tree, root) = mounted( + theme, + TreeTableView::new(model).add_column(column(|s: &String| s.clone())), + ); + [flat, bands(&tree, root)] +} + +/// Fluent's `ListViewItem` rhythm is 40 dp, which is what the Fluent recipe +/// writes for a table row and its header, and a laid-out `TableView` and +/// `TreeTableView` must measure it. Both views used to read the shipped 28 dp +/// row and 32 dp header constants whatever the style declared. +/// +/// Above Compact the 40 rises to the density's target size (44 at Touch), as +/// the list row does. The IntUI arm is the control: 32 / 28 at Compact, which +/// is what the views laid out before the style was consulted, then the +/// recipe's own ladder. +#[test] +fn a_table_measures_the_fluent_row_and_header_heights_at_every_density() { + let expected = [ + (TargetDensity::Compact, (40.0, 40.0), (32.0, 28.0)), + (TargetDensity::Comfortable, (40.0, 40.0), (32.0, 32.0)), + (TargetDensity::Touch, (44.0, 44.0), (44.0, 44.0)), + ]; + for (density, fluent, intui) in expected { + assert_eq!( + table_bands(&teksilo_core::presets::intui::light(), density), + [intui; 2], + "{density:?}: IntUI follows its recipe", + ); + for theme in [teksilo_theme_fluent::light(), teksilo_theme_fluent::dark()] { + assert_eq!( + table_bands(&theme, density), + [fluent; 2], + "{density:?}: the Fluent recipe's heights must reach both views", + ); + } + // The accessor half: what the installed slot tells a view asking it. + let theme = teksilo_theme_fluent::light().with_density(density); + let slot = theme + .style_slots + .table + .clone() + .expect("the preset installs a table slot"); + assert_eq!( + ( + slot.header_height(&theme.input), + slot.row_height(&theme.input) + ), + fluent, + "{density:?}: the installed slot reports the Fluent heights", + ); + } +} + +// ── TabBar tab height and header padding ─────────────────────────────── + +/// The laid-out `(strip height, tab height, header padding)` of a horizontal +/// two-tab `TabBar` built at `density` under `theme`, with no tab height of +/// its own. The padding is the inset of each header's content row inside the +/// `Padding` that wraps it, and it must be the same on both edges and on both +/// headers, so the number measured is the one the header's natural width was +/// sized from. +/// +/// Built *at* the density, never switched into it: a bar reads its style's +/// metrics when it builds. +fn tab_metrics(theme: &Theme, density: TargetDensity) -> (f32, f32, f32) { + use teksilo_core::signal::Signal; + use teksilo_data::ListModel; + use teksilo_i18n::lit; + use teksilo_widgets::tab_widget::{TabBar, TabDelegate, TabId, TabSizing}; + + let model = ListModel::from_vec(vec![ + (TabId::fresh(), "Documents"), + (TabId::fresh(), "Downloads"), + ]); + let delegate = TabDelegate::new(|_, t: &(TabId, &'static str)| lit!(t.1)); + let selected: Signal> = Signal::new(None); + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let bar = tree.add( + TabBar::horizontal(model, delegate, selected, |_, t| t.0) + .tab_sizing(TabSizing::Independent) + .show_scroll_arrows(false) + .show_overflow_dropdown(false), + ); + tree.layout(SizeProposal { + width: Some(800.0), + height: None, + }); + + let headers = nodes_named(&tree, bar, "TabHeader"); + assert_eq!(headers.len(), 2, "two tab headers"); + let tab_h = tree.bounds(headers[0]).height; + let mut pads = Vec::new(); + for header in headers { + assert_eq!(tree.bounds(header).height, tab_h, "one tab height"); + let padding = nodes_named(&tree, header, "Padding"); + assert_eq!(padding.len(), 1, "one padded content row per header"); + let outer = tree.bounds(padding[0]); + let row = tree.bounds(tree.children(padding[0])[0]); + let leading = row.x - outer.x; + let trailing = (outer.x + outer.width) - (row.x + row.width); + assert!( + (leading - trailing).abs() < 1e-3, + "{density:?}: the padding is symmetric ({leading} / {trailing})", + ); + pads.push(leading); + } + assert!((pads[0] - pads[1]).abs() < 1e-3, "one padding: {pads:?}"); + (tree.bounds(bar).height, tab_h, pads[0]) +} + +/// Assert `(strip, tab, padding)` measures `want` within float tolerance. +fn assert_tab_metrics(got: (f32, f32, f32), want: (f32, f32, f32), what: &str) { + assert!( + (got.0 - want.0).abs() < 1e-3 && (got.1 - want.1).abs() < 1e-3, + "{what}: strip and tab heights {got:?}, want {want:?}", + ); + assert!( + (got.2 - want.2).abs() < 1e-3, + "{what}: header padding {got:?}, want {want:?}", + ); +} + +/// `TabViewItemMinHeight` is 32 dp and `TabViewItemHeaderPadding` 8 dp in the +/// Fluent recipe, and a laid-out bar must measure both. The bar used to +/// reserve the shipped 50 dp strip and pad by the shipped 12 dp whatever the +/// style declared, so every Fluent tab strip rendered 18 dp taller and each +/// tab 8 dp wider than its recipe. +/// +/// Above Compact the 32 rises to the density's target size (44 at Touch); the +/// padding is WinUI's fixed 8 dp at every density. The IntUI arm is the +/// control: its 50 dp tab clears every target, and its padding walks the +/// spacing ladder. +#[test] +fn a_tab_bar_measures_the_fluent_tab_height_and_padding_at_every_density() { + let expected = [ + ( + TargetDensity::Compact, + (32.0, 32.0, 8.0), + (50.0, 50.0, 12.0), + ), + ( + TargetDensity::Comfortable, + (32.0, 32.0, 8.0), + (50.0, 50.0, 13.8), + ), + (TargetDensity::Touch, (44.0, 44.0, 8.0), (50.0, 50.0, 15.6)), + ]; + for (density, fluent, intui) in expected { + assert_tab_metrics( + tab_metrics(&teksilo_core::presets::intui::light(), density), + intui, + &format!("{density:?}: IntUI"), + ); + for theme in [teksilo_theme_fluent::light(), teksilo_theme_fluent::dark()] { + assert_tab_metrics( + tab_metrics(&theme, density), + fluent, + &format!("{density:?}: the Fluent recipe's tab metrics must reach the bar"), + ); + } + // The accessor half: what the installed slot tells a bar asking it. + let theme = teksilo_theme_fluent::light().with_density(density); + let slot = theme + .style_slots + .tab + .clone() + .expect("the preset installs a tab slot"); + assert!((slot.editor_height(&theme.input) - fluent.1).abs() < 1e-3); + assert!((slot.padding_horizontal(&theme.input) - fluent.2).abs() < 1e-3); + } +} + +// ── ScrollBar minimum thumb length ───────────────────────────────────── + +/// The laid-out thumb length of a vertical `ScrollBar` over a 400 dp track +/// built at `density` under `theme`, with no floor of its own. The content is +/// fifty viewports long (viewport ratio 0.02), so the unfloored thumb would be +/// 8 dp and the length measured is the floor alone. Read from the mounted bar's +/// reported thumb, which is the rectangle its hit test and its drag use. +/// +/// Built *at* the density, never switched into it: a bar reads its style's +/// floor when it builds. +fn scroll_thumb_length(theme: &Theme, density: TargetDensity) -> f32 { + use teksilo_core::signal::Signal; + use teksilo_widgets::scroll_bar::{SCROLLBAR_PART_THUMB, ScrollBar, ScrollBarOrientation}; + + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let bar = tree.add( + ScrollBar::new( + ScrollBarOrientation::Vertical, + Signal::new(0.0), + Signal::new(500.0), + Signal::new(0.02), + ) + .thickness(12.0), + ); + tree.layout(SizeProposal::exact(12.0, 400.0)); + tree.widget_target_regions(bar) + .into_iter() + .find(|r| r.part == SCROLLBAR_PART_THUMB) + .expect("the bar reports its thumb") + .rect + .height +} + +/// WinUI's `ScrollBarHorizontalThumbMinWidth` is 30 dp in the Fluent recipe, +/// and a laid-out bar must measure it. The bar used to recompute the shipped +/// 24 dp floor whatever the style declared, so every Fluent thumb on a long +/// document rendered 6 dp shorter than its recipe. +/// +/// Above Compact the 30 rises to the density's target size (32 at +/// Comfortable, 44 at Touch). The IntUI arm is the control: its 24 dp floor +/// walks the same ladder. +#[test] +fn a_scroll_bar_floors_its_thumb_at_the_fluent_minimum_at_every_density() { + let expected = [ + (TargetDensity::Compact, 30.0, 24.0), + (TargetDensity::Comfortable, 32.0, 32.0), + (TargetDensity::Touch, 44.0, 44.0), + ]; + for (density, preset, intui) in expected { + let got = scroll_thumb_length(&teksilo_core::presets::intui::light(), density); + assert!( + (got - intui).abs() < 1e-3, + "{density:?}: IntUI thumb {got}, want {intui}", + ); + for theme in [teksilo_theme_fluent::light(), teksilo_theme_fluent::dark()] { + let got = scroll_thumb_length(&theme, density); + assert!( + (got - preset).abs() < 1e-3, + "{density:?}: the Fluent recipe's thumb floor must reach the bar: \ + {got}, want {preset}", + ); + } + // The accessor half: what the installed slot tells a bar asking it. + let theme = teksilo_theme_fluent::light().with_density(density); + let slot = theme + .style_slots + .scroll_bar + .clone() + .expect("the preset installs a scroll bar slot"); + assert!((slot.min_thumb_length(&theme.input) - preset).abs() < 1e-3); + } +} + +// ── Menu rows ────────────────────────────────────────────────────────── + +// The tappable row around the menu, which denies the miss-only slop pass so +// the conformance verdict below is the row's own, is +// `teksilo_target_conformance::in_tappable_row`, imported rather than copied. +use teksilo_target_conformance::in_tappable_row; + +/// A `MenuList` at `density` under `theme`, in a tappable row: the laid-out +/// height of each of its four `MenuItem` rows, of its separator, and the +/// `(width, right edge minus the row's right edge)` of the submenu row's +/// chevron column, the trailing column `MenuItem` builds from the style's +/// metrics. Also the number of conformance failures the gate reports for +/// the row. +/// +/// Built *at* the density, never switched into it: a row reads its style in +/// `build()`. +fn menu_geometry(theme: &Theme, density: TargetDensity) -> (Vec, f32, (f32, f32), usize) { + use teksilo_core::accessibility::target_audit::target_audit; + use teksilo_core::signal::Signal; + use teksilo_widgets::primitives::FixedSize; + use teksilo_widgets::{MenuItem, MenuList}; + + let theme = theme.with_density(density); + let icon_column = theme + .style_slots + .menu_item + .as_ref() + .expect("the preset installs a menu-item slot") + .metrics() + .icon_column_width; + let mut tree = WidgetTree::new() + .with_theme(theme) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let menu = MenuList::new() + .item(MenuItem::new(lit!("Open"))) + .item(MenuItem::new(lit!("Save")).shortcut_label("Ctrl+S")) + .separator() + .item(MenuItem::new(lit!("Checkable")).checked(Signal::new(true))) + .item(MenuItem::submenu(lit!("Submenu"), || { + Box::new(MenuList::new().item(MenuItem::new(lit!("Inner")))) + })); + let root = in_tappable_row(&mut tree, FixedSize::new().width(240.0).child(menu)); + tree.layout(SizeProposal::exact(600.0, 400.0)); + + let rows = nodes_named(&tree, root, "MenuItem"); + assert_eq!(rows.len(), 4, "four rows"); + let heights = rows.iter().map(|r| tree.bounds(*r).height).collect(); + let separators = nodes_named(&tree, root, "MenuSeparator"); + assert_eq!(separators.len(), 1, "one separator"); + let separator = tree.bounds(separators[0]).height; + + let submenu = tree.bounds(rows[3]); + let chevron = nodes_named(&tree, rows[3], "FixedSize") + .into_iter() + .map(|id| tree.bounds(id)) + .filter(|b| (b.height - icon_column).abs() < 1e-4) + .max_by(|a, b| a.x.total_cmp(&b.x)) + .expect("the submenu row has a chevron column"); + let failures = target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(); + ( + heights, + separator, + (chevron.width, chevron.right() - submenu.right()), + failures, + ) +} + +/// The line `MockTextBackend` measures for any text, whatever the theme's +/// line height asks. +const MOCK_LINE: f32 = 16.0; + +/// A Fluent menu row is WinUI's 37 dp at Compact and Comfortable, whose +/// 32 dp target it already clears, and the 44 dp Touch target at Touch; the +/// separator, 3 dp, and the trailing column, 11 dp, are scaled by the +/// density's spacing factor (1.15 at Comfortable, 1.3 at Touch). No row +/// fails the conformance gate. The menu style used to report its Compact +/// metrics at every density, so a Touch separator and chevron column kept +/// their Compact sizes inside rows laid out for Touch, and `MenuList` capped +/// its height by the 37 dp row. +/// +/// The laid-out row is the item height less the difference between the +/// Body line box the shipped row arithmetic pads around (20 dp) and the +/// line the mock backend measures ([`MOCK_LINE`]): the recipe derives its +/// vertical padding from the nominal line box, a finding the conformance +/// gate records against the menu row, not something this preset decides. +#[test] +fn a_menu_measures_the_fluent_rows_at_every_density() { + let expected = [ + (TargetDensity::Compact, 37.0, 3.0, 11.0), + (TargetDensity::Comfortable, 37.0, 3.45, 12.65), + (TargetDensity::Touch, 44.0, 3.9, 14.3), + ]; + for (density, item, separator, trailing) in expected { + for theme in [teksilo_theme_fluent::light(), teksilo_theme_fluent::dark()] { + let (rows, sep, (chevron, chevron_offset), failures) = menu_geometry(&theme, density); + let body = &theme.typography.body; + let short = body.size * body.line_height - MOCK_LINE; + assert_eq!(rows, vec![item - short; 4], "{density:?}: the row height"); + assert!( + (sep - separator).abs() < 1e-3, + "{density:?}: the separator is {sep}, want {separator}" + ); + assert!( + (chevron - trailing).abs() < 1e-3, + "{density:?}: the chevron column is {chevron}, want {trailing}" + ); + assert!( + chevron_offset.abs() < 1e-3, + "{density:?}: the chevron column ends {chevron_offset} from the row's edge" + ); + assert_eq!(failures, 0, "{density:?}: a conformance failure"); + } + // The accessor half: what the installed slot tells a row asking it. + let theme = teksilo_theme_fluent::light().with_density(density); + let m = theme + .style_slots + .menu_item + .clone() + .expect("the preset installs a menu-item slot") + .metrics(); + assert_eq!(m.item_height, item, "{density:?}"); + assert!((m.separator_height - separator).abs() < 1e-3, "{density:?}"); + assert!( + (m.trailing_column_width - trailing).abs() < 1e-3, + "{density:?}" + ); + } +} diff --git a/crates/teksilo-theme-fluent/tests/slider_rtl.rs b/crates/teksilo-theme-fluent/tests/slider_rtl.rs new file mode 100644 index 000000000..70c33ccf9 --- /dev/null +++ b/crates/teksilo-theme-fluent/tests/slider_rtl.rs @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Fluent slider under right-to-left layout. +//! +//! A horizontal `Slider` reads its pointer and its arrow keys from the +//! leading edge, the right one under right to left. The style has to paint +//! the thumb and the fill from the same edge, or the thumb slides away from +//! the pointer dragging it and the arrow that raises the value moves the thumb +//! towards the minimum. These tests mount the real widget under the Fluent +//! theme in both directions and read what was painted. + +use std::cell::RefCell; +use std::rc::Rc; + +use teksilo_canvas::{MockTextBackend, Point, Rect, RenderFrame, ShapeKind, SizeProposal}; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::event::{Key, Modifiers, PointerButton}; +use teksilo_core::signal::Signal; +use teksilo_core::styles::Theme; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_tokens::Orientation; +use teksilo_widgets::primitives::FixedSize; +use teksilo_widgets::{Slider, VStack}; + +/// The thumb's outer disc (dp), `FluentSliderStyle`'s. +const THUMB: f32 = 22.0; +/// The track's thickness (dp). +const TRACK: f32 = 4.0; +/// The slider's length along its main axis (dp). +const LENGTH: f32 = 200.0; +/// The type name of the node the style paints. +const BODY: &str = "FluentSliderBody"; + +const LTR: LayoutDirection = LayoutDirection::LeftToRight; +const RTL: LayoutDirection = LayoutDirection::RightToLeft; + +fn themes() -> [(&'static str, Theme); 2] { + [ + ("Fluent light", teksilo_theme_fluent::light()), + ("Fluent dark", teksilo_theme_fluent::dark()), + ] +} + +fn rgba8(c: [f32; 4]) -> [u8; 4] { + c.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) +} + +/// A tree holding a `LENGTH` dp slider at `value` (0 to 1) laid out in +/// `direction`, `orientation`: the tree, the style's node bounds and the +/// value. +fn slider_tree( + theme: &Theme, + value: f32, + direction: LayoutDirection, + orientation: Orientation, +) -> (WidgetTree, Rect, Signal) { + let value = Signal::new(value); + let mut tree = WidgetTree::new() + .with_theme(theme.clone()) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + tree.set_layout_direction(direction); + let slider = Slider::new(value.clone(), 0.0, 1.0) + .orientation(orientation) + .jump_on_press(true); + let sized = match orientation { + Orientation::Horizontal => FixedSize::new().width(LENGTH).child(slider), + Orientation::Vertical => FixedSize::new().height(LENGTH).child(slider), + }; + let root = tree.add(VStack::new().child(sized)); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let bounds = body_bounds(&tree, root); + (tree, bounds, value) +} + +/// The bounds of the one node the style paints under `root`. +fn body_bounds(tree: &WidgetTree, root: WidgetId) -> Rect { + fn walk(tree: &WidgetTree, id: WidgetId, out: &mut Vec) { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(BODY)) + { + out.push(id); + } + for c in tree.children(id) { + walk(tree, c, out); + } + } + let mut out = Vec::new(); + walk(tree, root, &mut out); + assert_eq!(out.len(), 1, "one {BODY}"); + tree.bounds(out[0]) +} + +fn rerender(tree: &mut WidgetTree) -> Rc { + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.render() +} + +/// The centre of the painted thumb: the one filled disc of its diameter. +fn thumb_centre(frame: &RenderFrame) -> Point { + let discs: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.shape == ShapeKind::Circle && s.stroke_width == 0.0) + .filter(|s| (s.screen[2] - THUMB).abs() < 1e-3) + .collect(); + assert_eq!(discs.len(), 1, "one thumb face in {:?}", frame.shapes); + let s = discs[0].screen; + Point::new(s[0] + s[2] * 0.5, s[1] + s[3] * 0.5) +} + +/// The painted fill: the accent part of the track, `[x, y, width, height]`. +fn fill_rect(frame: &RenderFrame, theme: &Theme, orientation: Orientation) -> [f32; 4] { + let accent = rgba8(theme.colors.accent.to_array()); + let fills: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.shape != ShapeKind::Circle && s.stroke_width == 0.0) + .filter(|s| rgba8(s.color) == accent) + .filter(|s| { + let cross = match orientation { + Orientation::Horizontal => s.screen[3], + Orientation::Vertical => s.screen[2], + }; + (cross - TRACK).abs() < 1e-3 + }) + .collect(); + assert_eq!(fills.len(), 1, "one fill in {:?}", frame.shapes); + fills[0].screen +} + +/// The same value paints the thumb and the fill mirrored about the slider's +/// centre: the fill runs from the right end of the track to the thumb. +#[test] +fn right_to_left_mirrors_the_thumb_and_the_fill() { + let half = THUMB * 0.5; + for (scheme, theme) in themes() { + for value in [0.0_f32, 0.3, 0.75, 1.0] { + let (mut l_tree, lb, _) = slider_tree(&theme, value, LTR, Orientation::Horizontal); + let (mut r_tree, rb, _) = slider_tree(&theme, value, RTL, Orientation::Horizontal); + assert_eq!(rb.width, LENGTH, "{scheme}"); + let l_frame = rerender(&mut l_tree); + let r_frame = rerender(&mut r_tree); + + let (l, r) = (thumb_centre(&l_frame), thumb_centre(&r_frame)); + let from_left = l.x - lb.x; + let from_right = rb.x + rb.width - r.x; + assert!( + (from_left - from_right).abs() < 1e-3, + "{scheme} at {value}: the thumb is {from_left} from the left LTR \ + and {from_right} from the right RTL" + ); + assert!( + (from_right - half - (LENGTH - THUMB) * value).abs() < 1e-3, + "{scheme} at {value}: the minimum is at the right" + ); + assert!((l.y - lb.y - (r.y - rb.y)).abs() < 1e-3, "{scheme}"); + + if value == 0.0 { + continue; + } + let lf = fill_rect(&l_frame, &theme, Orientation::Horizontal); + let rf = fill_rect(&r_frame, &theme, Orientation::Horizontal); + assert!( + (lf[0] - (lb.x + half)).abs() < 1e-3, + "{scheme} at {value}: LTR fills from the left end of the track" + ); + assert!( + (rf[0] + rf[2] - (rb.x + rb.width - half)).abs() < 1e-3, + "{scheme} at {value}: RTL fills from the right end of the track" + ); + assert!((rf[0] - r.x).abs() < 1e-3, "{scheme}: up to the thumb"); + assert!((lf[2] - rf[2]).abs() < 1e-3, "{scheme}: the same length"); + assert!( + (lf[1] - lb.y - (rf[1] - rb.y)).abs() < 1e-3 && lf[3] == rf[3], + "{scheme}: on the same line" + ); + } + } +} + +/// A press puts the value where the thumb is then painted, and a drag +/// leftward raises the value with the thumb under the pointer. +#[test] +fn right_to_left_keeps_the_thumb_under_the_pointer() { + let half = THUMB * 0.5; + let usable = LENGTH - THUMB; + for (scheme, theme) in themes() { + let (mut tree, b, value) = slider_tree(&theme, 0.5, RTL, Orientation::Horizontal); + rerender(&mut tree); + + // A quarter of the travel in from the left is three quarters of the + // range under right to left. + let at = Point::new(b.x + half + usable * 0.25, b.y + b.height * 0.5); + tree.pointer_move(at); + tree.pointer_down_button(at, PointerButton::Primary); + let v = value.get(); + assert!( + (v - 0.75).abs() < 1e-3, + "{scheme}: the left is the high end, {v}" + ); + let thumb = thumb_centre(&rerender(&mut tree)); + assert!( + (thumb.x - at.x).abs() < 0.01, + "{scheme}: the thumb at {} is under the pointer at {}", + thumb.x, + at.x + ); + + // Dragging further left raises the value, and the thumb follows. The + // first move past the slop starts the drag at the press; the second + // moves it, to 90 %, a value on the 1 % grid a stepless slider snaps + // to. + let to = Point::new(at.x - usable * 0.15, at.y); + tree.pointer_move(Point::new(at.x - usable * 0.05, at.y)); + tree.pointer_move(to); + let raised = value.get(); + assert!( + raised > v, + "{scheme}: a leftward drag raises, {v} to {raised}" + ); + let thumb = thumb_centre(&rerender(&mut tree)); + assert!( + (thumb.x - to.x).abs() < 0.01, + "{scheme}: the thumb at {} follows the pointer to {}", + thumb.x, + to.x + ); + tree.pointer_up_button(to, PointerButton::Primary); + } +} + +/// The arrow that raises the value under right to left (the leftward one) +/// moves the thumb left, and the fill grows from the right with it. +#[test] +fn right_to_left_arrow_keys_move_the_thumb_the_way_they_point() { + for (scheme, theme) in themes() { + let (mut tree, _, value) = slider_tree(&theme, 0.5, RTL, Orientation::Horizontal); + rerender(&mut tree); + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut tree); + let start = thumb_centre(&frame); + let start_fill = fill_rect(&frame, &theme, Orientation::Horizontal); + + tree.press_key(Key::ArrowLeft, Modifiers::NONE); + assert!( + value.get() > 0.5, + "{scheme}: ArrowLeft raises, {}", + value.get() + ); + let frame = rerender(&mut tree); + let left = thumb_centre(&frame); + assert!(left.x < start.x, "{scheme}: and moves the thumb left"); + let fill = fill_rect(&frame, &theme, Orientation::Horizontal); + assert!(fill[2] > start_fill[2], "{scheme}: the fill grows"); + assert!( + (fill[0] + fill[2] - (start_fill[0] + start_fill[2])).abs() < 1e-3, + "{scheme}: from the same right end" + ); + + tree.press_key(Key::ArrowRight, Modifiers::NONE); + tree.press_key(Key::ArrowRight, Modifiers::NONE); + assert!( + value.get() < 0.5, + "{scheme}: ArrowRight lowers, {}", + value.get() + ); + let right = thumb_centre(&rerender(&mut tree)); + assert!(right.x > start.x, "{scheme}: and moves the thumb right"); + } +} + +/// A vertical slider has no leading edge: it paints the same in both +/// directions. +#[test] +fn a_vertical_slider_paints_the_same_in_both_directions() { + for (scheme, theme) in themes() { + let (mut l_tree, lb, _) = slider_tree(&theme, 0.3, LTR, Orientation::Vertical); + let (mut r_tree, rb, _) = slider_tree(&theme, 0.3, RTL, Orientation::Vertical); + let l_frame = rerender(&mut l_tree); + let r_frame = rerender(&mut r_tree); + let (l, r) = (thumb_centre(&l_frame), thumb_centre(&r_frame)); + assert!( + (l.x - lb.x - (r.x - rb.x)).abs() < 1e-3 && (l.y - lb.y - (r.y - rb.y)).abs() < 1e-3, + "{scheme}: the thumb at {l:?} in {lb:?} and {r:?} in {rb:?}" + ); + let lf = fill_rect(&l_frame, &theme, Orientation::Vertical); + let rf = fill_rect(&r_frame, &theme, Orientation::Vertical); + assert!( + (lf[0] - lb.x - (rf[0] - rb.x)).abs() < 1e-3 + && (lf[1] - lb.y - (rf[1] - rb.y)).abs() < 1e-3 + && lf[2] == rf[2] + && lf[3] == rf[3], + "{scheme}: the fill {lf:?} in {lb:?} and {rf:?} in {rb:?}" + ); + } +} diff --git a/crates/teksilo-theme-fluent/tests/slider_vertical.rs b/crates/teksilo-theme-fluent/tests/slider_vertical.rs new file mode 100644 index 000000000..d5408a10d --- /dev/null +++ b/crates/teksilo-theme-fluent/tests/slider_vertical.rs @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Fluent slider standing vertical. +//! +//! A vertical `Slider` puts its minimum at the bottom and its maximum at the +//! top: a press near the top sets a value near the maximum, `ArrowUp` raises +//! the value, and the thumb it reports through `target_regions` rises with +//! it. The style has to paint the thumb and the fill the same way, or the +//! thumb sits opposite to where a press puts the value and `ArrowUp` moves it +//! down. These tests mount the real widget under the Fluent theme and read +//! what was painted. + +use std::cell::RefCell; +use std::rc::Rc; + +use teksilo_canvas::{MockTextBackend, Point, Rect, RenderFrame, ShapeKind, SizeProposal}; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::event::{Key, Modifiers, PointerButton}; +use teksilo_core::signal::Signal; +use teksilo_core::styles::Theme; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_tokens::Orientation; +use teksilo_widgets::primitives::FixedSize; +use teksilo_widgets::slider::SLIDER_PART_THUMB; +use teksilo_widgets::{Slider, VStack}; + +/// The thumb's outer disc (dp), `FluentSliderStyle`'s. +const THUMB: f32 = 22.0; +/// The track's thickness (dp). +const TRACK: f32 = 4.0; +/// The slider's height (dp). +const LENGTH: f32 = 200.0; +/// The type name of the node the style paints. +const BODY: &str = "FluentSliderBody"; +/// The type name of the widget that maps the pointer and the keys. +const SLIDER: &str = "Slider"; + +fn themes() -> [(&'static str, Theme); 2] { + [ + ("Fluent light", teksilo_theme_fluent::light()), + ("Fluent dark", teksilo_theme_fluent::dark()), + ] +} + +fn rgba8(c: [f32; 4]) -> [u8; 4] { + c.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) +} + +/// A mounted vertical slider, `LENGTH` dp tall. +struct Mounted { + tree: WidgetTree, + /// The `Slider` itself. + slider: WidgetId, + /// The bounds of the node the style paints. + body: Rect, + value: Signal, +} + +/// A tree holding a vertical slider at `value` (0 to 1) laid out in +/// `direction`. +fn mount(theme: &Theme, value: f32, direction: LayoutDirection) -> Mounted { + let value = Signal::new(value); + let mut tree = WidgetTree::new() + .with_theme(theme.clone()) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + tree.set_layout_direction(direction); + let slider = Slider::new(value.clone(), 0.0, 1.0) + .orientation(Orientation::Vertical) + .jump_on_press(true); + let root = tree.add(VStack::new().child(FixedSize::new().height(LENGTH).child(slider))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let body = tree.bounds(only(&tree, root, BODY)); + let slider = only(&tree, root, SLIDER); + Mounted { + tree, + slider, + body, + value, + } +} + +/// The one node named `name` under `root`. +fn only(tree: &WidgetTree, root: WidgetId, name: &str) -> WidgetId { + fn walk(tree: &WidgetTree, id: WidgetId, name: &str, out: &mut Vec) { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(name)) + { + out.push(id); + } + for c in tree.children(id) { + walk(tree, c, name, out); + } + } + let mut out = Vec::new(); + walk(tree, root, name, &mut out); + assert_eq!(out.len(), 1, "one {name}"); + out[0] +} + +fn rerender(tree: &mut WidgetTree) -> Rc { + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.render() +} + +/// The centre of the thumb the widget reports, the one its pointer mapping +/// and its value readout agree with. +fn widget_thumb_centre(m: &Mounted) -> Point { + let regions = m.tree.widget_target_regions(m.slider); + let thumb = regions + .iter() + .find(|r| r.part == SLIDER_PART_THUMB) + .expect("the slider reports its thumb"); + Point::new( + thumb.rect.x + thumb.rect.width * 0.5, + thumb.rect.y + thumb.rect.height * 0.5, + ) +} + +/// The centre of the painted thumb: the one filled disc of its diameter. +fn thumb_centre(frame: &RenderFrame) -> Point { + let discs: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.shape == ShapeKind::Circle && s.stroke_width == 0.0) + .filter(|s| (s.screen[2] - THUMB).abs() < 1e-3) + .collect(); + assert_eq!(discs.len(), 1, "one thumb face in {:?}", frame.shapes); + let s = discs[0].screen; + Point::new(s[0] + s[2] * 0.5, s[1] + s[3] * 0.5) +} + +/// The painted fill, the accent part of the track, `[x, y, width, height]`; +/// `None` when nothing is filled. +fn fill_rect(frame: &RenderFrame, theme: &Theme) -> Option<[f32; 4]> { + let accent = rgba8(theme.colors.accent.to_array()); + let fills: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.shape != ShapeKind::Circle && s.stroke_width == 0.0) + .filter(|s| rgba8(s.color) == accent) + .filter(|s| (s.screen[2] - TRACK).abs() < 1e-3) + .collect(); + assert!(fills.len() <= 1, "at most one fill in {:?}", frame.shapes); + fills.first().map(|s| s.screen) +} + +/// At every value, and in either direction, the thumb is painted where the +/// widget reports its thumb: the maximum at the top, the minimum at the +/// bottom. +#[test] +fn the_painted_thumb_is_the_thumb_the_widget_reports() { + let half = THUMB * 0.5; + for (scheme, theme) in themes() { + for direction in [LayoutDirection::LeftToRight, LayoutDirection::RightToLeft] { + for value in [0.0_f32, 0.25, 0.5, 0.8, 1.0] { + let mut m = mount(&theme, value, direction); + assert_eq!(m.body.height, LENGTH, "{scheme}"); + assert_eq!(m.tree.bounds(m.slider), m.body, "{scheme}"); + let thumb = thumb_centre(&rerender(&mut m.tree)); + let reported = widget_thumb_centre(&m); + assert!( + (thumb.x - reported.x).abs() < 1e-3 && (thumb.y - reported.y).abs() < 1e-3, + "{scheme} {direction:?} at {value}: the thumb is painted at \ + {thumb:?}, the widget's thumb is at {reported:?}" + ); + let from_bottom = m.body.y + m.body.height - thumb.y; + assert!( + (from_bottom - half - (LENGTH - THUMB) * value).abs() < 1e-3, + "{scheme} {direction:?} at {value}: the minimum is at the \ + bottom, the thumb {from_bottom} from it" + ); + } + } + } +} + +/// The fill runs from the bottom end of the track, the minimum's, up to the +/// thumb. +#[test] +fn the_fill_rises_from_the_bottom_to_the_thumb() { + let half = THUMB * 0.5; + for (scheme, theme) in themes() { + let mut m = mount(&theme, 0.0, LayoutDirection::LeftToRight); + assert_eq!( + fill_rect(&rerender(&mut m.tree), &theme), + None, + "{scheme}: nothing is filled at the minimum" + ); + for value in [0.25_f32, 0.5, 0.8, 1.0] { + let mut m = mount(&theme, value, LayoutDirection::LeftToRight); + let frame = rerender(&mut m.tree); + let thumb = thumb_centre(&frame); + let fill = fill_rect(&frame, &theme).expect("a fill"); + let bottom = m.body.y + m.body.height - half; + assert!( + (fill[1] + fill[3] - bottom).abs() < 1e-3, + "{scheme} at {value}: the fill {fill:?} ends at the track's \ + bottom end {bottom}" + ); + assert!( + (fill[1] - thumb.y).abs() < 1e-3, + "{scheme} at {value}: the fill {fill:?} reaches the thumb at {}", + thumb.y + ); + assert!( + (fill[3] - (LENGTH - THUMB) * value).abs() < 1e-3, + "{scheme} at {value}: the fill is as long as the value" + ); + } + } +} + +/// A press near the top sets a value near the maximum, and the thumb is then +/// painted under the pointer; a drag down lowers the value and the thumb +/// follows. +#[test] +fn a_press_near_the_top_puts_the_thumb_under_the_pointer_near_the_maximum() { + let half = THUMB * 0.5; + let usable = LENGTH - THUMB; + for (scheme, theme) in themes() { + let mut m = mount(&theme, 0.5, LayoutDirection::LeftToRight); + rerender(&mut m.tree); + + let at = Point::new( + m.body.x + m.body.width * 0.5, + m.body.y + half + usable * 0.1, + ); + m.tree.pointer_move(at); + m.tree.pointer_down_button(at, PointerButton::Primary); + let v = m.value.get(); + assert!( + (v - 0.9).abs() < 1e-3, + "{scheme}: a press a tenth of the way down is 90 %, {v}" + ); + let thumb = thumb_centre(&rerender(&mut m.tree)); + assert!( + (thumb.y - at.y).abs() < 0.01, + "{scheme}: the thumb at {} is under the pointer at {}", + thumb.y, + at.y + ); + + // The first move past the slop starts the drag at the press; the + // second moves it, to 70 %, on the 1 % grid a stepless slider snaps + // to. + let to = Point::new(at.x, at.y + usable * 0.2); + m.tree.pointer_move(Point::new(at.x, at.y + usable * 0.05)); + m.tree.pointer_move(to); + let lowered = m.value.get(); + assert!( + lowered < v, + "{scheme}: a downward drag lowers, {v} to {lowered}" + ); + let thumb = thumb_centre(&rerender(&mut m.tree)); + assert!( + (thumb.y - to.y).abs() < 0.01, + "{scheme}: the thumb at {} follows the pointer to {}", + thumb.y, + to.y + ); + m.tree.pointer_up_button(to, PointerButton::Primary); + } +} + +/// `ArrowUp` raises the value and moves the painted thumb up, the fill +/// growing from the same bottom end; `ArrowDown` the opposite. +#[test] +fn arrow_up_moves_the_painted_thumb_up() { + for (scheme, theme) in themes() { + let mut m = mount(&theme, 0.5, LayoutDirection::LeftToRight); + rerender(&mut m.tree); + m.tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut m.tree); + let start = thumb_centre(&frame); + let start_fill = fill_rect(&frame, &theme).expect("a fill"); + + m.tree.press_key(Key::ArrowUp, Modifiers::NONE); + assert!( + m.value.get() > 0.5, + "{scheme}: ArrowUp raises, {}", + m.value.get() + ); + let frame = rerender(&mut m.tree); + let up = thumb_centre(&frame); + assert!(up.y < start.y, "{scheme}: and moves the thumb up"); + let fill = fill_rect(&frame, &theme).expect("a fill"); + assert!(fill[3] > start_fill[3], "{scheme}: the fill grows"); + assert!( + (fill[1] + fill[3] - (start_fill[1] + start_fill[3])).abs() < 1e-3, + "{scheme}: from the same bottom end" + ); + + m.tree.press_key(Key::ArrowDown, Modifiers::NONE); + m.tree.press_key(Key::ArrowDown, Modifiers::NONE); + assert!( + m.value.get() < 0.5, + "{scheme}: ArrowDown lowers, {}", + m.value.get() + ); + let down = thumb_centre(&rerender(&mut m.tree)); + assert!(down.y > start.y, "{scheme}: and moves the thumb down"); + } +} diff --git a/crates/teksilo-theme-kde/Cargo.toml b/crates/teksilo-theme-kde/Cargo.toml new file mode 100644 index 000000000..6d968fafc --- /dev/null +++ b/crates/teksilo-theme-kde/Cargo.toml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech + +[package] +name = "teksilo-theme-kde" +edition.workspace = true +version.workspace = true +license.workspace = true +publish.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +description = "KDE Plasma theme preset for Teksilo: the Breeze design language and its colour schemes and accents, as tokens. Not affiliated with or endorsed by KDE e.V." +documentation = "https://docs.rs/teksilo-theme-kde" +keywords = ["kde", "plasma", "breeze", "theme", "gui"] +categories = ["gui"] +# `tests/breeze_reference.rs`, `tests/contrast.rs`, `tests/mount.rs` and +# `tests/preset_metrics.rs` read the golden vectors under `tools/kde_golden` +# (Plasma's painted pixels, the Oxygen schemes and its control sizes), and +# `tests/density_gaps.rs` reads +# `engineering/docs/density-projection-gaps.md`; left in, they would not +# compile from the package and `cargo test` there would fail. +exclude = [ + "tests/breeze_reference.rs", + "tests/contrast.rs", + "tests/density_gaps.rs", + "tests/mount.rs", + "tests/preset_metrics.rs", +] + +[features] +# Both off by default. Plasma's faces are Noto Sans (text) and Hack +# (monospace); without either feature the typography keeps Teksilo's bundled +# Inter and JetBrains Mono, at the sizes and line boxes Noto Sans and Hack +# have at Plasma's default fonts. +# +# `bundled-fonts` embeds Noto Sans v2.015 (variable, upright and italic, +# OFL-1.1) and Hack v3.003 (regular, bold and their italics, MIT plus the +# Bitstream Vera licence), about 5.6 MB in all, switches the families to +# "Noto Sans" and "Hack", and exposes `font_registrar()`. +bundled-fonts = ["dep:teksilo-text"] +# `system-fonts` switches the families to "Noto Sans" and "Hack" without +# embedding them, for a desktop that has them installed, as every Plasma +# desktop does; the text engine finds them among the installed fonts, and +# falls back to the bundled default on a machine without them. +system-fonts = [] + +[dependencies] +teksilo-core = { workspace = true } +teksilo-tokens = { workspace = true } +# The Tier-3 widget styles this preset installs in `style_slots` are built +# from the `Recipe*Style` defaults and layout primitives of teksilo-widgets, +# drawing through teksilo-canvas. teksilo-widgets does not depend on the +# theme crates, so this is acyclic. +teksilo-canvas = { workspace = true } +teksilo-widgets = { workspace = true } +# The KDE colour-scheme model: the stock Breeze schemes, the resolved +# palette, Plasma's accent application and the colour arithmetic. +teksilo-kde-scheme = { workspace = true } +thiserror = { workspace = true } +# Only pulled in under `bundled-fonts`, for the `FontRegistrar` types. +# Declared directly, with `default-features = false`, so it does not turn on +# teksilo-text's Noto fallback fonts: which of those ship is the app's call. +teksilo-text = { path = "../teksilo-text", version = "0.19.0", optional = true, default-features = false } + +[dev-dependencies] +# Property-based testing: `tests/contrast.rs` checks every declared text and +# backdrop pair over random coherent schemes. `cargo-fuzz` needs a nightly +# toolchain, which is not a guaranteed CI dependency; proptest gives the same +# coverage plus shrinking on stable (see the workspace `Cargo.toml`). +proptest = { workspace = true } +# `tests/fonts.rs` checks every file under `fonts/` against the sha256 +# recorded for its upstream release. +sha2 = { workspace = true } +# Reads the golden vectors in `tools/kde_golden/vectors`. +serde_json = { workspace = true } +# `tests/mount.rs` and `tests/preset_metrics.rs` build real widget trees; +# `lit!` supplies the untranslated labels those constructors take. +teksilo-i18n = { workspace = true } +# The models behind the tables and tab bars those tests mount. +teksilo-data = { workspace = true } +# `tests/preset_metrics.rs` reuses the conformance harness's +# `in_tappable_row`, the row geometry that denies the miss-only slop pass, +# rather than a copy that could drift from the gate's. Path-only (no +# version): the crate is `publish = false`, and cargo strips versionless +# path dev-dependencies at publish time. +teksilo-target-conformance = { path = "../teksilo-target-conformance" } +# `tests/native.rs` hands `native_theme` to the app's native-theme seam, +# `resolve_native_theme`, with the desktop report the platform builds. +# Neither crate depends on this one, so this is acyclic. +teksilo-app = { workspace = true } +teksilo-platform = { workspace = true } +# `tests/breeze_reference.rs` renders the styles through the production wgpu +# renderer, with the text engine and the glyph atlas the previewer's +# snapshots use, and compares the pixels with Plasma's. +teksilo-render = { workspace = true } +teksilo-text = { workspace = true } +wgpu = { workspace = true } +pollster = { workspace = true } + +[package.metadata.docs.rs] +all-features = true diff --git a/crates/teksilo-theme-kde/fonts/Hack-Bold.ttf b/crates/teksilo-theme-kde/fonts/Hack-Bold.ttf new file mode 100644 index 000000000..7ff497528 Binary files /dev/null and b/crates/teksilo-theme-kde/fonts/Hack-Bold.ttf differ diff --git a/crates/teksilo-theme-kde/fonts/Hack-BoldItalic.ttf b/crates/teksilo-theme-kde/fonts/Hack-BoldItalic.ttf new file mode 100644 index 000000000..3b137d971 Binary files /dev/null and b/crates/teksilo-theme-kde/fonts/Hack-BoldItalic.ttf differ diff --git a/crates/teksilo-theme-kde/fonts/Hack-Italic.ttf b/crates/teksilo-theme-kde/fonts/Hack-Italic.ttf new file mode 100644 index 000000000..d26198a32 Binary files /dev/null and b/crates/teksilo-theme-kde/fonts/Hack-Italic.ttf differ diff --git a/crates/teksilo-theme-kde/fonts/Hack-Regular.ttf b/crates/teksilo-theme-kde/fonts/Hack-Regular.ttf new file mode 100644 index 000000000..92a90cb06 Binary files /dev/null and b/crates/teksilo-theme-kde/fonts/Hack-Regular.ttf differ diff --git a/crates/teksilo-theme-kde/fonts/NotoSans-Italic-Variable.ttf b/crates/teksilo-theme-kde/fonts/NotoSans-Italic-Variable.ttf new file mode 100644 index 000000000..b2e71e21c Binary files /dev/null and b/crates/teksilo-theme-kde/fonts/NotoSans-Italic-Variable.ttf differ diff --git a/crates/teksilo-theme-kde/fonts/NotoSans-Variable.ttf b/crates/teksilo-theme-kde/fonts/NotoSans-Variable.ttf new file mode 100644 index 000000000..ad87d1d91 Binary files /dev/null and b/crates/teksilo-theme-kde/fonts/NotoSans-Variable.ttf differ diff --git a/crates/teksilo-theme-kde/fonts/hack-LICENSE.txt b/crates/teksilo-theme-kde/fonts/hack-LICENSE.txt new file mode 100644 index 000000000..08927e504 --- /dev/null +++ b/crates/teksilo-theme-kde/fonts/hack-LICENSE.txt @@ -0,0 +1,45 @@ +The work in the Hack project is Copyright 2018 Source Foundry Authors and licensed under the MIT License + +The work in the DejaVu project was committed to the public domain. + +Bitstream Vera Sans Mono Copyright 2003 Bitstream Inc. and licensed under the Bitstream Vera License with Reserved Font Names "Bitstream" and "Vera" + +### MIT License + +Copyright (c) 2018 Source Foundry Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +### BITSTREAM VERA LICENSE + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". + +This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. + +The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. diff --git a/crates/teksilo-theme-kde/fonts/noto-sans-LICENSE.txt b/crates/teksilo-theme-kde/fonts/noto-sans-LICENSE.txt new file mode 100644 index 000000000..6843f3187 --- /dev/null +++ b/crates/teksilo-theme-kde/fonts/noto-sans-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/crates/teksilo-theme-kde/src/breeze_config.rs b/crates/teksilo-theme-kde/src/breeze_config.rs new file mode 100644 index 000000000..410894c79 --- /dev/null +++ b/crates/teksilo-theme-kde/src/breeze_config.rs @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze widget-style settings a desktop configures, carried on a +//! theme as an extension ([`BreezeStyleConfig`]). + +use std::time::Duration; + +use teksilo_kde_scheme::{BreezeSettings, KdeDesktop}; + +/// The Breeze widget-style settings of a desktop: `breezerc` and the +/// animation speed. +/// +/// A [`teksilo_core::styles::Theme`] extension. [`Default`] is a desktop +/// whose `breezerc` and `[KDE]` group set nothing. +/// +/// It holds what `breezerc` sets (read by +/// [`BreezeSettings::from_config`](teksilo_kde_scheme::BreezeSettings::from_config)) +/// and the desktop's animation speed, `kdeglobals [KDE] +/// AnimationDurationFactor`. Every theme of this preset carries one: +/// [`crate::light`] and the other stock themes carry the defaults, and +/// [`crate::from_desktop`] the desktop's own. The styles read it from the +/// theme they paint under, so a theme without one paints the defaults. +/// +/// What the preset does with each setting: +/// +/// | Setting | Effect | +/// | --- | --- | +/// | `[Style] MenuOpacity` | Below 100, a menu's panel is filled at that opacity and outlined in the window text at 25 % ([`crate::styles::popup`]) | +/// | `[Common] ShadowSize`, `ShadowStrength`, `ShadowColor` | The window shadow under every popup, `shadow_lg` and `shadow_inner_lg` ([`crate::shape::breeze_shape_for`]) | +/// | `[Style] ProgressBarAnimated`, `ProgressBarBusyStepDuration` | Whether a busy progress bar's stripes move, and the period they move in ([`crate::styles::progress_bar`]) | +/// | `[KDE] AnimationDurationFactor` | The motion tokens ([`crate::motion::desktop_motion`]); 0 is the desktop asking for reduced motion ([`BreezeStyleConfig::prefers_reduced_motion`]) | +/// +/// The other keys are carried for an application to read and change +/// nothing the preset paints, each for its reason: +/// +/// - `AnimationsEnabled`, `AnimationsDuration` and `AnimationSteps` time +/// Breeze's own style animations; Teksilo's controls take their timing +/// from the motion tokens, which follow Kirigami's rule for the factor +/// (spec j.2), so a desktop's factor reaches them and these do not. +/// - `MenuItemDrawStrongFocus`: a highlighted menu row is always washed and +/// outlined, Breeze's default. With the key off Breeze marks the row the +/// keyboard is on with a 1 px underline only, a weaker focus indicator +/// than the lifted outline the preset keeps. +/// - `ViewDrawFocusIndicator`: the focus frame of a focused list row is +/// the view's keyboard focus indicator, and the preset keeps it. +/// - `ScrollBarAddLineButtons`, `ScrollBarSubLineButtons`, +/// `SliderDrawTickMarks`, `ViewDrawTreeBranchLines`, +/// `ViewInvertSortIndicator`, `TabBarDrawCenteredTabs`, +/// `ToolBarDrawItemSeparator`, `DockWidgetDrawFrame`, +/// `SidePanelDrawFrame`, `StackedWidgetTransitionsEnabled` and +/// `MnemonicsMode` change parts the widgets build themselves (a scroll +/// bar's buttons, a slider's ticks, a tree's lines, a tab bar's +/// alignment, a toolbar's separators, a stack's transition, the mnemonic +/// underline), which a style is not asked to draw. +/// +/// ``` +/// use teksilo_theme_kde::{self as kde, BreezeStyleConfig}; +/// use teksilo_theme_kde::scheme::{KdeColorScheme, KdeDesktop}; +/// use teksilo_tokens::TargetDensity; +/// +/// let mut desktop = KdeDesktop::new(KdeColorScheme::breeze_light()); +/// desktop.breeze.menu_opacity = 80; +/// let theme = kde::from_desktop(&desktop, TargetDensity::Compact); +/// let config = theme.extension::().unwrap(); +/// assert_eq!(config.menu_opacity(), 0.8); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[non_exhaustive] +pub struct BreezeStyleConfig { + /// What `breezerc` sets. + pub settings: BreezeSettings, + /// `kdeglobals [KDE] AnimationDurationFactor`, `None` when the desktop + /// does not set it. + pub animation_factor: Option, +} + +impl BreezeStyleConfig { + /// The settings `desktop` configures. + pub fn from_desktop(desktop: &KdeDesktop) -> BreezeStyleConfig { + BreezeStyleConfig { + settings: desktop.breeze, + animation_factor: desktop.animation_duration_factor, + } + } + + /// `[S]` The opacity of a menu's panel, `MenuOpacity / 100`: 1 unless + /// the desktop makes menus translucent. + pub fn menu_opacity(&self) -> f32 { + f32::from(self.settings.menu_opacity.min(100)) / 100.0 + } + + /// `[S]` Whether menus are translucent: `MenuOpacity` below 100, which + /// is when Breeze also outlines the panel in the window text at 25 %. + pub fn menu_is_translucent(&self) -> bool { + self.settings.menu_opacity < 100 + } + + /// `[Hp]` The strength of the window shadow, `ShadowStrength / 255`. + pub fn shadow_strength(&self) -> f32 { + f32::from(self.settings.shadow_strength) / 255.0 + } + + /// `[S]` The period of a busy progress bar's stripes, or `None` when + /// they stand still: `ProgressBarAnimated` is off, or + /// `ProgressBarBusyStepDuration` is not a positive number of + /// milliseconds (`[unpublished]`: a period of no length cannot move). + pub fn busy_step(&self) -> Option { + let ms = self.settings.progress_bar_busy_step_duration_ms; + (self.settings.progress_bar_animated && ms > 0) + .then(|| Duration::from_millis(u64::try_from(ms).unwrap_or(u64::MAX))) + } + + /// Whether the desktop asks for reduced motion: its animation duration + /// factor is exactly 0, as the KDE desktop portal reports it. The + /// platform reads the same factor into the accessibility preferences, + /// which is how the framework's transitions learn to snap. + pub fn prefers_reduced_motion(&self) -> bool { + self.animation_factor == Some(0.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColorScheme; + + #[test] + fn the_defaults_are_a_desktop_that_sets_nothing() { + let c = BreezeStyleConfig::default(); + assert_eq!(c.settings, BreezeSettings::default()); + assert_eq!(c.animation_factor, None); + assert_eq!(c.menu_opacity(), 1.0); + assert!(!c.menu_is_translucent()); + assert_eq!(c.shadow_strength(), 1.0); + assert_eq!(c.busy_step(), Some(Duration::from_millis(800))); + assert!(!c.prefers_reduced_motion()); + let desktop = KdeDesktop::new(KdeColorScheme::breeze_light()); + assert_eq!(BreezeStyleConfig::from_desktop(&desktop), c); + } + + #[test] + fn a_desktop_carries_its_settings_through() { + let mut desktop = KdeDesktop::new(KdeColorScheme::breeze_dark()); + desktop.breeze.menu_opacity = 75; + desktop.breeze.shadow_strength = 51; + desktop.breeze.progress_bar_busy_step_duration_ms = 400; + desktop.animation_duration_factor = Some(0.0); + let c = BreezeStyleConfig::from_desktop(&desktop); + assert_eq!(c.menu_opacity(), 0.75); + assert!(c.menu_is_translucent()); + assert!((c.shadow_strength() - 0.2).abs() < 1e-6); + assert_eq!(c.busy_step(), Some(Duration::from_millis(400))); + assert!(c.prefers_reduced_motion()); + } + + #[test] + fn a_busy_bar_stands_still_when_its_animation_is_off_or_has_no_period() { + let mut c = BreezeStyleConfig::default(); + c.settings.progress_bar_animated = false; + assert_eq!(c.busy_step(), None); + c.settings.progress_bar_animated = true; + c.settings.progress_bar_busy_step_duration_ms = 0; + assert_eq!(c.busy_step(), None); + c.settings.progress_bar_busy_step_duration_ms = -5; + assert_eq!(c.busy_step(), None); + } + + #[test] + fn only_an_exact_zero_factor_asks_for_reduced_motion() { + let mut c = BreezeStyleConfig::default(); + for (factor, reduced) in [ + (None, false), + (Some(1.0), false), + (Some(0.5), false), + (Some(1e-9), false), + (Some(0.0), true), + ] { + c.animation_factor = factor; + assert_eq!(c.prefers_reduced_motion(), reduced, "{factor:?}"); + } + } +} diff --git a/crates/teksilo-theme-kde/src/color.rs b/crates/teksilo-theme-kde/src/color.rs new file mode 100644 index 000000000..f172bebff --- /dev/null +++ b/crates/teksilo-theme-kde/src/color.rs @@ -0,0 +1,1157 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! A resolved KDE palette projected onto Teksilo's [`ColorTokens`]. +//! +//! The source is the scheme's [`ResolvedPalette`]: Qt's palette roles +//! (Window, Base, Text, Highlight, ...) and the seven colour sets with their +//! decorations and computed backgrounds. This module chooses one source per +//! token, applies the Breeze painting rules that combine two of them (a frame +//! outline is the window colour moved toward the window text by the frame +//! contrast), and lifts the colours that would otherwise fail WCAG. +//! +//! `X over Y at a` below means `X` at alpha `a` composited on the opaque +//! `Y`; with both opaque that is KDE's mix from `Y` toward `X` by `a`, which +//! is how it is computed. `fc` is the scheme's frame contrast (0.2 for +//! Breeze). Every token is computed from one colour state: the Active one +//! for an active window, the Inactive one for an inactive window +//! ([`crate::palette::KdePalette`] keeps both), except the disabled tokens, +//! which always read the Disabled state, and the tooltip, which always reads +//! the Active one, as Qt's palette does. +//! +//! ## Where this preset departs from the scheme +//! +//! KDE never adjusts a colour for readability, and several stock Breeze +//! colours fall short of WCAG 2.x. Each departure below is a rule, applied +//! to whatever scheme the theme is built from, so a scheme whose colours +//! already pass keeps them exactly. Each one is marked at its assignment +//! and covered by a test that the lifted colour passes and a test that the +//! vendor colour fails. The palette extension keeps the vendor colours. +//! +//! - **D1, text on the accent.** Breeze Light's selection text is white on +//! its 61,174,233 highlight, 2.5:1 against SC 1.4.3's 4.5:1. The scheme's +//! selection text is kept when it reaches the floor on the fill it sits +//! on; otherwise the text is black or white by Plasma's own rule for text +//! on an accent (the one it writes when it applies an accent colour), +//! and the other of the two when that one still falls short. Where +//! Teksilo paints text it does not choose on a selection (a table cell, +//! a selected menu row, selected text in a field, all in the primary text +//! colour), the selection fill keeps its hue and moves in HCY luma until +//! that text reaches the floor; for Breeze Light nothing moves, and in +//! Breeze Dark the fill darkens, as Plasma's own accent application +//! darkens it. The accent's hover and press shades move the same way if +//! the accent text falls short on them, and so do the hover and press +//! washes made from the accent if the primary text falls short on them. +//! A selection or a wash moves only toward the window's end of the scale +//! (lighter in a light scheme), never across the text to the other end: +//! under a mid-grey text a light fill would otherwise turn black. +//! - **D2, inactive text.** The inactive foreground (secondary text, +//! placeholders, line numbers, a tooltip's shortcut, the title of an +//! unselected tab) is 3.7:1 on the Breeze Light window and 2.5:1 on its +//! unselected tab, which Breeze paints as the window darkened 120 percent +//! (k.5.14). It moves in luma to 4.5:1 on every surface text sits on, the +//! hover and press washes included, and on an unselected tab's face at +//! rest and hovered (the window moved 20 percent toward the hover colour), +//! computed from the same colour state. In Breeze Light the tab is the +//! darkest of those surfaces, so it decides how far the text moves: it +//! ends 2.0:1 from the primary text. A scheme whose tab face would bring +//! the two grades together gets them together: legibility wins over the +//! distinction between them. The faces themselves are the +//! specification's; under high contrast the secondary text is the primary +//! text, lifted on them in the same way where it falls short there. +//! - **D3, links.** Breeze Light's link colour is 3.8:1 on the window; the +//! unvisited, visited and hover links move to 4.5:1 the same way. +//! - **D4, status colours.** The negative, neutral and positive foregrounds +//! (4.3:1, 2.8:1 and 2.9:1 on Breeze Light's view) move to 4.5:1 on every +//! surface and on their own status background; as borders they move to +//! SC 1.4.11's 3:1. +//! - **D5, the focus decoration.** The focus colour, 61,174,233, is 2.5:1 +//! on the Breeze Light view; the focus ring moves to 3:1 on every control +//! surface. It stays opaque. +//! - **D6, frame outlines.** At the frame contrast 0.2 the frame outline is +//! 1.5:1 on the window. `border` keeps that outline, which Breeze draws +//! around passive frames; `border_strong` and `divider_strong`, which +//! Teksilo draws where a boundary must be seen, move to 3:1. +//! - **D9, the scroll-bar thumb.** Breeze paints the idle thumb in the +//! window text at an effective 17.5 % over the window, 1.4:1; the hovered +//! one in the hover colour at 50 %. Both move to 3:1. +//! - **D10, the scheme's own text.** The window, view and tooltip text +//! move to the text floor where they fall short, toward their own end of +//! the scale, and so does the button text Breeze mixes a checked button's +//! face and the header and combo box arrows from, on the button colour. +//! Stock Breeze's never do; a scheme whose text is a mid grey does, and +//! so does an inactive window whose scheme's inactive effects fade or +//! shade its text toward its background. +//! - **D11, surfaces in the middle of the scale.** Where black (under a +//! dark text) or white (under a light one) cannot reach the text floor on +//! a surface text is set on, the surface moves in luma away from the text +//! until it can, so that every lift above can succeed. It happens in an +//! inactive window whose scheme's inactive effects shade, lighten or +//! darken its backgrounds toward the middle of the scale, and under high +//! contrast, whose 7:1 needs a surface further from the middle; stock +//! Breeze, whose inactive effects are off, keeps every surface. All the +//! window's surfaces move the same way, the one the window lies in from +//! its text, and the tooltip by its own text. The palette's Breeze +//! combinations ([`crate::palette::BreezeColors`]) are made from the +//! moved surfaces, so a push button, a table header or a check box is +//! painted on the button colour its text was lifted on; the faces the +//! styles mix from it (a default, checked or pressed button, a hovered or +//! pressed header section) move as the accent washes of D1 do, until the +//! primary text reaches the floor on them, and a check box's mark is +//! lifted to the non-text floor on its washed box. The window moves on +//! until black or white also reaches the floor on the two faces Breeze +//! computes from it for an unselected tab, so D2 can lift the tab's title +//! there; the faces keep the specification's rule, computed from the moved +//! window. +//! +//! D7 and D8 are row heights, not colours, and live with the styles. The +//! specification has no rule for D10 and D11 either: like D1 to D9 they are +//! Teksilo's. +//! +//! A scheme colour may be translucent (the `r,g,b,a` and `#AARRGGBB` +//! forms). The specification is silent on how such a text or decoration +//! should be painted; every colour a rule above lifts is first made opaque, +//! composited over the surface it is lifted on first (the window, or the +//! view for the editor's text), so every lifted token is opaque and its +//! contrast is the contrast it is painted at. +//! +//! ## The high-contrast projection +//! +//! Under the OS "increase contrast" preference the tokens are rebuilt from +//! the scheme with a frame contrast of 1 and a contrast of 10, every text +//! floor raised to 7:1 (SC 1.4.6) and every non-text floor to 4.5:1, and the +//! secondary text grades made the primary ones (the secondary text lifted +//! further on an unselected tab's faces where the primary text falls short +//! there, D2). The accent moves in luma until its text reaches 7:1 on it +//! and it reaches 4.5:1 on the control surfaces. In a light scheme no single colour lets black text reach 7:1 +//! (the fill must be light) while the fill reaches 4.5:1 on a near-white +//! window (the fill must be dark), so there the accent darkens and its text +//! is white: the one place this preset overrides Plasma's black-text rule +//! of D1. +//! +//! A hovered or focused field, check box, radio button, switch or slider +//! handle is outlined in the scheme's hover or focus decoration in place of +//! its lifted outline, and a spin box's hovered half draws its chevron, its +//! only visual, in the hover decoration. Under high contrast the style +//! lifts that decoration the same way, to 4.5:1 on the control surfaces and +//! on the control's own face ([`high_contrast_boundary`]), so the control +//! keeps a 4.5:1 boundary while the pointer is over it. In the normal +//! projection it keeps the scheme's colour, as Breeze paints it. +//! +//! The selection fill does not move with the accent: it carries the +//! window's primary text, which must reach 7:1 on it, so where that text +//! falls short it moves toward the window until the text reaches 7:1 +//! (Breeze Light's highlight lightens, +//! Breeze Dark's darkens). That leaves it closer to the window than in the +//! normal projection, whose text floor is 4.5:1: 1.9:1 on Breeze Light's +//! window and 2.2:1 on Breeze Dark's, against 2.2:1 and 3.4:1. No colour +//! can do better, in any scheme: the text is at 7:1 on the window too, so +//! a fill at 7:1 under the text and 4.5:1 on the window would put the +//! darkest and the lightest of the three colours at least 31.5:1 apart, +//! whatever their order, beyond the 21:1 of black on white. The fill is a +//! backdrop for text, not a boundary; a style that must show a selection's +//! extent at 4.5:1 outlines it in a colour that reaches it, such as +//! `focus_ring`. +//! +//! ## Kept from Int UI +//! +//! KDE has no colours for four tokens, which keep Teksilo's Int UI values +//! for the scheme's appearance: `search_match_bg` and +//! `search_match_current_bg` (KDE has no find-match role), `scrim` (KDE +//! dims nothing behind a dialog) and `chart_palette` (KDE has no +//! data-visualisation sequence; Int UI's is the colour-blind-safe Okabe-Ito +//! one). +//! +//! ## Known limitations +//! +//! - `surface_selected` serves two kinds of text: cells and rows Teksilo +//! paints in the primary text colour, and rows whose style paints the +//! on-accent text. In Breeze Dark those are white and black, and no fill +//! carries both, so the token is the D1 fill for the primary text, and a +//! style that paints on-accent text on a selection draws on `accent`. +//! - Under high contrast the selection fill stays below 4.5:1 on the +//! window, which no fill carrying the window's text at 7:1 can reach (see +//! the high-contrast projection above). +//! - Teksilo sets the window text on the view too, so a scheme whose view +//! is in the other tone from its window (a dark view in a light window) +//! cannot keep both: D11 moves the view toward the window's tone until +//! the window text can be read on it. +//! - D11 moves a surface only until black or white reaches the floor on +//! it, so on a surface it moved, a lifted colour can end at black or +//! white and lose its hue. + +use teksilo_kde_scheme::math::{ + Hcy, LumaDirection, black_or_white_on, hsv_darker, hsv_lighter, lift_luma_toward, + lift_luma_until, luma, mix, read_channel, +}; +use teksilo_kde_scheme::{ + BackgroundRole, ColorSet, ColorState, DecorationRole, ForegroundRole, KdeColor, PaletteRole, + ResolvedPalette, +}; +use teksilo_tokens::contrast::composite_contrast; +use teksilo_tokens::{Color, ColorTokens}; + +use crate::styles::metrics::{TAB_HOVER_MIX, TAB_UNSELECTED_DARKER}; + +/// The contrast floors a projection lifts colours to. +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct Floors { + /// Text on its backdrop. + pub text: f32, + /// A control boundary, focus ring or other graphical object on its + /// backdrop. + pub non_text: f32, +} + +/// `[wcag]` SC 1.4.3 (text, 4.5:1) and SC 1.4.11 (non-text, 3:1). +pub(crate) const AA: Floors = Floors { + text: 4.5, + non_text: 3.0, +}; + +/// `[wcag]` SC 1.4.6 (text, 7:1), with non-text raised to 4.5:1, the +/// targets of Teksilo's high-contrast projection. +pub(crate) const ENHANCED: Floors = Floors { + text: 7.0, + non_text: 4.5, +}; + +/// `[Hp]` The alpha of Breeze's hover wash on an item and of its press +/// blend on a button: the style's blend value. +const BLEND: f64 = 0.3; + +/// `[Hp]` The idle scroll-bar thumb: the window text with alpha times 0.5, +/// times 0.7 while the pointer is away from the bar, painted at alpha times +/// 0.5 over the window. +const THUMB_ALPHA: f64 = 0.5 * 0.7 * 0.5; + +/// `[Hp]` The hovered scroll-bar thumb: the hover colour painted at 0.5 +/// over the window. +const THUMB_HOVER_ALPHA: f64 = 0.5; + +/// `[qt]` `[Hp]` Qt's `lighter` and `darker` factor Breeze uses for a +/// hovered or pressed highlight. +const FOCUS_LIGHTEN_FACTOR: i32 = 110; + +/// Everything a projection is computed from. +pub(crate) struct Mapping<'a> { + /// The resolved scheme. + pub palette: &'a ResolvedPalette, + /// The colour state the window paints in: Active or Inactive. + pub state: ColorState, + /// The frame contrast `fc`, in `0..=1`. + pub frame_contrast: f64, + /// Whether this is the high-contrast projection. + pub high_contrast: bool, + /// Whether the scheme is dark, which picks the Int UI values kept. + pub dark: bool, +} + +/// `top` at alpha `alpha` over the opaque `bottom`. +fn over(top: KdeColor, alpha: f64, bottom: KdeColor) -> KdeColor { + mix(bottom, top, alpha) +} + +/// The lowest WCAG contrast `fg` reaches, painted over each of `on`. +pub(crate) fn worst_contrast(fg: KdeColor, on: &[KdeColor]) -> f32 { + let fg = fg.to_color(); + on.iter() + .map(|bg| composite_contrast(fg, bg.to_color())) + .fold(f32::INFINITY, f32::min) +} + +fn passes(fg: KdeColor, on: &[KdeColor], floor: f32) -> bool { + worst_contrast(fg, on) >= floor +} + +/// `[wcag]` `color` as painted on `backdrop`, made opaque: a translucent +/// colour composited over the opaque `backdrop`, an opaque one unchanged. +fn opaque_on(color: KdeColor, backdrop: KdeColor) -> KdeColor { + if color.a == u16::MAX { + return color; + } + let solid = |c: KdeColor| KdeColor { a: u16::MAX, ..c }; + over(solid(color), read_channel(color.a), solid(backdrop)) +} + +/// `[wcag]` [`opaque_on`] for the colours a style paints: `color` as painted +/// on `backdrop`, whose own alpha is ignored, made opaque. +pub(crate) fn opaque_over(color: Color, backdrop: Color) -> Color { + opaque_on(KdeColor::from_color(color), KdeColor::from_color(backdrop)).to_color() +} + +/// `[wcag]` `color` with its HCY luma moved toward more contrast until it +/// reaches `floor` on every one of `on`; unchanged when it already does. +/// +/// A translucent `color` is first made opaque over the first of `on`, the +/// surface it is chiefly painted on, so every lifted token is opaque. +fn lift(color: KdeColor, on: &[KdeColor], floor: f32) -> KdeColor { + let color = on.first().map_or(color, |&bg| opaque_on(color, bg)); + lift_luma_until(color, on, floor) +} + +/// `[wcag]` A hover or focus decoration a style paints in place of a +/// control's only boundary, as it paints it: under the high-contrast +/// preference, [`lift`]ed to the enhanced non-text floor on the window, the +/// view and the frame background of `colors` (`surface_main`, +/// `surface_content`, `surface_raised`) and on `face`, the control's own +/// fill the outline meets; otherwise `decoration` unchanged, the scheme's +/// own colour. +/// +/// The hovered or focused outline of a field, a check box, a radio button, +/// a switch and a slider handle replaces the outline lifted at rest, and a +/// spin box's hovered chevron is drawn in it, so without this a hovered +/// control would lose the 4.5:1 boundary the high-contrast projection gives +/// it at rest (2.2:1 for Breeze Light's hover colour on its window, 2.5:1 +/// on its view). +pub(crate) fn high_contrast_boundary( + decoration: Color, + colors: &ColorTokens, + face: Color, + high_contrast: bool, +) -> Color { + if !high_contrast { + return decoration; + } + let on = [ + colors.surface_main, + colors.surface_content, + colors.surface_raised, + face, + ] + .map(KdeColor::from_color); + lift(KdeColor::from_color(decoration), &on, ENHANCED.non_text).to_color() +} + +/// `[wcag]` [`lift`] in the given direction only: a text lifted toward its +/// own end of the scale, so it never crosses to the other side of the +/// surfaces it is set on, where it might reach the floor on them but not on +/// a fill moved away from it. +/// +/// D10 for the button text the palette's Breeze combinations mix a checked +/// button's face and the arrows from goes through here too. +pub(crate) fn lift_toward( + color: KdeColor, + on: &[KdeColor], + floor: f32, + toward: LumaDirection, +) -> KdeColor { + let color = on.first().map_or(color, |&bg| opaque_on(color, bg)); + lift_luma_toward(color, on, floor, toward) +} + +/// The other direction. +fn opposite(direction: LumaDirection) -> LumaDirection { + match direction { + LumaDirection::Lighter => LumaDirection::Darker, + LumaDirection::Darker => LumaDirection::Lighter, + } +} + +/// The direction away from `text` on the luma scale, as seen from +/// `surface`: lighter for a surface at least as light as the text. +fn away_from(text: KdeColor, surface: KdeColor) -> LumaDirection { + if luma(surface) >= luma(text) { + LumaDirection::Lighter + } else { + LumaDirection::Darker + } +} + +/// `[wcag]` D11: `surface` moved in luma toward `direction`, away from +/// the text set on it, until the end of the scale on the text's side (black +/// under a dark text, white under a light one) reaches `floor` on it; +/// unchanged when it already does, as every surface of a scheme with +/// readable text does. +/// +/// This keeps every surface where a text lift can reach the floor. No text +/// reaches 7:1 on a surface in the middle of the scale, and no text reaches +/// 4.5:1 on a mid-scale surface and on the surfaces either side of it at +/// once, as happens in an inactive window when the scheme's inactive +/// effects shade its backgrounds toward the middle. The surface is measured +/// as opaque and keeps its alpha. +fn carry(surface: KdeColor, direction: LumaDirection, floor: f32) -> KdeColor { + let extreme = match direction { + LumaDirection::Lighter => KdeColor::BLACK, + LumaDirection::Darker => KdeColor::WHITE, + }; + let solid = KdeColor { + a: u16::MAX, + ..surface + }; + let carried = lift_luma_toward(solid, &[extreme], floor, direction); + KdeColor { + a: surface.a, + ..carried + } +} + +/// `[Hp]` An unselected tab's face (k.5.14): the window darkened 120 +/// percent, Qt's `darker`. +pub(crate) fn tab_unselected_face(window: KdeColor) -> KdeColor { + hsv_darker(window, TAB_UNSELECTED_DARKER) +} + +/// `[Hp]` A hovered unselected tab's face (k.5.14): the window moved 20 +/// percent toward the hover colour. +pub(crate) fn tab_hovered_face(window: KdeColor, hover: KdeColor) -> KdeColor { + mix(window, hover, TAB_HOVER_MIX) +} + +/// `[wcag]` D11 for the window: [`carry`]ed, and then moved further toward +/// `direction` until the end of the scale on the text's side also reaches +/// `floor` on the two faces Breeze derives from the window, an unselected +/// tab's and a hovered one's (k.5.14, with `hover` the hover colour), so a +/// tab's title can be lifted on them. The faces are always computed from +/// the window as the specification gives them; it is the window they are +/// computed from that moves. Unchanged when the end already reaches the +/// floor on all three, as in every stock Breeze scheme. +fn carry_window( + window: KdeColor, + hover: KdeColor, + direction: LumaDirection, + floor: f32, +) -> KdeColor { + let carried = carry(window, direction, floor); + let extreme = match direction { + LumaDirection::Lighter => KdeColor::BLACK, + LumaDirection::Darker => KdeColor::WHITE, + }; + let solid = |c: KdeColor| KdeColor { a: u16::MAX, ..c }; + let reaches = |w: KdeColor| { + let w = solid(w); + passes( + extreme, + &[w, tab_unselected_face(w), tab_hovered_face(w, hover)], + floor, + ) + }; + if reaches(carried) { + return carried; + } + let hcy = Hcy::from_color(solid(carried)); + let end = match direction { + LumaDirection::Lighter => 1.0, + LumaDirection::Darker => 0.0, + }; + let at = |step: u32| { + let t = f64::from(step) / f64::from(WINDOW_CARRY_STEPS); + Hcy { + y: hcy.y + (end - hcy.y) * t, + ..hcy + } + .to_color() + }; + let moved = (1..=WINDOW_CARRY_STEPS) + .map(at) + .find(|&w| reaches(w)) + .unwrap_or_else(|| at(WINDOW_CARRY_STEPS)); + KdeColor { + a: carried.a, + ..moved + } +} + +/// The luma steps [`carry_window`] takes from the carried window to the end +/// of the scale, as many as the scheme crate's own luma lifts take. +const WINDOW_CARRY_STEPS: u32 = 256; + +/// `[wcag]` D11 for one colour state: the window's, view's and button's +/// backgrounds and Breeze's frame background, each carried in the direction +/// the window lies from its text until black or white reaches `floor` on +/// it, and that direction. +/// +/// The colour tokens and the palette's Breeze combinations +/// ([`crate::palette::BreezeColors`]) both read their surfaces here, so a +/// style painting a button face paints the surface the text was lifted on. +#[derive(Debug, Clone, Copy)] +pub(crate) struct Surfaces { + /// Where the window lies from its text: the way every surface moves. + pub polarity: LumaDirection, + /// `[qt]` Window. + pub window: KdeColor, + /// `[qt]` Base. + pub base: KdeColor, + /// `[qt]` Button. + pub button: KdeColor, + /// `[Hp]` The frame background, the window 30 % of the way to the view, + /// mixed from the carried two and carried again: a mix of two surfaces + /// of different hues can be darker than both. + pub frame_background: KdeColor, +} + +impl Surfaces { + /// The surfaces of `state` of `palette`, carried to `floor`. + pub(crate) fn of(palette: &ResolvedPalette, state: ColorState, floor: f32) -> Surfaces { + let role = |r: PaletteRole| palette.role(state, r); + let polarity = away_from(role(PaletteRole::WindowText), role(PaletteRole::Window)); + let surface = |c: KdeColor| carry(c, polarity, floor); + let hover = palette + .set(state, ColorSet::View) + .decoration(DecorationRole::Hover); + let window = carry_window(role(PaletteRole::Window), hover, polarity, floor); + let base = surface(role(PaletteRole::Base)); + Surfaces { + polarity, + window, + base, + button: surface(role(PaletteRole::Button)), + frame_background: surface(mix(window, base, BLEND)), + } + } + + /// `[wcag]` D11 for any other surface of the same window. + pub(crate) fn carry(&self, c: KdeColor, floor: f32) -> KdeColor { + carry(c, self.polarity, floor) + } + + /// The end of the scale the window's text lies toward, which D10 lifts + /// a text toward. + pub(crate) fn text_side(&self) -> LumaDirection { + opposite(self.polarity) + } +} + +/// `[wcag]` D1 for a face a style paints under the primary text (a default +/// or checked push button's face, a pressed one, a hovered or pressed +/// header section): `face` moved, as an accent wash is, toward the +/// window's end of the scale until the primary text of `colors` reaches the +/// text floor on it, 7:1 under `high_contrast` and 4.5:1 otherwise; +/// unchanged when it already does, as in stock Breeze. +/// +/// These faces are mixes Breeze makes from the button colour toward the +/// highlight, the hover or focus colour or the button text, so a style +/// computes them from the colours it paints, the app's accent included; +/// the tokens' text is lifted on the button colour itself. +pub(crate) fn face_under_primary_text( + face: Color, + colors: &ColorTokens, + high_contrast: bool, +) -> Color { + let floor = if high_contrast { ENHANCED } else { AA }.text; + let text = KdeColor::from_color(colors.text_primary); + let toward = away_from(text, KdeColor::from_color(colors.surface_main)); + let solid = opaque_on( + KdeColor::from_color(face), + KdeColor::from_color(colors.surface_main), + ); + fill_toward(solid, &[text], floor, toward).to_color() +} + +/// `[wcag]` A mark a style draws on faces of its own (a check box's mark, a +/// combo box's or a header's arrow), lifted in luma to the non-text floor, +/// 3:1 or 4.5:1 under `high_contrast`, on every one of `faces`; unchanged +/// when it already reaches it, as stock Breeze's marks do. +/// +/// Where the lift ends at black or white short of the floor (a face in the +/// middle of the scale, with the mark already at the end on its side), the +/// mark is the other of black and white when that one does better: on one +/// opaque face one of the two reaches 4.58:1. +pub(crate) fn mark_on_faces(mark: Color, faces: &[Color], high_contrast: bool) -> Color { + let floor = if high_contrast { ENHANCED } else { AA }.non_text; + let on: Vec = faces.iter().map(|&f| KdeColor::from_color(f)).collect(); + let lifted = lift(KdeColor::from_color(mark), &on, floor); + if passes(lifted, &on, floor) { + return lifted.to_color(); + } + [KdeColor::BLACK, KdeColor::WHITE] + .into_iter() + .fold(lifted, |best, end| { + if worst_contrast(end, &on) > worst_contrast(best, &on) { + end + } else { + best + } + }) + .to_color() +} + +/// `[wcag]` D1: the text to paint on `fills`. +/// +/// `vendor` when it reaches `floor` on every fill; otherwise black or white +/// by Plasma's rule for text on an accent, judged on the first fill, or the +/// other of the two when the rule's choice still falls short (black and +/// white cannot both fall below 4.58:1 on one opaque fill). +/// +/// A translucent `vendor` is judged, and kept, made opaque over the first +/// fill. +pub(crate) fn text_on(vendor: KdeColor, fills: &[KdeColor], floor: f32) -> KdeColor { + let vendor = opaque_on(vendor, fills[0]); + if passes(vendor, fills, floor) { + return vendor; + } + let plasma = black_or_white_on(fills[0], true); + let other = if plasma == KdeColor::BLACK { + KdeColor::WHITE + } else { + KdeColor::BLACK + }; + if passes(plasma, fills, floor) || worst_contrast(plasma, fills) >= worst_contrast(other, fills) + { + plasma + } else { + other + } +} + +/// `[wcag]` D1: `fill` with its luma moved away from `text` until `text` +/// reaches `floor` on it; unchanged when it already does. +fn fill_under(fill: KdeColor, text: &[KdeColor], floor: f32) -> KdeColor { + lift_luma_until(fill, text, floor) +} + +/// `[wcag]` D1: `fill` with its luma moved toward `toward`, the window's end +/// of the scale, until `text` reaches `floor` on it; unchanged when it +/// already does. +/// +/// A wash or a selection sits among the window's surfaces, so it moves only +/// toward their end, never across the text to the other end, where the +/// text might read but the fill would be black in a light window. Where +/// even that end falls short the fill stops there, as near the floor as the +/// text can come; the text's own lift (D10) makes sure that cannot happen +/// to the primary text. +fn fill_toward(fill: KdeColor, text: &[KdeColor], floor: f32, toward: LumaDirection) -> KdeColor { + lift_luma_toward(fill, text, floor, toward) +} + +/// The high-contrast accent and its text. +/// +/// For each text candidate (D1's choice first, then the other of black and +/// white), the highlight moves until the text reaches `floors.text` on it +/// and then until it reaches `floors.non_text` on the control surfaces. The +/// first candidate that satisfies both wins. A light scheme ends on the +/// second candidate, white text on a darkened accent; see the module doc. +fn high_contrast_accent( + highlight: KdeColor, + vendor_text: KdeColor, + surfaces: &[KdeColor], + floors: Floors, +) -> (KdeColor, KdeColor) { + let first = text_on(vendor_text, &[highlight], floors.text); + // The other end of the scale from the first candidate: white for a dark + // text, black for a light one. + let second = + if worst_contrast(KdeColor::WHITE, &[first]) >= worst_contrast(KdeColor::BLACK, &[first]) { + KdeColor::WHITE + } else { + KdeColor::BLACK + }; + // How far a candidate falls short of either floor, as the smaller of + // its two margins: at least zero when it reaches both. + let attempt = |text: KdeColor| { + let fill = fill_under(highlight, &[text], floors.text); + let fill = lift_luma_until(fill, surfaces, floors.non_text); + let margin = (worst_contrast(text, &[fill]) - floors.text) + .min(worst_contrast(fill, surfaces) - floors.non_text); + (fill, text, margin) + }; + let (fill, text, margin) = attempt(first); + if margin >= 0.0 { + return (fill, text); + } + let (fill2, text2, margin2) = attempt(second); + if margin2 > margin { + (fill2, text2) + } else { + (fill, text) + } +} + +/// The colour tokens of `m`. +pub(crate) fn tokens(m: &Mapping<'_>) -> ColorTokens { + let p = m.palette; + let s = m.state; + let d = ColorState::Disabled; + let fc = m.frame_contrast; + let floors = if m.high_contrast { ENHANCED } else { AA }; + // Int UI's palette for the appearance, for the four tokens KDE has no + // colours for (see the module doc). + let kept = if m.dark { + ColorTokens::dark_default() + } else { + ColorTokens::light_default() + }; + + let role = |state: ColorState, r: PaletteRole| p.role(state, r); + let window_text = role(s, PaletteRole::WindowText); + // `[wcag]` D11: each surface text is set on moves away from the window + // text, in the direction the window lies from it, until black or white + // can reach the text floor on it. One direction for every surface, as + // the primary text is set on all of them. + let carried = Surfaces::of(p, s, floors.text); + let surface = |c: KdeColor| carried.carry(c, floors.text); + let window = carried.window; + let base = carried.base; + let alternate = surface(role(s, PaletteRole::AlternateBase)); + let button = carried.button; + let highlight = role(s, PaletteRole::Highlight); + let highlighted_text = role(s, PaletteRole::HighlightedText); + let view = p.set(s, ColorSet::View); + let window_set = p.set(s, ColorSet::Window); + // `[qt]` Qt's palette takes the tooltip from the Active state whatever + // state is painted. + let tooltip_set = p.set(ColorState::Active, ColorSet::Tooltip); + let focus = view.decoration(DecorationRole::Focus); + let hover = view.decoration(DecorationRole::Hover); + + // `[Hp]` Breeze's frame background (menus, popups, group boxes): the + // window colour 30 % of the way to the view's. + let frame_background = carried.frame_background; + // `[Hp]` The frame outline and the separator: the window colour moved + // toward the window text by the frame contrast. + let frame_outline = mix(window, window_text, fc); + // `[derived]` A recessed surface: the window's alternate background when + // it is darker than the window, otherwise `[Hp]` the window darkened as + // Breeze darkens an unselected tab. + let window_alternate = window_set.background(BackgroundRole::Alternate); + let sunken = surface(if luma(window_alternate) < luma(window) { + window_alternate + } else { + tab_unselected_face(window) + }); + // Every opaque plain surface text is set on. + let plain_surfaces = [window, base, frame_background, alternate, button, sunken]; + // `[Hp]` What a control stands on. + let control_surfaces = [window, base, frame_background, button]; + + // ── Accent ────────────────────────────────────────────────────────── + // `[qt]` Highlight is the accent. D1 and, for high contrast, the HCY + // move of the module doc. + let accent_of = |highlight: KdeColor, highlighted_text: KdeColor| { + if m.high_contrast { + high_contrast_accent(highlight, highlighted_text, &control_surfaces, floors) + } else { + ( + highlight, + // `[wcag]` D1: the scheme's selection text, or Plasma's black + // or white where it falls short. + text_on(highlighted_text, &[highlight], floors.text), + ) + } + }; + let (accent, text_on_accent) = accent_of(highlight, highlighted_text); + // `[Hp]` Breeze lightens a hovered highlight and darkens a pressed one + // by Qt's 110 % factor; `[wcag]` D1 moves either if the accent text + // falls short on it. + let accent_hover = fill_under( + hsv_lighter(accent, FOCUS_LIGHTEN_FACTOR), + &[text_on_accent], + floors.text, + ); + let accent_pressed = fill_under( + hsv_darker(accent, FOCUS_LIGHTEN_FACTOR), + &[text_on_accent], + floors.text, + ); + + // ── Text ──────────────────────────────────────────────────────────── + // `[qt]` WindowText is the text of the window. `[wcag]` D10: lifted to + // the text floor on the plain surfaces where it falls short (it never + // does in a stock scheme's normal projection; the high-contrast one + // raises the floor to 7:1). + let text_side = carried.text_side(); + let text_primary = lift_toward(window_text, &plain_surfaces, floors.text, text_side); + // The window's end of the luma scale, away from its text. + let toward_window = away_from(text_primary, window); + + // `[wcag]` D1: an accent wash carries the primary text, so it moves away + // from that text, toward the window's end of the scale, if the text + // falls short on it. + let wash = |fill: KdeColor| fill_toward(fill, &[text_primary], floors.text, toward_window); + // `[Hp]` The item-view hover: Highlight over the view at the blend + // value. Also the subtle accent wash. + let surface_hover = wash(over(accent, BLEND, base)); + // `[Hp]` A pressed push button's fill: the button colour 30 % of the way + // to the highlight. + let surface_pressed = wash(mix(button, accent, BLEND)); + // The same two washes in the Active state's accent. An inactive + // window's text is lifted against both, so a text colour does not + // change when the window loses focus while the scheme's surfaces do not. + let (active_accent, _) = accent_of( + role(ColorState::Active, PaletteRole::Highlight), + role(ColorState::Active, PaletteRole::HighlightedText), + ); + let active_hover = wash(over(active_accent, BLEND, base)); + let active_pressed = wash(mix(button, active_accent, BLEND)); + + // Every opaque surface text is set on, the washes included. + let text_surfaces = [ + window, + base, + frame_background, + alternate, + button, + sunken, + surface_hover, + surface_pressed, + active_hover, + active_pressed, + ]; + // `[Hp]` The faces of an unselected tab (k.5.14), as the tab style paints + // them: the window darkened 120 percent, and hovered, the window moved + // 20 percent toward the hover colour. A tab bar paints an unselected + // tab's title in the secondary text, so D2 lifts it on both. + let tab_unselected = tab_unselected_face(window); + let tab_hovered = tab_hovered_face(window, hover); + // `[wcag]` D2: the window set's inactive foreground, lifted to the text + // floor on every surface and on the two unselected-tab faces. In Breeze + // Light the tab face is the darkest of them and decides the lift, which + // leaves the text 2.0:1 from the primary text; where a scheme's tab face + // would bring the two together, legibility wins over the distinction. + // The high-contrast projection uses the primary text instead, lifted + // toward its own end of the scale on the same surfaces where it falls + // short on the tab faces (D10 lifted it on the plain surfaces only). + let secondary_surfaces: Vec = text_surfaces + .iter() + .copied() + .chain([tab_unselected, tab_hovered]) + .collect(); + let text_secondary = if m.high_contrast { + lift_toward(text_primary, &secondary_surfaces, floors.text, text_side) + } else { + lift( + window_set.foreground(ForegroundRole::Inactive), + &secondary_surfaces, + floors.text, + ) + }; + + // `[qt]` Text is the view's text. The editor surfaces it is set on. + let editor_current_line = alternate; + // `[derived]` A code block sits half-way between the view and the + // window: a step below the view in a light scheme, above it in a dark + // one. + // `[wcag]` D11 again: a mix of two carried surfaces of different hues + // can be darker than both. + let editor_code_block = surface(mix(base, window, 0.5)); + let editor_surfaces = [base, editor_current_line, editor_code_block]; + // `[wcag]` D10: lifted where it falls short on them. + let editor_fg = lift_toward( + view.foreground(ForegroundRole::Normal), + &editor_surfaces, + floors.text, + text_side, + ); + + // `[wcag]` D3: links on every text surface. + let link = |r: ForegroundRole| lift(view.foreground(r), &text_surfaces, floors.text); + // `[qt]` The view's link colours. KDE has no hovered-link colour: the + // active foreground, KDE's "hot" text, stands in for it. + let text_link = link(ForegroundRole::Link); + let text_link_visited = link(ForegroundRole::Visited); + let text_link_hover = link(ForegroundRole::Active); + + // `[wcag]` D4: each status colour on every text surface and on its own + // computed background (`[Hp]` the view's background tinted 30 % toward + // the foreground, KDE's negative, neutral, positive and active + // backgrounds). + let status = |fg: ForegroundRole, bg: BackgroundRole| { + let background = surface(view.background(bg)); + let mut on = text_surfaces.to_vec(); + on.push(background); + (lift(view.foreground(fg), &on, floors.text), background) + }; + let (error, error_bg) = status(ForegroundRole::Negative, BackgroundRole::Negative); + let (warning, warning_bg) = status(ForegroundRole::Neutral, BackgroundRole::Neutral); + let (success, success_bg) = status(ForegroundRole::Positive, BackgroundRole::Positive); + let (info, info_bg) = status(ForegroundRole::Active, BackgroundRole::Active); + // `[wcag]` D4 as a boundary: the status colours lifted to the non-text + // floor on the control surfaces. + let status_border = + |fg: ForegroundRole| lift(view.foreground(fg), &control_surfaces, floors.non_text); + let border_error = status_border(ForegroundRole::Negative); + let border_warning = status_border(ForegroundRole::Neutral); + + // ── Selection ─────────────────────────────────────────────────────── + // `[qt]` Highlight is the selection. `[wcag]` D1: the fill moves until + // the primary text Teksilo paints on it reaches the floor. + let selection_fill = + |fill: KdeColor| fill_toward(fill, &[text_primary, editor_fg], floors.text, toward_window); + let surface_selected = selection_fill(highlight); + // `[qt]` The Inactive state's highlight: KDE tints the window colour 40 % + // toward the selection colour, the muted selection of a backdrop window. + let inactive_highlight = role(ColorState::Inactive, PaletteRole::Highlight); + let surface_selected_inactive = selection_fill(inactive_highlight); + let selection_text_active = text_on(highlighted_text, &[surface_selected], floors.text); + let selection_text_inactive = text_on( + role(ColorState::Inactive, PaletteRole::HighlightedText), + &[surface_selected_inactive], + floors.text, + ); + + // ── Borders and focus ─────────────────────────────────────────────── + // `[wcag]` D6: the frame outline lifted to the non-text floor where a + // boundary must be seen. + let border_strong = lift(frame_outline, &control_surfaces, floors.non_text); + // `[Hp]` The separator is the frame outline's colour. + let divider = frame_outline; + let divider_strong = border_strong; + // `[wcag]` D5: the view's focus decoration lifted to the non-text floor. + let focus_ring = lift(focus, &control_surfaces, floors.non_text); + + // ── Scroll bar ────────────────────────────────────────────────────── + // `[wcag]` D9: Breeze's thumbs lifted to the non-text floor on the window + // and the view. + let thumb_backdrops = [window, base]; + let scrollbar_thumb = lift( + over(window_text, THUMB_ALPHA, window), + &thumb_backdrops, + floors.non_text, + ); + let scrollbar_thumb_hover = lift( + over(hover, THUMB_HOVER_ALPHA, window), + &thumb_backdrops, + floors.non_text, + ); + + // ── Tooltip ───────────────────────────────────────────────────────── + let tooltip_fg = tooltip_set.foreground(ForegroundRole::Normal); + // `[wcag]` D11 for the tooltip's own surface. + let tooltip_normal = tooltip_set.background(BackgroundRole::Normal); + let tooltip_bg = carry( + tooltip_normal, + away_from(tooltip_fg, tooltip_normal), + floors.text, + ); + // `[wcag]` D10. + let tooltip_text = lift(tooltip_fg, &[tooltip_bg], floors.text); + // `[wcag]` D2: the tooltip's inactive text, the shortcut's. + let tooltip_shortcut = if m.high_contrast { + tooltip_text + } else { + lift( + tooltip_set.foreground(ForegroundRole::Inactive), + &[tooltip_bg], + floors.text, + ) + }; + + // ── Editor gutter ─────────────────────────────────────────────────── + // `[wcag]` D2: the view's inactive text, for line numbers. + let editor_gutter_fg = if m.high_contrast { + editor_fg + } else { + lift( + view.foreground(ForegroundRole::Inactive), + &editor_surfaces, + floors.text, + ) + }; + + let c = |k: KdeColor| k.to_color(); + ColorTokens { + // ── Surfaces ── + // `[qt]` Window: the window's background. + surface_main: c(window), + // `[qt]` Base: the background of views and fields. + surface_content: c(base), + // `[Hp]` The frame background. + surface_raised: c(frame_background), + surface_sunken: c(sunken), + surface_hover: c(surface_hover), + surface_pressed: c(surface_pressed), + surface_selected: c(surface_selected), + surface_selected_inactive: c(surface_selected_inactive), + // `[qt]` AlternateBase: alternate rows. + surface_alt_row: c(alternate), + // `[qt]` A disabled control: the Disabled state's Button. + surface_disabled: c(role(d, PaletteRole::Button)), + + // ── Text ── + text_primary: c(text_primary), + text_secondary: c(text_secondary), + // `[qt]` The Disabled state's WindowText. Disabled text is exempt + // from SC 1.4.3 and is not lifted. + text_disabled: c(role(d, PaletteRole::WindowText)), + text_on_accent: c(text_on_accent), + text_link: c(text_link), + text_link_hover: c(text_link_hover), + text_link_visited: c(text_link_visited), + text_error: c(error), + text_warning: c(warning), + text_success: c(success), + + // ── Accent ── + accent: c(accent), + accent_hover: c(accent_hover), + accent_pressed: c(accent_pressed), + // `[qt]` The Disabled state's Highlight. + accent_disabled: c(role(d, PaletteRole::Highlight)), + // `[Hp]` The item hover wash. + accent_subtle_bg: c(surface_hover), + + // ── Borders ── + // `[Hp]` The frame outline, as Breeze draws it. + border: c(frame_outline), + border_strong: c(border_strong), + // `[Hp]` Focus is shown in the focus decoration, lifted by D5. + border_focused: c(focus_ring), + border_error: c(border_error), + border_warning: c(border_warning), + // `[Hp]` The Disabled state's frame outline. + border_disabled: c(mix( + role(d, PaletteRole::Window), + role(d, PaletteRole::WindowText), + fc, + )), + divider: c(divider), + divider_strong: c(divider_strong), + + // ── Status ── + status_info_fg: c(info), + status_info_bg: c(info_bg), + status_success_fg: c(success), + status_success_bg: c(success_bg), + status_warning_fg: c(warning), + status_warning_bg: c(warning_bg), + status_error_fg: c(error), + status_error_bg: c(error_bg), + + // ── Selection ── + selection_bg_active: c(surface_selected), + selection_text_active: c(selection_text_active), + selection_bg_inactive: c(surface_selected_inactive), + selection_text_inactive: c(selection_text_inactive), + // Kept from Int UI: KDE has no find-match colour. + search_match_bg: kept.search_match_bg, + search_match_current_bg: kept.search_match_current_bg, + + // ── Scroll bar ── + // `[Hp]` Breeze paints no scroll-bar track, idle or hovered. + scrollbar_track: Color::TRANSPARENT, + scrollbar_track_hover: Color::TRANSPARENT, + scrollbar_thumb: c(scrollbar_thumb), + scrollbar_thumb_hover: c(scrollbar_thumb_hover), + // `[Hp]` Breeze has no pressed handle colour: a grabbed handle is + // under the pointer and stays the hovered one. + scrollbar_thumb_pressed: c(scrollbar_thumb_hover), + + // ── Tooltip ── + // `[qt]` ToolTipBase and ToolTipText. + tooltip_bg: c(tooltip_bg), + tooltip_text: c(tooltip_text), + // `[Hp]` The tooltip outline: its background moved toward its text + // by the frame contrast. + tooltip_border: c(mix(tooltip_bg, tooltip_fg, fc)), + tooltip_shortcut: c(tooltip_shortcut), + + // ── Editor ── + // `[qt]` Base and Text. + editor_bg: c(base), + editor_fg: c(editor_fg), + // `[qt]` Qt draws the text cursor in the text colour. + editor_caret: c(editor_fg), + // `[derived]` KDE has no current-line colour: the view's alternate + // background, its subtlest step. + editor_current_line_bg: c(editor_current_line), + editor_gutter_fg: c(editor_gutter_fg), + // `[qt]` The selection, D1's fill. + editor_selection_bg: c(surface_selected), + editor_code_block_bg: c(editor_code_block), + // `[derived]` KDE has no code register: the view's text. + editor_code_block_fg: c(editor_fg), + + // ── Misc ── + focus_ring: c(focus_ring), + // `[wcag]` D4: the negative colour as a boundary. + focus_ring_error: c(border_error), + // Kept from Int UI: KDE dims nothing behind a dialog. + scrim: kept.scrim, + + // ── Cross-design-language roles ── + // `[derived]` White on the error fill, unless it falls short; then + // D1's black-or-white rule. + text_on_error: c(text_on(KdeColor::WHITE, &[error], floors.text)), + text_on_error_container: c(error), + // `[Hp]` The view's negative background. + surface_error_container: c(error_bg), + // `[Hp]` A group box is drawn on the frame background. + surface_container: c(frame_background), + // `[derived]` One step above the frame background: the view's. + surface_container_raised: c(base), + surface_container_sunken: c(sunken), + + // Kept from Int UI: KDE has no data-visualisation sequence. + chart_palette: kept.chart_palette, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColorScheme; + + fn active(scheme: &KdeColorScheme) -> ColorTokens { + let palette = scheme.resolve(); + tokens(&Mapping { + palette: &palette, + state: ColorState::Active, + frame_contrast: scheme.frame_contrast, + high_contrast: false, + dark: scheme.is_dark(), + }) + } + + #[test] + fn d1_keeps_a_passing_vendor_text_and_otherwise_follows_plasmas_rule() { + let blue = KdeColor::rgb8(61, 174, 233); + // White on the Breeze highlight fails, black is Plasma's choice. + assert_eq!(text_on(KdeColor::WHITE, &[blue], 4.5), KdeColor::BLACK); + // A vendor text that already passes is kept. + let navy = KdeColor::rgb8(0, 0, 80); + assert_eq!(text_on(navy, &[blue], 4.5), navy); + // On a dark fill Plasma's rule is white. + let dark = KdeColor::rgb8(30, 60, 90); + assert_eq!(text_on(KdeColor::BLACK, &[dark], 4.5), KdeColor::WHITE); + } + + #[test] + fn the_high_contrast_accent_is_white_on_a_darkened_accent_in_a_light_scheme() { + let window = KdeColor::rgb8(239, 240, 241); + let (fill, text) = high_contrast_accent( + KdeColor::rgb8(61, 174, 233), + KdeColor::WHITE, + &[window, KdeColor::WHITE], + ENHANCED, + ); + assert_eq!(text, KdeColor::WHITE); + assert!(worst_contrast(text, &[fill]) >= 7.0); + assert!(worst_contrast(fill, &[window, KdeColor::WHITE]) >= 4.5); + } + + #[test] + fn the_high_contrast_accent_keeps_black_text_in_a_dark_scheme() { + let window = KdeColor::rgb8(32, 35, 38); + let (fill, text) = high_contrast_accent( + KdeColor::rgb8(61, 174, 233), + KdeColor::rgb8(252, 252, 252), + &[window, KdeColor::rgb8(20, 22, 24)], + ENHANCED, + ); + assert_eq!(text, KdeColor::BLACK); + assert!(worst_contrast(text, &[fill]) >= 7.0); + } + + #[test] + fn the_light_selection_carries_dark_text_unmoved_and_the_dark_one_darkens() { + let light = active(&KdeColorScheme::breeze_light()); + assert_eq!(light.surface_selected, light.accent); + let dark = active(&KdeColorScheme::breeze_dark()); + assert_ne!(dark.surface_selected, dark.accent); + assert!(dark.surface_selected.relative_luminance() < dark.accent.relative_luminance()); + assert!(composite_contrast(dark.text_primary, dark.surface_selected) >= 4.5); + } + + #[test] + fn the_recessed_surface_is_the_window_alternate_or_the_darkened_window() { + // Breeze Light's window alternate (227,229,231) is darker than its + // window; Breeze Dark's (41,44,48) is lighter, so it darkens. + let light = active(&KdeColorScheme::breeze_light()); + assert_eq!( + light.surface_sunken, + KdeColor::rgb8(227, 229, 231).to_color() + ); + let dark = active(&KdeColorScheme::breeze_dark()); + assert!(dark.surface_sunken.relative_luminance() < dark.surface_main.relative_luminance()); + } +} diff --git a/crates/teksilo-theme-kde/src/fonts.rs b/crates/teksilo-theme-kde/src/fonts.rs new file mode 100644 index 000000000..54e4f9f82 --- /dev/null +++ b/crates/teksilo-theme-kde/src/fonts.rs @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Plasma's faces, Noto Sans and Hack: named, embedded, or neither. +//! +//! Plasma writes its text in Noto Sans and its fixed-width text in Hack. +//! Which family the preset's typography names depends on two Cargo +//! features, both off by default: +//! +//! | Features | Text family | Monospace family | Embedded | +//! | --- | --- | --- | --- | +//! | neither | Inter | JetBrains Mono | nothing; Teksilo's own bundled faces draw | +//! | `system-fonts` | Noto Sans | Hack | nothing; the installed faces draw | +//! | `bundled-fonts` | Noto Sans | Hack | the six faces below | +//! +//! The sizes and line boxes are the same in every row: they are Noto Sans +//! and Hack's at Plasma's default fonts ([`crate::typography`]), so turning +//! a feature on changes the glyphs, never a control's height. +//! +//! ## `bundled-fonts` +//! +//! Embeds the faces in the binary, about 5.6 MB, and exposes +//! `font_registrar()`, which an app hands to the app builder so the families +//! resolve to them before any text is shaped: +//! +//! ```ignore +//! TeksiloAppBuilder::new() +//! .theme(kde::light()) +//! .register_fonts(kde::font_registrar()) +//! .run(); +//! ``` +//! +//! | File | Face | Upstream | +//! | --- | --- | --- | +//! | `NotoSans-Variable.ttf` | Noto Sans, every weight, upright | Noto Sans v2.015, `NotoSans/full/variable-ttf/NotoSans[wdth,wght].ttf` | +//! | `NotoSans-Italic-Variable.ttf` | Noto Sans, every weight, italic | the same release, `NotoSans-Italic[wdth,wght].ttf` | +//! | `Hack-Regular.ttf`, `Hack-Bold.ttf` | Hack 400 and 700, upright | Hack v3.003, `Hack-v3.003-ttf.zip` | +//! | `Hack-Italic.ttf`, `Hack-BoldItalic.ttf` | Hack 400 and 700, italic | the same release | +//! +//! Each is redistributed unmodified, with its licence text beside it in +//! `fonts/`: the SIL Open Font License 1.1 for Noto Sans, the MIT licence +//! and the Bitstream Vera licence for Hack. Noto Sans is the variable +//! font, so the 600 weight of the bold text styles is drawn at that weight, +//! not synthesised or rounded to a bold. The italics are there for text an +//! app sets in italic: no text style of the preset is italic. +//! +//! The registrar registers the faces under the families "Noto Sans" and +//! "Hack" whatever their files' name tables say, and makes upright Noto +//! Sans at the body size the default face, the one text naming no family +//! is drawn in. +//! +//! ## `system-fonts` +//! +//! Names the families without embedding anything, for a desktop that has +//! them installed, as every Plasma desktop does: the text engine finds them +//! among the installed fonts, and on a machine without them falls back to +//! the bundled default, so the feature is never fatal. With both features +//! the bundled faces are registered and the installed ones are found too. +//! +//! [`crate::from_desktop`] names the desktop's own families with or without +//! either feature. + +/// `[measured-qt]` The family of Plasma's general, smallest readable, menu +/// and toolbar fonts, and the text styles' family under either font +/// feature. +pub const NOTO_SANS_FAMILY: &str = "Noto Sans"; + +/// `[measured-qt]` The family of Plasma's fixed font, and the monospace +/// style's family under either font feature. +pub const HACK_FAMILY: &str = "Hack"; + +#[cfg(feature = "bundled-fonts")] +mod bundled { + use std::sync::Arc; + + use teksilo_text::{FontFaceId, FontRegistrar, SharedFontData, TextFontService}; + + use super::{HACK_FAMILY, NOTO_SANS_FAMILY}; + use crate::typography::{BREEZE_BODY_PT, face_px}; + + /// Noto Sans v2.015, the variable upright face. + static NOTO_SANS: &[u8] = include_bytes!("../fonts/NotoSans-Variable.ttf"); + /// Noto Sans v2.015, the variable italic face. + static NOTO_SANS_ITALIC: &[u8] = include_bytes!("../fonts/NotoSans-Italic-Variable.ttf"); + /// Hack v3.003 Regular. + static HACK_REGULAR: &[u8] = include_bytes!("../fonts/Hack-Regular.ttf"); + /// Hack v3.003 Bold. + static HACK_BOLD: &[u8] = include_bytes!("../fonts/Hack-Bold.ttf"); + /// Hack v3.003 Italic. + static HACK_ITALIC: &[u8] = include_bytes!("../fonts/Hack-Italic.ttf"); + /// Hack v3.003 Bold Italic. + static HACK_BOLD_ITALIC: &[u8] = include_bytes!("../fonts/Hack-BoldItalic.ttf"); + + /// `[derived]` The weight each face is registered at: its file's OS/2 + /// weight class, 400 for Noto Sans (a variable face, drawn at every + /// weight from there) and Hack Regular and Italic, 700 for Hack Bold + /// and Bold Italic. + const REGULAR: u16 = 400; + /// See [`REGULAR`]. + const BOLD: u16 = 700; + + /// Registers the embedded faces under the families the typography + /// names. + pub(super) struct BreezeFontRegistrar; + + /// Shares a face compiled into the binary rather than copying it onto + /// the heap for the life of the process. + fn shared(bytes: &'static [u8]) -> SharedFontData { + Arc::new(bytes) + } + + impl FontRegistrar for BreezeFontRegistrar { + fn register_on_service(&self, service: &mut TextFontService) -> Option { + let body = service.register_font_shared_as( + shared(NOTO_SANS), + NOTO_SANS_FAMILY, + REGULAR, + false, + ); + service.register_font_shared_as( + shared(NOTO_SANS_ITALIC), + NOTO_SANS_FAMILY, + REGULAR, + true, + ); + for (bytes, weight, italic) in [ + (HACK_REGULAR, REGULAR, false), + (HACK_BOLD, BOLD, false), + (HACK_ITALIC, REGULAR, true), + (HACK_BOLD_ITALIC, BOLD, true), + ] { + service.register_font_shared_as(shared(bytes), HACK_FAMILY, weight, italic); + } + service.set_default_font(body, face_px(BREEZE_BODY_PT) as f32); + Some(body) + } + } +} + +/// A [`FontRegistrar`](teksilo_text::FontRegistrar) that embeds Noto Sans +/// and Hack and registers them under the families "Noto Sans" and "Hack", +/// upright Noto Sans at the body size as the default face. Pass it to the +/// app builder so the typography, which names those families under this +/// feature, resolves to them: +/// +/// ```ignore +/// TeksiloAppBuilder::new() +/// .theme(kde::light()) +/// .register_fonts(kde::font_registrar()) +/// .run(); +/// ``` +/// +/// Available only under the `bundled-fonts` Cargo feature. See [the module +/// documentation](crate::fonts) for the faces. +#[cfg(feature = "bundled-fonts")] +pub fn font_registrar() -> impl teksilo_text::FontRegistrar { + bundled::BreezeFontRegistrar +} diff --git a/crates/teksilo-theme-kde/src/geometry.rs b/crates/teksilo-theme-kde/src/geometry.rs new file mode 100644 index 000000000..9a5fc8992 --- /dev/null +++ b/crates/teksilo-theme-kde/src/geometry.rs @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Breeze geometry: the widget style's metric constants, and the control +//! sizes they give for a font. +//! +//! Most Breeze control heights are not constants: the style adds fixed +//! margins to the height of a line of text, so a larger desktop font makes +//! larger controls. [`BreezeGeometry::for_line_height`] holds every such +//! height for a body line height `H` (18 px for Plasma's default Noto Sans +//! 10 pt, [`crate::typography::breeze_body_line_height`]). The rules are +//! exact at that default. At other sizes Qt measures some controls by a +//! text box that can be one to three pixels taller than `H`, so a rule can +//! fall short of what Qt reports by that much; Teksilo keeps the +//! deterministic rule, and the tests bound the difference. +//! +//! These are Breeze's own numbers, in logical pixels at the Compact +//! density. Where Teksilo raises a height for its touch target or +//! accessibility floors, the widget styles do it, on top of these. + +/// `[M]` The margin of a frame drawn around a control. +pub const FRAME_WIDTH: f32 = 2.0; +/// `[M]` The top-level layout margin, a dialog's content margin. +pub const LAYOUT_TOP_LEVEL_MARGIN: f32 = 10.0; +/// `[M]` The margin of a nested layout. +pub const LAYOUT_CHILD_MARGIN: f32 = 6.0; +/// `[M]` The spacing between the items of a layout. +pub const LAYOUT_SPACING: f32 = 6.0; +/// `[M]` The frame width of a line edit. +pub const LINE_EDIT_FRAME_WIDTH: f32 = 6.0; +/// `[M]` The frame width of a combo box. +pub const COMBO_BOX_FRAME_WIDTH: f32 = 6.0; +/// `[M]` The frame width of a spin box. +pub const SPIN_BOX_FRAME_WIDTH: f32 = 6.0; +/// `[M]` The width of a spin box's arrow column. +pub const SPIN_BOX_ARROW_BUTTON_WIDTH: f32 = 20.0; +/// `[M]` A push button's minimum width before its margins. +pub const BUTTON_MIN_WIDTH: f32 = 80.0; +/// `[M]` A push button's horizontal margin. +pub const BUTTON_MARGIN_WIDTH: f32 = 6.0; +/// `[M]` The spacing between a button's icon, text and menu arrow. +pub const BUTTON_ITEM_SPACING: f32 = 4.0; +/// `[M]` A tool button's margin. +pub const TOOL_BUTTON_MARGIN_WIDTH: f32 = 6.0; +/// `[M]` The width of a button's menu indicator. +pub const MENU_BUTTON_INDICATOR_WIDTH: f32 = 20.0; +/// `[M]` The check box and radio button indicator, square. +pub const CHECKBOX_SIZE: f32 = 20.0; +/// `[M]` The spacing between a check box indicator and its label. +pub const CHECKBOX_ITEM_SPACING: f32 = 4.0; +/// `[M]` The margin a check box's focus frame takes. +pub const CHECKBOX_FOCUS_MARGIN: f32 = 2.0; +/// `[M]` The margin around a menu item, horizontally and vertically. +pub const MENU_ITEM_MARGIN: f32 = 4.0; +/// `[M]` How far a menu item's text starts from its contents' leading edge. +pub const MENU_ITEM_TEXT_LEFT_MARGIN: f32 = 8.0; +/// `[M]` The extra leading margin of a menu without an icon column. +pub const MENU_ITEM_EXTRA_LEFT_MARGIN: f32 = 4.0; +/// `[M]` The gap before a menu item's shortcut. +pub const MENU_ITEM_ACCELERATOR_SPACE: f32 = 16.0; +/// `[M]` A menu bar item's horizontal margin. +pub const MENU_BAR_ITEM_MARGIN_WIDTH: f32 = 10.0; +/// `[M]` A menu bar item's vertical margin. +pub const MENU_BAR_ITEM_MARGIN_HEIGHT: f32 = 6.0; +/// `[M]` The scroll bar's extent, including its 1 px separator line. +pub const SCROLL_BAR_EXTENT: f32 = 21.0; +/// `[M]` The width of the scroll bar's handle. +pub const SCROLL_BAR_HANDLE_WIDTH: f32 = 8.0; +/// `[M]` The shortest the scroll bar's handle gets. +pub const SCROLL_BAR_MIN_HANDLE_LENGTH: f32 = 20.0; +/// `[M]` The thickness of the progress bar's groove. +pub const PROGRESS_BAR_THICKNESS: f32 = 6.0; +/// `[M]` The progress bar's busy indicator size. +pub const PROGRESS_BAR_BUSY_INDICATOR_SIZE: f32 = 14.0; +/// `[Hp]` The period of the busy progress bar's stripes. +pub const PROGRESS_BAR_BUSY_PERIOD: f32 = 28.0; +/// `[M]` The thickness of a slider's groove. +pub const SLIDER_GROOVE_THICKNESS: f32 = 6.0; +/// `[M]` The thickness of a slider, the size of its handle box. +pub const SLIDER_CONTROL_THICKNESS: f32 = 20.0; +/// `[M]` The length of a slider's tick mark. +pub const SLIDER_TICK_LENGTH: f32 = 8.0; +/// `[M]` The gap between a slider's groove and its tick marks. +pub const SLIDER_TICK_MARGIN: f32 = 2.0; +/// `[Hp]` The diameter a slider handle is painted at: the handle box inset +/// by one, then stroked. +pub const SLIDER_HANDLE_PAINTED: f32 = 17.0; +/// `[M]` A tab's vertical margin. +pub const TAB_MARGIN_HEIGHT: f32 = 4.0; +/// `[M]` A tab's horizontal margin. +pub const TAB_MARGIN_WIDTH: f32 = 8.0; +/// `[M]` A tab's minimum width. +pub const TAB_MIN_WIDTH: f32 = 80.0; +/// `[M]` A tab's minimum height. +pub const TAB_MIN_HEIGHT: f32 = 30.0; +/// `[M]` A static tab's minimum height (document mode, not closable, not +/// movable, expanding or vertical). +pub const STATIC_TAB_MIN_HEIGHT: f32 = 34.0; +/// `[M]` The spacing between a tab's icon, text and buttons. +pub const TAB_ITEM_SPACING: f32 = 8.0; +/// `[M]` The thickness of the active tab's Highlight bar. +pub const TAB_ACTIVE_INDICATOR: f32 = 3.0; +/// `[M]` The margin of a table or tree header section. +pub const HEADER_MARGIN: f32 = 6.0; +/// `[M]` The size of a header's sort arrow. +pub const HEADER_ARROW_SIZE: f32 = 10.0; +/// `[M]` The size of an arrow (chevron). +pub const ARROW_SIZE: f32 = 10.0; +/// `[M]` An item-view item's left and right margins. +pub const ITEM_VIEW_MARGIN_WIDTH: f32 = 2.0; +/// `[M]` An item-view item's top and bottom margins. +pub const ITEM_VIEW_MARGIN_HEIGHT: f32 = 1.0; +/// `[M]` The extra top margin of an item view's first row. +pub const ITEM_VIEW_FIRST_ITEM_TOP_MARGIN: f32 = 2.0; +/// `[M]` An item-view item's padding, horizontally and vertically. +pub const ITEM_VIEW_PADDING: f32 = 2.0; +/// `[M]` The tooltip's frame width; Qt adds one more pixel of padding. +pub const TOOLTIP_FRAME_WIDTH: f32 = 3.0; +/// `[M]` The splitter handle's visual width. +pub const SPLITTER_WIDTH: f32 = 1.0; +/// `[M]` A group box title's margin. +pub const GROUP_BOX_TITLE_MARGIN: f32 = 4.0; +/// `[K]` The small icon size, used by buttons, menus and rows. +pub const SMALL_ICON_SIZE: f32 = 16.0; +/// `[K]` The small-medium icon size, used by larger tool buttons. +pub const SMALL_MEDIUM_ICON_SIZE: f32 = 22.0; +/// `[K]` The medium icon size. +pub const MEDIUM_ICON_SIZE: f32 = 32.0; +/// `[K]` The large icon size. +pub const LARGE_ICON_SIZE: f32 = 48.0; + +/// Every Breeze control height that follows the font, for one body line +/// height. +/// +/// Built by [`BreezeGeometry::for_line_height`]. The values are in logical +/// pixels at the Compact density, and each field says the rule it comes +/// from; `H` is the line height and icons are [`SMALL_ICON_SIZE`] unless a +/// field says otherwise. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeGeometry { + /// `H`, the body line height everything below is derived from. + pub line_height: f32, + /// `[S]` A push button with text: `H + 2 x 6 + 2 x 2`. 34 at H = 18. + pub push_button_height: f32, + /// `[S]` A push button's minimum width: `max(content + 12, 80) + 4`. + pub push_button_min_width: f32, + /// `[S]` A push button with only an icon: `I + 16`. 32 with a 16 px + /// icon. + pub icon_button_height: f32, + /// `[measured-qt]` The width of that icon-only button: 31. + pub icon_button_width: f32, + /// `[S]` A tool button without a frame (auto-raise): the icon plus 12. + /// 28 with a 16 px icon. + pub tool_button_height: f32, + /// `[S]` The same with a 22 px icon: 34. + pub tool_button_height_medium: f32, + /// `[S]` A tool button with a frame: the icon plus 16. 32 with a 16 px + /// icon. + pub framed_tool_button_height: f32, + /// `[S]` The same with a 22 px icon: 38. + pub framed_tool_button_height_medium: f32, + /// `[S]` `[qt]` A line edit: Qt's inner height `max(H, 14) + 2`, plus + /// the 6 px frame on each side. 32 at H = 18. + pub line_edit_height: f32, + /// `[S]` A combo box: `max(H, 20) + 2 x 6`. 32 at H = 18. + pub combo_box_height: f32, + /// `[S]` A spin box: the line edit's inner height plus its frame, at + /// least 20. 32 at H = 18. + pub spin_box_height: f32, + /// `[S]` A check box or radio button row: `max(H + 2 x 2, 20)`. 22 at + /// H = 18. + pub check_row_height: f32, + /// `[qt]` `[measured-qt]` A progress bar: Qt's hint, `H + 8`. 26 at + /// H = 18. + pub progress_bar_height: f32, + /// `[S]` A menu item: `max(H, 20, icon) + 1 + 2 x 4`. 29 at H = 18. + pub menu_item_height: f32, + /// `[S]` A menu section (a separator with a title): `max(H, 20, icon) + /// + 4 + 4`. 28 at H = 18. + pub menu_section_height: f32, + /// `[S]` A menu separator: `1 + 4`. + pub menu_separator_height: f32, + /// `[S]` A menu bar item: `H + 2 x 6`. 30 at H = 18. + pub menu_bar_item_height: f32, + /// `[S]` A tab: `max(H + 2 x 4, 30)`. 30 at H = 18. + pub tab_height: f32, + /// `[S]` A static tab: `max(H + 2 x 4, 34)`. 34 at H = 18. + pub static_tab_height: f32, + /// `[S]` A header section with a sort arrow: `max(H, 10) + 2 x 6`. 30 + /// at H = 18. + pub header_height: f32, + /// `[S]` A header section with an icon: `max(H, 22) + 2 x 6`. + pub header_height_with_icon: f32, + /// `[S]` A list or tree row of text: `H + 1 + 1 + 2 x 2`. 24 at H = 18. + pub list_row_height: f32, + /// `[measured-qt]` A row with a 16 px icon: `max(H, 16) + 2 + 1 + 1 + + /// 2 x 2` (Qt adds 2 for a decoration). 26 at H = 18. + pub list_row_height_with_icon: f32, + /// `[measured-qt]` A row with a check box: `max(H, 20) + 1 + 1 + 2 x 2`. + /// 26 at H = 18. + pub list_row_height_with_check: f32, + /// `[S]` The first row of a view without a frame is one taller: its top + /// margin is 2, not 1. The row styles do not apply it, as a row is not + /// told its index (see [`crate::styles::standard_item`]). + pub first_row_extra: f32, + /// `[measured-qt]` A table row: `H + 2 x (1 + 1)`, plus the 1 px grid + /// line the table view adds. 23 at H = 18. + pub table_row_height: f32, + /// `[measured-qt]` A one-line tooltip: `H` inside a 4 px inset. 26 at + /// H = 18. + pub tooltip_height: f32, + /// `[measured-qt]` The tooltip's content inset on every side: the 3 px + /// frame and Qt's 1. + pub tooltip_padding: f32, +} + +impl BreezeGeometry { + /// Every font-driven height for a body line height of `h` pixels. + pub fn for_line_height(h: f32) -> BreezeGeometry { + let icon = SMALL_ICON_SIZE; + let line_edit_inner = h.max(14.0) + 2.0; + BreezeGeometry { + line_height: h, + push_button_height: h + 2.0 * BUTTON_MARGIN_WIDTH + 2.0 * FRAME_WIDTH, + push_button_min_width: BUTTON_MIN_WIDTH + 2.0 * FRAME_WIDTH, + icon_button_height: icon + 2.0 * BUTTON_MARGIN_WIDTH + 2.0 * FRAME_WIDTH, + icon_button_width: 31.0, + tool_button_height: icon + 2.0 * TOOL_BUTTON_MARGIN_WIDTH, + tool_button_height_medium: SMALL_MEDIUM_ICON_SIZE + 2.0 * TOOL_BUTTON_MARGIN_WIDTH, + framed_tool_button_height: icon + 2.0 * TOOL_BUTTON_MARGIN_WIDTH + 2.0 * FRAME_WIDTH, + framed_tool_button_height_medium: SMALL_MEDIUM_ICON_SIZE + + 2.0 * TOOL_BUTTON_MARGIN_WIDTH + + 2.0 * FRAME_WIDTH, + line_edit_height: line_edit_inner + 2.0 * LINE_EDIT_FRAME_WIDTH, + combo_box_height: h.max(20.0) + 2.0 * COMBO_BOX_FRAME_WIDTH, + spin_box_height: (line_edit_inner + 2.0 * SPIN_BOX_FRAME_WIDTH).max(20.0), + check_row_height: (h + 2.0 * CHECKBOX_FOCUS_MARGIN).max(CHECKBOX_SIZE), + progress_bar_height: h + 8.0, + menu_item_height: h.max(20.0).max(icon) + 1.0 + 2.0 * MENU_ITEM_MARGIN, + menu_section_height: h.max(20.0).max(icon) + 2.0 * MENU_ITEM_MARGIN, + menu_separator_height: 1.0 + MENU_ITEM_MARGIN, + menu_bar_item_height: h + 2.0 * MENU_BAR_ITEM_MARGIN_HEIGHT, + tab_height: (h + 2.0 * TAB_MARGIN_HEIGHT).max(TAB_MIN_HEIGHT), + static_tab_height: (h + 2.0 * TAB_MARGIN_HEIGHT).max(STATIC_TAB_MIN_HEIGHT), + header_height: h.max(HEADER_ARROW_SIZE) + 2.0 * HEADER_MARGIN, + header_height_with_icon: h.max(SMALL_MEDIUM_ICON_SIZE) + 2.0 * HEADER_MARGIN, + list_row_height: h + 2.0 * ITEM_VIEW_MARGIN_HEIGHT + 2.0 * ITEM_VIEW_PADDING, + list_row_height_with_icon: h.max(icon) + + 2.0 + + 2.0 * ITEM_VIEW_MARGIN_HEIGHT + + 2.0 * ITEM_VIEW_PADDING, + list_row_height_with_check: h.max(CHECKBOX_SIZE) + + 2.0 * ITEM_VIEW_MARGIN_HEIGHT + + 2.0 * ITEM_VIEW_PADDING, + first_row_extra: ITEM_VIEW_FIRST_ITEM_TOP_MARGIN - ITEM_VIEW_MARGIN_HEIGHT, + table_row_height: h + 2.0 * (ITEM_VIEW_MARGIN_HEIGHT + 1.0) + 1.0, + tooltip_height: h + 2.0 * (TOOLTIP_FRAME_WIDTH + 1.0), + tooltip_padding: TOOLTIP_FRAME_WIDTH + 1.0, + } + } + + /// The geometry of Plasma's default fonts, `H` = 18. + pub fn breeze_default() -> BreezeGeometry { + BreezeGeometry::for_line_height(crate::typography::breeze_body_line_height()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::typography::{NOTO_SANS_METRICS, face_px, qt_line_height}; + + #[test] + fn the_default_font_gives_the_breeze_sizes() { + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.line_height, 18.0); + assert_eq!(g.push_button_height, 34.0); + assert_eq!(g.push_button_min_width, 84.0); + assert_eq!(g.icon_button_height, 32.0); + assert_eq!(g.icon_button_width, 31.0); + assert_eq!(g.tool_button_height, 28.0); + assert_eq!(g.tool_button_height_medium, 34.0); + assert_eq!(g.framed_tool_button_height, 32.0); + assert_eq!(g.framed_tool_button_height_medium, 38.0); + assert_eq!(g.line_edit_height, 32.0); + assert_eq!(g.combo_box_height, 32.0); + assert_eq!(g.spin_box_height, 32.0); + assert_eq!(g.check_row_height, 22.0); + assert_eq!(g.progress_bar_height, 26.0); + assert_eq!(g.menu_item_height, 29.0); + assert_eq!(g.menu_section_height, 28.0); + assert_eq!(g.menu_separator_height, 5.0); + assert_eq!(g.menu_bar_item_height, 30.0); + assert_eq!(g.tab_height, 30.0); + assert_eq!(g.static_tab_height, 34.0); + assert_eq!(g.header_height, 30.0); + assert_eq!(g.list_row_height, 24.0); + assert_eq!(g.list_row_height_with_icon, 26.0); + assert_eq!(g.list_row_height_with_check, 26.0); + assert_eq!(g.list_row_height + g.first_row_extra, 25.0); + assert_eq!(g.table_row_height, 23.0); + assert_eq!(g.tooltip_height, 26.0); + assert_eq!(g.tooltip_padding, 4.0); + } + + /// The heights Qt reported for Plasma's Breeze at each font size: push + /// button, line edit, combo box, spin box, check box row, menu item, + /// menu section, tab, header, list row, list row with an icon, table + /// row (with its grid line) and progress bar. + const MEASURED: [(f32, [f32; 13]); 6] = [ + ( + 8.0, + [ + 32., 29., 32., 29., 20., 29., 28., 30., 27., 21., 24., 20., 23., + ], + ), + ( + 9.0, + [ + 34., 31., 32., 31., 21., 29., 28., 30., 29., 23., 25., 22., 25., + ], + ), + ( + 10.0, + [ + 34., 32., 32., 32., 22., 29., 28., 30., 30., 24., 26., 23., 26., + ], + ), + ( + 11.0, + [ + 38., 34., 35., 34., 25., 30., 28., 30., 33., 27., 29., 26., 28., + ], + ), + ( + 12.0, + [ + 38., 36., 36., 36., 26., 31., 30., 30., 34., 28., 30., 27., 30., + ], + ), + ( + 14.0, + [ + 42., 40., 40., 40., 30., 35., 34., 34., 38., 32., 34., 31., 34., + ], + ), + ]; + + fn rules(g: &BreezeGeometry) -> [f32; 13] { + [ + g.push_button_height, + g.line_edit_height, + g.combo_box_height, + g.spin_box_height, + g.check_row_height, + g.menu_item_height, + g.menu_section_height, + g.tab_height, + g.header_height, + g.list_row_height, + g.list_row_height_with_icon, + g.table_row_height, + g.progress_bar_height, + ] + } + + #[test] + fn the_rules_are_exact_at_ten_points_and_never_more_than_three_pixels_short() { + for (pt, measured) in MEASURED { + let h = qt_line_height(face_px(pt), NOTO_SANS_METRICS) as f32; + let derived = rules(&BreezeGeometry::for_line_height(h)); + for (i, (rule, qt)) in derived.iter().zip(measured).enumerate() { + if pt == 10.0 { + assert_eq!(*rule, qt, "column {i} at {pt} pt"); + } + assert!( + *rule <= qt && qt - *rule <= 3.0, + "column {i} at {pt} pt: the rule gives {rule}, Qt {qt}" + ); + } + } + } + + #[test] + fn a_larger_font_never_makes_a_control_smaller() { + let small = rules(&BreezeGeometry::for_line_height(15.0)); + let large = rules(&BreezeGeometry::for_line_height(26.0)); + for (a, b) in small.iter().zip(large) { + assert!(b >= *a); + } + } +} diff --git a/crates/teksilo-theme-kde/src/lib.rs b/crates/teksilo-theme-kde/src/lib.rs new file mode 100644 index 000000000..47bfd34a7 --- /dev/null +++ b/crates/teksilo-theme-kde/src/lib.rs @@ -0,0 +1,1742 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! KDE Plasma theme preset for Teksilo: the Breeze design language. +//! +//! The colours come from a KDE colour scheme, the stock Breeze Light or +//! Breeze Dark or any other, read and resolved by [`scheme`] +//! (`teksilo-kde-scheme`) exactly as KDE resolves it, including Plasma's +//! accent colours. The geometry, typography and motion are Breeze's, as +//! Plasma 6.8 ships it ([`BREEZE_VERSION`]). +//! +//! ``` +//! use teksilo_theme_kde::{self as kde, PlasmaAccent}; +//! +//! let light = kde::light(); +//! let dark = kde::dark(); +//! let purple = kde::light_with_accent(PlasmaAccent::Purple.color()); +//! assert_eq!(light.id.as_str(), "kde.light"); +//! assert_eq!(dark.id.as_str(), "kde.dark"); +//! assert_ne!(purple.colors.accent, light.colors.accent); +//! ``` +//! +//! A scheme of the user's own, read from its `.colors` file, gives its own +//! theme, light or dark as KDE judges it: +//! +//! ``` +//! use teksilo_theme_kde::{self as kde, scheme::KdeColorScheme}; +//! +//! let scheme = KdeColorScheme::breeze_dark(); +//! let theme = kde::from_scheme(&scheme); +//! assert!(theme.is_dark()); +//! ``` +//! +//! ## What makes it read as Breeze +//! +//! - **One radius.** Controls and floating surfaces share Breeze's 5 px +//! frame radius (4.5 where a 1 px outline is stroked), between Fluent's 4 +//! and macOS's 6 and 10. +//! - **Breeze's buttons.** A push button is a flat fill outlined in the +//! button colour moved a fifth of the way to its text (the frame +//! contrast), with an outline that turns to the highlight under the +//! pointer or keyboard focus and a 1 px shadow below it in an active +//! window; the default button's fill moves toward the highlight, and a +//! tool button shows nothing until it is hovered ([`styles`]). +//! - **Breeze's indicators and fields.** A check box or radio button is a +//! 15 px box or disc in the button colour, filled toward the highlight and +//! marked in the view's text when checked, its label 24 px from its +//! leading edge; a switch is a handle as tall as its 36 x 18 indicator +//! riding a 12 px track; a text field is a flat view-coloured frame whose +//! 1 px outline turns to the focus colour while it has focus. A combo box +//! is the push button with a chevron 2 px inside its trailing edge, a +//! spin box the line edit with a column of two chevrons and no buttons +//! drawn, and a slider a 6 px groove filled toward the highlight up to a +//! 17 px round handle in the button colour. Other +//! controls are outlined in the window colour moved the same way toward +//! the text, the `border` token; the highlight-based outlines are in +//! [`BreezeColors`]. +//! - **Breeze's menus and popups.** A menu row is 29 px at Plasma's default +//! font, washed with the focus colour at 30 % and outlined while it is +//! highlighted, and filled with the accent while it is held down; a menu, +//! a popover and a tooltip are framed at the 5 px radius in a 1 px +//! outline over Breeze's two-layer window shadow, and a tooltip's text +//! lies 4 px inside its frame. +//! - **Breeze's rows, tabs and bars.** A list or tree row is 24 px at +//! Plasma's default font, 26 with an icon or a check box, its selection a +//! rounded rectangle inside the row's 2 px and 1 px margins, filled with +//! the highlight and written in the text on it, and muted only when the +//! window goes inactive; a table row is 24 px under a 30 px header of +//! button-coloured sections, the column sorted in ascending order shown +//! pressed. A selected tab is the window colour under a +//! 3 px highlight bar, an unselected one the window colour darkened. A +//! scroll bar is an 8 px handle over no track beside a 1 px separator, +//! and a progress bar a 6 px groove filled toward the highlight, its busy +//! state moving stripes. +//! - **Breeze's focus ring geometry.** Keyboard focus is a 2 px band, half +//! inside the control and half outside, in the scheme's focus colour. +//! Breeze paints it at 30 % ([`shape::BREEZE_FOCUS_RING_ALPHA`]); the +//! `focus_ring` token is opaque, lifted to 3:1, and the styles draw it as +//! it is. +//! - **Linear, quick motion.** Style transitions run 100 ms, linearly. +//! - **Breeze's frames and margins.** A card, a panel and a banner are +//! framed at the 5 px radius in the 1 px pen with their content 6 px +//! inside, the margin of a nested layout; a dialog's content lies 10 px +//! inside it, Breeze's dialog margin; a suggestion list lays its rows out +//! as a menu without icons ([`styles::recipes`]). +//! - **Text-driven heights.** A push button is the text line plus 16 px, a +//! field the line plus 14: 34 and 32 px at Plasma's default font, and +//! they grow with it ([`geometry`]). +//! +//! ## The live desktop +//! +//! [`from_desktop`] builds the theme of a KDE desktop as it is configured, +//! read by the platform or by [`scheme`] ([`scheme::KdeDesktop`]): its +//! colour scheme with the user's accent, its fonts, its animation speed and +//! its `breezerc`. [`native_theme`] is the same theme for a desktop styled +//! with Breeze and `None` for one styled otherwise, the shape +//! `teksilo-app`'s native-theme factory takes: +//! +//! ``` +//! use teksilo_theme_kde::{self as kde, scheme::KdeColorScheme, scheme::KdeDesktop}; +//! use teksilo_tokens::TargetDensity; +//! +//! let mut desktop = KdeDesktop::new(KdeColorScheme::breeze_light()); +//! assert!(kde::native_theme(&desktop, TargetDensity::Compact).is_some()); +//! desktop.widget_style = Some("Fusion".to_owned()); +//! assert!(kde::native_theme(&desktop, TargetDensity::Compact).is_none()); +//! ``` +//! +//! Every theme carries the desktop's `breezerc` settings as a +//! [`BreezeStyleConfig`] and its menu and toolbar fonts as +//! [`KdeFontRoles`], at Plasma's defaults for the stock themes. +//! +//! ## Fonts +//! +//! Plasma writes in Noto Sans and Hack. By default the preset draws +//! Teksilo's bundled Inter and JetBrains Mono at Noto Sans and Hack's sizes +//! and line boxes; two Cargo features, both off by default, name Plasma's +//! faces instead ([`fonts`]): +//! +//! - `bundled-fonts` embeds Noto Sans and Hack, about 5.6 MB, and +//! `font_registrar()` registers them for the app builder: +//! +//! ```ignore +//! TeksiloAppBuilder::new() +//! .theme(kde::light()) +//! .register_fonts(kde::font_registrar()) +//! .run(); +//! ``` +//! - `system-fonts` names them without embedding them, for a desktop that +//! has them installed. +//! +//! [`from_desktop`] names the desktop's own fonts either way. +//! +//! ## Accent colour +//! +//! [`light_with_accent`] and [`dark_with_accent`] apply an accent the way +//! Plasma's colour settings apply it to the stock scheme: links, focus and +//! hover take the accent (a link darkened or lightened once when it would be +//! hard to read), and the selection becomes the accent at 70 % over the +//! view with black or white text. So even Breeze's own blue, +//! [`PlasmaAccent::Blue`], gives a lighter selection than [`light`], as it +//! does on a Plasma desktop. [`PlasmaAccent`] lists the ten swatches Plasma +//! offers. +//! +//! ## Colours and WCAG +//! +//! KDE never adjusts a colour for readability, and some stock Breeze colours +//! fall short of WCAG 2.x (the light theme's secondary text, links, status +//! colours, focus colour, frame outlines, scroll-bar thumb and selection +//! text). The theme's colour tokens lift each of them by rule, for any +//! scheme, keeping its hue where it can, and make every lifted colour +//! opaque, a translucent scheme colour included; the [`KdePalette`] +//! extension keeps the scheme's own colours. A scheme's own text is lifted +//! the same way where it falls short, and where the scheme's inactive +//! effects shade an inactive window's backgrounds toward the middle of the +//! scale, those backgrounds move back until text can reach its floor on +//! them. The styles paint their faces from the moved backgrounds: a push +//! button, a combo box, a table header section and a check box carry their +//! label or mark at the same floors in every state, hovered, pressed, +//! checked or the default button, and an unselected tab carries its title +//! in the secondary text, lifted on the tab's darker face at rest and +//! hovered. The rules, the inactive-window and high-contrast colours and the +//! tokens kept from Int UI are listed in the colour mapping's documentation +//! (the private `color` module); every lift is tested to pass and its +//! premise, that the scheme's colour fails, is tested too. +//! +//! An inactive window paints the scheme's Inactive colours: for Breeze, a +//! selection and accent muted to the window colour tinted toward the +//! selection colour, as Plasma paints a backdrop window, with the focus ring +//! unchanged. Under the OS high-contrast preference the theme paints its own +//! high-contrast colours, with 7:1 text and 4.5:1 boundaries, in an active +//! and an inactive window alike. +//! +//! ## Trademarks +//! +//! KDE® and the K Desktop Environment® logo are registered trademarks of +//! KDE e.V. This preset is not affiliated with, endorsed by or sponsored by +//! KDE e.V.; the names KDE, Plasma and Breeze are used only to say which +//! design language it reproduces. It contains no KDE code, artwork, icon or +//! font; the Noto Sans and Hack faces the `bundled-fonts` feature embeds +//! are the Noto project's and Source Foundry's, under their own licences. +//! See `NOTICE` and `TRADEMARKS.md` in the Teksilo repository. +//! +//! ## Known limitations +//! +//! - **Some widget chrome is the framework's.** This version installs +//! Breeze styles for `Button`, `IconButton`, `SplitButton`, `Toggle`, +//! `Checkbox`, `RadioButton`, `TextInput`, an editable `RichTextEditor` +//! (framed as the line edit), `ComboBox`, `SpinBox`, `Slider`, +//! `MenuItem`, the popup frame, the tooltip, `StandardListItem` +//! and `StandardTreeItem`, `TableView` and `TreeTableView`, `TabBar` and +//! `TabWidget`, `ScrollBar` and `ProgressBar`. `Card`, `Panel`, `Dialog`, +//! `Snackbar`, `Toast`, `Banner`, `Link`, `Badge`, `Calendar`, +//! `SearchField`, `Avatar`, `Splitter`, the list containers and +//! `SegmentedControl` keep Teksilo's recipe style built with Breeze's +//! radius, pen and margins, and the dimensions Breeze has no number for, +//! or that the widget does not take from its style (a segmented control's +//! height, a calendar's cell size), stay the framework's +//! ([`styles::recipes`]). Every other widget keeps Teksilo's recipe style +//! as it ships, painted in the Breeze colours, radii, focus ring, +//! typography and motion. +//! - **A menu's shortcuts, separators, section titles and check marks are +//! the framework's.** `MenuItem` writes a shortcut in the shortcut text +//! role, where Breeze writes the window text at 70 % opacity, and gives +//! its style no say over it; a `MenuSeparator` runs the width of the +//! rows, where Breeze stops its line 4 px short of either end; a section +//! title is the widget an app hands `MenuList::header`, which +//! [`styles::metrics::BreezeMenuMetrics::section_height`] can size; and +//! a checkable row shows the framework's check mark in its icon column, +//! not Breeze's check box ([`styles::menu_item`]). +//! - **Every menu is laid out as one with icons.** `MenuItem` reserves its +//! icon column on every row, so a menu without icons keeps the column, +//! where Breeze moves the text 4 px past its 8 px margin instead +//! ([`styles::menu_item`]). +//! - **A pressed menu row is filled with the accent**, not the focus +//! colour, so the text on it is readable (D1); the two are the same colour +//! in the stock Breeze schemes. A highlighted row's outline is lifted to +//! 3:1 (D5, [`styles::menu_item`]). +//! - **A popup has no caret and a shadow on every side**, and is rounded +//! whatever the desktop's compositing; a menu translucent under +//! `breezerc`'s `MenuOpacity` has nothing blurred behind it, where Plasma's +//! compositor blurs what lies under it ([`styles::popup`]). +//! - **A plain tooltip is shorter than Plasma's.** `TooltipWidget` writes its +//! text in the small text style, where Breeze uses the body font, and +//! `TextWidget` lays a single line out at the face's own natural height, +//! ignoring the style's `line_height`. A one-line tooltip is therefore +//! that height plus 2 x 4 px: 21.31 px with the fixed presets (Inter at +//! 11 px, 13.31 px of text) and 22.98 px in system mode (Noto Sans at +//! 11 px, 14.98 px), where Plasma's is 26 px. Every preset has this, +//! because it is the framework's: matching Plasma would take the tooltip +//! text in the body style or `TextWidget` honouring `line_height`, both +//! changes outside this crate ([`styles::tooltip`]). +//! - **A spin box's arrows keep their colour at the limit**, and an empty +//! combo box's arrow is not greyed: the styles are not told the value's +//! limits or the item count ([`styles::spin_box`], +//! [`styles::combo_box`]). +//! - **A spin box's two arrow halves are 20 x 10**, Breeze's geometry, +//! under WCAG 2.2 SC 2.5.8's 24 px at every density; the value is also +//! reachable with the arrow and page keys, the mouse wheel and the +//! assistive Increment and Decrement actions ([`styles::spin_box`]). +//! - **A slider's tick marks are on a fixed side**, below a horizontal +//! slider and to the right of a vertical one: Breeze draws them on the +//! side the application asks for, and a `Slider` names none +//! ([`styles::slider`]). +//! - **A slider's handle is outlined in the hover colour while the pointer +//! is anywhere on the slider**, not only over the handle, and is +//! outlined at 3:1 at rest (D6, [`styles::slider`]). +//! - **A check box or radio button row is 24 px, not 22.** Breeze's row is +//! the label's line box plus 4 px; the widgets keep their 24 px minimum, +//! WCAG 2.2's target size, and paint the 20 px indicator unchanged in it +//! (D8, [`styles::checkbox`]). +//! - **The switch is not exactly the desktop style's.** Its label is the +//! `Toggle`'s, 6 px after the node, where the desktop style spaces it 4 px +//! after the indicator; keyboard focus adds the 2 px focus frame Breeze +//! draws around a check box, which the desktop style's switch has not; +//! the node is raised to the density's target on both axes, the 36 x 18 +//! indicator painted unchanged in it; and a checked track is outlined in +//! the highlight lifted to 3:1 (D6) ([`styles::toggle`]). +//! - **A table's selection band, alternate rows, grid lines and sort arrow +//! are the view's.** `TableView` and `TreeTableView` paint them +//! themselves, from the theme's tokens: the band is square and the +//! highlight, as Breeze's, but it mutes when the table loses keyboard +//! focus as well as when the window goes inactive, and a hovered row is +//! not washed. The style's own row background and sort arrow are +//! Breeze's, for a style that composes them ([`styles::table`]). +//! - **A tab is at least 96 px wide, and never Breeze's 34 px static-bar +//! height.** The bar applies its own minimum tab width, where Breeze's is +//! 80, and a `TabStyle` is not told whether its bar is one Breeze makes +//! 34 px tall (a document-mode, static bar), so every tab is the height of +//! any other: 30 px at the Compact density, rising with the density (32 +//! at Comfortable, 44 at Touch). Both numbers are in +//! [`styles::metrics::BreezeTabMetrics`] ([`styles::tab`]). +//! - **A scroll bar is as thick as the widget lays it out.** A +//! `ScrollArea`'s bars are 12 px and a `ScrollBar`'s 8 px unless the app +//! says otherwise, where Breeze's is 21; the handle keeps its 8 px width +//! and a permanent bar its 1 px separator in whatever thickness it is +//! given. The handle takes the hover colour while the pointer is over the +//! bar, not only over the handle, and is filled in the lifted thumb +//! colours (D9). It runs to the bar's ends, where Breeze keeps 3 px free +//! at each end of a bar without buttons: the widget gives its style no say +//! over the track ([`styles::scroll_bar`]). +//! - **A translucent paint over what lies behind a control is not Plasma's +//! shade.** The renderer composites in linear light and Qt's painter in +//! sRGB, so the push button's 1 px shadow, the scroll bar's separator, a +//! hovered row's outline, a highlighted menu row's wash and every other +//! paint laid at an alpha over a backdrop the style does not paint come +//! out a different shade of the same colour (Breeze Light's button shadow +//! is #e1e2e3 where Plasma's is #d1d2d3), and antialiased edges blend the +//! same way. Where the style paints the backdrop itself, as a check box's +//! wash over its face, it paints Plasma's composite. +//! - **A view's first row is not one taller.** Breeze gives the first row +//! of a view without a frame a 2 px top margin, where every other row has +//! 1, so its first text row is 25 px and its selection lies 2 px from the +//! top ([`geometry::BreezeGeometry::first_row_extra`]); a Teksilo row is +//! not told its index, so every row is 24 px with its selection 1 px from +//! the top ([`styles::standard_item`]). +//! - **A list row does not mark the current row apart from the selected +//! one.** Keyboard focus on a focused view adds the focus frame around the +//! selected row; Breeze outlines an unselected current row instead, which +//! a row is not told it is ([`styles::standard_item`]). +//! - **A progress bar shows no text.** `ProgressBar` has none to show; the +//! bar is still Qt's 26 px, room for it, around the 6 px groove +//! ([`styles::progress_bar`]). +//! - **A short text field keeps its frame.** Breeze paints only the fill of +//! a line edit shorter than the line box plus 12 px; the field keeps its +//! outline at every height, the field's only boundary (D6, +//! [`styles::text_input`]). +//! - **A filled `SplitButton` is filled with the accent.** The widget labels +//! its `Filled` and `Destructive` variants in the on-accent text and gives +//! its style no say, so they are framed in the accent rather than as +//! Breeze's default button, whose light fill would not carry that text in +//! a dark scheme ([`styles::split_button`]). +//! - **Without a font feature, the stock themes' fonts are not Plasma's.** +//! The text sizes and line boxes are Noto Sans and Hack's at Plasma's +//! default sizes, drawn in Teksilo's bundled Inter and JetBrains Mono +//! unless `bundled-fonts` or `system-fonts` is on ([`fonts`]). +//! [`from_desktop`] names the desktop's own families instead, which the +//! text engine finds among the installed fonts or replaces with the +//! bundled ones. +//! - **A desktop's fonts keep the text styles' weights**, 400 and 600, and +//! a family other than Noto Sans and Hack gets a line box from Noto +//! Sans's proportions, which can be a pixel off: the preset measures no +//! font ([`typography`]). A menu's label is written in the general font, +//! its row sized for the taller of the general and menu fonts, and a +//! toolbar's buttons in the general font: Teksilo has no text style for +//! either, and [`KdeFontRoles`] carries them for an app to apply. +//! - **Some `breezerc` settings change nothing.** The menu opacity, the +//! window shadow and the busy progress bar follow `breezerc`; the keys +//! that time Breeze's own style animations, draw its other focus +//! indicators, or change parts the widgets build themselves (scroll-bar +//! buttons, tick marks, tree lines, centred tabs, toolbar separators, +//! mnemonic underlines) are carried and not drawn ([`BreezeStyleConfig`] +//! lists each). +//! - **The high-contrast selection is light on a light window.** Under the +//! OS high-contrast preference the selection fill carries the window's +//! text at 7:1, which keeps it below 4.5:1 on the window (about 1.9:1 for +//! Breeze Light, 2.2:1 for Breeze Dark): no colour reaches both. +//! - **Widget shapes are chosen at build time.** Switching between this and +//! another preset at runtime re-tints instantly but keeps the shapes the +//! tree was built with, a property of the styling system. + +mod breeze_config; +mod color; +pub mod fonts; +pub mod geometry; +pub mod motion; +pub mod palette; +pub mod shape; +pub mod styles; +pub mod typography; + +pub use breeze_config::BreezeStyleConfig; +#[cfg(feature = "bundled-fonts")] +pub use fonts::font_registrar; +pub use palette::{BreezeColors, KdePalette, PlasmaAccent, UnknownPlasmaAccent}; +pub use teksilo_kde_scheme as scheme; +pub use typography::KdeFontRoles; + +use std::rc::Rc; +use std::time::Duration; + +use teksilo_core::presets::intui; +use teksilo_core::styles::{DensityProjection, PaintProjection, PaintState, Theme}; +use teksilo_kde_scheme::{KdeColorScheme, KdeDesktop}; +use teksilo_tokens::{ + Color, InputTokens, LayoutTokens, MotionTokens, ShapeTokens, TargetDensity, TypographyTokens, +}; + +/// The Plasma release whose Breeze style this preset transcribes. +pub const BREEZE_VERSION: &str = "6.8.0"; + +/// `[M]` The spacing between sibling controls: Breeze's layout spacing. +pub const BREEZE_CONTROL_GAP: f32 = 6.0; +/// `[K]` The spacing between sections: Kirigami's large spacing. +pub const BREEZE_SECTION_GAP: f32 = 8.0; + +/// **Breeze Light**, Plasma's default scheme. +pub fn light() -> Theme { + from_scheme(&KdeColorScheme::breeze_light()) +} + +/// **Breeze Dark**. +pub fn dark() -> Theme { + from_scheme(&KdeColorScheme::breeze_dark()) +} + +/// Breeze Light with `accent` applied as Plasma's colour settings apply it. +/// +/// ``` +/// use teksilo_theme_kde::{self as kde, PlasmaAccent}; +/// let theme = kde::light_with_accent(PlasmaAccent::Green.color()); +/// assert!(!theme.is_dark()); +/// ``` +pub fn light_with_accent(accent: Color) -> Theme { + from_scheme(&KdeColorScheme::breeze_light().with_accent(accent)) +} + +/// Breeze Dark with `accent` applied as Plasma's colour settings apply it. +pub fn dark_with_accent(accent: Color) -> Theme { + from_scheme(&KdeColorScheme::breeze_dark().with_accent(accent)) +} + +/// The Breeze theme of any KDE colour scheme. +/// +/// Light or dark, and the id `kde.light` or `kde.dark`, as KDE judges the +/// scheme ([`KdeColorScheme::is_dark`]). The scheme's colours, frame +/// contrast and inactive and disabled effects all come through; geometry, +/// typography and motion are Breeze's. +pub fn from_scheme(scheme: &KdeColorScheme) -> Theme { + build(KdePalette::from_scheme(scheme), Desktop::breeze_default()) +} + +/// The Breeze theme of a KDE desktop as it is configured, at `density`. +/// +/// Everything the desktop sets that the preset can follow comes through: +/// +/// - **Colours**: the desktop's scheme, as [`from_scheme`] takes it, with +/// the accent the user chose already applied ([`KdeDesktop::scheme`]). +/// - **Fonts**: the general, smallest readable and fixed fonts become the +/// six text styles, in the desktop's own families, at the whole pixel +/// sizes Qt draws them at and on Qt's line boxes +/// ([`typography::desktop_typography`]); the control heights follow the +/// general font's line box ([`geometry`]). The menu and toolbar fonts +/// are carried as [`KdeFontRoles`], and the menu rows are sized for the +/// menu font. +/// - **Animation speed**: `AnimationDurationFactor` scales the motion +/// tokens as Kirigami scales its units, with the tooltip delays and the +/// busy progress period left alone ([`motion::desktop_motion`]). A factor +/// of 0 is the desktop asking for reduced motion: the transitions are +/// then 0 or 1 ms long, and [`BreezeStyleConfig::prefers_reduced_motion`] +/// reports it, as the platform does from the same factor. +/// - **`breezerc`**: the menu opacity, the window shadow's size, colour and +/// strength and the busy progress bar's animation; the theme carries all +/// of it as a [`BreezeStyleConfig`], which lists what each key does. +/// +/// The theme's id is `kde.light` or `kde.dark`, as KDE judges the scheme, +/// whatever the desktop's widget style; [`native_theme`] is the form that +/// answers only for a Breeze desktop. +/// +/// ``` +/// use teksilo_theme_kde::{self as kde, scheme::KdeColorScheme, scheme::KdeDesktop}; +/// use teksilo_tokens::TargetDensity; +/// +/// let mut desktop = KdeDesktop::new(KdeColorScheme::breeze_dark()); +/// desktop.animation_duration_factor = Some(2.0); +/// let theme = kde::from_desktop(&desktop, TargetDensity::Touch); +/// assert!(theme.is_dark()); +/// assert_eq!(theme.motion.duration_normal.as_millis(), 400); +/// assert_eq!(theme.input.density, TargetDensity::Touch); +/// ``` +pub fn from_desktop(desktop: &KdeDesktop, density: TargetDensity) -> Theme { + build( + KdePalette::from_scheme(&desktop.scheme), + Desktop::from_desktop(desktop), + ) + .with_density(density) +} + +/// The Breeze theme of a KDE desktop for `teksilo-app`'s native-theme +/// seam, or `None` when the desktop does not use the Breeze widget style. +/// +/// `Some(`[`from_desktop`]`)` when `kdeglobals [KDE] widgetStyle` is absent, +/// empty or `Breeze` in any ASCII case ([`KdeDesktop::uses_breeze_style`]). +/// A desktop styled with Fusion, Oxygen, Kvantum or any other style gets +/// `None`: its applications do not draw Breeze's shapes, so a Teksilo +/// application keeps the framework's default geometry, coloured by the +/// desktop. +/// +/// Meant as `TeksiloAppBuilder::native_theme`'s factory, which is asked +/// with the desktop's report and a density: +/// +/// ```no_run +/// use teksilo_app::{TeksiloAppBuilder, ThemeMode}; +/// +/// TeksiloAppBuilder::new() +/// .theme_mode(ThemeMode::Native) +/// .native_theme(|request| { +/// let desktop = request.desktop.kde.as_ref()?; +/// teksilo_theme_kde::native_theme(desktop, request.density) +/// }) +/// .run(); +/// ``` +/// +/// The seam's contract is that a factory's theme is used as it comes: +/// `resolve_native_theme` sets no id and applies no density, and the window +/// manager, which resolves every theme that follows the OS, then gives it +/// the id `system` and projects it onto the density of the window it is +/// for. So the theme this returns carries `kde.light` or `kde.dark` and +/// `density`, and a window in Native mode shows it as `system`. A `None` +/// makes the seam fall back to the framework's IntUI coloured by the +/// desktop's report, which on a KDE session is the same scheme's colours. +pub fn native_theme(desktop: &KdeDesktop, density: TargetDensity) -> Option { + desktop + .uses_breeze_style() + .then(|| from_desktop(desktop, density)) +} + +/// What a theme takes from a desktop besides its colours. +struct Desktop { + typography: TypographyTokens, + fonts: KdeFontRoles, + shape: ShapeTokens, + motion: MotionTokens, + config: BreezeStyleConfig, +} + +impl Desktop { + /// A desktop at Plasma's defaults, drawn in the bundled faces. + fn breeze_default() -> Desktop { + Desktop { + typography: typography::breeze_typography(), + fonts: KdeFontRoles::breeze_default(), + shape: shape::breeze_shape(), + motion: motion::breeze_motion(), + config: BreezeStyleConfig::default(), + } + } + + /// What `desktop` configures. + fn from_desktop(desktop: &KdeDesktop) -> Desktop { + let config = BreezeStyleConfig::from_desktop(desktop); + let breeze = &config.settings; + let busy = match breeze.progress_bar_busy_step_duration_ms { + ms if ms > 0 => Duration::from_millis(u64::try_from(ms).unwrap_or(u64::MAX)), + // `[unpublished]` A period of no length cannot move; the bar + // stands still and the token keeps Breeze's default. + _ => Duration::from_millis(motion::BREEZE_BUSY_STEP_MS), + }; + Desktop { + typography: typography::desktop_typography(&desktop.fonts), + fonts: KdeFontRoles::from_fonts(&desktop.fonts), + shape: shape::breeze_shape_for( + breeze.shadow_size, + breeze.shadow_color.to_color(), + config.shadow_strength(), + ), + motion: motion::desktop_motion(desktop.animation_factor(), busy), + config, + } + } +} + +fn build(palette: KdePalette, desktop: Desktop) -> Theme { + // Starting from Int UI keeps the input tokens, the density ladder, at + // the framework's Compact baseline; every other token group is replaced. + let mut theme = if palette.is_dark() { + intui::dark().with_id("kde.dark") + } else { + intui::light().with_id("kde.light") + }; + theme.colors = palette.tokens(ACTIVE).clone(); + theme.layout = LayoutTokens { + control_gap: BREEZE_CONTROL_GAP, + section_gap: BREEZE_SECTION_GAP, + }; + theme.typography = desktop.typography; + theme.shape = desktop.shape; + theme.motion = desktop.motion; + theme.extensions.insert(palette); + theme.extensions.insert(desktop.fonts); + theme.extensions.insert(desktop.config); + install_styles(&mut theme); + theme.extensions.insert(DensityProjection(reproject)); + theme.extensions.insert(PaintProjection(project)); + theme +} + +/// The paint state of an active window without the high-contrast +/// preference. +const ACTIVE: PaintState = PaintState { + window_active: true, + high_contrast: false, +}; + +/// Re-derive a Breeze theme for another density. +/// +/// Registered as the theme's [`DensityProjection`]. `[unpublished]` KDE +/// publishes no touch ladder, so the input tokens are the framework's +/// generic [`InputTokens::for_density`] ladder; colours, id, the +/// [`KdePalette`] and both projections ride across untouched, and the +/// slots this preset installs are rebuilt for the new tokens. +fn reproject(base: &Theme, density: TargetDensity) -> Theme { + let mut theme = base.clone(); + theme.input = InputTokens::for_density(density); + install_styles(&mut theme); + theme +} + +/// Re-colour a Breeze theme for a window state and the high-contrast +/// preference. +/// +/// Registered as the theme's [`PaintProjection`]. While the theme's colours +/// are still the active tokens of its [`KdePalette`], the state's own tokens +/// are painted ([`KdePalette::tokens`]). A theme whose colours an app has +/// changed since, or one without the palette, gets the framework's generic +/// inactive and high-contrast projection of its own colours instead, so a +/// change the app made is never painted over. +fn project(theme: &Theme, state: PaintState) -> Theme { + if state == ACTIVE { + return theme.clone(); + } + match theme.extension::() { + Some(palette) if theme.colors == *palette.tokens(ACTIVE) => Theme { + colors: palette.tokens(state).clone(), + ..theme.clone() + }, + _ => { + let mut colors = theme.colors.clone(); + if !state.window_active { + colors = colors.for_inactive_window(); + } + if state.high_contrast { + colors = colors.for_high_contrast(); + } + Theme { + colors, + ..theme.clone() + } + } + } +} + +/// Install the Breeze Tier-3 chrome. +/// +/// The buttons, the check box, the radio button, the switch, the line edit, +/// the combo box, the spin box, the slider, the menu item, the popup frame, +/// the tooltip, the list row, the table, the tab, the scroll bar and the +/// progress bar are Breeze's own ([`styles`]); the card, the panel, the +/// dialog, the snackbar, the toast, the banner, the link, the badge, the +/// calendar, the search field, the avatar, the splitter, the list container +/// and the segmented control are the stock recipe styles built with Breeze's +/// metrics ([`styles::recipes`]); every other slot is left to the widget's +/// recipe style, which paints from the Breeze tokens, and the crate's tests +/// list the reason for each. Every style installed here reads +/// its metrics from the theme it builds under, `theme.input` included, so +/// [`reproject`] can rerun this alone. +fn install_styles(theme: &mut Theme) { + let menu_item = styles::menu_item::BreezeMenuItemStyle::for_theme(theme); + let popover = styles::popover::BreezePopoverStyle::for_tokens(&theme.input); + let tooltip = styles::tooltip::BreezeTooltipStyle::for_tokens(&theme.input); + let standard_item = styles::standard_item::BreezeStandardItemStyle::for_theme(theme); + let table = styles::table::BreezeTableStyle::for_theme(theme); + let tab = styles::tab::BreezeTabStyle::for_theme(theme); + let scroll_bar = styles::scroll_bar::BreezeScrollBarStyle::for_tokens(&theme.input); + let progress_bar = styles::progress_bar::BreezeProgressBarStyle::for_theme(theme); + let slots = &mut theme.style_slots; + slots.button = Some(Rc::new(styles::button::BreezeButtonStyle)); + slots.icon_button = Some(Rc::new(styles::icon_button::BreezeIconButtonStyle)); + slots.split_button = Some(Rc::new(styles::split_button::BreezeSplitButtonStyle)); + slots.toggle = Some(Rc::new(styles::toggle::BreezeToggleStyle)); + slots.checkbox = Some(Rc::new(styles::checkbox::BreezeCheckboxStyle)); + slots.radio = Some(Rc::new(styles::radio::BreezeRadioStyle)); + slots.text_input = Some(Rc::new(styles::text_input::BreezeTextInputStyle)); + slots.rich_text_editor = Some(Rc::new(styles::rich_text_editor::BreezeRichTextEditorStyle)); + slots.combo_box = Some(Rc::new(styles::combo_box::BreezeComboBoxStyle)); + slots.spin_box = Some(Rc::new(styles::spin_box::BreezeSpinBoxStyle)); + slots.slider = Some(Rc::new(styles::slider::BreezeSliderStyle)); + slots.menu_item = Some(Rc::new(menu_item)); + slots.popover = Some(Rc::new(popover)); + slots.tooltip = Some(Rc::new(tooltip)); + slots.standard_item = Some(Rc::new(standard_item)); + slots.table = Some(Rc::new(table)); + slots.tab = Some(Rc::new(tab)); + slots.scroll_bar = Some(Rc::new(scroll_bar)); + slots.progress_bar = Some(Rc::new(progress_bar)); + let t = &theme.input; + slots.card = Some(Rc::new(styles::recipes::breeze_card_style_for(t))); + slots.panel = Some(Rc::new(styles::recipes::breeze_panel_style_for(t))); + slots.dialog = Some(Rc::new(styles::recipes::breeze_dialog_style_for(t))); + slots.snackbar = Some(Rc::new(styles::recipes::breeze_snackbar_style_for(t))); + slots.toast = Some(Rc::new(styles::recipes::breeze_toast_style_for(t))); + slots.banner = Some(Rc::new(styles::recipes::breeze_banner_style_for(t))); + slots.link = Some(Rc::new(styles::recipes::breeze_link_style_for(t))); + slots.badge = Some(Rc::new(styles::recipes::breeze_badge_style_for(t))); + slots.calendar = Some(Rc::new(styles::recipes::breeze_calendar_style_for(t))); + slots.search_field = Some(Rc::new(styles::recipes::breeze_search_field_style_for(t))); + slots.avatar = Some(Rc::new(styles::recipes::breeze_avatar_style_for(t))); + slots.splitter = Some(Rc::new(styles::recipes::breeze_splitter_style_for(t))); + slots.list_container = Some(Rc::new(styles::recipes::breeze_list_container_style_for(t))); + slots.segmented_control = Some(Rc::new( + styles::recipes::breeze_segmented_control_style_for(t), + )); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeSet; + use std::path::Path; + use std::time::Duration; + use teksilo_core::styles::{ComponentStyleSlots, ThemeAppearance}; + use teksilo_kde_scheme::math::{hsv_darker, hsv_lighter, mix}; + use teksilo_kde_scheme::{ + BackgroundRole, ColorSet, ColorState, DecorationRole, ForegroundRole, KdeColor, PaletteRole, + }; + use teksilo_tokens::contrast::composite_contrast; + use teksilo_tokens::{ColorTokens, Easing}; + + /// Whether two themes are the same by content. + /// + /// `Theme`'s own equality compares style slots by pointer and extensions + /// by type only, so it cannot tell two independently built themes + /// apart. The destructuring has no `..`: a field added to `Theme` must + /// be compared here before this compiles. + fn assert_same_theme(a: &Theme, b: &Theme) { + let Theme { + id, + appearance, + colors, + layout, + typography, + shape, + motion, + input, + style_slots, + extensions, + } = a; + assert_eq!(*id, b.id, "id"); + assert_eq!(*appearance, b.appearance, "appearance"); + assert_eq!(*colors, b.colors, "colors"); + assert_eq!(*layout, b.layout, "layout"); + assert_eq!(*typography, b.typography, "typography"); + assert_eq!(*shape, b.shape, "shape"); + assert_eq!(*motion, b.motion, "motion"); + assert_eq!(*input, b.input, "input"); + assert_eq!( + style_slots.installed(), + b.style_slots.installed(), + "installed slots" + ); + assert_eq!( + extensions.get::(), + b.extension::(), + "KDE palette" + ); + assert_eq!( + extensions.get::(), + b.extension::(), + "KDE font roles" + ); + assert_eq!( + extensions.get::(), + b.extension::(), + "Breeze style config" + ); + } + + fn paint(theme: &Theme, window_active: bool, high_contrast: bool) -> Theme { + theme.for_paint_state(PaintState { + window_active, + high_contrast, + }) + } + + fn hex8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + #[test] + fn light_reports_light_appearance() { + assert_eq!(light().appearance, ThemeAppearance::Light); + assert!(!light().is_dark()); + } + + #[test] + fn dark_reports_dark_appearance() { + assert_eq!(dark().appearance, ThemeAppearance::Dark); + assert!(dark().is_dark()); + } + + #[test] + fn ids_are_namespaced_and_follow_the_appearance() { + assert_eq!(light().id.as_str(), "kde.light"); + assert_eq!(dark().id.as_str(), "kde.dark"); + // The widget catalog persists themes by this id. + assert_eq!( + light_with_accent(PlasmaAccent::Red.color()).id.as_str(), + "kde.light" + ); + assert_eq!( + dark_with_accent(PlasmaAccent::Red.color()).id.as_str(), + "kde.dark" + ); + assert_eq!( + from_scheme(&KdeColorScheme::breeze_classic()).id.as_str(), + "kde.light" + ); + } + + #[test] + fn from_scheme_of_breeze_light_is_light() { + assert_same_theme(&from_scheme(&KdeColorScheme::breeze_light()), &light()); + assert_same_theme(&from_scheme(&KdeColorScheme::breeze_dark()), &dark()); + } + + #[test] + fn every_theme_carries_the_desktop_extensions_at_their_defaults() { + for t in [ + light(), + dark(), + light_with_accent(PlasmaAccent::Red.color()), + ] { + assert_eq!( + t.extension::(), + Some(&BreezeStyleConfig::default()) + ); + assert_eq!( + t.extension::(), + Some(&KdeFontRoles::breeze_default()) + ); + } + } + + /// The default desktop is the stock theme but for the families, which + /// a desktop names and the stock theme leaves to the bundled faces. + #[test] + fn a_desktop_at_plasmas_defaults_is_the_stock_theme_in_its_own_faces() { + let desktop = from_desktop( + &KdeDesktop::new(KdeColorScheme::breeze_light()), + TargetDensity::Compact, + ); + let stock = light(); + let mut renamed = stock.clone(); + renamed.typography = desktop.typography.clone(); + renamed + .extensions + .insert(desktop.extension::().unwrap().clone()); + assert_same_theme(&desktop, &renamed); + assert_eq!(desktop.typography.body.family, "Noto Sans"); + assert_eq!(desktop.typography.mono.family, "Hack"); + for (a, b) in [ + (&desktop.typography.body, &stock.typography.body), + (&desktop.typography.small, &stock.typography.small), + (&desktop.typography.mono, &stock.typography.mono), + ] { + assert_eq!( + (a.size, a.line_height, a.weight), + (b.size, b.line_height, b.weight) + ); + } + } + + #[test] + fn breezes_own_blue_as_an_accent_still_changes_the_colours() { + // Plasma's accent application blends the selection from the accent + // over the view, so applying Breeze's own blue lightens it. + let accented = light_with_accent(Color::from_hex("#3daee9")); + let base = light(); + assert_ne!(accented.colors, base.colors); + assert_eq!(hex8(accented.colors.accent), [119, 198, 239, 255]); + assert_eq!(hex8(base.colors.accent), [61, 174, 233, 255]); + // Everything that is not a colour is the same. + let Theme { + id, + appearance, + layout, + typography, + shape, + motion, + input, + .. + } = &accented; + assert_eq!(*id, base.id); + assert_eq!(*appearance, base.appearance); + assert_eq!(*layout, base.layout); + assert_eq!(*typography, base.typography); + assert_eq!(*shape, base.shape); + assert_eq!(*motion, base.motion); + assert_eq!(*input, base.input); + } + + #[test] + fn surfaces_are_the_breeze_window_backgrounds() { + assert_eq!(hex8(light().colors.surface_main), [239, 240, 241, 255]); + assert_eq!(hex8(dark().colors.surface_main), [32, 35, 38, 255]); + assert_eq!(hex8(light().colors.surface_content), [255, 255, 255, 255]); + assert_eq!(hex8(dark().colors.surface_content), [20, 22, 24, 255]); + } + + #[test] + fn the_accent_is_the_highlight_with_plasmas_black_text() { + for t in [light(), dark()] { + assert_eq!(hex8(t.colors.accent), [61, 174, 233, 255]); + assert_eq!(t.colors.text_on_accent, Color::BLACK); + } + } + + #[test] + fn geometry_is_the_breeze_frame_radius_and_ring() { + for t in [light(), dark()] { + assert_eq!(t.shape.radius_control, 5.0); + assert_eq!(t.shape.radius_popup, 5.0); + assert_eq!(t.shape.focus_ring_width, 2.0); + assert_eq!(t.shape.focus_ring_offset, -1.0); + assert_eq!(t.layout.control_gap, 6.0); + assert_eq!(t.layout.section_gap, 8.0); + } + } + + #[test] + fn body_is_qts_thirteen_pixel_face_on_an_eighteen_pixel_line() { + let t = light(); + assert_eq!(t.typography.body.size, 13.0); + assert!((t.typography.body.size * t.typography.body.line_height - 18.0).abs() < 1e-4); + assert_eq!(t.typography.body.letter_spacing, 0.0); + } + + #[test] + fn motion_is_breezes_linear_hundred_milliseconds() { + for t in [light(), dark()] { + assert_eq!(t.motion.duration_fast, Duration::from_millis(100)); + assert_eq!(t.motion.duration_normal, Duration::from_millis(200)); + assert_eq!(t.motion.easing_standard, Easing::Linear); + } + } + + #[test] + fn the_palette_extension_is_present_and_matches_the_appearance() { + let l = light(); + let d = dark(); + let lp = l.extension::().expect("light palette"); + let dp = d.extension::().expect("dark palette"); + assert!(!lp.is_dark()); + assert!(dp.is_dark()); + assert_eq!( + lp.role(ColorState::Active, PaletteRole::Window), + l.colors.surface_main + ); + assert!((lp.frame_contrast() - 0.2).abs() < 1e-12); + } + + /// Every slot the preset deliberately leaves to the widget's own recipe + /// style, with the reason it is inherited rather than filled. Each is a + /// widget Breeze has no counterpart or no metric for, or one whose + /// dimensions the style cannot set; its recipe paints from the Breeze + /// tokens all the same. + const KDE_UNFILLED: &[(&str, &str)] = &[ + ( + "radio_tile", + "Breeze has no selectable card, so there is no Breeze metric to build it with; \ + the recipe frame paints the Breeze surface, accent, border and focus roles", + ), + ( + "chart", + "Breeze has no chart chrome, and the default ChartStyle lives in teksilo-charts, \ + which this preset does not depend on; it reads the series colours and the Breeze border role", + ), + ( + "color_picker", + "Breeze specifies no colour-picker geometry; the canvas, strips and swatches are \ + colour-space renderers that read the recipe's own dimensions, drawn in the theme roles", + ), + ( + "date_edit", + "the recipe is a passthrough around the TextInput, which this preset frames as Breeze's line edit", + ), + ( + "drop_zone", + "Breeze has no drop-zone control, so there is no Breeze metric to build it with; \ + the recipe tints its region from the theme roles", + ), + ( + "drop_target", + "Breeze has no drop-target control, so there is no Breeze metric to build it with; \ + the recipe border reads the theme roles", + ), + ( + "grid_view", + "Breeze specifies no grid view; the recipe's focus ring, marquee, insertion bar and \ + pinned header are the trait's default decorations, drawn in the roles this preset maps", + ), + ( + "web_view", + "the page paints itself; the recipe only tints the overlay shown over it, from theme roles", + ), + ( + "text_selection", + "Breeze defines no touch selection handles or magnifier, so there is no Breeze \ + metric to build it with; the recipe paints them in the theme roles", + ), + ]; + + /// Whether the installed slots and [`KDE_UNFILLED`] are exactly + /// [`ComponentStyleSlots::NAMES`], with no slot in both. + #[test] + fn installed_and_unfilled_slots_partition_the_roster() { + let unfilled: BTreeSet<&str> = KDE_UNFILLED.iter().map(|(name, _)| *name).collect(); + assert_eq!(unfilled.len(), KDE_UNFILLED.len(), "a slot is listed twice"); + for (name, reason) in KDE_UNFILLED { + assert!( + !reason.trim().is_empty(), + "{name} is listed without a reason" + ); + } + let names: BTreeSet<&str> = ComponentStyleSlots::NAMES.iter().copied().collect(); + for t in [light(), dark(), light().with_density(TargetDensity::Touch)] { + let installed: BTreeSet<&str> = t.style_slots.installed().into_iter().collect(); + let both: Vec<_> = installed.intersection(&unfilled).collect(); + assert!( + both.is_empty(), + "installed yet listed as unfilled: {both:?}" + ); + let covered: BTreeSet<&str> = installed.union(&unfilled).copied().collect(); + let missing: Vec<_> = names.difference(&covered).collect(); + let unknown: Vec<_> = covered.difference(&names).collect(); + assert!( + missing.is_empty() && unknown.is_empty(), + "neither installed nor listed: {missing:?}; not a slot: {unknown:?}" + ); + } + } + + #[test] + fn an_accent_moves_the_accent_family_and_the_links_but_no_neutral() { + let t = light_with_accent(PlasmaAccent::Purple.color()); + let base = light(); + assert_ne!(t.colors.accent, base.colors.accent); + assert_ne!(t.colors.focus_ring, base.colors.focus_ring); + assert_ne!(t.colors.surface_selected, base.colors.surface_selected); + // Plasma writes the accent into the link colour too, unlike macOS. + assert_ne!(t.colors.text_link, base.colors.text_link); + assert_eq!(t.colors.surface_main, base.colors.surface_main); + assert_eq!(t.colors.surface_content, base.colors.surface_content); + assert_eq!(t.colors.text_primary, base.colors.text_primary); + let palette = t.extension::().unwrap(); + let [r, g, b, _] = palette + .resolved() + .set(ColorState::Active, ColorSet::View) + .decoration(DecorationRole::Focus) + .to_rgba8(); + assert_eq!([r, g, b], [184, 117, 220]); + } + + #[test] + fn every_swatch_builds_a_usable_theme() { + for accent in PlasmaAccent::ALL { + for t in [ + light_with_accent(accent.color()), + dark_with_accent(accent.color()), + ] { + let c = &t.colors; + for fill in [c.accent, c.accent_hover, c.accent_pressed] { + assert!( + composite_contrast(c.text_on_accent, fill) >= 4.5, + "{accent:?}: the accent label is {:.2}:1", + composite_contrast(c.text_on_accent, fill) + ); + } + assert!(t.extension::().is_some()); + } + } + } + + #[test] + fn the_inactive_projection_paints_the_schemes_muted_selection() { + for t in [light(), dark()] { + let palette = t.extension::().unwrap(); + let inactive = paint(&t, false, false); + let pi = palette.role(ColorState::Inactive, PaletteRole::Highlight); + // KDE's Inactive state: the window colour tinted toward the + // selection colour. + assert_eq!(inactive.colors.accent, pi); + assert_ne!(inactive.colors.accent, t.colors.accent); + let chroma = |c: Color| { + let (_, s, _) = c.to_hsv(); + s + }; + assert!(chroma(inactive.colors.accent) < chroma(t.colors.accent)); + // Breeze's inactive effects are off: the neutrals pass through + // and the focus ring keeps the focus decoration. + assert_eq!(inactive.colors.surface_main, t.colors.surface_main); + assert_eq!(inactive.colors.text_primary, t.colors.text_primary); + assert_eq!(inactive.colors.focus_ring, t.colors.focus_ring); + assert_eq!( + inactive.colors, + *palette.tokens(PaintState { + window_active: false, + high_contrast: false, + }) + ); + } + // The Breeze Light example of the specification: 194,224,245. + assert_eq!( + hex8(paint(&light(), false, false).colors.accent), + [194, 224, 245, 255] + ); + } + + #[test] + fn a_scheme_with_inactive_effects_is_honoured() { + let mut scheme = KdeColorScheme::breeze_light(); + scheme.inactive_effect.enabled = true; + scheme.inactive_effect.intensity_effect = teksilo_kde_scheme::IntensityEffect::Darken; + scheme.inactive_effect.intensity_amount = 0.2; + let t = from_scheme(&scheme); + let inactive = paint(&t, false, false); + assert_ne!(inactive.colors.surface_main, t.colors.surface_main); + let palette = t.extension::().unwrap(); + assert_eq!( + inactive.colors.surface_main, + palette.role(ColorState::Inactive, PaletteRole::Window) + ); + } + + #[test] + fn a_colour_the_app_changed_is_never_painted_over() { + let mut t = light(); + t.colors.accent = Color::from_hex("#FF0000"); + let inactive = paint(&t, false, false); + assert_eq!(inactive.colors, t.colors.for_inactive_window()); + assert_eq!(paint(&t, true, false).colors, t.colors); + let mut bare = light(); + bare.extensions.remove::(); + assert_eq!( + paint(&bare, true, true).colors, + bare.colors.for_high_contrast() + ); + } + + #[test] + fn the_active_paint_state_is_the_theme_itself() { + let t = light(); + assert_same_theme(&paint(&t, true, false), &t); + } + + #[test] + fn a_density_switch_keeps_the_colours_and_both_projections() { + let base = light(); + let touch = base.with_density(TargetDensity::Touch); + assert_eq!(touch.input, InputTokens::for_density(TargetDensity::Touch)); + assert_eq!(touch.input.density, TargetDensity::Touch); + assert_eq!(touch.id, base.id); + assert_eq!(touch.colors, base.colors); + assert_eq!( + touch.extension::(), + base.extension::() + ); + assert!(touch.extension::().is_some()); + assert!(touch.extension::().is_some()); + // The projection still works on the re-derived theme. + assert_eq!( + paint(&touch, false, false).colors, + paint(&base, false, false).colors + ); + } + + /// The menu metrics `MenuItem`, `MenuSeparator` and `MenuList` read + /// follow the density a theme is projected to: the installed style is + /// rebuilt from the projected input tokens. + #[test] + fn a_density_switch_reprojects_the_menu_metrics() { + use teksilo_core::styles::MenuItemStyle; + let base = light(); + for (density, item, separator) in [ + (TargetDensity::Compact, 29.0, 5.0), + (TargetDensity::Comfortable, 32.0, 5.75), + (TargetDensity::Touch, 44.0, 6.5), + ] { + let t = base.with_density(density); + let m = t + .style_slots + .menu_item + .as_ref() + .expect("a menu style") + .metrics(); + assert_eq!(m.item_height, item, "{density:?}"); + assert!((m.separator_height - separator).abs() < 1e-4, "{density:?}"); + assert_eq!(m.icon_column_width, 16.0, "{density:?}"); + assert_eq!( + m, + styles::menu_item::BreezeMenuItemStyle::for_theme(&t).metrics(), + "{density:?}" + ); + } + // Projecting back to Compact gives Breeze's own numbers again. + let back = base + .with_density(TargetDensity::Touch) + .with_density(TargetDensity::Compact); + let m = back.style_slots.menu_item.as_ref().unwrap().metrics(); + assert_eq!(m.item_height, 29.0); + } + + /// The doc comment directly above the limitations test and the row + /// density test describes that test, not its neighbour. + #[test] + fn the_limitations_and_row_density_tests_carry_their_own_documentation() { + let source = include_str!("lib.rs"); + let doc_above = |name: &str| { + let at = source + .find(&format!(" #[test]\n fn {name}()")) + .unwrap_or_else(|| panic!("no test {name}")); + let mut lines: Vec<&str> = source[..at] + .lines() + .rev() + .take_while(|l| l.trim_start().starts_with("///")) + .map(|l| l.trim_start().trim_start_matches("///").trim()) + .collect(); + lines.reverse(); + lines.join(" ") + }; + let rows = doc_above("a_density_switch_reprojects_the_rows_and_bars"); + assert!( + rows.starts_with("The rows a `StandardListItem` lays out follow the density"), + "{rows:?}" + ); + let limitations = + doc_above("known_limitations_state_the_tab_and_first_row_heights_the_styles_give"); + assert!( + limitations.starts_with("The known limitations give the heights"), + "{limitations:?}" + ); + } + + /// The known limitations give the heights the styles do: a tab's at each + /// density, never Breeze's static-bar height, and a first row as tall as + /// the others where Breeze's is one taller. + #[test] + fn known_limitations_state_the_tab_and_first_row_heights_the_styles_give() { + // The crate documentation, as one line of prose. + let doc = include_str!("lib.rs") + .lines() + .filter_map(|l| l.strip_prefix("//!")) + .map(str::trim) + .collect::>() + .join(" "); + let limitations = &doc[doc.find("## Known limitations").expect("the section")..]; + let tab = |density| { + let t = light().with_density(density); + t.style_slots.tab.as_ref().unwrap().editor_height(&t.input) + }; + let static_height = + styles::metrics::breeze_tab_metrics_for(&light().input, &light().typography) + .static_height; + for stated in [ + format!("never Breeze's {static_height} px static-bar height"), + format!("{} px at the Compact density", tab(TargetDensity::Compact)), + format!("{} at Comfortable", tab(TargetDensity::Comfortable)), + format!("{} at Touch", tab(TargetDensity::Touch)), + ] { + assert!(limitations.contains(&stated), "not stated: {stated}"); + } + assert!( + !limitations.contains("always 30 px tall"), + "a tab's height rises with the density" + ); + + // Breeze's first row is one taller, which the row style never makes + // it, and the limitations say so with both heights. + let t = light(); + let g = geometry::BreezeGeometry::for_line_height(18.0); + assert!( + g.first_row_extra > 0.0, + "premise: Breeze's first row is taller" + ); + let row = t + .style_slots + .standard_item + .as_ref() + .unwrap() + .min_height_single_line(&t.input); + assert_eq!(row, g.list_row_height, "every row is the same height"); + for stated in [ + "A view's first row is not one taller.".to_owned(), + format!( + "first text row is {} px", + g.list_row_height + g.first_row_extra + ), + format!("every row is {row} px"), + ] { + assert!(limitations.contains(&stated), "not stated: {stated}"); + } + } + + /// The rows a `StandardListItem` lays out follow the density a theme is + /// projected to: the installed row, table, tab, scroll-bar and + /// progress-bar styles are rebuilt from the projected input tokens, and + /// a row measured in a tree is the one the style reports. + #[test] + fn a_density_switch_reprojects_the_rows_and_bars() { + use std::cell::RefCell; + use teksilo_canvas::{MockTextBackend, SizeProposal}; + use teksilo_core::widget_tree::WidgetTree; + let base = light(); + for (density, row, table, tab) in [ + (TargetDensity::Compact, 24.0, 24.0, 30.0), + (TargetDensity::Comfortable, 32.0, 32.0, 32.0), + (TargetDensity::Touch, 44.0, 44.0, 44.0), + ] { + let t = base.with_density(density); + let slots = &t.style_slots; + let input = &t.input; + let rows = slots.standard_item.as_ref().expect("a row style"); + assert_eq!(rows.min_height_single_line(input), row, "{density:?}"); + assert_eq!( + slots + .table + .as_ref() + .expect("a table style") + .row_height(input), + table, + "{density:?}" + ); + assert_eq!( + slots + .tab + .as_ref() + .expect("a tab style") + .editor_height(input), + tab, + "{density:?}" + ); + assert_eq!( + slots + .scroll_bar + .as_ref() + .expect("a scroll-bar style") + .min_thumb_length(input), + 20.0 + ); + assert_eq!( + slots + .progress_bar + .as_ref() + .expect("a progress-bar style") + .thickness(input), + Some(26.0) + ); + let mut tree = WidgetTree::new() + .with_theme(t.clone()) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let id = tree.add(teksilo_widgets::StandardListItem::new(teksilo_i18n::lit!( + "Row" + ))); + tree.layout(SizeProposal { + width: Some(300.0), + height: None, + }); + assert_eq!(tree.bounds(id).height, row, "{density:?}: the rendered row"); + } + } + + /// The recipe slots built with Breeze's metrics are rebuilt for the + /// density a theme is projected to: what a widget asking the installed + /// style is told follows the projected input tokens. + #[test] + fn a_density_switch_reprojects_the_recipe_slots() { + let base = light(); + for (density, factor, arrow) in [ + (TargetDensity::Compact, 1.0, 24.0), + (TargetDensity::Comfortable, 1.15, 32.0), + (TargetDensity::Touch, 1.3, 44.0), + ] { + let t = base.with_density(density); + let slots = &t.style_slots; + let search = slots.search_field.as_ref().expect("a search-field style"); + assert!( + (search.row_padding_horizontal(&t.input) - 16.0 * factor).abs() < 1e-4, + "{density:?}" + ); + assert!( + (search.row_padding_vertical(&t.input) - 4.0 * factor).abs() < 1e-4, + "{density:?}" + ); + let calendar = slots.calendar.as_ref().expect("a calendar style"); + assert_eq!(calendar.nav_arrow_size(&t.input), arrow, "{density:?}"); + for (name, installed) in [ + ("card", slots.card.is_some()), + ("panel", slots.panel.is_some()), + ("dialog", slots.dialog.is_some()), + ("snackbar", slots.snackbar.is_some()), + ("toast", slots.toast.is_some()), + ("banner", slots.banner.is_some()), + ("link", slots.link.is_some()), + ("badge", slots.badge.is_some()), + ("avatar", slots.avatar.is_some()), + ("splitter", slots.splitter.is_some()), + ("list_container", slots.list_container.is_some()), + ("segmented_control", slots.segmented_control.is_some()), + ] { + assert!(installed, "{density:?}: no {name} style"); + } + } + } + + /// Every colour token, pinned to its source in Breeze Light's resolved + /// palette. The destructuring has no `..`: a field added to + /// `ColorTokens` must be pinned here before this compiles. + /// + /// A lifted token is pinned by its rule: it keeps its source's hue, + /// reaches its floor, and equals the source where the source already + /// does (checked in Breeze Dark, where most of them pass). + #[test] + fn every_colour_token_comes_from_its_documented_source() { + let t = light(); + let p = t.extension::().unwrap().clone(); + let r = |role| p.resolved().role(ColorState::Active, role); + let d = |role| p.resolved().role(ColorState::Disabled, role); + let i = |role| p.resolved().role(ColorState::Inactive, role); + let view = *p.resolved().set(ColorState::Active, ColorSet::View); + let win = *p.resolved().set(ColorState::Active, ColorSet::Window); + let tip = *p.resolved().set(ColorState::Active, ColorSet::Tooltip); + let c = |k: KdeColor| k.to_color(); + let window = r(PaletteRole::Window); + let base = r(PaletteRole::Base); + let frame_background = mix(window, base, 0.3); + let outline = mix(window, r(PaletteRole::WindowText), 0.2); + let hover_wash = mix(base, r(PaletteRole::Highlight), 0.3); + let readable = |token: Color, source: KdeColor, floor: f32, on: &[Color]| { + let (h1, s1, _) = token.to_hsv(); + let (h2, s2, _) = c(source).to_hsv(); + if s1 > 0.05 && s2 > 0.05 { + let dh = (h1 - h2).abs().min(360.0 - (h1 - h2).abs()); + assert!(dh < 6.0, "{token:?} lost the hue of {source:?}"); + } + for bg in on { + assert!( + composite_contrast(token, *bg) >= floor, + "{token:?} from {source:?} is {:.2}:1 on {bg:?}", + composite_contrast(token, *bg) + ); + } + }; + let surfaces = [t.colors.surface_main, t.colors.surface_content]; + let kept = ColorTokens::light_default(); + + let ColorTokens { + surface_main, + surface_content, + surface_raised, + surface_sunken, + surface_hover, + surface_pressed, + surface_selected, + surface_selected_inactive, + surface_alt_row, + surface_disabled, + text_primary, + text_secondary, + text_disabled, + text_on_accent, + text_link, + text_link_hover, + text_link_visited, + text_error, + text_warning, + text_success, + accent, + accent_hover, + accent_pressed, + accent_disabled, + accent_subtle_bg, + border, + border_strong, + border_focused, + border_error, + border_warning, + border_disabled, + divider, + divider_strong, + status_info_fg, + status_info_bg, + status_success_fg, + status_success_bg, + status_warning_fg, + status_warning_bg, + status_error_fg, + status_error_bg, + selection_bg_active, + selection_text_active, + selection_bg_inactive, + selection_text_inactive, + search_match_bg, + search_match_current_bg, + scrollbar_track, + scrollbar_track_hover, + scrollbar_thumb, + scrollbar_thumb_hover, + scrollbar_thumb_pressed, + tooltip_bg, + tooltip_text, + tooltip_border, + tooltip_shortcut, + editor_bg, + editor_fg, + editor_caret, + editor_current_line_bg, + editor_gutter_fg, + editor_selection_bg, + editor_code_block_bg, + editor_code_block_fg, + focus_ring, + focus_ring_error, + scrim, + text_on_error, + text_on_error_container, + surface_error_container, + surface_container, + surface_container_raised, + surface_container_sunken, + chart_palette, + } = t.colors.clone(); + + // Surfaces. + assert_eq!(surface_main, c(window)); + assert_eq!(surface_content, c(base)); + assert_eq!(surface_raised, c(frame_background)); + assert_eq!(surface_sunken, c(win.background(BackgroundRole::Alternate))); + assert_eq!(surface_hover, c(hover_wash)); + assert_eq!( + surface_pressed, + c(mix(r(PaletteRole::Button), r(PaletteRole::Highlight), 0.3)) + ); + assert_eq!(surface_selected, c(r(PaletteRole::Highlight))); + assert_eq!(surface_selected_inactive, c(i(PaletteRole::Highlight))); + assert_eq!(surface_alt_row, c(r(PaletteRole::AlternateBase))); + assert_eq!(surface_disabled, c(d(PaletteRole::Button))); + assert_eq!(surface_container, c(frame_background)); + assert_eq!(surface_container_raised, c(base)); + assert_eq!(surface_container_sunken, surface_sunken); + assert_eq!( + surface_error_container, + c(view.background(BackgroundRole::Negative)) + ); + + // Text. + assert_eq!(text_primary, c(r(PaletteRole::WindowText))); + readable( + text_secondary, + win.foreground(ForegroundRole::Inactive), + 4.5, + &surfaces, + ); + assert_eq!(text_disabled, c(d(PaletteRole::WindowText))); + assert_eq!(text_on_accent, Color::BLACK); + readable( + text_link, + view.foreground(ForegroundRole::Link), + 4.5, + &surfaces, + ); + readable( + text_link_hover, + view.foreground(ForegroundRole::Active), + 4.5, + &surfaces, + ); + readable( + text_link_visited, + view.foreground(ForegroundRole::Visited), + 4.5, + &surfaces, + ); + readable( + text_error, + view.foreground(ForegroundRole::Negative), + 4.5, + &surfaces, + ); + readable( + text_warning, + view.foreground(ForegroundRole::Neutral), + 4.5, + &surfaces, + ); + readable( + text_success, + view.foreground(ForegroundRole::Positive), + 4.5, + &surfaces, + ); + assert_eq!(text_on_error, Color::WHITE); + assert_eq!(text_on_error_container, text_error); + + // Accent. + assert_eq!(accent, c(r(PaletteRole::Highlight))); + assert_eq!(accent_hover, c(hsv_lighter(r(PaletteRole::Highlight), 110))); + assert_eq!( + accent_pressed, + c(hsv_darker(r(PaletteRole::Highlight), 110)) + ); + assert_eq!(accent_disabled, c(d(PaletteRole::Highlight))); + assert_eq!(accent_subtle_bg, c(hover_wash)); + + // Borders. + assert_eq!(border, c(outline)); + readable(border_strong, outline, 3.0, &surfaces); + assert_eq!(divider, c(outline)); + assert_eq!(divider_strong, border_strong); + readable( + focus_ring, + view.decoration(DecorationRole::Focus), + 3.0, + &surfaces, + ); + assert_eq!(border_focused, focus_ring); + readable( + border_error, + view.foreground(ForegroundRole::Negative), + 3.0, + &surfaces, + ); + readable( + border_warning, + view.foreground(ForegroundRole::Neutral), + 3.0, + &surfaces, + ); + assert_eq!(focus_ring_error, border_error); + assert_eq!( + border_disabled, + c(mix(d(PaletteRole::Window), d(PaletteRole::WindowText), 0.2)) + ); + + // Status. + assert_eq!(status_error_fg, text_error); + assert_eq!(status_warning_fg, text_warning); + assert_eq!(status_success_fg, text_success); + readable( + status_info_fg, + view.foreground(ForegroundRole::Active), + 4.5, + &surfaces, + ); + assert_eq!(status_info_bg, c(view.background(BackgroundRole::Active))); + assert_eq!( + status_success_bg, + c(view.background(BackgroundRole::Positive)) + ); + assert_eq!( + status_warning_bg, + c(view.background(BackgroundRole::Neutral)) + ); + assert_eq!( + status_error_bg, + c(view.background(BackgroundRole::Negative)) + ); + + // Selection. + assert_eq!(selection_bg_active, surface_selected); + assert_eq!(selection_text_active, Color::BLACK); + assert_eq!(selection_bg_inactive, surface_selected_inactive); + assert_eq!(selection_text_inactive, c(i(PaletteRole::HighlightedText))); + assert_eq!(search_match_bg, kept.search_match_bg); + assert_eq!(search_match_current_bg, kept.search_match_current_bg); + + // Scroll bar. + assert_eq!(scrollbar_track, Color::TRANSPARENT); + assert_eq!(scrollbar_track_hover, Color::TRANSPARENT); + readable( + scrollbar_thumb, + mix(window, r(PaletteRole::WindowText), 0.175), + 3.0, + &surfaces, + ); + readable( + scrollbar_thumb_hover, + mix(window, view.decoration(DecorationRole::Hover), 0.5), + 3.0, + &surfaces, + ); + assert_eq!(scrollbar_thumb_pressed, scrollbar_thumb_hover); + + // Tooltip. + let tip_bg = tip.background(BackgroundRole::Normal); + let tip_fg = tip.foreground(ForegroundRole::Normal); + assert_eq!(tooltip_bg, c(tip_bg)); + assert_eq!(tooltip_text, c(tip_fg)); + assert_eq!(tooltip_border, c(mix(tip_bg, tip_fg, 0.2))); + readable( + tooltip_shortcut, + tip.foreground(ForegroundRole::Inactive), + 4.5, + &[tooltip_bg], + ); + + // Editor. + assert_eq!(editor_bg, c(base)); + assert_eq!(editor_fg, c(r(PaletteRole::Text))); + assert_eq!(editor_caret, editor_fg); + assert_eq!(editor_current_line_bg, c(r(PaletteRole::AlternateBase))); + readable( + editor_gutter_fg, + view.foreground(ForegroundRole::Inactive), + 4.5, + &[editor_bg, editor_current_line_bg], + ); + assert_eq!(editor_selection_bg, surface_selected); + assert_eq!(editor_code_block_bg, c(mix(base, window, 0.5))); + assert_eq!(editor_code_block_fg, editor_fg); + + // Kept from Int UI. + assert_eq!(scrim, kept.scrim); + assert_eq!(chart_palette, kept.chart_palette); + + // Where the source already reaches its floor the lift leaves it + // alone: Breeze Dark's tooltip shortcut text is readable as it is. + let dark = super::dark(); + let dp = dark.extension::().unwrap(); + let dark_shortcut = dp + .resolved() + .set(ColorState::Active, ColorSet::Tooltip) + .foreground(ForegroundRole::Inactive); + assert!(composite_contrast(dark_shortcut.to_color(), dark.colors.tooltip_bg) >= 4.5); + assert_eq!(dark.colors.tooltip_shortcut, dark_shortcut.to_color()); + } + + /// No assignment may stay tagged as unverified: the tag means a value + /// the specification has not settled. + #[test] + fn no_unverified_tags() { + let needle = concat!("[", "unverified", "]"); + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let mut found = Vec::new(); + let mut scanned = 0; + let mut stack = vec![root.join("src"), root.join("tests")]; + while let Some(dir) = stack.pop() { + let Ok(entries) = std::fs::read_dir(&dir) else { + continue; + }; + for entry in entries { + let path = entry.expect("a directory entry").path(); + if path.is_dir() { + stack.push(path); + } else if path.extension().is_some_and(|e| e == "rs") { + scanned += 1; + let text = std::fs::read_to_string(&path).expect("readable source"); + for (n, line) in text.lines().enumerate() { + if line.contains(needle) { + found.push(format!("{}:{}", path.display(), n + 1)); + } + } + } + } + } + assert!(scanned > 5, "only {scanned} source files were scanned"); + assert!( + found.is_empty(), + "unverified values remain:\n{}", + found.join("\n") + ); + } +} diff --git a/crates/teksilo-theme-kde/src/motion.rs b/crates/teksilo-theme-kde/src/motion.rs new file mode 100644 index 000000000..7711ae309 --- /dev/null +++ b/crates/teksilo-theme-kde/src/motion.rs @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Breeze motion tokens. +//! +//! Two upstream clocks drive a Plasma application's motion, and the tokens +//! take each step from the one that owns it: +//! +//! - **The Breeze widget style** animates hover, press, focus and toggle +//! transitions over its `AnimationsDuration`, 100 ms, linearly. Its +//! progress is quantised to ten steps, which at 60 Hz is about six frames +//! per transition, so the steps cannot be seen and the curve is +//! [`Easing::Linear`]. +//! - **Kirigami** (and the desktop QtQuick style built on it) derives its +//! units from one long duration, 200 ms: short is half of it, very long +//! twice it. Those give the normal, slow and collapse steps. +//! +//! | Token | ms | Source | +//! | --- | --- | --- | +//! | `duration_fast` | 100 | Breeze `AnimationsDuration`; Kirigami short duration | +//! | `duration_normal` | 200 | Kirigami long duration | +//! | `duration_slow` | 400 | Kirigami very long duration | +//! | `duration_collapse` | 200 | Kirigami long duration | +//! | `duration_indeterminate_sweep` | 800 | Breeze busy progress step | +//! | `tooltip_delay`, `tooltip_delay_heavy` | 700 | Kirigami tooltip delay | +//! +//! These are the values at the desktop's default animation speed. +//! +//! ## The desktop's animation speed +//! +//! Plasma's animation speed setting is `kdeglobals [KDE] +//! AnimationDurationFactor`, a real, 1 when absent, 2 for twice as slow. +//! [`desktop_motion`] applies it as Kirigami applies it to its units, +//! which drive the controls whose timing the preset reproduces (spec j.2, +//! `[derived]`): the long duration is `max(1, round(200 f))` ms with `f` +//! the factor (0 for a negative one), the short half of it rounded down and +//! the very long twice it. So `duration_normal` and `duration_collapse` are +//! that long duration, `duration_fast` its half and `duration_slow` its +//! double. The busy progress bar's period is `breezerc`'s +//! `ProgressBarBusyStepDuration` and the factor does not scale it, nor the +//! tooltip delays, which are dwell times rather than animations. +//! +//! A factor of 0 is the desktop asking for reduced motion: the long +//! duration is 1 ms and the short one 0, which the framework plays as no +//! animation, and the platform reports the preference from the same factor. +//! This is not [`MotionTokens::scaled`], which would scale every duration +//! by the factor itself: Kirigami derives the short duration from the long +//! one, and Breeze does not scale the busy bar. +//! +//! ## The switch curves +//! +//! The one Breeze control whose motion is not linear is the switch of the +//! desktop QtQuick style. Its handle moves on Qt's smoothed animation, which +//! Qt documents as a quadratic ease-in-out, and its colour changes on an +//! ease-in cubic. Both are given as cubic Bézier curves for the switch style +//! to use, [`BREEZE_SWITCH_HANDLE_EASING`] and [`BREEZE_SWITCH_COLOR_EASING`]. +//! The quadratic ease-in-out is two parabolas joined at the middle, which no +//! single cubic Bézier curve is, so the handle's curve is its usual +//! approximation; the ease-in cubic is itself a cubic Bézier curve, so the +//! colour's is exact. The tests bound how far each strays from the curve it +//! stands for. + +use std::time::Duration; + +use teksilo_tokens::{Easing, MotionTokens}; + +/// Hover, press and toggle transitions. `[S]` Breeze's `AnimationsDuration` +/// default, which is also `[K]` Kirigami's short duration. +pub const BREEZE_FAST_MS: u64 = 100; +/// `[K]` Kirigami's long duration. +pub const BREEZE_NORMAL_MS: u64 = 200; +/// `[K]` Kirigami's very long duration, twice the long one. +pub const BREEZE_SLOW_MS: u64 = 400; +/// `[K]` A disclosure opening or closing: Kirigami's long duration. +pub const BREEZE_COLLAPSE_MS: u64 = 200; +/// `[S]` One period of the busy progress bar's stripes. The desktop's +/// animation speed does not scale it. +pub const BREEZE_BUSY_STEP_MS: u64 = 800; +/// `[K]` Kirigami's tooltip delay. The desktop's animation speed does not +/// scale it. +pub const BREEZE_TOOLTIP_DELAY_MS: u64 = 700; + +/// `[S]` Breeze's widget-style transitions are linear. +pub const BREEZE_STANDARD_EASING: Easing = Easing::Linear; + +/// `[derived]` The switch handle's motion. Qt documents its smoothed +/// animation as a quadratic ease-in-out (`[qt]`); this cubic Bézier is that +/// curve's usual approximation, within 0.02 of it everywhere (see the +/// tests). +pub const BREEZE_SWITCH_HANDLE_EASING: Easing = Easing::CubicBezier { + x1: 0.455, + y1: 0.03, + x2: 0.515, + y2: 0.955, +}; + +/// `[derived]` The switch handle's colour change. The desktop QtQuick style +/// runs it on an ease-in cubic (`[Qd]`), `t` cubed. With its control points +/// at one and two thirds of the way along and none of the way up, a cubic +/// Bézier's horizontal coordinate is its parameter and its vertical one the +/// parameter cubed, so this curve is that ease-in exactly. (The common +/// approximation `(0.55, 0.055, 0.675, 0.19)` strays 0.03 from it.) +pub const BREEZE_SWITCH_COLOR_EASING: Easing = Easing::CubicBezier { + x1: 1.0 / 3.0, + y1: 0.0, + x2: 2.0 / 3.0, + y2: 0.0, +}; + +/// Breeze motion tokens at the desktop's default animation speed. +pub fn breeze_motion() -> MotionTokens { + MotionTokens { + duration_instant: Duration::ZERO, + duration_fast: Duration::from_millis(BREEZE_FAST_MS), + duration_normal: Duration::from_millis(BREEZE_NORMAL_MS), + duration_slow: Duration::from_millis(BREEZE_SLOW_MS), + duration_collapse: Duration::from_millis(BREEZE_COLLAPSE_MS), + duration_indeterminate_sweep: Duration::from_millis(BREEZE_BUSY_STEP_MS), + tooltip_delay: Duration::from_millis(BREEZE_TOOLTIP_DELAY_MS), + tooltip_delay_heavy: Duration::from_millis(BREEZE_TOOLTIP_DELAY_MS), + // `[unpublished]` KDE states no reshow delay: the framework's + // desktop baseline. + tooltip_reshow_delay: MotionTokens::default().tooltip_reshow_delay, + easing_standard: BREEZE_STANDARD_EASING, + } +} + +/// `[derived]` Breeze motion tokens at the desktop animation speed `factor` +/// (`kdeglobals [KDE] AnimationDurationFactor`, 1 when the desktop sets +/// none), with the busy progress bar's stripes moving one period every +/// `busy_step` (`breezerc` `ProgressBarBusyStepDuration`, 800 ms by +/// default). +/// +/// The long duration is `max(1, round(200 x max(0, factor)))` ms, rounded +/// half up: `duration_normal` and `duration_collapse` are it, +/// `duration_fast` half of it rounded down, `duration_slow` twice it. The +/// busy period and the tooltip delays are not scaled. A factor that is not +/// finite is read as 1. See the [module documentation](self). +pub fn desktop_motion(factor: f64, busy_step: Duration) -> MotionTokens { + let factor = if factor.is_finite() { + factor.max(0.0) + } else { + 1.0 + }; + // Capped so that twice the long duration still fits a `u64` of + // milliseconds. + let long = (BREEZE_NORMAL_MS as f64 * factor + 0.5) + .floor() + .clamp(1.0, f64::from(u32::MAX)) as u64; + MotionTokens { + duration_fast: Duration::from_millis(long / 2), + duration_normal: Duration::from_millis(long), + duration_slow: Duration::from_millis(2 * long), + duration_collapse: Duration::from_millis(long), + duration_indeterminate_sweep: busy_step, + ..breeze_motion() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn ms(d: Duration) -> u128 { + d.as_millis() + } + + #[test] + fn the_default_speed_is_the_default_motion() { + assert_eq!( + desktop_motion(1.0, Duration::from_millis(BREEZE_BUSY_STEP_MS)), + breeze_motion() + ); + } + + #[test] + fn the_factor_scales_kirigamis_long_duration() { + for (factor, fast, normal, slow) in [ + (2.0, 200, 400, 800), + (0.5, 50, 100, 200), + (0.25, 25, 50, 100), + // Half of the long duration is rounded down: 67 / 2. + (0.335, 33, 67, 134), + // Rounded half up: 200 x 0.0125 is 2.5. + (0.0125, 1, 3, 6), + ] { + let m = desktop_motion(factor, Duration::from_millis(800)); + assert_eq!(ms(m.duration_fast), fast, "{factor}"); + assert_eq!(ms(m.duration_normal), normal, "{factor}"); + assert_eq!(ms(m.duration_slow), slow, "{factor}"); + assert_eq!(m.duration_collapse, m.duration_normal, "{factor}"); + assert_eq!(m.duration_instant, Duration::ZERO); + assert_eq!(m.easing_standard, Easing::Linear); + } + } + + #[test] + fn the_busy_period_and_the_tooltip_delays_are_not_scaled() { + let m = desktop_motion(3.0, Duration::from_millis(500)); + assert_eq!(ms(m.duration_indeterminate_sweep), 500); + assert_eq!(ms(m.tooltip_delay), 700); + assert_eq!(ms(m.tooltip_delay_heavy), 700); + assert_eq!( + m.tooltip_reshow_delay, + MotionTokens::default().tooltip_reshow_delay + ); + } + + #[test] + fn a_factor_of_zero_leaves_a_one_millisecond_long_duration() { + for factor in [0.0, -1.0] { + let m = desktop_motion(factor, Duration::from_millis(800)); + assert_eq!(ms(m.duration_fast), 0, "{factor}"); + assert_eq!(ms(m.duration_normal), 1, "{factor}"); + assert_eq!(ms(m.duration_slow), 2, "{factor}"); + assert_eq!(ms(m.duration_collapse), 1, "{factor}"); + assert_eq!(ms(m.duration_indeterminate_sweep), 800, "{factor}"); + } + } + + #[test] + fn a_factor_that_is_not_finite_is_the_default_speed() { + for factor in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] { + assert_eq!( + desktop_motion(factor, Duration::from_millis(800)), + breeze_motion(), + "{factor}" + ); + } + // A huge finite factor is capped rather than overflowing. + let m = desktop_motion(1e300, Duration::from_millis(800)); + assert_eq!(ms(m.duration_normal), u128::from(u32::MAX)); + } + + #[test] + fn kirigamis_rule_is_not_the_frameworks_uniform_scale() { + // Pins why `desktop_motion` does not call `MotionTokens::scaled`: + // the uniform scale rounds the short duration on its own and + // scales the busy period. + let scaled = breeze_motion().scaled(0.335); + let kirigami = desktop_motion(0.335, Duration::from_millis(800)); + assert_eq!(ms(scaled.duration_fast), 34); + assert_eq!(ms(kirigami.duration_fast), 33); + assert_ne!( + scaled.duration_indeterminate_sweep, + kirigami.duration_indeterminate_sweep + ); + } + + /// The largest distance between two curves, sampled every 1/1000. + fn max_deviation(a: impl Fn(f32) -> f32, b: impl Fn(f32) -> f32) -> f32 { + (0..=1000) + .map(|i| { + let t = i as f32 / 1000.0; + (a(t) - b(t)).abs() + }) + .fold(0.0, f32::max) + } + + #[test] + fn durations_are_the_breeze_and_kirigami_values() { + let m = breeze_motion(); + assert_eq!(m.duration_instant, Duration::ZERO); + assert_eq!(m.duration_fast, Duration::from_millis(100)); + assert_eq!(m.duration_normal, Duration::from_millis(200)); + assert_eq!(m.duration_slow, Duration::from_millis(400)); + assert_eq!(m.duration_collapse, Duration::from_millis(200)); + assert_eq!(m.duration_indeterminate_sweep, Duration::from_millis(800)); + assert_eq!(m.tooltip_delay, Duration::from_millis(700)); + assert_eq!(m.tooltip_delay_heavy, Duration::from_millis(700)); + } + + #[test] + fn the_kirigami_steps_derive_from_the_long_duration() { + // Short is half the long duration and very long is twice it. + assert_eq!(BREEZE_FAST_MS * 2, BREEZE_NORMAL_MS); + assert_eq!(BREEZE_NORMAL_MS * 2, BREEZE_SLOW_MS); + assert_eq!(BREEZE_COLLAPSE_MS, BREEZE_NORMAL_MS); + } + + #[test] + fn the_standard_curve_is_linear() { + let e = breeze_motion().easing_standard; + assert_eq!(e, Easing::Linear); + for t in [0.0, 0.25, 0.5, 0.75, 1.0] { + assert!((e.apply(t) - t).abs() < 1e-6); + } + } + + #[test] + fn the_handle_curve_stays_within_two_hundredths_of_the_quadratic_ease_in_out() { + let deviation = max_deviation( + |t| BREEZE_SWITCH_HANDLE_EASING.apply(t), + |t| Easing::EaseInOut.apply(t), + ); + assert!( + deviation <= 0.02, + "the handle curve strays {deviation} from the quadratic ease-in-out" + ); + } + + #[test] + fn the_colour_curve_stays_within_two_hundredths_of_the_cubic_ease_in() { + let deviation = max_deviation(|t| BREEZE_SWITCH_COLOR_EASING.apply(t), |t| t * t * t); + assert!( + deviation <= 0.02, + "the colour curve strays {deviation} from the cubic ease-in" + ); + } + + #[test] + fn the_common_ease_in_cubic_approximation_would_stray_too_far() { + // Pins why the colour curve is the exact one. + let common = Easing::CubicBezier { + x1: 0.55, + y1: 0.055, + x2: 0.675, + y2: 0.19, + }; + assert!(max_deviation(|t| common.apply(t), |t| t * t * t) > 0.02); + } + + #[test] + fn the_reshow_delay_keeps_the_desktop_baseline() { + assert_eq!( + breeze_motion().tooltip_reshow_delay, + MotionTokens::default().tooltip_reshow_delay + ); + } +} diff --git a/crates/teksilo-theme-kde/src/palette.rs b/crates/teksilo-theme-kde/src/palette.rs new file mode 100644 index 000000000..8dcbdc55a --- /dev/null +++ b/crates/teksilo-theme-kde/src/palette.rs @@ -0,0 +1,571 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The KDE palette a Breeze theme is built from, attached to the theme as a +//! typed extension, and Plasma's ten accent swatches. +//! +//! [`ColorTokens`] carries one colour per Teksilo role. A KDE scheme carries +//! more: three colour states of seven colour sets, each with its own +//! foregrounds, backgrounds and decorations, and Breeze paints controls from +//! combinations of them (an outline is the window colour moved toward the +//! window text by the frame contrast). [`KdePalette`] holds the whole +//! resolved scheme, those combinations for each state ([`BreezeColors`]), +//! and the four sets of colour tokens a window paints with, so a Breeze +//! style or KDE-aware app code can read the exact colour it needs: +//! +//! ``` +//! use teksilo_theme_kde::{self as kde, KdePalette}; +//! use teksilo_theme_kde::scheme::ColorState; +//! +//! let theme = kde::light(); +//! let palette = theme.extension::().expect("a KDE theme carries its palette"); +//! let outline = palette.colors(ColorState::Active).frame_outline; +//! assert!(outline.a() > 0.0); +//! ``` +//! +//! The resolved scheme ([`KdePalette::resolved`], [`KdePalette::role`]) +//! keeps the scheme's own colours. Where the theme's tokens lift one to meet +//! WCAG (see the colour mapping's deviations in the crate documentation), +//! the token carries the lifted colour and the resolved scheme the vendor +//! one. Breeze's combinations are made from the surfaces the window paints: +//! where the mapping moves a background back from the middle of the scale +//! (D11), they are made from the moved one, so a style painting a button +//! face paints the surface the text was lifted on. + +use std::fmt; +use std::str::FromStr; +use std::sync::Arc; + +use teksilo_core::styles::PaintState; +use teksilo_kde_scheme::math::mix; +use teksilo_kde_scheme::{ + ACCENT_SWATCHES, ColorSet, ColorState, DecorationRole, KdeColor, KdeColorScheme, PaletteRole, + ResolvedPalette, +}; +use teksilo_tokens::{Color, ColorTokens}; + +use crate::color::{self, Mapping}; + +/// `[Hp]` How far a focus or hover outline is moved toward the window text. +const OUTLINE_TOWARD_TEXT: f64 = 0.15; + +/// `[Hp]` The alpha of a control's 1 px shadow. +const CONTROL_SHADOW_ALPHA: f32 = 0.125; + +/// The colours Breeze combines from a scheme's palette for one colour state. +/// +/// `fc` is the scheme's frame contrast. [`shadow`](Self::shadow) is +/// translucent; every other colour is as opaque as the scheme colours it is +/// made from, so all of stock Breeze's are opaque. A scheme may write a +/// colour with an alpha: D11 moves a translucent surface in luma and keeps +/// its alpha, so a translucent Button background gives a translucent +/// [`button`](Self::button), while a text lifted for contrast is made +/// opaque. A style painting a translucent colour composites it as it +/// documents (a check box makes its face opaque over the window, see +/// [`crate::styles::indicator`]). +/// +/// The combinations are made from the surfaces the window paints, which +/// are the scheme's own except where the colour mapping's D11 moves them +/// (an Active or Inactive state whose backgrounds lie in the middle of the +/// scale, under the scheme's inactive effects or the high-contrast floor): +/// the window, the view and the button colour are the colour tokens' +/// `surface_main`, `surface_content` and the button surface their text is +/// lifted on, the frame background is `surface_raised` and the frame +/// outline `border`. The button text is lifted the same way the window's +/// text is (D10). The Disabled state keeps the scheme's colours: its text +/// is exempt from WCAG's contrast floors. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeColors { + /// `[Hp]` The outline of a frame, field or menu: the window colour moved + /// toward the window text by `fc`. + pub frame_outline: Color, + /// `[Hp]` A separator line: the same mix as the frame outline. + pub separator: Color, + /// `[Hp]` The fill of a menu, popup or group box: the window colour 30 % + /// of the way to the view's. + pub frame_background: Color, + /// `[Hp]` The view set's focus decoration, the keyboard-focus colour. + pub focus: Color, + /// `[Hp]` The view set's hover decoration. + pub hover: Color, + /// `[Hp]` The focus colour moved 15 % toward the window text. + pub focus_outline: Color, + /// `[Hp]` The hover colour moved 15 % toward the window text. + pub hover_outline: Color, + /// `[qt]` The button colour, a push button's face. + pub button: Color, + /// `[qt]` The text of a button. + pub button_text: Color, + /// `[Hp]` A push button's outline: the button colour moved toward the + /// button text by `fc`. + pub button_outline: Color, + /// `[Hp]` A slider handle's outline: the same mix as the button outline. + pub slider_outline: Color, + /// `[Hp]` A control's shadow: black at 12.5 %. + pub shadow: Color, +} + +impl BreezeColors { + /// The combinations for `state` of `palette` at frame contrast `fc`, + /// their surfaces carried to the text floor of the high-contrast + /// projection (7:1) when `high_contrast` is set and to 4.5:1 otherwise. + pub fn compute( + palette: &ResolvedPalette, + state: ColorState, + fc: f64, + high_contrast: bool, + ) -> BreezeColors { + let role = |r: PaletteRole| palette.role(state, r); + let window_text = role(PaletteRole::WindowText); + let vendor_button_text = role(PaletteRole::ButtonText); + let (window, button, button_text, frame_background) = if state == ColorState::Disabled { + ( + role(PaletteRole::Window), + role(PaletteRole::Button), + vendor_button_text, + mix(role(PaletteRole::Window), role(PaletteRole::Base), 0.3), + ) + } else { + // `[wcag]` D11, the surfaces the colour tokens paint, and D10 for + // the button text on the button. + let floor = if high_contrast { + color::ENHANCED + } else { + color::AA + } + .text; + let s = color::Surfaces::of(palette, state, floor); + ( + s.window, + s.button, + color::lift_toward(vendor_button_text, &[s.button], floor, s.text_side()), + s.frame_background, + ) + }; + let view = palette.set(state, ColorSet::View); + let focus = view.decoration(DecorationRole::Focus); + let hover = view.decoration(DecorationRole::Hover); + let outline = mix(window, window_text, fc).to_color(); + let button_outline = mix(button, button_text, fc).to_color(); + BreezeColors { + frame_outline: outline, + separator: outline, + frame_background: frame_background.to_color(), + focus: focus.to_color(), + hover: hover.to_color(), + focus_outline: mix(focus, window_text, OUTLINE_TOWARD_TEXT).to_color(), + hover_outline: mix(hover, window_text, OUTLINE_TOWARD_TEXT).to_color(), + button: button.to_color(), + button_text: button_text.to_color(), + button_outline, + slider_outline: button_outline, + shadow: Color::BLACK.with_alpha(CONTROL_SHADOW_ALPHA), + } + } +} + +/// A resolved KDE scheme and everything a Breeze theme paints from it. +/// +/// Installed on a theme by [`crate::from_scheme`] and every constructor +/// built on it, and read back with `theme.extension::()`. Two +/// palettes are equal when their contents are. +/// +/// It also carries the four sets of colour tokens a window paints with: the +/// active and inactive ones, and each under the high-contrast preference. +/// The theme's paint projection swaps them in (see [`KdePalette::tokens`]). +#[derive(Debug, Clone, PartialEq)] +pub struct KdePalette { + resolved: Arc, + frame_contrast: f64, + dark: bool, + /// Indexed by [`ColorState::index`]. + colors: [BreezeColors; 3], + /// The same for the high-contrast scheme, indexed alike. + high_contrast_colors: [BreezeColors; 3], + active: ColorTokens, + inactive: ColorTokens, + high_contrast: ColorTokens, + inactive_high_contrast: ColorTokens, +} + +impl KdePalette { + /// The palette of `scheme`. + /// + /// The scheme's frame contrast is read clamped to `0..=1`, its default + /// 0.2 when it is not a number, as KDE reads the key. The high-contrast + /// tokens come from the same scheme with a frame contrast of 1 and a + /// contrast of 10. + pub fn from_scheme(scheme: &KdeColorScheme) -> KdePalette { + let frame_contrast = if scheme.frame_contrast.is_nan() { + teksilo_kde_scheme::scheme::DEFAULT_FRAME_CONTRAST + } else { + scheme.frame_contrast.clamp(0.0, 1.0) + }; + let dark = scheme.is_dark(); + let resolved = scheme.resolve(); + + let mut enhanced = scheme.clone(); + enhanced.frame_contrast = 1.0; + enhanced.contrast = 10; + let enhanced_resolved = enhanced.resolve(); + + let project = |palette: &ResolvedPalette, state, fc, high_contrast| { + color::tokens(&Mapping { + palette, + state, + frame_contrast: fc, + high_contrast, + dark, + }) + }; + let active = project(&resolved, ColorState::Active, frame_contrast, false); + let mut inactive = project(&resolved, ColorState::Inactive, frame_contrast, false); + let high_contrast = project(&enhanced_resolved, ColorState::Active, 1.0, true); + let mut inactive_high_contrast = + project(&enhanced_resolved, ColorState::Inactive, 1.0, true); + // The chart series keep the framework's inactive desaturation: KDE's + // inactive state has nothing to say about them. + inactive.chart_palette = active.for_inactive_window().chart_palette; + inactive_high_contrast.chart_palette = high_contrast.for_inactive_window().chart_palette; + + KdePalette { + colors: ColorState::ALL + .map(|state| BreezeColors::compute(&resolved, state, frame_contrast, false)), + high_contrast_colors: ColorState::ALL + .map(|state| BreezeColors::compute(&enhanced_resolved, state, 1.0, true)), + resolved: Arc::new(resolved), + frame_contrast, + dark, + active, + inactive, + high_contrast, + inactive_high_contrast, + } + } + + /// The resolved scheme: every colour of every set in every state. + pub fn resolved(&self) -> &ResolvedPalette { + &self.resolved + } + + /// The palette colour of `role` in `state`, as a Teksilo colour. + pub fn role(&self, state: ColorState, role: PaletteRole) -> Color { + self.resolved.role(state, role).to_color() + } + + /// The scheme's frame contrast, in `0..=1`. + pub fn frame_contrast(&self) -> f64 { + self.frame_contrast + } + + /// Whether the scheme is dark, as KDE reports it. + pub fn is_dark(&self) -> bool { + self.dark + } + + /// Breeze's colour combinations for `state`. + pub fn colors(&self, state: ColorState) -> &BreezeColors { + &self.colors[state.index()] + } + + /// Breeze's colour combinations for `state`, from the high-contrast + /// scheme when `high_contrast` is set: the scheme at a frame contrast of + /// 1 and a contrast of 10, the one the high-contrast tokens come from, + /// where an outline is the text colour itself. + pub fn breeze_colors(&self, state: ColorState, high_contrast: bool) -> &BreezeColors { + if high_contrast { + &self.high_contrast_colors[state.index()] + } else { + &self.colors[state.index()] + } + } + + /// The colour tokens a window paints with in `state`. + /// + /// An active window paints the active tokens. An inactive one paints the + /// tokens of the scheme's Inactive state: for Breeze, whose inactive + /// effects are off, they differ only in the selection and the accent, + /// which KDE mutes to the window colour tinted 40 % toward the selection + /// colour, while the focus ring keeps the focus decoration. A scheme + /// that turns its inactive effects on gets them everywhere. Under the + /// high-contrast preference the same choice is made between the + /// high-contrast tokens. + pub fn tokens(&self, state: PaintState) -> &ColorTokens { + match (state.window_active, state.high_contrast) { + (true, false) => &self.active, + (false, false) => &self.inactive, + (true, true) => &self.high_contrast, + (false, true) => &self.inactive_high_contrast, + } + } +} + +/// One of the ten accent swatches Plasma's colour settings page offers, in +/// the order it shows them. +/// +/// KDE gives the swatches no names; the identifiers here are Teksilo's, +/// chosen by hue (`[derived]`), and are stable, for persisting a choice +/// ([`as_str`](Self::as_str) and [`FromStr`]). +/// +/// ``` +/// use teksilo_theme_kde::{self as kde, PlasmaAccent}; +/// let theme = kde::dark_with_accent(PlasmaAccent::Purple.color()); +/// assert_eq!(theme.id.as_str(), "kde.dark"); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PlasmaAccent { + /// 233,58,154. + Pink, + /// 233,61,88. + Red, + /// 233,100,58. + Orange, + /// 232,203,45. + Yellow, + /// 61,212,37. + Green, + /// 0,211,184. + Teal, + /// 61,174,233, Breeze's own accent. + Blue, + /// 184,117,220. + Purple, + /// 146,110,228. + Violet, + /// 104,107,111. + Gray, +} + +impl PlasmaAccent { + /// Every swatch, in the order Plasma shows them. + pub const ALL: [PlasmaAccent; 10] = [ + PlasmaAccent::Pink, + PlasmaAccent::Red, + PlasmaAccent::Orange, + PlasmaAccent::Yellow, + PlasmaAccent::Green, + PlasmaAccent::Teal, + PlasmaAccent::Blue, + PlasmaAccent::Purple, + PlasmaAccent::Violet, + PlasmaAccent::Gray, + ]; + + /// The swatch's colour in KDE's 16-bit form. + pub fn kde_color(self) -> KdeColor { + ACCENT_SWATCHES[self as usize] + } + + /// The swatch's colour. + pub fn color(self) -> Color { + self.kde_color().to_color() + } + + /// The stable lowercase identifier of the swatch. + pub fn as_str(self) -> &'static str { + match self { + PlasmaAccent::Pink => "pink", + PlasmaAccent::Red => "red", + PlasmaAccent::Orange => "orange", + PlasmaAccent::Yellow => "yellow", + PlasmaAccent::Green => "green", + PlasmaAccent::Teal => "teal", + PlasmaAccent::Blue => "blue", + PlasmaAccent::Purple => "purple", + PlasmaAccent::Violet => "violet", + PlasmaAccent::Gray => "gray", + } + } +} + +impl fmt::Display for PlasmaAccent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// An identifier that names no [`PlasmaAccent`]. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +#[error("{0:?} is not a Plasma accent swatch")] +pub struct UnknownPlasmaAccent(pub String); + +impl FromStr for PlasmaAccent { + type Err = UnknownPlasmaAccent; + + /// The swatch whose [`as_str`](PlasmaAccent::as_str) is `s`, exactly. + fn from_str(s: &str) -> Result { + PlasmaAccent::ALL + .into_iter() + .find(|accent| accent.as_str() == s) + .ok_or_else(|| UnknownPlasmaAccent(s.to_owned())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeSet; + + #[test] + fn the_swatches_are_plasmas_in_order() { + let rgb: Vec<[u8; 3]> = PlasmaAccent::ALL + .iter() + .map(|a| { + let [r, g, b, _] = a.kde_color().to_rgba8(); + [r, g, b] + }) + .collect(); + assert_eq!( + rgb, + vec![ + [233, 58, 154], + [233, 61, 88], + [233, 100, 58], + [232, 203, 45], + [61, 212, 37], + [0, 211, 184], + [61, 174, 233], + [184, 117, 220], + [146, 110, 228], + [104, 107, 111], + ] + ); + } + + #[test] + fn identifiers_are_unique_and_read_back() { + let ids: BTreeSet<&str> = PlasmaAccent::ALL.iter().map(|a| a.as_str()).collect(); + assert_eq!(ids.len(), PlasmaAccent::ALL.len()); + for accent in PlasmaAccent::ALL { + assert_eq!(accent.as_str().parse::(), Ok(accent)); + assert_eq!(accent.to_string(), accent.as_str()); + } + assert_eq!( + "Blue".parse::(), + Err(UnknownPlasmaAccent("Blue".to_owned())) + ); + assert!("".parse::().is_err()); + } + + #[test] + fn breeze_blue_is_the_seventh_swatch() { + assert_eq!(PlasmaAccent::ALL[6], PlasmaAccent::Blue); + assert_eq!(PlasmaAccent::Blue.kde_color(), KdeColor::rgb8(61, 174, 233)); + } + + #[test] + fn the_combinations_follow_the_breeze_rules() { + let scheme = KdeColorScheme::breeze_light(); + let resolved = scheme.resolve(); + let c = BreezeColors::compute(&resolved, ColorState::Active, 0.2, false); + let r = |role| resolved.role(ColorState::Active, role); + assert_eq!( + c.frame_outline, + mix(r(PaletteRole::Window), r(PaletteRole::WindowText), 0.2).to_color() + ); + assert_eq!(c.separator, c.frame_outline); + assert_eq!( + c.frame_background, + mix(r(PaletteRole::Window), r(PaletteRole::Base), 0.3).to_color() + ); + assert_eq!(c.focus, KdeColor::rgb8(61, 174, 233).to_color()); + assert_eq!(c.hover, KdeColor::rgb8(61, 174, 233).to_color()); + assert_eq!( + c.button_outline, + mix(r(PaletteRole::Button), r(PaletteRole::ButtonText), 0.2).to_color() + ); + assert_eq!(c.slider_outline, c.button_outline); + assert_eq!(c.button, r(PaletteRole::Button).to_color()); + assert_eq!(c.button_text, r(PaletteRole::ButtonText).to_color()); + assert!((c.shadow.a() - 0.125).abs() < 1e-6); + } + + #[test] + fn the_high_contrast_combinations_outline_in_the_text_colour() { + let p = KdePalette::from_scheme(&KdeColorScheme::breeze_light()); + for state in ColorState::ALL { + assert_eq!(p.breeze_colors(state, false), p.colors(state)); + let hc = p.breeze_colors(state, true); + // At a frame contrast of 1 the outline is the text itself. + assert_eq!(hc.button_outline, hc.button_text); + assert_eq!(hc.frame_outline, p.role(state, PaletteRole::WindowText)); + } + assert_ne!( + p.breeze_colors(ColorState::Active, true).button_outline, + p.colors(ColorState::Active).button_outline + ); + } + + /// D11 reaches the combinations: in an inactive window whose scheme + /// lightens its backgrounds toward the middle of the scale, the frame + /// background and outline are the tokens' and the button colour carries + /// the primary text, which the scheme's own button colour does not. + #[test] + fn the_combinations_are_made_from_the_surfaces_the_window_paints() { + use teksilo_kde_scheme::{IntensityEffect, StateEffect}; + use teksilo_tokens::contrast::composite_contrast; + let mut scheme = KdeColorScheme::breeze_dark(); + scheme.inactive_effect = StateEffect { + enabled: true, + intensity_effect: IntensityEffect::Lighten, + intensity_amount: 0.4, + ..StateEffect::INACTIVE_DEFAULT + }; + let p = KdePalette::from_scheme(&scheme); + for (high_contrast, floor) in [(false, 4.5), (true, 7.0)] { + let t = p.tokens(PaintState { + window_active: false, + high_contrast, + }); + let b = p.breeze_colors(ColorState::Inactive, high_contrast); + assert_eq!(b.frame_background, t.surface_raised); + assert_eq!(b.frame_outline, t.border); + assert!(composite_contrast(t.text_primary, b.button) >= floor); + assert!(composite_contrast(b.button_text, b.button) >= floor); + let vendor = p.role(ColorState::Inactive, PaletteRole::Button); + assert!( + composite_contrast(t.text_primary, vendor) < floor, + "premise: the scheme's own button colour falls short" + ); + } + // The Disabled state keeps the scheme's colours. + assert_eq!( + p.colors(ColorState::Disabled).button, + p.role(ColorState::Disabled, PaletteRole::Button) + ); + } + + #[test] + fn palettes_compare_by_content() { + let a = KdePalette::from_scheme(&KdeColorScheme::breeze_light()); + let b = KdePalette::from_scheme(&KdeColorScheme::breeze_light()); + assert_eq!(a, b); + let c = KdePalette::from_scheme(&KdeColorScheme::breeze_dark()); + assert_ne!(a, c); + assert!(!a.is_dark() && c.is_dark()); + } + + #[test] + fn a_frame_contrast_out_of_range_is_clamped() { + let mut scheme = KdeColorScheme::breeze_light(); + scheme.frame_contrast = 3.0; + assert_eq!(KdePalette::from_scheme(&scheme).frame_contrast(), 1.0); + scheme.frame_contrast = f64::NAN; + assert_eq!(KdePalette::from_scheme(&scheme).frame_contrast(), 0.2); + } + + #[test] + fn the_four_token_sets_are_chosen_by_the_paint_state() { + let p = KdePalette::from_scheme(&KdeColorScheme::breeze_light()); + let state = |window_active, high_contrast| PaintState { + window_active, + high_contrast, + }; + assert_eq!(p.tokens(state(true, false)), &p.active); + assert_eq!(p.tokens(state(false, false)), &p.inactive); + assert_eq!(p.tokens(state(true, true)), &p.high_contrast); + assert_eq!(p.tokens(state(false, true)), &p.inactive_high_contrast); + assert_ne!(p.active, p.inactive); + assert_ne!(p.active, p.high_contrast); + } +} diff --git a/crates/teksilo-theme-kde/src/shape.rs b/crates/teksilo-theme-kde/src/shape.rs new file mode 100644 index 000000000..140a9056b --- /dev/null +++ b/crates/teksilo-theme-kde/src/shape.rs @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Breeze shape tokens: corner radii, stroke widths, the focus ring and the +//! shadows. +//! +//! ## Radii +//! +//! Breeze has one frame radius, 5 px, for controls and for floating +//! surfaces alike, which Kirigami repeats as its corner radius. A stroked +//! frame is drawn on a rectangle inset by half the 1 px pen, so the outline +//! a user sees has a radius of 4.5; the token carries the 5 and the styles +//! inset their own strokes. Smaller radii belong to individual parts and are +//! the constants below: the check box (4), the progress and slider grooves +//! (3) and the scroll-bar handle (4). +//! +//! ## The focus ring +//! +//! Breeze marks keyboard focus with a ring 2 px wide of the Highlight colour +//! at 30 % alpha, running from 1 px inside the control's edge to 1 px +//! outside it. That is a width of 2 and an offset of -1, which is what the +//! tokens say; the colour is the theme's `focus_ring`. +//! +//! ## Shadows +//! +//! A Breeze window shadow (menus, tooltips, combo-box popups) has two +//! layers, and its size is the `breezerc` `ShadowSize` setting, Large by +//! default. The tokens carry the Small, Medium and Large shadows, first +//! layer in `shadow_sm` / `shadow_md` / `shadow_lg` and second layer in the +//! matching `shadow_inner_*`; the styles paint both. `shadow_xs` is the push +//! button's own 1 px shadow, which has no second layer. Every offset in the +//! table is the size's offset plus the layer's, and the blur is Breeze's +//! radius, a CSS blur radius. The colour is black, at each layer's opacity. +//! Breeze's shadows do not change with the colour scheme, so light and dark +//! share one set of shape tokens. A desktop that changes the shadow's size, +//! colour or strength in `breezerc` gets its own window shadow through +//! [`breeze_shape_for`], which [`crate::from_desktop`] uses. + +use teksilo_kde_scheme::ShadowSize; +use teksilo_tokens::{Color, Shadow, ShapeTokens}; + +/// `[M]` The frame radius of a control: push button, field, combo box, menu +/// item, tab, row highlight. Also `[K]` Kirigami's corner radius. +pub const BREEZE_CONTROL_RADIUS: f32 = 5.0; +/// `[M]` The radius of a floating surface: menu, tooltip, popup, dialog +/// frame. Breeze uses the frame radius here too. +pub const BREEZE_POPUP_RADIUS: f32 = 5.0; +/// `[Hp]` The radius a 1 px stroked frame is drawn with: the frame radius +/// less half the pen, because the stroke lies on the rectangle inset by +/// half the pen. +pub const BREEZE_STROKED_FRAME_RADIUS: f32 = 4.5; +/// `[M]` The check box's radius: the frame radius less one. +pub const BREEZE_CHECKBOX_RADIUS: f32 = 4.0; +/// `[Hp]` The radius of the progress-bar and slider grooves. +pub const BREEZE_GROOVE_RADIUS: f32 = 3.0; +/// `[Hp]` The radius of the scroll-bar handle: half its 8 px width. +pub const BREEZE_SCROLL_HANDLE_RADIUS: f32 = 4.0; +/// `[M]` The pen every frame, separator and symbol is drawn with. Breeze +/// writes 1.001, an antialiasing workaround; Teksilo draws 1. +pub const BREEZE_PEN_WIDTH: f32 = 1.0; +/// `[Hp]` The focus ring's width. +pub const BREEZE_FOCUS_RING_WIDTH: f32 = 2.0; +/// `[Hp]` The focus ring's offset from the control edge: it starts 1 px +/// inside the edge. +pub const BREEZE_FOCUS_RING_OFFSET: f32 = -1.0; +/// `[Hp]` The alpha the focus ring's Highlight is painted at. +pub const BREEZE_FOCUS_RING_ALPHA: f32 = 0.3; +/// `[Hp]` The focus ring's outer radius: the inner edge has the frame +/// radius and the ring grows it by its width. +pub const BREEZE_FOCUS_RING_OUTER_RADIUS: f32 = 7.0; + +/// `[Hp]` The push button's shadow: a 1 px line of black at 12.5 % below the +/// button, which is an offset of (0, 1) with no blur. +pub const BREEZE_BUTTON_SHADOW_ALPHA: f32 = 0.125; + +/// `[Hp]` The overlap of a window and its shadow. +pub const BREEZE_SHADOW_OVERLAP: f32 = 2.0; + +/// One layer of a window shadow: vertical offset, blur and opacity, the +/// size's offset already added in. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ShadowLayer { + /// Downward offset in logical pixels. + pub offset_y: f32, + /// Blur radius in logical pixels. + pub blur: f32, + /// Opacity of the shadow colour, before `ShadowStrength`. + pub opacity: f32, +} + +/// `[Hp]` The two layers of a Breeze window shadow of `size`, `None` for +/// [`ShadowSize::None`]. +/// +/// | Size | Layer 1 (offset, blur, opacity) | Layer 2 | +/// | --- | --- | --- | +/// | Small | 3, 12, 0.26 | 1, 6, 0.16 | +/// | Medium | 4, 16, 0.24 | 2, 8, 0.14 | +/// | Large | 5, 20, 0.22 | 2, 10, 0.12 | +/// | Very large | 6, 24, 0.20 | 3, 12, 0.10 | +pub const fn window_shadow_layers(size: ShadowSize) -> Option<[ShadowLayer; 2]> { + const fn layer(offset_y: f32, blur: f32, opacity: f32) -> ShadowLayer { + ShadowLayer { + offset_y, + blur, + opacity, + } + } + match size { + ShadowSize::None => None, + ShadowSize::Small => Some([layer(3.0, 12.0, 0.26), layer(1.0, 6.0, 0.16)]), + ShadowSize::Medium => Some([layer(4.0, 16.0, 0.24), layer(2.0, 8.0, 0.14)]), + ShadowSize::Large => Some([layer(5.0, 20.0, 0.22), layer(2.0, 10.0, 0.12)]), + ShadowSize::VeryLarge => Some([layer(6.0, 24.0, 0.20), layer(3.0, 12.0, 0.10)]), + } +} + +/// A shadow token for `layer` in `color`, at the layer's opacity times +/// `strength` (`breezerc` `ShadowStrength / 255`, 1 by default). +pub fn shadow_token(layer: ShadowLayer, color: Color, strength: f32) -> Shadow { + Shadow { + offset_x: 0.0, + offset_y: layer.offset_y, + blur: layer.blur, + spread: 0.0, + color: color.with_alpha(layer.opacity * strength.clamp(0.0, 1.0)), + } +} + +/// Breeze shape tokens, for both colour schemes, at Breeze's default +/// window shadow: Large, black, at full strength. +pub fn breeze_shape() -> ShapeTokens { + breeze_shape_for(ShadowSize::Large, Color::BLACK, 1.0) +} + +/// Breeze shape tokens with the window shadow a desktop configures: +/// `breezerc`'s `ShadowSize` as `window`, `ShadowColor` as `color` and +/// `ShadowStrength / 255` as `strength`. +/// +/// `shadow_lg` and `shadow_inner_lg` are the window shadow the popups +/// paint, so they carry `window`'s two layers, and nothing at all for +/// [`ShadowSize::None`]; `shadow_sm` and `shadow_md` stay the Small and +/// Medium layers. All of them take `color` at each layer's opacity times +/// `strength`. The push button's own shadow, `shadow_xs`, is not a window +/// shadow and does not change. +pub fn breeze_shape_for(window: ShadowSize, color: Color, strength: f32) -> ShapeTokens { + let none = Shadow { + offset_x: 0.0, + offset_y: 0.0, + blur: 0.0, + spread: 0.0, + color: Color::TRANSPARENT, + }; + let pair = |size: ShadowSize| -> (Shadow, Shadow) { + match window_shadow_layers(size) { + Some([outer, inner]) => ( + shadow_token(outer, color, strength), + shadow_token(inner, color, strength), + ), + None => (none, none), + } + }; + let (sm, inner_sm) = pair(ShadowSize::Small); + let (md, inner_md) = pair(ShadowSize::Medium); + let (lg, inner_lg) = pair(window); + ShapeTokens { + radius_control: BREEZE_CONTROL_RADIUS, + radius_popup: BREEZE_POPUP_RADIUS, + // `[unpublished]` Breeze has no pill shape; the framework's fully + // rounded value, for the widgets that ask for one. + radius_pill: ShapeTokens::light_default().radius_pill, + border_width: BREEZE_PEN_WIDTH, + focus_ring_width: BREEZE_FOCUS_RING_WIDTH, + focus_ring_offset: BREEZE_FOCUS_RING_OFFSET, + shadow_xs: Shadow { + offset_x: 0.0, + offset_y: 1.0, + blur: 0.0, + spread: 0.0, + color: Color::BLACK.with_alpha(BREEZE_BUTTON_SHADOW_ALPHA), + }, + // `[Hp]` The button shadow has one layer: the second is empty. + shadow_inner_xs: Shadow { + offset_x: 0.0, + offset_y: 0.0, + blur: 0.0, + spread: 0.0, + color: Color::TRANSPARENT, + }, + shadow_sm: sm, + shadow_inner_sm: inner_sm, + shadow_md: md, + shadow_inner_md: inner_md, + shadow_lg: lg, + shadow_inner_lg: inner_lg, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn controls_and_popups_share_the_frame_radius() { + let s = breeze_shape(); + assert_eq!(s.radius_control, 5.0); + assert_eq!(s.radius_popup, 5.0); + assert_eq!(s.border_width, 1.0); + assert_eq!( + BREEZE_STROKED_FRAME_RADIUS, + BREEZE_CONTROL_RADIUS - BREEZE_PEN_WIDTH / 2.0 + ); + assert_eq!(BREEZE_CHECKBOX_RADIUS, BREEZE_CONTROL_RADIUS - 1.0); + } + + #[test] + fn the_focus_ring_runs_from_one_pixel_inside_to_one_pixel_outside() { + let s = breeze_shape(); + assert_eq!(s.focus_ring_width, 2.0); + assert_eq!(s.focus_ring_offset, -1.0); + // One pixel of the ring lies outside the control and is all it + // reserves; the stroke's centre line is on the edge itself. + assert_eq!(s.focus_ring_envelope(), 1.0); + assert_eq!(s.focus_ring_stroke_distance(), 0.0); + assert_eq!( + BREEZE_FOCUS_RING_OUTER_RADIUS, + BREEZE_CONTROL_RADIUS + BREEZE_FOCUS_RING_WIDTH + ); + } + + #[test] + fn the_window_shadows_are_the_breeze_table() { + let s = breeze_shape(); + let check = |shadow: Shadow, offset_y: f32, blur: f32, alpha: f32| { + assert_eq!(shadow.offset_x, 0.0); + assert_eq!(shadow.offset_y, offset_y); + assert_eq!(shadow.blur, blur); + assert_eq!(shadow.spread, 0.0); + assert!((shadow.color.a() - alpha).abs() < 1e-6); + assert_eq!(shadow.color.with_alpha(1.0), Color::BLACK); + }; + check(s.shadow_sm, 3.0, 12.0, 0.26); + check(s.shadow_inner_sm, 1.0, 6.0, 0.16); + check(s.shadow_md, 4.0, 16.0, 0.24); + check(s.shadow_inner_md, 2.0, 8.0, 0.14); + check(s.shadow_lg, 5.0, 20.0, 0.22); + check(s.shadow_inner_lg, 2.0, 10.0, 0.12); + check(s.shadow_xs, 1.0, 0.0, 0.125); + assert_eq!(s.shadow_inner_xs.color.a(), 0.0); + } + + #[test] + fn no_shadow_has_no_layers_and_strength_scales_the_opacity() { + assert_eq!(window_shadow_layers(ShadowSize::None), None); + let [outer, _] = window_shadow_layers(ShadowSize::VeryLarge).unwrap(); + assert_eq!((outer.offset_y, outer.blur), (6.0, 24.0)); + let half = shadow_token(outer, Color::BLACK, 0.5); + assert!((half.color.a() - 0.10).abs() < 1e-6); + // A strength outside 0..=1 is clamped rather than amplifying. + assert!((shadow_token(outer, Color::BLACK, 3.0).color.a() - 0.20).abs() < 1e-6); + } + + #[test] + fn the_desktops_window_shadow_is_the_popups() { + let red = Color::from_hex("#FF0000"); + let s = breeze_shape_for(ShadowSize::Small, red, 0.5); + // The window shadow is the Small pair, in the colour, at half the + // opacity. + assert_eq!((s.shadow_lg.offset_y, s.shadow_lg.blur), (3.0, 12.0)); + assert!((s.shadow_lg.color.a() - 0.13).abs() < 1e-6); + assert_eq!(s.shadow_lg.color.with_alpha(1.0), red); + assert_eq!( + (s.shadow_inner_lg.offset_y, s.shadow_inner_lg.blur), + (1.0, 6.0) + ); + assert!((s.shadow_inner_lg.color.a() - 0.08).abs() < 1e-6); + // The Small and Medium tokens keep their sizes in the same colour. + assert_eq!((s.shadow_md.offset_y, s.shadow_md.blur), (4.0, 16.0)); + assert_eq!(s.shadow_md.color.with_alpha(1.0), red); + // The button's shadow is not a window shadow. + assert_eq!(s.shadow_xs, breeze_shape().shadow_xs); + // No window shadow at all. + let bare = breeze_shape_for(ShadowSize::None, Color::BLACK, 1.0); + assert_eq!(bare.shadow_lg.color.a(), 0.0); + assert_eq!(bare.shadow_inner_lg.color.a(), 0.0); + assert_eq!(bare.shadow_sm, breeze_shape().shadow_sm); + // The defaults are Large, black, at full strength. + assert_eq!( + breeze_shape_for(ShadowSize::Large, Color::BLACK, 1.0), + breeze_shape() + ); + } + + #[test] + fn larger_shadows_reach_further_and_fade_more() { + let sizes = [ + ShadowSize::Small, + ShadowSize::Medium, + ShadowSize::Large, + ShadowSize::VeryLarge, + ]; + for pair in sizes.windows(2) { + let [a, _] = window_shadow_layers(pair[0]).unwrap(); + let [b, _] = window_shadow_layers(pair[1]).unwrap(); + assert!(b.blur > a.blur && b.offset_y > a.offset_y); + assert!(b.opacity < a.opacity); + } + } +} diff --git a/crates/teksilo-theme-kde/src/styles.rs b/crates/teksilo-theme-kde/src/styles.rs new file mode 100644 index 000000000..6d5fe9561 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Breeze Tier-3 widget styles, installed into the theme's `style_slots` by +//! [`crate::light`], [`crate::dark`] and every constructor built on +//! [`crate::from_scheme`]. +//! +//! Widgets whose Breeze counterpart differs in **structure** get a real +//! `impl FooStyle` here: +//! +//! - the push button, the default button and the flat tool button, with +//! their outline that turns to the highlight under the pointer and their +//! one-pixel shadow, sharing one painter, [`chrome::BreezeButtonChrome`]; +//! - the check box and the radio button, a 15 px box or disc in a 20 px +//! indicator marked in the view's text, and the desktop QtQuick style's +//! switch, a handle as tall as its track's indicator, which share their +//! colours through [`indicator`]; +//! - the line edit, a view-coloured frame whose outline turns to the focus +//! colour, in [`text_input`], which also frames an editable rich-text +//! editor, in [`rich_text_editor`]; +//! - the combo box, a push button with a chevron at its trailing edge, and +//! the spin box, a line edit with a column of two chevrons, which share +//! the chevron, [`arrow`]; +//! - the slider, a 6 px groove filled toward the highlight up to a 17 px +//! round handle, in [`slider`]; +//! - the menu item, washed with the focus colour and outlined while it is +//! highlighted, in [`menu_item`], and the frame of every popup, a menu's, +//! a popover's and a tooltip's, over Breeze's two-layer window shadow, +//! painted by [`popup`] for [`popover`] and [`tooltip`]; +//! - the list and tree row, a rounded selection inside the item margins +//! filled with the highlight, in [`standard_item`], and the table's header +//! sections and rows, in [`table`]; +//! - the tab, the window colour with a 3 px highlight bar when selected and +//! the window colour darkened when not, in [`tab`]; +//! - the scroll bar, a separator and an 8 px handle over no track, in +//! [`scroll_bar`], and the progress bar, a 6 px groove filled toward the +//! highlight or striped while busy, in [`progress_bar`]. +//! +//! The dimensions they are built with, all derived from [`crate::geometry`] +//! and the density ladder, are collected in [`metrics`]. +//! +//! Widgets whose shipped recipe style already paints Breeze's shape get that +//! style built with Breeze's numbers instead, in [`recipes`]: the card, the +//! panel, the dialog, the snackbar, the toast, the banner, the link, the +//! badge, the calendar, the search field, the avatar, the splitter, the list +//! container and the segmented control. + +pub mod arrow; +pub mod button; +pub mod checkbox; +pub mod chrome; +pub mod combo_box; +pub mod icon_button; +pub mod indicator; +pub mod menu_item; +pub mod metrics; +pub mod popover; +pub mod popup; +pub mod progress_bar; +pub mod radio; +pub mod recipes; +pub mod rich_text_editor; +pub mod scroll_bar; +pub mod slider; +pub mod spin_box; +pub mod split_button; +pub mod standard_item; +pub mod tab; +pub mod table; +pub mod text_input; +pub mod toggle; +pub mod tooltip; diff --git a/crates/teksilo-theme-kde/src/styles/arrow.rs b/crates/teksilo-theme-kde/src/styles/arrow.rs new file mode 100644 index 000000000..9704e97b1 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/arrow.rs @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze arrow: the chevron a combo box and a spin box draw. +//! +//! A polyline in a square of side `min(width, height, 10)` centred in the +//! box it is drawn in. Its two ends lie a quarter and three quarters of the +//! way across the square, on its centre line, and its apex on the square's +//! other centre line, a quarter of the side away from the ends, so the arms +//! meet at a right angle. It is drawn with a 1 px pen, square caps and a +//! mitred join, and moved along the way it points by -0.25, +0.5 or +//! +0.25 px when the side is 1, 2 or 3 more than a multiple of four, which +//! puts the apex on a pixel. +//! +//! An arrow is drawn in the arrow colour of a text role: that text moved +//! 15 % of the way toward its background. + +use teksilo_canvas::{Canvas, LineCap, LineJoin, Path, Point, Rect, StrokeStyle}; +use teksilo_tokens::Color; + +use crate::geometry::ARROW_SIZE; +use crate::styles::chrome::kde_mix; + +/// `[Hp]` How far an arrow's colour moves from its text toward the text's +/// background. +pub const ARROW_TOWARD_BACKGROUND: f64 = 0.15; + +/// `[Hp]` The pen an arrow is drawn with. +pub const ARROW_PEN: f32 = 1.0; + +/// Which way an arrow points. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ArrowDirection { + /// Up, the spin box's step up. + Up, + /// Down, the combo box's arrow and the spin box's step down. + Down, +} + +/// `[Hp]` The side of the square an arrow is drawn in inside `rect`. +pub fn arrow_side(rect: Rect) -> f32 { + rect.width.min(rect.height).clamp(0.0, ARROW_SIZE) +} + +/// `[Hp]` How far an arrow of side `side` is moved along the way it points, +/// so that its apex lands on a pixel. +pub fn arrow_nudge(side: f32) -> f32 { + match (side.round() as i64).rem_euclid(4) { + 1 => -0.25, + 2 => 0.5, + 3 => 0.25, + _ => 0.0, + } +} + +/// `[Hp]` The three points of an arrow pointing `direction` centred in +/// `rect`: the leading end, the apex and the trailing end. +/// +/// `[measured-qt]` The nudge moves an up arrow up and a down arrow down: +/// Plasma's spin box puts the ends of its up arrow half a pixel above the +/// centre of its half and those of its down arrow half a pixel below. +pub fn arrow_points(rect: Rect, direction: ArrowDirection) -> [Point; 3] { + let s = arrow_side(rect); + let cx = rect.x + rect.width * 0.5; + let cy = rect.y + rect.height * 0.5; + let quarter = s * 0.25; + let (ends, apex) = match direction { + ArrowDirection::Down => { + let ends = cy + arrow_nudge(s); + (ends, ends + quarter) + } + ArrowDirection::Up => { + let ends = cy - arrow_nudge(s); + (ends, ends - quarter) + } + }; + [ + Point::new(cx - quarter, ends), + Point::new(cx, apex), + Point::new(cx + quarter, ends), + ] +} + +/// The arrow colour of the text `text` on `background`. +pub fn arrow_color(text: Color, background: Color) -> Color { + kde_mix(text, background, ARROW_TOWARD_BACKGROUND) +} + +/// Draw an arrow pointing `direction` centred in `rect` in `color`. +pub fn paint_arrow(canvas: &mut Canvas, rect: Rect, direction: ArrowDirection, color: Color) { + if color.a() <= 0.0 || arrow_side(rect) <= 0.0 { + return; + } + let [a, b, c] = arrow_points(rect, direction); + let mut path = Path::new(); + path.move_to(a); + path.line_to(b); + path.line_to(c); + let mut style = StrokeStyle::solid(ARROW_PEN); + style.line_cap = LineCap::Square; + style.line_join = LineJoin::Miter; + canvas.stroke_path(&path, color, style); +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColor; + + fn xy(p: Point) -> [f32; 2] { + [p.x, p.y] + } + + /// The combo box's arrow, in its 20 px box at (128, 6) of a 150 x 32 + /// combo box: its ends on pixel row 16, five pixels apart, as Plasma + /// paints them, and its apex 2.5 px below. + #[test] + fn the_combo_arrow_lies_where_plasma_paints_it() { + let p = arrow_points(Rect::new(128.0, 6.0, 20.0, 20.0), ArrowDirection::Down); + assert_eq!(p.map(xy), [[135.5, 16.5], [138.0, 19.0], [140.5, 16.5]]); + } + + /// The spin box's two arrows, in the 20 x 10 halves at (78, 6) and + /// (78, 16) of a 100 x 32 spin box: the ends on pixel rows 10 and 21. + #[test] + fn the_spin_arrows_lie_where_plasma_paints_them() { + let up = arrow_points(Rect::new(78.0, 6.0, 20.0, 10.0), ArrowDirection::Up); + assert_eq!(up.map(xy), [[85.5, 10.5], [88.0, 8.0], [90.5, 10.5]]); + let down = arrow_points(Rect::new(78.0, 16.0, 20.0, 10.0), ArrowDirection::Down); + assert_eq!(down.map(xy), [[85.5, 21.5], [88.0, 24.0], [90.5, 21.5]]); + } + + #[test] + fn the_side_is_at_most_ten_and_the_nudge_follows_it() { + assert_eq!(arrow_side(Rect::new(0.0, 0.0, 20.0, 20.0)), 10.0); + assert_eq!(arrow_side(Rect::new(0.0, 0.0, 20.0, 7.0)), 7.0); + assert_eq!(arrow_nudge(10.0), 0.5); + assert_eq!(arrow_nudge(9.0), -0.25); + assert_eq!(arrow_nudge(7.0), 0.25); + assert_eq!(arrow_nudge(8.0), 0.0); + } + + #[test] + fn the_arms_meet_at_a_right_angle() { + let [a, b, c] = arrow_points(Rect::new(0.0, 0.0, 20.0, 20.0), ArrowDirection::Down); + let (u, v) = ((a.x - b.x, a.y - b.y), (c.x - b.x, c.y - b.y)); + assert!((u.0 * v.0 + u.1 * v.1).abs() < 1e-6); + } + + /// Breeze Light's combo arrow: the button text moved 15 % toward the + /// button colour. + #[test] + fn the_arrow_colour_moves_the_text_toward_its_background() { + let text = Color::from_hex("#232629"); + let button = Color::from_hex("#FCFCFC"); + let c = KdeColor::from_color(arrow_color(text, button)).to_rgba8(); + assert_eq!(c, [68, 70, 73, 255]); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/button.rs b/crates/teksilo-theme-kde/src/styles/button.rs new file mode 100644 index 000000000..e7a4d9286 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/button.rs @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze push button. +//! +//! Breeze has three buttons, and the seven `ButtonVariant`s map onto them: +//! +//! | `ButtonVariant` | Breeze button | +//! | --- | --- | +//! | `Filled` | the default button (the dialog's default action) | +//! | `Plain` (default) / `Tinted` / `Outlined` | the push button | +//! | `Destructive` | the push button | +//! | `Ghost` / `Link` | the flat button | +//! +//! Breeze has no tonal or outlined emphasis tier: its push button is +//! already outlined, so `Tinted` and `Outlined` are the push button rather +//! than an invented fourth look. It has no destructive button either: a KDE +//! application says an action is destructive in its text and icon, so +//! `Destructive` is the push button too. The default button is Breeze's +//! one emphasised button, a fill moved a fifth of the way to the highlight, +//! and only in an active window; its label stays the button text. +//! [`crate::styles::chrome`] paints all three. +//! +//! ## Labels +//! +//! Every Breeze button labels in the button text, a flat one in the window +//! text: both are the primary text role. The Button gives its `Filled` and +//! `Destructive` labels the on-accent role by default, for an accent fill +//! Breeze does not paint, so this style asks for the primary role there. +//! `Link` keeps the link colour. +//! +//! ## Metrics +//! +//! A push button is the label's line box plus 8 px above and below (the 6 px +//! button margin and the 2 px frame margin), 34 px at Plasma's default +//! font, and at least 84 px wide: Breeze's `max(text + 12, 80) + 4`. A flat +//! push button keeps those dimensions. See [`crate::styles::metrics`]. + +use teksilo_core::build_context::BuildContext; +use teksilo_core::styles::{ButtonStyle, ButtonStyleConfig, ButtonVariant}; +use teksilo_core::widget_id::WidgetId; +use teksilo_tokens::TextRole; +use teksilo_widgets::primitives::{MinSize, Padding, ZStack}; + +use crate::styles::chrome::{BreezeButtonChrome, BreezeButtonKind, BreezeButtonState}; +use crate::styles::metrics::breeze_button_metrics_for; + +/// Breeze `ButtonStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeButtonStyle; + +impl BreezeButtonStyle { + /// The Breeze button `variant` is painted as. + pub fn kind(variant: ButtonVariant) -> BreezeButtonKind { + match variant { + ButtonVariant::Filled => BreezeButtonKind::Default, + ButtonVariant::Plain + | ButtonVariant::Tinted + | ButtonVariant::Outlined + | ButtonVariant::Destructive => BreezeButtonKind::Push, + ButtonVariant::Ghost | ButtonVariant::Link => BreezeButtonKind::Flat, + } + } +} + +impl ButtonStyle for BreezeButtonStyle { + fn make_body(&self, cfg: &ButtonStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let theme = ctx.theme(); + let m = breeze_button_metrics_for(&theme.input, &theme.typography); + let state = BreezeButtonState::derive( + &cfg.is_disabled, + &cfg.is_pressed, + &cfg.is_hovered, + &cfg.is_focused, + ); + // The Button's focus signal is already its `:focus-visible` one. + let show_focus_frame = cfg + .is_focused + .zip(&cfg.is_disabled) + .map(|(focused, disabled)| *focused && !*disabled); + let chrome = ctx.add(BreezeButtonChrome::new( + Self::kind(cfg.variant), + state, + show_focus_frame, + )); + let padded = ctx.add( + Padding::new( + m.padding_vertical, + m.padding_horizontal, + m.padding_vertical, + m.padding_horizontal, + ) + .child(cfg.label), + ); + let stack = ctx.add(ZStack::new().child(chrome).child(padded)); + ctx.add(MinSize::new(m.min_width, m.height).child(stack)) + } + + fn label_text_role(&self, variant: ButtonVariant) -> Option { + match variant { + // The default button's fill is the button colour moved toward + // the highlight, not the accent: its label is the button text. + ButtonVariant::Filled | ButtonVariant::Destructive => Some(TextRole::Primary), + // The push and flat buttons already label in the primary text, + // and `Link` in the link colour: the Button's own mapping. + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_variants_map_onto_breezes_three_buttons() { + assert_eq!( + BreezeButtonStyle::kind(ButtonVariant::Filled), + BreezeButtonKind::Default + ); + for v in [ + ButtonVariant::Plain, + ButtonVariant::Tinted, + ButtonVariant::Outlined, + ButtonVariant::Destructive, + ] { + assert_eq!(BreezeButtonStyle::kind(v), BreezeButtonKind::Push, "{v:?}"); + } + for v in [ButtonVariant::Ghost, ButtonVariant::Link] { + assert_eq!(BreezeButtonStyle::kind(v), BreezeButtonKind::Flat, "{v:?}"); + } + } + + #[test] + fn no_variant_labels_in_the_on_accent_colour() { + let s = BreezeButtonStyle; + assert_eq!( + s.label_text_role(ButtonVariant::Filled), + Some(TextRole::Primary) + ); + assert_eq!( + s.label_text_role(ButtonVariant::Destructive), + Some(TextRole::Primary) + ); + for v in [ + ButtonVariant::Plain, + ButtonVariant::Tinted, + ButtonVariant::Outlined, + ButtonVariant::Ghost, + ButtonVariant::Link, + ] { + assert_eq!(s.label_text_role(v), None, "{v:?}"); + } + } +} diff --git a/crates/teksilo-theme-kde/src/styles/checkbox.rs b/crates/teksilo-theme-kde/src/styles/checkbox.rs new file mode 100644 index 000000000..c10e7cb61 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/checkbox.rs @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze check box. +//! +//! A 20 px indicator holding a 16 px box, stroked as 15 px at 2.5 with a +//! 4 px radius and filled with the button colour. Checked or partial, the +//! box is outlined in the highlight and filled again with the highlight at +//! 30 % (painted as one fill, the highlight composited over the face as Qt +//! composites it, [`BoxPaint::composited_fill`]), then marked in the view's +//! text: a check drawn with a 2 px pen, square caps and mitred joins +//! through (6, 10), (9, 13) and (14.5, 7.5) of the indicator, or three 2 px +//! squares 4 px apart for the partial state. Pressed, the +//! button colour darkens 110 %; under the pointer a 1 px ring of the focus +//! colour lies on the outline. The mark draws itself along its length over +//! the style's 100 ms, linearly, and the partial squares appear one at each +//! third. The colours, and where they depart from Breeze, are in +//! [`crate::styles::indicator`]. +//! +//! ## D8, the check box row +//! +//! Breeze lays a labelled check box out in a row as tall as the label's +//! line box plus 2 px above and below: 22 px at Plasma's default font, the +//! indicator 1 px from its top. That is under WCAG 2.2 SC 2.5.8's 24 px +//! target. The `Checkbox` widget already wraps its row in a minimum size of +//! the density's target, 24 px at Compact, so the node is 24 px tall and +//! the indicator, still painted at 20, sits 2 px from its top. Nothing is +//! scaled: only the row grows. +//! +//! ## The label +//! +//! Breeze puts the label 24 px from the indicator's leading edge, the +//! indicator and a 4 px spacing. The widget puts its own 6 px between the +//! style's body and the label, so the body is laid out 18 px wide, the +//! indicator less the trailing 2 px of its focus margin, and the label lands +//! where Breeze puts it. The box ends at 18, so it lies in the body; only +//! the keyboard focus frame reaches past it. +//! +//! ## Variants +//! +//! Breeze has one check box. `Square` is it; `Rounded` rounds the box to +//! 6 px and `Circle` makes it a disc (`[unpublished]`), with Breeze's fills, +//! outline and mark. + +use teksilo_canvas::{ + Canvas, LineCap, LineJoin, Path, Point, Rect, Size, SizeProposal, StrokeStyle, +}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{ + CheckboxState, CheckboxStyle, CheckboxStyleConfig, CheckboxVariant, FocusRingStroke, +}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::KdeColor; +use teksilo_tokens::{Color, CornerRadius}; +use teksilo_widgets::styles::recipe_checkbox_style::CHECKBOX_LABEL_GAP; + +use crate::shape::BREEZE_CHECKBOX_RADIUS; +use crate::styles::indicator::{BreezeIndicatorColors, box_rect, indicator_rect, stroked_box_rect}; +use crate::styles::metrics::{ + CHECK_MARK_PEN, INDICATOR_BOX_STROKED, INDICATOR_CHECKED_WASH, INDICATOR_SIZE, + PARTIAL_DOT_SIZE, PARTIAL_DOT_SPACING, indicator_body_width, +}; + +/// `[Hp]` The check mark's three points, in the indicator's coordinates: +/// `(left + 4, centre y)`, `(centre x - 1, bottom - 5)`, `(centre x + 4.5, +/// top + 5.5)` of the 16 px box at (2, 2), whose centre is (10, 10). +pub const CHECK_MARK_POINTS: [(f32, f32); 3] = [(6.0, 10.0), (9.0, 13.0), (14.5, 7.5)]; + +/// `[unpublished]` The radius of the `Rounded` variant's box. +const ROUNDED_RADIUS: f32 = 6.0; + +/// Breeze `CheckboxStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeCheckboxStyle; + +impl CheckboxStyle for BreezeCheckboxStyle { + fn make_body(&self, cfg: &CheckboxStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let marked = |s: &CheckboxState| !matches!(s, CheckboxState::Unchecked); + let progress = ctx.animated_signal(if marked(&cfg.state.get()) { 1.0 } else { 0.0 }); + // `[S]` Breeze's style transitions: 100 ms, linear. + let draw = ctx.animate().fast().standard(); + { + let progress = progress.clone(); + ctx.effect(&cfg.state, move |s| { + draw.to_or_snap(&progress, if marked(s) { 1.0 } else { 0.0 }); + }); + } + let show_focus_frame = cfg + .is_focused + .zip(&cfg.is_disabled) + .map(|(focused, disabled)| *focused && !*disabled); + ctx.add(BreezeCheckboxBody { + check: cfg.state.clone(), + progress, + is_hovered: cfg.is_hovered.clone(), + is_pressed: cfg.is_pressed.clone(), + is_disabled: cfg.is_disabled.clone(), + show_focus_frame, + variant: cfg.variant, + }) + } +} + +struct BreezeCheckboxBody { + check: Signal, + /// How far the mark is drawn, 0 to 1. + progress: Signal, + is_hovered: Signal, + is_pressed: Signal, + is_disabled: Signal, + show_focus_frame: Signal, + variant: CheckboxVariant, +} + +impl std::fmt::Debug for BreezeCheckboxBody { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeCheckboxBody") + .field("variant", &self.variant) + .finish() + } +} + +/// The radius the box of `variant` is stroked with. +pub fn variant_radius(variant: CheckboxVariant) -> f32 { + match variant { + CheckboxVariant::Square => BREEZE_CHECKBOX_RADIUS, + CheckboxVariant::Rounded => ROUNDED_RADIUS, + CheckboxVariant::Circle => INDICATOR_BOX_STROKED * 0.5, + } +} + +/// What a box paints: a face, an optional highlight wash over it, an +/// outline (laid twice when there is a wash, see [`paint_box`]), and +/// whether the hover ring lies on the outline. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BoxPaint { + /// The button colour, darkened while pressed. + pub face: Color, + /// The highlight at 30 % over the face, when checked or partial. + pub wash: Option, + /// The 1 px outline. + pub outline: Color, + /// The 1 px ring of the focus colour on the outline, under the pointer. + pub hover_ring: Option, + /// The mark's colour (the check mark, the partial squares, the radio + /// dot): the one lifted on the pressed face while pressed. + pub mark: Color, +} + +/// `[Hp]` What a box (or a radio disc) paints, marked or not, hovered or +/// pressed, enabled or not, from `c`. +pub fn box_paint( + marked: bool, + hovered: bool, + pressed: bool, + disabled: bool, + c: &BreezeIndicatorColors, +) -> BoxPaint { + let active = !disabled; + BoxPaint { + face: if pressed && active { + c.button_pressed + } else { + c.button + }, + wash: marked.then(|| c.highlight.with_alpha(INDICATOR_CHECKED_WASH)), + outline: if marked { c.highlight } else { c.outline }, + hover_ring: (hovered && active).then_some(c.focus), + mark: if pressed && active { + c.text_pressed + } else { + c.text + }, + } +} + +impl BoxPaint { + /// `[measured-qt]` The box's fill, the wash composited over an opaque + /// face as Qt's raster painter composites it, or `None` when the face is + /// translucent and the two are painted as two layers. + /// + /// The renderer composites in linear light, Qt's painter in sRGB: left + /// to the renderer, the highlight at 30 % over Breeze Light's face is + /// #d9e8f7 where Plasma paints #c2e4f6 (`check_box.on` in the golden + /// pixel samples, `tests/breeze_reference.rs`). The face is the style's + /// own, so the composite is known and is painted instead. + pub fn composited_fill(&self) -> Option { + if self.face.a() < 1.0 { + return None; + } + Some(match self.wash { + Some(wash) if wash.a() > 0.0 => painter_over(wash, self.face), + _ => self.face, + }) + } +} + +/// `[measured-qt]` `top` composited over the opaque `bottom` in 8 bits, the +/// alpha read as a byte and each channel rounded: Plasma's #c2e4f6 and +/// #2f5368 for the highlight at 30 % over the Breeze Light and Breeze Dark +/// faces. (KDE's colour arithmetic, which truncates, gives #2f5367 for the +/// second.) +fn painter_over(top: Color, bottom: Color) -> Color { + let [tr, tg, tb, ta] = KdeColor::from_color(top).to_rgba8(); + let [br, bg, bb, _] = KdeColor::from_color(bottom).to_rgba8(); + let a = u32::from(ta); + let ch = |t: u8, b: u8| ((u32::from(t) * a + u32::from(b) * (255 - a) + 127) / 255) as u8; + KdeColor::rgba8(ch(tr, br), ch(tg, bg), ch(tb, bb), 255).to_color() +} + +/// Paint `paint` on the stroked box `rect` at `radius`. +pub fn paint_box(canvas: &mut Canvas, rect: Rect, radius: f32, pen: f32, paint: &BoxPaint) { + let corner = CornerRadius::uniform(radius); + if let Some(fill) = paint.composited_fill() { + canvas.fill_rounded_rect(rect, corner, fill); + } else { + if paint.face.a() > 0.0 { + canvas.fill_rounded_rect(rect, corner, paint.face); + } + if let Some(wash) = paint.wash + && wash.a() > 0.0 + { + canvas.fill_rounded_rect(rect, corner, wash); + } + } + if pen <= 0.0 { + return; + } + if paint.outline.a() > 0.0 { + canvas.stroke_rounded_rect(rect, corner, paint.outline, pen); + // `[measured-qt]` Breeze fills a marked box again with the outline + // pen still set, so the outline is laid twice and its antialiased + // edge is that much denser: the corner pixels of Plasma's checked + // box and selected disc fit two strokes to within 15 levels and one + // stroke no closer than 33 (`check_box.on`, `radio_button.on`). + if paint.wash.is_some_and(|w| w.a() > 0.0) { + canvas.stroke_rounded_rect(rect, corner, paint.outline, pen); + } + } + if let Some(ring) = paint.hover_ring + && ring.a() > 0.0 + { + canvas.stroke_rounded_rect(rect, corner, ring, pen); + } +} + +/// `[Hp]` The keyboard focus frame around an indicator: a band as wide as +/// the focus ring whose inner edge is the 16 px box, with the box's corner +/// radius `radius`. +pub fn indicator_focus_frame(indicator: Rect, radius: f32, width: f32) -> FocusRingStroke { + FocusRingStroke::at_distance(box_rect(indicator), radius, width * 0.5) +} + +/// The points of the check mark drawn `progress` of the way along its +/// length, in canvas coordinates: none at 0, the whole polyline at 1. +pub fn check_mark(indicator: Rect, progress: f32) -> Vec { + let at = |(x, y): (f32, f32)| Point::new(indicator.x + x, indicator.y + y); + let points = CHECK_MARK_POINTS.map(at); + let progress = progress.clamp(0.0, 1.0); + if progress <= 0.0 { + return Vec::new(); + } + let lengths: Vec = points + .windows(2) + .map(|w| ((w[1].x - w[0].x).powi(2) + (w[1].y - w[0].y).powi(2)).sqrt()) + .collect(); + let mut left = lengths.iter().sum::() * progress; + let mut out = vec![points[0]]; + for (w, len) in points.windows(2).zip(lengths) { + if left >= len { + out.push(w[1]); + left -= len; + } else { + let t = left / len; + out.push(Point::new( + w[0].x + (w[1].x - w[0].x) * t, + w[0].y + (w[1].y - w[0].y) * t, + )); + break; + } + } + out +} + +/// The partial squares shown at `progress`: one at each third, from the +/// leading side. +pub fn partial_dots(indicator: Rect, progress: f32, direction_rtl: bool) -> Vec { + let shown = (progress.clamp(0.0, 1.0) * 3.0 + 1e-4).floor() as usize; + let cx = indicator.x + INDICATOR_SIZE * 0.5; + let cy = indicator.y + INDICATOR_SIZE * 0.5; + let half = PARTIAL_DOT_SIZE * 0.5; + let order: [f32; 3] = if direction_rtl { + [1.0, 0.0, -1.0] + } else { + [-1.0, 0.0, 1.0] + }; + order + .iter() + .take(shown) + .map(|side| { + Rect::new( + cx + side * PARTIAL_DOT_SPACING - half, + cy - half, + PARTIAL_DOT_SIZE, + PARTIAL_DOT_SIZE, + ) + }) + .collect() +} + +impl Widget for BreezeCheckboxBody { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + self.check.bind_to(id, registry, BindingLevel::RepaintOnly); + self.progress + .bind_to(id, registry, BindingLevel::RepaintOnly); + for s in [ + &self.is_hovered, + &self.is_pressed, + &self.is_disabled, + &self.show_focus_frame, + ] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + /// The 18 px body of the module documentation, as tall as the indicator. + /// The widget's own minimum size raises the row to the density's target + /// (D8). + fn layout_response(&self, _proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + Size::new(indicator_body_width(CHECKBOX_LABEL_GAP), INDICATOR_SIZE).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let check = self.check.get(); + let disabled = self.is_disabled.get(); + let colors = BreezeIndicatorColors::resolve( + ctx.theme, + ctx.window_active, + ctx.prefers_high_contrast, + disabled, + ); + let indicator = indicator_rect(bounds, ctx.layout_direction); + let pen = ctx.theme.shape.border_width; + let radius = variant_radius(self.variant); + let paint = box_paint( + !matches!(check, CheckboxState::Unchecked), + self.is_hovered.get(), + self.is_pressed.get(), + disabled, + &colors, + ); + paint_box( + canvas, + stroked_box_rect(indicator, pen), + radius, + pen, + &paint, + ); + + let progress = self.progress.get(); + let rtl = ctx.layout_direction == teksilo_core::environment::LayoutDirection::RightToLeft; + match check { + CheckboxState::Checked => { + let points = check_mark(indicator, progress); + if points.len() >= 2 && paint.mark.a() > 0.0 { + let mut path = Path::new(); + path.move_to(points[0]); + for p in &points[1..] { + path.line_to(*p); + } + let mut style = StrokeStyle::solid(CHECK_MARK_PEN); + style.line_join = LineJoin::Miter; + // `[measured-qt]` Square caps, Qt's default pen cap: the + // mark runs half its width past both end points + // (`check_box.on` covers (5, 10) of the indicator, 1 px + // before the first point, at 0.83). + style.line_cap = LineCap::Square; + canvas.stroke_path(&path, paint.mark, style); + } + } + CheckboxState::Indeterminate => { + for dot in partial_dots(indicator, progress, rtl) { + canvas.fill_rect(dot, paint.mark); + } + } + CheckboxState::Unchecked => {} + } + + if self.show_focus_frame.get() { + let shape = &ctx.theme.shape; + if colors.focus_ring.a() > 0.0 && shape.focus_ring_width > 0.0 { + let ring = indicator_focus_frame(indicator, radius, shape.focus_ring_width); + canvas.stroke_rounded_rect( + ring.rect, + CornerRadius::uniform(ring.corner_radius), + colors.focus_ring, + shape.focus_ring_width, + ); + } + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the `Checkbox` emits the check box node. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn c() -> BreezeIndicatorColors { + BreezeIndicatorColors::resolve(&crate::light(), true, false, false) + } + + #[test] + fn the_box_follows_breezes_rules() { + let c = c(); + let off = box_paint(false, false, false, false, &c); + assert_eq!(off.face, c.button); + assert_eq!(off.wash, None); + assert_eq!(off.outline, c.outline); + assert_eq!(off.hover_ring, None); + + let on = box_paint(true, false, false, false, &c); + assert_eq!(on.outline, c.highlight); + assert_eq!(on.wash, Some(c.highlight.with_alpha(0.3))); + + let pressed = box_paint(false, false, true, false, &c); + assert_eq!(pressed.face, c.button_pressed); + + let hovered = box_paint(false, true, false, false, &c); + assert_eq!(hovered.hover_ring, Some(c.focus)); + // A disabled box neither presses nor hovers. + let d = box_paint(false, true, true, true, &c); + assert_eq!((d.face, d.hover_ring), (c.button, None)); + } + + /// The wash is composited over the face in KDE's 8-bit blend, which is + /// what Plasma paints: #c2e4f6 for Breeze Light, #2f5368 for Breeze + /// Dark (`check_box.on` at (8, 5) in the golden pixel samples). + #[test] + fn the_wash_is_composited_over_the_face_as_plasma_paints_it() { + let rgba8 = |c: Color| KdeColor::from_color(c).to_rgba8(); + for (theme, want) in [ + (crate::light(), [194, 228, 246, 255]), + (crate::dark(), [47, 83, 104, 255]), + ] { + let c = BreezeIndicatorColors::resolve(&theme, true, false, false); + let on = box_paint(true, false, false, false, &c); + assert_eq!(on.composited_fill().map(rgba8), Some(want)); + // Unmarked, the fill is the face. + let off = box_paint(false, false, false, false, &c); + assert_eq!(off.composited_fill(), Some(c.button)); + } + // Over a translucent face the two layers are left to the renderer. + let c = c(); + let mut on = box_paint(true, false, false, false, &c); + on.face = on.face.with_alpha(0.5); + assert_eq!(on.composited_fill(), None); + } + + #[test] + fn the_mark_runs_through_the_three_points() { + let i = Rect::new(0.0, 0.0, 20.0, 20.0); + let whole = check_mark(i, 1.0); + assert_eq!( + whole, + vec![ + Point::new(6.0, 10.0), + Point::new(9.0, 13.0), + Point::new(14.5, 7.5) + ] + ); + assert!(check_mark(i, 0.0).is_empty()); + // Part way, it ends on the polyline, short of the end. + let half = check_mark(i, 0.5); + let end = *half.last().unwrap(); + assert!(end.x > 9.0 && end.x < 14.5, "{end:?}"); + assert!( + (end.y - (13.0 - (end.x - 9.0))).abs() < 1e-3, + "on the long arm" + ); + } + + #[test] + fn the_partial_squares_appear_one_a_third() { + let i = Rect::new(0.0, 0.0, 20.0, 20.0); + assert!(partial_dots(i, 0.0, false).is_empty()); + assert_eq!(partial_dots(i, 0.34, false).len(), 1); + assert_eq!(partial_dots(i, 0.67, false).len(), 2); + let all = partial_dots(i, 1.0, false); + assert_eq!( + all, + vec![ + Rect::new(5.0, 9.0, 2.0, 2.0), + Rect::new(9.0, 9.0, 2.0, 2.0), + Rect::new(13.0, 9.0, 2.0, 2.0) + ] + ); + // From the trailing side's opposite in right-to-left. + assert_eq!( + partial_dots(i, 0.34, true)[0], + Rect::new(13.0, 9.0, 2.0, 2.0) + ); + } + + #[test] + fn the_focus_frame_lies_outside_the_box() { + let i = Rect::new(0.0, 0.0, 20.0, 20.0); + let ring = indicator_focus_frame(i, BREEZE_CHECKBOX_RADIUS, 2.0); + // Centred 1 px outside the 16 px box: from its edge to the + // indicator's. + assert_eq!(ring.rect, Rect::new(1.0, 1.0, 18.0, 18.0)); + assert_eq!(ring.corner_radius, BREEZE_CHECKBOX_RADIUS + 1.0); + } + + #[test] + fn variants_move_only_the_radius() { + assert_eq!(variant_radius(CheckboxVariant::Square), 4.0); + assert!(variant_radius(CheckboxVariant::Rounded) > 4.0); + assert_eq!(variant_radius(CheckboxVariant::Circle), 7.5); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/chrome.rs b/crates/teksilo-theme-kde/src/styles/chrome.rs new file mode 100644 index 000000000..37dd56419 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/chrome.rs @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The paint vocabulary the Breeze button styles share: the button frame, +//! its one-pixel shadow and the keyboard focus frame. +//! +//! All three are painted by [`BreezeButtonChrome`], a leaf widget that +//! resolves every colour from [`PaintContext::theme`] at paint time, so a +//! live theme swap repaints with the new palette and the theme's inactive +//! and high-contrast projections reach the frame with no code here. +//! +//! ## The frame +//! +//! A Breeze button is a rounded rectangle on the node inset by one pixel on +//! every side (room for the shadow), stroked with a 1 px pen on that +//! rectangle inset by half the pen, so the outline lies inside it and its +//! radius is 4.5 ([`BREEZE_STROKED_FRAME_RADIUS`](crate::shape::BREEZE_STROKED_FRAME_RADIUS)). +//! Three buttons share it: +//! +//! - the **push button**: a flat fill of the button colour, outlined in the +//! button colour moved toward the button text by the frame contrast, the +//! outline turning to the highlight under the pointer or keyboard focus, +//! and the fill moving 30 % toward the highlight while pressed; +//! - the **default button**, in an active window only: the button colour +//! moved 20 % toward the highlight, outlined half-way between the +//! highlight and the button outline; +//! - the **flat button** (a tool button or a flat push button): nothing at +//! rest, a highlight outline with no fill under the pointer, the highlight +//! at 30 % while pressed and the button text at 12.5 % while checked. +//! +//! The push and default buttons cast a 1 px shadow, black at 12.5 % one +//! pixel below the frame (`shadow_xs`), only in an active window and never +//! while pressed, checked or disabled; a flat button never casts one. +//! +//! ## Which highlight +//! +//! Breeze outlines and fills a button with the **active** window's +//! highlight whatever the window state, so a button hovered in a backdrop +//! window still lights up in the accent, not in the muted selection colour +//! an inactive Breeze window paints its selections with. While the theme +//! paints its [`KdePalette`]'s own accent, that highlight comes from the +//! palette's active tokens for the same contrast preference; a theme whose +//! accent the app changed paints its own accent, as projected. A disabled +//! button uses the disabled highlight. +//! +//! ## Without the palette +//! +//! The neutral button colours come from the [`KdePalette`] extension, which +//! carries the button colour, its text and its outline, three colours +//! `ColorTokens` has no slot for. A theme without the extension paints the +//! frame from its own roles instead: the content surface, the primary text +//! and the border, so a Breeze button dropped into another theme still +//! paints a sane frame. +//! +//! ## The focus frame +//! +//! Keyboard focus adds Breeze's 2 px focus frame, from 1 px inside the node +//! to 1 px outside it, its inner edge at the control radius, which is what +//! the shape tokens say (`focus_ring_width` 2, `focus_ring_offset` -1). +//! Breeze paints it in the highlight at 30 %; this paints the `focus_ring` +//! token, the focus colour lifted to 3:1 and opaque (D5 of the colour +//! mapping), so the indicator meets WCAG SC 1.4.11 on its own. + +use teksilo_canvas::{Canvas, Rect, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{FocusRingStroke, PaintState, Theme}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::ColorState; +use teksilo_kde_scheme::KdeColor; +use teksilo_kde_scheme::math::mix; +use teksilo_tokens::{Color, CornerRadius, ShapeTokens}; + +use crate::color::face_under_primary_text; +use crate::palette::KdePalette; + +/// `[Hp]` How far a default button's fill moves from the button colour +/// toward the highlight. +pub const DEFAULT_BUTTON_FILL: f64 = 0.2; +/// `[Hp]` How far a default button's outline moves from the highlight toward +/// the button outline. +pub const DEFAULT_BUTTON_OUTLINE: f64 = 0.5; +/// `[Hp]` The alpha of a pressed flat button's highlight fill. +pub const FLAT_PRESSED_ALPHA: f32 = 0.3; +/// `[Hp]` How far a checked button's fill moves toward the button text, and +/// the alpha of a checked flat button's button-text fill. +pub const CHECKED_FILL: f32 = 0.125; +/// `[Hp]` The inset of a button's frame inside its node, which leaves room +/// for the 1 px shadow below it. +pub const SHADOW_INSET: f32 = 1.0; + +/// Which Breeze button a surface is painted as. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BreezeButtonKind { + /// The push button: button-colour fill, outline, shadow. + Push, + /// The default button: the push button with a fill moved toward the + /// highlight, in an active window. In an inactive window it is a push + /// button. + Default, + /// The flat button, a tool button or a flat push button: nothing at + /// rest and never a shadow. + Flat, + /// `[unpublished]` A push button filled with the accent and its hover + /// and press shades, for a control whose label is the on-accent text and + /// whose style has no say over it (a `SplitButton`'s filled variants). + /// Breeze has no accent-filled button; see + /// [`crate::styles::split_button`]. Its frame is outlined in its own + /// face colour, so it covers the push button's band, from 1 px inside + /// the node, and hides the shadow's sides as the push button's outline + /// does. + Accent, +} + +impl BreezeButtonKind { + /// Whether this kind can cast the 1 px shadow. + pub fn casts_shadow(self) -> bool { + !matches!(self, BreezeButtonKind::Flat) + } +} + +/// A button's interaction state, in Breeze's precedence order. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum BreezeButtonState { + /// At rest. + #[default] + Rest, + /// Hovered or showing keyboard focus: Breeze paints both the same. + Highlighted, + /// Pressed. + Pressed, + /// Disabled. + Disabled, +} + +impl BreezeButtonState { + /// Collapse the style configuration's four booleans into a state: + /// disabled first, then pressed, then hovered or focused. + /// + /// Each input may be a derived signal, so this only composes with `zip` + /// and `map`. + pub fn derive( + is_disabled: &Signal, + is_pressed: &Signal, + is_hovered: &Signal, + is_focused: &Signal, + ) -> Signal { + is_disabled + .zip3(is_pressed, is_hovered) + .zip(is_focused) + .map(|((disabled, pressed, hovered), focused)| { + if *disabled { + BreezeButtonState::Disabled + } else if *pressed { + BreezeButtonState::Pressed + } else if *hovered || *focused { + BreezeButtonState::Highlighted + } else { + BreezeButtonState::Rest + } + }) + } +} + +/// The colours a Breeze button is painted from, in one paint state. +/// +/// The faces a label is set on (the face at rest, pressed, checked and the +/// default button's) carry the primary text the label is painted in at the +/// text floor of the paint state, 4.5:1 or 7:1 under high contrast: the +/// button colour is the surface the colour tokens lift that text on, and +/// a face mixed from it is moved, as an accent wash is (D1 of the colour +/// mapping), until the text reaches the floor on it. In stock Breeze none +/// moves. A disabled button's faces are Breeze's, its text being exempt. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeButtonColors { + /// The button colour, a push button's face. + pub button: Color, + /// The button text, which a checked button's fill moves toward. + pub button_text: Color, + /// The push button's outline. + pub button_outline: Color, + /// The highlight a hovered, focused or pressed button is outlined in: + /// the active window's (see the module documentation), or the disabled + /// one for a disabled button. + pub highlight: Color, + /// A pressed push button's face: the button colour 30 % of the way to + /// the highlight, the theme's `surface_pressed`. + pub pressed: Color, + /// `[Hp]` The default button's face: the button colour moved toward the + /// highlight. + pub default_face: Color, + /// `[Hp]` A checked push button's face: the button colour moved toward + /// the button text. + pub checked_face: Color, + /// The accent family, for [`BreezeButtonKind::Accent`]. + pub accent: Color, + /// The hovered accent. + pub accent_hover: Color, + /// The pressed accent. + pub accent_pressed: Color, + /// The disabled accent. + pub accent_disabled: Color, +} + +impl BreezeButtonColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference, for an enabled + /// or a disabled button. + /// + /// `theme` is the theme the paint walker hands a widget, already + /// projected for that state; see the module documentation for where each + /// colour comes from. + pub fn resolve( + theme: &Theme, + window_active: bool, + high_contrast: bool, + disabled: bool, + ) -> BreezeButtonColors { + let c = &theme.colors; + let accent_family = + |button: Color, button_text: Color, outline: Color| BreezeButtonColors { + button, + button_text, + button_outline: outline, + highlight: if disabled { + c.accent_disabled + } else { + c.accent + }, + pressed: c.surface_pressed, + default_face: button, + checked_face: button, + accent: c.accent, + accent_hover: c.accent_hover, + accent_pressed: c.accent_pressed, + accent_disabled: c.accent_disabled, + }; + let mut colors = match theme.extension::() { + // No KDE palette: the theme's own roles stand in for the button + // colours it has no slot for. + None if disabled => { + accent_family(c.surface_disabled, c.text_primary, c.border_disabled) + } + None => accent_family(c.surface_content, c.text_primary, c.border), + Some(palette) => { + let state = if disabled { + ColorState::Disabled + } else if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let b = palette.breeze_colors(state, high_contrast); + let mut colors = accent_family(b.button, b.button_text, b.button_outline); + // `[Hp]` The active window's highlight, while the theme + // paints the palette's own accent for this state. + let painted = PaintState { + window_active, + high_contrast, + }; + if c.accent == palette.tokens(painted).accent { + let active = palette.tokens(PaintState { + window_active: true, + high_contrast, + }); + if !disabled { + colors.highlight = active.accent; + } + colors.pressed = active.surface_pressed; + } + colors + } + }; + // `[Hp]` The default button's face moves toward the highlight, a + // checked one's toward the button text. + colors.default_face = kde_mix(colors.button, colors.highlight, DEFAULT_BUTTON_FILL); + colors.checked_face = kde_mix(colors.button, colors.button_text, f64::from(CHECKED_FILL)); + if !disabled { + // `[wcag]` D1: each face mixed from the button colour carries the + // label's primary text, as the button colour itself does. + let under_label = |face: Color| face_under_primary_text(face, c, high_contrast); + colors.pressed = under_label(colors.pressed); + colors.default_face = under_label(colors.default_face); + colors.checked_face = under_label(colors.checked_face); + } + colors + } + + /// The default button's outline: half-way from the highlight to the + /// button outline. + pub fn default_outline(&self) -> Color { + kde_mix(self.highlight, self.button_outline, DEFAULT_BUTTON_OUTLINE) + } +} + +/// KDE's mix of two colours, in its 16-bit arithmetic. +pub(crate) fn kde_mix(a: Color, b: Color, t: f64) -> Color { + mix(KdeColor::from_color(a), KdeColor::from_color(b), t).to_color() +} + +/// What a button paints in one state: a face, an outline, a shadow, each +/// optional. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ButtonPaint { + /// The fill of the frame, or nothing. + pub face: Option, + /// The 1 px outline of the frame, or nothing. + pub outline: Option, + /// Whether the 1 px shadow is cast below the frame. + pub shadow: bool, +} + +impl ButtonPaint { + const NOTHING: ButtonPaint = ButtonPaint { + face: None, + outline: None, + shadow: false, + }; +} + +/// `[Hp]` What a `kind` button paints in `state`, checked or not, in a +/// window that is or is not active, from `c`. +/// +/// The rules of the module documentation. Pressing wins over checking, +/// which wins over hover and focus; a checked button keeps its plain outline +/// under the pointer. +pub fn button_paint( + kind: BreezeButtonKind, + state: BreezeButtonState, + checked: bool, + window_active: bool, + c: &BreezeButtonColors, +) -> ButtonPaint { + use BreezeButtonState::{Disabled, Highlighted, Pressed, Rest}; + // The default button is one only in an active window. + let kind = match kind { + BreezeButtonKind::Default if !window_active => BreezeButtonKind::Push, + other => other, + }; + let shadow = + kind.casts_shadow() && window_active && !checked && matches!(state, Rest | Highlighted); + let paint = |face: Option, outline: Option| ButtonPaint { + face, + outline, + shadow, + }; + match kind { + BreezeButtonKind::Push | BreezeButtonKind::Default => { + let (rest_face, rest_outline) = if kind == BreezeButtonKind::Default { + (c.default_face, c.default_outline()) + } else { + (c.button, c.button_outline) + }; + match state { + Pressed => paint(Some(c.pressed), Some(c.highlight)), + _ if checked => paint(Some(c.checked_face), Some(c.button_outline)), + Highlighted => paint(Some(rest_face), Some(c.highlight)), + Rest | Disabled => paint(Some(rest_face), Some(rest_outline)), + } + } + BreezeButtonKind::Flat => match state { + Pressed => paint( + Some(c.highlight.with_alpha(FLAT_PRESSED_ALPHA)), + Some(c.highlight), + ), + _ if checked => paint( + Some(c.button_text.with_alpha(CHECKED_FILL)), + Some(c.button_outline), + ), + Highlighted => paint(None, Some(c.highlight)), + Rest | Disabled => ButtonPaint::NOTHING, + }, + BreezeButtonKind::Accent => { + let face = match state { + Rest => c.accent, + Highlighted => c.accent_hover, + Pressed => c.accent_pressed, + Disabled => c.accent_disabled, + }; + paint(Some(face), Some(face)) + } + } +} + +/// `[Hp]` The rectangle a button's frame is filled and stroked on: the node +/// inset by [`SHADOW_INSET`], then by half the pen, so the outline lies +/// inside the inset rectangle. +pub fn frame_rect(bounds: Rect, pen: f32) -> Rect { + let d = SHADOW_INSET + pen * 0.5; + Rect::new( + bounds.x + d, + bounds.y + d, + (bounds.width - d * 2.0).max(0.0), + (bounds.height - d * 2.0).max(0.0), + ) +} + +/// The radius the frame of a theme's controls is stroked with: the control +/// radius less half the pen, 4.5 for Breeze. +pub fn frame_radius(shape: &ShapeTokens) -> f32 { + (shape.radius_control - shape.border_width * 0.5).max(0.0) +} + +/// Paint `paint` into the node `bounds`: the shadow, then the face, then the +/// outline, all on [`frame_rect`]. +pub fn paint_button(canvas: &mut Canvas, bounds: Rect, paint: &ButtonPaint, theme: &Theme) { + let shape = &theme.shape; + let pen = shape.border_width; + let rect = frame_rect(bounds, pen); + let corner = CornerRadius::uniform(frame_radius(shape)); + if paint.shadow { + // `[Hp]` The frame's outline in the shadow colour, moved down by the + // shadow's offset: the face covers all of it but the line below. + let s = &shape.shadow_xs; + let moved = Rect::new( + rect.x + s.offset_x, + rect.y + s.offset_y, + rect.width, + rect.height, + ); + canvas.stroke_rounded_rect(moved, corner, s.color, pen); + } + if let Some(face) = paint.face + && face.a() > 0.0 + { + canvas.fill_rounded_rect(rect, corner, face); + } + if let Some(outline) = paint.outline + && outline.a() > 0.0 + && pen > 0.0 + { + canvas.stroke_rounded_rect(rect, corner, outline, pen); + } +} + +/// The stroke of the focus frame around the node `bounds`: its inner edge +/// `focus_ring_offset` from the node edge with the control radius, the +/// stroke as wide as `focus_ring_width`. +pub fn focus_frame(bounds: Rect, shape: &ShapeTokens) -> FocusRingStroke { + // At its inner edge the ring has the control radius, so at the node edge + // it has that radius less the (signed) offset. + let edge_radius = (shape.radius_control - shape.focus_ring_offset).max(0.0); + FocusRingStroke::around(bounds, edge_radius, shape) +} + +/// Paint the focus frame around the node `bounds` in the theme's +/// `focus_ring` colour. +pub fn paint_focus_frame(canvas: &mut Canvas, bounds: Rect, theme: &Theme) { + let shape = &theme.shape; + let color = theme.colors.focus_ring; + if color.a() <= 0.0 || shape.focus_ring_width <= 0.0 { + return; + } + let ring = focus_frame(bounds, shape); + canvas.stroke_rounded_rect( + ring.rect, + CornerRadius::uniform(ring.corner_radius), + color, + shape.focus_ring_width, + ); +} + +/// The chrome layer of a Breeze button: its frame, shadow and focus frame, +/// painted behind whatever content the style stacks on top. +/// +/// Sized like `RectWidget`: it takes whatever the proposal offers and +/// reports zero when unbounded, so it can sit in a `ZStack` without changing +/// the control's size. +pub struct BreezeButtonChrome { + kind: BreezeButtonKind, + state: Signal, + /// `true` while the button shows its focus frame. + show_focus_frame: Signal, + /// The checked state of a checkable button. + checked: Option>, +} + +impl BreezeButtonChrome { + /// A chrome of `kind` following `state`, showing the focus frame while + /// `show_focus_frame` is set. + pub fn new( + kind: BreezeButtonKind, + state: Signal, + show_focus_frame: Signal, + ) -> Self { + Self { + kind, + state, + show_focus_frame, + checked: None, + } + } + + /// Paint the checked state while `checked` is set. + pub fn checked(mut self, checked: Option>) -> Self { + self.checked = checked; + self + } +} + +impl std::fmt::Debug for BreezeButtonChrome { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeButtonChrome") + .field("kind", &self.kind) + .finish() + } +} + +impl Widget for BreezeButtonChrome { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + self.state.bind_to(id, registry, BindingLevel::RepaintOnly); + self.show_focus_frame + .bind_to(id, registry, BindingLevel::RepaintOnly); + if let Some(checked) = &self.checked { + checked.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let state = self.state.get(); + let checked = self.checked.as_ref().is_some_and(Signal::get); + let colors = BreezeButtonColors::resolve( + ctx.theme, + ctx.window_active, + ctx.prefers_high_contrast, + state == BreezeButtonState::Disabled, + ); + let paint = button_paint(self.kind, state, checked, ctx.window_active, &colors); + paint_button(canvas, bounds, &paint, ctx.theme); + if self.show_focus_frame.get() { + paint_focus_frame(canvas, bounds, ctx.theme); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the owning control emits the real node. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::PaletteRole; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + fn colors(theme: &Theme, window_active: bool, disabled: bool) -> BreezeButtonColors { + BreezeButtonColors::resolve(theme, window_active, false, disabled) + } + + #[test] + fn the_state_follows_breezes_precedence() { + let disabled = Signal::new(false); + let pressed = Signal::new(false); + let hovered = Signal::new(false); + let focused = Signal::new(false); + let state = BreezeButtonState::derive(&disabled, &pressed, &hovered, &focused); + assert_eq!(state.get(), BreezeButtonState::Rest); + focused.set(true); + assert_eq!( + state.get(), + BreezeButtonState::Highlighted, + "focus lights it" + ); + focused.set(false); + hovered.set(true); + assert_eq!(state.get(), BreezeButtonState::Highlighted, "so does hover"); + pressed.set(true); + assert_eq!(state.get(), BreezeButtonState::Pressed, "press beats hover"); + disabled.set(true); + assert_eq!( + state.get(), + BreezeButtonState::Disabled, + "disabled beats all" + ); + } + + /// The Breeze Light values of the specification's rules, and the + /// colours Plasma 6.8 paints them in (the golden pixel samples). + #[test] + fn breeze_light_paints_the_specifications_colours() { + let t = crate::light(); + let c = colors(&t, true, false); + assert_eq!(rgba8(c.button), [252, 252, 252, 255]); + assert_eq!(rgba8(c.button_outline), [209, 209, 210, 255]); + assert_eq!(rgba8(c.highlight), [61, 174, 233, 255]); + assert_eq!(rgba8(c.pressed), [195, 229, 246, 255]); + assert_eq!(rgba8(c.default_face), [214, 236, 248, 255]); + assert_eq!(rgba8(c.default_outline()), [135, 192, 221, 255]); + + let d = colors(&crate::dark(), true, false); + assert_eq!(rgba8(d.button), [41, 44, 48, 255]); + assert_eq!(rgba8(d.button_outline), [83, 86, 89, 255]); + assert_eq!(rgba8(d.default_face), [45, 70, 85, 255]); + assert_eq!(rgba8(d.default_outline()), [72, 130, 161, 255]); + assert_eq!(rgba8(d.pressed), [47, 83, 104, 255]); + } + + #[test] + fn a_hovered_button_in_a_backdrop_window_is_outlined_in_the_active_highlight() { + let t = crate::light(); + let inactive = t.for_paint_state(PaintState { + window_active: false, + high_contrast: false, + }); + assert_ne!(inactive.colors.accent, t.colors.accent, "premise: it mutes"); + let c = colors(&inactive, false, false); + assert_eq!(c.highlight, t.colors.accent); + assert_eq!(c.pressed, t.colors.surface_pressed); + // A disabled button uses the disabled highlight. + assert_eq!(colors(&t, true, true).highlight, t.colors.accent_disabled); + } + + #[test] + fn an_accent_the_app_changed_is_painted_as_projected() { + let mut t = crate::light(); + t.colors.accent = Color::from_hex("#FF0000"); + let c = colors(&t, true, false); + assert_eq!(c.highlight, t.colors.accent); + } + + #[test] + fn the_high_contrast_outline_is_the_button_text() { + let t = crate::light(); + let hc = t.for_paint_state(PaintState { + window_active: true, + high_contrast: true, + }); + let c = BreezeButtonColors::resolve(&hc, true, true, false); + assert_eq!(c.button_outline, c.button_text); + assert_eq!(c.highlight, hc.colors.accent, "the lifted accent"); + } + + #[test] + fn a_theme_without_the_palette_paints_from_its_roles() { + let t = teksilo_core::presets::intui::light(); + let c = colors(&t, true, false); + assert_eq!(c.button, t.colors.surface_content); + assert_eq!(c.button_outline, t.colors.border); + assert_eq!(c.highlight, t.colors.accent); + let d = colors(&t, true, true); + assert_eq!(d.button, t.colors.surface_disabled); + assert_eq!(d.button_outline, t.colors.border_disabled); + } + + #[test] + fn the_disabled_push_button_paints_the_disabled_button_colours() { + let t = crate::light(); + let p = t.extension::().unwrap(); + let c = colors(&t, true, true); + assert_eq!(c.button, p.role(ColorState::Disabled, PaletteRole::Button)); + assert_eq!( + c.button_outline, + p.colors(ColorState::Disabled).button_outline + ); + // Plasma 6.8 paints 240,240,240 outlined in 226,226,226. + assert_eq!(rgba8(c.button), [240, 240, 240, 255]); + assert_eq!(rgba8(c.button_outline), [226, 226, 226, 255]); + let paint = button_paint( + BreezeButtonKind::Push, + BreezeButtonState::Disabled, + false, + true, + &c, + ); + assert_eq!(paint.face, Some(c.button)); + assert!(!paint.shadow, "a disabled button casts no shadow"); + } + + #[test] + fn the_paint_rules_for_each_kind_and_state() { + use BreezeButtonKind::{Accent, Default, Flat, Push}; + use BreezeButtonState::{Disabled, Highlighted, Pressed, Rest}; + let c = colors(&crate::light(), true, false); + let p = |kind, state, checked, active| button_paint(kind, state, checked, active, &c); + + assert_eq!( + p(Push, Rest, false, true), + ButtonPaint { + face: Some(c.button), + outline: Some(c.button_outline), + shadow: true + } + ); + assert_eq!(p(Push, Highlighted, false, true).outline, Some(c.highlight)); + assert!(p(Push, Highlighted, false, true).shadow); + let pressed = p(Push, Pressed, false, true); + assert_eq!( + (pressed.face, pressed.outline, pressed.shadow), + (Some(c.pressed), Some(c.highlight), false) + ); + let checked = p(Push, Highlighted, true, true); + assert_eq!( + (checked.face, checked.outline, checked.shadow), + (Some(c.checked_face), Some(c.button_outline), false) + ); + // The shadow is an active window's only. + assert!(!p(Push, Rest, false, false).shadow); + + let default = p(Default, Rest, false, true); + assert_eq!(default.face, Some(c.default_face)); + assert_eq!(default.outline, Some(c.default_outline())); + assert!(default.shadow); + // In an inactive window the default button is a push button. + assert_eq!(p(Default, Rest, false, false), p(Push, Rest, false, false)); + + assert_eq!(p(Flat, Rest, false, true), ButtonPaint::NOTHING); + assert_eq!(p(Flat, Disabled, false, true), ButtonPaint::NOTHING); + assert_eq!( + p(Flat, Highlighted, false, true), + ButtonPaint { + face: None, + outline: Some(c.highlight), + shadow: false + } + ); + let flat_pressed = p(Flat, Pressed, false, true); + assert_eq!( + flat_pressed.face, + Some(c.highlight.with_alpha(FLAT_PRESSED_ALPHA)) + ); + assert!(!flat_pressed.shadow); + let flat_checked = p(Flat, Rest, true, true); + assert_eq!( + flat_checked.face, + Some(c.button_text.with_alpha(CHECKED_FILL)) + ); + assert_eq!(flat_checked.outline, Some(c.button_outline)); + + assert_eq!(p(Accent, Rest, false, true).face, Some(c.accent)); + assert_eq!( + p(Accent, Highlighted, false, true).face, + Some(c.accent_hover) + ); + assert_eq!(p(Accent, Pressed, false, true).face, Some(c.accent_pressed)); + assert_eq!( + p(Accent, Disabled, false, true).face, + Some(c.accent_disabled) + ); + assert!(p(Accent, Rest, false, true).shadow); + // The accent frame is outlined in its own face, every state. + for state in [Rest, Highlighted, Pressed, Disabled] { + let accent = p(Accent, state, false, true); + assert_eq!(accent.outline, accent.face, "{state:?}"); + } + } + + #[test] + fn the_flat_and_checked_fills_are_breezes_samples() { + // Over the Breeze Light window, 239,240,241, as Plasma 6.8 paints a + // pressed and a checked tool button: within one step a channel, the + // margin by which Qt's raster painter rounds an 8-bit blend its own + // way. + let c = colors(&crate::light(), true, false); + let window = Color::from_hex("#EFF0F1"); + let over = |top: Color| { + let a = top.a(); + let ch = |f: f32, g: f32| ((f * a + g * (1.0 - a)) * 255.0).round() as i32; + [ + ch(top.r(), window.r()), + ch(top.g(), window.g()), + ch(top.b(), window.b()), + ] + }; + let near = |got: [i32; 3], want: [i32; 3]| { + assert!( + got.iter().zip(want).all(|(g, w)| (g - w).abs() <= 1), + "{got:?} is not within one step of {want:?}" + ); + }; + near( + over(c.highlight.with_alpha(FLAT_PRESSED_ALPHA)), + [185, 220, 238], + ); + near( + over(c.button_text.with_alpha(CHECKED_FILL)), + [213, 215, 216], + ); + } + + #[test] + fn the_frame_lies_inside_the_node_with_room_for_the_shadow() { + let t = crate::light(); + let node = Rect::new(0.0, 0.0, 84.0, 34.0); + let r = frame_rect(node, t.shape.border_width); + assert_eq!(r, Rect::new(1.5, 1.5, 81.0, 31.0)); + assert_eq!(frame_radius(&t.shape), 4.5); + // A degenerate node does not invert. + let tiny = frame_rect(Rect::new(0.0, 0.0, 2.0, 2.0), 1.0); + assert!(tiny.width >= 0.0 && tiny.height >= 0.0); + } + + #[test] + fn the_focus_frame_runs_from_one_pixel_inside_to_one_pixel_outside() { + let t = crate::light(); + let node = Rect::new(10.0, 10.0, 84.0, 34.0); + let ring = focus_frame(node, &t.shape); + // Centred on the node edge, 2 px wide: from 1 px inside to 1 px + // outside, inner radius 5 and outer radius 7. + assert_eq!(ring.rect, node); + assert_eq!(ring.corner_radius, 6.0); + assert_eq!( + ring.corner_radius - t.shape.focus_ring_width * 0.5, + t.shape.radius_control + ); + assert_eq!( + ring.corner_radius + t.shape.focus_ring_width * 0.5, + crate::shape::BREEZE_FOCUS_RING_OUTER_RADIUS + ); + } + + #[test] + fn only_the_flat_kind_never_casts_a_shadow() { + assert!(!BreezeButtonKind::Flat.casts_shadow()); + for k in [ + BreezeButtonKind::Push, + BreezeButtonKind::Default, + BreezeButtonKind::Accent, + ] { + assert!(k.casts_shadow()); + } + } +} diff --git a/crates/teksilo-theme-kde/src/styles/combo_box.rs b/crates/teksilo-theme-kde/src/styles/combo_box.rs new file mode 100644 index 000000000..821ca4028 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/combo_box.rs @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze combo box. +//! +//! A combo box that cannot be typed into is a push button with an arrow: +//! the push button's frame, face, outline and shadow +//! ([`crate::styles::chrome`]), with "pressed" meaning the popup is open, so +//! an open combo box is filled 30 % toward the highlight and casts no +//! shadow. Its arrow is a 10 px chevron ([`crate::styles::arrow`]) centred in +//! a 20 px box at the trailing edge, 2 px inside it (the frame margin), +//! drawn in the arrow colour of the button text: the button text moved 15 % +//! toward the button colour. While the combo box has keyboard focus and the +//! pointer is not over it the arrow is the window text. The arrow does not +//! change under the pointer, as in Breeze. +//! +//! The combo box is `max(H, 20)` plus the 6 px frame above and below, 32 px +//! at Plasma's default font, through the density ladder, and its label +//! starts 6 px inside the leading edge. It is Breeze's width rule, the label +//! plus 38 px, when nothing proposes a width: the 6 px frame on both sides +//! of the label, the 20 px arrow box and 6 more, laid out as 6 px before the +//! label, 10 px between it and the arrow box and the 2 px frame margin after +//! it ([`crate::styles::metrics::BreezeComboMetrics`]). The gaps scale with +//! the density's spacing; the arrow box and the frame margin are painted +//! and do not. Right to left, the arrow box is at the left edge. +//! +//! ## Where this departs from Breeze +//! +//! - **D5, keyboard focus.** As on a push button, keyboard focus adds the +//! 2 px focus frame in the opaque `focus_ring` token, from 1 px inside to +//! 1 px outside the node, besides the highlight outline Breeze draws. +//! - **The arrow on any scheme.** The arrow colour is lifted in luma to the +//! non-text floor (3:1, 4.5:1 under high contrast) on the face at rest and +//! on the open combo box's pressed face, which a scheme whose inactive +//! effects move the button colour toward the button text needs; stock +//! Breeze's arrow is kept. +//! - **An empty combo box keeps its arrow colour.** Breeze draws the arrow +//! of a combo box with no items in the disabled colours; the style is not +//! told how many items there are, so only a disabled combo box's arrow is +//! in them. +//! +//! ## Variants +//! +//! Breeze has one combo box that cannot be typed into. `Outlined` and +//! `Filled` are it. `Underline` (`[unpublished]`) draws only the bottom edge +//! of a line edit's outline, in its rest, hover and focus colours, the +//! popup counting as focus; `Plain` (`[unpublished]`) draws no frame, for a +//! combo box whose surroundings are its frame. Both keep the arrow and the +//! metrics. + +use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{ + ComboBoxStyle, ComboBoxStyleConfig, ComboBoxVariant, TextInputValidationLevel, Theme, +}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::ColorState; +use teksilo_tokens::Color; + +use crate::color::mark_on_faces; +use crate::palette::KdePalette; +use crate::styles::arrow::{ArrowDirection, arrow_color, paint_arrow}; +use crate::styles::chrome::{ + BreezeButtonChrome, BreezeButtonColors, BreezeButtonKind, BreezeButtonState, +}; +use crate::styles::metrics::{BreezeComboMetrics, breeze_combo_metrics_for}; +use crate::styles::text_input::BreezeFieldChrome; + +/// Breeze `ComboBoxStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeComboBoxStyle; + +impl ComboBoxStyle for BreezeComboBoxStyle { + fn make_body(&self, cfg: &ComboBoxStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let theme = ctx.theme(); + let metrics = breeze_combo_metrics_for(&theme.input, &theme.typography); + let chrome = match cfg.variant { + ComboBoxVariant::Outlined | ComboBoxVariant::Filled => { + // `[Hp]` The push button, the open popup standing for the + // press. The combo box's focus signal is already its + // `:focus-visible` one. + let state = BreezeButtonState::derive( + &cfg.is_disabled, + &cfg.is_open, + &cfg.is_hovered, + &cfg.is_focused, + ); + let show_focus_frame = cfg + .is_focused + .zip(&cfg.is_disabled) + .map(|(focused, disabled)| *focused && !*disabled); + Some(ctx.add(BreezeButtonChrome::new( + BreezeButtonKind::Push, + state, + show_focus_frame, + ))) + } + ComboBoxVariant::Underline => Some(ctx.add(BreezeFieldChrome::new( + cfg.is_focused.or(&cfg.is_open), + cfg.is_hovered.clone(), + cfg.is_disabled.clone(), + Signal::new(TextInputValidationLevel::None), + true, + ))), + ComboBoxVariant::Plain => None, + }; + let arrow = ctx.add(BreezeComboArrow { + is_focused: cfg.is_focused.clone(), + is_hovered: cfg.is_hovered.clone(), + is_disabled: cfg.is_disabled.clone(), + }); + ctx.add(BreezeComboLayout { + metrics, + chrome, + label: cfg.selected_label, + arrow, + }) + } +} + +/// `[Hp]` The colour of a combo box's arrow in `theme`, as it paints in a +/// window that is or is not active, with or without the high-contrast +/// preference, focused or not, enabled or not. +/// +/// The arrow colour of the button text, or the window text while focused +/// (and not under the pointer, which the caller decides), or the arrow +/// colour of the disabled button text. Without a [`KdePalette`] the primary +/// text and the content surface stand in for the button text and colour. +pub fn combo_arrow_color( + theme: &Theme, + window_active: bool, + high_contrast: bool, + focused: bool, + disabled: bool, +) -> Color { + let c = &theme.colors; + if focused && !disabled { + return c.text_primary; + } + let Some(palette) = theme.extension::() else { + return if disabled { + arrow_color(c.text_disabled, c.surface_disabled) + } else { + arrow_color(c.text_primary, c.surface_content) + }; + }; + let state = if disabled { + ColorState::Disabled + } else if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let b = palette.breeze_colors(state, high_contrast); + let arrow = arrow_color(b.button_text, b.button); + if disabled { + return arrow; + } + // `[wcag]` The arrow lies on the face at rest and on the pressed face + // while the popup is open: lifted to the non-text floor on both. + let faces = BreezeButtonColors::resolve(theme, window_active, high_contrast, false); + mark_on_faces(arrow, &[faces.button, faces.pressed], high_contrast) +} + +/// `[S]` Where a combo box's arrow box lies in the node `bounds`: at the +/// trailing edge, inside the frame margin, centred vertically. +pub fn combo_arrow_rect(bounds: Rect, m: &BreezeComboMetrics, rtl: bool) -> Rect { + let x = if rtl { + bounds.x + m.padding_trailing + } else { + bounds.x + bounds.width - m.padding_trailing - m.arrow_box + }; + Rect::new( + x, + bounds.y + (bounds.height - m.arrow_box) * 0.5, + m.arrow_box, + m.arrow_box, + ) +} + +/// The arrangement of a Breeze combo box: the frame over the whole node, +/// the label after the leading inset, the arrow box at the trailing edge. +struct BreezeComboLayout { + metrics: BreezeComboMetrics, + chrome: Option, + label: WidgetId, + arrow: WidgetId, +} + +impl std::fmt::Debug for BreezeComboLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeComboLayout") + .field("metrics", &self.metrics) + .finish() + } +} + +impl BreezeComboLayout { + /// The label's size when it is offered `width`. + fn label_size(&self, width: Option, ctx: &LayoutContext) -> Size { + ctx.child_size( + self.label, + SizeProposal { + width, + height: None, + }, + ) + .unwrap_or(Size::new(0.0, 0.0)) + } +} + +impl Widget for BreezeComboLayout { + fn build(&mut self, _ctx: &mut BuildContext) -> Vec { + self.children() + } + + fn children(&self) -> Vec { + self.chrome + .into_iter() + .chain([self.label, self.arrow]) + .collect() + } + + /// The label plus the chrome when nothing proposes a width, the + /// proposed width otherwise; the Breeze height, or the label's if it is + /// taller. + fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { + let chrome = self.metrics.chrome_width(); + let label = self.label_size(proposal.width.map(|w| (w - chrome).max(0.0)), ctx); + Size::new( + proposal.width.unwrap_or(label.width + chrome), + self.metrics.height.max(label.height), + ) + .into() + } + + fn place_children( + &self, + bounds: Rect, + _proposal: SizeProposal, + children: &mut [WidgetPlacement], + ctx: &LayoutContext, + ) { + let m = &self.metrics; + let rtl = ctx.is_rtl(); + let arrow = combo_arrow_rect(bounds, m, rtl); + let room = (bounds.width - m.chrome_width()).max(0.0); + let label = self.label_size(Some(room), ctx); + let label_width = label.width.min(room); + let label_x = if rtl { + bounds.x + bounds.width - m.padding_leading - label_width + } else { + bounds.x + m.padding_leading + }; + let label_rect = Rect::new( + label_x, + bounds.y + (bounds.height - label.height) * 0.5, + label_width, + label.height, + ); + for child in children.iter_mut() { + let rect = if Some(child.id) == self.chrome { + bounds + } else if child.id == self.label { + label_rect + } else { + arrow + }; + child.origin = Point::new(rect.x, rect.y); + child.size = Size::new(rect.width, rect.height); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Layout only: the walker prunes it and the `ComboBox` names itself. + builder.set_role(teksilo_core::accesskit::Role::GenericContainer); + } +} + +/// The arrow of a Breeze combo box, painted in its 20 px box. +struct BreezeComboArrow { + is_focused: Signal, + is_hovered: Signal, + is_disabled: Signal, +} + +impl std::fmt::Debug for BreezeComboArrow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeComboArrow").finish() + } +} + +impl Widget for BreezeComboArrow { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + for s in [&self.is_focused, &self.is_hovered, &self.is_disabled] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let color = combo_arrow_color( + ctx.theme, + ctx.window_active, + ctx.prefers_high_contrast, + self.is_focused.get() && !self.is_hovered.get(), + self.is_disabled.get(), + ); + paint_arrow(canvas, bounds, ArrowDirection::Down, color); + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the `ComboBox` emits the real node. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::styles::metrics::breeze_combo_metrics; + use teksilo_kde_scheme::KdeColor; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + /// Plasma's 150 x 32 combo box puts its arrow box at (128, 6). + #[test] + fn the_arrow_box_is_where_qt_puts_it() { + let m = breeze_combo_metrics(); + let b = Rect::new(0.0, 0.0, 150.0, 32.0); + assert_eq!( + combo_arrow_rect(b, &m, false), + Rect::new(128.0, 6.0, 20.0, 20.0) + ); + assert_eq!( + combo_arrow_rect(b, &m, true), + Rect::new(2.0, 6.0, 20.0, 20.0), + "mirrored right to left" + ); + } + + /// The arrow is the arrow colour of the button text, and the window + /// text while focused, in Breeze Light. + #[test] + fn the_arrow_colours_are_breezes() { + let t = crate::light(); + let p = t.extension::().unwrap(); + let b = p.colors(ColorState::Active); + let rest = combo_arrow_color(&t, true, false, false, false); + assert_eq!(rest, arrow_color(b.button_text, b.button)); + assert_eq!(rgba8(rest), [68, 70, 73, 255]); + let focused = combo_arrow_color(&t, true, false, true, false); + assert_eq!(rgba8(focused), [35, 38, 41, 255]); + let d = p.colors(ColorState::Disabled); + assert_eq!( + combo_arrow_color(&t, true, false, true, true), + arrow_color(d.button_text, d.button), + "disabled wins over focus" + ); + } + + #[test] + fn a_theme_without_the_palette_paints_from_its_roles() { + let t = teksilo_core::presets::intui::light(); + let c = &t.colors; + assert_eq!( + combo_arrow_color(&t, true, false, false, false), + arrow_color(c.text_primary, c.surface_content) + ); + assert_eq!( + combo_arrow_color(&t, true, false, false, true), + arrow_color(c.text_disabled, c.surface_disabled) + ); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/icon_button.rs b/crates/teksilo-theme-kde/src/styles/icon_button.rs new file mode 100644 index 000000000..04aed8a53 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/icon_button.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze tool button, for `IconButton`. +//! +//! An `IconButton` is Breeze's auto-raise tool button, the borderless kind a +//! toolbar holds: nothing at rest, a highlight outline under the pointer or +//! keyboard focus, the highlight at 30 % while pressed and, for a toggle +//! that is on, the button text at 12.5 % with the plain button outline. It +//! never casts a shadow. [`crate::styles::chrome`] paints it. +//! +//! ## Sizes +//! +//! Breeze sizes an auto-raise tool button as its icon plus the 6 px margin +//! on each side: 28 px around a 16 px icon and 34 px around the 22 px +//! toolbar icon. The five `IconButtonSize`s take those two and the same rule +//! at Kirigami's 32 and 48 px icon sizes, with a compact rung at the +//! density's target size ([`BreezeToolButtonSizes`]). Every one clears the +//! 24 dp conformance floor at every density, so no conformance box is built +//! around them. +//! +//! Breeze also draws a tool button with a frame, the push button's, 32 and +//! 38 px around the same icons. `IconButton` has no framed form, so those +//! two sizes ([`BreezeGeometry`](crate::geometry::BreezeGeometry)'s +//! `framed_tool_button_height` and its medium twin) have no widget to reach. +//! +//! The glyph inside is the one the `IconButton` builds at its own icon size +//! for each rung; a style does not size it. + +use teksilo_core::build_context::BuildContext; +use teksilo_core::styles::{IconButtonSize, IconButtonStyle, IconButtonStyleConfig}; +use teksilo_core::widget_id::WidgetId; +use teksilo_widgets::primitives::{Center, FixedSize, ZStack}; + +use crate::styles::chrome::{BreezeButtonChrome, BreezeButtonKind, BreezeButtonState}; +use crate::styles::metrics::{BreezeToolButtonSizes, breeze_tool_button_sizes_for}; + +/// Breeze `IconButtonStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeIconButtonStyle; + +impl BreezeIconButtonStyle { + /// The square of `size` among `sizes`. + pub fn square(size: IconButtonSize, sizes: &BreezeToolButtonSizes) -> f32 { + match size { + IconButtonSize::Compact => sizes.compact, + IconButtonSize::Default => sizes.default, + IconButtonSize::Toolbar => sizes.toolbar, + IconButtonSize::Large => sizes.large, + IconButtonSize::Hero => sizes.hero, + } + } +} + +impl IconButtonStyle for BreezeIconButtonStyle { + fn make_body(&self, cfg: &IconButtonStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let sizes = breeze_tool_button_sizes_for(&ctx.theme().input); + let square = Self::square(cfg.size, &sizes); + let state = BreezeButtonState::derive( + &cfg.is_disabled, + &cfg.is_pressed, + &cfg.is_hovered, + &cfg.is_focused, + ); + let show_focus_frame = cfg + .is_focused + .zip(&cfg.is_disabled) + .map(|(focused, disabled)| *focused && !*disabled); + let chrome = ctx.add( + BreezeButtonChrome::new(BreezeButtonKind::Flat, state, show_focus_frame) + .checked(cfg.is_on.clone()), + ); + let icon = ctx.add(Center::new().child(cfg.icon)); + let stack = ctx.add(ZStack::new().child(chrome).child(icon)); + ctx.add(FixedSize::new().width(square).height(square).child(stack)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::styles::metrics::breeze_tool_button_sizes; + + #[test] + fn each_size_takes_its_rung() { + let s = breeze_tool_button_sizes(); + let squares = [ + IconButtonSize::Compact, + IconButtonSize::Default, + IconButtonSize::Toolbar, + IconButtonSize::Large, + IconButtonSize::Hero, + ] + .map(|size| BreezeIconButtonStyle::square(size, &s)); + assert_eq!(squares, [24.0, 28.0, 34.0, 44.0, 60.0]); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/indicator.rs b/crates/teksilo-theme-kde/src/styles/indicator.rs new file mode 100644 index 000000000..f85e2d713 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/indicator.rs @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The colours and geometry the Breeze check box, radio button and switch +//! share. +//! +//! All three are painted from the **button** colours of the scheme (a check +//! box's box is the button colour, a switch's track is the button colour +//! moved toward the button text), from the current window's highlight (a +//! checked box, unlike a hovered push button, uses the muted highlight of a +//! backdrop window) and from the view's text for the mark. Every colour is +//! resolved at paint time from the theme the walker hands the widget, so the +//! inactive and high-contrast projections reach them. +//! +//! ## Where this departs from Breeze +//! +//! - **D6, the outline of an empty indicator.** Breeze outlines an unchecked +//! box and an unselected disc in the separator colour, the window colour +//! moved a fifth of the way to the window text: 1.5:1 on the Breeze Light +//! window. That outline is the whole control, so it is drawn in +//! `border_strong`, the same outline lifted to SC 1.4.11's 3:1 (D6 of the +//! colour mapping), which a scheme whose outline already passes keeps +//! exactly. A checked box and a selected disc keep Breeze's highlight +//! outline: their state is carried by the mark, in the view text, which +//! is well above 3:1 on the highlight-washed fill in stock Breeze. For any +//! scheme the mark is lifted in luma to the non-text floor (3:1, 4.5:1 +//! under high contrast) on that fill at rest and pressed, which a scheme +//! whose inactive effects move the button colour or the highlight toward +//! the text needs. +//! - **D6, the outline of a checked switch.** A switch has no mark: on, its +//! track's highlight outline (2.2:1 on the Breeze Light window) is as much +//! the control's only boundary as the off track's. It is drawn in the +//! highlight lifted in luma to the non-text floor on the control surfaces +//! and the button colour, [`BreezeIndicatorColors::checked_outline`]; a +//! scheme whose highlight already passes, Breeze Dark's, keeps it exactly. +//! See [`crate::styles::toggle`]. +//! - **The hover decorations under high contrast.** A hovered box or disc +//! is ringed in the view's focus decoration, stroked over its outline, +//! and a hovered switch outlines its track and handle in the button set's +//! hover decoration; both stand in for the control's lifted outline. +//! Under high contrast they are lifted to 4.5:1 on the control surfaces +//! and the button colour (Breeze Light's is 2.2:1 on its window), so a +//! hovered control keeps its boundary. In the normal projection they are +//! the scheme's own. +//! - **A translucent button colour.** Breeze fills a box over an opaque +//! button colour and washes the highlight over that fill +//! ([`crate::styles::checkbox`]). A scheme may give the button colour an +//! alpha; the specification is silent on how such a face is painted. It +//! is made opaque over the window, as the colour mapping makes a lifted +//! colour opaque, so the box, the disc and the switch's handle are one +//! opaque fill wherever the control sits, the wash is composited over it +//! as for stock Breeze, and the mark is lifted on the colour the box +//! actually shows. +//! - **D5, keyboard focus.** Breeze shows keyboard focus with a 2 px band +//! of the highlight at 30 % outside the box. The band is drawn in the +//! opaque `focus_ring` token, the focus colour lifted to 3:1, as on the +//! buttons. +//! +//! ## Without the palette +//! +//! The button colours come from [`KdePalette`]. A theme without it paints +//! from its own roles: the content surface for the button colour, the +//! primary text for its text, `border` for its outline, `border_focused` +//! for the hover decorations. + +use teksilo_canvas::Rect; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::styles::Theme; +use teksilo_kde_scheme::math::{hsv_darker, lift_luma_until}; +use teksilo_kde_scheme::{ColorSet, ColorState, DecorationRole, KdeColor, PaletteRole}; +use teksilo_tokens::Color; + +use crate::color::{AA, ENHANCED, high_contrast_boundary, mark_on_faces, opaque_over}; +use crate::palette::KdePalette; +use crate::styles::checkbox::box_paint; +use crate::styles::metrics::{INDICATOR_BOX_INSET, INDICATOR_PRESSED_DARKER, INDICATOR_SIZE}; + +/// The colours a Breeze check box, radio button or switch is painted from, in +/// one paint state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeIndicatorColors { + /// `[qt]` The button colour: the face of a box, a disc and a switch's + /// handle. A translucent button colour is made opaque over the window + /// (`surface_main`), so a box is one opaque fill whatever lies behind + /// it and its mark is lifted on what is painted under it. + pub button: Color, + /// `[Hp]` The button colour darkened 110 %, a pressed box's face. + pub button_pressed: Color, + /// `[qt]` The button text, which a switch's track is tinted toward. + pub button_text: Color, + /// `[Hp]` The button colour moved toward the button text by the frame + /// contrast: a switch handle's outline at rest. + pub button_outline: Color, + /// `[Qd]` The button set's hover decoration: a hovered or focused switch + /// handle's outline, and an off track's. Under high contrast it is + /// lifted to 4.5:1 on the control surfaces and the button colour, as it + /// stands in for the track's lifted outline. + pub button_hover: Color, + /// `[Hp]` The view's text: the check mark, the partial squares and the + /// radio dot on a box or disc at rest. + pub text: Color, + /// The same mark on a pressed box or disc, whose face is darker. + pub text_pressed: Color, + /// `[Hp]` The current window's highlight (the muted one in a backdrop + /// window, the disabled one for a disabled control): a checked box's + /// outline and wash, a checked track. + pub highlight: Color, + /// `[wcag]` D6: the outline of an unchecked box, an unselected disc and + /// an unchecked track, `border_strong`; `border_disabled` when disabled. + pub outline: Color, + /// `[wcag]` D6: the outline of a checked track, the highlight lifted in + /// luma to the non-text floor (3:1, 4.5:1 under high contrast) on the + /// window, the view, the frame background and the button colour; the + /// disabled highlight, unlifted, when disabled. + pub checked_outline: Color, + /// `[Hp]` The view's focus decoration, the ring a hovered box or disc + /// adds to its outline, stroked over it. Under high contrast it is + /// lifted to 4.5:1 on the control surfaces and the button colour, as it + /// covers the lifted outline. + pub focus: Color, + /// `[wcag]` D5: the keyboard focus frame, `focus_ring`. + pub focus_ring: Color, +} + +impl BreezeIndicatorColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference, for an enabled + /// or a disabled control. + pub fn resolve( + theme: &Theme, + window_active: bool, + high_contrast: bool, + disabled: bool, + ) -> BreezeIndicatorColors { + let mut colors = Self::resolve_faces(theme, window_active, high_contrast, disabled); + if !disabled { + let unlifted = colors; + colors.text = mark_on_marked_face(&unlifted, false, high_contrast); + colors.text_pressed = mark_on_marked_face(&unlifted, true, high_contrast); + } + colors + } + + /// [`resolve`](Self::resolve) before the mark is lifted on the faces. + fn resolve_faces( + theme: &Theme, + window_active: bool, + high_contrast: bool, + disabled: bool, + ) -> BreezeIndicatorColors { + let c = &theme.colors; + let highlight = if disabled { + c.accent_disabled + } else { + c.accent + }; + let outline = if disabled { + c.border_disabled + } else { + c.border_strong + }; + let Some(palette) = theme.extension::() else { + let (button, button_text, button_outline, text) = if disabled { + ( + c.surface_disabled, + c.text_disabled, + c.border_disabled, + c.text_disabled, + ) + } else { + (c.surface_content, c.text_primary, c.border, c.editor_fg) + }; + let button = opaque_over(button, c.surface_main); + let hover = high_contrast_boundary(c.border_focused, c, button, high_contrast); + return BreezeIndicatorColors { + button, + button_pressed: darker(button), + button_text, + button_outline, + button_hover: hover, + text, + text_pressed: text, + highlight, + outline, + checked_outline: checked_outline(theme, highlight, button, high_contrast, disabled), + focus: hover, + focus_ring: c.focus_ring, + }; + }; + let state = if disabled { + ColorState::Disabled + } else if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let b = palette.breeze_colors(state, high_contrast); + let button = opaque_over(b.button, c.surface_main); + // The view's text: the projected token, which the high-contrast + // projection lifts, or the disabled view text. + let text = if disabled { + palette.role(ColorState::Disabled, PaletteRole::Text) + } else { + c.editor_fg + }; + BreezeIndicatorColors { + button, + button_pressed: darker(button), + button_text: b.button_text, + button_outline: b.button_outline, + button_hover: high_contrast_boundary( + palette + .resolved() + .set(state, ColorSet::Button) + .decoration(DecorationRole::Hover) + .to_color(), + c, + button, + high_contrast, + ), + text, + text_pressed: text, + highlight, + outline, + checked_outline: checked_outline(theme, highlight, button, high_contrast, disabled), + focus: high_contrast_boundary(b.focus, c, button, high_contrast), + focus_ring: c.focus_ring, + } + } +} + +/// `[wcag]` The mark of `colors` (the check mark, the partial squares and +/// the radio dot, in the view's text) on a marked box or disc, pressed or +/// not, lifted to the non-text floor, 3:1 or 4.5:1 under high contrast, on +/// its face: the highlight washed over the button colour as it is painted, +/// one opaque fill, since [`BreezeIndicatorColors::button`] is opaque. +/// The view's text reaches the text floor on the view, not on this face: a +/// scheme whose inactive effects move the button colour or the highlight +/// toward the text would otherwise leave the mark unreadable. Stock +/// Breeze's mark is kept. +fn mark_on_marked_face( + colors: &BreezeIndicatorColors, + pressed: bool, + high_contrast: bool, +) -> Color { + let paint = box_paint(true, false, pressed, false, colors); + let face = paint.composited_fill().unwrap_or(paint.face); + mark_on_faces(colors.text, &[face], high_contrast) +} + +/// `[wcag]` D6: `highlight` lifted in luma until it reaches the non-text +/// floor on the control surfaces of `theme` and on `button`, the face of the +/// handle the outline meets; `highlight` itself when `disabled`, which +/// SC 1.4.11 exempts. +fn checked_outline( + theme: &Theme, + highlight: Color, + button: Color, + high_contrast: bool, + disabled: bool, +) -> Color { + if disabled { + return highlight; + } + let c = &theme.colors; + let floor = if high_contrast { ENHANCED } else { AA }.non_text; + let on = + [c.surface_main, c.surface_content, c.surface_raised, button].map(KdeColor::from_color); + lift_luma_until(KdeColor::from_color(highlight), &on, floor).to_color() +} + +/// KDE's darkening of a pressed indicator's button colour. +fn darker(c: Color) -> Color { + hsv_darker(KdeColor::from_color(c), INDICATOR_PRESSED_DARKER).to_color() +} + +/// `[Hp]` The 20 px indicator square inside a body laid out at `bounds`: +/// against the body's leading edge, centred vertically. +/// +/// The body can be narrower than the indicator (see +/// [`indicator_body_width`](crate::styles::metrics::indicator_body_width)), +/// so the indicator is anchored to the leading edge in either direction and +/// what does not fit lies past the trailing one. +pub fn indicator_rect(bounds: Rect, direction: LayoutDirection) -> Rect { + let x = match direction { + LayoutDirection::LeftToRight => bounds.x, + LayoutDirection::RightToLeft => bounds.x + bounds.width - INDICATOR_SIZE, + }; + Rect::new( + x, + bounds.y + (bounds.height - INDICATOR_SIZE) * 0.5, + INDICATOR_SIZE, + INDICATOR_SIZE, + ) +} + +/// `[Hp]` The box (or the disc's square) of an indicator: the indicator +/// inset by 2, 16 x 16. +pub fn box_rect(indicator: Rect) -> Rect { + inset(indicator, INDICATOR_BOX_INSET) +} + +/// `[Hp]` The rectangle the box is filled and stroked on: the box inset by +/// half the `pen`, 15 x 15 at 2.5 for the 1 px pen. +pub fn stroked_box_rect(indicator: Rect, pen: f32) -> Rect { + inset(box_rect(indicator), pen * 0.5) +} + +fn inset(r: Rect, d: f32) -> Rect { + Rect::new( + r.x + d, + r.y + d, + (r.width - 2.0 * d).max(0.0), + (r.height - 2.0 * d).max(0.0), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + #[test] + fn the_indicator_hugs_the_leading_edge_in_either_direction() { + let body = Rect::new(10.0, 0.0, 18.0, 24.0); + let ltr = indicator_rect(body, LayoutDirection::LeftToRight); + assert_eq!(ltr, Rect::new(10.0, 2.0, 20.0, 20.0)); + let rtl = indicator_rect(body, LayoutDirection::RightToLeft); + assert_eq!(rtl, Rect::new(8.0, 2.0, 20.0, 20.0)); + // The box lies in the body either way. + for r in [ltr, rtl] { + let b = box_rect(r); + assert!(b.x >= body.x && b.x + b.width <= body.x + body.width); + } + } + + #[test] + fn the_box_is_fifteen_pixels_at_two_and_a_half() { + let i = Rect::new(0.0, 0.0, 20.0, 20.0); + assert_eq!(box_rect(i), Rect::new(2.0, 2.0, 16.0, 16.0)); + assert_eq!(stroked_box_rect(i, 1.0), Rect::new(2.5, 2.5, 15.0, 15.0)); + } + + /// The Breeze Light colours, against Plasma's painted samples. + #[test] + fn breeze_light_paints_the_specifications_colours() { + let t = crate::light(); + let c = BreezeIndicatorColors::resolve(&t, true, false, false); + assert_eq!(rgba8(c.button), [252, 252, 252, 255]); + // 252 darkened 110 % is 229, Plasma's pressed box. + assert_eq!(rgba8(c.button_pressed), [229, 229, 229, 255]); + assert_eq!(rgba8(c.text), [35, 38, 41, 255]); + assert_eq!(rgba8(c.highlight), [61, 174, 233, 255]); + assert_eq!(rgba8(c.focus), [61, 174, 233, 255]); + assert_eq!(rgba8(c.button_hover), [61, 174, 233, 255]); + assert_eq!(c.outline, t.colors.border_strong); + assert_eq!(c.focus_ring, t.colors.focus_ring); + } + + #[test] + fn the_empty_outline_is_lifted_and_its_premise_holds() { + let t = crate::light(); + let p = t.extension::().unwrap(); + let separator = p.colors(ColorState::Active).separator; + let window = t.colors.surface_main; + assert!( + separator.contrast_ratio(window) < 3.0, + "premise: Breeze's own outline falls short" + ); + let c = BreezeIndicatorColors::resolve(&t, true, false, false); + assert!(c.outline.contrast_ratio(window) >= 3.0); + assert!(c.outline.contrast_ratio(c.button) >= 3.0); + } + + /// D6 for a checked switch: Breeze Light's highlight falls short of 3:1 + /// on its window, so the checked track is outlined in the highlight + /// lifted to 3:1 on every control surface and on the handle's face. + #[test] + fn the_checked_outline_is_lifted_and_its_premise_holds() { + let t = crate::light(); + let c = BreezeIndicatorColors::resolve(&t, true, false, false); + let window = t.colors.surface_main; + assert!( + c.highlight.contrast_ratio(window) < 3.0, + "premise: Breeze's own checked outline falls short" + ); + for on in [ + window, + t.colors.surface_content, + t.colors.surface_raised, + c.button, + ] { + assert!(c.checked_outline.contrast_ratio(on) >= 3.0, "{on:?}"); + } + assert_ne!(c.checked_outline, c.highlight); + } + + /// The lift is a rule: a highlight that already reaches 3:1 is kept + /// exactly, a disabled control keeps the disabled highlight, and the + /// high-contrast projection raises the floor to 4.5:1. + #[test] + fn the_checked_outline_follows_the_floor_of_each_projection() { + let dark = crate::dark(); + let c = BreezeIndicatorColors::resolve(&dark, true, false, false); + assert!(c.highlight.contrast_ratio(dark.colors.surface_main) >= 3.0); + assert_eq!(c.checked_outline, c.highlight, "Breeze Dark passes"); + + let light = crate::light(); + let disabled = BreezeIndicatorColors::resolve(&light, true, false, true); + assert_eq!(disabled.checked_outline, light.colors.accent_disabled); + + for t in [crate::light(), crate::dark()] { + let hc = t.for_paint_state(teksilo_core::styles::PaintState { + window_active: true, + high_contrast: true, + }); + let c = BreezeIndicatorColors::resolve(&hc, true, true, false); + for on in [hc.colors.surface_main, hc.colors.surface_content, c.button] { + assert!(c.checked_outline.contrast_ratio(on) >= 4.5, "{on:?}"); + } + } + + // Without the palette the rule reads the theme's own roles. + let t = teksilo_core::presets::intui::light(); + let c = BreezeIndicatorColors::resolve(&t, true, false, false); + assert!(c.checked_outline.contrast_ratio(t.colors.surface_main) >= 3.0); + } + + #[test] + fn a_backdrop_window_checks_in_its_muted_highlight() { + let t = crate::light(); + let inactive = t.for_paint_state(teksilo_core::styles::PaintState { + window_active: false, + high_contrast: false, + }); + let c = BreezeIndicatorColors::resolve(&inactive, false, false, false); + assert_eq!(c.highlight, inactive.colors.accent); + assert_ne!(c.highlight, t.colors.accent, "the current window's"); + } + + #[test] + fn a_disabled_indicator_paints_the_disabled_colours() { + let t = crate::light(); + let p = t.extension::().unwrap(); + let c = BreezeIndicatorColors::resolve(&t, true, false, true); + assert_eq!(c.button, p.role(ColorState::Disabled, PaletteRole::Button)); + assert_eq!(c.text, p.role(ColorState::Disabled, PaletteRole::Text)); + assert_eq!(c.highlight, t.colors.accent_disabled); + assert_eq!(c.outline, t.colors.border_disabled); + } + + /// A scheme may give the Button background an alpha, which D11 keeps. + /// The box is then still one opaque fill, the button colour composited + /// over the window with the wash over that, and the mark reaches the + /// floor on it: Breeze Dark with the Button background at alpha 0.8 + /// and Inactive Lighten 0.4 left the mark 4.39:1 on the box as painted + /// under high contrast, lifted against the bare face instead. + #[test] + fn a_translucent_button_colour_paints_one_opaque_box_its_mark_reads_on() { + use teksilo_kde_scheme::{ColorSet, IntensityEffect, KdeColorScheme, StateEffect}; + let mut scheme = KdeColorScheme::breeze_dark(); + scheme.sets[ColorSet::Button.index()].background_normal = KdeColor::rgba8(41, 44, 48, 204); + scheme.inactive_effect = StateEffect { + enabled: true, + intensity_effect: IntensityEffect::Lighten, + intensity_amount: 0.4, + ..StateEffect::INACTIVE_DEFAULT + }; + let theme = crate::from_scheme(&scheme); + let hc = theme.for_paint_state(teksilo_core::styles::PaintState { + window_active: false, + high_contrast: true, + }); + let p = hc.extension::().unwrap(); + assert!( + p.breeze_colors(ColorState::Inactive, true).button.a() < 1.0, + "premise: the carried button colour keeps the scheme's alpha" + ); + let c = BreezeIndicatorColors::resolve(&hc, false, true, false); + assert_eq!(c.button.a(), 1.0); + assert_eq!(c.button_pressed.a(), 1.0); + for pressed in [false, true] { + let paint = box_paint(true, false, pressed, false, &c); + let fill = paint.composited_fill().expect("one opaque fill"); + let ratio = paint.mark.contrast_ratio(fill); + assert!(ratio >= 4.5, "pressed {pressed}: {ratio:.2}:1"); + } + } + + #[test] + fn a_theme_without_the_palette_paints_from_its_roles() { + let t = teksilo_core::presets::intui::light(); + let c = BreezeIndicatorColors::resolve(&t, true, false, false); + assert_eq!(c.button, t.colors.surface_content); + assert_eq!(c.button_outline, t.colors.border); + assert_eq!(c.text, t.colors.editor_fg); + assert_eq!(c.highlight, t.colors.accent); + assert_eq!(c.outline, t.colors.border_strong); + assert_eq!(c.focus, t.colors.border_focused); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/menu_item.rs b/crates/teksilo-theme-kde/src/styles/menu_item.rs new file mode 100644 index 000000000..026db1725 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/menu_item.rs @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze menu item. +//! +//! A row is transparent at rest. Hovered or reached with the arrow keys it +//! is washed with the focus colour at 30 % and outlined in the focus colour +//! moved 15 % toward the window text, a 1 px outline stroked on the row +//! inset by half the pen at radius 4.5; held down it is filled with the +//! solid colour and its label and shortcut turn to the text on it +//! ([`MenuItemStyle::pressed_label_role`]), while a highlighted label stays +//! the window text. The highlight covers the whole row, which `MenuList` +//! already places 4 px inside the menu's frame, Breeze's menu margin. A +//! disabled row is never highlighted. +//! +//! The row is 29 px at Plasma's default font: `max(H, 20, 16) + 1` and the +//! 4 px item margin above and below, through the density ladder. Its layout +//! is Breeze's for a menu that shows icons, because `MenuItem` reserves its +//! leading column on every row: the icon column 12 px inside the row (the +//! 4 px margin and the 8 px text margin), 16 px wide, the text 3 px after +//! it, and at the trailing edge a 20 px arrow box 4 px inside the row with +//! the text ending 3 px before it. A separator is 5 px, its 1 px line in the +//! middle. The row arithmetic is the shipped [`RecipeMenuItemStyle`]'s, +//! built with these numbers ([`breeze_menu_item_recipe_for`]); its own +//! highlight is held off, so the one painted here is the only one. +//! +//! The style stores the recipe it was built with from the theme's input +//! tokens and typography, so [`MenuItemStyle::metrics`], which `MenuItem` +//! and `MenuSeparator` read for the parts they build themselves, follows +//! the density the theme was projected to. The `H` of the row is the line +//! box of the desktop's menu font when the theme carries one taller than +//! the body style ([`menu_row_typography`]). +//! +//! ## Where this departs from Breeze +//! +//! - **D5, the highlight outline.** The outline marks the row the keyboard +//! is on, so it is the menu's focus indicator. Breeze Light's, 57,154,204 +//! (its focus colour 61,174,233 moved toward the window text), is 2.9:1 +//! on the menu's frame background and 2.8:1 on the window; it is lifted in +//! luma, keeping its hue, until it reaches SC 1.4.11's 3:1 (4.5:1 under +//! high contrast) on the window, the view and the frame background +//! ([`BreezeMenuColors::outline`]). Breeze Dark's passes and is kept. +//! - **D1, the pressed fill.** Breeze fills a pressed row with the focus +//! colour and writes its text in the selection text. Teksilo's selection +//! text is chosen against the accent (D1 of the colour mapping), so the +//! pressed row is filled and outlined with `accent`, which the label's +//! [`TextRole::OnAccent`] is readable on. In the stock Breeze schemes the +//! focus colour and the accent are the same colour; they part when an +//! accent colour or the high-contrast projection changes the accent. +//! +//! What `MenuItem`, `MenuSeparator` and `MenuList` build themselves is +//! theirs, and the crate's known limitations list where it differs from +//! Breeze: the shortcut's colour, the separator's length, section titles, +//! a menu without icons and the check mark. + +use teksilo_canvas::{Canvas, Rect, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{MenuItemMetrics, MenuItemStyle, MenuItemStyleConfig, Theme}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::math::lift_luma_until; +use teksilo_kde_scheme::{ColorState, KdeColor}; +use teksilo_tokens::{Color, CornerRadius, InputTokens, TextRole, TextStyle, TypographyTokens}; +use teksilo_widgets::primitives::{MinSize, ZStack}; +use teksilo_widgets::styles::{MenuItemRecipe, RecipeMenuItemStyle}; + +use crate::color::{AA, ENHANCED}; +use crate::palette::KdePalette; +use crate::shape::BREEZE_STROKED_FRAME_RADIUS; +use crate::styles::metrics::{MENU_HIGHLIGHT_ALPHA, breeze_menu_metrics_for}; +use crate::typography::KdeFontRoles; + +/// The Breeze [`MenuItemRecipe`] at the density of `tokens`, for +/// `typography`: the row arithmetic of [`RecipeMenuItemStyle`] with the +/// dimensions of [`crate::styles::metrics::BreezeMenuMetrics`]. +pub fn breeze_menu_item_recipe_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> MenuItemRecipe { + let m = breeze_menu_metrics_for(tokens, typography); + MenuItemRecipe { + item_height: m.item_height, + // The trailing column is the row's right-hand padding: the shortcut + // and the arrow live in it. + padding_horizontal: m.trailing_column, + padding_leading: m.padding_leading, + icon_column_width: m.icon_column, + icon_label_gap: m.icon_label_gap, + shortcut_left_gap: m.shortcut_gap, + separator_height: m.separator_height, + // `[Hp]` Only the radius of the recipe's own highlight, which this + // style holds off. + item_corner_radius: BREEZE_STROKED_FRAME_RADIUS, + } +} + +/// [`breeze_menu_item_recipe_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_menu_item_recipe() -> MenuItemRecipe { + breeze_menu_item_recipe_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// Breeze `MenuItemStyle`. +/// +/// [`Default`] is the Compact density and the Breeze typography; the preset +/// installs [`BreezeMenuItemStyle::for_theme`] of the theme it builds. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeMenuItemStyle { + /// The row dimensions this style lays out and reports. + pub recipe: MenuItemRecipe, +} + +impl BreezeMenuItemStyle { + /// The style with `recipe`'s dimensions. + pub fn new(recipe: MenuItemRecipe) -> Self { + Self { recipe } + } + + /// The style at the density and typography of `theme`, its rows sized + /// for the theme's menu font ([`menu_row_typography`]). + pub fn for_theme(theme: &Theme) -> Self { + Self::new(breeze_menu_item_recipe_for( + &theme.input, + &menu_row_typography(theme), + )) + } +} + +/// The typography a menu row of `theme` is sized for. +/// +/// `[qt]` A Qt menu lays its rows out in the desktop's menu font, which +/// [`KdeFontRoles::menu`] carries; `[derived]` `MenuItem` writes its label +/// in the body style, so the row is sized for whichever of the two has the +/// taller line box, and the label always fits it. A theme without +/// [`KdeFontRoles`] sizes its rows for its body style. +pub fn menu_row_typography(theme: &Theme) -> TypographyTokens { + let mut typography = theme.typography.clone(); + if let Some(roles) = theme.extension::() { + let line_box = |s: &TextStyle| s.size * s.line_height; + if line_box(&roles.menu) > line_box(&typography.body) { + typography.body = roles.menu.clone(); + } + } + typography +} + +impl Default for BreezeMenuItemStyle { + fn default() -> Self { + Self::new(breeze_menu_item_recipe()) + } +} + +impl MenuItemStyle for BreezeMenuItemStyle { + fn make_body(&self, cfg: &MenuItemStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let state = MenuRowState::derive( + &cfg.is_disabled, + &cfg.is_pressed, + &cfg.is_hovered, + &cfg.is_highlighted, + ); + let highlight = ctx.add(BreezeMenuHighlight { state }); + // The recipe's row, with its interaction signals held low so its own + // highlight never paints under this one. + let quiet = Signal::new(false); + let inner = MenuItemStyleConfig { + label: cfg.label, + leading: cfg.leading, + trailing: cfg.trailing, + is_hovered: quiet.clone(), + is_pressed: quiet.clone(), + is_focused: cfg.is_focused.clone(), + is_disabled: cfg.is_disabled.clone(), + is_highlighted: quiet, + }; + let row = RecipeMenuItemStyle::new(self.recipe).make_body(&inner, ctx); + let stack = ctx.add(ZStack::new().child(highlight).child(row)); + // `[S]` The row is at least its height whatever its label measures: + // Breeze's rule takes the larger of the line box and 20 px. + ctx.add(MinSize::new(0.0, self.recipe.item_height).child(stack)) + } + + fn metrics(&self) -> MenuItemMetrics { + RecipeMenuItemStyle::new(self.recipe).metrics() + } + + /// `[wcag]` D1: the text on the pressed row's accent fill. Breeze writes + /// a highlighted row in the window text, which the 30 % wash keeps + /// readable, so [`MenuItemStyle::highlighted_label_role`] stays `None`. + fn pressed_label_role(&self) -> Option { + Some(TextRole::OnAccent) + } +} + +/// A menu row's interaction state, in Breeze's precedence order. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum MenuRowState { + /// Neither hovered nor reached by the keyboard. + #[default] + Rest, + /// Hovered, reached with the arrow keys, or a submenu trigger whose + /// submenu is open. + Highlighted, + /// Held down. + Pressed, + /// Disabled: never highlighted. + Disabled, +} + +impl MenuRowState { + /// Collapse the style configuration's booleans into a state: disabled + /// first, then pressed, then hovered or highlighted. + pub fn derive( + is_disabled: &Signal, + is_pressed: &Signal, + is_hovered: &Signal, + is_highlighted: &Signal, + ) -> Signal { + is_disabled + .zip3(is_pressed, is_hovered) + .zip(is_highlighted) + .map(|((disabled, pressed, hovered), highlighted)| { + if *disabled { + MenuRowState::Disabled + } else if *pressed { + MenuRowState::Pressed + } else if *hovered || *highlighted { + MenuRowState::Highlighted + } else { + MenuRowState::Rest + } + }) + } +} + +/// The colours a Breeze menu row is highlighted in, in one paint state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeMenuColors { + /// `[Hp]` The view's focus decoration of the current window state, which + /// washes a highlighted row at 30 %. + pub focus: Color, + /// `[wcag]` D5: a highlighted row's outline, the focus colour moved 15 % + /// toward the window text, lifted in luma to the non-text floor on the + /// window, the view and the frame background. + pub outline: Color, + /// `[wcag]` D1: a pressed row's fill and outline, `accent`. + pub pressed: Color, +} + +impl BreezeMenuColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference. + /// + /// `theme` is the theme the paint walker hands a widget, already + /// projected for that state. Without a [`KdePalette`] the focus colour + /// and the outline are `border_focused`. + pub fn resolve(theme: &Theme, window_active: bool, high_contrast: bool) -> BreezeMenuColors { + let c = &theme.colors; + let Some(palette) = theme.extension::() else { + return BreezeMenuColors { + focus: c.border_focused, + outline: c.border_focused, + pressed: c.accent, + }; + }; + let state = if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let b = palette.breeze_colors(state, high_contrast); + let floor = if high_contrast { ENHANCED } else { AA }.non_text; + let on = [c.surface_main, c.surface_content, c.surface_raised].map(KdeColor::from_color); + BreezeMenuColors { + focus: b.focus, + outline: lift_luma_until(KdeColor::from_color(b.focus_outline), &on, floor).to_color(), + pressed: c.accent, + } + } + + /// `[Hp]` A highlighted row's wash: the focus colour at 30 %. + pub fn wash(&self) -> Color { + self.focus.with_alpha(MENU_HIGHLIGHT_ALPHA) + } +} + +/// What a menu row paints in one state: a fill and a 1 px outline, each +/// optional. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct MenuRowPaint { + /// The fill of the row, or nothing. + pub fill: Option, + /// The outline of the row, or nothing. + pub outline: Option, +} + +/// `[Hp]` What a row paints in `state`, from `c`. +pub fn menu_row_paint(state: MenuRowState, c: &BreezeMenuColors) -> MenuRowPaint { + match state { + MenuRowState::Rest | MenuRowState::Disabled => MenuRowPaint { + fill: None, + outline: None, + }, + MenuRowState::Highlighted => MenuRowPaint { + fill: Some(c.wash()), + outline: Some(c.outline), + }, + MenuRowState::Pressed => MenuRowPaint { + fill: Some(c.pressed), + outline: Some(c.pressed), + }, + } +} + +/// `[Hp]` The rectangle a row's highlight is filled and stroked on: the row +/// inset by half the `pen`, so the outline lies inside the row. +pub fn highlight_rect(bounds: Rect, pen: f32) -> Rect { + let d = pen * 0.5; + Rect::new( + bounds.x + d, + bounds.y + d, + (bounds.width - 2.0 * d).max(0.0), + (bounds.height - 2.0 * d).max(0.0), + ) +} + +/// The highlight layer of a Breeze menu row, painted behind the row's +/// content. +/// +/// Sized like `RectWidget`: it takes whatever the proposal offers and +/// reports zero when unbounded, so it does not change the row's size. +struct BreezeMenuHighlight { + state: Signal, +} + +impl std::fmt::Debug for BreezeMenuHighlight { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeMenuHighlight").finish() + } +} + +impl Widget for BreezeMenuHighlight { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + self.state + .bind_to(id, ctx.binding_registry(), BindingLevel::RepaintOnly); + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let colors = + BreezeMenuColors::resolve(ctx.theme, ctx.window_active, ctx.prefers_high_contrast); + let paint = menu_row_paint(self.state.get(), &colors); + paint_menu_row(canvas, bounds, &paint, ctx.theme); + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the `MenuItem` emits the real node. + builder.set_hidden(); + } +} + +/// Paint `paint` into the row `bounds`: the fill, then the outline, both on +/// [`highlight_rect`] at radius 4.5. +pub fn paint_menu_row(canvas: &mut Canvas, bounds: Rect, paint: &MenuRowPaint, theme: &Theme) { + let pen = theme.shape.border_width; + let rect = highlight_rect(bounds, pen); + let corner = CornerRadius::uniform(crate::styles::chrome::frame_radius(&theme.shape)); + if let Some(fill) = paint.fill + && fill.a() > 0.0 + { + canvas.fill_rounded_rect(rect, corner, fill); + } + if let Some(outline) = paint.outline + && outline.a() > 0.0 + && pen > 0.0 + { + canvas.stroke_rounded_rect(rect, corner, outline, pen); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_core::styles::PaintState; + use teksilo_tokens::TargetDensity; + use teksilo_tokens::contrast::composite_contrast; + + use crate::PlasmaAccent; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + fn colors(theme: &Theme) -> BreezeMenuColors { + BreezeMenuColors::resolve(theme, true, false) + } + + #[test] + fn the_recipe_carries_breezes_menu_metrics() { + let r = breeze_menu_item_recipe(); + assert_eq!(r.item_height, 29.0); + assert_eq!(r.separator_height, 5.0); + assert_eq!(r.padding_leading, 12.0); + assert_eq!(r.icon_column_width, 16.0); + assert_eq!(r.icon_label_gap, 3.0); + assert_eq!(r.padding_horizontal, 27.0); + assert_eq!(r.shortcut_left_gap, 16.0); + assert_eq!(r.item_corner_radius, 4.5); + assert_eq!(BreezeMenuItemStyle::default().recipe, r); + assert_eq!(BreezeMenuItemStyle::for_theme(&crate::light()).recipe, r); + } + + /// The metrics `MenuItem` and `MenuSeparator` read are the recipe's. + #[test] + fn the_metrics_are_the_recipes_at_every_density() { + for (density, item, separator) in [ + (TargetDensity::Compact, 29.0, 5.0), + (TargetDensity::Comfortable, 32.0, 5.75), + (TargetDensity::Touch, 44.0, 6.5), + ] { + let theme = crate::light().with_density(density); + let m = BreezeMenuItemStyle::for_theme(&theme).metrics(); + assert_eq!(m.item_height, item, "{density:?}"); + assert!((m.separator_height - separator).abs() < 1e-4, "{density:?}"); + assert_eq!(m.icon_column_width, 16.0); + assert!(m.trailing_column_width >= 27.0); + } + } + + #[test] + fn the_state_follows_breezes_precedence() { + let disabled = Signal::new(false); + let pressed = Signal::new(false); + let hovered = Signal::new(false); + let highlighted = Signal::new(false); + let state = MenuRowState::derive(&disabled, &pressed, &hovered, &highlighted); + assert_eq!(state.get(), MenuRowState::Rest); + highlighted.set(true); + assert_eq!( + state.get(), + MenuRowState::Highlighted, + "the keyboard lights it" + ); + highlighted.set(false); + hovered.set(true); + assert_eq!( + state.get(), + MenuRowState::Highlighted, + "so does the pointer" + ); + pressed.set(true); + assert_eq!(state.get(), MenuRowState::Pressed); + disabled.set(true); + assert_eq!(state.get(), MenuRowState::Disabled, "disabled beats all"); + } + + #[test] + fn only_a_highlighted_or_pressed_row_paints() { + let c = colors(&crate::light()); + let nothing = MenuRowPaint { + fill: None, + outline: None, + }; + assert_eq!(menu_row_paint(MenuRowState::Rest, &c), nothing); + assert_eq!(menu_row_paint(MenuRowState::Disabled, &c), nothing); + let lit = menu_row_paint(MenuRowState::Highlighted, &c); + assert_eq!(lit.fill, Some(c.wash())); + assert_eq!(lit.outline, Some(c.outline)); + let held = menu_row_paint(MenuRowState::Pressed, &c); + assert_eq!(held.fill, Some(c.pressed)); + assert_eq!(held.outline, Some(c.pressed)); + } + + /// Breeze Light's and Dark's highlight, against Plasma's pixels: the + /// wash over the window is 185,220,238 and 40,76,97; the outline is + /// 57,154,204 and 90,186,236. + #[test] + fn the_wash_and_outline_are_breezes() { + let over = |top: Color, bottom: Color| { + let a = top.a(); + let ch = |t: f32, b: f32| t * a + b * (1.0 - a); + Color::new( + ch(top.r(), bottom.r()), + ch(top.g(), bottom.g()), + ch(top.b(), bottom.b()), + 1.0, + ) + }; + let light = crate::light(); + let c = colors(&light); + assert_eq!(rgba8(c.focus), [61, 174, 233, 255]); + let near = |a: [u8; 4], b: [u8; 4]| a.iter().zip(b).all(|(x, y)| x.abs_diff(y) <= 1); + assert!(near( + rgba8(over(c.wash(), light.colors.surface_main)), + [185, 220, 238, 255] + )); + let dark = crate::dark(); + let d = colors(&dark); + assert!(near( + rgba8(over(d.wash(), dark.colors.surface_main)), + [40, 76, 97, 255] + )); + // Breeze Dark's outline reaches 3:1 and is Breeze's own. + assert_eq!(rgba8(d.outline), [90, 186, 236, 255]); + assert_eq!(rgba8(c.pressed), rgba8(light.colors.accent)); + } + + /// D5: Breeze Light's outline is lifted to 3:1 on the menu's backdrops, + /// keeping its hue, and its own outline does not reach it. + #[test] + fn the_highlight_outline_is_lifted_to_three_to_one() { + let t = crate::light(); + let palette = t.extension::().unwrap(); + let vendor = palette.colors(ColorState::Active).focus_outline; + assert_eq!(rgba8(vendor), [57, 154, 204, 255]); + let c = &t.colors; + let backdrops = [c.surface_main, c.surface_content, c.surface_raised]; + let worst = |x: Color| { + backdrops + .iter() + .map(|b| composite_contrast(x, *b)) + .fold(f32::INFINITY, f32::min) + }; + assert!(worst(vendor) < 3.0, "premise: {:.2}:1", worst(vendor)); + let lifted = colors(&t).outline; + assert!(worst(lifted) >= 3.0, "{:.2}:1", worst(lifted)); + let (h0, _, _) = vendor.to_hsv(); + let (h1, _, _) = lifted.to_hsv(); + assert!((h0 - h1).abs() < 2.0, "the hue moved from {h0} to {h1}"); + // Under high contrast the floor is 4.5:1. + let hc = t.for_paint_state(PaintState { + window_active: true, + high_contrast: true, + }); + let hc_colors = BreezeMenuColors::resolve(&hc, true, true); + let hc_backdrops = [ + hc.colors.surface_main, + hc.colors.surface_content, + hc.colors.surface_raised, + ]; + for b in hc_backdrops { + assert!(composite_contrast(hc_colors.outline, b) >= 4.5); + } + } + + /// With every Plasma accent, in both schemes and both window states, the + /// window text stays readable on the wash and the pressed label on the + /// pressed fill, and the outline reaches 3:1. + #[test] + fn every_swatch_keeps_the_rows_readable() { + for accent in PlasmaAccent::ALL { + for base in [ + crate::light_with_accent(accent.color()), + crate::dark_with_accent(accent.color()), + ] { + for window_active in [true, false] { + let t = base.for_paint_state(PaintState { + window_active, + high_contrast: false, + }); + let c = BreezeMenuColors::resolve(&t, window_active, false); + let panel = t.colors.surface_raised; + let wash = { + let w = c.wash(); + let a = w.a(); + Color::new( + w.r() * a + panel.r() * (1.0 - a), + w.g() * a + panel.g() * (1.0 - a), + w.b() * a + panel.b() * (1.0 - a), + 1.0, + ) + }; + assert!( + composite_contrast(t.colors.text_primary, wash) >= 4.5, + "{accent:?}: the label on the wash" + ); + assert!( + composite_contrast(TextRole::OnAccent.resolve(&t.colors), c.pressed) >= 4.5, + "{accent:?}: the pressed label" + ); + assert!(composite_contrast(c.outline, panel) >= 3.0); + } + } + } + } + + #[test] + fn the_rows_degrade_without_the_palette() { + let mut bare = crate::light(); + bare.extensions.remove::(); + let c = colors(&bare); + assert_eq!(c.focus, bare.colors.border_focused); + assert_eq!(c.outline, bare.colors.border_focused); + assert_eq!(c.pressed, bare.colors.accent); + } + + #[test] + fn the_highlight_is_stroked_inside_the_row() { + let r = highlight_rect(Rect::new(4.0, 4.0, 200.0, 29.0), 1.0); + assert_eq!(r, Rect::new(4.5, 4.5, 199.0, 28.0)); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/metrics.rs b/crates/teksilo-theme-kde/src/styles/metrics.rs new file mode 100644 index 000000000..baac4ecc8 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/metrics.rs @@ -0,0 +1,1062 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The dimensions the Breeze widget styles are built with, at a density. +//! +//! Every height here starts from a Breeze rule in [`crate::geometry`]: the +//! text-driven ones from the theme's body line box, the icon-driven ones +//! from an icon size. A height of 24 or more is a target, so it goes through +//! the density ladder's `dp(.., Target)`, which leaves it alone at Compact +//! and raises it to the density's target size above it (`[unpublished]`: +//! KDE publishes no touch ladder, so the ladder is Teksilo's generic one). +//! A gap or padding goes through `spacing`. At Compact every value is +//! Breeze's own. + +use teksilo_core::styles::density::{dp, spacing}; +use teksilo_tokens::{InputTokens, TargetRole, TypographyTokens}; + +use crate::geometry::{ + BUTTON_MARGIN_WIDTH, BreezeGeometry, CHECKBOX_FOCUS_MARGIN, CHECKBOX_ITEM_SPACING, + CHECKBOX_SIZE, COMBO_BOX_FRAME_WIDTH, FRAME_WIDTH, HEADER_MARGIN, ITEM_VIEW_MARGIN_HEIGHT, + ITEM_VIEW_MARGIN_WIDTH, ITEM_VIEW_PADDING, LARGE_ICON_SIZE, LINE_EDIT_FRAME_WIDTH, + MEDIUM_ICON_SIZE, MENU_ITEM_ACCELERATOR_SPACE, MENU_ITEM_MARGIN, MENU_ITEM_TEXT_LEFT_MARGIN, + PROGRESS_BAR_THICKNESS, SCROLL_BAR_EXTENT, SCROLL_BAR_HANDLE_WIDTH, + SCROLL_BAR_MIN_HANDLE_LENGTH, SLIDER_CONTROL_THICKNESS, SLIDER_GROOVE_THICKNESS, + SLIDER_TICK_LENGTH, SLIDER_TICK_MARGIN, SMALL_ICON_SIZE, SPIN_BOX_ARROW_BUTTON_WIDTH, + SPIN_BOX_FRAME_WIDTH, TAB_MARGIN_WIDTH, TOOL_BUTTON_MARGIN_WIDTH, +}; +use crate::shape::BREEZE_PEN_WIDTH; + +/// `[M]` The square a check box or radio button indicator is painted in. +pub const INDICATOR_SIZE: f32 = CHECKBOX_SIZE; +/// `[Hp]` How far the box (or the radio disc) lies inside the indicator: the +/// indicator's focus margin. +pub const INDICATOR_BOX_INSET: f32 = CHECKBOX_FOCUS_MARGIN; +/// `[Hp]` The side of the box as stroked: the indicator inset by 2, then by +/// half the pen. 15. +pub const INDICATOR_BOX_STROKED: f32 = + INDICATOR_SIZE - 2.0 * INDICATOR_BOX_INSET - BREEZE_PEN_WIDTH; +/// `[M]` Where a check box's or radio button's label starts, from the +/// indicator's leading edge: the indicator and Breeze's 4 px label spacing. +pub const INDICATOR_LABEL_X: f32 = CHECKBOX_SIZE + CHECKBOX_ITEM_SPACING; +/// `[Hp]` The width of the pen a check mark is drawn with. +pub const CHECK_MARK_PEN: f32 = 2.0; +/// `[Hp]` The side of each of the three squares of a partial check box. +pub const PARTIAL_DOT_SIZE: f32 = 2.0; +/// `[Hp]` The distance between the centres of the partial squares. +pub const PARTIAL_DOT_SPACING: f32 = 4.0; +/// `[Hp]` The radius of a selected radio button's dot: `floor((18 - 12) / +/// 2)`, a 6 px dot. +pub const RADIO_DOT_RADIUS: f32 = 3.0; +/// `[Hp]` How much of the highlight fills a checked box or a selected disc +/// over the button colour. +pub const INDICATOR_CHECKED_WASH: f32 = 0.3; +/// `[Hp]` How much a pressed indicator's button colour is darkened, in Qt's +/// percent (d.11 of the specification). +pub const INDICATOR_PRESSED_DARKER: i32 = 110; + +/// `[K]` The height of a switch: Kirigami's grid unit. +pub const SWITCH_HEIGHT: f32 = 18.0; +/// `[Qd]` The width of a switch: twice its height. +pub const SWITCH_WIDTH: f32 = 2.0 * SWITCH_HEIGHT; +/// `[Qd]` How far the track lies inside the switch above and below: `floor(18 +/// / 6)`. +pub const SWITCH_TRACK_INSET: f32 = 3.0; +/// `[Qd]` The track's radius: `round(12 / 2)`. +pub const SWITCH_TRACK_RADIUS: f32 = 6.0; +/// `[Qd]` The handle's radius: `floor(18 / 2)`, a circle of the switch's +/// full height. +pub const SWITCH_HANDLE_RADIUS: f32 = 9.0; +/// `[Qd]` How much of the button text the track is filled with over the +/// button colour. +pub const SWITCH_TRACK_TEXT: f64 = 0.1; +/// `[Qd]` The alpha of the highlight that fills a checked track up to the +/// handle. +pub const SWITCH_CHECKED_ALPHA: f32 = 0.5; +/// `[qt]` The speed Qt's smoothed animation moves the handle at, at most, in +/// pixels a second: a full travel of 18 px takes 90 ms rather than the +/// 100 ms duration. +pub const SWITCH_HANDLE_VELOCITY: f32 = 200.0; + +// The switch is twice as wide as tall, its track centred inside it. +const _: () = assert!(SWITCH_WIDTH == 2.0 * SWITCH_HEIGHT); +const _: () = assert!(SWITCH_TRACK_RADIUS * 2.0 == SWITCH_HEIGHT - 2.0 * SWITCH_TRACK_INSET); +const _: () = assert!(SWITCH_HANDLE_RADIUS * 2.0 == SWITCH_HEIGHT); +// The box is the 15 px square of the specification. +const _: () = assert!(INDICATOR_BOX_STROKED == 15.0); + +/// The width the body of a check box or radio button is laid out at, when +/// the widget puts `label_gap` between it and its label: the label then +/// starts at [`INDICATOR_LABEL_X`], where Breeze puts it. +/// +/// With the widget's 6 px gap that is 18, the indicator less the trailing +/// 2 px of its focus margin; the box itself ends at 18, so it always lies in +/// the node, and only the focus frame reaches past it. Never narrower than +/// the box's far edge, so a larger gap moves the label rather than the box +/// out of the node. +pub fn indicator_body_width(label_gap: f32) -> f32 { + (INDICATOR_LABEL_X - label_gap).max(INDICATOR_SIZE - INDICATOR_BOX_INSET) +} + +/// The dimensions of a line edit, the frame a `TextInput` is drawn in. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeFieldMetrics { + /// `[S]` `[qt]` The field's height, at least: Qt's inner height + /// `max(H, 14) + 2` plus the 6 px frame above and below, through the + /// density ladder. 32 at Compact for the Breeze typography. + pub height: f32, + /// `[M]` The space between the frame and the text on the leading and + /// trailing sides: the 6 px line-edit frame, scaled by the density's + /// spacing. + pub padding_horizontal: f32, +} + +/// The line-edit dimensions at the density of `tokens`, for `typography`. +pub fn breeze_field_metrics_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> BreezeFieldMetrics { + let g = geometry_of(typography); + BreezeFieldMetrics { + height: dp(g.line_edit_height, TargetRole::Target, tokens), + padding_horizontal: spacing(LINE_EDIT_FRAME_WIDTH, tokens), + } +} + +/// [`breeze_field_metrics_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_field_metrics() -> BreezeFieldMetrics { + breeze_field_metrics_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// The body line box `H` of `typography`, in whole pixels: the height every +/// text-driven Breeze control is derived from. 18 for the Breeze typography. +pub fn body_line_height(typography: &TypographyTokens) -> f32 { + (typography.body.size * typography.body.line_height).round() +} + +/// The Breeze geometry of `typography`'s body line box. +pub fn geometry_of(typography: &TypographyTokens) -> BreezeGeometry { + BreezeGeometry::for_line_height(body_line_height(typography)) +} + +/// The dimensions of a push button, a default button and a flat push button, +/// which share them. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeButtonMetrics { + /// `[S]` The button's height, at least: `H + 16`, through the density + /// ladder. 34 at Compact for the Breeze typography. + pub height: f32, + /// `[S]` The button's width, at least: 84, Breeze's 80 plus the 2 px + /// frame margin on each side. + pub min_width: f32, + /// `[S]` The space between the frame and the label on the leading and + /// trailing sides: the 6 px button margin and the 2 px frame margin, + /// scaled by the density's spacing. + pub padding_horizontal: f32, + /// `[S]` The same above and below the label. It is part of the height + /// rule rather than a gap, so it does not scale: the height floor rises + /// with the density instead. + pub padding_vertical: f32, +} + +/// The button dimensions at the density of `tokens`, for `typography`. +pub fn breeze_button_metrics_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> BreezeButtonMetrics { + let g = geometry_of(typography); + BreezeButtonMetrics { + height: dp(g.push_button_height, TargetRole::Target, tokens), + min_width: g.push_button_min_width, + padding_horizontal: spacing(BUTTON_MARGIN_WIDTH + FRAME_WIDTH, tokens), + padding_vertical: BUTTON_MARGIN_WIDTH + FRAME_WIDTH, + } +} + +/// [`breeze_button_metrics_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_button_metrics() -> BreezeButtonMetrics { + breeze_button_metrics_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// The square an `IconButton` is drawn in at each of its five sizes: Breeze's +/// auto-raise tool button, an icon with the 6 px tool-button margin around +/// it, through the density ladder. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeToolButtonSizes { + /// `[unpublished]` Breeze has no tool button smaller than the one around + /// a 16 px icon; the compact rung is the density's target size, 24 at + /// Compact. + pub compact: f32, + /// `[S]` A 16 px icon: 28 at Compact. + pub default: f32, + /// `[S]` A 22 px icon, the toolbar size: 34 at Compact. + pub toolbar: f32, + /// `[derived]` Breeze's rule at Kirigami's 32 px medium icon: 44. + pub large: f32, + /// `[derived]` Breeze's rule at Kirigami's 48 px large icon: 60. + pub hero: f32, +} + +/// The tool-button squares at the density of `tokens`. +pub fn breeze_tool_button_sizes_for(tokens: &InputTokens) -> BreezeToolButtonSizes { + // Icon-driven, so the font does not enter: the default geometry's + // tool-button heights are those of any font. + let g = BreezeGeometry::breeze_default(); + let around = |icon: f32| icon + 2.0 * TOOL_BUTTON_MARGIN_WIDTH; + let target = |h: f32| dp(h, TargetRole::Target, tokens); + BreezeToolButtonSizes { + compact: target(tokens.min_target_conformance), + default: target(g.tool_button_height), + toolbar: target(g.tool_button_height_medium), + large: target(around(MEDIUM_ICON_SIZE)), + hero: target(around(LARGE_ICON_SIZE)), + } +} + +/// [`breeze_tool_button_sizes_for`] at the Compact density. +pub fn breeze_tool_button_sizes() -> BreezeToolButtonSizes { + breeze_tool_button_sizes_for(&InputTokens::default()) +} + +/// `[S]` The square a combo box's arrow is centred in, at the trailing edge +/// inside the 2 px frame margin. +pub const COMBO_ARROW_BOX: f32 = 20.0; +/// `[S]` What a combo box adds to its label's width: the 6 px frame on both +/// sides of the label, the arrow box, and 2 and 4 more. 38. +pub const COMBO_CHROME_WIDTH: f32 = 2.0 * COMBO_BOX_FRAME_WIDTH + COMBO_ARROW_BOX + 2.0 + 4.0; +/// `[derived]` The space between a combo box's label and its arrow box: the +/// chrome width less the leading frame, the arrow box and the trailing frame +/// margin. 10. +pub const COMBO_ARROW_GAP: f32 = + COMBO_CHROME_WIDTH - COMBO_BOX_FRAME_WIDTH - COMBO_ARROW_BOX - FRAME_WIDTH; +/// `[S]` The tallest a spin box's arrow column is, centred in the frame. +pub const SPIN_ARROW_MAX_HEIGHT: f32 = 20.0; + +/// `[Hp]` The radius of a slider's groove, on the groove inset by half the +/// pen. +pub const SLIDER_GROOVE_RADIUS: f32 = 3.0; +/// `[Hp]` How much of the window text a slider's groove is filled with over +/// the window: 0.2 times 0.7. +pub const SLIDER_GROOVE_ALPHA: f64 = 0.2 * 0.7; +/// `[Hp]` How much of the highlight fills the groove up to the handle, over +/// the window. +pub const SLIDER_FILL_ALPHA: f64 = 0.7; +/// `[measured-qt]` A slider's length when nothing proposes one: Qt's size +/// hint for a Breeze slider, 84 at every font size. +pub const SLIDER_DEFAULT_LENGTH: f32 = 84.0; +/// `[derived]` How far a row of tick marks reaches past the handle box: +/// the groove's half thickness, the 2 px gap and the 8 px tick, less half +/// the handle box. 3, the step Qt measures for each side with ticks. +pub const SLIDER_TICK_OVERHANG: f32 = + SLIDER_GROOVE_THICKNESS * 0.5 + SLIDER_TICK_MARGIN + SLIDER_TICK_LENGTH + - SLIDER_CONTROL_THICKNESS * 0.5; + +// The groove fits inside the handle box, and the ticks clear the groove. +const _: () = assert!(SLIDER_GROOVE_THICKNESS < SLIDER_CONTROL_THICKNESS); +const _: () = assert!(SLIDER_TICK_OVERHANG == 3.0); +const _: () = assert!(COMBO_CHROME_WIDTH == 38.0); +const _: () = assert!(COMBO_ARROW_GAP == 10.0); + +/// The dimensions of a combo box, a push-button frame holding the selected +/// item and an arrow. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeComboMetrics { + /// `[S]` The combo box's height, at least: `max(H, 20) + 2 x 6`, through + /// the density ladder. 32 at Compact for the Breeze typography. + pub height: f32, + /// `[S]` The space before the label: the 6 px combo-box frame, scaled by + /// the density's spacing. + pub padding_leading: f32, + /// `[derived]` The space between the label and the arrow box, + /// [`COMBO_ARROW_GAP`], scaled by the density's spacing. + pub arrow_gap: f32, + /// `[S]` The arrow box, [`COMBO_ARROW_BOX`]. Painted, so it does not + /// scale. + pub arrow_box: f32, + /// `[M]` The space after the arrow box: the 2 px frame margin. It holds + /// the frame, so it does not scale. + pub padding_trailing: f32, +} + +impl BreezeComboMetrics { + /// Everything the combo box adds to its label's width. + pub fn chrome_width(&self) -> f32 { + self.padding_leading + self.arrow_gap + self.arrow_box + self.padding_trailing + } +} + +/// The combo box dimensions at the density of `tokens`, for `typography`. +pub fn breeze_combo_metrics_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> BreezeComboMetrics { + let g = geometry_of(typography); + BreezeComboMetrics { + height: dp(g.combo_box_height, TargetRole::Target, tokens), + padding_leading: spacing(COMBO_BOX_FRAME_WIDTH, tokens), + arrow_gap: spacing(COMBO_ARROW_GAP, tokens), + arrow_box: COMBO_ARROW_BOX, + padding_trailing: FRAME_WIDTH, + } +} + +/// [`breeze_combo_metrics_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_combo_metrics() -> BreezeComboMetrics { + breeze_combo_metrics_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// The dimensions of a spin box: a line-edit frame holding the field and a +/// column of two arrows. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeSpinMetrics { + /// `[S]` The spin box's height, at least: the line edit's inner height + /// `max(H, 14) + 2` and the 6 px frame on each side, at least 20, + /// through the density ladder. 32 at Compact for the Breeze typography. + pub height: f32, + /// `[M]` The space before the field: the 6 px spin-box frame, scaled by + /// the density's spacing. + pub padding_leading: f32, + /// `[M]` The width of the arrow column, [`SPIN_BOX_ARROW_BUTTON_WIDTH`]. + /// Painted, so it does not scale. + pub arrow_width: f32, + /// `[M]` The space after the arrow column: the 2 px frame margin. + pub padding_trailing: f32, +} + +impl BreezeSpinMetrics { + /// `[S]` The two halves of the arrow column in a frame `height` tall: + /// the column is `min(inner height, 20)` tall, the inner height being the + /// frame less the 6 px frame above and below, and each half is the + /// column halved and rounded down. 10 and 10 at every density for the + /// Breeze typography. + pub fn arrow_half(height: f32) -> f32 { + let inner = (height - 2.0 * SPIN_BOX_FRAME_WIDTH).max(0.0); + (inner.min(SPIN_ARROW_MAX_HEIGHT) * 0.5).floor() + } +} + +/// The spin box dimensions at the density of `tokens`, for `typography`. +pub fn breeze_spin_metrics_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> BreezeSpinMetrics { + let g = geometry_of(typography); + BreezeSpinMetrics { + height: dp(g.spin_box_height, TargetRole::Target, tokens), + padding_leading: spacing(SPIN_BOX_FRAME_WIDTH, tokens), + arrow_width: SPIN_BOX_ARROW_BUTTON_WIDTH, + padding_trailing: FRAME_WIDTH, + } +} + +/// [`breeze_spin_metrics_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_spin_metrics() -> BreezeSpinMetrics { + breeze_spin_metrics_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// `[S]` The space a menu item's text starts after its icon column: Breeze's +/// 2 px and Qt's 1. +pub const MENU_ICON_TEXT_GAP: f32 = 3.0; +/// `[Hp]` The square a submenu arrow is centred in, at the trailing edge of +/// a menu item's contents. +pub const MENU_ARROW_BOX: f32 = 20.0; +/// `[Hp]` The space between the end of a menu item's text and its arrow +/// box. +pub const MENU_ARROW_TEXT_GAP: f32 = 3.0; +/// `[Hp]` How much of the focus colour washes a highlighted menu item: the +/// style's blend value. +pub const MENU_HIGHLIGHT_ALPHA: f32 = 0.3; +/// `[M]` How far a popup's content lies inside its frame: the menu margin, +/// which Breeze gives every popup panel. +pub const POPUP_PADDING: f32 = MENU_ITEM_MARGIN; +/// `[unpublished]` The widest a tooltip's text is laid out before it wraps: +/// Qt wraps a tooltip against the screen, which a tooltip in a Teksilo window +/// has no use for, so this is Teksilo's own 320 px. +pub const TOOLTIP_MAX_WIDTH: f32 = 320.0; + +/// The dimensions of a menu: its rows, separators and section titles. +/// +/// A row is laid out as Breeze lays out the item of a menu that shows icons: +/// `MenuItem` reserves its leading column on every row, the icon and the +/// check mark's alike. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeMenuMetrics { + /// `[S]` A row's height: `max(H, 20, 16) + 1` and the 4 px item margin + /// above and below, through the density ladder. 29 at Compact for the + /// Breeze typography. + pub item_height: f32, + /// `[S]` A separator's height: its 1 px line and the 4 px item margin, + /// scaled by the density's spacing. 5 at Compact. + pub separator_height: f32, + /// `[S]` A section title's height (a separator with text): `max(H, 20, + /// 16)` and the 4 px item margin above and below, through the density + /// ladder. 28 at Compact. `MenuList::header` shows a widget the app + /// builds, which the style never sees, so this is what an app sizes its + /// section titles with. + pub section_height: f32, + /// `[M]` The space before a row's icon column: the 4 px item margin and + /// Breeze's 8 px text margin, scaled by the density's spacing. 12 at + /// Compact. + pub padding_leading: f32, + /// `[K]` The icon column: the small icon size. Painted, so it does not + /// scale. + pub icon_column: f32, + /// `[S]` The space between the icon column and the text, + /// [`MENU_ICON_TEXT_GAP`], scaled by the density's spacing. + pub icon_label_gap: f32, + /// `[derived]` The column at a row's trailing edge: the 3 px gap after + /// the text, the 20 px arrow box and the 4 px item margin, the gaps + /// scaled by the density's spacing. 27 at Compact. + pub trailing_column: f32, + /// `[M]` The least space between a row's text and its shortcut: the + /// 16 px accelerator space, scaled by the density's spacing. + pub shortcut_gap: f32, + /// `[M]` The gap between a menu's frame and its rows on every side: the + /// 4 px menu margin, `MenuList`'s own inset. + pub popup_margin: f32, +} + +/// The menu dimensions at the density of `tokens`, for `typography`. +pub fn breeze_menu_metrics_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> BreezeMenuMetrics { + let g = geometry_of(typography); + BreezeMenuMetrics { + item_height: dp(g.menu_item_height, TargetRole::Target, tokens), + separator_height: spacing(g.menu_separator_height, tokens), + section_height: dp(g.menu_section_height, TargetRole::Target, tokens), + padding_leading: spacing(MENU_ITEM_MARGIN + MENU_ITEM_TEXT_LEFT_MARGIN, tokens), + icon_column: SMALL_ICON_SIZE, + icon_label_gap: spacing(MENU_ICON_TEXT_GAP, tokens), + trailing_column: spacing(MENU_ARROW_TEXT_GAP + MENU_ITEM_MARGIN, tokens) + MENU_ARROW_BOX, + shortcut_gap: spacing(MENU_ITEM_ACCELERATOR_SPACE, tokens), + popup_margin: MENU_ITEM_MARGIN, + } +} + +/// [`breeze_menu_metrics_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_menu_metrics() -> BreezeMenuMetrics { + breeze_menu_metrics_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// `[measured-qt]` How far a row's content lies inside its leading and +/// trailing edges: the 2 px item margin, the 2 px item padding and Qt's 3 px +/// text margin, where Qt puts a row's check box (7 px in, among the +/// `item_view_item` sub-rectangles of `sizes.json`). +pub const ROW_CONTENT_INSET: f32 = ITEM_VIEW_MARGIN_WIDTH + ITEM_VIEW_PADDING + 3.0; +/// `[Hp]` How much of the highlight washes a hovered row that is not +/// selected. +pub const ROW_HOVER_ALPHA: f32 = 0.3; +/// `[Hp]` The alpha of a hovered row's outline: the fill colour, at 0.8, +/// where a selected row's is opaque. +pub const ROW_HOVER_OUTLINE_ALPHA: f32 = 0.8; +/// `[Hp]` How much a hovered selected row's highlight is lightened, in Qt's +/// percent (d.11 of the specification). +pub const ROW_SELECTED_HOVER_LIGHTER: i32 = 110; + +/// The dimensions of a list or tree row, `StandardListItem` and +/// `StandardTreeItem`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeRowMetrics { + /// `[S]` A row of text: `H + 1 + 1 + 2 x 2`, the line box and the 1 px + /// item margin and 2 px item padding above and below, through the + /// density ladder. 24 at Compact for the Breeze typography. + pub height: f32, + /// `[measured-qt]` A row with an icon or a check box: 26 at Compact, Qt + /// adding 2 px for an item's icon and making room for the 20 px check + /// box, through the density ladder. + pub height_decorated: f32, + /// `[derived]` A row with a subtitle: two body line boxes and the same + /// margins and padding, `2H + 6`, through the density ladder. 42 at + /// Compact. Qt sizes an item to its text and has no two-line row of its + /// own. + pub height_two_line: f32, + /// `[measured-qt]` [`ROW_CONTENT_INSET`], scaled by the density's + /// spacing. + pub padding_horizontal: f32, + /// `[M]` The space above and below the content: the 1 px item margin. + /// It holds the selection's outline, so it does not scale; the height + /// floor rises with the density instead. + pub padding_vertical: f32, + /// `[M]` How far the selection lies inside the row's leading and + /// trailing edges: the 2 px item margin. Painted, so it does not scale. + pub selection_inset_horizontal: f32, + /// `[M]` The same above and below: the 1 px item margin. + pub selection_inset_vertical: f32, +} + +/// The row dimensions at the density of `tokens`, for `typography`. +pub fn breeze_row_metrics_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> BreezeRowMetrics { + let g = geometry_of(typography); + let margins = 2.0 * (ITEM_VIEW_MARGIN_HEIGHT + ITEM_VIEW_PADDING); + BreezeRowMetrics { + height: dp(g.list_row_height, TargetRole::Target, tokens), + height_decorated: dp( + g.list_row_height_with_icon + .max(g.list_row_height_with_check), + TargetRole::Target, + tokens, + ), + height_two_line: dp(2.0 * g.line_height + margins, TargetRole::Target, tokens), + padding_horizontal: spacing(ROW_CONTENT_INSET, tokens), + padding_vertical: ITEM_VIEW_MARGIN_HEIGHT, + selection_inset_horizontal: ITEM_VIEW_MARGIN_WIDTH, + selection_inset_vertical: ITEM_VIEW_MARGIN_HEIGHT, + } +} + +/// [`breeze_row_metrics_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_row_metrics() -> BreezeRowMetrics { + breeze_row_metrics_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// The dimensions of a `TableView` or `TreeTableView`: its rows and its +/// column header. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeTableMetrics { + /// `[wcag]` D7: a body row. Qt lays a Breeze table row out at `H + 2 x + /// (1 + 1)` and adds its 1 px grid line, 23 at Plasma's default font, + /// under WCAG 2.2 SC 2.5.8's 24 px; it is raised to 24, then through the + /// density ladder. + pub row_height: f32, + /// `[S]` The column header: `max(H, 10) + 2 x 6`, room for the sort + /// arrow, through the density ladder. 30 at Compact for the Breeze + /// typography. + pub header_height: f32, + /// `[M]` The space between a header section's edges and its label: the + /// 6 px header margin, scaled by the density's spacing. + pub header_padding: f32, +} + +/// The table dimensions at the density of `tokens`, for `typography`. +pub fn breeze_table_metrics_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> BreezeTableMetrics { + let g = geometry_of(typography); + BreezeTableMetrics { + row_height: dp( + g.table_row_height.max(tokens.min_target_conformance), + TargetRole::Target, + tokens, + ), + header_height: dp(g.header_height, TargetRole::Target, tokens), + header_padding: spacing(HEADER_MARGIN, tokens), + } +} + +/// [`breeze_table_metrics_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_table_metrics() -> BreezeTableMetrics { + breeze_table_metrics_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// `[Hp]` How far a hovered unselected tab's fill moves from the window +/// colour toward the hover colour. +pub const TAB_HOVER_MIX: f64 = 0.2; +/// `[Hp]` How much an unselected tab's window colour is darkened, in Qt's +/// percent. +pub const TAB_UNSELECTED_DARKER: i32 = 120; +/// `[Hp]` The radius of the active tab's highlight bar at its outer +/// corners: the frame radius. +pub const TAB_BAR_RADIUS: f32 = 5.0; +/// `[Hp]` The radius the selected tab's outline is stroked at, on the pen's +/// centre line: with a 1 px pen its outer edge turns at 5 px, the bar's +/// radius, and its inner edge at 4. +pub const TAB_OUTLINE_RADIUS: f32 = 4.5; + +/// The dimensions of a tab in a `TabBar` or `TabWidget`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeTabMetrics { + /// `[S]` A tab's height: `max(H + 2 x 4, 30)`, through the density + /// ladder. 30 at Compact for the Breeze typography. + pub height: f32, + /// `[S]` A static tab bar's tab: `max(H + 2 x 4, 34)`, through the + /// density ladder. Recorded, not laid out: a `TabStyle` is not told + /// whether its bar is one (see the crate's known limitations). + pub static_height: f32, + /// `[M]` The space between a tab's edges and its title: the 8 px tab + /// margin, scaled by the density's spacing. + pub padding_horizontal: f32, + /// `[M]` A tab's minimum width with a title. Recorded, not laid out: the + /// bar applies its own minimum tab width (see the crate's known + /// limitations). + pub min_width: f32, + /// `[M]` The thickness of the active tab's highlight bar. Painted, so it + /// does not scale. + pub indicator: f32, +} + +/// The tab dimensions at the density of `tokens`, for `typography`. +pub fn breeze_tab_metrics_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> BreezeTabMetrics { + let g = geometry_of(typography); + BreezeTabMetrics { + height: dp(g.tab_height, TargetRole::Target, tokens), + static_height: dp(g.static_tab_height, TargetRole::Target, tokens), + padding_horizontal: spacing(TAB_MARGIN_WIDTH, tokens), + min_width: crate::geometry::TAB_MIN_WIDTH, + indicator: crate::geometry::TAB_ACTIVE_INDICATOR, + } +} + +/// [`breeze_tab_metrics_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_tab_metrics() -> BreezeTabMetrics { + breeze_tab_metrics_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// `[Hp]` The width of the separator line on a scroll bar's leading edge. +pub const SCROLL_BAR_SEPARATOR: f32 = 1.0; + +/// The dimensions of a `ScrollBar`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeScrollBarMetrics { + /// `[M]` Breeze's extent, its 1 px separator included. Recorded, not laid + /// out: the widgets choose a scroll bar's thickness themselves (see the + /// crate's known limitations). + pub extent: f32, + /// `[M]` The handle's width, centred across what the separator leaves. + /// Painted, so it does not scale. + pub handle_width: f32, + /// `[M]` The shortest the handle gets. The handle is a grab, which this + /// clears at every density, so it is Breeze's 20 at all three. + pub min_handle_length: f32, + /// `[Hp]` The separator line's width. + pub separator: f32, +} + +/// The scroll-bar dimensions at the density of `tokens`. +pub fn breeze_scroll_bar_metrics_for(tokens: &InputTokens) -> BreezeScrollBarMetrics { + BreezeScrollBarMetrics { + extent: SCROLL_BAR_EXTENT, + handle_width: SCROLL_BAR_HANDLE_WIDTH, + min_handle_length: dp(SCROLL_BAR_MIN_HANDLE_LENGTH, TargetRole::Grab, tokens), + separator: SCROLL_BAR_SEPARATOR, + } +} + +/// [`breeze_scroll_bar_metrics_for`] at the Compact density. +pub fn breeze_scroll_bar_metrics() -> BreezeScrollBarMetrics { + breeze_scroll_bar_metrics_for(&InputTokens::default()) +} + +/// `[Hp]` How much of the window text a progress bar's groove is filled with +/// over the window: 0.2 times 0.7, as a slider's. +pub const PROGRESS_GROOVE_ALPHA: f64 = 0.2 * 0.7; +/// `[Hp]` How much of the highlight fills a determinate bar's contents over +/// the window. +pub const PROGRESS_CONTENTS_ALPHA: f64 = 0.7; +/// `[Hp]` How far the busy bar's lighter stripes move from the highlight +/// toward the window. +pub const PROGRESS_BUSY_LIGHT_MIX: f64 = 0.7; + +/// The dimensions of a `ProgressBar`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeProgressMetrics { + /// `[qt]` `[measured-qt]` The bar's thickness: Qt's size hint, `H + 8`, + /// 26 at Plasma's default font, the groove centred in it. A progress bar + /// is not a target, so it does not rise with the density. + pub thickness: f32, + /// `[M]` The groove's thickness. + pub groove: f32, + /// `[Hp]` The groove's radius, on the groove inset by half the pen. + pub radius: f32, + /// `[Hp]` The period of the busy bar's stripes: one highlight stripe and + /// one lighter stripe, each half of it. + pub busy_period: f32, +} + +/// The progress-bar dimensions for `typography`. None of them follows the +/// density. +pub fn breeze_progress_metrics_for(typography: &TypographyTokens) -> BreezeProgressMetrics { + let g = geometry_of(typography); + BreezeProgressMetrics { + thickness: g.progress_bar_height, + groove: PROGRESS_BAR_THICKNESS, + radius: crate::shape::BREEZE_GROOVE_RADIUS, + busy_period: crate::geometry::PROGRESS_BAR_BUSY_PERIOD, + } +} + +/// [`breeze_progress_metrics_for`] the Breeze typography. +pub fn breeze_progress_metrics() -> BreezeProgressMetrics { + breeze_progress_metrics_for(&crate::typography::breeze_typography()) +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_tokens::TargetDensity; + + fn at(density: TargetDensity) -> InputTokens { + InputTokens::for_density(density) + } + + #[test] + fn the_compact_button_is_breezes() { + let m = breeze_button_metrics(); + assert_eq!(m.height, 34.0); + assert_eq!(m.min_width, 84.0); + assert_eq!(m.padding_horizontal, 8.0); + assert_eq!(m.padding_vertical, 8.0); + // The label's 18 px line box and the two gutters make the height. + let h = body_line_height(&crate::typography::breeze_typography()); + assert_eq!(h, 18.0); + assert_eq!(h + 2.0 * m.padding_vertical, m.height); + // The width rule: max(text + 12, 80) + 4 is text + 16 or 84. + assert_eq!(2.0 * m.padding_horizontal, 12.0 + 2.0 * FRAME_WIDTH); + } + + #[test] + fn the_button_rises_with_the_density_and_never_falls() { + let t = crate::typography::breeze_typography(); + let compact = breeze_button_metrics_for(&at(TargetDensity::Compact), &t); + let comfortable = breeze_button_metrics_for(&at(TargetDensity::Comfortable), &t); + let touch = breeze_button_metrics_for(&at(TargetDensity::Touch), &t); + assert_eq!(comfortable.height, 34.0, "34 already clears 32"); + assert_eq!(touch.height, 44.0); + assert!((comfortable.padding_horizontal - 9.2).abs() < 1e-4); + assert!((touch.padding_horizontal - 10.4).abs() < 1e-4); + for m in [compact, comfortable, touch] { + assert_eq!(m.min_width, 84.0); + assert_eq!(m.padding_vertical, 8.0); + } + } + + #[test] + fn a_larger_body_font_makes_a_taller_button() { + let mut t = crate::typography::breeze_typography(); + // Plasma's 12 pt: a 16 px face on a 22 px line, which Qt makes 38. + t.body.size = 16.0; + t.body.line_height = 22.0 / 16.0; + let m = breeze_button_metrics_for(&InputTokens::default(), &t); + assert_eq!(m.height, 38.0); + } + + #[test] + fn the_tool_buttons_are_breezes_and_grow_with_the_density() { + let s = breeze_tool_button_sizes(); + assert_eq!( + (s.compact, s.default, s.toolbar, s.large, s.hero), + (24.0, 28.0, 34.0, 44.0, 60.0) + ); + let comfortable = breeze_tool_button_sizes_for(&at(TargetDensity::Comfortable)); + assert_eq!( + ( + comfortable.compact, + comfortable.default, + comfortable.toolbar + ), + (32.0, 32.0, 34.0) + ); + let touch = breeze_tool_button_sizes_for(&at(TargetDensity::Touch)); + assert_eq!( + ( + touch.compact, + touch.default, + touch.toolbar, + touch.large, + touch.hero + ), + (44.0, 44.0, 44.0, 44.0, 60.0) + ); + } + + #[test] + fn the_field_is_breezes_line_edit_and_grows_with_the_density() { + let m = breeze_field_metrics(); + assert_eq!(m.height, 32.0); + assert_eq!(m.padding_horizontal, 6.0); + let t = crate::typography::breeze_typography(); + let comfortable = breeze_field_metrics_for(&at(TargetDensity::Comfortable), &t); + let touch = breeze_field_metrics_for(&at(TargetDensity::Touch), &t); + assert_eq!(comfortable.height, 32.0, "32 already clears 32"); + assert_eq!(touch.height, 44.0); + assert!((touch.padding_horizontal - 7.8).abs() < 1e-4); + } + + #[test] + fn the_indicator_puts_the_label_where_breeze_does() { + assert_eq!(INDICATOR_LABEL_X, 24.0); + // The widgets' 6 px gap: an 18 px body. + let gap = teksilo_widgets::styles::recipe_checkbox_style::CHECKBOX_LABEL_GAP; + assert_eq!( + gap, + teksilo_widgets::styles::recipe_radio_style::RADIO_LABEL_GAP + ); + assert_eq!(indicator_body_width(gap), 18.0); + assert_eq!(indicator_body_width(gap) + gap, INDICATOR_LABEL_X); + // The box ends 2 px inside the indicator, so it stays in the body. + assert_eq!(INDICATOR_SIZE - INDICATOR_BOX_INSET, 18.0); + assert_eq!( + indicator_body_width(12.0), + 18.0, + "never narrower than the box" + ); + assert_eq!(indicator_body_width(0.0), 24.0); + } + + #[test] + fn the_combo_box_is_breezes_and_grows_with_the_density() { + let m = breeze_combo_metrics(); + assert_eq!(m.height, 32.0); + assert_eq!(m.padding_leading, 6.0); + assert_eq!(m.arrow_gap, 10.0); + assert_eq!(m.arrow_box, 20.0); + assert_eq!(m.padding_trailing, 2.0); + // Breeze's width rule: the label plus 12 + 20 + 2 + 4. + assert_eq!(m.chrome_width(), 38.0); + let t = crate::typography::breeze_typography(); + let comfortable = breeze_combo_metrics_for(&at(TargetDensity::Comfortable), &t); + let touch = breeze_combo_metrics_for(&at(TargetDensity::Touch), &t); + assert_eq!(comfortable.height, 32.0, "32 already clears 32"); + assert_eq!(touch.height, 44.0); + for m in [comfortable, touch] { + assert_eq!(m.arrow_box, 20.0, "the painted box does not scale"); + assert_eq!(m.padding_trailing, 2.0); + assert!(m.padding_leading > 6.0 && m.arrow_gap > 10.0); + } + } + + #[test] + fn the_spin_box_has_a_twenty_pixel_column_of_two_ten_pixel_halves() { + let m = breeze_spin_metrics(); + assert_eq!(m.height, 32.0); + assert_eq!(m.padding_leading, 6.0); + assert_eq!(m.arrow_width, 20.0); + assert_eq!(m.padding_trailing, 2.0); + assert_eq!(BreezeSpinMetrics::arrow_half(32.0), 10.0); + let t = crate::typography::breeze_typography(); + let touch = breeze_spin_metrics_for(&at(TargetDensity::Touch), &t); + assert_eq!(touch.height, 44.0); + assert_eq!(touch.arrow_width, 20.0); + // The column stops at 20, so a taller frame keeps the halves. + assert_eq!(BreezeSpinMetrics::arrow_half(touch.height), 10.0); + // A frame too short for a 20 px column halves what is left, rounded + // down: 29 tall leaves 17, two 8 px halves. + assert_eq!(BreezeSpinMetrics::arrow_half(29.0), 8.0); + assert_eq!(BreezeSpinMetrics::arrow_half(4.0), 0.0); + } + + #[test] + fn the_menu_is_breezes_and_grows_with_the_density() { + let m = breeze_menu_metrics(); + assert_eq!(m.item_height, 29.0); + assert_eq!(m.separator_height, 5.0); + assert_eq!(m.section_height, 28.0); + assert_eq!(m.padding_leading, 12.0); + assert_eq!(m.icon_column, 16.0); + assert_eq!(m.icon_label_gap, 3.0); + assert_eq!(m.trailing_column, 27.0); + assert_eq!(m.shortcut_gap, 16.0); + assert_eq!(m.popup_margin, 4.0); + // The text starts where Breeze puts it in a menu with icons: the + // 4 px margin, the 8 px text margin, the icon and 3 px after it. + assert_eq!(m.padding_leading + m.icon_column + m.icon_label_gap, 31.0); + // And ends 3 px before a 20 px arrow box 4 px inside the row. + assert_eq!( + m.trailing_column, + MENU_ARROW_TEXT_GAP + MENU_ARROW_BOX + MENU_ITEM_MARGIN + ); + let t = crate::typography::breeze_typography(); + let comfortable = breeze_menu_metrics_for(&at(TargetDensity::Comfortable), &t); + let touch = breeze_menu_metrics_for(&at(TargetDensity::Touch), &t); + assert_eq!(comfortable.item_height, 32.0); + assert_eq!(touch.item_height, 44.0); + assert_eq!(comfortable.section_height, 32.0); + assert_eq!(touch.section_height, 44.0); + assert!((comfortable.separator_height - 5.75).abs() < 1e-4); + assert!((touch.separator_height - 6.5).abs() < 1e-4); + for m in [comfortable, touch] { + assert_eq!(m.icon_column, 16.0, "the painted column does not scale"); + assert_eq!(m.popup_margin, 4.0, "MenuList's inset does not scale"); + assert!(m.trailing_column > 27.0 && m.trailing_column - MENU_ARROW_BOX > 7.0); + assert!(m.padding_leading > 12.0 && m.shortcut_gap > 16.0); + } + } + + #[test] + fn a_larger_body_font_makes_taller_menu_rows() { + let mut t = crate::typography::breeze_typography(); + // Plasma's 12 pt: a 22 px line, which Qt makes a 31 px item and a + // 30 px section. + t.body.size = 16.0; + t.body.line_height = 22.0 / 16.0; + let m = breeze_menu_metrics_for(&InputTokens::default(), &t); + assert_eq!(m.item_height, 31.0); + assert_eq!(m.section_height, 30.0); + assert_eq!(m.separator_height, 5.0, "a separator has no text"); + } + + #[test] + fn the_rows_are_breezes_and_grow_with_the_density() { + let m = breeze_row_metrics(); + assert_eq!(m.height, 24.0); + assert_eq!(m.height_decorated, 26.0); + assert_eq!(m.height_two_line, 42.0); + assert_eq!(m.padding_horizontal, 7.0); + assert_eq!(m.padding_vertical, 1.0); + assert_eq!( + (m.selection_inset_horizontal, m.selection_inset_vertical), + (2.0, 1.0) + ); + let t = crate::typography::breeze_typography(); + let comfortable = breeze_row_metrics_for(&at(TargetDensity::Comfortable), &t); + let touch = breeze_row_metrics_for(&at(TargetDensity::Touch), &t); + assert_eq!( + ( + comfortable.height, + comfortable.height_decorated, + comfortable.height_two_line + ), + (32.0, 32.0, 42.0) + ); + assert_eq!( + (touch.height, touch.height_decorated, touch.height_two_line), + (44.0, 44.0, 44.0) + ); + for m in [comfortable, touch] { + assert_eq!(m.padding_vertical, 1.0, "holds the outline"); + assert_eq!(m.selection_inset_horizontal, 2.0, "painted"); + assert!(m.padding_horizontal > 7.0); + } + } + + #[test] + fn a_larger_body_font_makes_taller_rows() { + let mut t = crate::typography::breeze_typography(); + // Plasma's 12 pt: a 22 px line, a 28 px row and a 30 px row with an + // icon, as Qt measures them. + t.body.size = 16.0; + t.body.line_height = 22.0 / 16.0; + let m = breeze_row_metrics_for(&InputTokens::default(), &t); + assert_eq!((m.height, m.height_decorated), (28.0, 30.0)); + assert_eq!(m.height_two_line, 50.0); + } + + /// D7: Qt's table row is 23 px with its grid line; Teksilo's is 24. + #[test] + fn d7_the_table_row_is_raised_to_the_target_size() { + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.table_row_height, 23.0, "Qt's row, below the floor"); + let m = breeze_table_metrics(); + assert_eq!(m.row_height, 24.0); + assert_eq!(m.header_height, 30.0); + assert_eq!(m.header_padding, 6.0); + // The label's line box and the margin above and below fill the + // header exactly. + assert_eq!(g.line_height + 2.0 * m.header_padding, m.header_height); + let t = crate::typography::breeze_typography(); + let touch = breeze_table_metrics_for(&at(TargetDensity::Touch), &t); + assert_eq!((touch.row_height, touch.header_height), (44.0, 44.0)); + // A larger font's row is Qt's own once it clears the floor. + let mut big = t; + big.body.size = 16.0; + big.body.line_height = 22.0 / 16.0; + assert_eq!( + breeze_table_metrics_for(&InputTokens::default(), &big).row_height, + 27.0 + ); + } + + #[test] + fn the_tab_is_breezes_and_grows_with_the_density() { + let m = breeze_tab_metrics(); + assert_eq!(m.height, 30.0); + assert_eq!(m.static_height, 34.0); + assert_eq!(m.padding_horizontal, 8.0); + assert_eq!(m.min_width, 80.0); + assert_eq!(m.indicator, 3.0); + let t = crate::typography::breeze_typography(); + let comfortable = breeze_tab_metrics_for(&at(TargetDensity::Comfortable), &t); + let touch = breeze_tab_metrics_for(&at(TargetDensity::Touch), &t); + assert_eq!(comfortable.height, 32.0); + assert_eq!(touch.height, 44.0); + assert!((touch.padding_horizontal - 10.4).abs() < 1e-4); + assert_eq!(touch.indicator, 3.0, "the painted bar does not scale"); + } + + #[test] + fn the_scroll_bar_handle_is_breezes_at_every_density() { + for density in [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ] { + let tokens = at(density); + let m = breeze_scroll_bar_metrics_for(&tokens); + assert_eq!(m.extent, 21.0); + assert_eq!(m.handle_width, 8.0); + assert_eq!(m.min_handle_length, 20.0, "{density:?}"); + assert!(m.min_handle_length >= tokens.grab_size); + assert_eq!(m.separator, 1.0); + // The handle centred in what the separator leaves is 6 px clear + // of it on either side. + assert_eq!((m.extent - m.separator - m.handle_width) / 2.0, 6.0); + } + } + + #[test] + fn the_progress_bar_is_qts_hint_around_breezes_groove() { + let m = breeze_progress_metrics(); + assert_eq!(m.thickness, 26.0); + assert_eq!(m.groove, 6.0); + assert_eq!(m.radius, 3.0); + assert_eq!(m.busy_period, 28.0); + assert!((PROGRESS_GROOVE_ALPHA - 0.14).abs() < 1e-12); + } + + #[test] + fn every_tool_button_clears_the_conformance_floor_without_a_box() { + for density in [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ] { + let tokens = at(density); + let s = breeze_tool_button_sizes_for(&tokens); + for size in [s.compact, s.default, s.toolbar, s.large, s.hero] { + assert!(size >= tokens.min_target_conformance, "{density:?}: {size}"); + } + assert!(s.compact <= s.default && s.default <= s.toolbar); + assert!(s.toolbar <= s.large && s.large <= s.hero); + } + } +} diff --git a/crates/teksilo-theme-kde/src/styles/popover.rs b/crates/teksilo-theme-kde/src/styles/popover.rs new file mode 100644 index 000000000..0fd9571c5 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/popover.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze popup panel: the frame around a menu, a combo box's list, a +//! search field's suggestions and a popover. +//! +//! The frame is [`crate::styles::popup`]'s: the frame background in the +//! frame outline at radius 4.5, over Breeze's two-layer window shadow. A +//! menu's frame has no padding of its own: `MenuList` puts its rows 4 px +//! inside it, Breeze's menu margin, and the highlight of a row fills the row +//! up to that margin. Any other popup's content lies 4 px inside the frame, +//! the same margin, scaled by the density's spacing. The `Menu` variant +//! follows the desktop's menu opacity ([`BreezePopupKind::Menu`]), and the +//! `Tooltip` variant is drawn in the tooltip's colours. +//! +//! ## Where this departs from Breeze +//! +//! - **No caret.** A Breeze popup has no arrow pointing at what opened it, +//! so the frame ignores [`PopoverStyleConfig::show_caret`] and lies flush +//! against the placement the overlay gives it. + +use teksilo_core::build_context::BuildContext; +use teksilo_core::styles::{PopoverStyle, PopoverStyleConfig, PopoverVariant}; +use teksilo_core::widget_id::WidgetId; +use teksilo_tokens::InputTokens; + +use crate::styles::metrics::POPUP_PADDING; +use crate::styles::popup::{BreezePopupFrame, BreezePopupKind, PopupNode}; +use teksilo_core::styles::density::spacing; + +/// Breeze `PopoverStyle`. +/// +/// [`Default`] is the Compact density; the preset installs +/// [`BreezePopoverStyle::for_tokens`] of the theme it builds. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezePopoverStyle { + /// `[M]` The space between the frame and the content of a popup that is + /// not a menu. + pub padding: f32, +} + +impl BreezePopoverStyle { + /// The style at the density of `tokens`. + pub fn for_tokens(tokens: &InputTokens) -> Self { + Self { + padding: spacing(POPUP_PADDING, tokens), + } + } +} + +impl Default for BreezePopoverStyle { + fn default() -> Self { + Self::for_tokens(&InputTokens::default()) + } +} + +impl PopoverStyle for BreezePopoverStyle { + fn make_body(&self, cfg: &PopoverStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let (kind, padding, node) = match cfg.variant { + // The menu's owner carries the container role, and its rows + // their own margin. + PopoverVariant::Menu => (BreezePopupKind::Menu, 0.0, PopupNode::Container), + PopoverVariant::Default => ( + BreezePopupKind::Panel, + self.padding, + PopupNode::Dialog(cfg.name.clone()), + ), + PopoverVariant::Tooltip => ( + BreezePopupKind::Tooltip, + self.padding, + PopupNode::Dialog(cfg.name.clone()), + ), + }; + ctx.add(BreezePopupFrame::new( + cfg.content, + kind, + padding, + None, + node, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_tokens::TargetDensity; + + #[test] + fn the_padding_is_the_menu_margin_and_grows_with_the_density() { + assert_eq!(BreezePopoverStyle::default().padding, 4.0); + let touch = BreezePopoverStyle::for_tokens(&InputTokens::for_density(TargetDensity::Touch)); + assert!((touch.padding - 5.2).abs() < 1e-4); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/popup.rs b/crates/teksilo-theme-kde/src/styles/popup.rs new file mode 100644 index 000000000..5ef7a919a --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/popup.rs @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The frame every Breeze popup is drawn in: a menu, a combo box's list, a +//! popover and a tooltip. +//! +//! A Breeze popup is a window of its own, a rounded rectangle at the 5 px +//! frame radius, filled and stroked with a 1 px pen on the rectangle inset +//! by half the pen, so the outline lies inside it at radius 4.5. A menu or +//! any other popup panel is filled with the frame background (the window +//! colour 30 % of the way to the view's, `surface_raised`) and outlined in +//! the frame outline (`border`); a tooltip is filled with the tooltip +//! background and outlined in the tooltip background moved toward the +//! tooltip text by the frame contrast (`tooltip_bg`, `tooltip_border`). +//! Both colours are resolved from the theme at paint time, so the inactive +//! and high-contrast projections reach them; the tooltip tokens are the +//! active state's in every state, as Qt's palette has them. +//! +//! Under the frame lies Breeze's window shadow at its default size, Large: +//! two layers of black, one 5 px down with a 20 px blur at 22 %, one 2 px +//! down with a 10 px blur at 12 %, both on the window's 5 px rounded +//! rectangle (`shadow_lg` and `shadow_inner_lg`, spec k.6). +//! +//! ## Where this departs from Breeze +//! +//! - **The panel is always rounded.** Breeze rounds a popup only on a +//! compositing desktop; a Teksilo popup is drawn in its window's own +//! pass, so it is always the composited form. +//! - **A translucent menu has nothing blurred behind it.** When +//! `breezerc`'s `MenuOpacity` is below 100 a menu's panel is filled at +//! that opacity and outlined in the window text at 25 %, as Breeze paints +//! it ([`BreezePopupKind::Menu`], [`crate::BreezeStyleConfig`]); on a +//! Plasma desktop the compositor blurs what lies behind such a menu, and +//! Teksilo draws the panel over its window's content as it is. The other +//! popups stay opaque. +//! - **The shadow is drawn on every side.** The framework's popover cuts +//! the shadow on the side that meets the control that opened it; a Breeze +//! popup is a window, and its shadow surrounds it. +//! - **The frame outline is not lifted** (D6): it is the outline Breeze +//! draws around a passive frame, and the shadow already separates the +//! panel from what lies under it. + +use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::build_context::BuildContext; +use teksilo_core::styles::Theme; +use teksilo_core::widget::{ + LayoutContext, LayoutResponse, PaintContext, PendingChild, Widget, WidgetPlacement, +}; +use teksilo_core::widget_id::WidgetId; +use teksilo_tokens::{Color, CornerRadius}; + +use crate::breeze_config::BreezeStyleConfig; +use crate::styles::chrome::frame_radius; + +/// `[S]` The alpha of a translucent menu's outline: the window text at +/// 25 %, where an opaque panel is outlined in the frame outline. +pub const TRANSLUCENT_MENU_OUTLINE_ALPHA: f32 = 0.25; + +/// Which Breeze popup a frame is painted as. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BreezePopupKind { + /// A menu, a combo box's list or a search field's suggestions: a panel + /// that follows the desktop's menu opacity. + Menu, + /// Any other popup panel, such as a popover. + Panel, + /// A tooltip. + Tooltip, +} + +/// The colours of a popup frame. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezePopupColors { + /// The frame's fill. + pub fill: Color, + /// The frame's 1 px outline. + pub outline: Color, +} + +impl BreezePopupColors { + /// `[Hp]` The colours of a `kind` popup in `theme`, the theme the paint + /// walker hands a widget. + /// + /// A menu whose theme carries a [`BreezeStyleConfig`] with a + /// `MenuOpacity` below 100 is filled at that opacity and outlined in + /// the window text at [`TRANSLUCENT_MENU_OUTLINE_ALPHA`] (`[S]`); + /// otherwise it is the panel. + pub fn resolve(theme: &Theme, kind: BreezePopupKind) -> BreezePopupColors { + let c = &theme.colors; + let panel = BreezePopupColors { + fill: c.surface_raised, + outline: c.border, + }; + match kind { + BreezePopupKind::Menu => match theme.extension::() { + Some(config) if config.menu_is_translucent() => BreezePopupColors { + fill: c + .surface_raised + .with_alpha(c.surface_raised.a() * config.menu_opacity()), + outline: c.text_primary.with_alpha(TRANSLUCENT_MENU_OUTLINE_ALPHA), + }, + _ => panel, + }, + BreezePopupKind::Panel => panel, + BreezePopupKind::Tooltip => BreezePopupColors { + fill: c.tooltip_bg, + outline: c.tooltip_border, + }, + } + } +} + +/// Paint a `kind` popup frame on `bounds`: both shadow layers on the +/// window's rounded rectangle, then the fill and the outline on the +/// rectangle inset by half the pen. +pub fn paint_popup(canvas: &mut Canvas, bounds: Rect, kind: BreezePopupKind, theme: &Theme) { + let shape = &theme.shape; + let window = CornerRadius::uniform(shape.radius_popup); + for shadow in [&shape.shadow_lg, &shape.shadow_inner_lg] { + if shadow.color.a() > 0.0 { + canvas.draw_shadow(bounds, window, shadow); + } + } + let colors = BreezePopupColors::resolve(theme, kind); + let pen = shape.border_width; + let d = pen * 0.5; + let rect = Rect::new( + bounds.x + d, + bounds.y + d, + (bounds.width - 2.0 * d).max(0.0), + (bounds.height - 2.0 * d).max(0.0), + ); + let corner = CornerRadius::uniform(frame_radius(shape)); + if colors.fill.a() > 0.0 { + canvas.fill_rounded_rect(rect, corner, colors.fill); + } + if colors.outline.a() > 0.0 && pen > 0.0 { + canvas.stroke_rounded_rect(rect, corner, colors.outline, pen); + } +} + +/// The accessibility node a popup frame emits. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum PopupNode { + /// Nothing of its own: a bare `GenericContainer`, which the adapters + /// drop while keeping its children. For a menu, whose owner carries the + /// container role. + Container, + /// A non-modal dialog named after what opened it, as the framework's + /// popover surface is. + Dialog(String), + /// Hidden: the tooltip widget emits the tooltip node. + Hidden, +} + +/// A popup frame around one content subtree, `padding` inside it on every +/// side. +pub(crate) struct BreezePopupFrame { + content: Option, + pending: Option, + kind: BreezePopupKind, + padding: f32, + /// The widest the frame lays its content out, even when nothing bounds + /// it. + max_width: Option, + node: PopupNode, +} + +impl BreezePopupFrame { + pub(crate) fn new( + content: WidgetId, + kind: BreezePopupKind, + padding: f32, + max_width: Option, + node: PopupNode, + ) -> Self { + Self { + content: None, + pending: Some(PendingChild::Id(content)), + kind, + padding, + max_width, + node, + } + } +} + +impl std::fmt::Debug for BreezePopupFrame { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezePopupFrame") + .field("kind", &self.kind) + .finish() + } +} + +impl Widget for BreezePopupFrame { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + if let Some(pending) = self.pending.take() { + self.content = Some(match pending { + PendingChild::Id(id) => id, + PendingChild::Deferred(w) => ctx.add_boxed(w), + }); + } + self.children() + } + + fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { + let inset = 2.0 * self.padding; + let width = match self.max_width { + Some(max) => Some(proposal.width.map_or(max, |w| w.min(max))), + None => proposal.width, + }; + let inner = SizeProposal { + width: width.map(|w| (w - inset).max(0.0)), + height: proposal.height.map(|h| (h - inset).max(0.0)), + }; + self.content + .and_then(|id| ctx.child_size(id, inner)) + .map(|s| Size::new(s.width + inset, s.height + inset)) + .unwrap_or_else(|| proposal.resolve(inset, inset)) + .into() + } + + fn place_children( + &self, + bounds: Rect, + _proposal: SizeProposal, + children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + let p = self.padding; + for child in children.iter_mut() { + child.origin = Point::new(bounds.x + p, bounds.y + p); + child.size = Size::new( + (bounds.width - 2.0 * p).max(0.0), + (bounds.height - 2.0 * p).max(0.0), + ); + } + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + paint_popup(canvas, bounds, self.kind, ctx.theme); + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + match &self.node { + PopupNode::Container => { + builder.set_role(teksilo_core::accesskit::Role::GenericContainer); + } + PopupNode::Dialog(name) => { + builder.set_role(teksilo_core::accesskit::Role::Dialog); + // An empty name would hide the `labelled_by` relation the + // popover names its surface with. + if !name.is_empty() { + builder.set_name(name); + } + } + PopupNode::Hidden => builder.set_hidden(), + } + } + + fn children(&self) -> Vec { + self.content.into_iter().collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColor; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + /// Plasma's menu panel and tooltip, in Breeze Light and Dark. + #[test] + fn the_frames_are_breezes() { + let light = crate::light(); + let panel = BreezePopupColors::resolve(&light, BreezePopupKind::Panel); + assert_eq!(rgba8(panel.fill), [244, 245, 245, 255]); + assert_eq!(rgba8(panel.outline), [198, 200, 201, 255]); + let tip = BreezePopupColors::resolve(&light, BreezePopupKind::Tooltip); + assert_eq!(rgba8(tip.fill), [247, 247, 247, 255]); + assert_eq!(rgba8(tip.outline), [205, 205, 206, 255]); + let dark = crate::dark(); + let panel = BreezePopupColors::resolve(&dark, BreezePopupKind::Panel); + assert_eq!(rgba8(panel.fill), [28, 31, 34, 255]); + assert_eq!(rgba8(panel.outline), [76, 78, 81, 255]); + let tip = BreezePopupColors::resolve(&dark, BreezePopupKind::Tooltip); + assert_eq!(rgba8(tip.fill), [41, 44, 48, 255]); + assert_eq!(rgba8(tip.outline), [83, 86, 89, 255]); + } + + /// A menu is the panel at the default menu opacity, and translucent + /// below it; other popups never are. + #[test] + fn a_menu_follows_the_menu_opacity() { + let opaque = crate::light(); + assert_eq!( + BreezePopupColors::resolve(&opaque, BreezePopupKind::Menu), + BreezePopupColors::resolve(&opaque, BreezePopupKind::Panel) + ); + let mut t = crate::light(); + let mut config = BreezeStyleConfig::default(); + config.settings.menu_opacity = 60; + t.extensions.insert(config); + let menu = BreezePopupColors::resolve(&t, BreezePopupKind::Menu); + assert_eq!(rgba8(menu.fill), [244, 245, 245, 153]); + assert_eq!(menu.outline, t.colors.text_primary.with_alpha(0.25)); + let panel = BreezePopupColors::resolve(&t, BreezePopupKind::Panel); + assert_eq!(panel.fill.a(), 1.0); + assert_eq!(panel.outline, t.colors.border); + // Without the extension a menu is opaque. + let mut bare = t.clone(); + bare.extensions.remove::(); + assert_eq!( + BreezePopupColors::resolve(&bare, BreezePopupKind::Menu) + .fill + .a(), + 1.0 + ); + } + + /// The frame is painted from the projected tokens, so the high-contrast + /// projection reaches its outline. + #[test] + fn the_frame_follows_the_high_contrast_projection() { + let t = crate::light().for_paint_state(teksilo_core::styles::PaintState { + window_active: true, + high_contrast: true, + }); + let panel = BreezePopupColors::resolve(&t, BreezePopupKind::Panel); + assert_eq!(panel.outline, t.colors.border); + assert_ne!(panel.outline, crate::light().colors.border); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/progress_bar.rs b/crates/teksilo-theme-kde/src/styles/progress_bar.rs new file mode 100644 index 000000000..cd3c6a381 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/progress_bar.rs @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze progress bar. +//! +//! A 6 px groove centred across the bar, filled and stroked with a 1 px pen +//! on itself inset by half the pen at radius 3: the window text at 14 % over +//! the window, outlined in the window text at the frame contrast. A +//! determinate bar's contents run from the start of the groove, `floor(value +//! x length)` long, in the highlight at 70 % over the window, outlined in the +//! highlight at the frame contrast; they start at the right-hand end in a +//! right-to-left window, and at the bottom of a vertical bar. A disabled bar +//! paints the disabled colours. +//! +//! A busy (indeterminate) bar fills its groove with stripes 28 px apart, a +//! 14 px stripe of the highlight and a 14 px stripe of the highlight moved +//! 70 % toward the window, which move one period along the groove every +//! 800 ms, linearly and looping. The style paints this itself +//! ([`ProgressBarStyle::paints_busy_indicator`]), so the bar draws no sweep +//! of its own; under the reduced-motion preference the stripes stand still. +//! The desktop's animation speed does not change the period, as in Breeze; +//! its `breezerc` can, and can stop the stripes ([`busy_step`]). +//! +//! The bar is 26 px thick at Plasma's default font, Qt's size hint, the +//! groove centred in it ([`BreezeProgressMetrics`]); a thickness the app +//! sets wins. A track or fill colour the app sets takes the place of the +//! groove's or the highlight's. +//! +//! ## Where this departs from Breeze +//! +//! - **A short contents keeps its length.** Breeze draws contents shorter +//! than the groove's thickness 6 px long and clips them; here they are +//! drawn at their length, the radius shrinking to half of it. + +use std::time::Duration; + +use teksilo_canvas::{Canvas, Rect, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::color_prop::ColorProp; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::signal::{Prop, Signal}; +use teksilo_core::styles::{ProgressBarStyle, ProgressBarStyleConfig, ProgressKind, Theme}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::{ColorState, PaletteRole}; +use teksilo_tokens::{Color, CornerRadius, InputTokens, Orientation, TypographyTokens}; + +use crate::breeze_config::BreezeStyleConfig; +use crate::palette::KdePalette; +use crate::styles::chrome::kde_mix; +use crate::styles::metrics::{ + BreezeProgressMetrics, PROGRESS_BUSY_LIGHT_MIX, PROGRESS_CONTENTS_ALPHA, PROGRESS_GROOVE_ALPHA, + breeze_progress_metrics_for, +}; + +/// Breeze `ProgressBarStyle`. +/// +/// [`Default`] is the Breeze typography; the preset installs +/// [`BreezeProgressBarStyle::for_theme`] of the theme it builds. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeProgressBarStyle { + /// The progress-bar dimensions this style paints and reports. + pub metrics: BreezeProgressMetrics, +} + +impl BreezeProgressBarStyle { + /// The style for `typography`. + pub fn for_typography(typography: &TypographyTokens) -> Self { + Self { + metrics: breeze_progress_metrics_for(typography), + } + } + + /// The style for the typography of `theme`. + pub fn for_theme(theme: &Theme) -> Self { + Self::for_typography(&theme.typography) + } +} + +impl Default for BreezeProgressBarStyle { + fn default() -> Self { + Self::for_typography(&crate::typography::breeze_typography()) + } +} + +impl ProgressBarStyle for BreezeProgressBarStyle { + fn make_body(&self, cfg: &ProgressBarStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let (value, phase) = match &cfg.progress { + ProgressKind::Determinate(p) => (Some(p.clone()), None), + ProgressKind::Indeterminate => { + let phase = ctx.animated_signal(0.0); + if let Some(step) = busy_step(ctx.theme()) { + let spec = ctx.animate().duration(step).linear().looping(); + if !spec.reduced_motion() { + spec.to(&phase, 1.0); + } + } + (None, Some(phase)) + } + }; + ctx.add(BreezeProgressBody { + orientation: cfg.orientation, + value, + phase, + track: cfg.track_color_override.clone(), + fill: cfg.fill_color_override.clone(), + metrics: self.metrics, + }) + } + + /// [`BreezeProgressMetrics::thickness`], 26 at Plasma's default font. + fn thickness(&self, _tokens: &InputTokens) -> Option { + Some(self.metrics.thickness) + } + + /// The busy stripes are this style's. + fn paints_busy_indicator(&self) -> bool { + true + } +} + +/// The period a busy bar's stripes move in under `theme`, or `None` when +/// they stand still. +/// +/// `[S]` The desktop's `breezerc` decides whether they move +/// ([`BreezeStyleConfig::busy_step`]); the period is the theme's +/// `duration_indeterminate_sweep`, which the preset sets to +/// `ProgressBarBusyStepDuration`, 800 ms by default. A theme without the +/// config moves them with its sweep duration; a sweep of no length stands +/// still. +pub fn busy_step(theme: &Theme) -> Option { + let moves = theme + .extension::() + .is_none_or(|config| config.busy_step().is_some()); + let step = theme.motion.duration_indeterminate_sweep; + (moves && !step.is_zero()).then_some(step) +} + +/// The colours a Breeze progress bar is painted in, in one paint state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeProgressColors { + /// `[Hp]` The groove: the window text at 14 % over the window. + pub groove: Color, + /// `[Hp]` Its outline: the window text at the frame contrast. + pub groove_outline: Color, + /// `[Hp]` The contents: the highlight at 70 % over the window. + pub contents: Color, + /// `[Hp]` Their outline: the highlight at the frame contrast. + pub contents_outline: Color, + /// `[Hp]` A busy bar's highlight stripe. + pub busy: Color, + /// `[Hp]` A busy bar's other stripe: the highlight moved 70 % toward the + /// window. + pub busy_light: Color, +} + +impl BreezeProgressColors { + /// The colours of `theme` as it paints with or without the high-contrast + /// preference, for an enabled or a disabled bar, with the track and fill + /// colours an app set, if any. + /// + /// The window, its text and the highlight are the theme's projected + /// tokens (the disabled palette's window and window text for a disabled + /// bar); the frame contrast comes from [`KdePalette`], Breeze's 0.2 + /// without it. + pub fn resolve( + theme: &Theme, + high_contrast: bool, + disabled: bool, + track: Option, + fill: Option, + ) -> BreezeProgressColors { + let c = &theme.colors; + let palette = theme.extension::(); + let (window, window_text) = match (disabled, palette) { + (true, Some(p)) => ( + p.role(ColorState::Disabled, PaletteRole::Window), + p.role(ColorState::Disabled, PaletteRole::WindowText), + ), + (true, None) => (c.surface_main, c.text_disabled), + (false, _) => (c.surface_main, c.text_primary), + }; + let highlight = fill.unwrap_or(if disabled { + c.accent_disabled + } else { + c.accent + }); + // `[derived]` The frame contrast: 1 in the high-contrast scheme the + // palette builds, as `KdePalette::breeze_colors` computes it. + let fc = match palette { + Some(_) if high_contrast => 1.0, + Some(p) => p.frame_contrast(), + None => teksilo_kde_scheme::scheme::DEFAULT_FRAME_CONTRAST, + } as f32; + BreezeProgressColors { + groove: track.unwrap_or_else(|| kde_mix(window, window_text, PROGRESS_GROOVE_ALPHA)), + groove_outline: window_text.with_alpha(fc), + contents: kde_mix(window, highlight, PROGRESS_CONTENTS_ALPHA), + contents_outline: highlight.with_alpha(fc), + busy: highlight, + busy_light: kde_mix(highlight, window, PROGRESS_BUSY_LIGHT_MIX), + } + } +} + +/// `[Hp]` The groove in a bar's `bounds`: `thickness` across, centred and +/// rounded down to a whole pixel, the bar's full length along it. +pub fn groove_rect(bounds: Rect, orientation: Orientation, thickness: f32) -> Rect { + match orientation { + Orientation::Horizontal => { + let t = thickness.min(bounds.height); + let y = bounds.y + ((bounds.height - t) * 0.5).floor(); + Rect::new(bounds.x, y, bounds.width, t) + } + Orientation::Vertical => { + let t = thickness.min(bounds.width); + let x = bounds.x + ((bounds.width - t) * 0.5).floor(); + Rect::new(x, bounds.y, t, bounds.height) + } + } +} + +/// `[Hp]` A determinate bar's contents in `groove`: `floor(value x length)` +/// long from the start, the right-hand end in a right-to-left window and +/// the bottom of a vertical bar. +pub fn contents_rect( + groove: Rect, + orientation: Orientation, + direction: LayoutDirection, + value: f32, +) -> Rect { + let value = value.clamp(0.0, 1.0); + match orientation { + Orientation::Horizontal => { + let len = (value * groove.width).floor(); + let x = if direction == LayoutDirection::RightToLeft { + groove.x + groove.width - len + } else { + groove.x + }; + Rect::new(x, groove.y, len, groove.height) + } + Orientation::Vertical => { + let len = (value * groove.height).floor(); + Rect::new(groove.x, groove.y + groove.height - len, groove.width, len) + } + } +} + +/// `[Hp]` The highlight stripes of a busy bar's `groove`, moved `phase` of a +/// period along it: each `(start, end)` along the groove, clipped to it. +pub fn busy_stripes(length: f32, period: f32, phase: f32) -> Vec<(f32, f32)> { + if length <= 0.0 || period <= 0.0 { + return Vec::new(); + } + let half = period * 0.5; + let shift = phase.rem_euclid(1.0) * period; + let mut stripes = Vec::new(); + let mut start = shift - period; + while start < length { + let a = start.max(0.0); + let b = (start + half).min(length); + if b > a { + stripes.push((a, b)); + } + start += period; + } + stripes +} + +/// The `(along, length)` part of `groove` as a rectangle. +fn along(groove: Rect, orientation: Orientation, from: f32, to: f32) -> Rect { + match orientation { + Orientation::Horizontal => Rect::new(groove.x + from, groove.y, to - from, groove.height), + Orientation::Vertical => Rect::new(groove.x, groove.y + from, groove.width, to - from), + } +} + +/// Paints a Breeze progress bar: its groove and contents, or its stripes. +struct BreezeProgressBody { + orientation: Orientation, + value: Option>, + /// The busy stripes' position in their period, for an indeterminate + /// bar. + phase: Option>, + track: Option, + fill: Option, + metrics: BreezeProgressMetrics, +} + +impl std::fmt::Debug for BreezeProgressBody { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeProgressBody") + .field("orientation", &self.orientation) + .finish() + } +} + +impl Widget for BreezeProgressBody { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + if let Some(v) = &self.value { + v.register_if_bound(id, registry, BindingLevel::RepaintOnly); + } + if let Some(p) = &self.phase { + p.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + // The bar owns its size; this fills what it is given. + proposal.resolve(0.0, 0.0).into() + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let theme = ctx.theme; + let enabled = ctx.effective_enabled; + let colors = BreezeProgressColors::resolve( + theme, + ctx.prefers_high_contrast, + !enabled, + self.track.as_ref().map(|p| p.resolve(theme, enabled)), + self.fill.as_ref().map(|p| p.resolve(theme, enabled)), + ); + let groove = groove_rect(bounds, self.orientation, self.metrics.groove); + let pen = theme.shape.border_width; + let radius = CornerRadius::uniform(self.metrics.radius); + let inset = |r: Rect| { + Rect::new( + r.x + pen * 0.5, + r.y + pen * 0.5, + (r.width - pen).max(0.0), + (r.height - pen).max(0.0), + ) + }; + + if let Some(phase) = &self.phase { + // Busy: the lighter stripes fill the groove and the highlight + // stripes are laid over them. + let length = match self.orientation { + Orientation::Horizontal => groove.width, + Orientation::Vertical => groove.height, + }; + canvas.fill_rounded_rect(groove, radius, colors.busy_light); + for (a, b) in busy_stripes(length, self.metrics.busy_period, phase.get()) { + let stripe = along(groove, self.orientation, a, b); + // A stripe at an end of the groove keeps the groove's + // rounded corners there. + let mut corners = CornerRadius::ZERO; + let r = self.metrics.radius; + let (at_start, at_end) = (a <= 0.0, b >= length); + match self.orientation { + Orientation::Horizontal => { + if at_start { + corners.top_left = r; + corners.bottom_left = r; + } + if at_end { + corners.top_right = r; + corners.bottom_right = r; + } + } + Orientation::Vertical => { + if at_start { + corners.top_left = r; + corners.top_right = r; + } + if at_end { + corners.bottom_left = r; + corners.bottom_right = r; + } + } + } + canvas.fill_rounded_rect(stripe, corners, colors.busy); + } + return; + } + + let stroked = inset(groove); + canvas.fill_rounded_rect(stroked, radius, colors.groove); + if pen > 0.0 { + canvas.stroke_rounded_rect(stroked, radius, colors.groove_outline, pen); + } + let value = self.value.as_ref().map_or(0.0, Prop::get); + let contents = contents_rect(groove, self.orientation, ctx.layout_direction, value); + if contents.width > 0.0 && contents.height > 0.0 { + let stroked = inset(contents); + canvas.fill_rounded_rect(stroked, radius, colors.contents); + if pen > 0.0 { + canvas.stroke_rounded_rect(stroked, radius, colors.contents_outline, pen); + } + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the `ProgressBar` announces its value. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColor; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + /// Whether two colours are within one step a channel: Qt's raster + /// painter rounds an 8-bit blend its own way. + fn near(a: [u8; 4], b: [u8; 4]) -> bool { + a.iter().zip(b).all(|(x, y)| x.abs_diff(y) <= 1) + } + + #[test] + fn the_style_sizes_the_bar_and_paints_its_busy_indicator() { + let s = BreezeProgressBarStyle::default(); + assert_eq!(s.thickness(&InputTokens::default()), Some(26.0)); + assert!(s.paints_busy_indicator()); + assert_eq!(BreezeProgressBarStyle::for_theme(&crate::light()), s); + } + + /// The groove Qt laid out in a 26 px bar, 10 px down, and the one + /// Plasma painted in an 18 px sample, 6 px down. + #[test] + fn the_groove_is_centred_across_the_bar() { + let g = groove_rect( + Rect::new(0.0, 0.0, 160.0, 26.0), + Orientation::Horizontal, + 6.0, + ); + assert_eq!((g.x, g.y, g.width, g.height), (0.0, 10.0, 160.0, 6.0)); + let g = groove_rect( + Rect::new(0.0, 0.0, 200.0, 18.0), + Orientation::Horizontal, + 6.0, + ); + assert_eq!(g.y, 6.0); + let g = groove_rect(Rect::new(0.0, 0.0, 26.0, 91.0), Orientation::Vertical, 6.0); + assert_eq!((g.x, g.width, g.height), (10.0, 6.0, 91.0)); + } + + #[test] + fn the_contents_run_from_the_start_and_mirror() { + let g = Rect::new(0.0, 6.0, 200.0, 6.0); + let c = contents_rect( + g, + Orientation::Horizontal, + LayoutDirection::LeftToRight, + 0.5, + ); + assert_eq!((c.x, c.width), (0.0, 100.0)); + let c = contents_rect( + g, + Orientation::Horizontal, + LayoutDirection::RightToLeft, + 0.5, + ); + assert_eq!((c.x, c.width), (100.0, 100.0)); + let c = contents_rect( + g, + Orientation::Horizontal, + LayoutDirection::LeftToRight, + 0.333, + ); + assert_eq!(c.width, 66.0, "rounded down"); + let v = Rect::new(0.0, 0.0, 6.0, 100.0); + let c = contents_rect(v, Orientation::Vertical, LayoutDirection::LeftToRight, 0.25); + assert_eq!((c.y, c.height), (75.0, 25.0)); + } + + #[test] + fn the_stripes_are_fourteen_of_twenty_eight_and_move_with_the_phase() { + assert_eq!( + busy_stripes(60.0, 28.0, 0.0), + vec![(0.0, 14.0), (28.0, 42.0), (56.0, 60.0)] + ); + assert_eq!( + busy_stripes(60.0, 28.0, 0.5), + vec![(14.0, 28.0), (42.0, 56.0)] + ); + let shifted = busy_stripes(60.0, 28.0, 0.25); + assert_eq!(shifted[0], (7.0, 21.0)); + assert_eq!(shifted[1], (35.0, 49.0)); + // A whole period is the same picture. + assert_eq!(busy_stripes(60.0, 28.0, 1.0), busy_stripes(60.0, 28.0, 0.0)); + assert!(busy_stripes(0.0, 28.0, 0.3).is_empty()); + } + + /// The groove and contents Plasma painted in Breeze Light. + #[test] + fn breeze_lights_progress_colours_are_plasmas() { + let c = BreezeProgressColors::resolve(&crate::light(), false, false, None, None); + assert!(near(rgba8(c.groove), [210, 211, 213, 255]), "#d2d3d5"); + assert!(near(rgba8(c.contents), [114, 194, 235, 255]), "#72c2eb"); + assert_eq!(rgba8(c.busy), [61, 174, 233, 255]); + assert!((c.groove_outline.a() - 0.2).abs() < 1e-6); + assert!((c.contents_outline.a() - 0.2).abs() < 1e-6); + let light = rgba8(c.busy_light); + // 70 % of the way from the highlight to the window. + assert_eq!(light, [186, 220, 239, 255]); + } + + #[test] + fn overrides_and_the_disabled_state_change_the_colours() { + let theme = crate::light(); + let c = BreezeProgressColors::resolve( + &theme, + false, + false, + Some(Color::RED), + Some(Color::BLUE), + ); + assert_eq!(c.groove, Color::RED); + assert_eq!(c.busy, Color::BLUE); + let d = BreezeProgressColors::resolve(&theme, false, true, None, None); + assert_eq!(d.busy, theme.colors.accent_disabled); + let mut bare = theme.clone(); + bare.extensions.remove::(); + let b = BreezeProgressColors::resolve(&bare, false, false, None, None); + assert_eq!(b.busy, bare.colors.accent); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/radio.rs b/crates/teksilo-theme-kde/src/styles/radio.rs new file mode 100644 index 000000000..7d88de401 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/radio.rs @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze radio button. +//! +//! The check box's twin: a 20 px indicator holding a 15 px disc at 2.5, +//! filled with the button colour and outlined in the separator colour. +//! Selected, the disc is outlined in the highlight and filled again with +//! the highlight at 30 %, and a 6 px dot of the view's text sits at its +//! centre, growing to its size over the style's 100 ms. Pressed, the button +//! colour darkens 110 %; under the pointer a 1 px ring of the focus colour +//! lies on the outline. +//! +//! The row, the label position and the departures from Breeze are the check +//! box's: see [`crate::styles::checkbox`] (D8, the 18 px body) and +//! [`crate::styles::indicator`] (D5 and D6). +//! +//! ## Variants +//! +//! Breeze has one radio button. `Circle` is it; `Square` and `Rounded` +//! (`[unpublished]`) paint the disc as the check box's box at its 4 px and +//! the check box's `Rounded` 6 px radius, with the same dot. + +use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{CheckboxVariant, RadioStyle, RadioStyleConfig, RadioVariant}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_tokens::CornerRadius; +use teksilo_widgets::styles::recipe_radio_style::RADIO_LABEL_GAP; + +use crate::styles::checkbox::{box_paint, indicator_focus_frame, paint_box, variant_radius}; +use crate::styles::indicator::{BreezeIndicatorColors, indicator_rect, stroked_box_rect}; +use crate::styles::metrics::{INDICATOR_SIZE, RADIO_DOT_RADIUS, indicator_body_width}; + +/// Breeze `RadioStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeRadioStyle; + +impl RadioStyle for BreezeRadioStyle { + fn make_body(&self, cfg: &RadioStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let progress = ctx.animated_signal(if cfg.is_selected.get() { 1.0 } else { 0.0 }); + // `[S]` Breeze's style transitions: 100 ms, linear. + let grow = ctx.animate().fast().standard(); + { + let progress = progress.clone(); + ctx.effect(&cfg.is_selected, move |on| { + grow.to_or_snap(&progress, if *on { 1.0 } else { 0.0 }); + }); + } + let show_focus_frame = cfg + .is_focused + .zip(&cfg.is_disabled) + .map(|(focused, disabled)| *focused && !*disabled); + ctx.add(BreezeRadioBody { + selected: cfg.is_selected.clone(), + progress, + is_hovered: cfg.is_hovered.clone(), + is_pressed: cfg.is_pressed.clone(), + is_disabled: cfg.is_disabled.clone(), + show_focus_frame, + variant: cfg.variant, + }) + } +} + +struct BreezeRadioBody { + selected: Signal, + /// How far the dot has grown, 0 to 1. + progress: Signal, + is_hovered: Signal, + is_pressed: Signal, + is_disabled: Signal, + show_focus_frame: Signal, + variant: RadioVariant, +} + +impl std::fmt::Debug for BreezeRadioBody { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeRadioBody") + .field("variant", &self.variant) + .finish() + } +} + +/// The radius the disc of `variant` is stroked with. +pub fn variant_radius_of(variant: RadioVariant) -> f32 { + match variant { + RadioVariant::Circle => variant_radius(CheckboxVariant::Circle), + RadioVariant::Square => variant_radius(CheckboxVariant::Square), + RadioVariant::Rounded => variant_radius(CheckboxVariant::Rounded), + } +} + +/// `[Hp]` The dot's radius `progress` of the way to its full size. +pub fn dot_radius(progress: f32) -> f32 { + RADIO_DOT_RADIUS * progress.clamp(0.0, 1.0) +} + +impl Widget for BreezeRadioBody { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + self.progress + .bind_to(id, registry, BindingLevel::RepaintOnly); + for s in [ + &self.selected, + &self.is_hovered, + &self.is_pressed, + &self.is_disabled, + &self.show_focus_frame, + ] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + /// The check box's 18 px body; the widget raises the row (D8). + fn layout_response(&self, _proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + Size::new(indicator_body_width(RADIO_LABEL_GAP), INDICATOR_SIZE).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let selected = self.selected.get(); + let disabled = self.is_disabled.get(); + let colors = BreezeIndicatorColors::resolve( + ctx.theme, + ctx.window_active, + ctx.prefers_high_contrast, + disabled, + ); + let indicator = indicator_rect(bounds, ctx.layout_direction); + let pen = ctx.theme.shape.border_width; + let radius = variant_radius_of(self.variant); + let paint = box_paint( + selected, + self.is_hovered.get(), + self.is_pressed.get(), + disabled, + &colors, + ); + paint_box( + canvas, + stroked_box_rect(indicator, pen), + radius, + pen, + &paint, + ); + + let r = if selected { + dot_radius(self.progress.get()) + } else { + 0.0 + }; + if r > 0.0 && paint.mark.a() > 0.0 { + let centre = Point::new( + indicator.x + INDICATOR_SIZE * 0.5, + indicator.y + INDICATOR_SIZE * 0.5, + ); + canvas.fill_circle(centre, r, paint.mark); + } + + if self.show_focus_frame.get() { + let shape = &ctx.theme.shape; + if colors.focus_ring.a() > 0.0 && shape.focus_ring_width > 0.0 { + // The disc's frame is a circle: the box's radius is half of + // it, which the ring grows with. + let box_radius = if self.variant == RadioVariant::Circle { + (INDICATOR_SIZE * 0.5) - crate::styles::metrics::INDICATOR_BOX_INSET + } else { + radius + }; + let ring = indicator_focus_frame(indicator, box_radius, shape.focus_ring_width); + canvas.stroke_rounded_rect( + ring.rect, + CornerRadius::uniform(ring.corner_radius), + colors.focus_ring, + shape.focus_ring_width, + ); + } + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the `RadioButton` emits the radio node. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_dot_is_six_pixels_and_grows_with_the_progress() { + assert_eq!(dot_radius(1.0) * 2.0, 6.0); + assert_eq!(dot_radius(0.0), 0.0); + assert_eq!(dot_radius(0.5), 1.5); + assert_eq!(dot_radius(2.0), 3.0, "clamped"); + } + + #[test] + fn the_circle_is_breezes_disc() { + assert_eq!(variant_radius_of(RadioVariant::Circle), 7.5); + assert_eq!(variant_radius_of(RadioVariant::Square), 4.0); + assert!(variant_radius_of(RadioVariant::Rounded) > 4.0); + } + + #[test] + fn the_focus_frame_of_the_disc_is_a_circle_outside_it() { + let i = Rect::new(0.0, 0.0, 20.0, 20.0); + let ring = indicator_focus_frame(i, 8.0, 2.0); + assert_eq!(ring.rect, Rect::new(1.0, 1.0, 18.0, 18.0)); + assert_eq!(ring.corner_radius, 9.0, "half its side: a circle"); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/recipes.rs b/crates/teksilo-theme-kde/src/styles/recipes.rs new file mode 100644 index 000000000..0ada24a06 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/recipes.rs @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Breeze metrics for the widgets whose stock `Recipe*Style` already paints +//! the right shape: they only need Breeze's numbers. +//! +//! Every style here is the shipped Teksilo implementation built with a +//! Breeze recipe, not a reimplementation, the way the macOS preset's +//! `styles::metrics` builds its own. Each has a `_for(&InputTokens)` factory, +//! which [`crate::light`] and the other constructors install and the +//! density projection reruns, and a zero-argument twin at the Compact +//! density. +//! +//! Three Breeze rules give almost every value: +//! +//! - **One radius.** A frame in the page (a group box, a generic frame, a +//! button) and a window (a menu, a tooltip, a dialog) round at the 5 px +//! frame radius, [`BREEZE_CONTROL_RADIUS`] and [`BREEZE_POPUP_RADIUS`], +//! outlined in the 1 px pen, [`BREEZE_PEN_WIDTH`] (spec k.1, k.5.19, +//! k.5.20, k.6, k.7). +//! - **Three layout gaps.** A window's content lies 10 px inside it, a +//! nested group's 6 px inside its frame, and siblings stand 6 px apart +//! ([`LAYOUT_TOP_LEVEL_MARGIN`], [`LAYOUT_CHILD_MARGIN`], +//! [`LAYOUT_SPACING`], spec k.1 and k.7). +//! - **A popup list is a menu.** The rows of a search field's suggestion +//! list lie where Breeze lays out the items of a menu without icons +//! (spec k.5.13). +//! +//! A gap goes through the density's `spacing`, as every Breeze gap does in +//! [`crate::styles::metrics`]; a radius or a pen is painted and does not +//! scale. `[unpublished]` KDE publishes no touch ladder, so the ladder is +//! the framework's generic one. +//! +//! ## What is left at the stock recipe +//! +//! A field is set only when Breeze has a number for it **and** the widget +//! renders it. The rest keep the stock recipe's value, for one of four +//! reasons. +//! +//! - **The widget does not render it.** `engineering/docs/density-projection-gaps.md` +//! lists the projected fields a widget composes from the recipe module's +//! constant instead of the style, and a crate test holds every factory +//! here to that list: a value written there would never reach the +//! screen. So the segmented control's height and paddings, the +//! calendar's cell size, header height, outer padding, section gap and +//! cell gap, the search field's row height, panel padding and +//! input-to-panel gap, the toast's title and action gaps, the banner's +//! title gap and every avatar size stay the recipe's. The same holds for +//! the fields nothing reads at all, projected or not: the banner's and +//! toast's glyph sizes, the search field's glyph, glyph slot and two +//! radii, the calendar's navigation icon and arrow radius, and the +//! avatar's default ring width. +//! - **Breeze has no counterpart.** The specification records no Breeze +//! snackbar, toast, badge, avatar or segmented control, and no rule for a +//! dialog's minimum width, a toast's or a snackbar's padding, a badge's padding, the +//! avatar's shape or the drag-insertion line of a list. Those keep the +//! framework's values rather than invented ones. +//! - **Breeze's value would cost a target.** The calendar's header keeps +//! the recipe's gap between its arrows, narrower than Breeze's 6 px layout +//! spacing, which at the Touch density would leave the month title +//! narrower than WCAG 2.2's 24 px target. +//! - **Breeze's rule gives the stock value.** The calendar's navigation +//! arrow is a flat tool button around its chevron, the chevron plus 6 px +//! on each side ([`TOOL_BUTTON_MARGIN_WIDTH`]): around the 12 px chevron +//! the widget draws, that is the recipe's own 24. +//! +//! A card keeps the recipe's elevation too: Breeze casts no shadow from a +//! frame in the page (spec k.5.19), so an app that asks for an `Elevated` +//! card is asking for something Breeze does not draw, and gets the +//! framework's, in Breeze's shadow colours. + +use teksilo_core::styles::density::spacing; +use teksilo_tokens::InputTokens; +use teksilo_widgets::styles::{ + AvatarRecipe, BadgeRecipe, BannerRecipe, CalendarRecipe, CardRecipe, DialogRecipe, LinkRecipe, + PanelRecipe, RecipeAvatarStyle, RecipeBadgeStyle, RecipeBannerStyle, RecipeCalendarStyle, + RecipeCardStyle, RecipeDialogStyle, RecipeLinkStyle, RecipeListContainerStyle, + RecipePanelStyle, RecipeSearchFieldStyle, RecipeSegmentedControlStyle, RecipeSnackbarStyle, + RecipeSplitterStyle, RecipeToastStyle, SearchFieldRecipe, SegmentedControlRecipe, + SnackbarRecipe, SplitterRecipe, ToastRecipe, +}; + +#[cfg(doc)] +use crate::geometry::TOOL_BUTTON_MARGIN_WIDTH; +use crate::geometry::{ + LAYOUT_CHILD_MARGIN, LAYOUT_SPACING, LAYOUT_TOP_LEVEL_MARGIN, MENU_ITEM_EXTRA_LEFT_MARGIN, + MENU_ITEM_MARGIN, MENU_ITEM_TEXT_LEFT_MARGIN, SPLITTER_WIDTH, +}; +use crate::shape::{ + BREEZE_CONTROL_RADIUS, BREEZE_FOCUS_RING_WIDTH, BREEZE_PEN_WIDTH, BREEZE_POPUP_RADIUS, +}; + +/// `[derived]` How far a suggestion's text lies inside its row, on either +/// side: a menu item's contents start 4 px inside the item and 8 px more on +/// the leading side, and 4 px more again in a menu without icons (spec +/// k.5.13). Teksilo pads both sides alike. 16 at Compact. +pub const SUGGESTION_TEXT_INSET: f32 = + MENU_ITEM_MARGIN + MENU_ITEM_TEXT_LEFT_MARGIN + MENU_ITEM_EXTRA_LEFT_MARGIN; + +const _: () = assert!(SUGGESTION_TEXT_INSET == 16.0); + +/// `[derived]` The radius of the centre line of a link's focus ring: the +/// ring's inner edge rounds at the frame radius and its outer edge at the +/// frame radius plus the ring's width (spec k.5.8: 5 and 7), so the line +/// halfway between them rounds at the frame radius plus half the width. +/// 6 px. +pub const LINK_FOCUS_RING_RADIUS: f32 = BREEZE_CONTROL_RADIUS + BREEZE_FOCUS_RING_WIDTH / 2.0; + +const _: () = assert!(LINK_FOCUS_RING_RADIUS == 6.0); + +// ── Frames in the page ────────────────────────────────────────────────── + +/// `Card`: Breeze's group box (spec k.5.19), a frame at the frame radius +/// whose contents Qt lays out with the nested-layout margin. +pub fn breeze_card_style_for(tokens: &InputTokens) -> RecipeCardStyle { + RecipeCardStyle::new(CardRecipe { + // `[derived]` A framed group's layout takes the child margin (k.1, + // k.4 "Dialog margin ... child margin 6"). + padding: spacing(LAYOUT_CHILD_MARGIN, tokens), + // `[M]` The frame radius. + corner_radius: BREEZE_CONTROL_RADIUS, + // `[M]` The pen of every frame. + border_width: BREEZE_PEN_WIDTH, + ..CardRecipe::for_tokens(tokens) + }) +} + +/// `Panel`: Breeze's group box, as [`breeze_card_style_for`]. +pub fn breeze_panel_style_for(tokens: &InputTokens) -> RecipePanelStyle { + RecipePanelStyle::new(PanelRecipe { + // `[derived]` A framed group's layout takes the child margin. + padding: spacing(LAYOUT_CHILD_MARGIN, tokens), + // `[M]` The frame radius. + corner_radius: BREEZE_CONTROL_RADIUS, + // `[M]` The pen of every frame. + border_width: BREEZE_PEN_WIDTH, + }) +} + +/// `Banner`: an in-page notice, framed as a group box is and laid out as a +/// row of a nested layout. +pub fn breeze_banner_style_for(tokens: &InputTokens) -> RecipeBannerStyle { + RecipeBannerStyle::new(BannerRecipe { + // `[derived]` The child margin on every side, as a framed group. + padding_horizontal: spacing(LAYOUT_CHILD_MARGIN, tokens), + padding_vertical: spacing(LAYOUT_CHILD_MARGIN, tokens), + // `[derived]` A frame in the page rounds at the frame radius. + corner_radius: BREEZE_CONTROL_RADIUS, + // `[M]` The layout spacing between the glyph and the text. + content_gap: spacing(LAYOUT_SPACING, tokens), + ..BannerRecipe::for_tokens(tokens) + }) +} + +/// `Link`: the focus frame Breeze draws around a focused control, and an +/// underline in the 1 px pen. +/// +/// Breeze's focus frame is a 2 px ring from 1 px inside the control to 1 px +/// outside it, its inner edge at the frame radius and its outer edge 2 px +/// further out (spec k.5.8). The recipe strokes the ring centred on the +/// link's bounds in the theme's focus-ring width, which is that ring when +/// the stroke's centre line rounds at [`LINK_FOCUS_RING_RADIUS`]. +pub fn breeze_link_style_for(_tokens: &InputTokens) -> RecipeLinkStyle { + RecipeLinkStyle::new(LinkRecipe { + // `[derived]` The ring's centre line, on the link's bounds. + corner_radius: LINK_FOCUS_RING_RADIUS, + // `[derived]` Drawn in the pen of every Breeze line. + underline_thickness: BREEZE_PEN_WIDTH, + }) +} + +/// `Badge`: Breeze draws no pill, so a badge rounds at the frame radius like +/// every other Breeze frame (spec k.7). +pub fn breeze_badge_style_for(tokens: &InputTokens) -> RecipeBadgeStyle { + RecipeBadgeStyle::new(BadgeRecipe { + // `[derived]` The frame radius, in place of a pill. + corner_radius: BREEZE_CONTROL_RADIUS, + ..BadgeRecipe::for_tokens(tokens) + }) +} + +/// `SegmentedControl`: the frame of a Breeze button, at the frame radius in +/// the 1 px pen. +/// +/// Only the radius and the pen: the control lays its height and paddings out +/// from the recipe module's constants, so a value written here would not +/// reach the screen (`density-projection-gaps.md`, section 2). +pub fn breeze_segmented_control_style_for(tokens: &InputTokens) -> RecipeSegmentedControlStyle { + RecipeSegmentedControlStyle::new(SegmentedControlRecipe { + // `[M]` The frame radius. + corner_radius: BREEZE_CONTROL_RADIUS, + // `[M]` The pen of every frame. + border_width: BREEZE_PEN_WIDTH, + ..SegmentedControlRecipe::for_tokens(tokens) + }) +} + +/// `Splitter`: Breeze's 1 px splitter handle (spec k.5.21). +/// +/// The handle Breeze grabs is a 12 px proxy around the line (`breezerc` +/// `SplitterProxyWidth`); the `Splitter` widget widens its own gutter for the +/// pointer, which the style is not asked about. +pub fn breeze_splitter_style_for(_tokens: &InputTokens) -> RecipeSplitterStyle { + RecipeSplitterStyle::new(SplitterRecipe { + // `[M]` The splitter width. + divider_line_thickness: SPLITTER_WIDTH, + }) +} + +/// `ListView` and `TreeView` containers: the stock style, whose one +/// decoration is the drag-insertion line in the accent. +/// +/// `[unpublished]` The spec records no Breeze drop indicator, so the line +/// keeps the framework's 2 px; the style carries no dimension of its own and +/// the projection is the identity. +pub fn breeze_list_container_style_for(tokens: &InputTokens) -> RecipeListContainerStyle { + RecipeListContainerStyle::for_tokens(tokens) +} + +/// `Calendar`: a selected day is filled as a Breeze frame is rounded, at +/// the frame radius, and today's ring is the 1 px pen. (Breeze has no +/// calendar; an item-view row's selection, the nearest part it has, is +/// stroked at that radius, half a pixel rounder at its outer edge, +/// [`crate::styles::standard_item::selection_radius`].) +/// +/// The header's gap stays the recipe's: Breeze's 6 px layout spacing between +/// its four arrows would take the month title below a target's width at the +/// Touch density, where the arrows are 44 px. +pub fn breeze_calendar_style_for(tokens: &InputTokens) -> RecipeCalendarStyle { + RecipeCalendarStyle::new(CalendarRecipe { + // `[derived]` The frame radius. + cell_radius: BREEZE_CONTROL_RADIUS, + // `[derived]` A month or a year is selected as a day is. + zoom_cell_radius: BREEZE_CONTROL_RADIUS, + // `[M]` The pen of every frame. + today_ring_width: BREEZE_PEN_WIDTH, + ..CalendarRecipe::for_tokens(tokens) + }) +} + +/// `SearchField`: the suggestion list is a menu without icons, so a row's +/// text lies [`SUGGESTION_TEXT_INSET`] inside it across and the 4 px item +/// margin inside it down (spec k.5.13). The list already lies 4 px inside +/// its panel, the recipe's constant, which is Breeze's menu margin. +pub fn breeze_search_field_style_for(tokens: &InputTokens) -> RecipeSearchFieldStyle { + RecipeSearchFieldStyle::new(SearchFieldRecipe { + // `[derived]` A menu item's text inset, without icons. + row_padding_horizontal: spacing(SUGGESTION_TEXT_INSET, tokens), + // `[M]` The menu item margin. + row_padding_vertical: spacing(MENU_ITEM_MARGIN, tokens), + ..SearchFieldRecipe::for_tokens(tokens) + }) +} + +/// `Avatar`: Breeze has no avatar; only the presence dot's outline takes +/// the 1 px pen of every Breeze outline. +pub fn breeze_avatar_style_for(tokens: &InputTokens) -> RecipeAvatarStyle { + RecipeAvatarStyle::new(AvatarRecipe { + // `[derived]` The pen of every frame. + presence_outline_width: BREEZE_PEN_WIDTH, + ..AvatarRecipe::for_tokens(tokens) + }) +} + +// ── Windows ───────────────────────────────────────────────────────────── + +/// `Dialog`: a window, rounded as Breeze rounds a window's frame (spec +/// k.6), its content inside the top-level layout margin, Breeze's dialog +/// margin (spec k.4, k.7). +pub fn breeze_dialog_style_for(tokens: &InputTokens) -> RecipeDialogStyle { + RecipeDialogStyle::new(DialogRecipe { + // `[M]` The top-level layout margin. + content_padding: spacing(LAYOUT_TOP_LEVEL_MARGIN, tokens), + // `[Hp]` A window's radius. + corner_radius: BREEZE_POPUP_RADIUS, + ..DialogRecipe::for_tokens(tokens) + }) +} + +/// `Snackbar`: a floating surface, so a window's radius. +pub fn breeze_snackbar_style_for(tokens: &InputTokens) -> RecipeSnackbarStyle { + RecipeSnackbarStyle::new(SnackbarRecipe { + // `[derived]` A floating surface rounds as a window does. + corner_radius: BREEZE_POPUP_RADIUS, + ..SnackbarRecipe::for_tokens(tokens) + }) +} + +/// `Toast`: a floating surface, so a window's radius, its glyph, text and +/// close button the layout spacing apart. +pub fn breeze_toast_style_for(tokens: &InputTokens) -> RecipeToastStyle { + RecipeToastStyle::new(ToastRecipe { + // `[derived]` A floating surface rounds as a window does. + corner_radius: BREEZE_POPUP_RADIUS, + // `[M]` The layout spacing. + content_gap: spacing(LAYOUT_SPACING, tokens), + ..ToastRecipe::for_tokens(tokens) + }) +} + +// ── Compact twins ─────────────────────────────────────────────────────── + +/// [`breeze_card_style_for`] at the Compact density. +pub fn breeze_card_style() -> RecipeCardStyle { + breeze_card_style_for(&InputTokens::default()) +} +/// [`breeze_panel_style_for`] at the Compact density. +pub fn breeze_panel_style() -> RecipePanelStyle { + breeze_panel_style_for(&InputTokens::default()) +} +/// [`breeze_banner_style_for`] at the Compact density. +pub fn breeze_banner_style() -> RecipeBannerStyle { + breeze_banner_style_for(&InputTokens::default()) +} +/// [`breeze_link_style_for`] at the Compact density. +pub fn breeze_link_style() -> RecipeLinkStyle { + breeze_link_style_for(&InputTokens::default()) +} +/// [`breeze_badge_style_for`] at the Compact density. +pub fn breeze_badge_style() -> RecipeBadgeStyle { + breeze_badge_style_for(&InputTokens::default()) +} +/// [`breeze_segmented_control_style_for`] at the Compact density. +pub fn breeze_segmented_control_style() -> RecipeSegmentedControlStyle { + breeze_segmented_control_style_for(&InputTokens::default()) +} +/// [`breeze_splitter_style_for`] at the Compact density. +pub fn breeze_splitter_style() -> RecipeSplitterStyle { + breeze_splitter_style_for(&InputTokens::default()) +} +/// [`breeze_list_container_style_for`] at the Compact density. +pub fn breeze_list_container_style() -> RecipeListContainerStyle { + breeze_list_container_style_for(&InputTokens::default()) +} +/// [`breeze_calendar_style_for`] at the Compact density. +pub fn breeze_calendar_style() -> RecipeCalendarStyle { + breeze_calendar_style_for(&InputTokens::default()) +} +/// [`breeze_search_field_style_for`] at the Compact density. +pub fn breeze_search_field_style() -> RecipeSearchFieldStyle { + breeze_search_field_style_for(&InputTokens::default()) +} +/// [`breeze_avatar_style_for`] at the Compact density. +pub fn breeze_avatar_style() -> RecipeAvatarStyle { + breeze_avatar_style_for(&InputTokens::default()) +} +/// [`breeze_dialog_style_for`] at the Compact density. +pub fn breeze_dialog_style() -> RecipeDialogStyle { + breeze_dialog_style_for(&InputTokens::default()) +} +/// [`breeze_snackbar_style_for`] at the Compact density. +pub fn breeze_snackbar_style() -> RecipeSnackbarStyle { + breeze_snackbar_style_for(&InputTokens::default()) +} +/// [`breeze_toast_style_for`] at the Compact density. +pub fn breeze_toast_style() -> RecipeToastStyle { + breeze_toast_style_for(&InputTokens::default()) +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_tokens::TargetDensity; + + const DENSITIES: [(TargetDensity, f32); 3] = [ + (TargetDensity::Compact, 1.0), + (TargetDensity::Comfortable, 1.15), + (TargetDensity::Touch, 1.3), + ]; + + fn near(a: f32, b: f32) -> bool { + (a - b).abs() < 1e-4 + } + + /// The link's ring, stroked centred in the 2 px focus-ring width, has + /// Breeze's inner radius 5 and outer radius 7 (spec k.5.8); its + /// underline is the pen. + #[test] + fn a_links_focus_ring_has_breezes_inner_and_outer_radii() { + let link = breeze_link_style().recipe; + let half = BREEZE_FOCUS_RING_WIDTH / 2.0; + assert_eq!(link.corner_radius - half, 5.0); + assert_eq!(link.corner_radius + half, 7.0); + assert_eq!(link.underline_thickness, 1.0); + } + + #[test] + fn every_frame_rounds_at_the_frame_radius_in_the_one_pixel_pen() { + assert_eq!(breeze_card_style().recipe.corner_radius, 5.0); + assert_eq!(breeze_card_style().recipe.border_width, 1.0); + assert_eq!(breeze_panel_style().recipe.corner_radius, 5.0); + assert_eq!(breeze_panel_style().recipe.border_width, 1.0); + assert_eq!(breeze_banner_style().recipe.corner_radius, 5.0); + assert_eq!(breeze_badge_style().recipe.corner_radius, 5.0); + let s = breeze_segmented_control_style().recipe; + assert_eq!((s.corner_radius, s.border_width), (5.0, 1.0)); + let c = breeze_calendar_style().recipe; + assert_eq!((c.cell_radius, c.zoom_cell_radius), (5.0, 5.0)); + assert_eq!(c.today_ring_width, 1.0); + assert_eq!(breeze_splitter_style().recipe.divider_line_thickness, 1.0); + assert_eq!(breeze_avatar_style().recipe.presence_outline_width, 1.0); + } + + #[test] + fn windows_round_at_the_window_radius() { + assert_eq!(breeze_dialog_style().recipe.corner_radius, 5.0); + assert_eq!(breeze_snackbar_style().recipe.corner_radius, 5.0); + assert_eq!(breeze_toast_style().recipe.corner_radius, 5.0); + } + + /// The layout gaps at Compact are Breeze's, and every one scales with + /// the density's spacing; the painted radii and pens do not. + #[test] + fn the_gaps_are_breezes_and_follow_the_density() { + for (density, factor) in DENSITIES { + let t = InputTokens::for_density(density); + let child = 6.0 * factor; + assert!(near(breeze_card_style_for(&t).recipe.padding, child)); + assert!(near(breeze_panel_style_for(&t).recipe.padding, child)); + let b = breeze_banner_style_for(&t).recipe; + assert!(near(b.padding_horizontal, child) && near(b.padding_vertical, child)); + assert!(near(b.content_gap, 6.0 * factor)); + assert!(near( + breeze_dialog_style_for(&t).recipe.content_padding, + 10.0 * factor + )); + assert!(near( + breeze_toast_style_for(&t).recipe.content_gap, + 6.0 * factor + )); + let s = breeze_search_field_style_for(&t).recipe; + assert!(near(s.row_padding_horizontal, 16.0 * factor)); + assert!(near(s.row_padding_vertical, 4.0 * factor)); + assert_eq!(breeze_card_style_for(&t).recipe.corner_radius, 5.0); + assert_eq!(breeze_dialog_style_for(&t).recipe.corner_radius, 5.0); + } + } + + /// What Breeze has no number for is the stock recipe's, at every + /// density. + #[test] + fn what_breeze_does_not_say_is_the_stock_recipe() { + for (density, _) in DENSITIES { + let t = InputTokens::for_density(density); + let card = breeze_card_style_for(&t).recipe; + assert_eq!( + card.shadow_density, + CardRecipe::for_tokens(&t).shadow_density + ); + let dialog = breeze_dialog_style_for(&t).recipe; + assert_eq!(dialog.min_width, DialogRecipe::for_tokens(&t).min_width); + let snackbar = breeze_snackbar_style_for(&t).recipe; + let stock = SnackbarRecipe::for_tokens(&t); + assert_eq!( + (snackbar.padding_horizontal, snackbar.padding_vertical), + (stock.padding_horizontal, stock.padding_vertical) + ); + let toast = breeze_toast_style_for(&t).recipe; + let stock = ToastRecipe::for_tokens(&t); + assert_eq!( + (toast.padding_horizontal, toast.padding_vertical), + (stock.padding_horizontal, stock.padding_vertical) + ); + let badge = breeze_badge_style_for(&t).recipe; + let stock = BadgeRecipe::for_tokens(&t); + assert_eq!( + (badge.padding_horizontal, badge.padding_vertical), + (stock.padding_horizontal, stock.padding_vertical) + ); + let avatar = breeze_avatar_style_for(&t).recipe; + assert_eq!( + avatar.rounded_radius_ratio, + AvatarRecipe::for_tokens(&t).rounded_radius_ratio + ); + assert_eq!( + breeze_calendar_style_for(&t).recipe.header_gap, + CalendarRecipe::for_tokens(&t).header_gap + ); + // Breeze's tool-button rule around the recipe's chevron gives + // the recipe's own arrow. + assert_eq!( + breeze_calendar_style_for(&t).recipe.nav_arrow_size, + CalendarRecipe::for_tokens(&t).nav_arrow_size + ); + } + // The `Calendar` draws its navigation chevrons 12 px wide. + assert_eq!( + 12.0 + 2.0 * crate::geometry::TOOL_BUTTON_MARGIN_WIDTH, + CalendarRecipe::default().nav_arrow_size + ); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/rich_text_editor.rs b/crates/teksilo-theme-kde/src/styles/rich_text_editor.rs new file mode 100644 index 000000000..fc9dad049 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/rich_text_editor.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze frame of an editable `RichTextEditor`: the line edit's. +//! +//! The specification frames a text area, as it frames every scroll area, +//! with a fill of the view background in the input-frame outline stroked at +//! radius 4.5 (k.5.20), the outline the line edit is drawn in (k.5.3). So +//! an editable editor is framed by the line edit's own painter, +//! [`BreezeFieldChrome`]: a 1 px outline on the node inset by half the pen, +//! in `border_strong` at rest and `border_focused` while the editor has +//! focus, its width unchanged by focus. A `RichTextEditor::background` +//! replaces the view fill and keeps the outline. +//! +//! A read-only viewer is frameless, as the shipped recipe style leaves it: +//! a viewer sits on a surface that owns its chrome. The style hands it to +//! [`RecipeRichTextEditorStyle`], which honours the viewer's +//! `content_padding` and `background`. +//! +//! ## Where this departs from Breeze +//! +//! - **D6, the outline at rest**, as on the line edit +//! ([`crate::styles::text_input`]): the frame outline is 1.5:1 on the +//! Breeze Light window and the view fill 1.1:1, so the outline at rest is +//! `border_strong`, lifted to 3:1; focus is `border_focused`, lifted to +//! 3:1 (D5). +//! - **The text inset** (`[derived]`). The specification gives a text +//! area's frame its 2 px frame width and is silent on where the text +//! starts inside it. The text lies 6 px inside the frame on every side, +//! the line edit's text inset (k.5.3), through the density ladder, so an +//! editor's text lines up with a line edit's above it. An app's +//! `RichTextEditor::content_padding` replaces it. +//! - **No hover outline.** The editor gives its style no hover state, so +//! the outline does not turn to the hover colour under the pointer. + +use teksilo_core::build_context::BuildContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{ + RichTextEditorStyle, RichTextEditorStyleConfig, TextInputValidationLevel, +}; +use teksilo_core::widget_id::WidgetId; +use teksilo_widgets::primitives::{Padding, ZStack}; +use teksilo_widgets::styles::RecipeRichTextEditorStyle; + +use crate::styles::metrics::breeze_field_metrics_for; +use crate::styles::text_input::BreezeFieldChrome; + +/// Breeze `RichTextEditorStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeRichTextEditorStyle; + +impl RichTextEditorStyle for BreezeRichTextEditorStyle { + fn make_body(&self, cfg: &RichTextEditorStyleConfig, ctx: &mut BuildContext) -> WidgetId { + if cfg.is_read_only { + return RecipeRichTextEditorStyle.make_body(cfg, ctx); + } + let theme = ctx.theme(); + let inset = breeze_field_metrics_for(&theme.input, &theme.typography).padding_horizontal; + let mut chrome = BreezeFieldChrome::new( + cfg.is_focused.clone(), + Signal::new(false), + Signal::new(false), + Signal::new(TextInputValidationLevel::None), + false, + ); + if let Some(background) = cfg.background.clone() { + chrome = chrome.fill(background); + } + let chrome = ctx.add(chrome); + let (top, right, bottom, left) = + cfg.content_padding.unwrap_or((inset, inset, inset, inset)); + let padded = ctx.add(Padding::new(top, right, bottom, left).child(cfg.viewport)); + ctx.add(ZStack::new().child(chrome).child(padded)) + } +} diff --git a/crates/teksilo-theme-kde/src/styles/scroll_bar.rs b/crates/teksilo-theme-kde/src/styles/scroll_bar.rs new file mode 100644 index 000000000..86a17e2f3 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/scroll_bar.rs @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze scroll bar. +//! +//! Breeze paints no track. A permanent bar has a 1 px separator line on its +//! leading edge, the edge toward the content (the top of a horizontal bar, +//! the left of a vertical one, the right of a vertical one in a +//! right-to-left window), in the view's text at the frame contrast. The +//! handle is 8 px wide, centred across what the separator leaves, and +//! stroked with a 1 px pen on itself inset by half the pen, so it is painted +//! 7 px wide, at a radius of half its width, 4. Along the bar it is the +//! widget's own thumb, at least 20 px long, so the handle painted is the +//! one a press and a drag find. +//! +//! The handle is filled with the `scrollbar_thumb` token at rest and +//! `scrollbar_thumb_hover` while the pointer is over the bar or the handle +//! is dragged, and outlined in the window text at rest and the hover colour +//! otherwise, each at the frame contrast. An overlay or thin bar, which +//! lies over the content, has no separator. A bar with nothing to scroll +//! paints nothing. A thumb colour the app sets +//! (`ScrollBar::thumb_color`) takes the place of the window text and the +//! hover colour. +//! +//! ## Where this departs from Breeze +//! +//! - **D9, the handle's fill.** Breeze fills the idle handle with the window +//! text at an effective 17.5 % over the window (25 % while the pointer is +//! over the bar), 1.4:1, and a hovered one with the hover colour at 50 %; +//! the tokens lift both to 3:1, keeping their hue (D9 of the colour +//! mapping), and the handle is painted in them. Lifted, the idle and the +//! pointer-over-the-bar handles are one colour. +//! - **The hover colour follows the bar, not the handle.** Breeze turns the +//! handle to the hover colour when the pointer is over the handle itself; +//! the style is told only that the pointer is over the bar. +//! - **The handle runs to the bar's ends.** Breeze keeps 3 px free at each +//! end of a bar without buttons and runs the handle over what is left; +//! the widget hit-tests its thumb over the whole bar and gives its style +//! no say over the track, so the handle painted here starts at the top of +//! the bar, where a press finds it. +//! - **The bar is as thick as the widget lays it out**, not Breeze's 21 px: +//! the handle keeps its 8 px width and the separator its 1 px in whatever +//! thickness the bar is given (see the crate's known limitations). + +use teksilo_canvas::{Canvas, Rect, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::color_prop::ColorProp; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{ + ScrollBarOrientation, ScrollBarStyle, ScrollBarStyleConfig, ScrollBarVariant, Theme, +}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::{ColorState, PaletteRole}; +use teksilo_tokens::{Color, CornerRadius, InputTokens}; + +use crate::palette::KdePalette; +use crate::shape::BREEZE_SCROLL_HANDLE_RADIUS; +use crate::styles::metrics::{BreezeScrollBarMetrics, breeze_scroll_bar_metrics_for}; + +/// `[Hp]` The alpha a handle colour the app sets is painted at, at rest and +/// while the pointer is over the bar: Breeze's 0.5 x 0.7 x 0.5 and 0.5, the +/// effective alphas of its own handle. +pub const OVERRIDE_THUMB_ALPHA: [f32; 2] = [0.175, 0.5]; + +/// Breeze `ScrollBarStyle`. +/// +/// [`Default`] is the Compact density; the preset installs +/// [`BreezeScrollBarStyle::for_tokens`] of the theme it builds. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeScrollBarStyle { + /// The scroll-bar dimensions this style paints and reports. + pub metrics: BreezeScrollBarMetrics, +} + +impl BreezeScrollBarStyle { + /// The style at the density of `tokens`. + pub fn for_tokens(tokens: &InputTokens) -> Self { + Self { + metrics: breeze_scroll_bar_metrics_for(tokens), + } + } +} + +impl Default for BreezeScrollBarStyle { + fn default() -> Self { + Self::for_tokens(&InputTokens::default()) + } +} + +impl ScrollBarStyle for BreezeScrollBarStyle { + fn make_body(&self, cfg: &ScrollBarStyleConfig, ctx: &mut BuildContext) -> WidgetId { + ctx.add(BreezeScrollBarBody { + orientation: cfg.orientation, + separator: matches!(cfg.variant, ScrollBarVariant::Permanent), + min_thumb_length: cfg.min_thumb_length, + scroll_ratio: cfg.scroll_ratio.clone(), + viewport_ratio: cfg.viewport_ratio.clone(), + is_hovered: cfg.is_hovered.clone(), + is_dragging: cfg.is_dragging.clone(), + is_idle: cfg.is_idle.clone(), + thumb_color: cfg.thumb_color.clone(), + metrics: self.metrics, + }) + } + + /// [`BreezeScrollBarMetrics::min_handle_length`], 20 at every density. + fn min_thumb_length(&self, _tokens: &InputTokens) -> f32 { + self.metrics.min_handle_length + } +} + +/// The colours a Breeze scroll bar is painted in, in one paint state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeScrollBarColors { + /// `[wcag]` D9: the handle at rest, `scrollbar_thumb`. + pub thumb: Color, + /// `[wcag]` D9: the handle with the pointer over the bar or while it is + /// dragged, `scrollbar_thumb_hover`. + pub thumb_hover: Color, + /// `[Hp]` The handle's outline at rest: the window text at the frame + /// contrast. + pub outline: Color, + /// `[Hp]` Its outline otherwise: the hover colour at the frame contrast. + pub outline_hover: Color, + /// `[Hp]` The separator: the view's text at the frame contrast. + pub separator: Color, +} + +impl BreezeScrollBarColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference. + /// + /// The thumbs are the theme's projected tokens; the outlines and the + /// separator come from [`KdePalette`]. Without it the outlines are the + /// border and `border_focused`, and the separator the divider. + pub fn resolve( + theme: &Theme, + window_active: bool, + high_contrast: bool, + ) -> BreezeScrollBarColors { + let c = &theme.colors; + let Some(palette) = theme.extension::() else { + return BreezeScrollBarColors { + thumb: c.scrollbar_thumb, + thumb_hover: c.scrollbar_thumb_hover, + outline: c.border, + outline_hover: c.border_focused, + separator: c.divider, + }; + }; + let state = if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let fc = frame_contrast(palette, high_contrast); + BreezeScrollBarColors { + thumb: c.scrollbar_thumb, + thumb_hover: c.scrollbar_thumb_hover, + outline: palette.role(state, PaletteRole::WindowText).with_alpha(fc), + outline_hover: palette + .breeze_colors(state, high_contrast) + .hover + .with_alpha(fc), + separator: palette.role(state, PaletteRole::Text).with_alpha(fc), + } + } + + /// The handle's fill and outline: hovered or dragged, or at rest. + pub fn handle(&self, active: bool) -> (Color, Color) { + if active { + (self.thumb_hover, self.outline_hover) + } else { + (self.thumb, self.outline) + } + } +} + +/// `[derived]` The frame contrast: 1 in the high-contrast scheme the palette +/// builds, as `KdePalette::breeze_colors` computes it. +fn frame_contrast(palette: &KdePalette, high_contrast: bool) -> f32 { + if high_contrast { + 1.0 + } else { + palette.frame_contrast() as f32 + } +} + +/// The parts of a Breeze scroll bar across its thickness, in a bar whose +/// bounds are `bounds`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ScrollBarParts { + /// The separator line, when the bar has one. + pub separator: Option, + /// The band the handle runs in: its offset across the bar and its width. + pub handle_across: (f32, f32), +} + +/// `[Hp]` Where the separator and the handle lie across a bar: the separator +/// on the leading edge, the handle centred in what it leaves, at most the +/// handle width wide. +pub fn scroll_bar_parts( + bounds: Rect, + orientation: ScrollBarOrientation, + direction: LayoutDirection, + separator: bool, + m: &BreezeScrollBarMetrics, +) -> ScrollBarParts { + let sep = if separator { m.separator } else { 0.0 }; + let (start, thickness) = match orientation { + ScrollBarOrientation::Vertical => (bounds.x, bounds.width), + ScrollBarOrientation::Horizontal => (bounds.y, bounds.height), + }; + // The separator is on the edge toward the content: the top of a + // horizontal bar, the left of a vertical one, its right in a + // right-to-left window, where the bar sits left of the content. + let separator_first = !(orientation == ScrollBarOrientation::Vertical + && direction == LayoutDirection::RightToLeft); + let (sep_at, region_start) = if separator_first { + (start, start + sep) + } else { + (start + thickness - sep, start) + }; + let region = (thickness - sep).max(0.0); + let width = m.handle_width.min(region); + let offset = ((region - width) * 0.5).floor(); + let separator = separator.then(|| match orientation { + ScrollBarOrientation::Vertical => Rect::new(sep_at, bounds.y, sep, bounds.height), + ScrollBarOrientation::Horizontal => Rect::new(bounds.x, sep_at, bounds.width, sep), + }); + ScrollBarParts { + separator, + handle_across: (region_start + offset, width), + } +} + +/// The along-the-bar extent of the thumb in a track `track` long: its offset +/// and its length, the widget's own arithmetic, so the handle painted is the +/// one the bar hit-tests. +pub fn thumb_along(track: f32, viewport_ratio: f32, scroll_ratio: f32, min: f32) -> (f32, f32) { + let len = (track * viewport_ratio.clamp(0.0, 1.0)).max(min).min(track); + let offset = scroll_ratio.clamp(0.0, 1.0) * (track - len); + (offset, len) +} + +/// Paints a Breeze scroll bar: its separator and its handle. +struct BreezeScrollBarBody { + orientation: ScrollBarOrientation, + separator: bool, + min_thumb_length: f32, + scroll_ratio: Signal, + viewport_ratio: Signal, + is_hovered: Signal, + is_dragging: Signal, + is_idle: Signal, + thumb_color: Option, + metrics: BreezeScrollBarMetrics, +} + +impl std::fmt::Debug for BreezeScrollBarBody { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeScrollBarBody") + .field("orientation", &self.orientation) + .finish() + } +} + +impl Widget for BreezeScrollBarBody { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + for s in [&self.scroll_ratio, &self.viewport_ratio] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + for s in [&self.is_hovered, &self.is_dragging, &self.is_idle] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + Vec::new() + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + // The bar owns its thickness; this fills what it is given. + proposal.resolve(0.0, 0.0).into() + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + if self.is_idle.get() { + return; + } + let theme = ctx.theme; + let mut colors = + BreezeScrollBarColors::resolve(theme, ctx.window_active, ctx.prefers_high_contrast); + if let Some(prop) = &self.thumb_color { + let tint = prop.resolve(theme, true); + let fc = theme.extension::().map_or( + teksilo_kde_scheme::scheme::DEFAULT_FRAME_CONTRAST as f32, + |p| frame_contrast(p, ctx.prefers_high_contrast), + ); + colors.thumb = tint.with_alpha(OVERRIDE_THUMB_ALPHA[0]); + colors.thumb_hover = tint.with_alpha(OVERRIDE_THUMB_ALPHA[1]); + colors.outline = tint.with_alpha(fc); + colors.outline_hover = colors.outline; + } + let parts = scroll_bar_parts( + bounds, + self.orientation, + ctx.layout_direction, + self.separator, + &self.metrics, + ); + if let Some(line) = parts.separator { + canvas.fill_rect(line, colors.separator); + } + let (across, width) = parts.handle_across; + if width <= 0.0 { + return; + } + let track = match self.orientation { + ScrollBarOrientation::Vertical => bounds.height, + ScrollBarOrientation::Horizontal => bounds.width, + }; + let (offset, len) = thumb_along( + track, + self.viewport_ratio.get(), + self.scroll_ratio.get(), + self.min_thumb_length, + ); + let handle = match self.orientation { + ScrollBarOrientation::Vertical => Rect::new(across, bounds.y + offset, width, len), + ScrollBarOrientation::Horizontal => { + // The start of the content is the right-hand end of the + // track in a right-to-left window, where the bar's own hit + // test puts the thumb. + let x = if ctx.layout_direction == LayoutDirection::RightToLeft { + bounds.x + bounds.width - offset - len + } else { + bounds.x + offset + }; + Rect::new(x, across, len, width) + } + }; + let (fill, outline) = colors.handle(self.is_hovered.get() || self.is_dragging.get()); + let pen = theme.shape.border_width; + let half = pen * 0.5; + let stroked = Rect::new( + handle.x + half, + handle.y + half, + (handle.width - pen).max(0.0), + (handle.height - pen).max(0.0), + ); + // `[Hp]` Half the handle's narrower side, at most 4. + let radius = (0.5 * handle.width.min(handle.height)).min(BREEZE_SCROLL_HANDLE_RADIUS); + let corner = CornerRadius::uniform(radius); + canvas.fill_rounded_rect(stroked, corner, fill); + if pen > 0.0 && outline.a() > 0.0 { + canvas.stroke_rounded_rect(stroked, corner, outline, pen); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the bar announces itself. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColor; + use teksilo_kde_scheme::math::mix; + use teksilo_tokens::TargetDensity; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + fn m() -> BreezeScrollBarMetrics { + crate::styles::metrics::breeze_scroll_bar_metrics() + } + + /// Breeze's 21 px vertical bar: the separator in the leftmost column, + /// the handle from 7 to 15, as Plasma painted it. + #[test] + fn breezes_own_bar_puts_the_handle_six_pixels_from_the_separator() { + let p = scroll_bar_parts( + Rect::new(0.0, 0.0, 21.0, 200.0), + ScrollBarOrientation::Vertical, + LayoutDirection::LeftToRight, + true, + &m(), + ); + let sep = p.separator.unwrap(); + assert_eq!((sep.x, sep.width, sep.height), (0.0, 1.0, 200.0)); + assert_eq!(p.handle_across, (7.0, 8.0)); + // Right to left the separator moves to the right edge. + let p = scroll_bar_parts( + Rect::new(0.0, 0.0, 21.0, 200.0), + ScrollBarOrientation::Vertical, + LayoutDirection::RightToLeft, + true, + &m(), + ); + assert_eq!(p.separator.unwrap().x, 20.0); + assert_eq!(p.handle_across, (6.0, 8.0)); + // A horizontal bar's separator is its top line in either direction. + let p = scroll_bar_parts( + Rect::new(0.0, 0.0, 200.0, 21.0), + ScrollBarOrientation::Horizontal, + LayoutDirection::RightToLeft, + true, + &m(), + ); + let sep = p.separator.unwrap(); + assert_eq!((sep.y, sep.height, sep.width), (0.0, 1.0, 200.0)); + assert_eq!(p.handle_across, (7.0, 8.0)); + } + + #[test] + fn a_thinner_bar_keeps_the_handle_and_an_overlay_has_no_separator() { + let p = scroll_bar_parts( + Rect::new(100.0, 0.0, 12.0, 200.0), + ScrollBarOrientation::Vertical, + LayoutDirection::LeftToRight, + true, + &m(), + ); + assert_eq!(p.handle_across, (102.0, 8.0)); + let p = scroll_bar_parts( + Rect::new(100.0, 0.0, 12.0, 200.0), + ScrollBarOrientation::Vertical, + LayoutDirection::LeftToRight, + false, + &m(), + ); + assert!(p.separator.is_none()); + assert_eq!(p.handle_across, (102.0, 8.0)); + // Narrower than the handle: the handle fills it. + let p = scroll_bar_parts( + Rect::new(0.0, 0.0, 4.0, 200.0), + ScrollBarOrientation::Vertical, + LayoutDirection::LeftToRight, + false, + &m(), + ); + assert_eq!(p.handle_across, (0.0, 4.0)); + } + + #[test] + fn the_thumb_is_the_widgets_and_never_shorter_than_twenty() { + assert_eq!(thumb_along(200.0, 0.5, 0.0, 20.0), (0.0, 100.0)); + assert_eq!(thumb_along(200.0, 0.5, 1.0, 20.0), (100.0, 100.0)); + assert_eq!(thumb_along(200.0, 0.01, 0.5, 20.0), (90.0, 20.0)); + assert_eq!(thumb_along(10.0, 0.01, 0.5, 20.0), (0.0, 10.0)); + for density in [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ] { + let tokens = InputTokens::for_density(density); + assert_eq!( + BreezeScrollBarStyle::for_tokens(&tokens).min_thumb_length(&tokens), + 20.0 + ); + } + } + + /// The separator Plasma painted, the view's text at the frame contrast + /// over the window, and the thumbs are the lifted tokens. + #[test] + fn the_colours_are_plasmas_lines_and_the_lifted_thumbs() { + for (theme, separator) in [ + (crate::light(), [198, 200, 201, 255]), + (crate::dark(), [76, 78, 80, 255]), + ] { + let c = BreezeScrollBarColors::resolve(&theme, true, false); + let window = KdeColor::from_color(theme.colors.surface_main); + let over = |x: Color| { + rgba8( + mix( + window, + KdeColor::from_color(x.with_alpha(1.0)), + f64::from(x.a()), + ) + .to_color(), + ) + }; + let got = over(c.separator); + for (g, w) in got.iter().zip(separator) { + assert!(g.abs_diff(w) <= 1, "{got:?} against {separator:?}"); + } + assert_eq!(c.thumb, theme.colors.scrollbar_thumb); + assert_eq!(c.thumb_hover, theme.colors.scrollbar_thumb_hover); + assert_eq!(c.handle(false), (c.thumb, c.outline)); + assert_eq!(c.handle(true), (c.thumb_hover, c.outline_hover)); + assert!((c.outline.a() - 0.2).abs() < 1e-6); + } + } + + #[test] + fn the_bar_paints_from_the_roles_without_the_palette() { + let mut bare = crate::light(); + bare.extensions.remove::(); + let c = BreezeScrollBarColors::resolve(&bare, true, false); + assert_eq!(c.outline, bare.colors.border); + assert_eq!(c.separator, bare.colors.divider); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/slider.rs b/crates/teksilo-theme-kde/src/styles/slider.rs new file mode 100644 index 000000000..03e7be854 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/slider.rs @@ -0,0 +1,777 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze slider. +//! +//! Breeze's slider is 20 px thick. Its **groove** is 6 px, centred, as long +//! as the slider, filled and stroked on itself inset by half the pen at a +//! 3 px radius: the window text at 14 % over the window, outlined in the +//! window text at the frame contrast, and from the start up to the handle's +//! centre the highlight at 70 % over the window, outlined in the highlight at +//! the frame contrast. The start is the leading end of a horizontal groove +//! and the top of a vertical one, as Breeze fills a slider that is not +//! inverted. A `Slider` puts a vertical slider's minimum at the bottom, as +//! Qt does, so a vertical groove is filled on its maximum side, from the top +//! down to the handle. Its **handle** is a 20 px box, centred across the +//! groove and placed along it by the value over the length less 20, painted +//! as a 17 px circle (the box inset by 1, then stroked) in the button colour +//! with a 1 px outline: the slider outline at rest, the hover colour under +//! the pointer, the focus colour while focused. A 1 px ring of the shadow +//! colour, black at 12.5 %, shows at the handle's bottom right, unless the +//! handle is being dragged. A disabled slider shows only the groove's +//! background part, in the disabled colours. +//! +//! **Tick marks**, for a slider given a tick count, are 1 px lines 8 px long, +//! 2 px from the groove; they take the slider 3 px further, 23 px thick, and +//! are drawn in the highlight up to the value and in the separator colour +//! after it. Breeze draws them on the side the application asks for; a +//! `Slider` names no side, so they are below a horizontal slider and to the +//! right of a vertical one (`[unpublished]`), Qt's ticks below. +//! +//! Right to left, a horizontal slider runs from the right: the handle, the +//! fill and the ticks are mirrored, as the `Slider` mirrors its pointer and +//! arrow keys. +//! +//! The handle box is the knob the `Slider` maps the pointer with +//! ([`SliderStyle::thumb_diameter_for`]): 20 px at every density, the +//! distance Breeze divides the value over. The node is raised to the +//! density's target size across the groove (24 px at Compact), with the +//! 20 or 23 px of the slider centred in it; along the groove it is 84 px +//! when nothing proposes a length, Qt's size hint. +//! +//! ## Where this departs from Breeze +//! +//! - **D6, the handle's outline.** The slider outline, the button colour +//! moved toward the button text by the frame contrast, is 1.3:1 on the +//! Breeze Light window, and the groove is no further from the window; the +//! handle is the one part of the slider that marks where it is and what it +//! is set to, so its outline at rest is `border_strong`, lifted to SC +//! 1.4.11's 3:1 (D6 of the colour mapping). A scheme whose outline passes +//! keeps it. The groove keeps its colours. +//! - **D5, focus.** The focused outline is `border_focused`, the focus +//! colour lifted to 3:1, and keyboard focus adds the 2 px focus frame +//! around the handle in the opaque `focus_ring` token, from 1 px inside +//! the box to 1 px outside it, both edges circles. +//! - **D6, the tick marks.** The separator colour is 1.5:1 on the Breeze +//! Light window and Breeze Light's highlight 2.2:1, and a tick marks a +//! value the slider can take, so the ticks after the value are +//! `border_strong` and those up to it the highlight lifted to 3:1, as a +//! checked switch's outline ([`crate::styles::indicator`]). +//! - **Hover is the slider's.** Breeze outlines the handle in the hover +//! colour while the pointer is over the handle; the style is told only +//! whether the pointer is over the slider, so the outline turns while the +//! pointer is anywhere on it. Under high contrast the hover colour is +//! lifted to 4.5:1 on the control surfaces and the handle's face, as it +//! replaces the handle's lifted outline. +//! - **The node is the target size.** Breeze's 20 px slider is under WCAG +//! 2.2 SC 2.5.8's 24 px, so the node is raised; nothing painted grows. + +use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::focus::FocusOrigin; +use teksilo_core::signal::Signal; +use teksilo_core::styles::density::density_min_size; +use teksilo_core::styles::{ + FocusRingStroke, SliderOrientation, SliderStyle, SliderStyleConfig, Theme, +}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::{ColorState, PaletteRole}; +use teksilo_tokens::{Color, CornerRadius, InputTokens, TargetAxes}; + +use crate::color::high_contrast_boundary; +use crate::geometry::{ + SLIDER_CONTROL_THICKNESS, SLIDER_GROOVE_THICKNESS, SLIDER_HANDLE_PAINTED, SLIDER_TICK_LENGTH, + SLIDER_TICK_MARGIN, +}; +use crate::palette::KdePalette; +use crate::styles::chrome::kde_mix; +use crate::styles::indicator::BreezeIndicatorColors; +use crate::styles::metrics::{ + SLIDER_DEFAULT_LENGTH, SLIDER_FILL_ALPHA, SLIDER_GROOVE_ALPHA, SLIDER_GROOVE_RADIUS, + SLIDER_TICK_OVERHANG, +}; + +/// `[Hp]` How far the handle's circle lies inside its box, before it is +/// stroked. +const HANDLE_INSET: f32 = 1.0; +/// `[derived]` How far the handle's shadow ring is moved toward the bottom +/// right. Breeze draws it as an ellipse turned 45 degrees so that it shows +/// at the bottom right; the handle's own circle moved half a pixel each way +/// shows the same crescent. +const SHADOW_OFFSET: f32 = 0.5; +/// `[Hp]` The pen a tick mark is drawn with. +const TICK_PEN: f32 = 1.0; + +// The painted handle is the box inset by one and then by half the pen. +const _: () = assert!(SLIDER_HANDLE_PAINTED == SLIDER_CONTROL_THICKNESS - 2.0 * HANDLE_INSET - 1.0); + +/// Breeze `SliderStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeSliderStyle; + +impl SliderStyle for BreezeSliderStyle { + fn thumb_diameter(&self, _cfg: &SliderStyleConfig) -> f32 { + SLIDER_CONTROL_THICKNESS + } + + /// `[Hp]` The handle box at every density: Breeze places the handle by + /// the value over the slider's length less the box, which is what the + /// `Slider` maps the pointer with. The node, not the handle, grows to + /// the density's target size, and a press anywhere on it moves the + /// value. + fn thumb_diameter_for(&self, cfg: &SliderStyleConfig, _tokens: &InputTokens) -> f32 { + self.thumb_diameter(cfg) + } + + fn make_body(&self, cfg: &SliderStyleConfig, ctx: &mut BuildContext) -> WidgetId { + ctx.add(BreezeSliderBody { + value: cfg.value_normalized.clone(), + is_hovered: cfg.is_hovered.clone(), + is_dragging: cfg.is_dragging.clone(), + is_disabled: cfg.is_disabled.clone(), + focus_origin: cfg.focus_origin.clone(), + orientation: cfg.orientation, + tick_count: cfg.tick_count.filter(|n| *n >= 2), + }) + } +} + +/// The colours a Breeze slider is painted from, in one paint state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeSliderColors { + /// `[Hp]` The groove: the window text at 14 % over the window. + pub groove: Color, + /// `[Hp]` The groove's outline: the window text at the frame contrast. + pub groove_outline: Color, + /// `[Hp]` The groove up to the handle: the highlight at 70 % over the + /// window. + pub fill: Color, + /// `[Hp]` Its outline: the highlight at the frame contrast. + pub fill_outline: Color, + /// `[qt]` The handle's face, the button colour. + pub handle: Color, + /// `[wcag]` D6: the handle's outline at rest, `border_strong`; + /// `border_disabled` for a disabled slider. + pub handle_outline: Color, + /// `[Hp]` The handle's outline under the pointer: the view's hover + /// decoration, lifted to 4.5:1 on the control surfaces and the handle's + /// face under high contrast, where it stands in for the lifted outline. + pub hover: Color, + /// `[wcag]` D5: the handle's outline while focused, `border_focused`. + pub focus: Color, + /// `[wcag]` D5: the keyboard focus frame, `focus_ring`. + pub focus_ring: Color, + /// `[Hp]` The handle's shadow, black at 12.5 %. + pub shadow: Color, + /// `[wcag]` D6: a tick up to the value, the highlight lifted to the + /// non-text floor. + pub tick_on: Color, + /// `[wcag]` D6: a tick after the value, `border_strong`. + pub tick_off: Color, +} + +impl BreezeSliderColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference, for an enabled + /// or a disabled slider. + /// + /// The window, its text and the highlight are the theme's projected + /// tokens (the disabled palette's window and window text for a disabled + /// slider); the button colour, the frame contrast and the hover colour + /// come from [`KdePalette`]. Without it the button colour is the content + /// surface, the frame contrast Breeze's 0.2 and the hover colour + /// `border_focused`. + pub fn resolve( + theme: &Theme, + window_active: bool, + high_contrast: bool, + disabled: bool, + ) -> BreezeSliderColors { + let c = &theme.colors; + let palette = theme.extension::(); + let state = if disabled { + ColorState::Disabled + } else if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let (window, window_text) = match (disabled, palette) { + (true, Some(p)) => ( + p.role(ColorState::Disabled, PaletteRole::Window), + p.role(ColorState::Disabled, PaletteRole::WindowText), + ), + (true, None) => (c.surface_main, c.text_disabled), + (false, _) => (c.surface_main, c.text_primary), + }; + let highlight = if disabled { + c.accent_disabled + } else { + c.accent + }; + // `[derived]` The frame contrast: 1 in the high-contrast scheme the + // palette builds, as `KdePalette::breeze_colors` computes it. + let fc = match palette { + Some(_) if high_contrast => 1.0, + Some(p) => p.frame_contrast(), + None => teksilo_kde_scheme::scheme::DEFAULT_FRAME_CONTRAST, + } as f32; + let (handle, hover, shadow) = match palette { + Some(p) => { + let b = p.breeze_colors(state, high_contrast); + (b.button, b.hover, b.shadow) + } + None => ( + if disabled { + c.surface_disabled + } else { + c.surface_content + }, + c.border_focused, + theme.shape.shadow_xs.color, + ), + }; + let indicator = + BreezeIndicatorColors::resolve(theme, window_active, high_contrast, disabled); + BreezeSliderColors { + groove: kde_mix(window, window_text, SLIDER_GROOVE_ALPHA), + groove_outline: window_text.with_alpha(fc), + fill: kde_mix(window, highlight, SLIDER_FILL_ALPHA), + fill_outline: highlight.with_alpha(fc), + handle, + handle_outline: if disabled { + c.border_disabled + } else { + c.border_strong + }, + hover: high_contrast_boundary(hover, c, handle, high_contrast), + focus: c.border_focused, + focus_ring: c.focus_ring, + shadow, + tick_on: indicator.checked_outline, + tick_off: indicator.outline, + } + } + + /// `[Hp]` The handle's outline: disabled first, then focus, then hover, + /// then rest. + pub fn handle_outline_for(&self, focused: bool, hovered: bool, disabled: bool) -> Color { + if disabled { + self.handle_outline + } else if focused { + self.focus + } else if hovered { + self.hover + } else { + self.handle_outline + } + } +} + +/// Where a Breeze slider's parts lie in its node. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct SliderGeometry { + /// `[Hp]` The groove, 6 px thick and the node's full length, before it + /// is inset by half the pen. + pub groove: Rect, + /// `[Hp]` The handle's 20 px box. + pub handle: Rect, + /// `[Hp]` The groove from its start to the handle's centre, before it is + /// inset by half the pen: from the minimum's end of a horizontal groove, + /// and from the top, the maximum's end, of a vertical one. + pub fill: Rect, + /// The main-axis coordinate of the handle's centre at the value 0 and at + /// the value 1. + pub travel: (f32, f32), + /// The cross-axis coordinate of the groove's centre. + pub centre: f32, +} + +/// `[Hp]` The parts of a slider whose node is `bounds`, at the value +/// `value` (0 to 1), laid out `orientation`, `direction` and with or without +/// a row of tick marks. +pub fn slider_geometry( + bounds: Rect, + value: f32, + orientation: SliderOrientation, + direction: LayoutDirection, + ticks: bool, +) -> SliderGeometry { + let t = value.clamp(0.0, 1.0); + let box_ = SLIDER_CONTROL_THICKNESS; + let half = box_ * 0.5; + let thickness = slider_thickness(ticks); + match orientation { + SliderOrientation::Horizontal => { + // The slider (with its ticks below) centred across the node. + let top = bounds.y + (bounds.height - thickness) * 0.5; + let cy = top + half; + let (start, end) = match direction { + LayoutDirection::LeftToRight => (bounds.x + half, bounds.x + bounds.width - half), + LayoutDirection::RightToLeft => (bounds.x + bounds.width - half, bounds.x + half), + }; + let cx = start + (end - start) * t; + let groove = Rect::new( + bounds.x, + cy - SLIDER_GROOVE_THICKNESS * 0.5, + bounds.width, + SLIDER_GROOVE_THICKNESS, + ); + let fill = match direction { + LayoutDirection::LeftToRight => { + Rect::new(groove.x, groove.y, (cx - groove.x).max(0.0), groove.height) + } + LayoutDirection::RightToLeft => Rect::new( + cx, + groove.y, + (groove.x + groove.width - cx).max(0.0), + groove.height, + ), + }; + SliderGeometry { + groove, + handle: Rect::new(cx - half, cy - half, box_, box_), + fill, + travel: (start, end), + centre: cy, + } + } + SliderOrientation::Vertical => { + // The ticks are to the right, so the slider is centred with them. + let left = bounds.x + (bounds.width - thickness) * 0.5; + let cx = left + half; + let (start, end) = (bounds.y + bounds.height - half, bounds.y + half); + let cy = start + (end - start) * t; + let groove = Rect::new( + cx - SLIDER_GROOVE_THICKNESS * 0.5, + bounds.y, + SLIDER_GROOVE_THICKNESS, + bounds.height, + ); + // Breeze fills a vertical groove that is not inverted from its + // top, and a `Slider`'s maximum is at the top. + let fill = Rect::new(groove.x, groove.y, groove.width, (cy - groove.y).max(0.0)); + SliderGeometry { + groove, + handle: Rect::new(cx - half, cy - half, box_, box_), + fill, + travel: (start, end), + centre: cx, + } + } + } +} + +/// `[Hp]` `[measured-qt]` How thick a slider is: 20, and 23 with a row of +/// tick marks. +pub fn slider_thickness(ticks: bool) -> f32 { + if ticks { + SLIDER_CONTROL_THICKNESS + SLIDER_TICK_OVERHANG + } else { + SLIDER_CONTROL_THICKNESS + } +} + +/// `[Hp]` The tick mark for the stop `stop` of `count` (from the minimum) of +/// a slider laid out as `g`: a line 8 px long, 2 px from the groove, as the +/// two points it runs between. +pub fn tick_line( + g: &SliderGeometry, + orientation: SliderOrientation, + stop: u32, + count: u32, +) -> (Point, Point) { + let f = if count > 1 { + stop as f32 / (count - 1) as f32 + } else { + 0.0 + }; + let (start, end) = g.travel; + let at = start + (end - start) * f; + let near = g.centre + SLIDER_GROOVE_THICKNESS * 0.5 + SLIDER_TICK_MARGIN; + let far = near + SLIDER_TICK_LENGTH; + match orientation { + SliderOrientation::Horizontal => (Point::new(at, near), Point::new(at, far)), + SliderOrientation::Vertical => (Point::new(near, at), Point::new(far, at)), + } +} + +/// `[Hp]` A rectangle inset by half of `pen` on every side. +fn inset_half(r: Rect, pen: f32) -> Rect { + let d = pen * 0.5; + Rect::new( + r.x + d, + r.y + d, + (r.width - pen).max(0.0), + (r.height - pen).max(0.0), + ) +} + +struct BreezeSliderBody { + value: Signal, + is_hovered: Signal, + is_dragging: Signal, + is_disabled: Signal, + focus_origin: Signal>, + orientation: SliderOrientation, + tick_count: Option, +} + +impl std::fmt::Debug for BreezeSliderBody { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeSliderBody") + .field("orientation", &self.orientation) + .field("tick_count", &self.tick_count) + .finish() + } +} + +impl Widget for BreezeSliderBody { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + self.value.bind_to(id, registry, BindingLevel::RepaintOnly); + for s in [&self.is_hovered, &self.is_dragging, &self.is_disabled] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + self.focus_origin + .bind_to(id, registry, BindingLevel::RepaintOnly); + vec![] + } + + /// The proposed length (84 when none is), and the slider's thickness + /// raised to the density's target size. + fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { + let thickness = slider_thickness(self.tick_count.is_some()); + let (size, axes) = match self.orientation { + SliderOrientation::Horizontal => ( + Size::new(proposal.width.unwrap_or(SLIDER_DEFAULT_LENGTH), thickness), + TargetAxes::HEIGHT, + ), + SliderOrientation::Vertical => ( + Size::new(thickness, proposal.height.unwrap_or(SLIDER_DEFAULT_LENGTH)), + TargetAxes::WIDTH, + ), + }; + density_min_size(size, axes, &ctx.theme.input).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let disabled = self.is_disabled.get(); + let colors = BreezeSliderColors::resolve( + ctx.theme, + ctx.window_active, + ctx.prefers_high_contrast, + disabled, + ); + let pen = ctx.theme.shape.border_width; + let value = self.value.get().clamp(0.0, 1.0); + let g = slider_geometry( + bounds, + value, + self.orientation, + ctx.layout_direction, + self.tick_count.is_some(), + ); + let corner = CornerRadius::uniform(SLIDER_GROOVE_RADIUS); + + // The groove, then its part up to the handle. + let groove = inset_half(g.groove, pen); + canvas.fill_rounded_rect(groove, corner, colors.groove); + if pen > 0.0 && colors.groove_outline.a() > 0.0 { + canvas.stroke_rounded_rect(groove, corner, colors.groove_outline, pen); + } + let fill = inset_half(g.fill, pen); + if !disabled && fill.width > 0.0 && fill.height > 0.0 { + canvas.fill_rounded_rect(fill, corner, colors.fill); + if pen > 0.0 && colors.fill_outline.a() > 0.0 { + canvas.stroke_rounded_rect(fill, corner, colors.fill_outline, pen); + } + } + + // The ticks, up to the value in the highlight. + if let Some(n) = self.tick_count { + for stop in 0..n { + let f = stop as f32 / (n - 1) as f32; + let color = if f <= value + 1e-4 { + colors.tick_on + } else { + colors.tick_off + }; + let (a, b) = tick_line(&g, self.orientation, stop, n); + let rect = match self.orientation { + SliderOrientation::Horizontal => { + Rect::new(a.x - TICK_PEN * 0.5, a.y, TICK_PEN, b.y - a.y) + } + SliderOrientation::Vertical => { + Rect::new(a.x, a.y - TICK_PEN * 0.5, b.x - a.x, TICK_PEN) + } + }; + canvas.fill_rect(rect, color); + } + } + + // The handle: its shadow, its face and its outline. + let centre = Point::new( + g.handle.x + g.handle.width * 0.5, + g.handle.y + g.handle.height * 0.5, + ); + let radius = (g.handle.width * 0.5 - HANDLE_INSET - pen * 0.5).max(0.0); + if !self.is_dragging.get() && colors.shadow.a() > 0.0 && pen > 0.0 { + canvas.stroke_circle( + Point::new(centre.x + SHADOW_OFFSET, centre.y + SHADOW_OFFSET), + radius, + colors.shadow, + pen, + ); + } + canvas.fill_circle(centre, radius, colors.handle); + let focused = self.focus_origin.get().is_some(); + let outline = colors.handle_outline_for(focused, self.is_hovered.get(), disabled); + if pen > 0.0 && outline.a() > 0.0 { + canvas.stroke_circle(centre, radius, outline, pen); + } + + if self.focus_origin.get() == Some(FocusOrigin::Keyboard) && !disabled { + let shape = &ctx.theme.shape; + if colors.focus_ring.a() > 0.0 && shape.focus_ring_width > 0.0 { + let ring = slider_focus_frame(g.handle, shape.focus_ring_width); + canvas.stroke_rounded_rect( + ring.rect, + CornerRadius::uniform(ring.corner_radius), + colors.focus_ring, + shape.focus_ring_width, + ); + } + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the `Slider` emits the slider node. + builder.set_hidden(); + } +} + +/// `[Hp]` The keyboard focus frame around a slider's handle box `handle`: a +/// band `width` wide whose inner edge is the box inset by 1, a circle. +pub fn slider_focus_frame(handle: Rect, width: f32) -> FocusRingStroke { + let inner = Rect::new( + handle.x + HANDLE_INSET, + handle.y + HANDLE_INSET, + (handle.width - 2.0 * HANDLE_INSET).max(0.0), + (handle.height - 2.0 * HANDLE_INSET).max(0.0), + ); + FocusRingStroke::at_distance(inner, inner.width * 0.5, width * 0.5) +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColor; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + const LTR: LayoutDirection = LayoutDirection::LeftToRight; + const RTL: LayoutDirection = LayoutDirection::RightToLeft; + + /// Plasma's 200 x 20 slider at 30 % puts its groove at (0, 7, 200, 6) + /// and its handle box at (54, 0, 20, 20). + #[test] + fn the_groove_and_the_handle_are_where_qt_puts_them() { + let b = Rect::new(0.0, 0.0, 200.0, 20.0); + let g = slider_geometry(b, 0.3, SliderOrientation::Horizontal, LTR, false); + assert_eq!(g.groove, Rect::new(0.0, 7.0, 200.0, 6.0)); + assert!((g.handle.x - 54.0).abs() < 1e-4, "{:?}", g.handle); + assert_eq!( + (g.handle.y, g.handle.width, g.handle.height), + (0.0, 20.0, 20.0) + ); + assert_eq!(g.fill.x, 0.0); + assert_eq!(g.fill.width, g.handle.x + 10.0, "up to the handle's centre"); + } + + #[test] + fn right_to_left_mirrors_the_handle_and_the_fill() { + let b = Rect::new(0.0, 0.0, 200.0, 20.0); + let l = slider_geometry(b, 0.25, SliderOrientation::Horizontal, LTR, false); + let r = slider_geometry(b, 0.25, SliderOrientation::Horizontal, RTL, false); + assert_eq!(r.handle.x, 200.0 - l.handle.x - 20.0); + assert_eq!(r.fill.x + r.fill.width, 200.0, "from the right end"); + assert_eq!(r.fill.x, r.handle.x + 10.0); + // The stops mirror too. + let (a, _) = tick_line(&r, SliderOrientation::Horizontal, 0, 5); + assert_eq!(a.x, 190.0, "the minimum's tick at the right"); + } + + /// The handle's travel is the one the `Slider` maps the pointer over: + /// the length less the 20 px box. + #[test] + fn the_travel_is_the_widgets() { + for orientation in [SliderOrientation::Horizontal, SliderOrientation::Vertical] { + let b = Rect::new(10.0, 20.0, 200.0, 300.0); + let g = slider_geometry(b, 0.0, orientation, LTR, false); + let length = match orientation { + SliderOrientation::Horizontal => b.width, + SliderOrientation::Vertical => b.height, + }; + assert_eq!((g.travel.1 - g.travel.0).abs(), length - 20.0); + } + let style = BreezeSliderStyle; + let cfg = SliderStyleConfig { + value_normalized: Signal::new(0.0), + is_hovered: Signal::new(false), + is_dragging: Signal::new(false), + is_disabled: Signal::new(false), + focus_origin: Signal::new(None), + orientation: SliderOrientation::Horizontal, + tick_count: None, + variant: teksilo_core::styles::SliderVariant::Continuous, + }; + assert_eq!(style.thumb_diameter(&cfg), 20.0); + for density in [ + teksilo_tokens::TargetDensity::Compact, + teksilo_tokens::TargetDensity::Comfortable, + teksilo_tokens::TargetDensity::Touch, + ] { + assert_eq!( + style.thumb_diameter_for(&cfg, &InputTokens::for_density(density)), + 20.0 + ); + } + } + + /// A vertical slider's minimum is at the bottom, and its groove is + /// filled from the top down to the handle, as Breeze fills a vertical + /// slider that is not inverted (spec k.5.10). + #[test] + fn a_vertical_slider_fills_from_the_top() { + let b = Rect::new(0.0, 0.0, 20.0, 120.0); + let g = slider_geometry(b, 0.25, SliderOrientation::Vertical, LTR, false); + assert_eq!(g.travel, (110.0, 10.0)); + assert_eq!(g.handle.y + 10.0, 110.0 - 25.0); + assert_eq!(g.fill.y, 0.0, "from the groove's top"); + assert_eq!(g.fill.y + g.fill.height, g.handle.y + 10.0, "to the handle"); + assert_eq!(g.groove, Rect::new(7.0, 0.0, 6.0, 120.0)); + // At the minimum the whole groove above the handle is filled; at the + // maximum the fill is the half handle above its centre. + let low = slider_geometry(b, 0.0, SliderOrientation::Vertical, LTR, false); + assert_eq!(low.fill, Rect::new(7.0, 0.0, 6.0, 110.0)); + let high = slider_geometry(b, 1.0, SliderOrientation::Vertical, LTR, false); + assert_eq!(high.fill, Rect::new(7.0, 0.0, 6.0, 10.0)); + } + + /// Qt's slider with ticks below is 23 high: the ticks run from 5 to 13 + /// px below the groove's centre. + #[test] + fn the_ticks_lie_below_the_groove_and_add_three_pixels() { + assert_eq!(slider_thickness(false), 20.0); + assert_eq!(slider_thickness(true), 23.0); + let b = Rect::new(0.0, 0.0, 200.0, 23.0); + let g = slider_geometry(b, 0.5, SliderOrientation::Horizontal, LTR, true); + assert_eq!(g.centre, 10.0, "the slider at the top, its ticks below"); + let (a, z) = tick_line(&g, SliderOrientation::Horizontal, 0, 3); + assert_eq!((a, z), (Point::new(10.0, 15.0), Point::new(10.0, 23.0))); + let (a, _) = tick_line(&g, SliderOrientation::Horizontal, 2, 3); + assert_eq!(a.x, 190.0); + } + + /// Whether two colours are within one step a channel: Qt's raster + /// painter rounds an 8-bit blend its own way. + fn near(a: [u8; 4], b: [u8; 4]) -> bool { + a.iter().zip(b).all(|(x, y)| x.abs_diff(y) <= 1) + } + + /// The Breeze Light colours, against Plasma's painted samples. + #[test] + fn breeze_light_paints_the_specifications_colours() { + let t = crate::light(); + let c = BreezeSliderColors::resolve(&t, true, false, false); + assert!( + near(rgba8(c.groove), [210, 211, 213, 255]), + "{:?}", + rgba8(c.groove) + ); + assert!( + near(rgba8(c.fill), [114, 194, 235, 255]), + "{:?}", + rgba8(c.fill) + ); + assert_eq!(rgba8(c.handle), [252, 252, 252, 255]); + assert_eq!(rgba8(c.hover), [61, 174, 233, 255]); + assert_eq!(c.handle_outline, t.colors.border_strong); + assert_eq!(c.focus, t.colors.border_focused); + assert_eq!(c.groove_outline.a(), 0.2); + let d = BreezeSliderColors::resolve(&t, true, false, true); + assert!( + near(rgba8(d.groove), [217, 220, 221, 255]), + "{:?}", + rgba8(d.groove) + ); + assert_eq!(rgba8(d.handle), [240, 240, 240, 255]); + } + + #[test] + fn focus_wins_over_hover_and_disabled_over_both() { + let c = BreezeSliderColors::resolve(&crate::light(), true, false, false); + assert_eq!(c.handle_outline_for(false, false, false), c.handle_outline); + assert_eq!(c.handle_outline_for(false, true, false), c.hover); + assert_eq!(c.handle_outline_for(true, true, false), c.focus); + assert_eq!(c.handle_outline_for(true, true, true), c.handle_outline); + } + + /// D6: the handle's outline and the ticks reach 3:1 on the window, which + /// Breeze's slider outline, separator and Breeze Light's highlight do + /// not. + #[test] + fn the_handle_and_tick_colours_are_lifted_and_their_premises_hold() { + for t in [crate::light(), crate::dark()] { + let window = t.colors.surface_main; + let c = BreezeSliderColors::resolve(&t, true, false, false); + let b = t + .extension::() + .unwrap() + .colors(ColorState::Active); + assert!( + b.slider_outline.contrast_ratio(window) < 3.0, + "premise: Breeze's slider outline falls short" + ); + assert!(b.separator.contrast_ratio(window) < 3.0); + assert!(c.handle_outline.contrast_ratio(window) >= 3.0); + assert!(c.tick_off.contrast_ratio(window) >= 3.0); + assert!(c.tick_on.contrast_ratio(window) >= 3.0); + } + let t = crate::light(); + assert!(t.colors.accent.contrast_ratio(t.colors.surface_main) < 3.0); + } + + #[test] + fn the_focus_frame_is_a_circle_around_the_handle() { + let ring = slider_focus_frame(Rect::new(54.0, 0.0, 20.0, 20.0), 2.0); + assert_eq!(ring.rect, Rect::new(54.0, 0.0, 20.0, 20.0)); + assert_eq!(ring.corner_radius, 10.0); + } + + #[test] + fn a_theme_without_the_palette_paints_from_its_roles() { + let t = teksilo_core::presets::intui::light(); + let c = BreezeSliderColors::resolve(&t, true, false, false); + assert_eq!(c.handle, t.colors.surface_content); + assert_eq!(c.hover, t.colors.border_focused); + assert!((c.groove_outline.a() - 0.2).abs() < 1e-6); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/spin_box.rs b/crates/teksilo-theme-kde/src/styles/spin_box.rs new file mode 100644 index 000000000..9254ec060 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/spin_box.rs @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze spin box. +//! +//! A line edit ([`crate::styles::text_input`]) with a column of two arrows +//! at its trailing edge. The frame is the line edit's: the view colour in a +//! 1 px outline, the focus colour while the field has focus, the hover +//! colour under the pointer. The column is 20 px wide, 2 px inside the +//! trailing edge (the frame margin), and `min(inner height, 20)` tall, +//! centred, the inner height being the frame less the 6 px frame above and +//! below; it is split into an upper and a lower half, each half the column +//! rounded down, 10 and 10 at Plasma's default font +//! ([`crate::styles::metrics::BreezeSpinMetrics`]). Each half holds a +//! chevron ([`crate::styles::arrow`]), up and down, in the arrow colour of +//! the view's text, the hover colour on the half under the pointer. Breeze +//! draws no button in the column: no fill, no outline, no press. +//! +//! The spin box is the line edit's inner height plus the 6 px frame above +//! and below, 32 px at Plasma's default font, through the density ladder. +//! The field starts 6 px inside the leading edge and ends at the column. +//! Right to left, the column is at the left edge. +//! +//! The column's halves are the `SpinBox`'s own step buttons, which keep +//! their press, hold-to-repeat and pointer handling; the style places them +//! at Breeze's geometry and draws their arrows itself, painting the step +//! buttons' own chrome at zero opacity. +//! +//! ## Where this departs from Breeze +//! +//! - **D6, the outline at rest.** As on the line edit, the frame is +//! outlined in `border_strong` rather than in the frame outline, which is +//! 1.5:1 on the Breeze Light window; focus is `border_focused` (D5). +//! - **The hovered arrow under high contrast.** The chevron is a half's +//! only visual, and the view's hover decoration it turns to is 2.5:1 on +//! the Breeze Light view. Under high contrast it is lifted to 4.5:1 on the +//! view and the other control surfaces, as a hovered field's outline is; +//! in the normal projection it is the scheme's own. +//! - **An arrow keeps its colour at the limit.** Breeze draws the arrow +//! that can step no further in the disabled colours. The style is not told +//! the value's limits, so only a disabled spin box's arrows are in them; +//! an arrow at the limit does nothing when pressed. +//! - **The column's halves are under the target size.** Two 20 x 10 +//! halves are Breeze's geometry, below WCAG 2.2 SC 2.5.8's 24 px at every +//! density. The value is reachable with the arrow and page keys, the +//! mouse wheel and the assistive Increment and Decrement actions, the +//! criterion's "equivalent" exception. + +use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{ + ButtonLayout, SpinBoxStyle, SpinBoxStyleConfig, TextInputValidationLevel, Theme, +}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_builder::WidgetBuilder; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::{ColorState, PaletteRole}; +use teksilo_tokens::Color; + +use crate::color::high_contrast_boundary; +use crate::palette::KdePalette; +use crate::styles::arrow::{ArrowDirection, arrow_color, paint_arrow}; +use crate::styles::metrics::{BreezeSpinMetrics, breeze_spin_metrics_for}; +use crate::styles::text_input::BreezeFieldChrome; + +/// Breeze `SpinBoxStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeSpinBoxStyle; + +impl SpinBoxStyle for BreezeSpinBoxStyle { + fn make_body(&self, cfg: &SpinBoxStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let theme = ctx.theme(); + let metrics = breeze_spin_metrics_for(&theme.input, &theme.typography); + // The frame turns to the hover colour while the pointer is anywhere + // over the spin box, which is over one of its parts. + let hovered = Signal::new(false); + let chrome = ctx.add(BreezeFieldChrome::new( + cfg.is_focused.clone(), + hovered.clone(), + cfg.is_disabled.clone(), + Signal::new(TextInputValidationLevel::None), + false, + )); + let column = match (cfg.layout, cfg.step_up, cfg.step_down) { + (ButtonLayout::Stacked, Some(up), Some(down)) => { + let halves = [up, down].map(|step| { + // The step button keeps its handlers and its node; its + // own background and icon are not Breeze's. + ctx.set_opacity(step, 0.0); + let hover = Signal::new(false); + let half = ctx.add(BreezeSpinHalf { step }.hover_within(hover.clone())); + (half, hover) + }); + let [(up_half, up_hover), (down_half, down_hover)] = halves; + Some(ctx.add(BreezeSpinArrows { + up: up_half, + down: down_half, + up_hovered: up_hover, + down_hovered: down_hover, + is_disabled: cfg.is_disabled.clone(), + })) + } + _ => None, + }; + let layout = BreezeSpinLayout { + metrics, + chrome, + field: cfg.field, + column, + }; + ctx.add(layout.hover_within(hovered)) + } +} + +/// The colours a Breeze spin box's arrows are painted in. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeSpinArrowColors { + /// `[Hp]` An arrow at rest: the arrow colour of the view's text, on the + /// view; of the disabled view's text for a disabled spin box. + pub arrow: Color, + /// `[Hp]` The arrow of the half under the pointer: the view's hover + /// decoration, lifted under high contrast to 4.5:1 on the view and the + /// other control surfaces. + pub hover: Color, +} + +impl BreezeSpinArrowColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference, for an enabled + /// or a disabled spin box. + /// + /// Without a [`KdePalette`] the arrow is the primary text over the + /// content surface (the disabled text over the disabled surface) and the + /// hover colour `border_focused`. + pub fn resolve( + theme: &Theme, + window_active: bool, + high_contrast: bool, + disabled: bool, + ) -> BreezeSpinArrowColors { + let c = &theme.colors; + let Some(palette) = theme.extension::() else { + let arrow = if disabled { + arrow_color(c.text_disabled, c.surface_disabled) + } else { + arrow_color(c.text_primary, c.surface_content) + }; + return BreezeSpinArrowColors { + arrow, + hover: c.border_focused, + }; + }; + let state = if disabled { + ColorState::Disabled + } else if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + // The view's text and background: the projected tokens, which the + // high-contrast projection lifts, or the disabled view's. + let arrow = if disabled { + arrow_color( + palette.role(ColorState::Disabled, PaletteRole::Text), + palette.role(ColorState::Disabled, PaletteRole::Base), + ) + } else { + arrow_color(c.editor_fg, c.surface_content) + }; + // `[wcag]` The hovered half's chevron is its only visual: under high + // contrast the hover colour is lifted to 4.5:1 on the view it is + // drawn on and the other control surfaces, as a hovered field's + // outline is. + BreezeSpinArrowColors { + arrow, + hover: high_contrast_boundary( + palette.breeze_colors(state, high_contrast).hover, + c, + c.surface_content, + high_contrast, + ), + } + } +} + +/// `[S]` The arrow column of a spin box whose node is `bounds`: 20 px wide +/// at the trailing edge, inside the frame margin, the full height. +pub fn spin_column_rect(bounds: Rect, m: &BreezeSpinMetrics, rtl: bool) -> Rect { + let x = if rtl { + bounds.x + m.padding_trailing + } else { + bounds.x + bounds.width - m.padding_trailing - m.arrow_width + }; + Rect::new(x, bounds.y, m.arrow_width, bounds.height) +} + +/// `[S]` The upper and lower halves of the arrow column `column`, centred in +/// it. +pub fn spin_halves(column: Rect) -> (Rect, Rect) { + let half = BreezeSpinMetrics::arrow_half(column.height); + let top = column.y + (column.height - 2.0 * half) * 0.5; + ( + Rect::new(column.x, top, column.width, half), + Rect::new(column.x, top + half, column.width, half), + ) +} + +/// The arrangement of a Breeze spin box: the frame over the whole node, the +/// field from the leading inset to the column, the column at the trailing +/// edge. +struct BreezeSpinLayout { + metrics: BreezeSpinMetrics, + chrome: WidgetId, + field: WidgetId, + column: Option, +} + +impl std::fmt::Debug for BreezeSpinLayout { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeSpinLayout") + .field("metrics", &self.metrics) + .finish() + } +} + +impl BreezeSpinLayout { + /// Everything the frame adds to the field's width. + fn chrome_width(&self) -> f32 { + let m = &self.metrics; + match self.column { + Some(_) => m.padding_leading + m.arrow_width + m.padding_trailing, + // Without the column the field is inset on both sides. + None => 2.0 * m.padding_leading, + } + } +} + +impl Widget for BreezeSpinLayout { + fn build(&mut self, _ctx: &mut BuildContext) -> Vec { + self.children() + } + + fn children(&self) -> Vec { + [self.chrome, self.field] + .into_iter() + .chain(self.column) + .collect() + } + + /// The field plus the chrome when nothing proposes a width, the + /// proposed width otherwise; the Breeze height, or the field's if it is + /// taller. + fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { + let chrome = self.chrome_width(); + let field = ctx + .child_size( + self.field, + SizeProposal { + width: proposal.width.map(|w| (w - chrome).max(0.0)), + height: None, + }, + ) + .unwrap_or(Size::new(0.0, 0.0)); + Size::new( + proposal.width.unwrap_or(field.width + chrome), + self.metrics.height.max(field.height), + ) + .into() + } + + fn place_children( + &self, + bounds: Rect, + _proposal: SizeProposal, + children: &mut [WidgetPlacement], + ctx: &LayoutContext, + ) { + let m = &self.metrics; + let rtl = ctx.is_rtl(); + let column = spin_column_rect(bounds, m, rtl); + let field_width = (bounds.width - self.chrome_width()).max(0.0); + let field_x = if rtl { + bounds.x + bounds.width - m.padding_leading - field_width + } else { + bounds.x + m.padding_leading + }; + let field = Rect::new(field_x, bounds.y, field_width, bounds.height); + for child in children.iter_mut() { + let rect = if child.id == self.chrome { + bounds + } else if child.id == self.field { + field + } else { + column + }; + child.origin = Point::new(rect.x, rect.y); + child.size = Size::new(rect.width, rect.height); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Layout only: the `SpinBox` field carries the spin button's node. + builder.set_role(teksilo_core::accesskit::Role::GenericContainer); + } +} + +/// One half of the arrow column: holds a step button and gives it the whole +/// half, so its press region is Breeze's 20 x 10. +struct BreezeSpinHalf { + step: WidgetId, +} + +impl std::fmt::Debug for BreezeSpinHalf { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeSpinHalf").finish() + } +} + +impl Widget for BreezeSpinHalf { + fn build(&mut self, _ctx: &mut BuildContext) -> Vec { + vec![self.step] + } + + fn children(&self) -> Vec { + vec![self.step] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + bounds: Rect, + _proposal: SizeProposal, + children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + for child in children.iter_mut() { + child.origin = Point::new(bounds.x, bounds.y); + child.size = Size::new(bounds.width, bounds.height); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + builder.set_role(teksilo_core::accesskit::Role::GenericContainer); + } +} + +/// The arrow column of a Breeze spin box: places the two halves holding the +/// step buttons and draws their arrows. +struct BreezeSpinArrows { + up: WidgetId, + down: WidgetId, + up_hovered: Signal, + down_hovered: Signal, + is_disabled: Signal, +} + +impl std::fmt::Debug for BreezeSpinArrows { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeSpinArrows").finish() + } +} + +impl Widget for BreezeSpinArrows { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + for s in [&self.up_hovered, &self.down_hovered, &self.is_disabled] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![self.up, self.down] + } + + fn children(&self) -> Vec { + vec![self.up, self.down] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + bounds: Rect, + _proposal: SizeProposal, + children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + let (up, down) = spin_halves(bounds); + for child in children.iter_mut() { + let rect = if child.id == self.up { up } else { down }; + child.origin = Point::new(rect.x, rect.y); + child.size = Size::new(rect.width, rect.height); + } + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let disabled = self.is_disabled.get(); + let colors = BreezeSpinArrowColors::resolve( + ctx.theme, + ctx.window_active, + ctx.prefers_high_contrast, + disabled, + ); + let (up, down) = spin_halves(bounds); + let color = |hovered: bool| { + if hovered && !disabled { + colors.hover + } else { + colors.arrow + } + }; + paint_arrow(canvas, up, ArrowDirection::Up, color(self.up_hovered.get())); + paint_arrow( + canvas, + down, + ArrowDirection::Down, + color(self.down_hovered.get()), + ); + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Layout and paint only: the step buttons are the `SpinBox`'s own. + builder.set_role(teksilo_core::accesskit::Role::GenericContainer); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::styles::metrics::breeze_spin_metrics; + use teksilo_kde_scheme::KdeColor; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + /// Plasma's 100 x 32 spin box: the up half at (78, 6) and the down half + /// at (78, 16), each 20 x 10. + #[test] + fn the_column_and_its_halves_are_where_qt_puts_them() { + let m = breeze_spin_metrics(); + let b = Rect::new(0.0, 0.0, 100.0, 32.0); + let column = spin_column_rect(b, &m, false); + assert_eq!(column, Rect::new(78.0, 0.0, 20.0, 32.0)); + let (up, down) = spin_halves(column); + assert_eq!(up, Rect::new(78.0, 6.0, 20.0, 10.0)); + assert_eq!(down, Rect::new(78.0, 16.0, 20.0, 10.0)); + assert_eq!( + spin_column_rect(b, &m, true), + Rect::new(2.0, 0.0, 20.0, 32.0), + "mirrored right to left" + ); + } + + /// A taller frame keeps the 20 px column, centred. + #[test] + fn a_touch_frame_centres_the_same_halves() { + let (up, down) = spin_halves(Rect::new(0.0, 0.0, 20.0, 44.0)); + assert_eq!(up, Rect::new(0.0, 12.0, 20.0, 10.0)); + assert_eq!(down, Rect::new(0.0, 22.0, 20.0, 10.0)); + } + + #[test] + fn the_arrows_are_the_views_text_and_hover_colours() { + let t = crate::light(); + let c = BreezeSpinArrowColors::resolve(&t, true, false, false); + assert_eq!( + c.arrow, + arrow_color(t.colors.editor_fg, t.colors.surface_content) + ); + assert_eq!(rgba8(c.hover), [61, 174, 233, 255]); + let p = t.extension::().unwrap(); + let d = BreezeSpinArrowColors::resolve(&t, true, false, true); + assert_eq!( + d.arrow, + arrow_color( + p.role(ColorState::Disabled, PaletteRole::Text), + p.role(ColorState::Disabled, PaletteRole::Base), + ) + ); + } + + #[test] + fn a_theme_without_the_palette_paints_from_its_roles() { + let t = teksilo_core::presets::intui::light(); + let c = BreezeSpinArrowColors::resolve(&t, true, false, false); + assert_eq!( + c.arrow, + arrow_color(t.colors.text_primary, t.colors.surface_content) + ); + assert_eq!(c.hover, t.colors.border_focused); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/split_button.rs b/crates/teksilo-theme-kde/src/styles/split_button.rs new file mode 100644 index 000000000..b2fffb9a4 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/split_button.rs @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze frame of a `SplitButton`. +//! +//! A `SplitButton` is a push button with a menu, and its frame is the Breeze +//! push button's ([`crate::styles::chrome`]): the same fill, outline, +//! highlight under the pointer or keyboard focus, press and 1 px shadow, +//! around the widget's own row (the action, a divider, the menu chevron). It +//! is as tall as a push button, 34 px at Plasma's default font, and at least +//! as wide, 84 px ([`crate::styles::metrics`]). +//! +//! | `ButtonVariant` | Frame | +//! | --- | --- | +//! | `Plain` (default) / `Tinted` / `Outlined` | the push button | +//! | `Ghost` / `Link` | the flat button | +//! | `Filled` / `Destructive` | the push button filled with the accent | +//! +//! ## The filled variants +//! +//! A `SplitButton` labels its `Filled` and `Destructive` variants in the +//! on-accent text and gives its style no say over the label. Breeze has no +//! accent-filled button, and its default button's light fill under the +//! on-accent text would be unreadable in a dark scheme (black on Breeze +//! Dark's 45,70,85), so those two variants are framed in the accent itself, +//! with its hover, press and disabled shades, which the on-accent text is +//! lifted to read on (D1 of the colour mapping). This is the one place the +//! Breeze styles paint an accent fill (`[unpublished]`). A `Button`'s +//! `Filled` variant is the Breeze default button instead, because its style +//! chooses the label. + +use teksilo_core::build_context::BuildContext; +use teksilo_core::styles::{ButtonVariant, SplitButtonStyle, SplitButtonStyleConfig}; +use teksilo_core::widget_id::WidgetId; +use teksilo_widgets::primitives::{MinSize, ZStack}; + +use crate::styles::chrome::{BreezeButtonChrome, BreezeButtonKind, BreezeButtonState}; +use crate::styles::metrics::breeze_button_metrics_for; + +/// Breeze `SplitButtonStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeSplitButtonStyle; + +impl BreezeSplitButtonStyle { + /// The Breeze button a split button of `variant` is framed as. + pub fn kind(variant: ButtonVariant) -> BreezeButtonKind { + match variant { + ButtonVariant::Plain | ButtonVariant::Tinted | ButtonVariant::Outlined => { + BreezeButtonKind::Push + } + ButtonVariant::Ghost | ButtonVariant::Link => BreezeButtonKind::Flat, + ButtonVariant::Filled | ButtonVariant::Destructive => BreezeButtonKind::Accent, + } + } +} + +impl SplitButtonStyle for BreezeSplitButtonStyle { + fn make_body(&self, cfg: &SplitButtonStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let theme = ctx.theme(); + let m = breeze_button_metrics_for(&theme.input, &theme.typography); + let state = BreezeButtonState::derive( + &cfg.is_disabled, + &cfg.is_pressed, + &cfg.is_hovered, + &cfg.is_focused, + ); + let show_focus_frame = cfg + .is_focused + .zip(&cfg.is_disabled) + .map(|(focused, disabled)| *focused && !*disabled); + let chrome = ctx.add(BreezeButtonChrome::new( + Self::kind(cfg.variant), + state, + show_focus_frame, + )); + let stack = ctx.add(ZStack::new().child(chrome).child(cfg.content)); + ctx.add(MinSize::new(m.min_width, m.height).child(stack)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_variants_map_onto_the_frames() { + for v in [ + ButtonVariant::Plain, + ButtonVariant::Tinted, + ButtonVariant::Outlined, + ] { + assert_eq!(BreezeSplitButtonStyle::kind(v), BreezeButtonKind::Push); + } + for v in [ButtonVariant::Ghost, ButtonVariant::Link] { + assert_eq!(BreezeSplitButtonStyle::kind(v), BreezeButtonKind::Flat); + } + for v in [ButtonVariant::Filled, ButtonVariant::Destructive] { + assert_eq!(BreezeSplitButtonStyle::kind(v), BreezeButtonKind::Accent); + } + } + + #[test] + fn the_on_accent_label_reads_on_every_accent_frame() { + use teksilo_tokens::contrast::composite_contrast; + for t in [crate::light(), crate::dark()] { + let c = &t.colors; + for fill in [c.accent, c.accent_hover, c.accent_pressed] { + assert!(composite_contrast(c.text_on_accent, fill) >= 4.5); + } + } + } +} diff --git a/crates/teksilo-theme-kde/src/styles/standard_item.rs b/crates/teksilo-theme-kde/src/styles/standard_item.rs new file mode 100644 index 000000000..2385fd840 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/standard_item.rs @@ -0,0 +1,647 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze list and tree row, `StandardListItem` and `StandardTreeItem`. +//! +//! A row at rest paints nothing. Its selection is a rounded rectangle 2 px +//! inside the row's leading and trailing edges and 1 px inside its top and +//! bottom, the item margins, stroked with a 1 px pen on that rectangle inset +//! by half the pen at radius 5, the frame radius itself +//! ([`selection_radius`]): +//! +//! - **selected:** filled and outlined in the highlight, the row's label, +//! subtitle and chevron in the text on it +//! ([`StandardItemStyle::selected_label_role`]); +//! - **hovered and selected:** the highlight lightened 110 %; +//! - **hovered:** the highlight at 30 % over the view, outlined in the +//! highlight at 80 %; +//! - **disabled:** never hovered; a selected disabled row in the disabled +//! highlight. +//! +//! Qt picks the colour group by window, so a selection mutes only when the +//! window goes inactive, never when the view loses keyboard focus +//! ([`SelectionEmphasis::WindowActive`]): an inactive window paints the +//! scheme's inactive highlight, for Breeze the window colour tinted toward +//! the selection colour, with the row's label in the window text. +//! +//! A row of text is 24 px at Plasma's default font, a row with an icon or a +//! check box 26 px, as Qt measures them, and a row with a subtitle 42 px, +//! all through the density ladder ([`BreezeRowMetrics`]). The content lies +//! 7 px inside the leading and trailing edges, where Qt puts a row's check +//! box, and 1 px inside the top and bottom. The row arithmetic is the +//! shipped [`RecipeStandardItemStyle`]'s, built with these numbers and its +//! own chrome held off, so the selection painted here is the only one. +//! +//! ## Where this departs from Breeze +//! +//! - **D1, the selection fill.** Breeze writes a selected row in its +//! selection text; Teksilo's text on the accent is chosen against the +//! accent (D1 of the colour mapping), so a selected row is filled with +//! `accent`, which [`TextRole::OnAccent`] is readable on, and a hovered +//! one is washed with `surface_hover`, the highlight at 30 % over the +//! view moved until the primary text is readable on it. In the stock +//! Breeze schemes both are Breeze's own colours. +//! - **Keyboard focus** on the selected row of a focused view adds the 2 px +//! focus frame (D5) around the selection, as on the other controls; +//! Breeze draws no frame on a selected row, and Teksilo's rows have no way +//! to tell the current row from the selected one. +//! - **A pressed row** paints as it does at rest or hovered: Breeze has no +//! pressed item. +//! - **The first row is not one taller.** Breeze gives the first row of a +//! view without a frame a 2 px top margin, where every other row has 1, +//! so the first text row is 25 px with its selection 2 px from the top +//! ([`BreezeGeometry::first_row_extra`](crate::geometry::BreezeGeometry::first_row_extra)). +//! A row is not told its index, so every row is laid out as the others, +//! 24 px with its selection 1 px from the top. + +use teksilo_canvas::{Canvas, Point, Rect, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{SelectionEmphasis, StandardItemStyle, StandardItemStyleConfig, Theme}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_builder::WidgetBuilder; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::KdeColor; +use teksilo_kde_scheme::math::hsv_lighter; +use teksilo_tokens::{Color, CornerRadius, InputTokens, ShapeTokens, TextRole, TypographyTokens}; +use teksilo_widgets::styles::{RecipeStandardItemStyle, StandardItemRecipe}; + +use crate::geometry::SMALL_ICON_SIZE; +use crate::shape::{BREEZE_FOCUS_RING_WIDTH, BREEZE_STROKED_FRAME_RADIUS}; +use crate::styles::chrome::paint_focus_frame; +use crate::styles::metrics::{ + BreezeRowMetrics, ROW_HOVER_OUTLINE_ALPHA, ROW_SELECTED_HOVER_LIGHTER, breeze_row_metrics_for, +}; + +/// The Breeze [`StandardItemRecipe`] at the density of `tokens`, for +/// `typography`: the row arithmetic of [`RecipeStandardItemStyle`] with the +/// dimensions of [`BreezeRowMetrics`]. +/// +/// The slot gaps and the tree indent are the recipe's own: `StandardListItem` +/// lays them out from its own constants, so a value written here would not +/// reach the row. +pub fn breeze_standard_item_recipe_for( + tokens: &InputTokens, + typography: &TypographyTokens, +) -> StandardItemRecipe { + let m = breeze_row_metrics_for(tokens, typography); + StandardItemRecipe { + // `[K]` The small icon size. + icon_size: SMALL_ICON_SIZE, + padding_horizontal: m.padding_horizontal, + padding_vertical: m.padding_vertical, + min_height_single_line: m.height, + min_height_two_line: m.height_two_line, + // `[Hp]` Only the radius of the recipe's own chrome, which this style + // holds off. + item_corner_radius: BREEZE_STROKED_FRAME_RADIUS, + bg_horizontal_inset: m.selection_inset_horizontal, + focus_ring_width: BREEZE_FOCUS_RING_WIDTH, + // The selection painted here is the only boundary. + selection_edge_width: 0.0, + ..StandardItemRecipe::for_tokens(tokens) + } +} + +/// [`breeze_standard_item_recipe_for`] at the Compact density and the Breeze +/// typography. +pub fn breeze_standard_item_recipe() -> StandardItemRecipe { + breeze_standard_item_recipe_for( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) +} + +/// Breeze `StandardItemStyle`. +/// +/// [`Default`] is the Compact density and the Breeze typography; the preset +/// installs [`BreezeStandardItemStyle::for_theme`] of the theme it builds, +/// so the row heights follow the density the theme is projected to. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeStandardItemStyle { + /// The row dimensions this style lays out and reports. + pub metrics: BreezeRowMetrics, + /// The recipe the row's content is laid out with. + pub recipe: StandardItemRecipe, +} + +impl BreezeStandardItemStyle { + /// The style at the density of `tokens`, for `typography`. + pub fn for_tokens(tokens: &InputTokens, typography: &TypographyTokens) -> Self { + Self { + metrics: breeze_row_metrics_for(tokens, typography), + recipe: breeze_standard_item_recipe_for(tokens, typography), + } + } + + /// The style at the density and typography of `theme`. + pub fn for_theme(theme: &Theme) -> Self { + Self::for_tokens(&theme.input, &theme.typography) + } +} + +impl Default for BreezeStandardItemStyle { + fn default() -> Self { + Self::for_tokens( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) + } +} + +impl StandardItemStyle for BreezeStandardItemStyle { + fn make_body(&self, cfg: &StandardItemStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let selection = BreezeRowSelection { + is_selected: cfg.is_selected.clone(), + is_hovered: cfg.is_hovered.clone(), + is_disabled: cfg.is_disabled.clone(), + emphasised: self + .selection_emphasis() + .emphasised(&cfg.is_focused, &cfg.is_window_active), + show_focus: cfg + .is_selected + .zip3(&cfg.is_focused, &cfg.is_focus_visible) + .map(|(selected, focused, visible)| *selected && *focused && *visible), + inset_horizontal: self.metrics.selection_inset_horizontal, + inset_vertical: self.metrics.selection_inset_vertical, + }; + // The selection is listed first, so it paints behind the row; it is + // still a sibling the hit walk visits, and it spans the whole row, so + // it is taken out of hit testing and a press reaches the row's own + // controls (a check box) wherever it lands. + let selection = ctx.add(selection.hit_transparent(true)); + + // The recipe's row, with its interaction signals held low so its own + // chrome never paints under this selection. + let quiet = Signal::new(false); + let inner = StandardItemStyleConfig { + content: cfg.content, + is_selected: quiet.clone(), + is_hovered: quiet.clone(), + is_pressed: quiet, + is_focused: cfg.is_focused.clone(), + is_focus_visible: cfg.is_focus_visible.clone(), + is_disabled: cfg.is_disabled.clone(), + is_window_active: cfg.is_window_active.clone(), + }; + let row = RecipeStandardItemStyle::new(self.recipe).make_body(&inner, ctx); + ctx.add(BreezeRowFrame { selection, row }) + } + + /// `[wcag]` D1: the text on the selection's accent fill. + fn selected_label_role(&self) -> Option { + Some(TextRole::OnAccent) + } + + /// `[qt]` Qt picks a palette colour group per window, so only the window + /// mutes a selection. + fn selection_emphasis(&self) -> SelectionEmphasis { + SelectionEmphasis::WindowActive + } + + /// [`BreezeRowMetrics::height`]. The style was built at a density, so + /// `tokens` is not consulted again. + fn min_height_single_line(&self, _tokens: &InputTokens) -> f32 { + self.metrics.height + } + + /// [`BreezeRowMetrics::height_decorated`]. + fn min_height_decorated(&self, _tokens: &InputTokens) -> f32 { + self.metrics.height_decorated + } + + /// [`BreezeRowMetrics::height_two_line`]. + fn min_height_two_line(&self, _tokens: &InputTokens) -> f32 { + self.metrics.height_two_line + } +} + +/// Stacks the selection under the delegated row, stretching both to the +/// frame's bounds, so the selection spans the row's full width. +/// +/// A `ZStack` would measure the row at an unspecified width, where the +/// row's `Expand` reports only its padding, and centre a narrow row in a +/// wide list; this places both children at the frame's bounds, as +/// `StandardListItem` places its one root. +struct BreezeRowFrame { + selection: WidgetId, + row: WidgetId, +} + +impl std::fmt::Debug for BreezeRowFrame { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeRowFrame").finish() + } +} + +impl Widget for BreezeRowFrame { + fn build(&mut self, _ctx: &mut BuildContext) -> Vec { + vec![self.selection, self.row] + } + + fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { + // The row owns the frame's size, its shrink weight and floor + // included; the selection is decoration. + ctx.child_layout_response(self.row, proposal) + .unwrap_or_else(|| proposal.resolve(0.0, 0.0).into()) + } + + fn place_children( + &self, + bounds: Rect, + _proposal: SizeProposal, + children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + for child in children.iter_mut() { + child.origin = Point::new(bounds.x, bounds.y); + child.size = bounds.size(); + } + } + + fn children(&self) -> Vec { + vec![self.selection, self.row] + } +} + +/// The colours a Breeze row's selection is painted in, in one paint state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeRowColors { + /// `[wcag]` D1: an emphasised selected row's fill, `accent`. + pub selected: Color, + /// `[Hp]` `[wcag]` D1: the same row hovered, the highlight lightened + /// 110 %, `accent_hover` (moved, as D1 moves it, where the text on the + /// accent would fall short on it). + pub selected_hover: Color, + /// `[Hp]` The fill of a selected row in an inactive window: the scheme's + /// inactive highlight, `surface_selected_inactive`. + pub selected_inactive: Color, + /// `[wcag]` D1: a hovered row's wash, `surface_hover`. + pub hover: Color, + /// `[Hp]` A selected disabled row's fill, the disabled highlight. + pub disabled: Color, +} + +impl BreezeRowColors { + /// The colours of `theme`, the theme the paint walker hands a widget, + /// already projected for its window state. + pub fn resolve(theme: &Theme) -> BreezeRowColors { + let c = &theme.colors; + BreezeRowColors { + selected: c.accent, + selected_hover: c.accent_hover, + selected_inactive: c.surface_selected_inactive, + hover: c.surface_hover, + disabled: c.accent_disabled, + } + } +} + +/// What a row's selection paints: a fill and a 1 px outline, each optional. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct RowPaint { + /// The fill of the selection, or nothing. + pub fill: Option, + /// The outline of the selection, or nothing. + pub outline: Option, +} + +impl RowPaint { + const NOTHING: RowPaint = RowPaint { + fill: None, + outline: None, + }; +} + +/// `[Hp]` What a row paints, from `c`: disabled first, then selected (and +/// hovered), then hovered, then nothing. `emphasised` says whether a +/// selected row is drawn in the active highlight. +pub fn row_paint( + selected: bool, + hovered: bool, + disabled: bool, + emphasised: bool, + c: &BreezeRowColors, +) -> RowPaint { + let solid = |fill: Color| RowPaint { + fill: Some(fill), + outline: Some(fill), + }; + if disabled { + return if selected { + solid(c.disabled) + } else { + RowPaint::NOTHING + }; + } + if selected { + return solid(match (emphasised, hovered) { + (true, false) => c.selected, + (true, true) => c.selected_hover, + (false, false) => c.selected_inactive, + (false, true) => lighter(c.selected_inactive), + }); + } + if hovered { + // The outline is the highlight of the row's colour group at 80 %; + // in an inactive window the projected accent is that group's. + let highlight = if emphasised { + c.selected + } else { + c.selected_inactive + }; + return RowPaint { + fill: Some(c.hover), + outline: Some(highlight.with_alpha(ROW_HOVER_OUTLINE_ALPHA)), + }; + } + RowPaint::NOTHING +} + +/// `[Hp]` Qt's lighter at 110 %, in KDE's arithmetic. +fn lighter(c: Color) -> Color { + hsv_lighter(KdeColor::from_color(c), ROW_SELECTED_HOVER_LIGHTER).to_color() +} + +/// `[M]` The rectangle a row's selection lies in: the row inset by the item +/// margins. +pub fn selection_rect(bounds: Rect, inset_horizontal: f32, inset_vertical: f32) -> Rect { + Rect::new( + bounds.x + inset_horizontal, + bounds.y + inset_vertical, + (bounds.width - 2.0 * inset_horizontal).max(0.0), + (bounds.height - 2.0 * inset_vertical).max(0.0), + ) +} + +/// `[measured-qt]` The radius a row's selection is stroked and filled at: +/// the control radius, 5 for Breeze, not the 4.5 of a stroked control frame. +/// +/// The specification gives 4.5, the frame radius less half the pen, as for +/// the push button; Plasma 6.8's painted rows say otherwise. Their corner +/// pixels (`item_view_row.selected` and `.hover` in the golden pixel +/// samples, `tests/breeze_reference.rs`) fit a radius of 5 on the stroked +/// rectangle to within 23 levels at every pixel, where 4.5 leaves up to 75: +/// at 4.5 the pixel where the arc crosses the diagonal is 0.58 covered, +/// where Plasma covers 0.22 of it. The push button's corners, painted by +/// the same painter, fit 4.5. +pub fn selection_radius(shape: &ShapeTokens) -> f32 { + shape.radius_control.max(0.0) +} + +/// The selection layer of a Breeze row, painted behind the row's content. +/// +/// A leaf rather than a composition: a virtualized list realizes and +/// recycles rows constantly. Sized like `RectWidget`: it takes whatever the +/// proposal offers and reports zero when unbounded. +struct BreezeRowSelection { + is_selected: Signal, + is_hovered: Signal, + is_disabled: Signal, + emphasised: Signal, + show_focus: Signal, + inset_horizontal: f32, + inset_vertical: f32, +} + +impl std::fmt::Debug for BreezeRowSelection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeRowSelection").finish() + } +} + +impl Widget for BreezeRowSelection { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + for s in [ + &self.is_selected, + &self.is_hovered, + &self.is_disabled, + &self.emphasised, + &self.show_focus, + ] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let theme = ctx.theme; + let paint = row_paint( + self.is_selected.get(), + self.is_hovered.get(), + self.is_disabled.get(), + self.emphasised.get(), + &BreezeRowColors::resolve(theme), + ); + let area = selection_rect(bounds, self.inset_horizontal, self.inset_vertical); + let pen = theme.shape.border_width; + let half = pen * 0.5; + let stroked = Rect::new( + area.x + half, + area.y + half, + (area.width - pen).max(0.0), + (area.height - pen).max(0.0), + ); + let corner = CornerRadius::uniform(selection_radius(&theme.shape)); + if let Some(fill) = paint.fill + && fill.a() > 0.0 + { + canvas.fill_rounded_rect(stroked, corner, fill); + } + if let Some(outline) = paint.outline + && outline.a() > 0.0 + && pen > 0.0 + { + canvas.stroke_rounded_rect(stroked, corner, outline, pen); + } + if self.show_focus.get() && !self.is_disabled.get() { + paint_focus_frame(canvas, area, theme); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the row announces its own selection. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_core::styles::PaintState; + use teksilo_tokens::TargetDensity; + use teksilo_tokens::contrast::composite_contrast; + + use crate::PlasmaAccent; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + #[test] + fn the_recipe_carries_breezes_row_metrics() { + let r = breeze_standard_item_recipe(); + assert_eq!(r.min_height_single_line, 24.0); + assert_eq!(r.min_height_two_line, 42.0); + assert_eq!(r.padding_horizontal, 7.0); + assert_eq!(r.padding_vertical, 1.0); + assert_eq!(r.icon_size, 16.0); + assert_eq!(r.bg_horizontal_inset, 2.0); + assert_eq!(r.selection_edge_width, 0.0); + // The fields the row lays out from its own constants are left alone. + let shipped = StandardItemRecipe::default(); + assert_eq!(r.slot_gap, shipped.slot_gap); + assert_eq!(r.subtitle_slot_gap, shipped.subtitle_slot_gap); + assert_eq!(r.label_subtitle_gap, shipped.label_subtitle_gap); + assert_eq!(r.tree_indent_step, shipped.tree_indent_step); + assert_eq!(BreezeStandardItemStyle::default().recipe, r); + assert_eq!( + BreezeStandardItemStyle::for_theme(&crate::light()).recipe, + r + ); + } + + /// The floors a row asks for: 24, 26 with a decoration, 42 with a + /// subtitle at Compact, through the density ladder above it. + #[test] + fn the_floors_are_the_row_metrics_at_every_density() { + for (density, floors) in [ + (TargetDensity::Compact, (24.0, 26.0, 42.0)), + (TargetDensity::Comfortable, (32.0, 32.0, 42.0)), + (TargetDensity::Touch, (44.0, 44.0, 44.0)), + ] { + let theme = crate::light().with_density(density); + let style = BreezeStandardItemStyle::for_theme(&theme); + let t = &theme.input; + assert_eq!( + ( + style.min_height_single_line(t), + style.min_height_decorated(t), + style.min_height_two_line(t) + ), + floors, + "{density:?}" + ); + } + } + + #[test] + fn the_style_flips_the_label_and_follows_the_window() { + let s = BreezeStandardItemStyle::default(); + assert_eq!(s.selected_label_role(), Some(TextRole::OnAccent)); + assert_eq!(s.selection_emphasis(), SelectionEmphasis::WindowActive); + } + + #[test] + fn a_row_paints_breezes_selection_in_each_state() { + let c = BreezeRowColors::resolve(&crate::light()); + assert_eq!(row_paint(false, false, false, true, &c), RowPaint::NOTHING); + assert_eq!(row_paint(false, true, true, true, &c), RowPaint::NOTHING); + let selected = row_paint(true, false, false, true, &c); + assert_eq!(selected.fill, Some(c.selected)); + assert_eq!(selected.outline, Some(c.selected), "opaque outline"); + let hovered = row_paint(false, true, false, true, &c); + assert_eq!(hovered.fill, Some(c.hover)); + assert_eq!(hovered.outline, Some(c.selected.with_alpha(0.8))); + let selected_hovered = row_paint(true, true, false, true, &c); + assert_ne!(selected_hovered.fill, Some(c.selected)); + assert_eq!(selected_hovered.fill, selected_hovered.outline); + assert_eq!( + row_paint(true, false, false, false, &c).fill, + Some(c.selected_inactive) + ); + assert_eq!(row_paint(true, true, true, true, &c).fill, Some(c.disabled)); + } + + /// The colours Plasma painted in a 200 x 24 row: the highlight, its + /// lightened hover, the 30 % wash and the inactive highlight. + #[test] + fn breeze_lights_row_colours_are_plasmas() { + let light = crate::light(); + let c = BreezeRowColors::resolve(&light); + assert_eq!(rgba8(c.selected), [61, 174, 233, 255]); + assert_eq!( + rgba8(row_paint(true, true, false, true, &c).fill.unwrap()), + [68, 191, 255, 255], + "#44bfff" + ); + let wash = rgba8(c.hover); + for (got, want) in wash.iter().zip([196u8, 230, 248]) { + assert!(got.abs_diff(want) <= 1, "{wash:?}, Plasma #c4e6f8"); + } + let inactive = light.for_paint_state(PaintState { + window_active: false, + high_contrast: false, + }); + let ci = BreezeRowColors::resolve(&inactive); + assert_eq!( + rgba8(row_paint(true, false, false, false, &ci).fill.unwrap()), + [194, 224, 245, 255], + "#c2e0f5" + ); + } + + /// The label on a selected row is readable on the fill and its hover, + /// with every swatch, light and dark. + #[test] + fn the_label_is_readable_on_the_selection_with_every_accent() { + for accent in PlasmaAccent::ALL { + for theme in [ + crate::light_with_accent(accent.color()), + crate::dark_with_accent(accent.color()), + ] { + let c = BreezeRowColors::resolve(&theme); + let text = theme.colors.text_on_accent; + for selected_hover in [false, true] { + let fill = row_paint(true, selected_hover, false, true, &c) + .fill + .unwrap(); + assert!( + composite_contrast(text, fill) >= 4.5, + "{accent:?}: {:.2}:1", + composite_contrast(text, fill) + ); + } + let wash = row_paint(false, true, false, true, &c).fill.unwrap(); + assert!(composite_contrast(theme.colors.text_primary, wash) >= 4.5); + } + } + } + + #[test] + fn the_selection_lies_inside_the_item_margins() { + let r = selection_rect(Rect::new(0.0, 0.0, 200.0, 24.0), 2.0, 1.0); + assert_eq!((r.x, r.y, r.width, r.height), (2.0, 1.0, 196.0, 22.0)); + let tiny = selection_rect(Rect::new(0.0, 0.0, 3.0, 1.0), 2.0, 1.0); + assert_eq!((tiny.width, tiny.height), (0.0, 0.0)); + } + + /// Without the palette the colours are the theme's own tokens, so a row + /// in another theme still paints. + #[test] + fn the_colours_are_tokens_and_need_no_palette() { + let mut bare = crate::light(); + bare.extensions.remove::(); + assert_eq!( + BreezeRowColors::resolve(&bare), + BreezeRowColors::resolve(&crate::light()) + ); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/tab.rs b/crates/teksilo-theme-kde/src/styles/tab.rs new file mode 100644 index 000000000..f7d518791 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/tab.rs @@ -0,0 +1,700 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze tab, in a `TabBar` or a `TabWidget`. +//! +//! The selected tab is filled with the window colour, merging into the page +//! below it, outlined on its two sides in the window colour moved toward the +//! window text by the frame contrast, and open on the side of the page. A +//! 3 px highlight bar runs along its outer edge, its two outer corners +//! rounded at the 5 px frame radius. The outline is stroked at radius 4.5, +//! so its outer edge turns through the same 5 px corner: the curve the bar +//! starts runs on into the outline and the face below the bar, rows 3 and 4 +//! of a 30 px tab, before the sides straighten. An unselected tab is the window colour +//! darkened 120 %, square; hovered, the window colour moved 20 % toward the +//! hover colour. A disabled tab paints the disabled colours. The title of +//! an unselected tab is the bar's idle text, the secondary text, which the +//! colour mapping lifts to the text floor on both unselected faces (D2). +//! +//! The bar is on the edge the bar's `active_indicator` names: the top of a +//! horizontal tab and the leading side of a vertical one by default, the +//! bottom or the trailing side for `InnerEdge`; the open side is always the +//! opposite one. A tab is 30 px at Plasma's default font, through the density +//! ladder, its title 8 px inside its edges ([`BreezeTabMetrics`]). The strip +//! behind the tabs, its separator and the drop indicator of a reorder drag +//! are the shipped [`RecipeTabStyle`]'s, built with these numbers. +//! +//! Plasma's samples of a standalone tab bar fill the selected tab with the +//! window colour; the specification gives the frame background for a tab +//! bar that frames a tab widget's panel. A Teksilo tab's page is the window, +//! so the window colour is used, as measured. +//! +//! ## Where this departs from Breeze +//! +//! - **Keyboard focus** draws the 2 px focus frame (D5) flush inside the +//! tab, as the framework's tabs keep their ring inside so a neighbouring +//! tab cannot cover it; Breeze draws no focus frame on a tab. +//! - **The highlight bar is not lifted.** It is Breeze's highlight, as are a +//! slider's fill and a check box's wash; the selected tab is also told by +//! its fill, its outline and its open side. + +use teksilo_canvas::{Canvas, Path, Point, Rect, SizeProposal, arc_to_cubics}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{ + TabBarChromeConfig, TabBarOrientation, TabIndicatorPosition, TabStyle, TabStyleConfig, Theme, +}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::{ColorState, KdeColor, PaletteRole}; +use teksilo_tokens::{Color, InputTokens, TypographyTokens}; +use teksilo_widgets::primitives::{HStack, ZStack}; +use teksilo_widgets::styles::{RecipeTabStyle, TabRecipe}; + +use crate::color; +use crate::palette::KdePalette; +use crate::styles::chrome::paint_focus_frame; +use crate::styles::metrics::{ + BreezeTabMetrics, TAB_BAR_RADIUS, TAB_OUTLINE_RADIUS, breeze_tab_metrics_for, +}; + +/// Breeze `TabStyle`. +/// +/// [`Default`] is the Compact density and the Breeze typography; the preset +/// installs [`BreezeTabStyle::for_theme`] of the theme it builds. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeTabStyle { + /// The tab dimensions this style lays out and reports. + pub metrics: BreezeTabMetrics, + /// The recipe the bar's strip is built with. + pub recipe: TabRecipe, +} + +impl BreezeTabStyle { + /// The style at the density of `tokens`, for `typography`. + pub fn for_tokens(tokens: &InputTokens, typography: &TypographyTokens) -> Self { + let metrics = breeze_tab_metrics_for(tokens, typography); + Self { + metrics, + recipe: TabRecipe { + editor_height: metrics.height, + padding_horizontal: metrics.padding_horizontal, + ..TabRecipe::for_tokens(tokens) + }, + } + } + + /// The style at the density and typography of `theme`. + pub fn for_theme(theme: &Theme) -> Self { + Self::for_tokens(&theme.input, &theme.typography) + } +} + +impl Default for BreezeTabStyle { + fn default() -> Self { + Self::for_tokens( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) + } +} + +impl TabStyle for BreezeTabStyle { + fn make_body(&self, cfg: &TabStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let painter = ctx.add(BreezeTabShape { + is_active: cfg.is_active.clone(), + is_hovered: cfg.is_hovered.clone(), + is_focused: cfg.is_focused.clone(), + is_disabled: cfg.is_disabled.clone(), + edge: bar_edge(cfg.orientation, cfg.indicator_position), + indicator: self.metrics.indicator, + }); + let mut row = HStack::new(); + if let Some(id) = cfg.leading { + row = row.child(id); + } + row = row.child(cfg.label); + if let Some(id) = cfg.trailing { + row = row.child(id); + } + let row = ctx.add(row); + ctx.add(ZStack::new().child(painter).child(row)) + } + + fn make_bar(&self, cfg: &TabBarChromeConfig, ctx: &mut BuildContext) -> WidgetId { + RecipeTabStyle::new(self.recipe).make_bar(cfg, ctx) + } + + /// [`BreezeTabMetrics::height`], 30 at Compact. The style was built at a + /// density, so `tokens` is not consulted again. + fn editor_height(&self, _tokens: &InputTokens) -> f32 { + self.metrics.height + } + + /// [`BreezeTabMetrics::padding_horizontal`], 8 at Compact. + fn padding_horizontal(&self, _tokens: &InputTokens) -> f32 { + self.metrics.padding_horizontal + } +} + +/// Which edge of a tab carries the highlight bar, before right-to-left +/// mirroring. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TabBarEdge { + /// The top of a horizontal tab. + Top, + /// The bottom of a horizontal tab. + Bottom, + /// The leading side of a vertical tab. + Leading, + /// The trailing side of a vertical tab. + Trailing, +} + +/// The edge a tab's bar is on, for the bar's orientation and indicator +/// position. +pub fn bar_edge(orientation: TabBarOrientation, position: TabIndicatorPosition) -> TabBarEdge { + match (orientation, position) { + (TabBarOrientation::Horizontal, TabIndicatorPosition::OuterEdge) => TabBarEdge::Top, + (TabBarOrientation::Horizontal, TabIndicatorPosition::InnerEdge) => TabBarEdge::Bottom, + (TabBarOrientation::Vertical, TabIndicatorPosition::OuterEdge) => TabBarEdge::Leading, + (TabBarOrientation::Vertical, TabIndicatorPosition::InnerEdge) => TabBarEdge::Trailing, + } +} + +/// The physical side of a tab: the bar's edge with leading and trailing +/// resolved for a layout direction. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Side { + Top, + Bottom, + Left, + Right, +} + +fn side_of(edge: TabBarEdge, direction: LayoutDirection) -> Side { + let rtl = direction == LayoutDirection::RightToLeft; + match edge { + TabBarEdge::Top => Side::Top, + TabBarEdge::Bottom => Side::Bottom, + TabBarEdge::Leading if rtl => Side::Right, + TabBarEdge::Leading => Side::Left, + TabBarEdge::Trailing if rtl => Side::Left, + TabBarEdge::Trailing => Side::Right, + } +} + +/// A selected tab's length along the edge carrying its bar, and its depth +/// away from that edge. +fn extent(bounds: Rect, side: Side) -> (f32, f32) { + match side { + Side::Top | Side::Bottom => (bounds.width, bounds.height), + Side::Left | Side::Right => (bounds.height, bounds.width), + } +} + +/// The point `u` along the bar's edge of a tab in `bounds` and `v` in from +/// that edge, on the screen. +fn to_screen(bounds: Rect, side: Side, u: f32, v: f32) -> Point { + match side { + Side::Top => Point::new(bounds.x + u, bounds.y + v), + Side::Bottom => Point::new(bounds.x + u, bounds.y + bounds.height - v), + Side::Left => Point::new(bounds.x + v, bounds.y + u), + Side::Right => Point::new(bounds.x + bounds.width - v, bounds.y + u), + } +} + +/// A rectangle in a tab's own frame, `u` along the bar's edge and `v` in +/// from it, whose two corners on the bar's edge are rounded at `radius`. +#[derive(Debug, Clone, Copy, PartialEq)] +struct OuterRounded { + u0: f32, + u1: f32, + v0: f32, + v1: f32, + radius: f32, +} + +impl OuterRounded { + /// The whole tab, its outer corners rounded at `radius`. + fn tab(len: f32, depth: f32, radius: f32) -> Self { + Self { + u0: 0.0, + u1: len, + v0: 0.0, + v1: depth, + radius, + } + } +} + +/// `[Hp]` The part of `shape` between `from` and `to` in from the bar's +/// edge, as a path on the screen. Its two sides follow the rounded corners +/// wherever the band crosses them, so a band cut below the bar keeps the +/// curve the bar started. +fn band_path(bounds: Rect, side: Side, shape: OuterRounded, from: f32, to: f32) -> Path { + let OuterRounded { + u0, + u1, + v0, + v1, + radius, + } = shape; + let mut path = Path::new(); + let (from, to) = (from.max(v0), to.min(v1)); + if to <= from || u1 <= u0 { + return path; + } + let r = radius.min((u1 - u0) * 0.5).min(v1 - v0).max(0.0); + let cy = v0 + r; + // How far round a corner the side is at `v`: 90 degrees on the edge, 0 + // where the corner meets the straight side. + let lift = |v: f32| -> f32 { + if r <= 0.0 { + 0.0 + } else { + ((cy - v) / r).clamp(0.0, 1.0).asin().to_degrees() + } + }; + let inset = |v: f32| -> f32 { + if r <= 0.0 || v >= cy { + 0.0 + } else { + r - (r * r - (cy - v) * (cy - v)).max(0.0).sqrt() + } + }; + let pt = |u: f32, v: f32| to_screen(bounds, side, u, v); + let arc = |path: &mut Path, cx: f32, start: f32, sweep: f32| { + let square = Rect::new(cx - r, cy - r, 2.0 * r, 2.0 * r); + for seg in arc_to_cubics(square, start, sweep) { + path.line_to(pt(seg.from.x, seg.from.y)); + path.cubic_to( + pt(seg.control1.x, seg.control1.y), + pt(seg.control2.x, seg.control2.y), + pt(seg.to.x, seg.to.y), + ); + } + }; + let curved = r > 0.0 && from < cy; + let knee = to.min(cy); + path.move_to(pt(u0 + inset(from), from)); + if curved { + // Down the leading corner, from `from` toward the straight side. + arc( + &mut path, + u0 + r, + 180.0 + lift(from), + lift(knee) - lift(from), + ); + } + path.line_to(pt(u0 + inset(to), to)); + path.line_to(pt(u1 - inset(to), to)); + if curved { + path.line_to(pt(u1 - inset(knee), knee)); + // Up the trailing corner, back to `from`. + arc(&mut path, u1 - r, -lift(knee), lift(knee) - lift(from)); + } + path.line_to(pt(u1 - inset(from), from)); + path.close(); + path +} + +/// The three paths of a selected tab in `bounds` with the bar `bar` thick +/// along the side `side` and a `pen` wide outline: the bar, the outline and +/// the face, the last two from the bar to the open side. +/// +/// The bar is the tab's outer edge rounded at [`TAB_BAR_RADIUS`]. The +/// outline is the tab stroked at [`TAB_OUTLINE_RADIUS`]: its outer edge +/// that radius plus half the pen, its inner edge, the face, that radius +/// less half the pen, so both turn through the corner below the bar. +fn selected_paths(bounds: Rect, side: Side, bar: f32, pen: f32) -> [Path; 3] { + let (len, depth) = extent(bounds, side); + let bar = bar.min(depth).max(0.0); + let bar_path = band_path( + bounds, + side, + OuterRounded::tab(len, depth, TAB_BAR_RADIUS), + 0.0, + bar, + ); + let pen = pen.max(0.0); + let outer = OuterRounded::tab(len, depth, TAB_OUTLINE_RADIUS + pen * 0.5); + let outline = band_path(bounds, side, outer, bar, depth); + let face = if pen > 0.0 { + let inner = OuterRounded { + u0: pen, + u1: len - pen, + v0: pen, + v1: depth, + radius: (TAB_OUTLINE_RADIUS - pen * 0.5).max(0.0), + }; + band_path(bounds, side, inner, bar, depth) + } else { + outline.clone() + }; + [bar_path, outline, face] +} + +/// The colours a Breeze tab is painted in, in one paint state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeTabColors { + /// `[measured-qt]` The selected tab's fill: the window colour. + pub selected: Color, + /// `[Hp]` The selected tab's outline: its fill moved toward the window + /// text by the frame contrast. + pub outline: Color, + /// `[Hp]` The highlight bar: the highlight of the tab's colour group. + pub bar: Color, + /// `[Hp]` An unselected tab: the window colour darkened 120 %. + pub unselected: Color, + /// `[Hp]` A hovered unselected tab: the window colour moved 20 % toward + /// the hover colour. + pub hovered: Color, +} + +impl BreezeTabColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference, for an enabled + /// or a disabled tab. + /// + /// The window colour and the highlight are the theme's projected tokens + /// (the disabled palette's window for a disabled tab); the outline and + /// the hover colour come from [`KdePalette`]. Without it the outline is + /// the border, an unselected tab the sunken surface and a hovered one the + /// hover surface. + pub fn resolve( + theme: &Theme, + window_active: bool, + high_contrast: bool, + disabled: bool, + ) -> BreezeTabColors { + let c = &theme.colors; + let bar = if disabled { + c.accent_disabled + } else { + c.accent + }; + let Some(palette) = theme.extension::() else { + return BreezeTabColors { + selected: c.surface_main, + outline: c.border, + bar, + unselected: c.surface_sunken, + hovered: c.surface_hover, + }; + }; + let state = if disabled { + ColorState::Disabled + } else if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let window = if disabled { + palette.role(ColorState::Disabled, PaletteRole::Window) + } else { + c.surface_main + }; + let b = palette.breeze_colors(state, high_contrast); + BreezeTabColors { + selected: window, + outline: b.frame_outline, + bar, + unselected: color::tab_unselected_face(KdeColor::from_color(window)).to_color(), + hovered: color::tab_hovered_face( + KdeColor::from_color(window), + KdeColor::from_color(b.hover), + ) + .to_color(), + } + } +} + +/// The chrome of a Breeze tab, painted behind its title. +struct BreezeTabShape { + is_active: Signal, + is_hovered: Signal, + is_focused: Signal, + is_disabled: Signal, + edge: TabBarEdge, + indicator: f32, +} + +impl std::fmt::Debug for BreezeTabShape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeTabShape").finish() + } +} + +impl Widget for BreezeTabShape { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + for s in [ + &self.is_active, + &self.is_hovered, + &self.is_focused, + &self.is_disabled, + ] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let theme = ctx.theme; + let disabled = self.is_disabled.get(); + let c = BreezeTabColors::resolve( + theme, + ctx.window_active, + ctx.prefers_high_contrast, + disabled, + ); + if self.is_active.get() { + let side = side_of(self.edge, ctx.layout_direction); + let pen = theme.shape.border_width; + let [bar, outline, face] = selected_paths(bounds, side, self.indicator, pen); + if !bar.is_empty() { + canvas.fill_path(&bar, c.bar); + } + if pen > 0.0 && !outline.is_empty() { + canvas.fill_path(&outline, c.outline); + } + if !face.is_empty() { + canvas.fill_path(&face, c.selected); + } + } else { + let fill = if self.is_hovered.get() && !disabled { + c.hovered + } else { + c.unselected + }; + canvas.fill_rect(bounds, fill); + } + if self.is_focused.get() && !disabled { + // Flush inside the tab: the frame runs 1 px either side of a + // rectangle 1 px in. + let inner = Rect::new( + bounds.x + 1.0, + bounds.y + 1.0, + (bounds.width - 2.0).max(0.0), + (bounds.height - 2.0).max(0.0), + ); + paint_focus_frame(canvas, inner, theme); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the tab announces itself. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_tokens::TargetDensity; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + /// Whether two colours are within one step a channel: Qt's raster + /// painter rounds an 8-bit blend its own way. + fn near(a: [u8; 4], b: [u8; 4]) -> bool { + a.iter().zip(b).all(|(x, y)| x.abs_diff(y) <= 1) + } + + #[test] + fn the_metrics_and_the_strip_recipe_are_breezes() { + let s = BreezeTabStyle::default(); + let t = InputTokens::default(); + assert_eq!(s.editor_height(&t), 30.0); + assert_eq!(s.padding_horizontal(&t), 8.0); + assert_eq!(s.recipe.editor_height, 30.0); + assert_eq!(s.recipe.padding_horizontal, 8.0); + for (density, height) in [ + (TargetDensity::Comfortable, 32.0), + (TargetDensity::Touch, 44.0), + ] { + let theme = crate::light().with_density(density); + let s = BreezeTabStyle::for_theme(&theme); + assert_eq!(s.editor_height(&theme.input), height, "{density:?}"); + } + } + + /// The tab colours Plasma painted in Breeze Light and Breeze Dark. + #[test] + fn the_tab_colours_are_plasmas() { + for (theme, selected, outline, unselected, hovered) in [ + ( + crate::light(), + [239, 240, 241, 255], + [198, 200, 201, 255], + [199, 200, 201, 255], + [203, 227, 240, 255], + ), + ( + crate::dark(), + [32, 35, 38, 255], + [76, 78, 81, 255], + [27, 29, 32, 255], + [38, 63, 77, 255], + ), + ] { + let c = BreezeTabColors::resolve(&theme, true, false, false); + assert_eq!(rgba8(c.selected), selected); + assert!(near(rgba8(c.outline), outline), "{:?}", rgba8(c.outline)); + assert!( + near(rgba8(c.unselected), unselected), + "{:?}", + rgba8(c.unselected) + ); + assert!(near(rgba8(c.hovered), hovered), "{:?}", rgba8(c.hovered)); + assert_eq!(rgba8(c.bar), [61, 174, 233, 255]); + } + } + + #[test] + fn the_bar_follows_the_orientation_the_position_and_the_direction() { + use LayoutDirection::{LeftToRight, RightToLeft}; + use TabBarOrientation::{Horizontal, Vertical}; + use TabIndicatorPosition::{InnerEdge, OuterEdge}; + assert_eq!(bar_edge(Horizontal, OuterEdge), TabBarEdge::Top); + assert_eq!(bar_edge(Horizontal, InnerEdge), TabBarEdge::Bottom); + assert_eq!(bar_edge(Vertical, OuterEdge), TabBarEdge::Leading); + assert_eq!(bar_edge(Vertical, InnerEdge), TabBarEdge::Trailing); + assert_eq!(side_of(TabBarEdge::Leading, LeftToRight), Side::Left); + assert_eq!(side_of(TabBarEdge::Leading, RightToLeft), Side::Right); + assert_eq!(side_of(TabBarEdge::Trailing, RightToLeft), Side::Left); + assert_eq!(side_of(TabBarEdge::Top, RightToLeft), Side::Top); + } + + fn inside(path: &Path, x: f32, y: f32) -> bool { + path.contains_point(Point::new(x, y), teksilo_canvas::FillRule::Winding, 0.01) + } + + fn near_rect(r: Rect, x: f32, y: f32, w: f32, h: f32) -> bool { + (r.x - x).abs() < 0.01 + && (r.y - y).abs() < 0.01 + && (r.width - w).abs() < 0.01 + && (r.height - h).abs() < 0.01 + } + + /// A selected 80 x 30 tab: the bar along the top 3 px, the outline down + /// the two sides below it, the bottom open, as Plasma's sample. + #[test] + fn a_selected_tab_is_a_bar_two_sides_and_an_open_edge() { + let b = Rect::new(0.0, 0.0, 80.0, 30.0); + let [bar, outline, face] = selected_paths(b, Side::Top, 3.0, 1.0); + // The bar's rounded corners keep it clear of the tab's two sides: + // at its inner edge, 3 px down a 5 px corner, it is 5 - sqrt(21) + // in from each. + let corner = 5.0 - 21.0_f32.sqrt(); + let bb = bar.exact_bounds(0.01); + assert!( + near_rect(bb, corner, 0.0, 80.0 - 2.0 * corner, 3.0), + "{bb:?}" + ); + assert!(near_rect(outline.bounds(), 0.0, 3.0, 80.0, 27.0)); + assert!(near_rect(face.bounds(), 1.0, 3.0, 78.0, 27.0)); + assert!(inside(&bar, 40.0, 1.5)); + assert!(!inside(&bar, 40.0, 3.5)); + // The outline is the 1 px either side of the face, down to the + // open bottom. + for y in [15.0, 29.5] { + assert!(inside(&outline, 0.5, y) && !inside(&face, 0.5, y)); + assert!(inside(&outline, 79.5, y) && !inside(&face, 79.5, y)); + assert!(inside(&face, 40.0, y)); + } + let [bar, _, face] = selected_paths(Rect::new(0.0, 0.0, 30.0, 80.0), Side::Right, 3.0, 1.0); + let bb = bar.exact_bounds(0.01); + assert!( + near_rect(bb, 27.0, corner, 3.0, 80.0 - 2.0 * corner), + "{bb:?}" + ); + assert!(near_rect(face.bounds(), 0.0, 1.0, 27.0, 78.0)); + } + + /// The corner the bar starts goes on turning below it: in rows 3 and 4 + /// the outline's outer edge and the face's edge are still curving, as + /// in Plasma's sample (column 0 is blended there and only reaches the + /// outline colour at row 5), rather than stepping to a square body. + #[test] + fn the_outline_and_the_face_follow_the_bars_corner() { + let b = Rect::new(0.0, 0.0, 80.0, 30.0); + let [bar, outline, face] = selected_paths(b, Side::Top, 3.0, 1.0); + // The bar's rounded corner. + assert!(!inside(&bar, 0.3, 0.3)); + assert!(inside(&bar, 2.5, 2.5)); + // Row 3: the outline starts about 0.4 px in, not at the edge. + assert!(!inside(&outline, 0.1, 3.1)); + assert!(inside(&outline, 0.8, 3.1)); + assert!(!inside(&outline, 79.9, 3.1)); + // Row 4: the edge has nearly reached the side, and the face is still + // inset more than the straight 1 px pen. + assert!(inside(&outline, 0.5, 4.5)); + assert!(!inside(&face, 0.9, 4.5)); + assert!(inside(&face, 1.5, 4.5)); + assert!(inside(&face, 1.1, 15.0)); + // Every orientation and direction carries the same corner. + for (bounds, side, outside) in [ + (Rect::new(0.0, 0.0, 80.0, 30.0), Side::Bottom, (0.1, 26.9)), + (Rect::new(0.0, 0.0, 30.0, 80.0), Side::Left, (3.1, 0.1)), + (Rect::new(0.0, 0.0, 30.0, 80.0), Side::Right, (26.9, 79.9)), + ] { + let [_, outline, _] = selected_paths(bounds, side, 3.0, 1.0); + assert!(!inside(&outline, outside.0, outside.1), "{side:?}"); + } + // At the default 1 px pen the outline's outer edge is the bar's + // radius. + assert_eq!(TAB_OUTLINE_RADIUS + 0.5, TAB_BAR_RADIUS); + } + + #[test] + fn a_band_without_a_radius_is_a_rectangle() { + let b = Rect::new(10.0, 20.0, 40.0, 30.0); + let shape = OuterRounded::tab(40.0, 30.0, 0.0); + let p = band_path(b, Side::Top, shape, 3.0, 30.0); + assert!(near_rect(p.bounds(), 10.0, 23.0, 40.0, 27.0)); + assert!(inside(&p, 10.1, 23.1)); + assert!(band_path(b, Side::Top, shape, 30.0, 30.0).is_empty()); + } + + #[test] + fn a_disabled_tab_paints_the_disabled_colours() { + let theme = crate::light(); + let c = BreezeTabColors::resolve(&theme, true, false, true); + assert_eq!(c.bar, theme.colors.accent_disabled); + let palette = theme.extension::().unwrap(); + assert_eq!( + c.selected, + palette.role(ColorState::Disabled, PaletteRole::Window) + ); + } + + #[test] + fn the_tab_paints_from_the_roles_without_the_palette() { + let mut bare = crate::light(); + bare.extensions.remove::(); + let c = BreezeTabColors::resolve(&bare, true, false, false); + assert_eq!(c.outline, bare.colors.border); + assert_eq!(c.unselected, bare.colors.surface_sunken); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/table.rs b/crates/teksilo-theme-kde/src/styles/table.rs new file mode 100644 index 000000000..604effc64 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/table.rs @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze table: `TableView` and `TreeTableView`, their rows and their +//! column header. +//! +//! A header section is filled with the button colour, moved 20 % toward the +//! hover colour under the pointer, and toward the focus colour while its +//! column is being resized (Breeze's pressed section) or while it is the +//! column sorted down, with a 1 px line along its bottom and its trailing +//! edge in the window text at the frame contrast. +//! +//! Sorted down is Qt's indicator, not the arrow Breeze draws: Qt's header +//! view marks a column sorted in ascending order with its down indicator, +//! and Breeze, which inverts the sort indicator by default +//! (`ViewInvertSortIndicator`), draws that column's arrow pointing up. So +//! the ascending column is the one tinted, under an up arrow, and a +//! descending one keeps the button colour under its down arrow. Its label lies 6 px inside it, the header margin. The header +//! row is 30 px at Plasma's default font, room for the 10 px sort arrow, +//! through the density ladder. +//! +//! A body row is 24 px (D7, below). Table cells are square: a selected row +//! is the highlight across the row, a hovered one the highlight at 30 % +//! over the view, an alternate row the view's alternate background. +//! +//! The views paint their own selection band, alternate rows and grid lines +//! from the theme's tokens in one pass over the visible rows, and draw their +//! own sort arrow, so [`TableStyle::make_row_background`] and +//! [`TableStyle::make_sort_indicator`] are Breeze's for a style author who +//! composes them, and the band the views paint is the `surface_selected` +//! token; the crate's known limitations say where that differs from Breeze. +//! +//! ## Where this departs from Breeze +//! +//! - **D7, the row height.** Qt lays a Breeze table row out at the line box +//! and its item margins and padding and adds its 1 px grid line: 23 px at +//! Plasma's default font, under WCAG 2.2 SC 2.5.8's 24 px target. The row +//! is raised to 24 px, then through the density ladder; at a larger font +//! Qt's own row, already taller, is kept. + +use teksilo_canvas::{Canvas, Rect, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{ + SortDirection, TableGridRecipe, TableHeaderCellConfig, TableRowConfig, TableStyle, Theme, +}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::{ColorState, PaletteRole}; +use teksilo_tokens::{Color, InputTokens, TypographyTokens}; +use teksilo_widgets::primitives::ZStack; + +use crate::color::{face_under_primary_text, mark_on_faces}; +use crate::palette::KdePalette; +use crate::styles::arrow::{ArrowDirection, arrow_color, paint_arrow}; +use crate::styles::chrome::kde_mix; +use crate::styles::metrics::{BreezeTableMetrics, ROW_HOVER_ALPHA, breeze_table_metrics_for}; + +/// `[Hp]` How far a hovered or pressed header section moves from the button +/// colour toward the hover or focus colour. +pub const HEADER_STATE_MIX: f64 = 0.2; +/// `[Hp]` The width of a header section's bottom and trailing lines. +pub const HEADER_LINE_WIDTH: f32 = 1.0; + +/// Breeze `TableStyle`. +/// +/// [`Default`] is the Compact density and the Breeze typography; the preset +/// installs [`BreezeTableStyle::for_theme`] of the theme it builds. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeTableStyle { + /// The table dimensions this style reports. + pub metrics: BreezeTableMetrics, +} + +impl BreezeTableStyle { + /// The style at the density of `tokens`, for `typography`. + pub fn for_tokens(tokens: &InputTokens, typography: &TypographyTokens) -> Self { + Self { + metrics: breeze_table_metrics_for(tokens, typography), + } + } + + /// The style at the density and typography of `theme`. + pub fn for_theme(theme: &Theme) -> Self { + Self::for_tokens(&theme.input, &theme.typography) + } +} + +impl Default for BreezeTableStyle { + fn default() -> Self { + Self::for_tokens( + &InputTokens::default(), + &crate::typography::breeze_typography(), + ) + } +} + +impl TableStyle for BreezeTableStyle { + fn make_header_cell(&self, cfg: &TableHeaderCellConfig, ctx: &mut BuildContext) -> WidgetId { + let section = ctx.add(BreezeHeaderSection { + is_hovered: cfg.is_hovered.clone(), + is_resizing: cfg.is_resizing.clone(), + sorted_down: sorts_down(cfg.sort), + }); + ctx.add(ZStack::new().child(section).child(cfg.label)) + } + + fn make_sort_indicator(&self, direction: SortDirection, ctx: &mut BuildContext) -> WidgetId { + ctx.add(BreezeSortArrow { direction }) + } + + fn make_row_background(&self, cfg: &TableRowConfig, ctx: &mut BuildContext) -> WidgetId { + ctx.add(BreezeTableRowBackground { + is_selected: cfg.is_selected.clone(), + is_hovered: cfg.is_hovered.clone(), + is_alt: cfg.is_alt, + // `[qt]` A selection mutes with its window only. + is_window_active: cfg.is_window_active.clone(), + }) + } + + /// `[unpublished]` The specification records no grid-line colour of its + /// own: the framework's 1 px lines in the divider role, Breeze's frame + /// outline, and its frozen-column shadow. + fn grid(&self) -> TableGridRecipe { + TableGridRecipe::default() + } + + /// [`BreezeTableMetrics::header_padding`]. The style was built at a + /// density, so `tokens` is not consulted again. + fn cell_padding_horizontal(&self, _tokens: &InputTokens) -> f32 { + self.metrics.header_padding + } + + /// [`BreezeTableMetrics::header_padding`]: the label's line box and the + /// header margin above and below fill the header. + fn cell_padding_vertical(&self, _tokens: &InputTokens) -> f32 { + self.metrics.header_padding + } + + /// [`BreezeTableMetrics::row_height`], 24 at Compact (D7). + fn row_height(&self, _tokens: &InputTokens) -> f32 { + self.metrics.row_height + } + + /// [`BreezeTableMetrics::header_height`], 30 at Compact. + fn header_height(&self, _tokens: &InputTokens) -> f32 { + self.metrics.header_height + } +} + +/// The colours a Breeze header section is painted in, in one paint state. +/// +/// The section carries its label's primary text at the paint state's text +/// floor: at rest it is the button colour the colour tokens lift that text +/// on, and hovered or pressed it is moved, as an accent wash is (D1 of the +/// colour mapping), until the text reaches the floor on it. The sort arrow +/// is lifted to the non-text floor on all three. In stock Breeze nothing +/// moves. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeHeaderColors { + /// `[qt]` The section at rest: the button colour. + pub rest: Color, + /// `[Hp]` Hovered: the button colour moved 20 % toward the hover colour. + pub hovered: Color, + /// `[Hp]` Pressed (its column being resized) or sorted down: the button + /// colour moved 20 % toward the focus colour. + pub pressed: Color, + /// `[Hp]` The bottom and trailing lines: the window text at the frame + /// contrast, composited over the section. + pub line: Color, + /// `[Hp]` The sort arrow: the arrow colour of the button text. + pub arrow: Color, +} + +impl BreezeHeaderColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference. + /// + /// The button, hover and focus colours come from [`KdePalette`]; without + /// it the section is the raised surface, its states the hover and pressed + /// surfaces and its lines the border. + pub fn resolve(theme: &Theme, window_active: bool, high_contrast: bool) -> BreezeHeaderColors { + let c = &theme.colors; + let Some(palette) = theme.extension::() else { + return BreezeHeaderColors { + rest: c.surface_raised, + hovered: c.surface_hover, + pressed: c.surface_pressed, + line: c.border, + arrow: c.text_secondary, + }; + }; + let state = if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let b = palette.breeze_colors(state, high_contrast); + // `[derived]` The frame contrast: 1 in the high-contrast scheme the + // palette builds, as `KdePalette::breeze_colors` computes it. + let fc = if high_contrast { + 1.0 + } else { + palette.frame_contrast() as f32 + }; + // `[wcag]` D1: a section mixed toward the hover or focus colour + // carries the label's primary text, as the button colour does. + let under_label = |face: Color| face_under_primary_text(face, c, high_contrast); + let hovered = under_label(kde_mix(b.button, b.hover, HEADER_STATE_MIX)); + let pressed = under_label(kde_mix(b.button, b.focus, HEADER_STATE_MIX)); + BreezeHeaderColors { + rest: b.button, + hovered, + pressed, + line: palette.role(state, PaletteRole::WindowText).with_alpha(fc), + // `[wcag]` The arrow on all three sections. + arrow: mark_on_faces( + arrow_color(b.button_text, b.button), + &[b.button, hovered, pressed], + high_contrast, + ), + } + } + + /// The section's fill: pressed (or sorted down) over hovered over rest. + pub fn fill(&self, hovered: bool, pressed: bool) -> Color { + if pressed { + self.pressed + } else if hovered { + self.hovered + } else { + self.rest + } + } +} + +/// `[qt]` Whether a header sorted in `sort` carries Qt's down indicator, +/// which Breeze paints as a pressed section: Qt's header view marks an +/// ascending column down. Breeze's inverted arrow then points up, as +/// [`TableStyle::make_sort_indicator`] draws it. +pub fn sorts_down(sort: Option) -> bool { + sort == Some(SortDirection::Ascending) +} + +/// `[Hp]` The bottom line and the trailing separator of a header section in +/// `bounds`, each 1 px, the separator on the left in a right-to-left window. +pub fn header_lines(bounds: Rect, direction: LayoutDirection) -> [Rect; 2] { + let w = HEADER_LINE_WIDTH; + let bottom = Rect::new(bounds.x, bounds.y + bounds.height - w, bounds.width, w); + let x = match direction { + LayoutDirection::LeftToRight => bounds.x + bounds.width - w, + LayoutDirection::RightToLeft => bounds.x, + }; + [bottom, Rect::new(x, bounds.y, w, bounds.height)] +} + +/// The chrome of a Breeze header section, painted behind its label. +struct BreezeHeaderSection { + is_hovered: Signal, + is_resizing: Signal, + /// Fixed for the build: the header cell is rebuilt when its sort + /// changes. + sorted_down: bool, +} + +impl std::fmt::Debug for BreezeHeaderSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeHeaderSection").finish() + } +} + +impl Widget for BreezeHeaderSection { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + self.is_hovered + .bind_to(id, registry, BindingLevel::RepaintOnly); + self.is_resizing + .bind_to(id, registry, BindingLevel::RepaintOnly); + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let c = + BreezeHeaderColors::resolve(ctx.theme, ctx.window_active, ctx.prefers_high_contrast); + canvas.fill_rect( + bounds, + c.fill( + self.is_hovered.get(), + self.is_resizing.get() || self.sorted_down, + ), + ); + for line in header_lines(bounds, ctx.layout_direction) { + canvas.fill_rect(line, c.line); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the header cell announces its column. + builder.set_hidden(); + } +} + +/// A header's sort arrow: the 10 px Breeze chevron, pointing down for a +/// descending sort and up for an ascending one. +struct BreezeSortArrow { + direction: SortDirection, +} + +impl std::fmt::Debug for BreezeSortArrow { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeSortArrow").finish() + } +} + +impl Widget for BreezeSortArrow { + fn layout_response(&self, _proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + // `[M]` The header's 10 px arrow. + let side = crate::geometry::HEADER_ARROW_SIZE; + teksilo_canvas::Size::new(side, side).into() + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let c = + BreezeHeaderColors::resolve(ctx.theme, ctx.window_active, ctx.prefers_high_contrast); + let direction = match self.direction { + SortDirection::Ascending => ArrowDirection::Up, + SortDirection::Descending => ArrowDirection::Down, + }; + paint_arrow(canvas, bounds, direction, c.arrow); + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // The header announces its sort direction. + builder.set_hidden(); + } +} + +/// `[Hp]` The fill of a table row: a selected row the highlight (the +/// inactive one in an inactive window), a hovered one the highlight at 30 % +/// over the view, an alternate one the alternate background, otherwise +/// nothing. +pub fn table_row_fill( + theme: &Theme, + selected: bool, + hovered: bool, + alt: bool, + window_active: bool, +) -> Option { + let c = &theme.colors; + if selected { + Some(if window_active { + c.surface_selected + } else { + c.surface_selected_inactive + }) + } else if hovered { + Some(c.accent.with_alpha(ROW_HOVER_ALPHA)) + } else if alt { + Some(c.surface_alt_row) + } else { + None + } +} + +/// The background of one Breeze table row, for a style author who composes +/// rows from [`TableStyle::make_row_background`]. Square, as Breeze paints +/// table cells. +struct BreezeTableRowBackground { + is_selected: Signal, + is_hovered: Signal, + is_alt: bool, + is_window_active: Option>, +} + +impl std::fmt::Debug for BreezeTableRowBackground { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeTableRowBackground").finish() + } +} + +impl Widget for BreezeTableRowBackground { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + self.is_selected + .bind_to(id, registry, BindingLevel::RepaintOnly); + self.is_hovered + .bind_to(id, registry, BindingLevel::RepaintOnly); + if let Some(active) = &self.is_window_active { + active.bind_to(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let window_active = self + .is_window_active + .as_ref() + .map_or(ctx.window_active, Signal::get); + if let Some(fill) = table_row_fill( + ctx.theme, + self.is_selected.get(), + self.is_hovered.get(), + self.is_alt, + window_active, + ) { + canvas.fill_rect(bounds, fill); + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColor; + use teksilo_tokens::TargetDensity; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + /// Whether two colours are within one step a channel: Qt's raster + /// painter rounds an 8-bit blend its own way. + fn near(a: [u8; 4], b: [u8; 4]) -> bool { + a.iter().zip(b).all(|(x, y)| x.abs_diff(y) <= 1) + } + + /// `c` composited over the opaque `under`, in 8 bits. + fn over(c: Color, under: Color) -> [u8; 4] { + rgba8(kde_mix(under, c.with_alpha(1.0), f64::from(c.a()))) + } + + #[test] + fn the_heights_and_padding_are_breezes_at_every_density() { + for (density, row, header) in [ + (TargetDensity::Compact, 24.0, 30.0), + (TargetDensity::Comfortable, 32.0, 32.0), + (TargetDensity::Touch, 44.0, 44.0), + ] { + let theme = crate::light().with_density(density); + let s = BreezeTableStyle::for_theme(&theme); + assert_eq!(s.row_height(&theme.input), row, "{density:?}"); + assert_eq!(s.header_height(&theme.input), header, "{density:?}"); + } + let s = BreezeTableStyle::default(); + let t = InputTokens::default(); + assert_eq!(s.cell_padding_horizontal(&t), 6.0); + assert_eq!(s.cell_padding_vertical(&t), 6.0); + assert_eq!(s.grid(), TableGridRecipe::default()); + } + + /// The header section Plasma painted in Breeze Light: the button colour, + /// its hover and pressed mixes, and the line in the window text at the + /// frame contrast over each. + #[test] + fn breeze_lights_header_is_plasmas() { + let c = BreezeHeaderColors::resolve(&crate::light(), true, false); + assert_eq!(rgba8(c.rest), [252, 252, 252, 255], "#fcfcfc"); + assert_eq!(rgba8(c.hovered), [214, 236, 248, 255], "#d6ecf8"); + assert_eq!(rgba8(c.pressed), [214, 236, 248, 255], "#d6ecf8"); + assert!(near(over(c.line, c.rest), [209, 210, 210, 255]), "#d1d2d2"); + assert!( + near(over(c.line, c.hovered), [178, 197, 206, 255]), + "#b2c5ce" + ); + assert_eq!(c.fill(true, true), c.pressed); + assert_eq!(c.fill(true, false), c.hovered); + assert_eq!(c.fill(false, false), c.rest); + } + + /// In a scheme whose view focus and hover decorations differ, the + /// pressed section moves toward the focus colour and the hovered one + /// toward the hover colour. Breeze Light's two are the same colour, so + /// only such a scheme tells the two mixes apart. + #[test] + fn the_pressed_section_mixes_toward_focus_and_the_hovered_toward_hover() { + let (scheme, diagnostics) = teksilo_kde_scheme::KdeColorScheme::parse( + "[Colors:View]\nDecorationFocus=61,174,233\nDecorationHover=246,116,0\n", + ); + assert!(diagnostics.is_empty(), "{diagnostics:?}"); + let theme = crate::from_scheme(&scheme); + let b = theme + .extension::() + .unwrap() + .breeze_colors(ColorState::Active, false); + // Premise: the scheme's two decorations are the ones written. + assert_eq!(rgba8(b.focus), [61, 174, 233, 255]); + assert_eq!(rgba8(b.hover), [246, 116, 0, 255]); + let c = BreezeHeaderColors::resolve(&theme, true, false); + assert_eq!(c.pressed, kde_mix(b.button, b.focus, HEADER_STATE_MIX)); + assert_eq!(c.hovered, kde_mix(b.button, b.hover, HEADER_STATE_MIX)); + assert_ne!(c.pressed, c.hovered); + assert_eq!(c.fill(true, true), c.pressed); + assert_eq!(c.fill(true, false), c.hovered); + } + + #[test] + fn the_ascending_column_is_the_one_sorted_down() { + assert!(sorts_down(Some(SortDirection::Ascending))); + assert!(!sorts_down(Some(SortDirection::Descending))); + assert!(!sorts_down(None)); + } + + #[test] + fn the_header_lines_run_along_the_bottom_and_the_trailing_edge() { + let b = Rect::new(10.0, 20.0, 100.0, 30.0); + let [bottom, side] = header_lines(b, LayoutDirection::LeftToRight); + assert_eq!( + (bottom.x, bottom.y, bottom.width, bottom.height), + (10.0, 49.0, 100.0, 1.0) + ); + assert_eq!( + (side.x, side.y, side.width, side.height), + (109.0, 20.0, 1.0, 30.0) + ); + let [_, rtl] = header_lines(b, LayoutDirection::RightToLeft); + assert_eq!(rtl.x, 10.0); + } + + #[test] + fn a_table_row_is_filled_by_state() { + let t = crate::light(); + let c = &t.colors; + assert_eq!( + table_row_fill(&t, true, true, true, true), + Some(c.surface_selected) + ); + assert_eq!( + table_row_fill(&t, true, false, false, false), + Some(c.surface_selected_inactive) + ); + assert_eq!( + table_row_fill(&t, false, true, true, true), + Some(c.accent.with_alpha(0.3)) + ); + assert_eq!( + table_row_fill(&t, false, false, true, true), + Some(c.surface_alt_row) + ); + assert_eq!(table_row_fill(&t, false, false, false, true), None); + } + + #[test] + fn the_header_paints_from_the_roles_without_the_palette() { + let mut bare = crate::light(); + bare.extensions.remove::(); + let c = BreezeHeaderColors::resolve(&bare, true, false); + assert_eq!(c.rest, bare.colors.surface_raised); + assert_eq!(c.line, bare.colors.border); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/text_input.rs b/crates/teksilo-theme-kde/src/styles/text_input.rs new file mode 100644 index 000000000..9fe37c96a --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/text_input.rs @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze line edit, the frame a `TextInput` is drawn in. +//! +//! A flat fill of the view background in a 1 px outline stroked on the +//! node inset by half the pen, at radius 4.5, with no shadow and no inset +//! for one. The outline is the input-frame outline: the focus colour while +//! the field has focus, the hover colour under the pointer, otherwise the +//! frame outline. Focus changes only the outline's colour; its width stays +//! 1 px and no band is drawn around the field. A disabled field fills with +//! the disabled view background in the disabled frame outline. The hover +//! outline follows the configuration's hover signal, which `TextInput` +//! itself never sets, so a plain `TextInput` shows only the rest and focus +//! outlines. +//! +//! The field is the body line box plus 14 px, 32 px at Plasma's default +//! font, through the density ladder, and its text starts 6 px inside the +//! frame ([`crate::styles::metrics::breeze_field_metrics_for`]). An app's +//! `TextInput::min_height` replaces that height. `TextInput` has already +//! inset its editor vertically by the time a style sees it, so the style +//! pads only horizontally. +//! +//! ## Where this departs from Breeze +//! +//! - **D6, the outline at rest.** The frame outline is the window colour +//! moved a fifth of the way to the window text, 1.5:1 on the Breeze Light +//! window, and the field's view background is no further from the window +//! (1.1:1), so nothing else marks where the field is. The outline at rest +//! is `border_strong`, lifted to SC 1.4.11's 3:1 (D6 of the colour +//! mapping). +//! - **D5, focus.** The focus outline is `border_focused`, the focus colour +//! lifted to 3:1, which Breeze Light's 61,174,233 does not reach on the +//! white view. The hover outline is the scheme's own hover colour, which +//! shows where the pointer is; under high contrast it is lifted to 4.5:1 +//! on the control surfaces and the view, so a hovered field keeps the +//! boundary the high-contrast outline gives it at rest. +//! - **A short field keeps its frame.** Breeze paints only the fill of a +//! line edit shorter than the line box plus 12 px. This keeps the frame +//! at every height, for the reason of D6. +//! - **Validation** (`[unpublished]`): Breeze has none. A field with a +//! validation level is outlined in its colour, as in the other presets. +//! +//! ## Variants +//! +//! `Outlined` and `Filled` are the line edit (Breeze has one field); +//! `Underline` keeps only the bottom edge of the outline; `Bare` returns the +//! editor untouched, for a control that draws its own frame around it. + +use teksilo_canvas::{Canvas, Rect, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::color_prop::ColorProp; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{ + TextInputStyle, TextInputStyleConfig, TextInputValidationLevel, TextInputVariant, Theme, +}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_kde_scheme::{ColorState, PaletteRole}; +use teksilo_tokens::{Color, CornerRadius}; +use teksilo_widgets::primitives::{MinSize, Padding, ZStack}; + +use crate::color::high_contrast_boundary; +use crate::palette::KdePalette; +use crate::styles::chrome::frame_radius; +use crate::styles::metrics::breeze_field_metrics_for; + +/// Breeze `TextInputStyle`. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeTextInputStyle; + +impl TextInputStyle for BreezeTextInputStyle { + fn make_body(&self, cfg: &TextInputStyleConfig, ctx: &mut BuildContext) -> WidgetId { + // The embedded case: the surrounding control draws the frame. + if cfg.variant == TextInputVariant::Bare { + return cfg.editor; + } + let theme = ctx.theme(); + let m = breeze_field_metrics_for(&theme.input, &theme.typography); + let chrome = ctx.add(BreezeFieldChrome::new( + cfg.is_focused.clone(), + cfg.is_hovered.clone(), + cfg.is_disabled.clone(), + cfg.validation.clone(), + cfg.variant == TextInputVariant::Underline, + )); + let padded = ctx.add( + Padding::new(0.0, m.padding_horizontal, 0.0, m.padding_horizontal).child(cfg.editor), + ); + let stack = ctx.add(ZStack::new().child(chrome).child(padded)); + // An app's `TextInput::min_height` (or a styled field's floor) wins. + ctx.add(MinSize::new(0.0, cfg.min_height.unwrap_or(m.height)).child(stack)) + } +} + +/// The colours a Breeze field is painted from, in one paint state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeFieldColors { + /// `[qt]` The view background; the disabled one for a disabled field. + pub fill: Color, + /// `[wcag]` D6: the outline at rest, `border_strong`; `border_disabled` + /// for a disabled field. + pub outline: Color, + /// `[Hp]` The outline under the pointer: the view's hover decoration, + /// lifted to 4.5:1 on the control surfaces and the fill under high + /// contrast, where it stands in for the lifted outline. + pub hover: Color, + /// `[wcag]` D5: the outline while focused, `border_focused`. + pub focus: Color, +} + +impl BreezeFieldColors { + /// The colours of `theme` as it paints in a window that is or is not + /// active, with or without the high-contrast preference, for an enabled + /// or a disabled field. + /// + /// Without a [`KdePalette`] the disabled fill is `surface_disabled` and + /// the hover outline `border_focused`. + pub fn resolve( + theme: &Theme, + window_active: bool, + high_contrast: bool, + disabled: bool, + ) -> BreezeFieldColors { + let c = &theme.colors; + let palette = theme.extension::(); + let state = if disabled { + ColorState::Disabled + } else if window_active { + ColorState::Active + } else { + ColorState::Inactive + }; + let fill = match (disabled, palette) { + (false, _) => c.surface_content, + (true, Some(p)) => p.role(ColorState::Disabled, PaletteRole::Base), + (true, None) => c.surface_disabled, + }; + let hover = palette.map_or(c.border_focused, |p| { + p.breeze_colors(state, high_contrast).hover + }); + BreezeFieldColors { + fill, + outline: if disabled { + c.border_disabled + } else { + c.border_strong + }, + hover: high_contrast_boundary(hover, c, fill, high_contrast), + focus: c.border_focused, + } + } + + /// `[Hp]` The outline in a state: disabled first, then a validation + /// colour, then focus, then hover, then rest. + pub fn outline_for( + &self, + focused: bool, + hovered: bool, + disabled: bool, + validation: Option, + ) -> Color { + if disabled { + self.outline + } else if let Some(tint) = validation { + tint + } else if focused { + self.focus + } else if hovered { + self.hover + } else { + self.outline + } + } +} + +/// The tint a validation level outlines a field in, or `None` for +/// [`TextInputValidationLevel::None`]. +pub fn validation_color(level: TextInputValidationLevel, theme: &Theme) -> Option { + let c = &theme.colors; + match level { + TextInputValidationLevel::None => None, + TextInputValidationLevel::Info => Some(c.status_info_fg), + TextInputValidationLevel::Warning => Some(c.border_warning), + TextInputValidationLevel::Error => Some(c.border_error), + TextInputValidationLevel::Corrected => Some(c.accent), + } +} + +/// `[Hp]` The rectangle a field's frame is filled and stroked on: the node +/// inset by half the pen. A field casts no shadow, so there is no further +/// inset. +pub fn field_frame_rect(bounds: Rect, pen: f32) -> Rect { + let d = pen * 0.5; + Rect::new( + bounds.x + d, + bounds.y + d, + (bounds.width - 2.0 * d).max(0.0), + (bounds.height - 2.0 * d).max(0.0), + ) +} + +/// The frame layer of a Breeze field, painted behind the editor. +/// +/// Sized like `RectWidget`: it takes whatever the proposal offers, so it can +/// sit in a `ZStack` without changing the field's size. +pub struct BreezeFieldChrome { + is_focused: Signal, + is_hovered: Signal, + is_disabled: Signal, + validation: Signal, + underline: bool, + fill: Option, +} + +impl BreezeFieldChrome { + /// A field frame outlined by the focus, hover and disabled states and a + /// validation level; with `underline`, only its bottom edge is outlined. + /// + /// The spin box and an editable rich-text editor frame their fields with + /// it, and a combo box's `Underline` variant draws its bottom edge. + pub fn new( + is_focused: Signal, + is_hovered: Signal, + is_disabled: Signal, + validation: Signal, + underline: bool, + ) -> Self { + Self { + is_focused, + is_hovered, + is_disabled, + validation, + underline, + fill: None, + } + } + + /// Fill the frame with `fill` in place of the view background, as a + /// `RichTextEditor::background` asks; the outline is unchanged. + pub fn fill(mut self, fill: impl Into) -> Self { + self.fill = Some(fill.into()); + self + } +} + +impl std::fmt::Debug for BreezeFieldChrome { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeFieldChrome") + .field("underline", &self.underline) + .finish() + } +} + +impl Widget for BreezeFieldChrome { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + for s in [&self.is_focused, &self.is_hovered, &self.is_disabled] { + s.bind_to(id, registry, BindingLevel::RepaintOnly); + } + self.validation + .bind_to(id, registry, BindingLevel::RepaintOnly); + if let Some(fill) = &self.fill { + fill.register_if_bound(id, registry, BindingLevel::RepaintOnly); + } + vec![] + } + + fn layout_response(&self, proposal: SizeProposal, _ctx: &LayoutContext) -> LayoutResponse { + proposal.resolve(0.0, 0.0).into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let disabled = self.is_disabled.get(); + let colors = BreezeFieldColors::resolve( + ctx.theme, + ctx.window_active, + ctx.prefers_high_contrast, + disabled, + ); + let pen = ctx.theme.shape.border_width; + let rect = field_frame_rect(bounds, pen); + let corner = CornerRadius::uniform(frame_radius(&ctx.theme.shape)); + let fill = self + .fill + .as_ref() + .map_or(colors.fill, |f| f.resolve(ctx.theme, !disabled)); + if fill.a() > 0.0 { + canvas.fill_rounded_rect(rect, corner, fill); + } + let outline = colors.outline_for( + self.is_focused.get(), + self.is_hovered.get(), + disabled, + validation_color(self.validation.get(), ctx.theme), + ); + if outline.a() > 0.0 && pen > 0.0 { + if self.underline { + canvas.draw_border_bottom(bounds, outline, pen); + } else { + canvas.stroke_rounded_rect(rect, corner, outline, pen); + } + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the `TextInput` emits the field's node. + builder.set_hidden(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColor; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + #[test] + fn focus_wins_over_hover_and_disabled_over_both() { + let c = BreezeFieldColors::resolve(&crate::light(), true, false, false); + assert_eq!(c.outline_for(false, false, false, None), c.outline); + assert_eq!(c.outline_for(false, true, false, None), c.hover); + assert_eq!(c.outline_for(true, true, false, None), c.focus); + let red = Color::from_hex("#FF0000"); + assert_eq!(c.outline_for(true, true, false, Some(red)), red); + assert_eq!(c.outline_for(true, true, true, Some(red)), c.outline); + } + + /// The Breeze Light colours, against Plasma's painted samples. + #[test] + fn breeze_light_paints_the_specifications_colours() { + let t = crate::light(); + let c = BreezeFieldColors::resolve(&t, true, false, false); + assert_eq!(rgba8(c.fill), [255, 255, 255, 255]); + assert_eq!(rgba8(c.hover), [61, 174, 233, 255]); + assert_eq!(c.outline, t.colors.border_strong); + assert_eq!(c.focus, t.colors.border_focused); + let d = BreezeFieldColors::resolve(&t, true, false, true); + assert_eq!(rgba8(d.fill), [243, 243, 243, 255]); + assert_eq!(d.outline, t.colors.border_disabled); + } + + #[test] + fn the_rest_and_focus_outlines_are_lifted_and_their_premises_hold() { + for t in [crate::light(), crate::dark()] { + let p = t.extension::().unwrap(); + let b = p.colors(ColorState::Active); + let view = t.colors.surface_content; + let window = t.colors.surface_main; + assert!( + b.frame_outline.contrast_ratio(view) < 3.0, + "premise: Breeze's frame outline falls short on the view" + ); + let c = BreezeFieldColors::resolve(&t, true, false, false); + assert!(c.outline.contrast_ratio(view) >= 3.0); + assert!(c.outline.contrast_ratio(window) >= 3.0); + assert!(c.focus.contrast_ratio(view) >= 3.0); + } + // Breeze Light's focus colour itself falls short on the white view. + let t = crate::light(); + let focus = t + .extension::() + .unwrap() + .colors(ColorState::Active) + .focus; + assert!(focus.contrast_ratio(t.colors.surface_content) < 3.0); + } + + #[test] + fn the_frame_lies_inside_the_node() { + let r = field_frame_rect(Rect::new(0.0, 0.0, 150.0, 32.0), 1.0); + assert_eq!(r, Rect::new(0.5, 0.5, 149.0, 31.0)); + assert_eq!(frame_radius(&crate::light().shape), 4.5); + } + + #[test] + fn every_validation_level_but_none_tints_the_outline() { + let t = crate::light(); + for level in [ + TextInputValidationLevel::Info, + TextInputValidationLevel::Warning, + TextInputValidationLevel::Error, + TextInputValidationLevel::Corrected, + ] { + assert!(validation_color(level, &t).is_some(), "{level:?}"); + } + assert!(validation_color(TextInputValidationLevel::None, &t).is_none()); + } + + #[test] + fn a_theme_without_the_palette_paints_from_its_roles() { + let t = teksilo_core::presets::intui::light(); + let c = BreezeFieldColors::resolve(&t, true, false, false); + assert_eq!(c.fill, t.colors.surface_content); + assert_eq!(c.hover, t.colors.border_focused); + let d = BreezeFieldColors::resolve(&t, true, false, true); + assert_eq!(d.fill, t.colors.surface_disabled); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/toggle.rs b/crates/teksilo-theme-kde/src/styles/toggle.rs new file mode 100644 index 000000000..f353441ef --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/toggle.rs @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze switch. +//! +//! Plasma's widget style has no switch: the one KDE applications show is the +//! desktop QtQuick style's, which Kirigami applications use (`[Qd]`), and +//! this transcribes it. It is drawn in the **button** colours. +//! +//! - **The indicator** is 36 x 18, Kirigami's grid unit tall and twice as +//! wide. +//! - **The track** is the indicator inset by 3 above and below, 36 x 12 at +//! a 6 px radius, filled with the button colour moved 10 % toward the +//! button text and outlined in 1 px: the highlight when on, otherwise the +//! handle's outline. On, the track is filled again with the highlight at +//! 50 % from its leading end to the handle's trailing edge. +//! - **The handle** is a circle of the indicator's full height, 18 px, +//! filled with the button colour and outlined in 1 px: the button set's +//! hover decoration while hovered or focused from the keyboard, otherwise +//! the button colour moved toward the button text by the frame contrast. +//! Its leading edge is at `clamp(p x 36 - 9, 0, 18)` for the position `p`, +//! 0 off and 1 on, mirrored right to left. +//! +//! ## Motion +//! +//! The handle moves on Qt's smoothed animation: Kirigami's short duration, +//! 100 ms, or the time it takes at Qt's default 200 px a second if that is +//! shorter, so the 18 px travel takes 90 ms, on the quadratic ease-in-out +//! approximated by [`BREEZE_SWITCH_HANDLE_EASING`]. The handle's outline +//! changes colour over the short duration on the cubic ease-in, +//! [`BREEZE_SWITCH_COLOR_EASING`]. Both snap under the reduced-motion +//! preference. +//! +//! ## Where this departs from the desktop style +//! +//! - **D6, the track's outline.** Off and at rest, the track is outlined in +//! the handle's outline, which is 1.3:1 on the Breeze Light window; it is +//! the only boundary of the off switch, so it is drawn in `border_strong`, +//! lifted to 3:1 (D6 of the colour mapping). On, it is outlined in the +//! highlight, 2.2:1 on that window, and is just as much the only boundary, +//! so it is drawn in the highlight lifted the same way, +//! [`checked_outline`](BreezeIndicatorColors::checked_outline), which +//! Breeze Dark's highlight already passes and keeps. The lift includes the +//! button colour, the handle's face, so in either state the handle stands +//! out at 3:1 where the track's outline meets it. Hovered, the off track +//! and the handle are outlined in the button set's hover decoration, +//! which under high contrast is lifted to 4.5:1 on the control surfaces +//! and the handle's face, so the off switch keeps its boundary +//! ([`BreezeIndicatorColors::button_hover`]). +//! - **The handle keeps its colours.** Its face, the button colour, is +//! 1.7:1 on the checked fill and its outline 1.3:1 on the Breeze Light +//! window, as the desktop style paints them. The handle's position is +//! shown by where it interrupts the track's lifted outline, which is 3:1 +//! on its face. +//! - **D5, keyboard focus.** The desktop style shows keyboard focus only by +//! the handle's hover outline. The switch adds the 2 px focus frame the +//! Breeze widget style draws around a check box, in the opaque +//! `focus_ring` token, from the indicator's edge outward (`[derived]`). +//! - **The target.** The 36 x 18 indicator is painted unchanged, centred in +//! a node raised to the density's target size on both axes (36 x 24 at +//! Compact), which WCAG 2.2 SC 2.5.8 asks of a control. +//! - **The label** is the `Toggle`'s, 6 px after the node; the desktop style +//! puts it 4 px after the indicator. + +use std::time::Duration; + +use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal}; +use teksilo_core::accessibility::AccessNodeBuilder; +use teksilo_core::binding::BindingLevel; +use teksilo_core::build_context::BuildContext; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::signal::Signal; +use teksilo_core::styles::density::density_min_size; +use teksilo_core::styles::{FocusRingStroke, ToggleStyle, ToggleStyleConfig}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; +use teksilo_core::widget_id::WidgetId; +use teksilo_tokens::{Color, CornerRadius, TargetAxes}; + +use crate::motion::{BREEZE_SWITCH_COLOR_EASING, BREEZE_SWITCH_HANDLE_EASING}; +use crate::styles::chrome::kde_mix; +use crate::styles::indicator::BreezeIndicatorColors; +use crate::styles::metrics::{ + SWITCH_CHECKED_ALPHA, SWITCH_HANDLE_RADIUS, SWITCH_HANDLE_VELOCITY, SWITCH_HEIGHT, + SWITCH_TRACK_INSET, SWITCH_TRACK_RADIUS, SWITCH_TRACK_TEXT, SWITCH_WIDTH, +}; + +/// The handle's travel, from off to on. +const TRAVEL: f32 = SWITCH_WIDTH - SWITCH_HEIGHT; + +/// `[Qd]` How far the handle's leading edge lies from the indicator's leading +/// edge at the visual position `position` (0 off, 1 on). +pub fn handle_offset(position: f32) -> f32 { + (position * SWITCH_WIDTH - SWITCH_HANDLE_RADIUS).clamp(0.0, TRAVEL) +} + +/// `[qt]` How long the handle takes to travel: the short duration, or the +/// time the full travel takes at Qt's default velocity if that is shorter. +pub fn handle_duration(short: Duration) -> Duration { + short.min(Duration::from_secs_f32(TRAVEL / SWITCH_HANDLE_VELOCITY)) +} + +/// Breeze `ToggleStyle`: the desktop QtQuick style's switch. +#[derive(Debug, Default, Clone, Copy)] +pub struct BreezeToggleStyle; + +impl ToggleStyle for BreezeToggleStyle { + fn make_body(&self, cfg: &ToggleStyleConfig, ctx: &mut BuildContext) -> WidgetId { + let short = ctx.theme().motion.duration_fast; + let target = |on: bool| handle_offset(if on { 1.0 } else { 0.0 }); + + let offset = ctx.animated_signal(target(cfg.is_on.get())); + let slide = ctx + .animate() + .duration(handle_duration(short)) + .easing(BREEZE_SWITCH_HANDLE_EASING); + { + let offset = offset.clone(); + ctx.effect(&cfg.is_on, move |on| slide.to_or_snap(&offset, target(*on))); + } + + // The handle's outline: hovered, or focused from the keyboard. + let emphasised = cfg + .is_hovered + .zip3(&cfg.is_focused, &cfg.is_focus_visible) + .map(|(hovered, focused, visible)| *hovered || (*focused && *visible)); + let emphasis = ctx.animated_signal(if emphasised.get() { 1.0 } else { 0.0 }); + let tint = ctx.animate().fast().easing(BREEZE_SWITCH_COLOR_EASING); + { + let emphasis = emphasis.clone(); + ctx.effect(&emphasised, move |on| { + tint.to_or_snap(&emphasis, if *on { 1.0 } else { 0.0 }) + }); + } + + let show_focus_frame = cfg + .is_focused + .zip3(&cfg.is_focus_visible, &cfg.is_disabled) + .map(|(focused, visible, disabled)| *focused && *visible && !*disabled); + + ctx.add(BreezeSwitchBody { + offset, + emphasis, + is_disabled: cfg.is_disabled.clone(), + show_focus_frame, + }) + } +} + +struct BreezeSwitchBody { + /// The handle's leading edge from the indicator's, 0 to 18. + offset: Signal, + /// How far the handle's outline has turned to the hover colour, 0 to 1. + emphasis: Signal, + is_disabled: Signal, + show_focus_frame: Signal, +} + +impl std::fmt::Debug for BreezeSwitchBody { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BreezeSwitchBody").finish() + } +} + +/// What a switch paints at one moment. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct SwitchPaint { + /// The track: the button colour moved 10 % toward the button text. + pub track: Color, + /// The track's outline. + pub track_outline: Color, + /// The highlight at 50 % from the track's leading end to the handle's + /// trailing edge. + pub checked_fill: Color, + /// The handle's face. + pub handle: Color, + /// The handle's outline. + pub handle_outline: Color, +} + +/// `[Qd]` The colours of a switch whose handle has travelled `on` of the way +/// (0 off, 1 on) and whose outline has turned `emphasis` of the way to the +/// hover colour, from `c`. +pub fn switch_paint(on: f32, emphasis: f32, c: &BreezeIndicatorColors) -> SwitchPaint { + let on = on.clamp(0.0, 1.0); + let emphasis = emphasis.clamp(0.0, 1.0); + let handle_outline = c.button_outline.mix(c.button_hover, emphasis); + // D6: the track's outline is the lifted one, off at rest and on. + let off_outline = c.outline.mix(c.button_hover, emphasis); + SwitchPaint { + track: kde_mix(c.button, c.button_text, SWITCH_TRACK_TEXT), + track_outline: off_outline.mix(c.checked_outline, on), + checked_fill: c.highlight.with_alpha(SWITCH_CHECKED_ALPHA), + handle: c.button, + handle_outline, + } +} + +/// `[Qd]` The 36 x 18 indicator, centred in the node `bounds`. +pub fn switch_indicator(bounds: Rect) -> Rect { + Rect::new( + bounds.x + (bounds.width - SWITCH_WIDTH) * 0.5, + bounds.y + (bounds.height - SWITCH_HEIGHT) * 0.5, + SWITCH_WIDTH, + SWITCH_HEIGHT, + ) +} + +/// `[Qd]` The track: the indicator inset by 3 above and below. +pub fn switch_track(indicator: Rect) -> Rect { + Rect::new( + indicator.x, + indicator.y + SWITCH_TRACK_INSET, + indicator.width, + (indicator.height - 2.0 * SWITCH_TRACK_INSET).max(0.0), + ) +} + +/// The handle's square, `offset` from the indicator's leading edge in +/// `direction`. +pub fn switch_handle(indicator: Rect, offset: f32, direction: LayoutDirection) -> Rect { + let x = match direction { + LayoutDirection::LeftToRight => indicator.x + offset, + LayoutDirection::RightToLeft => indicator.x + indicator.width - SWITCH_HEIGHT - offset, + }; + Rect::new(x, indicator.y, SWITCH_HEIGHT, SWITCH_HEIGHT) +} + +/// The checked part of the track: from its leading end to the handle's +/// trailing edge. +pub fn switch_checked_part(track: Rect, handle: Rect, direction: LayoutDirection) -> Rect { + match direction { + LayoutDirection::LeftToRight => Rect::new( + track.x, + track.y, + (handle.x + handle.width - track.x).max(0.0), + track.height, + ), + LayoutDirection::RightToLeft => Rect::new( + handle.x, + track.y, + (track.x + track.width - handle.x).max(0.0), + track.height, + ), + } +} + +impl Widget for BreezeSwitchBody { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let id = ctx.self_id(); + let registry = ctx.binding_registry(); + self.offset.bind_to(id, registry, BindingLevel::RepaintOnly); + self.emphasis + .bind_to(id, registry, BindingLevel::RepaintOnly); + self.is_disabled + .bind_to(id, registry, BindingLevel::RepaintOnly); + self.show_focus_frame + .bind_to(id, registry, BindingLevel::RepaintOnly); + vec![] + } + + /// The indicator, raised to the density's target size on both axes; the + /// paint stays 36 x 18, centred. + fn layout_response(&self, _proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { + density_min_size( + Size::new(SWITCH_WIDTH, SWITCH_HEIGHT), + TargetAxes::BOTH, + &ctx.theme.input, + ) + .into() + } + + fn place_children( + &self, + _bounds: Rect, + _proposal: SizeProposal, + _children: &mut [WidgetPlacement], + _ctx: &LayoutContext, + ) { + } + + fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) { + let colors = BreezeIndicatorColors::resolve( + ctx.theme, + ctx.window_active, + ctx.prefers_high_contrast, + self.is_disabled.get(), + ); + let offset = self.offset.get().clamp(0.0, TRAVEL); + let paint = switch_paint(offset / TRAVEL, self.emphasis.get(), &colors); + let pen = ctx.theme.shape.border_width; + let direction = ctx.layout_direction; + + let indicator = switch_indicator(bounds); + let track = switch_track(indicator); + let handle = switch_handle(indicator, offset, direction); + let track_corner = CornerRadius::uniform(SWITCH_TRACK_RADIUS); + + canvas.fill_rounded_rect(track, track_corner, paint.track); + if offset > 0.0 && paint.checked_fill.a() > 0.0 { + canvas.fill_rounded_rect( + switch_checked_part(track, handle, direction), + track_corner, + paint.checked_fill, + ); + } + if pen > 0.0 && paint.track_outline.a() > 0.0 { + // A Qt Quick border lies inside its rectangle. + canvas.stroke_rounded_rect( + inset(track, pen * 0.5), + CornerRadius::uniform((SWITCH_TRACK_RADIUS - pen * 0.5).max(0.0)), + paint.track_outline, + pen, + ); + } + + let centre = Point::new( + handle.x + SWITCH_HANDLE_RADIUS, + handle.y + SWITCH_HANDLE_RADIUS, + ); + canvas.fill_circle(centre, SWITCH_HANDLE_RADIUS, paint.handle); + if pen > 0.0 && paint.handle_outline.a() > 0.0 { + canvas.stroke_circle( + centre, + SWITCH_HANDLE_RADIUS - pen * 0.5, + paint.handle_outline, + pen, + ); + } + + if self.show_focus_frame.get() { + let shape = &ctx.theme.shape; + if colors.focus_ring.a() > 0.0 && shape.focus_ring_width > 0.0 { + let ring = FocusRingStroke::at_distance( + indicator, + SWITCH_HEIGHT * 0.5, + shape.focus_ring_width * 0.5, + ); + canvas.stroke_rounded_rect( + ring.rect, + CornerRadius::uniform(ring.corner_radius), + colors.focus_ring, + shape.focus_ring_width, + ); + } + } + } + + fn accessibility(&self, builder: &mut AccessNodeBuilder) { + // Presentational: the `Toggle` emits the switch node. + builder.set_hidden(); + } +} + +fn inset(r: Rect, d: f32) -> Rect { + Rect::new( + r.x + d, + r.y + d, + (r.width - 2.0 * d).max(0.0), + (r.height - 2.0 * d).max(0.0), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_kde_scheme::KdeColor; + + fn rgba8(c: Color) -> [u8; 4] { + KdeColor::from_color(c).to_rgba8() + } + + #[test] + fn the_handle_follows_the_desktop_styles_position_rule() { + assert_eq!(handle_offset(0.0), 0.0); + assert_eq!(handle_offset(1.0), 18.0); + // It moves only over the middle half of the position. + assert_eq!(handle_offset(0.25), 0.0); + assert_eq!(handle_offset(0.5), 9.0); + assert_eq!(handle_offset(0.75), 18.0); + } + + #[test] + fn the_travel_takes_ninety_milliseconds_at_the_default_speed() { + let d = handle_duration(Duration::from_millis(100)); + assert!((d.as_secs_f32() - 0.09).abs() < 1e-6, "{d:?}"); + // A shorter short duration wins. + assert_eq!( + handle_duration(Duration::from_millis(50)), + Duration::from_millis(50) + ); + assert_eq!(handle_duration(Duration::ZERO), Duration::ZERO); + } + + #[test] + fn the_geometry_is_the_desktop_styles() { + let node = Rect::new(0.0, 0.0, 36.0, 24.0); + let i = switch_indicator(node); + assert_eq!(i, Rect::new(0.0, 3.0, 36.0, 18.0)); + assert_eq!(switch_track(i), Rect::new(0.0, 6.0, 36.0, 12.0)); + let off = switch_handle(i, 0.0, LayoutDirection::LeftToRight); + let on = switch_handle(i, 18.0, LayoutDirection::LeftToRight); + assert_eq!(off, Rect::new(0.0, 3.0, 18.0, 18.0)); + assert_eq!(on, Rect::new(18.0, 3.0, 18.0, 18.0)); + // Mirrored right to left. + let rtl_off = switch_handle(i, 0.0, LayoutDirection::RightToLeft); + assert_eq!(rtl_off, on); + let rtl_on = switch_handle(i, 18.0, LayoutDirection::RightToLeft); + assert_eq!(rtl_on, off); + // The checked part runs from the leading end to the handle's trailing + // edge: the whole track when on, either way. + let t = switch_track(i); + assert_eq!(switch_checked_part(t, on, LayoutDirection::LeftToRight), t); + assert_eq!( + switch_checked_part(t, rtl_on, LayoutDirection::RightToLeft), + t + ); + } + + /// The figures the module documentation gives for the handle, which + /// keeps the desktop style's colours, and the 3:1 its position is read + /// at in either state. + #[test] + fn the_handle_keeps_its_colours_and_meets_a_lifted_outline() { + let t = crate::light(); + let c = BreezeIndicatorColors::resolve(&t, true, false, false); + let off = switch_paint(0.0, 0.0, &c); + let on = switch_paint(1.0, 0.0, &c); + let checked = over(on.checked_fill, on.track); + let face = on.handle.contrast_ratio(checked); + assert!((1.65..1.75).contains(&face), "{face}"); + let outline = on.handle_outline.contrast_ratio(t.colors.surface_main); + assert!((1.3..1.4).contains(&outline), "{outline}"); + for p in [off, on] { + assert!(p.track_outline.contrast_ratio(p.handle) >= 3.0, "{p:?}"); + } + } + + /// `top` composited over the opaque `bottom`. + fn over(top: Color, bottom: Color) -> Color { + bottom.with_alpha(1.0).mix(top.with_alpha(1.0), top.a()) + } + + #[test] + fn breeze_lights_switch_colours() { + let c = BreezeIndicatorColors::resolve(&crate::light(), true, false, false); + let p = switch_paint(0.0, 0.0, &c); + // 252 moved 10 % toward 35,38,41. + assert_eq!(rgba8(p.track), [230, 231, 231, 255]); + assert_eq!(p.handle, c.button); + assert_eq!(p.handle_outline, c.button_outline); + assert_eq!(p.track_outline, c.outline, "D6 at rest"); + let on = switch_paint(1.0, 0.0, &c); + assert_eq!(on.track_outline, c.checked_outline, "D6 when on"); + assert_ne!(on.track_outline, c.highlight, "Breeze Light's is lifted"); + let on_hovered = switch_paint(1.0, 1.0, &c); + assert_eq!(on_hovered.track_outline, c.checked_outline); + assert_eq!(on.checked_fill, c.highlight.with_alpha(0.5)); + let hovered = switch_paint(0.0, 1.0, &c); + assert_eq!(hovered.handle_outline, c.button_hover); + assert_eq!(hovered.track_outline, c.button_hover); + } +} diff --git a/crates/teksilo-theme-kde/src/styles/tooltip.rs b/crates/teksilo-theme-kde/src/styles/tooltip.rs new file mode 100644 index 000000000..911a24fb8 --- /dev/null +++ b/crates/teksilo-theme-kde/src/styles/tooltip.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze tooltip. +//! +//! The frame is [`crate::styles::popup`]'s in the tooltip's colours: the +//! tooltip background in a 1 px outline moved toward the tooltip text by the +//! frame contrast, at radius 4.5, over Breeze's two-layer window shadow. The +//! text lies 4 px inside the frame on every side, Breeze's 3 px tooltip frame +//! and the pixel Qt adds, scaled by the density's spacing, so a one-line +//! tooltip of Plasma's default font is 26 px tall, as Qt measures it. The +//! text wraps at 320 px ([`TOOLTIP_MAX_WIDTH`]). +//! +//! The text itself is the tooltip widget's: a plain `TooltipWidget` writes +//! it in the small text style, where Breeze uses the body font. And +//! `TextWidget` does not use `TextStyle::line_height`: the text backend lays +//! a single line out at the face's own natural height (Inter at 11 px: +//! 13.31 px; Noto Sans at 11 px: 14.98 px), so a one-line tooltip is that +//! height plus 2 x 4 px, 21.31 px with the fixed presets and 22.98 px in +//! system mode, not the 26 px of Plasma. That is a property of the framework +//! shared by every preset; matching Plasma would take the tooltip text in +//! the body style or `TextWidget` honouring `line_height`, both outside this +//! crate (the crate's known limitations). + +use teksilo_core::build_context::BuildContext; +use teksilo_core::styles::density::spacing; +use teksilo_core::styles::{TooltipStyle, TooltipStyleConfig}; +use teksilo_core::widget_id::WidgetId; +use teksilo_tokens::InputTokens; + +use crate::geometry::BreezeGeometry; +use crate::styles::metrics::TOOLTIP_MAX_WIDTH; +use crate::styles::popup::{BreezePopupFrame, BreezePopupKind, PopupNode}; + +/// Breeze `TooltipStyle`. +/// +/// [`Default`] is the Compact density; the preset installs +/// [`BreezeTooltipStyle::for_tokens`] of the theme it builds. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BreezeTooltipStyle { + /// `[measured-qt]` The space between the frame and the text on every + /// side. + pub padding: f32, + /// `[unpublished]` The widest the frame lays its content out. + pub max_width: f32, +} + +impl BreezeTooltipStyle { + /// The style at the density of `tokens`. + pub fn for_tokens(tokens: &InputTokens) -> Self { + Self { + padding: spacing(BreezeGeometry::breeze_default().tooltip_padding, tokens), + max_width: TOOLTIP_MAX_WIDTH, + } + } +} + +impl Default for BreezeTooltipStyle { + fn default() -> Self { + Self::for_tokens(&InputTokens::default()) + } +} + +impl TooltipStyle for BreezeTooltipStyle { + fn make_body(&self, cfg: &TooltipStyleConfig, ctx: &mut BuildContext) -> WidgetId { + ctx.add(BreezePopupFrame::new( + cfg.content, + BreezePopupKind::Tooltip, + self.padding, + Some(self.max_width), + // The tooltip widget emits the tooltip node. + PopupNode::Hidden, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use teksilo_tokens::TargetDensity; + + #[test] + fn a_one_line_tooltip_is_breezes_twenty_six_pixels() { + let s = BreezeTooltipStyle::default(); + assert_eq!(s.padding, 4.0); + assert_eq!(s.max_width, 320.0); + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.line_height + 2.0 * s.padding, 26.0); + assert_eq!(g.tooltip_height, 26.0); + let touch = BreezeTooltipStyle::for_tokens(&InputTokens::for_density(TargetDensity::Touch)); + assert!((touch.padding - 5.2).abs() < 1e-4); + assert_eq!(touch.max_width, 320.0); + } +} diff --git a/crates/teksilo-theme-kde/src/typography.rs b/crates/teksilo-theme-kde/src/typography.rs new file mode 100644 index 000000000..2fec50322 --- /dev/null +++ b/crates/teksilo-theme-kde/src/typography.rs @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Breeze typography: Plasma's default fonts mapped onto Teksilo's six +//! [`TextStyle`] slots. +//! +//! | Slot | Plasma font | Glyph size | Line box | Weight | +//! | --- | --- | --- | --- | --- | +//! | `body` | general font, Noto Sans 10 pt | 13 px | 18 px | 400 | +//! | `body_bold` | the same, Kirigami's heading weight | 13 px | 18 px | 600 | +//! | `small` | smallest readable font, Noto Sans 8 pt | 11 px | 15 px | 400 | +//! | `small_bold` | the same, bold | 11 px | 15 px | 600 | +//! | `tiny` | the smallest readable font again: KDE has no smaller role | 11 px | 15 px | 400 | +//! | `mono` | fixed font, Hack 10 pt | 13 px | 15 px | 400 | +//! +//! Nothing is tracked: letter spacing is 0 everywhere. +//! +//! ## Sizes are Qt's, not the point size's +//! +//! A 10 pt font at 96 dpi is 13⅓ px, but Qt resolves a font at a whole +//! pixel size and draws that face: 13 px. Teksilo uses the same 13 px +//! ([`face_px`]), so a label is the size it is in a Qt application next to +//! it rather than a third of a pixel larger. +//! +//! The line box follows Qt's measurement too ([`qt_line_height`]): the +//! face's ascent and descent at that pixel size, each truncated to 1/64 px +//! and rounded to a whole pixel separately, then added. For Noto Sans that +//! gives 15, 17, 18, 20, 22 and 26 px at 8, 9, 10, 11, 12 and 14 pt, the +//! heights Qt reports; a rule such as "1.35 times the size" is a pixel short +//! at several of them. The line-height ratio each style carries is that +//! whole line box over the glyph size, so the box Teksilo lays out is the +//! integer one. The control heights in [`crate::geometry`] are all derived +//! from the body line box. +//! +//! ## Families +//! +//! Plasma's faces are Noto Sans and Hack. Under the `bundled-fonts` or the +//! `system-fonts` feature the stock themes name them ([`crate::fonts`]); +//! without either they keep Teksilo's bundled Inter and JetBrains Mono. The +//! sizes and line boxes above are the same either way: they are computed +//! from the Noto Sans and Hack metrics whichever family draws them, the +//! monospace line box from Hack's `hhea` table (a natural line height of +//! 1.1640625 em, 15 px by Qt's rule at the 13 px face). +//! +//! ## The desktop's fonts +//! +//! [`desktop_typography`] maps a desktop's own fonts onto the six slots: +//! the general font onto `body` and `body_bold`, the smallest readable +//! font onto `small`, `small_bold` and `tiny`, the fixed font onto `mono`. +//! Each takes the font's family as Plasma names it and its size by Qt's +//! rule above (a font stored in pixels keeps its pixel size); the weights +//! stay the slots' own, 400 and 600. The line box is Qt's for Noto Sans and +//! Hack, whose metrics are known, and `round(P x 1.362)` for any other +//! family ([`desktop_line_height`]), Noto Sans's natural line height, which +//! can be a pixel off for a face with other metrics: the preset measures no +//! font. Teksilo has no slot for the menu and toolbar fonts, so +//! [`KdeFontRoles`] carries them on the theme; the menu rows are sized for +//! the menu font ([`crate::styles::menu_item`]). + +use teksilo_kde_scheme::{KdeFont, KdeFonts}; +use teksilo_tokens::{FontWeight, TextStyle, TypographyTokens}; + +use crate::fonts::{HACK_FAMILY, NOTO_SANS_FAMILY}; + +/// The vertical metrics of a font face that its line height is computed +/// from: the `hhea` ascender and the absolute descender, in font units. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FaceMetrics { + /// Units per em. + pub units_per_em: u32, + /// The `hhea` ascender. + pub ascender: u32, + /// The `hhea` descender, as a positive number. + pub descender: u32, +} + +/// `[measured-qt]` Noto Sans v2.015: 1000 units per em, ascender 1069, +/// descender 293, no line gap. The values Qt's line heights reproduce. +pub const NOTO_SANS_METRICS: FaceMetrics = FaceMetrics { + units_per_em: 1000, + ascender: 1069, + descender: 293, +}; + +/// `[measured-qt]` Hack v3.003: 2048 units per em, ascender 1901, descender +/// 483, no line gap. +pub const HACK_METRICS: FaceMetrics = FaceMetrics { + units_per_em: 2048, + ascender: 1901, + descender: 483, +}; + +/// `[measured-qt]` Plasma's general font size, in points. +pub const BREEZE_BODY_PT: f32 = 10.0; +/// `[measured-qt]` Plasma's smallest readable font size, in points. +pub const BREEZE_SMALL_PT: f32 = 8.0; +/// `[measured-qt]` Plasma's fixed font size, in points. +pub const BREEZE_MONO_PT: f32 = 10.0; + +/// `[K]` The weight Kirigami gives a heading, used for the bold styles. +pub const BREEZE_BOLD_WEIGHT: FontWeight = FontWeight::SEMI_BOLD; + +/// `[measured-qt]` The whole pixel size Qt resolves a font of `pt` points +/// to at 96 dpi: `pt x 96 / 72`, rounded half up. 13 for 10 pt, 11 for +/// 8 pt. +pub fn face_px(pt: f32) -> u32 { + let px = f64::from(pt) * 96.0 / 72.0; + (px + 0.5).floor().max(0.0) as u32 +} + +/// `[measured-qt]` The line height Qt reports for a face of `face_px` +/// pixels with `metrics`: the ascent `floor(64 P asc / upm) / 64` and the +/// descent likewise, each rounded half up to a whole pixel, then added. +pub fn qt_line_height(face_px: u32, metrics: FaceMetrics) -> u32 { + let sixty_fourths = |units: u32| -> u64 { + 64 * u64::from(face_px) * u64::from(units) / u64::from(metrics.units_per_em.max(1)) + }; + let round = |v: u64| -> u64 { (v + 32) / 64 }; + (round(sixty_fourths(metrics.ascender)) + round(sixty_fourths(metrics.descender))) as u32 +} + +/// The body line height `H` of Plasma's default general font, 18 px: what +/// every text-driven control height in [`crate::geometry`] starts from. +pub fn breeze_body_line_height() -> f32 { + qt_line_height(face_px(BREEZE_BODY_PT), NOTO_SANS_METRICS) as f32 +} + +/// Breeze typography tokens at Plasma's default fonts. +pub fn breeze_typography() -> TypographyTokens { + // `[unpublished]` Without a font feature, the faces are Teksilo's + // bundled defaults, at the sizes Plasma uses. + let mut t = TypographyTokens::default(); + + // Under either font feature, name Plasma's faces: embedded and + // registered by `crate::font_registrar` under `bundled-fonts`, found + // among the installed fonts under `system-fonts`. + #[cfg(any(feature = "bundled-fonts", feature = "system-fonts"))] + { + for style in [ + &mut t.body, + &mut t.body_bold, + &mut t.small, + &mut t.small_bold, + &mut t.tiny, + ] { + style.family = NOTO_SANS_FAMILY.to_string(); + } + t.mono.family = HACK_FAMILY.to_string(); + } + + let body_px = face_px(BREEZE_BODY_PT); + let body_line = qt_line_height(body_px, NOTO_SANS_METRICS); + let small_px = face_px(BREEZE_SMALL_PT); + let small_line = qt_line_height(small_px, NOTO_SANS_METRICS); + let mono_px = face_px(BREEZE_MONO_PT); + let mono_line = qt_line_height(mono_px, HACK_METRICS); + + let set = |style: &mut teksilo_tokens::TextStyle, px: u32, line: u32, weight: FontWeight| { + style.size = px as f32; + style.line_height = line as f32 / px as f32; + style.weight = weight; + style.letter_spacing = 0.0; + }; + set(&mut t.body, body_px, body_line, FontWeight::REGULAR); + set(&mut t.body_bold, body_px, body_line, BREEZE_BOLD_WEIGHT); + set(&mut t.small, small_px, small_line, FontWeight::REGULAR); + set(&mut t.small_bold, small_px, small_line, BREEZE_BOLD_WEIGHT); + // `[derived]` KDE has no role below the smallest readable font. + set(&mut t.tiny, small_px, small_line, FontWeight::REGULAR); + set(&mut t.mono, mono_px, mono_line, FontWeight::REGULAR); + t +} + +/// `[derived]` The line height of a family whose metrics the preset does +/// not know, in em: Noto Sans's natural line height, its ascender and +/// descender over its units per em, rounded to three places. +pub const NATURAL_LINE_HEIGHT_EM: f64 = 1.362; + +/// The metrics of `family` when the preset knows them: Noto Sans and Hack, +/// named in any ASCII case. +pub fn known_metrics(family: &str) -> Option { + if family.eq_ignore_ascii_case(NOTO_SANS_FAMILY) { + Some(NOTO_SANS_METRICS) + } else if family.eq_ignore_ascii_case(HACK_FAMILY) { + Some(HACK_METRICS) + } else { + None + } +} + +/// `[measured-qt]` The whole pixel size Qt draws `font` at: its pixel size +/// when it is stored in pixels, otherwise its point size by [`face_px`], +/// otherwise `default`'s. At least 1. +pub fn desktop_face_px(font: &KdeFont, default: &KdeFont) -> u32 { + let points = |f: &KdeFont| f.point_size.map(|pt| face_px(pt as f32)); + let pixels = |f: &KdeFont| f.pixel_size.map(|px| px.max(0) as u32); + pixels(font) + .or_else(|| points(font)) + .or_else(|| pixels(default)) + .or_else(|| points(default)) + .unwrap_or_else(|| face_px(BREEZE_BODY_PT)) + .max(1) +} + +/// The line height of a `face_px` face of `family`: [`qt_line_height`] +/// with its metrics when the preset knows them ([`known_metrics`]), and +/// `[derived]` `round(face_px x 1.362)`, rounded half up, otherwise. +pub fn desktop_line_height(face_px: u32, family: &str) -> u32 { + match known_metrics(family) { + Some(metrics) => qt_line_height(face_px, metrics), + None => (f64::from(face_px) * NATURAL_LINE_HEIGHT_EM + 0.5).floor() as u32, + } +} + +/// The text style of a desktop `font` at `weight`: its family (`default`'s +/// when it names none, `[unpublished]`), the whole pixel size Qt draws it +/// at ([`desktop_face_px`]) and the line box of [`desktop_line_height`], +/// with no tracking. +pub fn desktop_text_style(font: &KdeFont, default: &KdeFont, weight: FontWeight) -> TextStyle { + let family = if font.family.trim().is_empty() { + default.family.clone() + } else { + font.family.clone() + }; + let px = desktop_face_px(font, default); + let line = desktop_line_height(px, &family); + TextStyle { + family, + size: px as f32, + weight, + line_height: line as f32 / px as f32, + letter_spacing: 0.0, + } +} + +/// Breeze typography tokens for a desktop's `fonts`. See [the module +/// documentation](self#the-desktops-fonts). +pub fn desktop_typography(fonts: &KdeFonts) -> TypographyTokens { + let defaults = KdeFonts::default(); + let general = |weight| desktop_text_style(&fonts.general, &defaults.general, weight); + let small = |weight| desktop_text_style(&fonts.small, &defaults.small, weight); + TypographyTokens { + body: general(FontWeight::REGULAR), + body_bold: general(BREEZE_BOLD_WEIGHT), + small: small(FontWeight::REGULAR), + small_bold: small(BREEZE_BOLD_WEIGHT), + // `[derived]` KDE has no role below the smallest readable font. + tiny: small(FontWeight::REGULAR), + mono: desktop_text_style(&fonts.fixed, &defaults.fixed, FontWeight::REGULAR), + } +} + +/// The two KDE font roles Teksilo's six text styles have no slot for, as +/// text styles: the menu font and the toolbar font. +/// +/// A [`teksilo_core::styles::Theme`] extension. The menu rows the preset +/// installs are sized for [`menu`](Self::menu); `MenuItem` still writes its +/// label in the body style unless an app hands it this one, and a toolbar's +/// buttons write in the body style, so an app that wants the desktop's +/// toolbar font passes [`toolbar`](Self::toolbar) to the labels it builds. +#[derive(Debug, Clone, PartialEq)] +pub struct KdeFontRoles { + /// `[General] menuFont`: Plasma's default, Noto Sans 10 pt. + pub menu: TextStyle, + /// `[General] toolBarFont`: Plasma's default, Noto Sans 10 pt. + pub toolbar: TextStyle, +} + +impl KdeFontRoles { + /// The stock themes' roles: both are the body style of + /// [`breeze_typography`], as Plasma's defaults are the general font. + pub fn breeze_default() -> KdeFontRoles { + let body = breeze_typography().body; + KdeFontRoles { + menu: body.clone(), + toolbar: body, + } + } + + /// The roles of a desktop's `fonts`, regular weight, as + /// [`desktop_text_style`] maps them. + pub fn from_fonts(fonts: &KdeFonts) -> KdeFontRoles { + let defaults = KdeFonts::default(); + KdeFontRoles { + menu: desktop_text_style(&fonts.menu, &defaults.menu, FontWeight::REGULAR), + toolbar: desktop_text_style(&fonts.toolbar, &defaults.toolbar, FontWeight::REGULAR), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn line_box(style: &teksilo_tokens::TextStyle) -> f32 { + style.size * style.line_height + } + + #[test] + fn the_face_size_is_the_whole_pixel_size_qt_resolves() { + assert_eq!(face_px(10.0), 13); + assert_eq!(face_px(8.0), 11); + assert_eq!(face_px(9.0), 12); + assert_eq!(face_px(11.0), 15); + assert_eq!(face_px(12.0), 16); + assert_eq!(face_px(14.0), 19); + } + + #[test] + fn the_line_height_reproduces_every_size_qt_reported() { + for (pt, expected) in [ + (8.0, 15), + (9.0, 17), + (10.0, 18), + (11.0, 20), + (12.0, 22), + (14.0, 26), + ] { + assert_eq!( + qt_line_height(face_px(pt), NOTO_SANS_METRICS), + expected, + "Noto Sans {pt} pt" + ); + } + for (pt, expected) in [(9.0, 14), (10.0, 15), (11.0, 18)] { + assert_eq!( + qt_line_height(face_px(pt), HACK_METRICS), + expected, + "Hack {pt} pt" + ); + } + } + + #[test] + fn rounding_ascent_and_descent_together_would_be_a_pixel_short() { + // The reason the two are rounded separately: at 9 pt the ascent + // 12.81 and the descent 3.52 round to 13 and 4, 17 in all, while + // their sum 16.33 rounds to 16. Qt reports 17. + let together = |pt: f32| { + let p = f64::from(face_px(pt)); + let m = NOTO_SANS_METRICS; + let a = (64.0 * p * f64::from(m.ascender) / f64::from(m.units_per_em)).floor() / 64.0; + let d = (64.0 * p * f64::from(m.descender) / f64::from(m.units_per_em)).floor() / 64.0; + (a + d).round() as u32 + }; + assert_eq!(together(9.0), 16); + assert_eq!(qt_line_height(face_px(9.0), NOTO_SANS_METRICS), 17); + } + + #[test] + fn the_styles_carry_the_whole_line_boxes() { + let t = breeze_typography(); + assert_eq!(t.body.size, 13.0); + assert!((line_box(&t.body) - 18.0).abs() < 1e-4); + assert_eq!(t.body_bold.size, 13.0); + assert!((line_box(&t.body_bold) - 18.0).abs() < 1e-4); + for small in [&t.small, &t.small_bold, &t.tiny] { + assert_eq!(small.size, 11.0); + assert!((line_box(small) - 15.0).abs() < 1e-4); + } + assert_eq!(t.mono.size, 13.0); + assert!((line_box(&t.mono) - 15.0).abs() < 1e-4); + assert_eq!(breeze_body_line_height(), 18.0); + } + + #[test] + fn weights_and_tracking() { + let t = breeze_typography(); + assert_eq!(t.body.weight, FontWeight::REGULAR); + assert_eq!(t.body_bold.weight, FontWeight::SEMI_BOLD); + assert_eq!(t.small_bold.weight, FontWeight::SEMI_BOLD); + assert_eq!(t.tiny.weight, FontWeight::REGULAR); + for s in [ + &t.body, + &t.body_bold, + &t.small, + &t.small_bold, + &t.tiny, + &t.mono, + ] { + assert_eq!(s.letter_spacing, 0.0); + } + } + + #[test] + fn plasmas_default_fonts_give_the_breeze_sizes_and_line_boxes() { + let t = desktop_typography(&KdeFonts::default()); + let b = breeze_typography(); + for (desktop, stock) in [ + (&t.body, &b.body), + (&t.body_bold, &b.body_bold), + (&t.small, &b.small), + (&t.small_bold, &b.small_bold), + (&t.tiny, &b.tiny), + (&t.mono, &b.mono), + ] { + assert_eq!(desktop.size, stock.size); + assert_eq!(desktop.line_height, stock.line_height); + assert_eq!(desktop.weight, stock.weight); + assert_eq!(desktop.letter_spacing, 0.0); + } + assert_eq!(t.body.family, "Noto Sans"); + assert_eq!(t.small.family, "Noto Sans"); + assert_eq!(t.mono.family, "Hack"); + let roles = KdeFontRoles::from_fonts(&KdeFonts::default()); + assert_eq!(roles.menu, t.body); + assert_eq!(roles.toolbar, t.body); + } + + #[test] + fn a_family_without_known_metrics_takes_noto_sans_natural_line_height() { + // 11 pt is a 15 px face: 15 x 1.362 is 20.43. + assert_eq!(desktop_line_height(15, "Cantarell"), 20); + // 16 x 1.362 is 21.79, and 22 for Noto Sans by Qt's rule as well. + assert_eq!(desktop_line_height(16, "Inter"), 22); + assert_eq!(desktop_line_height(16, "noto sans"), 22); + // Hack's own metrics: 15 at 13 px, where the rule would say 18. + assert_eq!(desktop_line_height(13, "HACK"), 15); + assert_eq!(desktop_line_height(13, "Fira Code"), 18); + } + + #[test] + fn a_font_stored_in_pixels_keeps_its_pixel_size() { + let defaults = KdeFonts::default(); + let mut font = KdeFont::new("Noto Sans", 0.0); + font.point_size = None; + font.pixel_size = Some(17); + assert_eq!(desktop_face_px(&font, &defaults.general), 17); + // Neither stored: the default's 10 pt. + font.pixel_size = None; + assert_eq!(desktop_face_px(&font, &defaults.general), 13); + // A tiny point size still draws a pixel. + font.point_size = Some(0.1); + assert_eq!(desktop_face_px(&font, &defaults.general), 1); + } + + #[test] + fn a_font_without_a_family_takes_the_roles_default() { + let defaults = KdeFonts::default(); + let style = desktop_text_style( + &KdeFont::new("", 12.0), + &defaults.fixed, + FontWeight::REGULAR, + ); + assert_eq!(style.family, "Hack"); + assert_eq!(style.size, 16.0); + } + + #[cfg(not(any(feature = "bundled-fonts", feature = "system-fonts")))] + #[test] + fn the_families_are_the_bundled_defaults() { + let t = breeze_typography(); + let base = TypographyTokens::default(); + assert_eq!(t.body.family, base.body.family); + assert_eq!(t.small.family, base.small.family); + assert_eq!(t.mono.family, base.mono.family); + } + + #[cfg(any(feature = "bundled-fonts", feature = "system-fonts"))] + #[test] + fn a_font_feature_names_plasmas_faces() { + let t = breeze_typography(); + for style in [&t.body, &t.body_bold, &t.small, &t.small_bold, &t.tiny] { + assert_eq!(style.family, NOTO_SANS_FAMILY); + } + assert_eq!(t.mono.family, HACK_FAMILY); + } +} diff --git a/crates/teksilo-theme-kde/tests/breeze_reference.rs b/crates/teksilo-theme-kde/tests/breeze_reference.rs new file mode 100644 index 000000000..8a4da671a --- /dev/null +++ b/crates/teksilo-theme-kde/tests/breeze_reference.rs @@ -0,0 +1,1342 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze controls rendered through the production wgpu renderer, +//! compared pixel by pixel with Plasma 6.8 painting the same controls +//! (`tools/kde_golden/vectors/pixels.json`, described in +//! `tools/kde_golden/README.md`). +//! +//! The unit tests beside each style check the colours and the rectangles it +//! asks for, and `tests/mount.rs` checks them in the render frame. Neither +//! sees where a 1 px outline lands on the pixel grid, how much of a corner +//! pixel a radius covers, how a 2 px check mark with mitred joins +//! rasterises, or which pixels a scroll-bar handle covers: those are decided +//! by the renderer from what the style asked for, and they are what this +//! file compares. +//! +//! ## How a control is rendered +//! +//! Each control is the root of a tree laid out at the size Plasma painted +//! it at (or, for the check box and the radio button, in the 24 px row the +//! widgets lay their 20 px indicator in, D8), with the text engine and the +//! glyph atlas the previewer's snapshots use (`teksilo-preview-ui`'s +//! `shot.rs`). It is rendered by `Renderer::render_capture` into an sRGB +//! texture cleared to the colour Plasma filled its image with, and read +//! back. Labels are empty: the vectors paint no text. +//! +//! ## How a pixel is compared +//! +//! Each sample names what it shows and is compared one of three ways: +//! +//! - **Solid**: a pixel wholly inside one opaque paint (a face, an outline +//! that covers its pixel, the background). The colours must agree within +//! [`SOLID_TOLERANCE`] on every channel. +//! - **Share**: an antialiased pixel, or a translucent paint. Each image's +//! pixel is reduced to its share of the way from an `under` colour to an +//! `over` colour (the projection onto the line between them), and the two +//! shares must agree within [`SHARE_TOLERANCE`]. The share is measured in +//! each image's own compositing space: Qt's raster painter blends in sRGB, +//! Teksilo's renderer in linear light (it draws into an `Rgba8UnormSrgb` +//! target). An edge covering 40 % of a pixel is 40 % of the way from one +//! colour to the other in both, and a shadow at 12.5 % is 12.5 % of the way +//! to black in both, although the 8-bit colours differ: compared as +//! colours, every antialiased edge and every translucent paint over a +//! backdrop the style does not paint would differ by the gap between the +//! two spaces, which is up to 70 levels for a dark mark on a light face +//! and says nothing about the style. Where the two colours are too close +//! for one level to leave the share meaningful ([`MAX_SHARE_STEP`]), the +//! pixel is compared as a solid one. +//! - **Bare** and **Ink**: the pixel is the background in both images, or +//! covered in both. Only for the scroll-bar handle and the unmarked check +//! box's outline, whose colours the preset lifts (D9, D6). +//! +//! The `under` and `over` colours are either a pixel of the same image (so a +//! colour the preset deliberately lifts, D6 or D9, is measured against its +//! own lifted value) or a palette colour, the same in both. +//! +//! ## The tolerances +//! +//! - [`SOLID_TOLERANCE`], 2 levels: the colours are KDE's 16-bit +//! arithmetic rounded to 8 bits, then linearised and re-encoded by the +//! texture, each step within one level. +//! - [`SHARE_TOLERANCE`], the sum of two measured gaps, each pinned by a +//! test below. [`RENDERER_COVERAGE_GAP`]: the renderer's shape pipeline +//! and its path atlas ramp a pixel's coverage with a smoothstep over the +//! signed distance at its centre, where the area of the shape inside the +//! pixel is the true coverage; on a straight edge the two differ by up to +//! 0.096, and on the arcs sampled here by up to 0.093. [`QT_CORNER_GAP`]: +//! Qt's own corners are not the area of the specification's shape either, +//! by up to 0.12 at the push button's (its arcs lie a little outside the +//! radius). The two can add. Half a pixel of radius still moves the share +//! of the corner pixel on the diagonal by 0.3 or more, a missing square +//! cap the pixel before the check mark by 0.33, a misplaced outline by 1. +//! +//! One pixel is left out for that reason: the inner edge of the checked +//! box's corner, (5, 3), where the outline is laid twice and the renderer's +//! gap doubles with it. The doubled outline also saturates the box's corner +//! pixels, so the box's radius is held from below only: at 3.5 its corners +//! disagree, at 4.5 they do not. +//! +//! ## Where the two bars differ +//! +//! The scroll bar's handle is filled in the lifted thumb colour (D9), so it +//! is compared by which pixels it covers, not by its colour; and Breeze +//! starts it 3 px down the bar, the margin it keeps at each end of a bar +//! without buttons, which the widget leaves its style no say over (the +//! crate's known limitations): the handle samples are compared 3 px apart. +//! +//! A host with no GPU adapter skips every test here with a message (unless +//! `TEKSILO_TEST_REQUIRE_ADAPTER` asks for one). This file reads the vectors +//! from the repository, so it is left out of the published package (the +//! `exclude` list in `Cargo.toml`). + +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::rc::Rc; + +use serde_json::Value; +use teksilo_canvas::SizeProposal; +use teksilo_core::event::PointerButton; +use teksilo_core::event_source::TreeAppContext; +use teksilo_core::signal::Signal; +use teksilo_core::styles::Theme; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_i18n::lit; +use teksilo_kde_scheme::{ColorState, PaletteRole}; +use teksilo_render::test_support; +use teksilo_text::SharedTypesetter; +use teksilo_theme_kde::{self as kde, KdePalette}; +use teksilo_tokens::Color; +use teksilo_widgets::scroll_bar::{ScrollBar, ScrollBarOrientation, ScrollBarVariant}; +use teksilo_widgets::{Button, ButtonVariant, CheckState, Checkbox, RadioButton, StandardListItem}; + +const PIXELS_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tools/kde_golden/vectors/pixels.json" +)); + +/// The largest per-channel difference between two solid pixels. +const SOLID_TOLERANCE: u8 = 2; + +/// The most the renderer's coverage of a pixel departs from the area its +/// shape covers: `max |smoothstep(t) - t|` over a one-pixel ramp, +/// [`the_renderer_coverage_gap_is_the_smoothsteps`]. +const RENDERER_COVERAGE_GAP: f32 = 0.097; + +/// The most Qt's coverage of a push button's corner pixel departs from the +/// area of the specification's shape (a 1 px outline at radius 4.5), +/// [`qts_corners_depart_from_the_specified_shape_by_the_qt_gap`]. +const QT_CORNER_GAP: f32 = 0.12; + +/// The largest difference between two shares. See the module documentation. +const SHARE_TOLERANCE: f32 = RENDERER_COVERAGE_GAP + QT_CORNER_GAP; + +/// The most one 8-bit level of a pixel may move its share for the share to +/// be measured. Where the `under` and `over` colours are closer than that +/// (a shadow at 12.5 % over Breeze Dark's window, which is 32 levels from +/// black in sRGB but 0.015 from it in linear light), the pixel is compared as +/// a colour instead: the two compositing spaces cannot then differ by more +/// than [`SOLID_TOLERANCE`]. +const MAX_SHARE_STEP: f32 = SHARE_TOLERANCE / 4.0; + +/// How far a pixel must be from the background, in levels on some channel, +/// to count as covered by the scroll-bar handle. +const INK_THRESHOLD: u8 = 8; + +// ── The vectors ───────────────────────────────────────────────────────── + +/// One of Plasma's painted controls: its image size, the colour the image +/// was filled with, and the sampled rows and columns. +struct Golden { + size: (usize, usize), + background: [u8; 4], + rows: HashMap>, + cols: HashMap>, +} + +fn hex(s: &str) -> [u8; 4] { + let h = s.trim_start_matches('#'); + let byte = |i: usize| u8::from_str_radix(&h[i..i + 2], 16).expect("a hex colour"); + [byte(0), byte(2), byte(4), byte(6)] +} + +fn lines(value: &Value) -> HashMap> { + value + .as_object() + .expect("sampled lines") + .iter() + .map(|(at, runs)| { + let runs = runs + .as_array() + .expect("a run-length line") + .iter() + .map(|run| { + ( + run[0].as_u64().expect("a start") as usize, + run[1].as_u64().expect("a length") as usize, + hex(run[2].as_str().expect("a colour")), + ) + }) + .collect(); + (at.parse().expect("a line index"), runs) + }) + .collect() +} + +impl Golden { + fn load(scheme: &str, id: &str) -> Golden { + let all: Value = serde_json::from_str(PIXELS_JSON).expect("pixels.json parses"); + let record = all["schemes"][scheme]["records"] + .as_array() + .expect("the scheme's records") + .iter() + .find(|r| r["id"] == id) + .unwrap_or_else(|| panic!("{scheme} has no {id}")); + let size = record["size"].as_array().expect("a size"); + Golden { + size: ( + size[0].as_u64().unwrap() as usize, + size[1].as_u64().unwrap() as usize, + ), + background: hex(record["background"].as_str().expect("a background")), + rows: lines(&record["rows"]), + cols: lines(&record["cols"]), + } + } + + /// The colour Plasma painted at `(x, y)`, which must lie on a sampled + /// row or column. + fn at(&self, x: usize, y: usize) -> [u8; 4] { + let (runs, along) = if let Some(row) = self.rows.get(&y) { + (row, x) + } else if let Some(col) = self.cols.get(&x) { + (col, y) + } else { + panic!("neither row {y} nor column {x} is sampled"); + }; + runs.iter() + .find(|(start, len, _)| (*start..start + len).contains(&along)) + .map(|(_, _, c)| *c) + .expect("the line covers the pixel") + } +} + +// ── The renderer ──────────────────────────────────────────────────────── + +/// The production renderer on the shared offscreen device, with the text +/// engine and glyph atlas a snapshot needs. +struct Gpu { + renderer: teksilo_render::Renderer, + device: wgpu::Device, + queue: wgpu::Queue, + typesetter: SharedTypesetter, + atlas_version: u64, +} + +impl Gpu { + /// The renderer, or `None` with a message when this host has no adapter. + fn new(test: &str) -> Option { + let Some((renderer, device, queue)) = + pollster::block_on(test_support::require_test_renderer("breeze_reference")) + else { + println!("{test}: skipped, no GPU adapter on this host"); + return None; + }; + Some(Gpu { + renderer, + device, + queue, + typesetter: SharedTypesetter::new_with_default_font(), + atlas_version: 0, + }) + } + + /// A tree under `theme` with the typesetter as its text backend and as + /// app state, where the editable text widgets look for it. + fn tree(&self, theme: Theme) -> WidgetTree { + let mut tree = WidgetTree::new() + .with_theme(theme) + .with_text_backend(self.typesetter.as_text_backend()); + let mut app_state: HashMap> = HashMap::new(); + app_state.insert( + TypeId::of::(), + Box::new(self.typesetter.clone()), + ); + tree.set_app_context(Rc::new(TreeAppContext::empty().with_app_state(app_state))); + tree + } + + /// Render `tree`, laid out at `width` x `height`, on a target cleared to + /// `background`: tightly packed RGBA. + fn capture( + &mut self, + tree: &mut WidgetTree, + width: u32, + height: u32, + background: [u8; 4], + ) -> Capture { + tree.layout(SizeProposal::exact(width as f32, height as f32)); + let frame = tree.render(); + // Glyphs rasterise during the render above; the renderer only knows + // them once the atlas is uploaded. + let atlas = self + .typesetter + .bridge() + .borrow_mut() + .atlas_info(self.atlas_version); + if !atlas.pixels.is_empty() && atlas.width > 0 && atlas.height > 0 { + self.renderer + .upload_atlas(atlas.width, atlas.height, &atlas.pixels); + self.atlas_version = atlas.version; + } + let clear = + teksilo_render::vertex::srgb_to_linear_rgba(background.map(|v| f32::from(v) / 255.0)); + let texture = self.device.create_texture(&wgpu::TextureDescriptor { + label: Some("breeze_reference"), + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + self.renderer + .render_capture(&frame, &view, 1.0, width, height, clear); + Capture { + width: width as usize, + rgba: test_support::read_texture_rgba( + &self.device, + &self.queue, + &texture, + width, + height, + ), + } + } +} + +/// A rendered image. +struct Capture { + width: usize, + rgba: Vec, +} + +impl Capture { + fn at(&self, x: usize, y: usize) -> [u8; 4] { + let i = (y * self.width + x) * 4; + [ + self.rgba[i], + self.rgba[i + 1], + self.rgba[i + 2], + self.rgba[i + 3], + ] + } +} + +// ── The checks ────────────────────────────────────────────────────────── + +/// A colour a share is measured from or toward. +#[derive(Debug, Clone, Copy)] +enum Ref { + /// The pixel at `(x, y)` of the golden image, and the matching pixel of + /// the capture. + At(usize, usize), + /// A colour of the scheme's active palette, the same in both images. + Role(PaletteRole), + /// Black, the shadow colour. + Black, +} + +#[derive(Debug, Clone, Copy)] +enum Check { + /// A pixel inside one opaque paint: the colours agree. + Solid, + /// An antialiased pixel or a translucent paint: the shares from `under` + /// to `over` agree. + Share { under: Ref, over: Ref }, + /// The pixel is the background in both images (scroll-bar handle). + Bare, + /// The pixel is covered, not the background, in both images. + Ink, +} + +/// One compared pixel: what it shows, where it is in the golden image, and +/// how it is compared. +#[derive(Debug, Clone, Copy)] +struct Sample { + what: &'static str, + at: (usize, usize), + check: Check, + /// Added to the capture's coordinates: where a control places a part + /// elsewhere than Plasma does, by design (see the module documentation). + shift: (isize, isize), +} + +fn solid(what: &'static str, x: usize, y: usize) -> Sample { + Sample { + what, + at: (x, y), + check: Check::Solid, + shift: (0, 0), + } +} + +fn share(what: &'static str, x: usize, y: usize, under: Ref, over: Ref) -> Sample { + Sample { + what, + at: (x, y), + check: Check::Share { under, over }, + shift: (0, 0), + } +} + +/// `pixels` as shares of the way from `under` to `over`. +fn shares(what: &'static str, pixels: &[(usize, usize)], under: Ref, over: Ref) -> Vec { + pixels + .iter() + .map(|&(x, y)| share(what, x, y, under, over)) + .collect() +} + +/// `pixels` checked as covered or bare. +fn coverage(what: &'static str, pixels: &[(usize, usize)], check: Check) -> Vec { + pixels + .iter() + .map(|&(x, y)| Sample { + what, + at: (x, y), + check, + shift: (0, 0), + }) + .collect() +} + +/// `pixels` as solid pixels. +fn solids(what: &'static str, pixels: &[(usize, usize)]) -> Vec { + pixels.iter().map(|&(x, y)| solid(what, x, y)).collect() +} + +fn unit(c: [u8; 4], linear: bool) -> [f32; 3] { + let f = [c[0], c[1], c[2], 255].map(|v| f32::from(v) / 255.0); + let f = if linear { + teksilo_render::vertex::srgb_to_linear_rgba(f) + } else { + f + }; + [f[0], f[1], f[2]] +} + +/// `pixel`'s share of the way from `under` to `over`, in sRGB or in linear +/// light, and how much it moves when the pixel moves one 8-bit level on +/// every channel; `None` when the two colours are the same. +fn share_of(pixel: [u8; 4], under: [u8; 4], over: [u8; 4], linear: bool) -> Option<(f32, f32)> { + let (p, u, o) = (unit(pixel, linear), unit(under, linear), unit(over, linear)); + let d = [o[0] - u[0], o[1] - u[1], o[2] - u[2]]; + let span = d.iter().map(|v| v * v).sum::(); + if span <= f32::EPSILON { + return None; + } + let share = (0..3).map(|i| (p[i] - u[i]) * d[i]).sum::() / span; + let up = pixel.map(|v| v.saturating_add(1)); + let q = unit(up, linear); + let step = (0..3).map(|i| ((q[i] - p[i]) * d[i]).abs()).sum::() / span; + Some((share, step)) +} + +fn rgb(c: [u8; 4]) -> String { + format!("#{:02x}{:02x}{:02x}", c[0], c[1], c[2]) +} + +fn differs(a: [u8; 4], b: [u8; 4], by: u8) -> bool { + (0..3).any(|i| a[i].abs_diff(b[i]) > by) +} + +// ── The scenarios ─────────────────────────────────────────────────────── + +/// One golden record and the control rendered to match it. +struct Scenario { + /// The record's id in `pixels.json`. + id: &'static str, + /// The capture's size. + canvas: (u32, u32), + /// Where the golden image's origin lies in the capture. + origin: (usize, usize), + /// Mount the control as the tree's root. + mount: fn(&mut WidgetTree) -> WidgetId, + /// Put it in the record's state, after a first layout. + prepare: fn(&mut WidgetTree, WidgetId), + samples: Vec, +} + +fn at_rest(_: &mut WidgetTree, _: WidgetId) {} + +fn hovered(tree: &mut WidgetTree, id: WidgetId) { + let centre = tree.bounds(id).center(); + tree.pointer_move(centre); +} + +fn pressed(tree: &mut WidgetTree, id: WidgetId) { + let centre = tree.bounds(id).center(); + tree.pointer_move(centre); + tree.pointer_down_button(centre, PointerButton::Primary); +} + +fn palette_colour(theme: &Theme, role: PaletteRole) -> [u8; 4] { + let c: Color = theme + .extension::() + .expect("the preset's palette") + .role(ColorState::Active, role); + c.to_array() + .map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) +} + +/// Render every scenario in Breeze Light and Breeze Dark and compare its +/// samples: the report of every sample that disagrees, empty when all agree. +fn compare(gpu: &mut Gpu, scenarios: &[Scenario]) -> Vec { + let mut failures = Vec::new(); + for (scheme, theme) in [("BreezeLight", kde::light()), ("BreezeDark", kde::dark())] { + for s in scenarios { + let golden = Golden::load(scheme, s.id); + let mut tree = gpu.tree(theme.clone()); + let id = (s.mount)(&mut tree); + tree.layout(SizeProposal::exact(s.canvas.0 as f32, s.canvas.1 as f32)); + (s.prepare)(&mut tree, id); + let shot = gpu.capture(&mut tree, s.canvas.0, s.canvas.1, golden.background); + let ours = |(x, y): (usize, usize), shift: (isize, isize)| { + let gx = (x + s.origin.0) as isize + shift.0; + let gy = (y + s.origin.1) as isize + shift.1; + assert!( + gx >= 0 && gy >= 0 && (gx as u32) < s.canvas.0 && (gy as u32) < s.canvas.1, + "{}: ({x}, {y}) lies outside the capture", + s.id + ); + shot.at(gx as usize, gy as usize) + }; + let colour = |r: Ref, shift: (isize, isize)| match r { + Ref::At(x, y) => (golden.at(x, y), ours((x, y), shift)), + Ref::Role(role) => { + let c = palette_colour(&theme, role); + (c, c) + } + Ref::Black => ([0, 0, 0, 255], [0, 0, 0, 255]), + }; + assert!( + golden.size.0 + s.origin.0 <= s.canvas.0 as usize + && golden.size.1 + s.origin.1 <= s.canvas.1 as usize, + "{}: Plasma's {:?} image does not fit the capture", + s.id, + golden.size + ); + for sample in &s.samples { + let (x, y) = sample.at; + let theirs = golden.at(x, y); + let mine = ours((x, y), sample.shift); + let fail = match sample.check { + Check::Solid => differs(mine, theirs, SOLID_TOLERANCE) + .then(|| format!("{} against Plasma's {}", rgb(mine), rgb(theirs))), + Check::Share { under, over } => { + let (u_theirs, u_mine) = colour(under, sample.shift); + let (o_theirs, o_mine) = colour(over, sample.shift); + let measured = |p, u, o, linear| { + share_of(p, u, o, linear).unwrap_or_else(|| { + panic!( + "{} {}: {under:?} and {over:?} are one colour", + s.id, sample.what + ) + }) + }; + let (t, t_step) = measured(theirs, u_theirs, o_theirs, false); + let (m, m_step) = measured(mine, u_mine, o_mine, true); + if t_step.max(m_step) > MAX_SHARE_STEP { + differs(mine, theirs, SOLID_TOLERANCE).then(|| { + format!( + "{} against Plasma's {} (too close to {under:?} for a share)", + rgb(mine), + rgb(theirs) + ) + }) + } else { + ((t - m).abs() > SHARE_TOLERANCE).then(|| { + format!( + "share {m:.3} ({}) against Plasma's {t:.3} ({})", + rgb(mine), + rgb(theirs) + ) + }) + } + } + Check::Bare => (differs(theirs, golden.background, SOLID_TOLERANCE) + || differs(mine, golden.background, SOLID_TOLERANCE)) + .then(|| { + format!( + "{} and Plasma's {} are not both the background", + rgb(mine), + rgb(theirs) + ) + }), + Check::Ink => (!differs(theirs, golden.background, INK_THRESHOLD) + || !differs(mine, golden.background, INK_THRESHOLD)) + .then(|| { + format!( + "{} and Plasma's {} are not both covered", + rgb(mine), + rgb(theirs) + ) + }), + }; + if let Some(why) = fail { + failures.push(format!( + "{scheme} {} {} at ({x}, {y}): {why}", + s.id, sample.what + )); + } + } + } + } + failures +} + +fn assert_agree(test: &str, scenarios: Vec) { + let Some(mut gpu) = Gpu::new(test) else { + return; + }; + let failures = compare(&mut gpu, &scenarios); + assert!( + failures.is_empty(), + "{} samples disagree with Plasma:\n{}", + failures.len(), + failures.join("\n") + ); +} + +// ── Push buttons ──────────────────────────────────────────────────────── + +/// The push button's frame, as a list of samples: the outline on all four +/// sides lands on the pixel 1 px inside the node (the frame inset by 1 for +/// the shadow, stroked on the rectangle inset by half the pen), the face +/// fills what it encloses, nothing is painted outside it, and the corners +/// round at 4.5 on the outline's centre line. +fn button_frame() -> Vec { + let face = Ref::At(42, 17); + let top = Ref::At(42, 1); + let leading = Ref::At(1, 17); + let mut s = vec![ + solid("face", 42, 17), + solid("leading outline", 1, 17), + solid("trailing outline", 82, 17), + solid("top outline", 42, 1), + solid("bottom outline", 42, 32), + solid("outside, leading", 0, 17), + solid("outside, trailing", 83, 17), + solid("outside, above", 42, 0), + ]; + s.extend(shares( + "top-leading arc, outer edge", + &[(3, 1), (4, 1), (5, 1)], + Ref::At(0, 1), + top, + )); + s.extend(shares( + "top-trailing arc, outer edge", + &[(78, 1), (79, 1), (80, 1)], + Ref::At(83, 1), + top, + )); + s.extend(shares( + "leading arc, outer edge", + &[(1, 3), (1, 4), (1, 5)], + Ref::At(1, 0), + leading, + )); + s.extend(shares( + "arc on the diagonal", + &[(2, 2), (81, 2)], + Ref::At(0, 2), + top, + )); + s.extend(shares( + "top-leading arc, inner edge", + &[(4, 2), (5, 2)], + face, + top, + )); + s.extend(shares( + "top-trailing arc, inner edge", + &[(78, 2), (79, 2)], + face, + top, + )); + s.extend(shares( + "leading arc, inner edge", + &[(2, 4), (2, 5)], + face, + leading, + )); + s +} + +/// The 1 px shadow below the frame: black at 12.5 % over the window. +fn button_shadow() -> Vec { + shares( + "shadow line", + &[(42, 33), (20, 33), (70, 33)], + Ref::At(0, 33), + Ref::Black, + ) +} + +fn push_button( + id: &'static str, + mount: fn(&mut WidgetTree) -> WidgetId, + prepare: fn(&mut WidgetTree, WidgetId), + shadow: bool, +) -> Scenario { + let mut samples = button_frame(); + if shadow { + samples.extend(button_shadow()); + } else { + samples.push(solid("no shadow", 42, 33)); + } + Scenario { + id, + canvas: (84, 34), + origin: (0, 0), + mount, + prepare, + samples, + } +} + +fn plain_button(tree: &mut WidgetTree) -> WidgetId { + tree.add(Button::new(lit!(""))) +} + +fn default_button(tree: &mut WidgetTree) -> WidgetId { + tree.add(Button::new(lit!("")).variant(ButtonVariant::Filled)) +} + +/// The push button at rest, hovered, pressed and as the default button: the +/// frame's outline and radius in each colour, the shadow where Plasma casts +/// it and nothing below a pressed button. +#[test] +fn push_buttons_land_on_plasmas_pixels() { + assert_agree( + "push_buttons_land_on_plasmas_pixels", + vec![ + push_button("push_button.normal", plain_button, at_rest, true), + push_button("push_button.hover", plain_button, hovered, true), + push_button("push_button.pressed", plain_button, pressed, false), + push_button("push_button.default", default_button, at_rest, true), + ], + ); +} + +// ── Check boxes and radio buttons ─────────────────────────────────────── + +/// Where the 20 px indicator lies in the 24 px row the widgets lay it out in +/// (D8): 2 px down. +const INDICATOR_ORIGIN: (usize, usize) = (0, 2); +const INDICATOR_CANVAS: (u32, u32) = (20, 24); + +/// The box: its outline on the pixel 2 px inside the indicator on every +/// side (the 16 px box stroked at 2.5), and nothing outside it. A marked +/// box's outline is Plasma's colour, so its corners are compared too; an +/// unmarked box's outline is lifted (D6), so only which pixels it covers is. +fn box_frame(marked: bool) -> Vec { + let mut s = solids("outside the box", &[(0, 10), (19, 10), (8, 0), (10, 19)]); + let edge = [(2, 8), (17, 8), (8, 2), (8, 17)]; + if !marked { + s.extend(coverage("box outline", &edge, Check::Ink)); + s.extend(coverage( + "just outside the outline", + &[(1, 8), (18, 8), (8, 1), (8, 18)], + Check::Bare, + )); + return s; + } + s.extend(solids("box outline", &edge)); + s.extend(shares( + "box corner, outer edge", + &[(4, 2), (5, 2), (14, 2), (15, 2)], + Ref::At(0, 2), + Ref::At(8, 2), + )); + s.extend(shares( + "box corner, outer edge", + &[(2, 4), (2, 5), (2, 14), (2, 15)], + Ref::At(2, 0), + Ref::At(2, 8), + )); + s.extend(shares( + "box corner on the diagonal", + &[(3, 3), (16, 3)], + Ref::At(0, 3), + Ref::At(8, 2), + )); + s +} + +fn checked_box(tree: &mut WidgetTree) -> WidgetId { + tree.add(Checkbox::new(Signal::new(true))) +} + +fn unchecked_box(tree: &mut WidgetTree) -> WidgetId { + tree.add(Checkbox::new(Signal::new(false))) +} + +fn partial_box(tree: &mut WidgetTree) -> WidgetId { + tree.add(Checkbox::tristate(Signal::new(CheckState::Indeterminate))) +} + +/// A checked box: the frame, the highlight wash over the face, and the +/// check mark, a 2 px polyline with mitred joins through (6, 10), (9, 13) +/// and (14.5, 7.5) of the indicator: the pixels it covers wholly, and its +/// antialiased edges, the square caps past both ends among them. +fn checked_samples() -> Vec { + let wash = Ref::At(8, 5); + let mark = Ref::At(13, 8); + let mut s = box_frame(true); + s.extend(solids("wash", &[(8, 5), (3, 8), (16, 12), (14, 10)])); + // Not (5, 3) and (14, 3), where the arc's inner edge crosses the pixel: + // the outline is laid twice there, which doubles the gap between the two + // coverage models with it (0.16 at that pixel, see the module + // documentation), while the outer edge and the pixels on either side of + // the inner one stay within the tolerance. + s.extend(shares( + "box corner, inner edge", + &[(6, 3), (13, 3), (4, 3), (15, 3)], + wash, + Ref::At(8, 2), + )); + s.extend(solids( + "check mark", + &[(13, 8), (6, 10), (11, 10), (8, 12), (9, 12), (10, 11)], + )); + s.extend(shares( + "check mark edge", + &[ + (12, 8), + (14, 8), + (11, 8), + (15, 8), + (7, 10), + (8, 10), + (10, 10), + (12, 10), + (13, 10), + (7, 12), + (10, 12), + (6, 12), + (11, 12), + (8, 11), + (8, 13), + (8, 14), + (10, 9), + (10, 12), + (10, 13), + ], + wash, + mark, + )); + s.extend(shares( + "check mark, square cap at the start", + &[(4, 10), (5, 10)], + wash, + mark, + )); + s +} + +/// A partial box: three 2 x 2 squares of the view's text on the box's +/// centre line, 4 px apart. +fn partial_samples() -> Vec { + let mut s = box_frame(true); + s.extend(solids( + "partial square", + &[ + (5, 10), + (6, 10), + (9, 10), + (10, 10), + (13, 10), + (14, 10), + (10, 9), + ], + )); + s.extend(solids( + "between the squares", + &[ + (7, 10), + (8, 10), + (11, 10), + (12, 10), + (10, 8), + (10, 11), + (8, 5), + ], + )); + s +} + +/// An unchecked box: the face, and the frame's geometry. +fn unchecked_samples() -> Vec { + let mut s = box_frame(false); + s.extend(solids("face", &[(8, 8), (10, 10), (3, 8), (16, 12)])); + s +} + +fn selected_radio(tree: &mut WidgetTree) -> WidgetId { + tree.add(RadioButton::new(0, Signal::new(0))) +} + +fn unselected_radio(tree: &mut WidgetTree) -> WidgetId { + tree.add(RadioButton::new(1, Signal::new(0))) +} + +/// The disc: the 15 px circle at 2.5 inside the indicator, outlined on the +/// pixel 2 px inside it on every side, and, selected, its arc. Unselected, +/// the outline is lifted (D6), as the box's is. +fn disc_frame(marked: bool) -> Vec { + let mut s = solids("outside the disc", &[(0, 10), (19, 10), (10, 0), (10, 19)]); + let edge = [(2, 10), (17, 10), (10, 2), (10, 17)]; + if !marked { + s.extend(coverage("disc outline", &edge, Check::Ink)); + s.extend(coverage( + "just outside the outline", + &[(1, 10), (18, 10), (10, 1), (10, 18)], + Check::Bare, + )); + return s; + } + s.extend(solids("disc outline", &edge)); + s.extend(shares( + "disc arc, outer edge", + &[(4, 3), (5, 3), (14, 3), (15, 3)], + Ref::At(0, 3), + Ref::At(10, 2), + )); + s +} + +/// A selected radio button: the disc, the wash and the 6 px dot. +fn selected_radio_samples() -> Vec { + let wash = Ref::At(5, 10); + let dot = Ref::At(10, 10); + let mut s = disc_frame(true); + s.extend(solids("wash", &[(5, 10), (14, 10), (10, 5), (10, 14)])); + s.extend(shares( + "disc arc, inner edge", + &[ + (3, 10), + (16, 10), + (10, 3), + (10, 16), + (7, 3), + (8, 3), + (12, 3), + ], + wash, + Ref::At(10, 2), + )); + s.extend(solids("dot", &[(9, 10), (10, 10), (10, 8), (10, 11)])); + s.extend(shares( + "dot edge", + &[(7, 10), (12, 10), (10, 7), (10, 12)], + wash, + dot, + )); + s +} + +/// An unselected radio button: the face and the disc's geometry. +fn unselected_radio_samples() -> Vec { + let mut s = disc_frame(false); + s.extend(solids("face", &[(10, 10), (5, 10), (10, 14)])); + s +} + +fn indicator( + id: &'static str, + mount: fn(&mut WidgetTree) -> WidgetId, + samples: Vec, +) -> Scenario { + Scenario { + id, + canvas: INDICATOR_CANVAS, + origin: INDICATOR_ORIGIN, + mount, + prepare: at_rest, + samples, + } +} + +/// The check box checked, partial and unchecked, and the radio button +/// selected and not: the box and the disc where Plasma puts them, at its +/// radii, the wash, the check mark, the partial squares and the dot. +#[test] +fn check_boxes_and_radio_buttons_land_on_plasmas_pixels() { + assert_agree( + "check_boxes_and_radio_buttons_land_on_plasmas_pixels", + vec![ + indicator("check_box.on", checked_box, checked_samples()), + indicator("check_box.partial", partial_box, partial_samples()), + indicator("check_box.off", unchecked_box, unchecked_samples()), + indicator("radio_button.on", selected_radio, selected_radio_samples()), + indicator( + "radio_button.off", + unselected_radio, + unselected_radio_samples(), + ), + ], + ); +} + +// ── List rows ─────────────────────────────────────────────────────────── + +fn plain_row(tree: &mut WidgetTree) -> WidgetId { + tree.add(StandardListItem::new(lit!(""))) +} + +fn selected_row(tree: &mut WidgetTree) -> WidgetId { + tree.add(StandardListItem::new(lit!("")).selected(Signal::new(true))) +} + +/// The selection's rectangle: 2 px inside the row's leading and trailing +/// edges and 1 px inside its top and bottom, nothing outside it, and its +/// four rounded corners. +fn selection_frame() -> Vec { + let mut s = solids( + "outside the selection", + &[(1, 12), (198, 12), (100, 0), (100, 23), (0, 1), (199, 22)], + ); + s.extend(shares( + "top-leading corner", + &[(4, 1), (5, 1), (6, 1), (3, 2), (4, 2)], + Ref::At(0, 1), + Ref::At(100, 1), + )); + s.extend(shares( + "leading corner", + &[(2, 3), (2, 4), (2, 5), (3, 2)], + Ref::At(2, 0), + Ref::At(2, 12), + )); + s.extend(shares( + "top-trailing corner", + &[(193, 1), (194, 1), (195, 1), (195, 2), (196, 2)], + Ref::At(199, 1), + Ref::At(100, 1), + )); + s.extend(shares( + "bottom-leading corner", + &[(4, 22), (5, 22), (6, 22)], + Ref::At(0, 22), + Ref::At(100, 22), + )); + s +} + +/// A selected row: the highlight, filled and outlined in it. +fn selected_row_samples() -> Vec { + let mut s = selection_frame(); + s.extend(solids( + "selection, outline and fill", + &[(100, 12), (2, 12), (197, 12), (100, 1), (100, 22)], + )); + s +} + +/// A hovered row: the wash, and the outline in the highlight at 80 % over +/// it. +fn hovered_row_samples() -> Vec { + let mut s = selection_frame(); + s.push(solid("wash", 100, 12)); + s.extend(shares( + "outline at 80 %", + &[(2, 12), (197, 12), (100, 1), (100, 22)], + Ref::At(100, 0), + Ref::Role(PaletteRole::Highlight), + )); + s +} + +/// An unhovered, unselected row: nothing. +fn plain_row_samples() -> Vec { + solids( + "nothing painted", + &[(100, 12), (2, 12), (100, 1), (3, 2), (2, 3)], + ) +} + +fn row( + id: &'static str, + mount: fn(&mut WidgetTree) -> WidgetId, + prepare: fn(&mut WidgetTree, WidgetId), + samples: Vec, +) -> Scenario { + Scenario { + id, + canvas: (200, 24), + origin: (0, 0), + mount, + prepare, + samples, + } +} + +/// A list row selected, hovered and at rest: the selection's rectangle, its +/// corners and its outline. +#[test] +fn list_rows_land_on_plasmas_pixels() { + assert_agree( + "list_rows_land_on_plasmas_pixels", + vec![ + row( + "item_view_row.selected", + selected_row, + at_rest, + selected_row_samples(), + ), + row( + "item_view_row.hover", + plain_row, + hovered, + hovered_row_samples(), + ), + row( + "item_view_row.normal", + plain_row, + at_rest, + plain_row_samples(), + ), + ], + ); +} + +// ── The scroll bar ────────────────────────────────────────────────────── + +fn scroll_bar(tree: &mut WidgetTree) -> WidgetId { + // The handle Plasma paints: a page of 20 in a range of 0 to 100, at the + // top, 32 px long on its 194 px groove; 0.16 of the 200 px bar here. + tree.add( + ScrollBar::new( + ScrollBarOrientation::Vertical, + Signal::new(0.0), + Signal::new(100.0), + Signal::new(0.16), + ) + .thickness(21.0) + .variant(ScrollBarVariant::Permanent), + ) +} + +/// Breeze starts the handle 3 px down the bar (see the module +/// documentation). +const HANDLE_SHIFT: (isize, isize) = (0, -3); + +fn handle(what: &'static str, x: usize, y: usize, check: Check) -> Sample { + Sample { + what, + at: (x, y), + check, + shift: HANDLE_SHIFT, + } +} + +/// The scroll bar: the 1 px separator on its leading edge in the view's +/// text at the frame contrast, the whole length of the bar; the handle 8 px +/// wide, 6 px from the separator, covering the pixels Plasma's covers +/// across the bar and along it, and nothing on the track. +fn scroll_bar_samples() -> Vec { + let mut s = shares( + "separator", + &[(0, 18), (0, 100), (0, 150), (0, 199)], + Ref::At(1, 150), + Ref::Role(PaletteRole::Text), + ); + s.extend(solids( + "no track", + &[(1, 150), (10, 150), (20, 150), (10, 60)], + )); + for x in [1, 6, 15, 20] { + s.push(handle("beside the handle", x, 18, Check::Bare)); + } + for x in 7..=14 { + s.push(handle("across the handle", x, 18, Check::Ink)); + } + for y in [3, 4, 5, 18, 32, 33, 34] { + s.push(handle("along the handle", 10, y, Check::Ink)); + } + for y in [35, 36, 40] { + s.push(handle("past the handle's end", 10, y, Check::Bare)); + } + s +} + +/// The scroll bar at rest. +#[test] +fn the_scroll_bar_lands_on_plasmas_pixels() { + assert_agree( + "the_scroll_bar_lands_on_plasmas_pixels", + vec![Scenario { + id: "scroll_bar.normal", + canvas: (21, 200), + origin: (0, 0), + mount: scroll_bar, + prepare: at_rest, + samples: scroll_bar_samples(), + }], + ); +} + +// ── The comparison itself ─────────────────────────────────────────────── + +/// The share is the coverage in each space: a colour 30 % of the way from +/// one colour to another in sRGB is 0.3 there, and the same 30 % composited +/// in linear light is 0.3 in linear light. +#[test] +fn a_share_is_measured_in_each_images_compositing_space() { + let window = [239, 240, 241, 255]; + let black = [0, 0, 0, 255]; + // Black at 12.5 % over the window, composited in sRGB, as Qt does. + let qt = [209, 210, 211, 255]; + let (in_srgb, step) = share_of(qt, window, black, false).unwrap(); + assert!((in_srgb - 0.125).abs() < 0.01, "{in_srgb}"); + assert!(step <= MAX_SHARE_STEP); + // The same composited in linear light, as the renderer does: a lighter + // colour, the same share. + let linear = |v: u8| teksilo_render::vertex::srgb_to_linear_rgba([f32::from(v) / 255.0; 4])[0]; + let encode = |l: f32| { + let e = if l <= 0.003_130_8 { + l * 12.92 + } else { + 1.055 * l.powf(1.0 / 2.4) - 0.055 + }; + (e * 255.0).round() as u8 + }; + let gpu = window.map(|v| encode(linear(v) * 0.875)); + let gpu = [gpu[0], gpu[1], gpu[2], 255]; + assert!(gpu[0] > qt[0] + 10, "{gpu:?}: lighter"); + let (in_linear, _) = share_of(gpu, window, black, true).unwrap(); + assert!((in_linear - 0.125).abs() < 0.01, "{in_linear}"); + // Read in the wrong space it would be far off: why each is read in its + // own. + assert!(share_of(gpu, window, black, false).unwrap().0 < 0.08); + // Near black one level moves the linear share too far to measure it. + let dark = [16, 17, 18, 255]; + assert!(share_of([14, 15, 16, 255], dark, black, true).unwrap().1 > MAX_SHARE_STEP); + assert_eq!(share_of(qt, window, window, false), None); +} + +/// The signed distance from `(px, py)` to the rounded rectangle `rect` of +/// radius `r`: negative inside. +fn rounded_rect_distance(px: f32, py: f32, rect: [f32; 4], r: f32) -> f32 { + let [x, y, w, h] = rect; + let qx = (px - (x + w / 2.0)).abs() - w / 2.0 + r; + let qy = (py - (y + h / 2.0)).abs() - h / 2.0 + r; + qx.max(qy).min(0.0) + qx.max(0.0).hypot(qy.max(0.0)) - r +} + +/// The share of the pixel `(x, y)` a 1 px outline stroked on `rect` at +/// radius `r` covers, by area, on a 64 x 64 grid of subsamples. +fn outline_area(x: usize, y: usize, rect: [f32; 4], r: f32) -> f32 { + const N: usize = 64; + let mut inside = 0; + for i in 0..N { + for j in 0..N { + let px = x as f32 + (i as f32 + 0.5) / N as f32; + let py = y as f32 + (j as f32 + 0.5) / N as f32; + if rounded_rect_distance(px, py, rect, r).abs() <= 0.5 { + inside += 1; + } + } + } + inside as f32 / (N * N) as f32 +} + +/// [`RENDERER_COVERAGE_GAP`] is what it says: the smoothstep's largest +/// departure from a linear ramp, which is an edge's area coverage, across +/// the one pixel the shape pipeline ramps over. +#[test] +fn the_renderer_coverage_gap_is_the_smoothsteps() { + let gap = (0..=10_000) + .map(|i| { + let t = i as f32 / 10_000.0; + (t * t * (3.0 - 2.0 * t) - t).abs() + }) + .fold(0.0, f32::max); + assert!( + gap <= RENDERER_COVERAGE_GAP && gap > RENDERER_COVERAGE_GAP - 0.002, + "{gap}" + ); + // The arcs sampled here depart no further: the push button's outline at + // 4.5, the row's at 5 and the box's at 4, through the renderer's own + // ramp. + for (rect, r) in [ + ([1.5, 1.5, 81.0, 31.0], 4.5), + ([2.5, 1.5, 195.0, 21.0], 5.0), + ([2.5, 2.5, 15.0, 15.0], 4.0), + ] { + for x in 0..9 { + for y in 0..9 { + let d = rounded_rect_distance(x as f32 + 0.5, y as f32 + 0.5, rect, r).abs(); + let t = d.clamp(0.0, 1.0); + let ramp = 1.0 - t * t * (3.0 - 2.0 * t); + let area = outline_area(x, y, rect, r); + assert!( + (ramp - area).abs() <= RENDERER_COVERAGE_GAP, + "{rect:?} at ({x}, {y}): {ramp} against {area}" + ); + } + } + } +} + +/// [`QT_CORNER_GAP`] is measured, not chosen: the largest departure of +/// Plasma's push-button corner pixels from the area of a 1 px outline at +/// radius 4.5 on the rectangle the specification gives, over every button +/// sampled, in both schemes. +#[test] +fn qts_corners_depart_from_the_specified_shape_by_the_qt_gap() { + let rect = [1.5, 1.5, 81.0, 31.0]; + let mut largest: f32 = 0.0; + for scheme in ["BreezeLight", "BreezeDark"] { + for id in [ + "push_button.normal", + "push_button.hover", + "push_button.pressed", + "push_button.default", + ] { + let g = Golden::load(scheme, id); + for ((x, y), under, over) in [ + ((3, 1), (0, 1), (42, 1)), + ((4, 1), (0, 1), (42, 1)), + ((5, 1), (0, 1), (42, 1)), + ((1, 3), (1, 0), (1, 17)), + ((1, 4), (1, 0), (1, 17)), + ((1, 5), (1, 0), (1, 17)), + ((2, 2), (0, 2), (42, 1)), + ] { + let (qt, _) = share_of( + g.at(x, y), + g.at(under.0, under.1), + g.at(over.0, over.1), + false, + ) + .expect("distinct colours"); + largest = largest.max((qt - outline_area(x, y, rect, 4.5)).abs()); + } + } + } + assert!( + largest <= QT_CORNER_GAP && largest > QT_CORNER_GAP - 0.01, + "{largest}" + ); +} diff --git a/crates/teksilo-theme-kde/tests/contrast.proptest-regressions b/crates/teksilo-theme-kde/tests/contrast.proptest-regressions new file mode 100644 index 000000000..191e8654f --- /dev/null +++ b/crates/teksilo-theme-kde/tests/contrast.proptest-regressions @@ -0,0 +1,10 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc cf984e8352cee5cb79a954abf90d3192a82a04ae1f5781c9928a68380280cdc5 # shrinks to scheme = KdeColorScheme { name: Some("Breeze Light"), scheme_id: Some("BreezeLight"), sets: [SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 64507, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, background_alternate: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 13878, b: 54741, a: 65535 }, foreground_active: KdeColor { r: 19532, g: 30069, b: 42148, a: 65535 }, foreground_link: KdeColor { r: 31868, g: 13364, b: 3598, a: 65535 }, foreground_visited: KdeColor { r: 45746, g: 58853, b: 39321, a: 65535 }, foreground_negative: KdeColor { r: 46003, g: 26985, b: 5654, a: 65535 }, foreground_neutral: KdeColor { r: 46517, g: 29298, b: 18761, a: 65535 }, foreground_positive: KdeColor { r: 39321, g: 20303, b: 25957, a: 65535 }, decoration_focus: KdeColor { r: 48059, g: 60909, b: 16962, a: 65535 }, decoration_hover: KdeColor { r: 17733, g: 36237, b: 32382, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 10794, g: 11822, b: 12850, a: 65535 }, background_alternate: KdeColor { r: 6939, g: 7710, b: 8224, a: 65535 }, foreground_normal: KdeColor { r: 64764, g: 64764, b: 64764, a: 65535 }, foreground_inactive: KdeColor { r: 41377, g: 43433, b: 45489, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 7453, g: 39321, b: 62451, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } }, SetColors { background_normal: KdeColor { r: 57054, g: 57568, b: 58082, a: 65535 }, background_alternate: KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }, foreground_normal: KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }, foreground_inactive: KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 10537, g: 32896, b: 47545, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } }], inactive_sets: [None, None, None, None, None, None, Some(SetColors { background_normal: KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }, background_alternate: KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }, foreground_normal: KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }, foreground_inactive: KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 10537, g: 32896, b: 47545, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } })], header_group_present: true, disabled_effect: StateEffect { enabled: true, intensity_effect: Darken, intensity_amount: 0.1, color_effect: None, color_amount: 0.0, color: KdeColor { r: 14392, g: 14392, b: 14392, a: 65535 }, contrast_effect: Fade, contrast_amount: 0.65 }, inactive_effect: StateEffect { enabled: true, intensity_effect: Darken, intensity_amount: 0.791283626371766, color_effect: Desaturate, color_amount: 0.2632572107572368, color: KdeColor { r: 15420, g: 63222, b: 34695, a: 65535 }, contrast_effect: Tint, contrast_amount: 0.4859836335278482 }, change_selection_color: true, wm: WmColors { active_background: Some(KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }), active_foreground: Some(KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }), inactive_background: Some(KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }), inactive_foreground: Some(KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }), active_blend: Some(KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }), inactive_blend: Some(KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }) }, contrast: 0, frame_contrast: 0.9209235857238294, accent: None, tint_factor: None, titlebar_is_accent_colored: false, source: SchemeSource(none) } +cc b7bd559993e8e2665df44ff98c01043d30167394c42b2ab20981dd78e7004b7e # shrinks to scheme = KdeColorScheme { name: Some("Breeze Light"), scheme_id: Some("BreezeLight"), sets: [SetColors { background_normal: KdeColor { r: 59110, g: 56026, b: 65278, a: 65535 }, background_alternate: KdeColor { r: 55512, g: 54484, b: 57311, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 257, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, background_alternate: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 10794, g: 11822, b: 12850, a: 65535 }, background_alternate: KdeColor { r: 6939, g: 7710, b: 8224, a: 65535 }, foreground_normal: KdeColor { r: 64764, g: 64764, b: 64764, a: 65535 }, foreground_inactive: KdeColor { r: 41377, g: 43433, b: 45489, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 7453, g: 39321, b: 62451, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } }, SetColors { background_normal: KdeColor { r: 57054, g: 57568, b: 58082, a: 65535 }, background_alternate: KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }, foreground_normal: KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }, foreground_inactive: KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 10537, g: 32896, b: 47545, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } }], inactive_sets: [None, None, None, None, None, None, Some(SetColors { background_normal: KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }, background_alternate: KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }, foreground_normal: KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }, foreground_inactive: KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 10537, g: 32896, b: 47545, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } })], header_group_present: true, disabled_effect: StateEffect { enabled: true, intensity_effect: Darken, intensity_amount: 0.1, color_effect: None, color_amount: 0.0, color: KdeColor { r: 14392, g: 14392, b: 14392, a: 65535 }, contrast_effect: Fade, contrast_amount: 0.65 }, inactive_effect: StateEffect { enabled: true, intensity_effect: Darken, intensity_amount: 0.7562921906482618, color_effect: None, color_amount: 0.0, color: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, contrast_effect: None, contrast_amount: 0.0 }, change_selection_color: true, wm: WmColors { active_background: Some(KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }), active_foreground: Some(KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }), inactive_background: Some(KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }), inactive_foreground: Some(KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }), active_blend: Some(KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }), inactive_blend: Some(KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }) }, contrast: 0, frame_contrast: 0.0, accent: None, tint_factor: None, titlebar_is_accent_colored: false, source: SchemeSource(none) } +cc 2485aeef4be28b2d2b668923f9faa97c8817447495cac78ab5fed1b6e4b80083 # shrinks to scheme = KdeColorScheme { name: Some("Breeze Light"), scheme_id: Some("BreezeLight"), sets: [SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, background_alternate: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 1285, b: 42405, a: 65535 }, foreground_active: KdeColor { r: 18761, g: 28013, b: 10537, a: 65535 }, foreground_link: KdeColor { r: 55769, g: 57825, b: 31868, a: 65535 }, foreground_visited: KdeColor { r: 58339, g: 9252, b: 24672, a: 65535 }, foreground_negative: KdeColor { r: 5911, g: 64507, b: 14392, a: 65535 }, foreground_neutral: KdeColor { r: 3855, g: 21331, b: 24415, a: 65535 }, foreground_positive: KdeColor { r: 55255, g: 23130, b: 54227, a: 65535 }, decoration_focus: KdeColor { r: 18247, g: 11822, b: 18761, a: 65535 }, decoration_hover: KdeColor { r: 16448, g: 55255, b: 20817, a: 65535 } }, SetColors { background_normal: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, background_alternate: KdeColor { r: 53970, g: 53970, b: 53970, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 10794, g: 11822, b: 12850, a: 65535 }, background_alternate: KdeColor { r: 6939, g: 7710, b: 8224, a: 65535 }, foreground_normal: KdeColor { r: 64764, g: 64764, b: 64764, a: 65535 }, foreground_inactive: KdeColor { r: 41377, g: 43433, b: 45489, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 7453, g: 39321, b: 62451, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } }, SetColors { background_normal: KdeColor { r: 57054, g: 57568, b: 58082, a: 65535 }, background_alternate: KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }, foreground_normal: KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }, foreground_inactive: KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 10537, g: 32896, b: 47545, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } }], inactive_sets: [None, None, None, None, None, None, Some(SetColors { background_normal: KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }, background_alternate: KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }, foreground_normal: KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }, foreground_inactive: KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 10537, g: 32896, b: 47545, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } })], header_group_present: true, disabled_effect: StateEffect { enabled: true, intensity_effect: Darken, intensity_amount: 0.1, color_effect: None, color_amount: 0.0, color: KdeColor { r: 14392, g: 14392, b: 14392, a: 65535 }, contrast_effect: Fade, contrast_amount: 0.65 }, inactive_effect: StateEffect { enabled: true, intensity_effect: Shade, intensity_amount: 0.5980862313975345, color_effect: None, color_amount: 0.22593280421506706, color: KdeColor { r: 31868, g: 2056, b: 59110, a: 65535 }, contrast_effect: Fade, contrast_amount: 0.43370767995677395 }, change_selection_color: true, wm: WmColors { active_background: Some(KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }), active_foreground: Some(KdeColor { r: 8995, g: 9766, b: 10537, a: 65535 }), inactive_background: Some(KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }), inactive_foreground: Some(KdeColor { r: 28784, g: 32125, b: 35466, a: 65535 }), active_blend: Some(KdeColor { r: 58339, g: 58853, b: 59367, a: 65535 }), inactive_blend: Some(KdeColor { r: 61423, g: 61680, b: 61937, a: 65535 }) }, contrast: 6, frame_contrast: 0.025181047508168207, accent: None, tint_factor: None, titlebar_is_accent_colored: false, source: SchemeSource(none) } +cc 5e44f22293320147a89daca46879f066d5068a96b4bef7eb41fa41ed4b55dee4 # shrinks to scheme = KdeColorScheme { name: Some("Breeze Dark"), scheme_id: Some("BreezeDark"), sets: [SetColors { background_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, background_alternate: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_normal: KdeColor { r: 51400, g: 51400, b: 51400, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, background_alternate: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_normal: KdeColor { r: 51400, g: 51400, b: 51400, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 6682, g: 514, b: 2570, a: 65535 }, background_alternate: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_normal: KdeColor { r: 51400, g: 51400, b: 51400, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 0, g: 15934, b: 0, a: 65535 }, background_alternate: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 1028, g: 36237, b: 23644, a: 65535 }, foreground_link: KdeColor { r: 48830, g: 46774, b: 14135, a: 65535 }, foreground_visited: KdeColor { r: 52171, g: 8738, b: 33410, a: 65535 }, foreground_negative: KdeColor { r: 52428, g: 48316, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 40349, g: 39064, b: 35980, a: 65535 }, foreground_positive: KdeColor { r: 49601, g: 24929, b: 10537, a: 65535 }, decoration_focus: KdeColor { r: 13364, g: 55512, b: 2827, a: 65535 }, decoration_hover: KdeColor { r: 58853, g: 41634, b: 61680, a: 65535 } }, SetColors { background_normal: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, background_alternate: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_normal: KdeColor { r: 51400, g: 51400, b: 51400, a: 65535 }, foreground_inactive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_active: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_link: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_visited: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_negative: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_neutral: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_focus: KdeColor { r: 0, g: 0, b: 0, a: 65535 }, decoration_hover: KdeColor { r: 0, g: 0, b: 0, a: 65535 } }, SetColors { background_normal: KdeColor { r: 8224, g: 8995, b: 9766, a: 65535 }, background_alternate: KdeColor { r: 7710, g: 22359, b: 29812, a: 65535 }, foreground_normal: KdeColor { r: 64764, g: 64764, b: 64764, a: 65535 }, foreground_inactive: KdeColor { r: 41377, g: 43433, b: 45489, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 7453, g: 39321, b: 62451, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } }, SetColors { background_normal: KdeColor { r: 10537, g: 11308, b: 12336, a: 65535 }, background_alternate: KdeColor { r: 8224, g: 8995, b: 9766, a: 65535 }, foreground_normal: KdeColor { r: 64764, g: 64764, b: 64764, a: 65535 }, foreground_inactive: KdeColor { r: 41377, g: 43433, b: 45489, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 7453, g: 39321, b: 62451, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } }], inactive_sets: [None, None, None, None, None, None, Some(SetColors { background_normal: KdeColor { r: 8224, g: 8995, b: 9766, a: 65535 }, background_alternate: KdeColor { r: 10537, g: 11308, b: 12336, a: 65535 }, foreground_normal: KdeColor { r: 64764, g: 64764, b: 64764, a: 65535 }, foreground_inactive: KdeColor { r: 41377, g: 43433, b: 45489, a: 65535 }, foreground_active: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, foreground_link: KdeColor { r: 7453, g: 39321, b: 62451, a: 65535 }, foreground_visited: KdeColor { r: 39835, g: 22873, b: 46774, a: 65535 }, foreground_negative: KdeColor { r: 56026, g: 17476, b: 21331, a: 65535 }, foreground_neutral: KdeColor { r: 63222, g: 29812, b: 0, a: 65535 }, foreground_positive: KdeColor { r: 10023, g: 44718, b: 24672, a: 65535 }, decoration_focus: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 }, decoration_hover: KdeColor { r: 15677, g: 44718, b: 59881, a: 65535 } })], header_group_present: true, disabled_effect: StateEffect { enabled: true, intensity_effect: Darken, intensity_amount: 0.1, color_effect: None, color_amount: 0.0, color: KdeColor { r: 14392, g: 14392, b: 14392, a: 65535 }, contrast_effect: Fade, contrast_amount: 0.65 }, inactive_effect: StateEffect { enabled: true, intensity_effect: Lighten, intensity_amount: 0.6973713499094516, color_effect: Tint, color_amount: 0.5866690477466117, color: KdeColor { r: 40863, g: 12079, b: 47802, a: 65535 }, contrast_effect: Tint, contrast_amount: 0.2725198265629683 }, change_selection_color: true, wm: WmColors { active_background: Some(KdeColor { r: 10023, g: 11308, b: 12593, a: 65535 }), active_foreground: Some(KdeColor { r: 64764, g: 64764, b: 64764, a: 65535 }), inactive_background: Some(KdeColor { r: 8224, g: 9252, b: 10280, a: 65535 }), inactive_foreground: Some(KdeColor { r: 41377, g: 43433, b: 45489, a: 65535 }), active_blend: Some(KdeColor { r: 64764, g: 64764, b: 64764, a: 65535 }), inactive_blend: Some(KdeColor { r: 41377, g: 43433, b: 45489, a: 65535 }) }, contrast: 1, frame_contrast: 0.7589596747157055, accent: None, tint_factor: None, titlebar_is_accent_colored: false, source: SchemeSource(none) } diff --git a/crates/teksilo-theme-kde/tests/contrast.rs b/crates/teksilo-theme-kde/tests/contrast.rs new file mode 100644 index 000000000..6c7d5097f --- /dev/null +++ b/crates/teksilo-theme-kde/tests/contrast.rs @@ -0,0 +1,1641 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The WCAG lifts of the Breeze colour mapping, and a sweep of every text +//! and backdrop pair the tokens declare. +//! +//! Each lift (D1 to D6 and D9 of the colour mapping's documentation) has a +//! premise test, that the scheme's own colour fails in stock Breeze, and a +//! test that the token passes; D10 and D11, which stock Breeze never needs, +//! have their premises in the schemes built to defeat the lifts. The sweep +//! then checks every declared pair in all four paint states (an active and +//! an inactive window, each with and without high contrast), over Breeze +//! Light, Dark and Classic, every scheme the golden vectors record (Oxygen +//! and Oxygen Dark turn inactive effects on, and two synthetic schemes +//! carry intensity effects and translucent colours), the ten Plasma accents +//! on Light and on Dark, and schemes built to defeat the lifts, each shown +//! to need them. A second sweep checks the faces the styles paint their own +//! text and marks on (a push button's in every state, a combo box's, a +//! table header section's, a check box's, and an unselected tab's at rest +//! and hovered, read from a mounted tab bar's frame with its title) over +//! the same schemes and paint states. The property at the end does both over random coherent schemes, +//! with random inactive effects. +//! +//! This file reads `tools/kde_golden/vectors/resolve.json` from the +//! repository, so it is left out of the published package (the `exclude` +//! list in `Cargo.toml`). +//! +//! The property builds four complete token sets per case, each lifting some +//! thirty colours by up to 256 luma steps against up to twelve backdrops, +//! and mounts a tab bar in each of the four paint states: about 10 ms a +//! case in a debug build, so it keeps proptest's default 256 cases. Manual override knob: `PROPTEST_CASES=N cargo test -p +//! teksilo-theme-kde --test contrast`. +//! +//! Safe run (engineering/docs/property-testing.md): +//! +//! ```text +//! cargo test -p teksilo-theme-kde --test contrast --no-run +//! BIN=$(cargo test -p teksilo-theme-kde --test contrast --no-run --message-format=json 2>/dev/null \ +//! | jq -r 'select(.executable != null) | .executable' | tail -1) +//! ( ulimit -v 6000000 -t 300; "$BIN" --test-threads=1 ) +//! ``` + +use std::cell::RefCell; +use std::rc::Rc; + +use proptest::prelude::*; +use serde_json::Value; +use teksilo_canvas::{GlyphQuad, MockTextBackend, SizeProposal}; +use teksilo_core::signal::Signal; +use teksilo_core::styles::{PaintState, Theme}; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_data::ListModel; +use teksilo_i18n::lit; +use teksilo_theme_kde::scheme::math::mix; +use teksilo_theme_kde::scheme::{ + ColorEffect, ColorSet, ColorState, ContrastEffect, DecorationRole, ForegroundRole, + IntensityEffect, KdeColor, KdeColorScheme, PaletteRole, SetColors, StateEffect, +}; +use teksilo_theme_kde::styles::checkbox::box_paint; +use teksilo_theme_kde::styles::chrome::{ + BreezeButtonColors, BreezeButtonKind, BreezeButtonState, button_paint, +}; +use teksilo_theme_kde::styles::combo_box::combo_arrow_color; +use teksilo_theme_kde::styles::indicator::BreezeIndicatorColors; +use teksilo_theme_kde::styles::tab::BreezeTabColors; +use teksilo_theme_kde::styles::table::BreezeHeaderColors; +use teksilo_theme_kde::{self as kde, KdePalette, PlasmaAccent}; +use teksilo_tokens::contrast::composite_contrast; +use teksilo_tokens::{Color, ColorTokens}; +use teksilo_widgets::tab_widget::{TabBar, TabDelegate, TabId, TabSizing}; + +/// `[wcag]` SC 1.4.3 and SC 1.4.11. +const TEXT: f32 = 4.5; +const NON_TEXT: f32 = 3.0; + +fn ratio(fg: Color, bg: Color) -> f32 { + composite_contrast(fg, bg) +} + +/// `top` composited over `bottom`, whose alpha is ignored, made opaque. +fn painted_over(top: Color, bottom: Color) -> Color { + let solid = |c: Color| KdeColor::from_color(c.with_alpha(1.0)); + mix(solid(bottom), solid(top), f64::from(top.a())).to_color() +} + +fn palette(theme: &Theme) -> &KdePalette { + theme + .extension::() + .expect("a KDE theme carries its palette") +} + +fn role(theme: &Theme, r: PaletteRole) -> Color { + palette(theme).role(ColorState::Active, r) +} + +fn view_foreground(theme: &Theme, r: ForegroundRole) -> Color { + palette(theme) + .resolved() + .set(ColorState::Active, ColorSet::View) + .foreground(r) + .to_color() +} + +fn paint(theme: &Theme, window_active: bool, high_contrast: bool) -> ColorTokens { + theme + .for_paint_state(PaintState { + window_active, + high_contrast, + }) + .colors +} + +/// Every surface the tokens set text on, the hover and press washes +/// included. +fn text_surfaces(c: &ColorTokens) -> Vec<(&'static str, Color)> { + vec![ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ("surface_raised", c.surface_raised), + ("surface_container", c.surface_container), + ("surface_container_raised", c.surface_container_raised), + ("surface_container_sunken", c.surface_container_sunken), + ("surface_sunken", c.surface_sunken), + ("surface_alt_row", c.surface_alt_row), + ("surface_hover", c.surface_hover), + ("surface_pressed", c.surface_pressed), + ("accent_subtle_bg", c.accent_subtle_bg), + ] +} + +/// Every declared pair of `c` that falls short of its floor, by name. +fn failures(c: &ColorTokens, text: f32, non_text: f32) -> Vec { + let mut pairs: Vec<(String, Color, Color, f32)> = Vec::new(); + let mut add = |fg_name: &str, fg: Color, bg_name: &str, bg: Color, floor: f32| { + pairs.push((format!("{fg_name} on {bg_name}"), fg, bg, floor)); + }; + let surfaces = text_surfaces(c); + for (name, fg) in [ + ("text_primary", c.text_primary), + ("text_secondary", c.text_secondary), + ("text_link", c.text_link), + ("text_link_hover", c.text_link_hover), + ("text_link_visited", c.text_link_visited), + ("text_error", c.text_error), + ("text_warning", c.text_warning), + ("text_success", c.text_success), + ("status_info_fg", c.status_info_fg), + ] { + for (bg_name, bg) in &surfaces { + add(name, fg, bg_name, *bg, text); + } + } + for (bg_name, bg) in [ + ("accent", c.accent), + ("accent_hover", c.accent_hover), + ("accent_pressed", c.accent_pressed), + ] { + add("text_on_accent", c.text_on_accent, bg_name, bg, text); + } + for (bg_name, bg) in [ + ("surface_selected", c.surface_selected), + ("surface_selected_inactive", c.surface_selected_inactive), + ("selection_bg_active", c.selection_bg_active), + ("selection_bg_inactive", c.selection_bg_inactive), + ] { + add("text_primary", c.text_primary, bg_name, bg, text); + } + add( + "selection_text_active", + c.selection_text_active, + "selection_bg_active", + c.selection_bg_active, + text, + ); + add( + "selection_text_inactive", + c.selection_text_inactive, + "selection_bg_inactive", + c.selection_bg_inactive, + text, + ); + for (bg_name, bg) in [ + ("editor_bg", c.editor_bg), + ("editor_current_line_bg", c.editor_current_line_bg), + ("editor_selection_bg", c.editor_selection_bg), + ("editor_code_block_bg", c.editor_code_block_bg), + ] { + add("editor_fg", c.editor_fg, bg_name, bg, text); + add("editor_caret", c.editor_caret, bg_name, bg, text); + } + for (bg_name, bg) in [ + ("editor_bg", c.editor_bg), + ("editor_current_line_bg", c.editor_current_line_bg), + ] { + add("editor_gutter_fg", c.editor_gutter_fg, bg_name, bg, text); + } + add( + "editor_code_block_fg", + c.editor_code_block_fg, + "editor_code_block_bg", + c.editor_code_block_bg, + text, + ); + add( + "tooltip_text", + c.tooltip_text, + "tooltip_bg", + c.tooltip_bg, + text, + ); + add( + "tooltip_shortcut", + c.tooltip_shortcut, + "tooltip_bg", + c.tooltip_bg, + text, + ); + for (name, fg, bg) in [ + ("status_info", c.status_info_fg, c.status_info_bg), + ("status_success", c.status_success_fg, c.status_success_bg), + ("status_warning", c.status_warning_fg, c.status_warning_bg), + ("status_error", c.status_error_fg, c.status_error_bg), + ] { + add(name, fg, "its background", bg, text); + } + add( + "text_on_error", + c.text_on_error, + "text_error", + c.text_error, + text, + ); + add( + "text_on_error_container", + c.text_on_error_container, + "surface_error_container", + c.surface_error_container, + text, + ); + for (name, fg) in [ + ("focus_ring", c.focus_ring), + ("border_focused", c.border_focused), + ("border_strong", c.border_strong), + ("divider_strong", c.divider_strong), + ("border_error", c.border_error), + ("border_warning", c.border_warning), + ("focus_ring_error", c.focus_ring_error), + ] { + for (bg_name, bg) in [ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ("surface_raised", c.surface_raised), + ] { + add(name, fg, bg_name, bg, non_text); + } + } + for (name, fg) in [ + ("scrollbar_thumb", c.scrollbar_thumb), + ("scrollbar_thumb_hover", c.scrollbar_thumb_hover), + ("scrollbar_thumb_pressed", c.scrollbar_thumb_pressed), + ] { + for (bg_name, bg) in [ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ] { + add(name, fg, bg_name, bg, non_text); + } + } + pairs + .into_iter() + .filter(|(_, fg, bg, floor)| ratio(*fg, *bg) < *floor) + .map(|(name, fg, bg, floor)| { + format!( + "{name}: {:.2}:1 < {floor} ({fg:?} on {bg:?})", + ratio(fg, bg) + ) + }) + .collect() +} + +// ── D1, text on the accent ────────────────────────────────────────────── + +#[test] +fn d1_premise_breezes_selection_text_fails_on_its_highlight() { + for t in [kde::light(), kde::dark()] { + let text = role(&t, PaletteRole::HighlightedText); + let fill = role(&t, PaletteRole::Highlight); + assert!(ratio(text, fill) < TEXT, "{:.2}:1", ratio(text, fill)); + } +} + +#[test] +fn d1_the_accent_text_is_plasmas_black_and_passes() { + for t in [kde::light(), kde::dark()] { + let c = &t.colors; + assert_eq!(c.text_on_accent, Color::BLACK); + for fill in [c.accent, c.accent_hover, c.accent_pressed] { + assert!(ratio(c.text_on_accent, fill) >= TEXT); + } + } + // The selected text: black on Breeze Light's highlight, and the + // scheme's own text on Breeze Dark's darkened selection, where it + // passes. + assert_eq!(kde::light().colors.selection_text_active, Color::BLACK); + let dark = kde::dark(); + assert_eq!( + dark.colors.selection_text_active, + role(&dark, PaletteRole::HighlightedText) + ); + assert!( + ratio( + dark.colors.selection_text_active, + dark.colors.selection_bg_active + ) >= TEXT + ); +} + +#[test] +fn d1_premise_breeze_darks_primary_text_fails_on_its_highlight() { + let t = kde::dark(); + let fill = role(&t, PaletteRole::Highlight); + assert!(ratio(t.colors.text_primary, fill) < TEXT); +} + +#[test] +fn d1_the_selection_fill_carries_the_primary_text() { + for t in [kde::light(), kde::dark()] { + let c = &t.colors; + assert!(ratio(c.text_primary, c.surface_selected) >= TEXT); + assert!(ratio(c.editor_fg, c.editor_selection_bg) >= TEXT); + // The fill keeps the highlight's hue. + let (h1, _, _) = c.surface_selected.to_hsv(); + let (h2, _, _) = role(&t, PaletteRole::Highlight).to_hsv(); + assert!((h1 - h2).abs() < 6.0, "hue {h1} strays from {h2}"); + } +} + +/// The fills D1 moves under the primary text: the hover and press washes +/// and the selections. +fn washes_and_selections(c: &ColorTokens) -> [(&'static str, Color); 7] { + [ + ("surface_hover", c.surface_hover), + ("surface_pressed", c.surface_pressed), + ("accent_subtle_bg", c.accent_subtle_bg), + ("surface_selected", c.surface_selected), + ("surface_selected_inactive", c.surface_selected_inactive), + ("selection_bg_active", c.selection_bg_active), + ("editor_selection_bg", c.editor_selection_bg), + ] +} + +/// Breeze Light with its window and view text set to the grey `v`. +fn breeze_light_with_grey_text(v: u8) -> KdeColorScheme { + with( + KdeColorScheme::breeze_light(), + &[ColorSet::View, ColorSet::Window], + |set| set.foreground_normal = KdeColor::rgb8(v, v, v), + ) +} + +#[test] +fn d1_a_wash_or_selection_stays_on_the_windows_side_of_a_mid_grey_text() { + // An active light window whose text is in the middle of the scale, and + // a backdrop window whose inactive effect shades the text toward it. + let mut cases: Vec<(String, ColorTokens)> = (110..=130u8) + .step_by(4) + .map(|v| { + let t = kde::from_scheme(&breeze_light_with_grey_text(v)); + (format!("grey {v}, active"), paint(&t, true, false)) + }) + .collect(); + for amount in [0.2, 0.25, 0.4] { + let mut scheme = KdeColorScheme::breeze_light(); + scheme.inactive_effect = StateEffect { + enabled: true, + intensity_effect: IntensityEffect::Shade, + intensity_amount: amount, + ..StateEffect::INACTIVE_DEFAULT + }; + let t = kde::from_scheme(&scheme); + cases.push((format!("shade {amount}, inactive"), paint(&t, false, false))); + } + for (name, c) in cases { + let text = c.text_primary.relative_luminance(); + let window = c.surface_main.relative_luminance(); + assert!(window > text, "{name}: the window is the lighter"); + for (fill_name, fill) in washes_and_selections(&c) { + // Lighter than the text, as the window is: never moved across it + // to black. + assert!( + fill.relative_luminance() > text, + "{name}: {fill_name} {fill:?} crossed the text {:?}", + c.text_primary + ); + assert!( + ratio(c.text_primary, fill) >= TEXT, + "{name}: text on {fill_name}" + ); + } + } +} + +// ── D10, the scheme's own text ────────────────────────────────────────── + +#[test] +fn d10_a_scheme_text_that_passes_is_kept_and_one_that_fails_is_lifted() { + for t in [kde::light(), kde::dark()] { + for (_, active, high_contrast, _, _) in &PAINT_STATES[..2] { + let c = paint(&t, *active, *high_contrast); + let state = if *active { + ColorState::Active + } else { + ColorState::Inactive + }; + assert_eq!( + c.text_primary, + palette(&t).role(state, PaletteRole::WindowText) + ); + assert_eq!(c.editor_fg, palette(&t).role(state, PaletteRole::Text)); + } + } + let t = kde::from_scheme(&breeze_light_with_grey_text(118)); + let vendor = role(&t, PaletteRole::WindowText); + let c = &t.colors; + assert!(ratio(vendor, c.surface_main) < TEXT); + assert_ne!(c.text_primary, vendor); + assert!(c.text_primary.relative_luminance() < vendor.relative_luminance()); + for (name, bg) in text_surfaces(c) { + assert!(ratio(c.text_primary, bg) >= TEXT, "on {name}"); + } + assert!(ratio(c.editor_fg, c.editor_bg) >= TEXT); +} + +// ── D11, surfaces in the middle of the scale ──────────────────────────── + +#[test] +fn d11_an_inactive_window_shaded_toward_the_middle_is_moved_back() { + // Breeze Dark with an inactive shade of 0.1: white reaches 6.44:1 on + // the inactive window, short of the 7:1 high-contrast floor. + let mut scheme = KdeColorScheme::breeze_dark(); + scheme.inactive_effect = StateEffect { + enabled: true, + intensity_effect: IntensityEffect::Shade, + intensity_amount: 0.1, + ..StateEffect::INACTIVE_DEFAULT + }; + let t = kde::from_scheme(&scheme); + let vendor = palette(&t).role(ColorState::Inactive, PaletteRole::Window); + assert!(ratio(Color::WHITE, vendor) < 7.0); + let c = paint(&t, false, true); + assert!(c.surface_main.relative_luminance() < vendor.relative_luminance()); + assert!(ratio(c.text_primary, c.surface_main) >= 7.0); + // The normal projection needs no move: white reaches 4.5:1 there. + assert_eq!(paint(&t, false, false).surface_main, vendor); + // Nor does any stock surface, in any paint state. + for t in [kde::light(), kde::dark()] { + for (_, active, high_contrast, _, _) in PAINT_STATES { + let c = paint(&t, active, high_contrast); + let state = if active { + ColorState::Active + } else { + ColorState::Inactive + }; + assert_eq!(c.surface_main, palette(&t).role(state, PaletteRole::Window)); + assert_eq!( + c.surface_content, + palette(&t).role(state, PaletteRole::Base) + ); + } + } +} + +// ── D2, inactive text ─────────────────────────────────────────────────── + +#[test] +fn d2_premise_breeze_lights_inactive_text_fails_on_its_window() { + let t = kde::light(); + let inactive = palette(&t) + .resolved() + .set(ColorState::Active, ColorSet::Window) + .foreground(ForegroundRole::Inactive) + .to_color(); + assert!(ratio(inactive, role(&t, PaletteRole::Window)) < TEXT); +} + +#[test] +fn d2_secondary_text_passes_on_every_surface() { + for t in [kde::light(), kde::dark()] { + let c = &t.colors; + for (name, bg) in text_surfaces(c) { + assert!( + ratio(c.text_secondary, bg) >= TEXT, + "secondary text on {name}: {:.2}:1", + ratio(c.text_secondary, bg) + ); + } + assert!(ratio(c.tooltip_shortcut, c.tooltip_bg) >= TEXT); + assert!(ratio(c.editor_gutter_fg, c.editor_bg) >= TEXT); + } +} + +/// The two faces of an unselected Breeze tab, at rest and hovered, in +/// `theme` as the paint walker projects it for the paint state. +fn unselected_tab_faces( + theme: &Theme, + window_active: bool, + high_contrast: bool, +) -> [(&'static str, Color); 2] { + let projected = theme.for_paint_state(PaintState { + window_active, + high_contrast, + }); + let t = BreezeTabColors::resolve(&projected, window_active, high_contrast, false); + [ + ("unselected tab", t.unselected), + ("hovered unselected tab", t.hovered), + ] +} + +/// Breeze paints an unselected tab darker than the window (k.5.14), and the +/// scheme's inactive text, which a tab bar's idle title is painted in, falls +/// short on it even in stock Breeze Light in an active window. +#[test] +fn d2_premise_breeze_lights_inactive_text_fails_on_its_unselected_tab() { + let t = kde::light(); + let inactive = palette(&t) + .resolved() + .set(ColorState::Active, ColorSet::Window) + .foreground(ForegroundRole::Inactive) + .to_color(); + let [(_, face), _] = unselected_tab_faces(&t, true, false); + assert!( + ratio(inactive, face) < TEXT, + "{:.2}:1 on {face:?}", + ratio(inactive, face) + ); +} + +/// D2 lifts the secondary text on the unselected tab's faces too, at rest +/// and hovered, for Breeze Light, Dark and Classic, every recorded scheme, +/// the ten accents on Light and on Dark and the adversarial schemes, in +/// every paint state; the property over random schemes checks the same +/// pair through a mounted tab bar ([`face_failures`]). +#[test] +fn d2_secondary_text_passes_on_the_unselected_tab_faces_in_every_paint_state() { + let mut all = Vec::new(); + for (name, scheme) in sweep_schemes() { + let theme = kde::from_scheme(&scheme); + for (state, active, high_contrast, text, _) in PAINT_STATES { + let secondary = paint(&theme, active, high_contrast).text_secondary; + for (face_name, face) in unselected_tab_faces(&theme, active, high_contrast) { + if ratio(secondary, face) < text { + all.push(format!( + "{name}, {state}: text_secondary on the {face_name}: {:.2}:1 < {text}", + ratio(secondary, face) + )); + } + } + } + } + assert!( + all.is_empty(), + "{} failures:\n{}", + all.len(), + all.join("\n") + ); +} + +// ── D3, links ─────────────────────────────────────────────────────────── + +#[test] +fn d3_premise_breeze_lights_link_fails_on_its_window() { + let t = kde::light(); + let link = view_foreground(&t, ForegroundRole::Link); + assert!(ratio(link, role(&t, PaletteRole::Window)) < TEXT); +} + +#[test] +fn d3_links_pass_on_every_surface() { + for t in [kde::light(), kde::dark()] { + let c = &t.colors; + for (name, bg) in text_surfaces(c) { + for (link, fg) in [ + ("text_link", c.text_link), + ("text_link_hover", c.text_link_hover), + ("text_link_visited", c.text_link_visited), + ] { + assert!(ratio(fg, bg) >= TEXT, "{link} on {name}"); + } + } + } +} + +// ── D4, status colours ────────────────────────────────────────────────── + +#[test] +fn d4_premise_breeze_lights_status_colours_fail_on_its_view() { + let t = kde::light(); + let base = role(&t, PaletteRole::Base); + for r in [ + ForegroundRole::Negative, + ForegroundRole::Neutral, + ForegroundRole::Positive, + ] { + assert!(ratio(view_foreground(&t, r), base) < TEXT, "{r:?}"); + } +} + +#[test] +fn d4_status_colours_pass_as_text_and_as_borders() { + for t in [kde::light(), kde::dark()] { + let c = &t.colors; + for fg in [ + c.text_error, + c.text_warning, + c.text_success, + c.status_info_fg, + ] { + for (name, bg) in text_surfaces(c) { + assert!(ratio(fg, bg) >= TEXT, "{fg:?} on {name}"); + } + } + for fg in [c.border_error, c.border_warning, c.focus_ring_error] { + assert!(ratio(fg, c.surface_main) >= NON_TEXT); + assert!(ratio(fg, c.surface_content) >= NON_TEXT); + } + } +} + +// ── D5, the focus decoration ──────────────────────────────────────────── + +#[test] +fn d5_premise_breeze_lights_focus_colour_fails_on_its_view() { + let t = kde::light(); + let focus = palette(&t) + .resolved() + .set(ColorState::Active, ColorSet::View) + .decoration(DecorationRole::Focus) + .to_color(); + assert!(ratio(focus, role(&t, PaletteRole::Base)) < NON_TEXT); +} + +/// Breeze Light with a translucent negative and focus colour, and the +/// golden scheme with a translucent focus and a transparent positive. +fn translucent_schemes() -> Vec<(&'static str, KdeColorScheme)> { + let half = with(KdeColorScheme::breeze_light(), &TEXT_SETS, |set| { + set.foreground_negative = KdeColor::rgba8(218, 68, 83, 128); + set.decoration_focus = KdeColor::rgba8(61, 174, 233, 128); + }); + vec![ + ("half-translucent negative and focus", half), + ("SyntheticValueForms", golden_scheme("SyntheticValueForms")), + ] +} + +#[test] +fn d5_the_focus_ring_passes_and_stays_opaque() { + let mut themes = vec![("Breeze Light", kde::light()), ("Breeze Dark", kde::dark())]; + for (name, scheme) in translucent_schemes() { + themes.push((name, kde::from_scheme(&scheme))); + } + for (name, t) in themes { + for (state, active, high_contrast, _, non_text) in PAINT_STATES { + let c = paint(&t, active, high_contrast); + assert_eq!(c.focus_ring.a(), 1.0, "{name}, {state}"); + assert_eq!(c.focus_ring, c.border_focused); + for bg in [c.surface_main, c.surface_content, c.surface_raised] { + assert!(ratio(c.focus_ring, bg) >= non_text, "{name}, {state}"); + } + } + } +} + +#[test] +fn every_lifted_token_is_opaque_even_from_a_translucent_scheme_colour() { + for (name, scheme) in translucent_schemes() { + let t = kde::from_scheme(&scheme); + // The premise: the scheme has translucent colours to lift. + let view = &scheme.sets[ColorSet::View.index()]; + assert!(view.decoration_focus.a < u16::MAX, "{name}"); + for (state, active, high_contrast, _, _) in PAINT_STATES { + let c = paint(&t, active, high_contrast); + for (token, color) in [ + ("text_primary", c.text_primary), + ("text_secondary", c.text_secondary), + ("text_link", c.text_link), + ("text_link_hover", c.text_link_hover), + ("text_link_visited", c.text_link_visited), + ("text_error", c.text_error), + ("text_warning", c.text_warning), + ("text_success", c.text_success), + ("status_info_fg", c.status_info_fg), + ("text_on_accent", c.text_on_accent), + ("selection_text_active", c.selection_text_active), + ("selection_text_inactive", c.selection_text_inactive), + ("editor_fg", c.editor_fg), + ("editor_gutter_fg", c.editor_gutter_fg), + ("tooltip_text", c.tooltip_text), + ("tooltip_shortcut", c.tooltip_shortcut), + ("border_strong", c.border_strong), + ("border_error", c.border_error), + ("border_warning", c.border_warning), + ("focus_ring", c.focus_ring), + ("focus_ring_error", c.focus_ring_error), + ("scrollbar_thumb", c.scrollbar_thumb), + ("scrollbar_thumb_hover", c.scrollbar_thumb_hover), + ] { + assert_eq!(color.a(), 1.0, "{name}, {state}: {token} {color:?}"); + } + } + } +} + +// ── D6, frame outlines ────────────────────────────────────────────────── + +#[test] +fn d6_premise_the_frame_outline_fails_on_its_window() { + let t = kde::light(); + let outline = palette(&t).colors(ColorState::Active).frame_outline; + assert!(ratio(outline, role(&t, PaletteRole::Window)) < NON_TEXT); +} + +#[test] +fn d6_the_strong_outline_passes_and_the_plain_one_is_breezes() { + for t in [kde::light(), kde::dark()] { + let c = &t.colors; + assert_eq!( + c.border, + palette(&t).colors(ColorState::Active).frame_outline + ); + for bg in [c.surface_main, c.surface_content, c.surface_raised] { + assert!(ratio(c.border_strong, bg) >= NON_TEXT); + assert!(ratio(c.divider_strong, bg) >= NON_TEXT); + } + } +} + +// ── D9, the scroll-bar thumb ──────────────────────────────────────────── + +#[test] +fn d9_premise_breezes_thumbs_fail_on_the_window() { + let t = kde::light(); + let window = palette(&t) + .resolved() + .role(ColorState::Active, PaletteRole::Window); + let text = palette(&t) + .resolved() + .role(ColorState::Active, PaletteRole::WindowText); + let hover = palette(&t) + .resolved() + .set(ColorState::Active, ColorSet::View) + .decoration(DecorationRole::Hover); + let idle = mix(window, text, 0.5 * 0.7 * 0.5).to_color(); + let hovered = mix(window, hover, 0.5).to_color(); + assert!(ratio(idle, window.to_color()) < NON_TEXT); + assert!(ratio(hovered, window.to_color()) < NON_TEXT); +} + +#[test] +fn d9_the_thumbs_pass_and_no_track_is_painted() { + for t in [kde::light(), kde::dark()] { + let c = &t.colors; + for fg in [ + c.scrollbar_thumb, + c.scrollbar_thumb_hover, + c.scrollbar_thumb_pressed, + ] { + assert!(ratio(fg, c.surface_main) >= NON_TEXT); + assert!(ratio(fg, c.surface_content) >= NON_TEXT); + } + assert_eq!(c.scrollbar_track.a(), 0.0); + assert_eq!(c.scrollbar_track_hover.a(), 0.0); + } +} + +// ── The sweep ─────────────────────────────────────────────────────────── + +/// `scheme` with `edit` applied to each of `sets`. +fn with( + mut scheme: KdeColorScheme, + sets: &[ColorSet], + edit: impl Fn(&mut SetColors), +) -> KdeColorScheme { + for set in sets { + edit(&mut scheme.sets[set.index()]); + } + scheme +} + +const TEXT_SETS: [ColorSet; 4] = [ + ColorSet::View, + ColorSet::Window, + ColorSet::Button, + ColorSet::Tooltip, +]; + +/// Schemes built to defeat the lifts: secondary, link, status and focus +/// colours that nearly vanish into their backgrounds, frame contrast at +/// both ends, a highlight that is the window colour or a mid grey, inactive +/// effects turned on, with and without an intensity effect that shades the +/// inactive backgrounds toward the middle of the scale, a scheme tinted +/// toward its accent, a window text in the middle of the scale, and +/// translucent status and focus colours. +fn adversarial() -> Vec<(&'static str, KdeColorScheme)> { + let rgb = KdeColor::rgb8; + let faint_light = { + let mut s = with(KdeColorScheme::breeze_light(), &TEXT_SETS, |set| { + set.foreground_inactive = rgb(220, 222, 224); + set.foreground_active = rgb(210, 230, 250); + set.foreground_link = rgb(200, 220, 250); + set.foreground_visited = rgb(225, 205, 235); + set.foreground_negative = rgb(250, 200, 200); + set.foreground_neutral = rgb(250, 230, 200); + set.foreground_positive = rgb(200, 240, 210); + set.decoration_focus = rgb(239, 240, 241); + set.decoration_hover = rgb(239, 240, 241); + }); + s.frame_contrast = 0.0; + s + }; + let faint_dark = { + let mut s = with(KdeColorScheme::breeze_dark(), &TEXT_SETS, |set| { + set.foreground_inactive = rgb(45, 47, 50); + set.foreground_active = rgb(20, 50, 80); + set.foreground_link = rgb(20, 40, 90); + set.foreground_visited = rgb(60, 30, 70); + set.foreground_negative = rgb(90, 20, 20); + set.foreground_neutral = rgb(90, 50, 0); + set.foreground_positive = rgb(10, 60, 30); + set.decoration_focus = rgb(32, 35, 38); + set.decoration_hover = rgb(32, 35, 38); + }); + s.frame_contrast = 0.0; + s + }; + let window_highlight = { + let mut s = with( + KdeColorScheme::breeze_light(), + &[ColorSet::Selection], + |set| { + set.background_normal = rgb(239, 240, 241); + set.foreground_normal = rgb(255, 255, 255); + }, + ); + s.frame_contrast = 1.0; + s + }; + let grey = |scheme: KdeColorScheme| { + with(scheme, &[ColorSet::Selection], |set| { + set.background_normal = rgb(128, 128, 128); + set.foreground_normal = rgb(128, 128, 128); + }) + }; + let effects_on = { + let mut s = KdeColorScheme::breeze_light(); + s.inactive_effect = StateEffect { + enabled: true, + ..StateEffect::INACTIVE_DEFAULT + }; + s + }; + let effects_on_dark = { + let mut s = KdeColorScheme::breeze_dark(); + s.inactive_effect = StateEffect { + enabled: true, + ..StateEffect::INACTIVE_DEFAULT + }; + s + }; + let tinted = { + let mut s = KdeColorScheme::breeze_light(); + s.tint_factor = Some(0.4); + s.with_accent(PlasmaAccent::Pink.kde_color()) + }; + let tinted_dark = { + let mut s = KdeColorScheme::breeze_dark(); + s.tint_factor = Some(0.4); + s.with_accent(PlasmaAccent::Yellow.kde_color()) + }; + // A window and view text in the middle of the scale: lifting a light + // fill until it carries this text at 4.5:1 cannot succeed on the light + // side, and once went all the way to black. + let mid_grey_text = with( + KdeColorScheme::breeze_light(), + &[ColorSet::View, ColorSet::Window], + |set| set.foreground_normal = rgb(118, 118, 118), + ); + // `[ColorEffects:Inactive]` with Enable and one intensity effect, the + // other keys at KDE's defaults. + let intensity = |mut s: KdeColorScheme, intensity_effect, intensity_amount| { + s.inactive_effect = StateEffect { + enabled: true, + intensity_effect, + intensity_amount, + ..StateEffect::INACTIVE_DEFAULT + }; + s + }; + // Translucent colours, which the `r,g,b,a` form writes. + let translucent = with(KdeColorScheme::breeze_light(), &TEXT_SETS, |set| { + set.foreground_negative = KdeColor::rgba8(218, 68, 83, 128); + set.decoration_focus = KdeColor::rgba8(61, 174, 233, 128); + }); + // A translucent Button background, which D11 carries with its alpha, + // in an inactive window whose effects move it toward the middle. + let translucent_button = |intensity_effect, intensity_amount| { + intensity( + with(KdeColorScheme::breeze_dark(), &[ColorSet::Button], |set| { + set.background_normal = KdeColor::rgba8(41, 44, 48, 204); + }), + intensity_effect, + intensity_amount, + ) + }; + vec![ + ("faint light", faint_light), + ("faint dark", faint_dark), + ("highlight is the window", window_highlight), + ( + "grey highlight, light", + grey(KdeColorScheme::breeze_light()), + ), + ("grey highlight, dark", grey(KdeColorScheme::breeze_dark())), + ("inactive effects on, light", effects_on), + ("inactive effects on, dark", effects_on_dark), + ("tinted pink", tinted), + ("tinted yellow, dark", tinted_dark), + ("mid-grey text, light", mid_grey_text), + ( + "inactive shade 0.2, light", + intensity(KdeColorScheme::breeze_light(), IntensityEffect::Shade, 0.2), + ), + ( + "inactive shade 0.4, light", + intensity(KdeColorScheme::breeze_light(), IntensityEffect::Shade, 0.4), + ), + ( + "inactive shade 0.1, dark", + intensity(KdeColorScheme::breeze_dark(), IntensityEffect::Shade, 0.1), + ), + ( + "inactive lighten 0.4, dark", + intensity(KdeColorScheme::breeze_dark(), IntensityEffect::Lighten, 0.4), + ), + ("translucent status and focus", translucent), + ( + "translucent button, inactive lighten 0.4, dark", + translucent_button(IntensityEffect::Lighten, 0.4), + ), + ( + "translucent button, inactive shade 0.1, dark", + translucent_button(IntensityEffect::Shade, 0.1), + ), + ] +} + +const RESOLVE_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tools/kde_golden/vectors/resolve.json" +)); + +/// An installed scheme the golden vectors record, parsed from its groups +/// written back as configuration text (a nested group is recorded with its +/// path joined by `][`, so bracketing the name gives the header line). +fn golden_scheme(wanted: &str) -> KdeColorScheme { + let all: Value = serde_json::from_str(RESOLVE_JSON).expect("resolve.json parses"); + let scheme = all["schemes"] + .as_array() + .expect("resolve.json has a list of schemes") + .iter() + .find(|s| s["name"].as_str() == Some(wanted)) + .unwrap_or_else(|| panic!("resolve.json has no scheme {wanted}")); + let mut text = String::new(); + for (group, entries) in scheme["input"].as_object().expect("an input object") { + text.push('['); + text.push_str(group); + text.push_str("]\n"); + for (key, value) in entries.as_object().expect("a group object") { + text.push_str(key); + text.push('='); + text.push_str(value.as_str().expect("a value is a string")); + text.push('\n'); + } + text.push('\n'); + } + KdeColorScheme::parse(&text).0 +} + +/// The name of every scheme the golden vectors record. +fn golden_names() -> Vec { + let all: Value = serde_json::from_str(RESOLVE_JSON).expect("resolve.json parses"); + all["schemes"] + .as_array() + .expect("resolve.json has a list of schemes") + .iter() + .map(|s| s["name"].as_str().expect("a scheme has a name").to_owned()) + .collect() +} + +#[test] +fn the_golden_vectors_record_thirteen_schemes_two_with_inactive_intensity_or_alpha() { + let names = golden_names(); + assert_eq!(names.len(), 13, "{names:?}"); + // The two the sweep needs most: an intensity effect in the Inactive + // state, and translucent and transparent colours. + let effects = golden_scheme("SyntheticInactiveEffects").inactive_effect; + assert!(effects.enabled); + assert_eq!(effects.intensity_effect, IntensityEffect::Shade); + let forms = golden_scheme("SyntheticValueForms"); + let view = &forms.sets[ColorSet::View.index()]; + assert!(view.decoration_focus.a < u16::MAX); + assert_eq!(view.foreground_positive.a, 0); +} + +/// Oxygen's three schemes, the installed schemes with inactive effects. +fn oxygen() -> Vec<(&'static str, KdeColorScheme)> { + vec![ + ("Oxygen", golden_scheme("Oxygen")), + ("Oxygen Cold", golden_scheme("OxygenCold")), + ("Oxygen Dark", golden_scheme("OxygenDark")), + ] +} + +#[test] +fn oxygen_and_oxygen_dark_turn_inactive_effects_on() { + let schemes = oxygen(); + let enabled: Vec = schemes + .iter() + .map(|(_, s)| s.inactive_effect.enabled) + .collect(); + assert_eq!(enabled, [true, false, true]); + assert!(!schemes[0].1.is_dark()); + assert!(schemes[2].1.is_dark()); +} + +fn sweep_schemes() -> Vec<(String, KdeColorScheme)> { + let mut out = vec![ + ("Breeze Light".to_owned(), KdeColorScheme::breeze_light()), + ("Breeze Dark".to_owned(), KdeColorScheme::breeze_dark()), + ( + "Breeze Classic".to_owned(), + KdeColorScheme::breeze_classic(), + ), + ]; + for name in golden_names() { + out.push((format!("golden {name}"), golden_scheme(&name))); + } + for accent in PlasmaAccent::ALL { + out.push(( + format!("Breeze Light, {accent}"), + KdeColorScheme::breeze_light().with_accent(accent.kde_color()), + )); + out.push(( + format!("Breeze Dark, {accent}"), + KdeColorScheme::breeze_dark().with_accent(accent.kde_color()), + )); + } + for (name, scheme) in adversarial() { + out.push((name.to_owned(), scheme)); + } + out +} + +/// The four paint states, named, with the floors each is held to. +const PAINT_STATES: [(&str, bool, bool, f32, f32); 4] = [ + ("active", true, false, TEXT, NON_TEXT), + ("inactive", false, false, TEXT, NON_TEXT), + ("active, high contrast", true, true, 7.0, 4.5), + ("inactive, high contrast", false, true, 7.0, 4.5), +]; + +#[test] +fn every_declared_pair_passes_for_every_scheme_in_every_paint_state() { + let mut all = Vec::new(); + for (name, scheme) in sweep_schemes() { + let theme = kde::from_scheme(&scheme); + for (state, active, high_contrast, text, non_text) in PAINT_STATES { + for failure in failures(&paint(&theme, active, high_contrast), text, non_text) { + all.push(format!("{name}, {state}: {failure}")); + } + } + } + assert!( + all.is_empty(), + "{} failures:\n{}", + all.len(), + all.join("\n") + ); +} + +/// The title of a mounted two-tab bar's unselected tab and the face the +/// tab paints behind it, at rest and hovered, in a window that is or is not +/// active, with or without the high-contrast preference: each distinct +/// glyph colour and the tab's fill, as the frame records them. +fn mounted_tab_titles( + theme: &Theme, + window_active: bool, + high_contrast: bool, +) -> Vec<(&'static str, Color, Color)> { + let first = TabId::fresh(); + let model = ListModel::from_vec(vec![(first, "Tab"), (TabId::fresh(), "Second")]); + let delegate = TabDelegate::new(|_, t: &(TabId, &'static str)| lit!(t.1)); + let selected: Signal> = Signal::new(Some(first)); + let bar = TabBar::horizontal(model, delegate, selected, |_, t| t.0) + .tab_sizing(TabSizing::Independent) + .show_scroll_arrows(false) + .show_overflow_dropdown(false); + let mut tree = WidgetTree::new() + .with_theme(theme.clone()) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + tree.set_window_active(window_active); + tree.set_accessibility_preferences(high_contrast, false, 1.0); + let bar = tree.add(bar); + let proposal = SizeProposal { + width: Some(800.0), + height: None, + }; + tree.layout(proposal); + let headers = nodes_named(&tree, bar, "TabHeader"); + assert_eq!(headers.len(), 2, "two tab headers"); + let other = tree.bounds(headers[1]); + let rect = [other.x, other.y, other.width, other.height]; + let color = |c: [f32; 4]| Color::new(c[0], c[1], c[2], c[3]); + let mut out = Vec::new(); + for what in ["resting", "hovered"] { + if what == "hovered" { + tree.pointer_move(other.center()); + tree.layout(proposal); + } + let frame = tree.render(); + let face = frame + .decorations + .iter() + .rev() + .find(|d| d.rect == rect) + .unwrap_or_else(|| panic!("no {what} unselected tab face")) + .color; + let inside = |g: &&GlyphQuad| { + let (x, y) = ( + g.screen[0] + g.screen[2] * 0.5, + g.screen[1] + g.screen[3] * 0.5, + ); + x >= rect[0] && x <= rect[0] + rect[2] && y >= rect[1] && y <= rect[1] + rect[3] + }; + let glyphs: Vec<&GlyphQuad> = frame.glyphs.iter().filter(inside).collect(); + assert!(!glyphs.is_empty(), "the {what} unselected tab has a title"); + for g in glyphs { + let pair = (what, color(g.color), color(face)); + if !out.contains(&pair) { + out.push(pair); + } + } + } + out +} + +/// Every node under `root` whose widget type is `name`, in tree order. +fn nodes_named(tree: &WidgetTree, root: WidgetId, name: &str) -> Vec { + fn walk(tree: &WidgetTree, id: WidgetId, name: &str, out: &mut Vec) { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(name)) + { + out.push(id); + } + for c in tree.children(id) { + walk(tree, c, name, out); + } + } + let mut out = Vec::new(); + walk(tree, root, name, &mut out); + out +} + +/// What the Breeze styles paint on a face of their own rather than on a +/// surface token: the push button's label on its face in every state, a +/// combo box's arrow on its face, a table header's label and sort arrow on +/// its section, a check box's mark on its box, and an unselected tab's +/// title on its face, at rest and hovered. Each face is the style's, read +/// from the theme as the paint walker projects it, and the tab's title and +/// face are read from a mounted tab bar's frame, so the pairs follow what +/// is painted. +fn face_failures(theme: &Theme, window_active: bool, high_contrast: bool) -> Vec { + let (text, non_text) = if high_contrast { + (7.0, 4.5) + } else { + (TEXT, NON_TEXT) + }; + let projected = theme.for_paint_state(PaintState { + window_active, + high_contrast, + }); + let c = &projected.colors; + let mut pairs: Vec<(String, Color, Color, f32)> = Vec::new(); + + let button = BreezeButtonColors::resolve(&projected, window_active, high_contrast, false); + for kind in [BreezeButtonKind::Push, BreezeButtonKind::Default] { + for state in [ + BreezeButtonState::Rest, + BreezeButtonState::Highlighted, + BreezeButtonState::Pressed, + ] { + for checked in [false, true] { + let face = button_paint(kind, state, checked, window_active, &button) + .face + .expect("a push or default button has a face"); + pairs.push(( + format!("{kind:?} button label on its {state:?} face, checked {checked}"), + c.text_primary, + face, + text, + )); + } + } + } + + let header = BreezeHeaderColors::resolve(&projected, window_active, high_contrast); + for (hovered, pressed) in [(false, false), (true, false), (false, true)] { + let fill = header.fill(hovered, pressed); + let what = format!("hovered {hovered}, pressed {pressed}"); + pairs.push(( + format!("header label on its section, {what}"), + c.text_primary, + fill, + text, + )); + pairs.push(( + format!("header sort arrow on its section, {what}"), + header.arrow, + fill, + non_text, + )); + } + + let arrow = combo_arrow_color(&projected, window_active, high_contrast, false, false); + for (what, face) in [("at rest", button.button), ("open", button.pressed)] { + pairs.push(( + format!("combo box arrow on its face {what}"), + arrow, + face, + non_text, + )); + } + + let indicator = BreezeIndicatorColors::resolve(&projected, window_active, high_contrast, false); + for pressed in [false, true] { + let paint = box_paint(true, false, pressed, false, &indicator); + // The box as painted: one opaque fill, or a translucent face and + // the wash laid as two layers over the window. + let face = paint.composited_fill().unwrap_or_else(|| { + let face = painted_over(paint.face, c.surface_main); + paint.wash.map_or(face, |wash| painted_over(wash, face)) + }); + pairs.push(( + format!("check mark on its box, pressed {pressed}"), + paint.mark, + face, + non_text, + )); + } + + for (what, title, face) in mounted_tab_titles(theme, window_active, high_contrast) { + pairs.push(( + format!("unselected tab title on its {what} face"), + title, + face, + text, + )); + } + + pairs + .into_iter() + .filter(|(_, fg, bg, floor)| ratio(*fg, *bg) < *floor) + .map(|(name, fg, bg, floor)| { + format!( + "{name}: {:.2}:1 < {floor} ({fg:?} on {bg:?})", + ratio(fg, bg) + ) + }) + .collect() +} + +/// The faces the styles paint reach the same floors as the tokens, for +/// every scheme of the sweep in every paint state: a face is the surface +/// its text was lifted on, moved back by D11 where the scheme's inactive +/// effects shade it toward the middle of the scale. +#[test] +fn every_face_a_style_paints_carries_its_text_for_every_scheme_in_every_paint_state() { + let mut all = Vec::new(); + for (name, scheme) in sweep_schemes() { + let theme = kde::from_scheme(&scheme); + for (state, active, high_contrast, _, _) in PAINT_STATES { + for failure in face_failures(&theme, active, high_contrast) { + all.push(format!("{name}, {state}: {failure}")); + } + } + } + assert!( + all.is_empty(), + "{} failures:\n{}", + all.len(), + all.join("\n") + ); +} + +/// A colour pair a scheme itself gives, read from the palette the theme +/// keeps, with the floor its token is lifted to. +#[derive(Debug, Clone, Copy)] +enum Vendor { + /// A view foreground on the view (Base): D2, D3 and D4. + OnView(ForegroundRole), + /// The view's focus decoration on the view, a non-text pair: D5. + FocusOnView, + /// The selection text on the selection: D1's text. + SelectionText, + /// The window text on the selection: D1's fill. + WindowTextOnSelection, + /// The window text on the window: D10, and D11 where no text could + /// reach the floor on the window. + WindowTextOnWindow, + /// The same pair at the high-contrast text floor, 7:1. + WindowTextOnWindowEnhanced, + /// The view text, a check box's mark, on a checked box as the scheme's + /// colours paint it (the Button background over the window, the + /// highlight at 30 % over that), at the high-contrast non-text floor, + /// 4.5:1. + MarkOnCheckedBoxEnhanced, +} + +fn window_text_on_window(p: &KdePalette, state: ColorState) -> f32 { + ratio( + p.role(state, PaletteRole::WindowText), + p.role(state, PaletteRole::Window), + ) +} + +impl Vendor { + /// The pair's contrast in `state`, and its floor. + fn contrast(self, p: &KdePalette, state: ColorState) -> (f32, f32) { + let view = p.resolved().set(state, ColorSet::View); + let base = p.role(state, PaletteRole::Base); + let highlight = p.role(state, PaletteRole::Highlight); + match self { + Vendor::OnView(r) => (ratio(view.foreground(r).to_color(), base), TEXT), + Vendor::FocusOnView => ( + ratio(view.decoration(DecorationRole::Focus).to_color(), base), + NON_TEXT, + ), + Vendor::SelectionText => ( + ratio(p.role(state, PaletteRole::HighlightedText), highlight), + TEXT, + ), + Vendor::WindowTextOnSelection => ( + ratio(p.role(state, PaletteRole::WindowText), highlight), + TEXT, + ), + Vendor::WindowTextOnWindow => (window_text_on_window(p, state), TEXT), + Vendor::WindowTextOnWindowEnhanced => (window_text_on_window(p, state), 7.0), + Vendor::MarkOnCheckedBoxEnhanced => { + let face = painted_over( + p.role(state, PaletteRole::Button), + p.role(state, PaletteRole::Window), + ); + let checked = painted_over(highlight.with_alpha(0.3), face); + (ratio(p.role(state, PaletteRole::Text), checked), 4.5) + } + } + } +} + +/// For each adversarial scheme, the state it targets and the vendor pairs +/// in that state that fall short, so its passing the sweep is the lifts' +/// doing. +fn what_each_adversarial_scheme_defeats() -> Vec<(&'static str, ColorState, Vec)> { + use ColorState::{Active, Inactive}; + use ForegroundRole::{Inactive as Secondary, Link, Negative, Neutral}; + use Vendor::*; + let faint = vec![ + OnView(Secondary), + OnView(Link), + OnView(Negative), + FocusOnView, + ]; + vec![ + ("faint light", Active, faint.clone()), + ("faint dark", Active, faint), + ("highlight is the window", Active, vec![SelectionText]), + ( + "grey highlight, light", + Active, + vec![SelectionText, WindowTextOnSelection], + ), + ( + "grey highlight, dark", + Active, + vec![SelectionText, WindowTextOnSelection], + ), + ( + "inactive effects on, light", + Inactive, + vec![OnView(Negative), OnView(Neutral), FocusOnView], + ), + ( + "inactive effects on, dark", + Inactive, + vec![OnView(Negative)], + ), + ( + "tinted pink", + Active, + vec![OnView(Secondary), OnView(Neutral)], + ), + ( + "tinted yellow, dark", + Active, + vec![WindowTextOnSelection, OnView(Negative)], + ), + ( + "mid-grey text, light", + Active, + vec![WindowTextOnWindow, WindowTextOnSelection], + ), + ( + "inactive shade 0.2, light", + Inactive, + vec![WindowTextOnWindow], + ), + ( + "inactive shade 0.4, light", + Inactive, + vec![WindowTextOnWindow], + ), + ( + "inactive shade 0.1, dark", + Inactive, + vec![WindowTextOnWindowEnhanced], + ), + ( + "inactive lighten 0.4, dark", + Inactive, + vec![WindowTextOnWindow], + ), + ( + "translucent status and focus", + Active, + vec![OnView(Negative), FocusOnView], + ), + ( + "translucent button, inactive lighten 0.4, dark", + Inactive, + vec![WindowTextOnWindow, MarkOnCheckedBoxEnhanced], + ), + ( + "translucent button, inactive shade 0.1, dark", + Inactive, + // The scheme's own mark passes (5.13:1); the box D11 carries + // for the 7:1 text does not carry it without the lift. + vec![WindowTextOnWindowEnhanced], + ), + ] +} + +#[test] +fn the_adversarial_schemes_do_need_the_lifts() { + // A scheme meant to defeat the lifts that every pair passed without + // them would test nothing: its own colours must fail. + let schemes = adversarial(); + let needs = what_each_adversarial_scheme_defeats(); + let names: Vec<&str> = schemes.iter().map(|(n, _)| *n).collect(); + let listed: Vec<&str> = needs.iter().map(|(n, _, _)| *n).collect(); + assert_eq!(names, listed, "an adversarial scheme is unlisted"); + for ((name, scheme), (_, state, pairs)) in schemes.into_iter().zip(needs) { + let theme = kde::from_scheme(&scheme); + let p = palette(&theme); + for pair in pairs { + let (got, floor) = pair.contrast(p, state); + assert!(got < floor, "{name}: {pair:?} already passes, {got:.2}:1"); + // An inactive-effects scheme earns its place by the effect: the + // Inactive colour falls further short than the Active one. + if state == ColorState::Inactive { + let (active, _) = pair.contrast(p, ColorState::Active); + assert!( + got < active, + "{name}: {pair:?} is no worse inactive ({got:.2}:1) than active ({active:.2}:1)" + ); + } + } + } +} + +// ── Random coherent schemes ───────────────────────────────────────────── + +/// One 8-bit triple with every channel in `range`. Cost: three bytes. +fn arb_triple(range: std::ops::RangeInclusive) -> impl Strategy { + [range.clone(), range.clone(), range] +} + +/// A set of twelve colours: both backgrounds on the scheme's side, the +/// normal text on the other, the other nine free. Cost: twelve triples. +fn arb_set(dark: bool) -> impl Strategy { + let (side, text) = if dark { + (0..=45, 200..=255) + } else { + (210..=255, 0..=60) + }; + ( + arb_triple(side.clone()), + arb_triple(side), + arb_triple(text), + prop::collection::vec(any::<[u8; 3]>(), 9), + ) + .prop_map(|(bg, alt, fg, free)| { + let mut rows = [[0u8; 3]; 12]; + rows[0] = bg; + rows[1] = alt; + rows[2] = fg; + rows[3..].copy_from_slice(&free); + SetColors::from_rgb8(rows) + }) +} + +/// A random `[ColorEffects:Inactive]`: enabled or not, each of the three +/// effects any of its kinds at any amount in `0..=1`, and any colour. +/// +/// Cost: one flag, three small enums, three numbers and a triple. +fn arb_inactive_effect() -> impl Strategy { + ( + any::(), + prop_oneof![ + Just(IntensityEffect::None), + Just(IntensityEffect::Shade), + Just(IntensityEffect::Darken), + Just(IntensityEffect::Lighten), + ], + 0.0_f64..=1.0, + prop_oneof![ + Just(ColorEffect::None), + Just(ColorEffect::Desaturate), + Just(ColorEffect::Fade), + Just(ColorEffect::Tint), + ], + 0.0_f64..=1.0, + any::<[u8; 3]>(), + prop_oneof![ + Just(ContrastEffect::None), + Just(ContrastEffect::Fade), + Just(ContrastEffect::Tint), + ], + 0.0_f64..=1.0, + ) + .prop_map( + |( + enabled, + intensity_effect, + intensity_amount, + color_effect, + color_amount, + [r, g, b], + contrast_effect, + contrast_amount, + )| StateEffect { + enabled, + intensity_effect, + intensity_amount, + color_effect, + color_amount, + color: KdeColor::rgb8(r, g, b), + contrast_effect, + contrast_amount, + }, + ) +} + +/// A coherent random scheme. +/// +/// Coherent means its surfaces are on one side, every window, view, button +/// and tooltip background light (channels 210 to 255) or dark (0 to 45), +/// and its normal texts on the other (0 to 60, or 200 to 255). Every +/// colour a lift or a rule derives (inactive text, links, status colours, +/// decorations, the whole selection set) is free, as are the frame +/// contrast, the contrast and the inactive effects, which can shade an +/// inactive window anywhere on the scale. +/// +/// Cost: four sets of twelve colours plus twelve free ones, two numbers, a +/// flag and an effect. Building the theme is the expensive part (see the +/// module doc); the scheme itself is about 60 triples. +fn arb_scheme() -> impl Strategy { + any::().prop_flat_map(|dark| { + ( + arb_set(dark), + arb_set(dark), + arb_set(dark), + arb_set(dark), + prop::collection::vec(any::<[u8; 3]>(), 12), + 0.0_f64..=1.0, + 0_i32..=10, + arb_inactive_effect(), + ) + .prop_map( + move |(view, window, button, tooltip, selection, fc, contrast, inactive)| { + let mut scheme = if dark { + KdeColorScheme::breeze_dark() + } else { + KdeColorScheme::breeze_light() + }; + scheme.sets[ColorSet::View.index()] = view; + scheme.sets[ColorSet::Window.index()] = window; + scheme.sets[ColorSet::Button.index()] = button; + scheme.sets[ColorSet::Tooltip.index()] = tooltip; + let mut rows = [[0u8; 3]; 12]; + rows.copy_from_slice(&selection); + scheme.sets[ColorSet::Selection.index()] = SetColors::from_rgb8(rows); + scheme.frame_contrast = fc; + scheme.contrast = contrast; + scheme.inactive_effect = inactive; + scheme + }, + ) + }) +} + +// ── 1. every declared pair of every coherent scheme reaches its floor in +// all four paint states: 4.5:1 text and 3:1 non-text for an active +// and an inactive window, 7:1 and 4.5:1 under high contrast ── +proptest! { + #[test] + fn every_coherent_scheme_meets_its_floors_in_every_paint_state(scheme in arb_scheme()) { + let theme = kde::from_scheme(&scheme); + for (active, high_contrast) in [(true, false), (false, false), (true, true), (false, true)] { + let (text, non_text) = if high_contrast { (7.0, 4.5) } else { (TEXT, NON_TEXT) }; + let mut f = failures(&paint(&theme, active, high_contrast), text, non_text); + f.extend(face_failures(&theme, active, high_contrast)); + prop_assert!( + f.is_empty(), + "active {} high contrast {}: {:?}\nscheme {:?}", + active, high_contrast, f, scheme.sets, + ); + } + } +} diff --git a/crates/teksilo-theme-kde/tests/density_gaps.rs b/crates/teksilo-theme-kde/tests/density_gaps.rs new file mode 100644 index 000000000..2c81e7973 --- /dev/null +++ b/crates/teksilo-theme-kde/tests/density_gaps.rs @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! No recipe this preset builds sets a field the widget does not render. +//! +//! `engineering/docs/density-projection-gaps.md` lists the density-projected +//! recipe fields that the widget composes from the recipe module's constant +//! instead of the style: a preset that writes one writes a number nobody +//! renders. Its sections 1 to 3 are the unrendered fields (section 4 lists +//! fields the page's text rule misses but a reader can see are read). +//! +//! Two checks hold this crate to that list, both driven by the page itself so +//! a row added there is covered here with no edit: +//! +//! - every struct literal of a recipe type in this crate's `src/`, the +//! segmented control's among them, leaves out every field the page lists +//! for that recipe; +//! - every factory that builds one of those recipes gives each listed field +//! the stock recipe's value at every density. +//! +//! This file reads the page from the repository, so it is left out of the +//! published package (the `exclude` list in `Cargo.toml`). + +use std::collections::BTreeSet; +use std::path::{Path, PathBuf}; + +use teksilo_theme_kde::styles::recipes; +use teksilo_theme_kde::styles::standard_item::breeze_standard_item_recipe_for; +use teksilo_theme_kde::typography::breeze_typography; +use teksilo_tokens::{InputTokens, TargetDensity}; +use teksilo_widgets::styles::{ + AvatarRecipe, BannerRecipe, CalendarRecipe, SearchFieldRecipe, SegmentedControlRecipe, + StandardItemRecipe, ToastRecipe, +}; + +const GAPS_MD: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../engineering/docs/density-projection-gaps.md" +)); + +const DENSITIES: [TargetDensity; 3] = [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, +]; + +/// The text between two backticks of a table cell, if the cell is one. +fn ticked(cell: &str) -> Option<&str> { + cell.trim().strip_prefix('`')?.strip_suffix('`') +} + +/// The `(recipe, field)` rows of the page's sections 1 to 3, with the +/// section each came from. +fn unrendered() -> Vec<(String, String, String)> { + let sections = GAPS_MD + .split("\n## 4.") + .next() + .expect("the page has a section 4"); + let mut section = String::new(); + let mut rows = Vec::new(); + for line in sections.lines() { + if let Some(h) = line.strip_prefix("## ").or(line.strip_prefix("### ")) { + section = h.split_whitespace().next().unwrap_or("").to_string(); + continue; + } + let cells: Vec<&str> = line.split('|').collect(); + if cells.len() < 4 || !cells[0].trim().is_empty() { + continue; + } + if let (Some(recipe), Some(field)) = (ticked(cells[1]), ticked(cells[2])) + && recipe.ends_with("Recipe") + { + rows.push((section.clone(), recipe.to_string(), field.to_string())); + } + } + rows +} + +/// The page parses: every section 1 to 3 table yields rows, and the +/// segmented control's are among them. +#[test] +fn the_page_lists_unrendered_fields_in_every_section() { + let rows = unrendered(); + let sections: BTreeSet<&str> = rows.iter().map(|(s, _, _)| s.as_str()).collect(); + for s in ["1.", "2.", "3a.", "3b."] { + assert!( + sections.contains(s), + "section {s} yields no row; parsed {sections:?}" + ); + } + assert!( + rows.iter() + .any(|(_, r, f)| r == "SegmentedControlRecipe" && f == "height"), + "the segmented control's height is listed" + ); +} + +fn strip_comments(src: &str) -> String { + src.lines() + .map(|line| match line.find("//") { + Some(i) => &line[..i], + None => line, + }) + .collect::>() + .join("\n") +} + +/// The identifier ending just before `pos`, skipping whitespace, and the text +/// before it. +fn ident_before(src: &str, pos: usize) -> Option<(&str, &str)> { + let head = src[..pos].trim_end(); + let start = head + .char_indices() + .rev() + .take_while(|(_, c)| c.is_alphanumeric() || *c == '_') + .last() + .map(|(i, _)| i)?; + Some((&head[start..], &head[..start])) +} + +fn matching_close(bytes: &[u8], open: usize) -> Option { + let mut depth = 0i32; + for (i, b) in bytes.iter().enumerate().skip(open) { + match b { + b'{' | b'(' | b'[' => depth += 1, + b'}' | b')' | b']' => { + depth -= 1; + if depth == 0 { + return Some(i); + } + } + _ => {} + } + } + None +} + +fn split_top_level(body: &str) -> Vec<&str> { + let mut parts = Vec::new(); + let mut depth = 0i32; + let mut start = 0; + for (i, c) in body.char_indices() { + match c { + '{' | '(' | '[' => depth += 1, + '}' | ')' | ']' => depth -= 1, + ',' if depth == 0 => { + parts.push(&body[start..i]); + start = i + 1; + } + _ => {} + } + } + parts.push(&body[start..]); + parts +} + +/// Every `FooRecipe { field: .. }` literal in `src`: the recipe and the +/// fields it initialises. A type named after `struct`, `impl`, `for`, `enum` +/// or a return arrow opens a declaration or a body, not a literal. +fn recipe_literals(src: &str) -> Vec<(String, Vec)> { + let src = strip_comments(src); + let bytes = src.as_bytes(); + let mut out = Vec::new(); + for (open, _) in src.match_indices('{') { + let Some((name, before)) = ident_before(&src, open) else { + continue; + }; + if !name.ends_with("Recipe") || !name.starts_with(char::is_uppercase) { + continue; + } + let prev = before.trim_end(); + if ["struct", "impl", "for", "enum", "->"] + .iter() + .any(|k| prev.ends_with(k)) + { + continue; + } + let Some(close) = matching_close(bytes, open) else { + continue; + }; + let fields = split_top_level(&src[open + 1..close]) + .into_iter() + .filter_map(|part| { + let (field, _) = part.split_once(':')?; + let field = field.trim(); + (!field.is_empty() + && field + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')) + .then(|| field.to_string()) + }) + .collect(); + out.push((name.to_string(), fields)); + } + out +} + +fn sources() -> Vec<(PathBuf, String)> { + let mut out = Vec::new(); + let mut stack = vec![Path::new(env!("CARGO_MANIFEST_DIR")).join("src")]; + while let Some(dir) = stack.pop() { + for entry in std::fs::read_dir(&dir).expect("a readable source directory") { + let path = entry.expect("a directory entry").path(); + if path.is_dir() { + stack.push(path); + } else if path.extension().is_some_and(|e| e == "rs") { + let text = std::fs::read_to_string(&path).expect("readable source"); + out.push((path, text)); + } + } + } + out +} + +/// No recipe literal in this crate initialises a field the page lists as +/// unrendered for that recipe. +#[test] +fn no_recipe_literal_sets_an_unrendered_field() { + let rows = unrendered(); + let mut literals = 0; + let mut seen: BTreeSet = BTreeSet::new(); + let mut offenders = Vec::new(); + for (path, text) in sources() { + for (recipe, fields) in recipe_literals(&text) { + literals += 1; + seen.insert(recipe.clone()); + for field in fields { + if rows.iter().any(|(_, r, f)| *r == recipe && *f == field) { + offenders.push(format!("{}: {recipe}::{field}", path.display())); + } + } + } + } + // The scan finds the literals it is meant to judge, the segmented + // control's included. + assert!(literals >= 10, "only {literals} recipe literals found"); + for recipe in [ + "SegmentedControlRecipe", + "CalendarRecipe", + "SearchFieldRecipe", + "StandardItemRecipe", + ] { + assert!(seen.contains(recipe), "no {recipe} literal found: {seen:?}"); + } + assert!( + offenders.is_empty(), + "unrendered fields set:\n{}", + offenders.join("\n") + ); +} + +/// The scan sees a field initialised in a literal, and none in a +/// declaration or a function body. +#[test] +fn the_scan_finds_fields_in_literals_only() { + let src = "pub struct FooRecipe { pub height: f32 }\n\ + fn make() -> FooRecipe {\n\ + let x: f32 = 1.0;\n\ + FooRecipe {\n\ + // a comment, with a comma: height\n\ + height: dp(x, TargetRole::Target, t),\n\ + width: 2.0,\n\ + ..FooRecipe::for_tokens(t)\n\ + }\n\ + }"; + assert_eq!( + recipe_literals(src), + vec![( + "FooRecipe".to_string(), + vec!["height".to_string(), "width".to_string()] + )] + ); +} + +/// One recipe's fields, by name. +macro_rules! fields { + ($r:expr; $($f:ident),* $(,)?) => { + vec![$((stringify!($f), $r.$f)),*] + }; +} + +type Fields = Vec<(&'static str, f32)>; + +/// Every factory of this crate that builds a recipe the page lists, with the +/// stock recipe's values at the same density: `(recipe, preset, stock)`. +fn factories(tokens: &InputTokens) -> Vec<(&'static str, Fields, Fields)> { + let avatar = |r: AvatarRecipe| fields!(r; size_small, size_medium, size_large, size_x_large); + let banner = |r: BannerRecipe| fields!(r; title_description_gap); + let calendar = |r: CalendarRecipe| fields!(r; cell_size, header_height, outer_padding, section_gap, cell_gap); + let search = |r: SearchFieldRecipe| fields!(r; row_height, input_panel_gap, panel_padding); + let segmented = + |r: SegmentedControlRecipe| fields!(r; height, padding_horizontal, padding_vertical); + let toast = |r: ToastRecipe| fields!(r; title_body_gap, body_actions_gap); + let item = |r: StandardItemRecipe| fields!(r; tree_indent_step, label_subtitle_gap, slot_gap, subtitle_slot_gap); + vec![ + ( + "AvatarRecipe", + avatar(recipes::breeze_avatar_style_for(tokens).recipe), + avatar(AvatarRecipe::for_tokens(tokens)), + ), + ( + "BannerRecipe", + banner(recipes::breeze_banner_style_for(tokens).recipe), + banner(BannerRecipe::for_tokens(tokens)), + ), + ( + "CalendarRecipe", + calendar(recipes::breeze_calendar_style_for(tokens).recipe), + calendar(CalendarRecipe::for_tokens(tokens)), + ), + ( + "SearchFieldRecipe", + search(recipes::breeze_search_field_style_for(tokens).recipe), + search(SearchFieldRecipe::for_tokens(tokens)), + ), + ( + "SegmentedControlRecipe", + segmented(recipes::breeze_segmented_control_style_for(tokens).recipe), + segmented(SegmentedControlRecipe::for_tokens(tokens)), + ), + ( + "ToastRecipe", + toast(recipes::breeze_toast_style_for(tokens).recipe), + toast(ToastRecipe::for_tokens(tokens)), + ), + ( + "StandardItemRecipe", + item(breeze_standard_item_recipe_for( + tokens, + &breeze_typography(), + )), + item(StandardItemRecipe::for_tokens(tokens)), + ), + ] +} + +/// Every listed field of a recipe this crate builds is the stock recipe's, +/// at every density: the factory leaves the unrendered dimension to the +/// widget's own constant, as the page says it must. +#[test] +fn every_factory_leaves_the_unrendered_fields_at_the_stock_projection() { + let rows = unrendered(); + let mut compared = 0; + for density in DENSITIES { + let tokens = InputTokens::for_density(density); + for (recipe, preset, stock) in factories(&tokens) { + let listed = rows + .iter() + .filter(|(_, r, _)| r == recipe) + .map(|(_, _, f)| f.as_str()); + for field in listed { + compared += 1; + let value = |set: &Fields| { + set.iter() + .find(|(f, _)| *f == field) + .map(|(_, v)| *v) + .unwrap_or_else(|| panic!("{recipe}::{field} is not compared here")) + }; + assert_eq!( + value(&preset), + value(&stock), + "{density:?}: {recipe}::{field} is set to a value the widget does not render" + ); + } + } + } + assert!( + compared > 0, + "no listed field of a recipe this crate builds" + ); +} diff --git a/crates/teksilo-theme-kde/tests/fonts.rs b/crates/teksilo-theme-kde/tests/fonts.rs new file mode 100644 index 000000000..617e68d4a --- /dev/null +++ b/crates/teksilo-theme-kde/tests/fonts.rs @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Plasma's faces: the files under `fonts/`, the families the typography +//! names with and without a font feature, and the registrar +//! `bundled-fonts` adds. +//! +//! The file tests run in every build: the files ship in the package +//! whichever features are on. + +use std::path::{Path, PathBuf}; + +use sha2::{Digest, Sha256}; +use teksilo_theme_kde as kde; +use teksilo_theme_kde::typography::{FaceMetrics, HACK_METRICS, NOTO_SANS_METRICS}; + +/// Every file under `fonts/` and the sha256 recorded for it in `NOTICE`, +/// sections 2.6 and 2.7. `NotoSans-Variable.ttf` and `Hack-Regular.ttf` +/// carry the hashes the specification's ledger gives (o.10); every file +/// came out of an upstream archive whose hash it gives too. +const FILES: [(&str, &str); 8] = [ + ( + "NotoSans-Variable.ttf", + "ae80a5e79e4afbf493e9f4684ec4988e6a861b3598102ee48950b4b7a754cdff", + ), + ( + "NotoSans-Italic-Variable.ttf", + "8bcb0ae904c973257ec957ca92559d425469d92e0913d489fe80fbb49c3b6ded", + ), + ( + "noto-sans-LICENSE.txt", + "cee9892f9f0cc8fe882c9e9537ee6a89621d86ee7ceaf70b02e2b2b1c25c061a", + ), + ( + "Hack-Regular.ttf", + "15f55cc0c85a2988d2b4b3a8cdb5d77fdfbaf319e1bb5309d725db9818fb7125", + ), + ( + "Hack-Bold.ttf", + "5bbf531eff7f8a0c2559c9a0656718e2828a012a9b1f60b5f54006d59a4de8d4", + ), + ( + "Hack-Italic.ttf", + "096fb67a2b85f3c866e9cb3e965b27c2c10b977315f4d3d7f095674be35091c1", + ), + ( + "Hack-BoldItalic.ttf", + "64f74a079700b7dfe128551a1e28875d5ba980971e55f5e0f0596e37bdc6a6bc", + ), + ( + "hack-LICENSE.txt", + "1f61bb7c790c59b4b0ecdf304628b94e42ae4c8020094a8c3da73381ab212623", + ), +]; + +fn fonts_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("fonts") +} + +fn read(name: &str) -> Vec { + let path = fonts_dir().join(name); + std::fs::read(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display())) +} + +fn sha256_hex(bytes: &[u8]) -> String { + Sha256::digest(bytes) + .iter() + .map(|b| format!("{b:02x}")) + .collect() +} + +#[test] +fn every_file_is_the_one_recorded() { + for (name, expected) in FILES { + assert_eq!(sha256_hex(&read(name)), expected, "{name}"); + } +} + +#[test] +fn the_fonts_directory_holds_only_the_recorded_files() { + let mut present: Vec = std::fs::read_dir(fonts_dir()) + .expect("the fonts directory") + .map(|e| e.expect("a directory entry").file_name()) + .map(|n| n.to_string_lossy().into_owned()) + .collect(); + present.sort(); + let mut recorded: Vec = FILES.iter().map(|(n, _)| (*n).to_owned()).collect(); + recorded.sort(); + assert_eq!(present, recorded); +} + +/// The table `tag` of a TrueType file: its offset in `font`. +fn table(font: &[u8], tag: &[u8; 4]) -> usize { + let u16_at = |at: usize| u16::from_be_bytes([font[at], font[at + 1]]); + let u32_at = + |at: usize| u32::from_be_bytes([font[at], font[at + 1], font[at + 2], font[at + 3]]); + let tables = usize::from(u16_at(4)); + (0..tables) + .map(|i| 12 + 16 * i) + .find(|&record| &font[record..record + 4] == tag) + .map(|record| u32_at(record + 8) as usize) + .unwrap_or_else(|| panic!("no {} table", String::from_utf8_lossy(tag))) +} + +/// The metrics a line box is computed from, read from the file's `head` +/// and `hhea` tables, and the `hhea` line gap. +fn metrics_of(font: &[u8]) -> (FaceMetrics, i16) { + let i16_at = |at: usize| i16::from_be_bytes([font[at], font[at + 1]]); + let head = table(font, b"head"); + let hhea = table(font, b"hhea"); + let units_per_em = u16::from_be_bytes([font[head + 18], font[head + 19]]); + let ascender = i16_at(hhea + 4); + let descender = i16_at(hhea + 6); + let line_gap = i16_at(hhea + 8); + ( + FaceMetrics { + units_per_em: u32::from(units_per_em), + ascender: u32::try_from(ascender).expect("a positive ascender"), + descender: u32::try_from(-i32::from(descender)).expect("a negative descender"), + }, + line_gap, + ) +} + +/// The line boxes are computed from constants; these are the files the +/// constants describe, upright and italic, regular and bold alike. +#[test] +fn the_line_boxes_are_computed_from_the_bundled_faces_metrics() { + for name in ["NotoSans-Variable.ttf", "NotoSans-Italic-Variable.ttf"] { + assert_eq!(metrics_of(&read(name)), (NOTO_SANS_METRICS, 0), "{name}"); + } + for name in [ + "Hack-Regular.ttf", + "Hack-Bold.ttf", + "Hack-Italic.ttf", + "Hack-BoldItalic.ttf", + ] { + assert_eq!(metrics_of(&read(name)), (HACK_METRICS, 0), "{name}"); + } + // Hack's natural line height, (1901 + 483) / 2048 em, is 1.1640625: + // 15.13 px at the 13 px face, 15 by Qt's rule, the monospace line box. + let natural = f64::from(HACK_METRICS.ascender + HACK_METRICS.descender) + / f64::from(HACK_METRICS.units_per_em); + assert_eq!(natural, 1.1640625); + let mono = kde::light().typography.mono; + assert_eq!(mono.size, 13.0); + assert!((mono.size * mono.line_height - 15.0).abs() < 1e-4); +} + +/// The sizes and line boxes do not depend on the features: only the +/// families do. +#[test] +fn the_sizes_and_line_boxes_are_the_same_in_every_build() { + let t = kde::light().typography; + for (style, size, line) in [ + (&t.body, 13.0, 18.0), + (&t.body_bold, 13.0, 18.0), + (&t.small, 11.0, 15.0), + (&t.small_bold, 11.0, 15.0), + (&t.tiny, 11.0, 15.0), + (&t.mono, 13.0, 15.0), + ] { + assert_eq!(style.size, size); + assert!((style.size * style.line_height - line).abs() < 1e-4); + } +} + +#[cfg(any(feature = "bundled-fonts", feature = "system-fonts"))] +mod with_a_font_feature { + use teksilo_kde_scheme::{KdeColorScheme, KdeDesktop}; + use teksilo_theme_kde::KdeFontRoles; + use teksilo_theme_kde::fonts::{HACK_FAMILY, NOTO_SANS_FAMILY}; + use teksilo_tokens::TargetDensity; + + use super::kde; + + #[test] + fn the_stock_themes_name_noto_sans_and_hack() { + for theme in [kde::light(), kde::dark()] { + let t = &theme.typography; + for style in [&t.body, &t.body_bold, &t.small, &t.small_bold, &t.tiny] { + assert_eq!(style.family, NOTO_SANS_FAMILY); + } + assert_eq!(t.mono.family, HACK_FAMILY); + let roles = theme.extension::().expect("the font roles"); + assert_eq!(roles.menu.family, NOTO_SANS_FAMILY); + assert_eq!(roles.toolbar.family, NOTO_SANS_FAMILY); + } + } + + /// With Plasma's faces named, a desktop at Plasma's default fonts has + /// the stock theme's typography exactly, families included. + #[test] + fn a_desktop_at_plasmas_defaults_has_the_stock_typography() { + let desktop = kde::from_desktop( + &KdeDesktop::new(KdeColorScheme::breeze_light()), + TargetDensity::Compact, + ); + let stock = kde::light(); + assert_eq!(desktop.typography, stock.typography); + assert_eq!( + desktop.extension::(), + stock.extension::() + ); + } +} + +#[cfg(not(any(feature = "bundled-fonts", feature = "system-fonts")))] +mod without_a_font_feature { + use teksilo_tokens::TypographyTokens; + + use super::kde; + + #[test] + fn the_stock_themes_keep_the_bundled_inter_and_jetbrains_mono() { + let base = TypographyTokens::default(); + assert_eq!(base.body.family, "Inter"); + assert_eq!(base.mono.family, "JetBrains Mono"); + for theme in [kde::light(), kde::dark()] { + let t = &theme.typography; + for style in [&t.body, &t.body_bold, &t.small, &t.small_bold, &t.tiny] { + assert_eq!(style.family, base.body.family); + } + assert_eq!(t.mono.family, base.mono.family); + } + } +} + +#[cfg(feature = "bundled-fonts")] +mod bundled { + use teksilo_text::{FontRegistrar, TextFontService}; + use teksilo_theme_kde::fonts::{HACK_FAMILY, NOTO_SANS_FAMILY}; + + use super::kde; + + #[test] + fn the_registrar_registers_both_families_and_defaults_to_noto_sans() { + let mut service = TextFontService::new_without_system_fonts(); + let body = kde::font_registrar() + .register_on_service(&mut service) + .expect("the default face"); + assert_eq!( + service.font_family_name(body).as_deref(), + Some(NOTO_SANS_FAMILY) + ); + let registry = service.font_registry(); + assert_eq!(registry.default_font(), Some(body)); + assert_eq!(registry.default_size_px(), 13.0); + + // Noto Sans: one variable face upright, one italic, answering every + // weight the text styles ask for. + for weight in [400, 600] { + assert_eq!( + registry.query_font(NOTO_SANS_FAMILY, weight, false), + Some(body) + ); + } + let italic = registry + .query_font(NOTO_SANS_FAMILY, 400, true) + .expect("an italic Noto Sans"); + assert_ne!(italic, body); + + // Hack: four static faces, the bold weights going to the bold face. + let hack = |weight, italic| { + registry + .query_font(HACK_FAMILY, weight, italic) + .expect("a Hack face") + }; + let faces = [ + hack(400, false), + hack(700, false), + hack(400, true), + hack(700, true), + ]; + for (i, a) in faces.iter().enumerate() { + for b in &faces[i + 1..] { + assert_ne!(a, b); + } + assert_ne!(*a, body); + assert_eq!(service.font_family_name(*a).as_deref(), Some(HACK_FAMILY)); + } + assert_eq!(hack(600, false), faces[1]); + assert!(service.family_is_monospaced(HACK_FAMILY)); + assert!(!service.family_is_monospaced(NOTO_SANS_FAMILY)); + } +} diff --git a/crates/teksilo-theme-kde/tests/high_contrast.rs b/crates/teksilo-theme-kde/tests/high_contrast.rs new file mode 100644 index 000000000..1e153b91f --- /dev/null +++ b/crates/teksilo-theme-kde/tests/high_contrast.rs @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The Breeze preset's own high-contrast projection. +//! +//! Under the OS "increase contrast" preference every text must reach 7:1 on +//! what it is set on (WCAG SC 1.4.6) and every boundary, focus ring and the +//! accent 4.5:1, for Breeze Light and Dark, each of Plasma's ten accents on +//! both, and an active and an inactive window. `tests/contrast.rs` holds +//! every other scheme it sweeps to the same floors, inactive effects and +//! translucent colours included. +//! +//! The selection fill is the one fill held to its text only. It carries the +//! window's text at 7:1, and the window's text is at 7:1 on the window, so +//! no fill can also reach 4.5:1 on the window: the last tests check that +//! premise and that the fill keeps its text. +//! +//! A hovered field, check box, radio button, switch or slider handle is +//! outlined in a hover or focus decoration in place of its lifted outline, +//! and a spin box's hovered half draws its chevron in the hover decoration, +//! so the decoration is held to the boundary floor too. + +use teksilo_core::styles::{PaintState, Theme}; +use teksilo_theme_kde::scheme::ColorState; +use teksilo_theme_kde::styles::checkbox::box_paint; +use teksilo_theme_kde::styles::indicator::BreezeIndicatorColors; +use teksilo_theme_kde::styles::slider::BreezeSliderColors; +use teksilo_theme_kde::styles::spin_box::BreezeSpinArrowColors; +use teksilo_theme_kde::styles::text_input::BreezeFieldColors; +use teksilo_theme_kde::styles::toggle::switch_paint; +use teksilo_theme_kde::{self as kde, KdePalette, PlasmaAccent}; +use teksilo_tokens::contrast::composite_contrast; +use teksilo_tokens::{Color, ColorTokens}; + +/// `[wcag]` SC 1.4.6 for text, and Teksilo's 4.5:1 for everything else. +const TEXT: f32 = 7.0; +const NON_TEXT: f32 = 4.5; + +fn high_contrast(theme: &Theme, window_active: bool) -> ColorTokens { + theme + .for_paint_state(PaintState { + window_active, + high_contrast: true, + }) + .colors +} + +fn themes() -> Vec<(String, Theme)> { + let mut out = vec![ + ("Breeze Light".to_owned(), kde::light()), + ("Breeze Dark".to_owned(), kde::dark()), + ]; + for accent in PlasmaAccent::ALL { + out.push(( + format!("Breeze Light, {accent}"), + kde::light_with_accent(accent.color()), + )); + out.push(( + format!("Breeze Dark, {accent}"), + kde::dark_with_accent(accent.color()), + )); + } + out +} + +/// Every pair of `c` below its high-contrast floor. +fn failures(c: &ColorTokens) -> Vec { + let surfaces = [ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ("surface_raised", c.surface_raised), + ("surface_container", c.surface_container), + ("surface_container_raised", c.surface_container_raised), + ("surface_sunken", c.surface_sunken), + ("surface_alt_row", c.surface_alt_row), + ("surface_hover", c.surface_hover), + ("surface_pressed", c.surface_pressed), + ]; + let mut pairs: Vec<(String, Color, Color, f32)> = Vec::new(); + for (name, fg) in [ + ("text_primary", c.text_primary), + ("text_secondary", c.text_secondary), + ("text_link", c.text_link), + ("text_link_hover", c.text_link_hover), + ("text_link_visited", c.text_link_visited), + ("text_error", c.text_error), + ("text_warning", c.text_warning), + ("text_success", c.text_success), + ("status_info_fg", c.status_info_fg), + ] { + for (bg_name, bg) in surfaces { + pairs.push((format!("{name} on {bg_name}"), fg, bg, TEXT)); + } + } + for (bg_name, bg) in [ + ("accent", c.accent), + ("accent_hover", c.accent_hover), + ("accent_pressed", c.accent_pressed), + ] { + pairs.push(( + format!("text_on_accent on {bg_name}"), + c.text_on_accent, + bg, + TEXT, + )); + } + for (name, fg, bg) in [ + ( + "text_primary on surface_selected", + c.text_primary, + c.surface_selected, + ), + ( + "selection_text_active on selection_bg_active", + c.selection_text_active, + c.selection_bg_active, + ), + ( + "selection_text_inactive on selection_bg_inactive", + c.selection_text_inactive, + c.selection_bg_inactive, + ), + ("editor_fg on editor_bg", c.editor_fg, c.editor_bg), + ( + "editor_fg on editor_selection_bg", + c.editor_fg, + c.editor_selection_bg, + ), + ( + "editor_gutter_fg on editor_bg", + c.editor_gutter_fg, + c.editor_bg, + ), + ("tooltip_text on tooltip_bg", c.tooltip_text, c.tooltip_bg), + ( + "tooltip_shortcut on tooltip_bg", + c.tooltip_shortcut, + c.tooltip_bg, + ), + ( + "status_error_fg on status_error_bg", + c.status_error_fg, + c.status_error_bg, + ), + ("text_on_error on text_error", c.text_on_error, c.text_error), + ] { + pairs.push((name.to_owned(), fg, bg, TEXT)); + } + for (name, fg) in [ + ("accent", c.accent), + ("focus_ring", c.focus_ring), + ("border_focused", c.border_focused), + ("border_strong", c.border_strong), + ("divider_strong", c.divider_strong), + ("border_error", c.border_error), + ("border_warning", c.border_warning), + ] { + for (bg_name, bg) in [ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ("surface_raised", c.surface_raised), + ] { + pairs.push((format!("{name} on {bg_name}"), fg, bg, NON_TEXT)); + } + } + for (name, fg) in [ + ("scrollbar_thumb", c.scrollbar_thumb), + ("scrollbar_thumb_hover", c.scrollbar_thumb_hover), + ] { + for (bg_name, bg) in [ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ] { + pairs.push((format!("{name} on {bg_name}"), fg, bg, NON_TEXT)); + } + } + pairs + .into_iter() + .filter(|(_, fg, bg, floor)| composite_contrast(*fg, *bg) < *floor) + .map(|(name, fg, bg, floor)| { + format!("{name}: {:.2}:1 < {floor}", composite_contrast(fg, bg)) + }) + .collect() +} + +#[test] +fn every_swatch_meets_the_enhanced_floors_in_both_window_states() { + let mut all = Vec::new(); + for (name, theme) in themes() { + for (state, active) in [("active", true), ("inactive", false)] { + for failure in failures(&high_contrast(&theme, active)) { + all.push(format!("{name}, {state}: {failure}")); + } + } + } + assert!( + all.is_empty(), + "{} failures:\n{}", + all.len(), + all.join("\n") + ); +} + +#[test] +fn secondary_text_is_the_primary_text() { + for (name, theme) in themes() { + for active in [true, false] { + let c = high_contrast(&theme, active); + assert_eq!(c.text_secondary, c.text_primary, "{name}"); + assert_eq!(c.tooltip_shortcut, c.tooltip_text, "{name}"); + assert_eq!(c.editor_gutter_fg, c.editor_fg, "{name}"); + } + } +} + +#[test] +fn the_frame_outline_is_the_text_at_full_frame_contrast() { + for theme in [kde::light(), kde::dark()] { + let c = high_contrast(&theme, true); + assert_eq!(c.border, c.text_primary); + assert_eq!(c.divider, c.text_primary); + } +} + +/// The documented exception to Plasma's black text on the accent: in a +/// light scheme the accent darkens and its text is white. +#[test] +fn a_light_scheme_puts_white_text_on_a_darkened_accent() { + let normal = kde::light(); + let c = high_contrast(&normal, true); + assert_eq!(normal.colors.text_on_accent, Color::BLACK); + assert_eq!(c.text_on_accent, Color::WHITE); + assert!(c.accent.relative_luminance() < normal.colors.accent.relative_luminance()); + let dark = high_contrast(&kde::dark(), true); + assert_eq!(dark.text_on_accent, Color::BLACK); +} + +/// The premise of that exception: no colour lets black text reach 7:1 on +/// it while it reaches 4.5:1 on Breeze Light's window. +#[test] +fn no_accent_carries_black_text_and_clears_a_light_window() { + let window = kde::light().colors.surface_main; + let mut found = None; + for v in 0..=255u8 { + let grey = Color::new( + f32::from(v) / 255.0, + f32::from(v) / 255.0, + f32::from(v) / 255.0, + 1.0, + ); + if composite_contrast(Color::BLACK, grey) >= TEXT + && composite_contrast(grey, window) >= NON_TEXT + { + found = Some(v); + } + } + // Luminance decides both ratios, and a grey reaches every luminance. + assert_eq!(found, None); +} + +#[test] +fn the_high_contrast_tokens_differ_from_the_normal_ones() { + for theme in [kde::light(), kde::dark()] { + assert_ne!(high_contrast(&theme, true), theme.colors); + assert_ne!(high_contrast(&theme, false), high_contrast(&theme, true)); + } +} + +/// The contrast of two relative luminances. +fn luminance_ratio(a: f32, b: f32) -> f32 { + (a.max(b) + 0.05) / (a.min(b) + 0.05) +} + +/// The premise of the high-contrast selection's limitation: for every +/// swatch and window state, no luminance a fill could have puts the +/// primary text at 7:1 on it and the fill at 4.5:1 on the window. Both +/// ratios depend on luminance alone, so a sweep of it covers every colour. +#[test] +fn no_selection_fill_carries_the_text_and_clears_the_window() { + for (name, theme) in themes() { + for active in [true, false] { + let c = high_contrast(&theme, active); + let text = c.text_primary.relative_luminance(); + let window = c.surface_main.relative_luminance(); + assert!(luminance_ratio(text, window) >= TEXT, "{name}"); + let found = (0..=100_000u32) + .map(|i| i as f32 / 100_000.0) + .find(|&fill| { + luminance_ratio(text, fill) >= TEXT && luminance_ratio(fill, window) >= NON_TEXT + }); + assert_eq!(found, None, "{name}, active {active}"); + } + } +} + +/// What the projection does instead: the selection fill keeps the primary +/// and the editor text at 7:1, and stays below 4.5:1 on the window, as the +/// premise above forces. +#[test] +fn the_selection_fill_is_held_to_its_text() { + for (name, theme) in themes() { + for active in [true, false] { + let c = high_contrast(&theme, active); + for fill in [ + c.surface_selected, + c.selection_bg_active, + c.editor_selection_bg, + ] { + assert!(composite_contrast(c.text_primary, fill) >= TEXT, "{name}"); + assert!(composite_contrast(c.editor_fg, fill) >= TEXT, "{name}"); + assert!( + composite_contrast(fill, c.surface_main) < NON_TEXT, + "{name}" + ); + } + } + } + // The figures the colour mapping's documentation gives. + for (theme, normal, high) in [(kde::light(), 2.18, 1.90), (kde::dark(), 3.40, 2.19)] { + let n = &theme.colors; + let h = high_contrast(&theme, true); + let normal_ratio = composite_contrast(n.surface_selected, n.surface_main); + let high_ratio = composite_contrast(h.surface_selected, h.surface_main); + assert!((normal_ratio - normal).abs() < 0.01, "{normal_ratio}"); + assert!((high_ratio - high).abs() < 0.01, "{high_ratio}"); + } +} + +/// The outline a control shows under the pointer, against what it meets. +fn hovered_boundaries(theme: &Theme, window_active: bool) -> Vec<(&'static str, Color, Color)> { + let hc = theme.for_paint_state(PaintState { + window_active, + high_contrast: true, + }); + let c = &hc.colors; + let surfaces = [c.surface_main, c.surface_content, c.surface_raised]; + let mut out = Vec::new(); + let mut against = |name: &'static str, outline: Color, face: Color| { + for bg in surfaces.into_iter().chain([face]) { + out.push((name, outline, bg)); + } + }; + + let field = BreezeFieldColors::resolve(&hc, window_active, true, false); + against( + "hovered field outline", + field.outline_for(false, true, false, None), + field.fill, + ); + + let ind = BreezeIndicatorColors::resolve(&hc, window_active, true, false); + let hovered_box = box_paint(false, true, false, false, &ind); + against( + "hovered box ring", + hovered_box.hover_ring.expect("a hovered box is ringed"), + hovered_box.face, + ); + let hovered_switch = switch_paint(0.0, 1.0, &ind); + against( + "hovered off track outline", + hovered_switch.track_outline, + ind.button, + ); + against( + "hovered handle outline", + hovered_switch.handle_outline, + hovered_switch.handle, + ); + + let slider = BreezeSliderColors::resolve(&hc, window_active, true, false); + against( + "hovered slider handle outline", + slider.handle_outline_for(false, true, false), + slider.handle, + ); + + // A spin box's hovered half draws no fill and no outline: its chevron, + // on the view, is all there is. + let spin = BreezeSpinArrowColors::resolve(&hc, window_active, true, false); + against("hovered spin box arrow", spin.hover, c.surface_content); + out +} + +/// Under high contrast a hovered control keeps a 4.5:1 boundary: the hover +/// or focus decoration that replaces its outline is lifted like the outline. +#[test] +fn a_hovered_control_keeps_a_four_and_a_half_to_one_boundary() { + let mut all = Vec::new(); + for (name, theme) in themes() { + for (state, active) in [("active", true), ("inactive", false)] { + for (what, outline, bg) in hovered_boundaries(&theme, active) { + let ratio = composite_contrast(outline, bg); + if ratio < NON_TEXT { + all.push(format!("{name}, {state}: {what} {ratio:.2}:1 on {bg:?}")); + } + } + } + } + assert!( + all.is_empty(), + "{} failures:\n{}", + all.len(), + all.join("\n") + ); +} + +/// The premise: the scheme's own hover and focus decorations, which Breeze +/// paints there, fall short of 4.5:1 on Breeze Light's high-contrast window. +#[test] +fn the_schemes_hover_decorations_fall_short_on_a_light_window() { + let theme = kde::light(); + let hc = theme.for_paint_state(PaintState { + window_active: true, + high_contrast: true, + }); + let b = hc + .extension::() + .unwrap() + .breeze_colors(ColorState::Active, true); + for decoration in [b.hover, b.focus] { + assert!(composite_contrast(decoration, hc.colors.surface_main) < NON_TEXT); + } + // And the hover colour on the view, where a spin box draws its chevron. + assert!(composite_contrast(b.hover, hc.colors.surface_content) < NON_TEXT); +} + +/// The lift is high contrast's alone: the normal projection paints the +/// scheme's hover colour, as Breeze does. +#[test] +fn the_normal_projection_keeps_the_schemes_hover_colour() { + let theme = kde::light(); + let b = theme + .extension::() + .unwrap() + .breeze_colors(ColorState::Active, false); + let field = BreezeFieldColors::resolve(&theme, true, false, false); + assert_eq!(field.hover, b.hover); + let ind = BreezeIndicatorColors::resolve(&theme, true, false, false); + assert_eq!(ind.focus, b.focus); + let slider = BreezeSliderColors::resolve(&theme, true, false, false); + assert_eq!(slider.hover, b.hover); + let spin = BreezeSpinArrowColors::resolve(&theme, true, false, false); + assert_eq!(spin.hover, b.hover); +} diff --git a/crates/teksilo-theme-kde/tests/mount.rs b/crates/teksilo-theme-kde/tests/mount.rs new file mode 100644 index 000000000..f2ea762a3 --- /dev/null +++ b/crates/teksilo-theme-kde/tests/mount.rs @@ -0,0 +1,3981 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Mount every widget the Breeze preset restyles, in both schemes, render +//! it, and check the colours it paints against Plasma's own: the buttons, +//! the check box, the radio button, the switch, the line edit, the combo +//! box, the spin box, the slider, the menu, the popup frame, the tooltip, +//! the list and tree rows, the table, the tab, the scroll bar and the +//! progress bar, and the widgets whose stock recipe style the preset builds +//! with Breeze's metrics. +//! +//! The unit tests beside each style check its rules. This checks that the +//! styles run: a Tier-3 style is only exercised when its widget is built, +//! and its `make_body` and `paint` reach framework surfaces (derived +//! signals, the binding registry, the paint context's window state) whose +//! preconditions the trait signature does not show. +//! +//! The exact-colour checks read the colours from the render frame, which +//! carries what the style asked for, and compare them with the golden +//! samples of Plasma 6.8 painting the same controls +//! (`tools/kde_golden/vectors/pixels.json`). This file reads that file from +//! the repository, so it is left out of the published package (the +//! `exclude` list in `Cargo.toml`). + +use std::cell::RefCell; +use std::rc::Rc; +use std::time::Duration; + +use serde_json::Value; +use teksilo_canvas::{FillRule, MockTextBackend, Path, RenderFrame, ShapeKind, SizeProposal}; +use teksilo_core::build_context::BuildContext; +use teksilo_core::event::{Key, Modifiers, PointerButton}; +use teksilo_core::overlay::OverlayPlacement; +use teksilo_core::signal::Signal; +use teksilo_core::styles::TabIndicatorPosition; +use teksilo_core::styles::{PopoverStyleConfig, PopoverVariant, Theme}; +use teksilo_core::widget::{LayoutContext, LayoutResponse, Widget}; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_data::{ListModel, TreeModel}; +use teksilo_i18n::lit; +use teksilo_text::text_document::TextDocument; +use teksilo_theme_kde::styles::indicator::BreezeIndicatorColors; +use teksilo_theme_kde::styles::table::BreezeHeaderColors; +use teksilo_theme_kde::{self as kde, KdePalette, PlasmaAccent}; +use teksilo_tokens::{Color, Orientation}; +use teksilo_widgets::primitives::{FixedSize, HStack, IconWidget, TextWidget}; +use teksilo_widgets::rich_text::RichTextEditor; +use teksilo_widgets::scroll_bar::{ScrollBar, ScrollBarOrientation, ScrollBarVariant}; +use teksilo_widgets::tab_widget::{TabBar, TabDelegate, TabId, TabSizing}; +use teksilo_widgets::table_view::{Column, ColumnWidth, SortDirection}; +use teksilo_widgets::{ + Button, ButtonVariant, CheckState, Checkbox, ComboBox, IconButton, IconButtonSize, MenuItem, + MenuList, ProgressBar, RadioButton, Slider, SpinBox, SplitButton, StandardListItem, + StandardTreeItem, TableView, TextInput, Toggle, TooltipWidget, TreeTableView, VStack, +}; + +const PIXELS_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tools/kde_golden/vectors/pixels.json" +)); + +const VARIANTS: [ButtonVariant; 7] = [ + ButtonVariant::Filled, + ButtonVariant::Tinted, + ButtonVariant::Outlined, + ButtonVariant::Plain, + ButtonVariant::Ghost, + ButtonVariant::Link, + ButtonVariant::Destructive, +]; + +const SIZES: [IconButtonSize; 5] = [ + IconButtonSize::Compact, + IconButtonSize::Default, + IconButtonSize::Toolbar, + IconButtonSize::Large, + IconButtonSize::Hero, +]; + +fn themes() -> [(&'static str, Theme); 2] { + [("BreezeLight", kde::light()), ("BreezeDark", kde::dark())] +} + +fn tree_with(theme: Theme) -> WidgetTree { + WidgetTree::new() + .with_theme(theme) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))) +} + +/// Mount `w` under `theme`, lay it out and render it: the number of draw +/// commands, so a caller can assert something was painted. +fn mount(theme: Theme, w: impl Widget + 'static) -> usize { + let mut tree = tree_with(theme); + tree.add(w); + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.render().draw_order.len() +} + +/// A tree holding `w` at its natural size in a row, laid out: the tree and +/// the widget's id. +fn placed(theme: Theme, w: impl Widget + 'static) -> (WidgetTree, WidgetId) { + let mut tree = tree_with(theme); + let row = tree.add(HStack::new().child(w)); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let id = tree.children(row)[0]; + (tree, id) +} + +/// A tree holding `w` at its natural height in a column, laid out: the tree +/// and the widget's id. +fn in_column(theme: Theme, w: impl Widget + 'static) -> (WidgetTree, WidgetId) { + let mut tree = tree_with(theme); + let column = tree.add(VStack::new().child(HStack::new().child(w))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let row = tree.children(column)[0]; + let id = tree.children(row)[0]; + (tree, id) +} + +fn rerender(tree: &mut WidgetTree) -> Rc { + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.render() +} + +fn rgba8(c: [f32; 4]) -> [u8; 4] { + c.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) +} + +/// One painted shape: its colour in 8 bits, its stroke width (0 for a +/// fill), its rectangle, its kind and its top-leading corner radius. +#[derive(Debug, Clone, Copy, PartialEq)] +struct Painted { + color: [u8; 4], + stroke: f32, + screen: [f32; 4], + kind: ShapeKind, + radius: f32, +} + +fn painted(frame: &RenderFrame) -> Vec { + frame + .shapes + .iter() + .map(|s| Painted { + color: rgba8(s.color), + stroke: s.stroke_width, + screen: s.screen, + kind: s.shape, + radius: s.corner_radii[0], + }) + .collect() +} + +fn fills(frame: &RenderFrame) -> Vec<[u8; 4]> { + painted(frame) + .into_iter() + .filter(|p| p.stroke == 0.0) + .map(|p| p.color) + .collect() +} + +fn strokes(frame: &RenderFrame) -> Vec<[u8; 4]> { + painted(frame) + .into_iter() + .filter(|p| p.stroke > 0.0) + .map(|p| p.color) + .collect() +} + +/// Black at 12.5 %, the button shadow, in 8 bits. +const SHADOW: [u8; 4] = [0, 0, 0, 32]; + +fn has_shadow(frame: &RenderFrame) -> bool { + strokes(frame).contains(&SHADOW) +} + +/// The colour Plasma painted at `(x, y)` of the control `id` in `scheme`. +fn sample(scheme: &str, id: &str, x: usize, y: usize) -> [u8; 4] { + let all: Value = serde_json::from_str(PIXELS_JSON).expect("pixels.json parses"); + let record = all["schemes"][scheme]["records"] + .as_array() + .expect("the scheme's records") + .iter() + .find(|r| r["id"] == id) + .unwrap_or_else(|| panic!("{scheme} has no {id}")); + let (line, at) = if let Some(row) = record["rows"].get(y.to_string()) { + (row, x) + } else { + ( + record["cols"] + .get(x.to_string()) + .unwrap_or_else(|| panic!("{id} samples neither row {y} nor column {x}")), + y, + ) + }; + let run = line + .as_array() + .expect("a run-length line") + .iter() + .find(|run| { + let start = run[0].as_u64().unwrap() as usize; + let len = run[1].as_u64().unwrap() as usize; + (start..start + len).contains(&at) + }) + .expect("the line covers the pixel"); + let hex = run[2].as_str().unwrap().trim_start_matches('#'); + let byte = |i: usize| u8::from_str_radix(&hex[i..i + 2], 16).unwrap(); + [byte(0), byte(2), byte(4), byte(6)] +} + +/// `top` composited over the opaque `bottom`, in 8 bits. +fn over(top: [u8; 4], bottom: [u8; 4]) -> [u8; 4] { + let a = f32::from(top[3]) / 255.0; + let ch = |i: usize| (f32::from(top[i]) * a + f32::from(bottom[i]) * (1.0 - a)).round() as u8; + [ch(0), ch(1), ch(2), 255] +} + +/// Whether two colours are within one step a channel: Qt's raster painter +/// rounds an 8-bit blend its own way. +fn near(a: [u8; 4], b: [u8; 4]) -> bool { + a.iter().zip(b).all(|(x, y)| x.abs_diff(y) <= 1) +} + +// ── Every restyled widget mounts and paints ───────────────────────────── + +#[test] +fn buttons_mount_in_every_variant() { + for (_, theme) in themes() { + for variant in VARIANTS { + let drawn = mount(theme.clone(), Button::new(lit!("Save")).variant(variant)); + assert!(drawn > 0, "{variant:?} painted nothing"); + } + } +} + +#[test] +fn icon_buttons_mount_at_every_size() { + for (_, theme) in themes() { + for size in SIZES { + let drawn = mount( + theme.clone(), + IconButton::new(IconWidget::checkmark(12.0)).size(size), + ); + assert!(drawn > 0, "{size:?} painted nothing"); + for on in [false, true] { + let drawn = mount( + theme.clone(), + IconButton::new(IconWidget::checkmark(12.0)) + .size(size) + .toggle(Signal::new(on)), + ); + assert!(drawn > 0, "a {size:?} toggle painted nothing"); + } + } + } +} + +#[test] +fn split_buttons_mount_in_every_variant() { + for (_, theme) in themes() { + for variant in VARIANTS { + let drawn = mount( + theme.clone(), + SplitButton::new() + .item(MenuItem::new(lit!("Run"))) + .item(MenuItem::new(lit!("Debug"))) + .variant(variant), + ); + assert!(drawn > 0, "{variant:?} painted nothing"); + } + } +} + +#[test] +fn indicators_and_fields_mount_in_every_state_and_variant() { + use teksilo_core::styles::{CheckboxVariant, RadioVariant, TextInputVariant}; + for (_, theme) in themes() { + for on in [false, true] { + assert!( + mount( + theme.clone(), + Toggle::new(Signal::new(on)).label(lit!("Wi-Fi")) + ) > 0 + ); + assert!(mount(theme.clone(), Toggle::new(Signal::new(on)).enabled(false)) > 0); + } + for state in [ + CheckState::Unchecked, + CheckState::Checked, + CheckState::Indeterminate, + ] { + for variant in [ + CheckboxVariant::Square, + CheckboxVariant::Rounded, + CheckboxVariant::Circle, + ] { + let drawn = mount( + theme.clone(), + Checkbox::tristate(Signal::new(state)) + .variant(variant) + .label(lit!("Enable")), + ); + assert!(drawn > 0, "{state:?} {variant:?} painted nothing"); + } + } + for variant in [ + RadioVariant::Circle, + RadioVariant::Square, + RadioVariant::Rounded, + ] { + let selected = Signal::new(0_usize); + let drawn = mount( + theme.clone(), + VStack::new() + .child( + RadioButton::new(0, selected.clone()) + .variant(variant) + .label(lit!("One")), + ) + .child( + RadioButton::new(1, selected.clone()) + .variant(variant) + .label(lit!("Two")), + ), + ); + assert!(drawn > 0, "{variant:?} painted nothing"); + } + for variant in [ + TextInputVariant::Outlined, + TextInputVariant::Filled, + TextInputVariant::Underline, + TextInputVariant::Bare, + ] { + let drawn = mount( + theme.clone(), + TextInput::new(Signal::new("hello".to_string())) + .placeholder(lit!("Name")) + .variant(variant), + ); + assert!(drawn > 0, "{variant:?} painted nothing"); + } + } +} + +#[test] +fn combo_boxes_spin_boxes_and_sliders_mount_in_every_state_and_variant() { + use teksilo_core::styles::{ComboBoxVariant, SliderVariant}; + use teksilo_tokens::Orientation; + use teksilo_widgets::SpinButtonLayout; + for (_, theme) in themes() { + for variant in [ + ComboBoxVariant::Outlined, + ComboBoxVariant::Filled, + ComboBoxVariant::Underline, + ComboBoxVariant::Plain, + ] { + for enabled in [true, false] { + let drawn = mount( + theme.clone(), + ComboBox::new(["Item", "Other"], Signal::new(Some("Item".to_string()))) + .variant(variant) + .enabled(enabled), + ); + assert!(drawn > 0, "{variant:?} painted nothing"); + } + } + for layout in [SpinButtonLayout::Stacked, SpinButtonLayout::Hidden] { + for enabled in [true, false] { + let drawn = mount( + theme.clone(), + SpinBox::new(Signal::new(3_i32), 0, 10) + .button_layout(layout) + .enabled(enabled), + ); + assert!(drawn > 0, "{layout:?} painted nothing"); + } + } + for orientation in [Orientation::Horizontal, Orientation::Vertical] { + for (variant, ticks) in [ + (SliderVariant::Continuous, None), + (SliderVariant::Discrete, Some(5)), + ] { + for enabled in [true, false] { + let mut slider = Slider::new(Signal::new(0.4), 0.0, 1.0) + .orientation(orientation) + .variant(variant) + .enabled(enabled); + if let Some(n) = ticks { + slider = slider.tick_count(n); + } + let drawn = mount(theme.clone(), slider); + assert!(drawn > 0, "{orientation:?} {variant:?} painted nothing"); + } + } + } + } +} + +/// The styles read the theme at paint time, so a tree built under Breeze +/// survives a live swap to the other scheme, out to another design language +/// and back. +#[test] +fn a_mounted_tree_survives_a_theme_swap() { + let mut tree = tree_with(kde::light()); + tree.add( + VStack::new() + .child(Button::new(lit!("Save")).variant(ButtonVariant::Filled)) + .child(Button::new(lit!("Cancel"))) + .child(IconButton::new(IconWidget::checkmark(12.0)).toggle(Signal::new(true))) + .child(SplitButton::new().item(MenuItem::new(lit!("Run")))) + .child(Toggle::new(Signal::new(true))) + .child(Checkbox::new(Signal::new(true)).label(lit!("On"))) + .child(RadioButton::new(0, Signal::new(0_usize)).label(lit!("One"))) + .child(TextInput::new(Signal::new("text".to_string()))) + .child(ComboBox::new( + ["A", "B"], + Signal::new(Some("A".to_string())), + )) + .child(SpinBox::new(Signal::new(3_i32), 0, 10)) + .child(Slider::new(Signal::new(0.4), 0.0, 1.0).tick_count(5)), + ); + for theme in [ + kde::light(), + kde::dark(), + teksilo_core::presets::intui::light(), + kde::light(), + ] { + tree.set_theme(theme); + let frame = rerender(&mut tree); + assert!(!frame.draw_order.is_empty()); + } +} + +/// The neutral button colours come from `KdePalette`; without it the styles +/// paint from the theme's roles rather than panicking or painting nothing. +#[test] +fn the_styles_degrade_without_the_palette() { + let mut bare = kde::light(); + bare.extensions.remove::(); + assert!(bare.extension::().is_none()); + for variant in VARIANTS { + assert!(mount(bare.clone(), Button::new(lit!("Save")).variant(variant)) > 0); + } + assert!(mount(bare.clone(), IconButton::new(IconWidget::checkmark(12.0))) > 0); + assert!( + mount( + bare.clone(), + SplitButton::new().item(MenuItem::new(lit!("Run"))) + ) > 0 + ); + assert!(mount(bare.clone(), Toggle::new(Signal::new(true))) > 0); + assert!(mount(bare.clone(), Checkbox::new(Signal::new(true))) > 0); + assert!(mount(bare.clone(), RadioButton::new(0, Signal::new(0_usize))) > 0); + assert!(mount(bare.clone(), TextInput::new(Signal::new(String::new()))) > 0); + assert!( + mount( + bare.clone(), + ComboBox::new(["A", "B"], Signal::new(Some("A".to_string()))) + ) > 0 + ); + assert!(mount(bare.clone(), SpinBox::new(Signal::new(3_i32), 0, 10)) > 0); + assert!( + mount( + bare.clone(), + Slider::new(Signal::new(0.5), 0.0, 1.0).tick_count(3) + ) > 0 + ); + // The push button still has a face and an outline, from the roles. + let (mut tree, _) = placed(bare.clone(), Button::new(lit!("Save"))); + let frame = rerender(&mut tree); + assert!(fills(&frame).contains(&rgba8(bare.colors.surface_content.to_array()))); + assert!(strokes(&frame).contains(&rgba8(bare.colors.border.to_array()))); +} + +#[test] +fn every_swatch_mounts() { + for accent in PlasmaAccent::ALL { + for theme in [ + kde::light_with_accent(accent.color()), + kde::dark_with_accent(accent.color()), + ] { + let drawn = mount( + theme, + VStack::new() + .child(Button::new(lit!("Save")).variant(ButtonVariant::Filled)) + .child(Button::new(lit!("Cancel"))) + .child(IconButton::new(IconWidget::checkmark(12.0))) + .child( + SplitButton::new() + .item(MenuItem::new(lit!("Run"))) + .variant(ButtonVariant::Filled), + ) + .child(Toggle::new(Signal::new(true))) + .child(Checkbox::new(Signal::new(true)).label(lit!("On"))) + .child(RadioButton::new(0, Signal::new(0_usize)).label(lit!("One"))) + .child(TextInput::new(Signal::new(String::new()))) + .child(ComboBox::new( + ["A", "B"], + Signal::new(Some("A".to_string())), + )) + .child(SpinBox::new(Signal::new(3_i32), 0, 10)) + .child(Slider::new(Signal::new(0.5), 0.0, 1.0)), + ); + assert!(drawn > 0, "{accent:?} painted nothing"); + } + } +} + +/// The widgets whose Breeze styling is the stock recipe style built with +/// Breeze's metrics ([`kde::styles::recipes`]): each mounts and paints in +/// both schemes and without the palette. A recipe field that stops existing +/// is a compile error, but a nonsensical dimension still compiles and can +/// make a widget paint nothing. +#[test] +fn recipe_styled_widgets_mount() { + use teksilo_widgets::splitter::{Splitter, SplitterModel}; + use teksilo_widgets::{ + Avatar, Badge, Banner, Calendar, Card, Link, ListView, ModalContainer, Panel, SearchField, + SegmentedControl, + }; + let leaf = || FixedSize::new().width(120.0).height(40.0); + let mut bare = kde::light(); + bare.extensions.remove::(); + let mut all: Vec<(&str, Theme)> = themes().into_iter().collect(); + all.push(("no palette", bare)); + for (scheme, theme) in all { + for (name, drawn) in [ + ("card", mount(theme.clone(), Card::new().content(leaf()))), + ("panel", mount(theme.clone(), Panel::new().child(leaf()))), + ("dialog", mount(theme.clone(), ModalContainer::new(leaf()))), + ( + "banner", + mount( + theme.clone(), + Banner::warning(lit!("Low disk")).description(lit!("Free some space")), + ), + ), + ("link", mount(theme.clone(), Link::new(lit!("Docs")))), + ("badge", mount(theme.clone(), Badge::new(lit!("9")))), + ( + "calendar", + mount(theme.clone(), Calendar::single(Signal::new(None))), + ), + ( + "search_field", + mount(theme.clone(), SearchField::new(Signal::new(String::new()))), + ), + ( + "avatar", + mount(theme.clone(), Avatar::with_name(lit!("Jane Doe"))), + ), + ( + "segmented_control", + mount( + theme.clone(), + SegmentedControl::indexed(Signal::new(0_usize)) + .segment(lit!("One")) + .segment(lit!("Two")), + ), + ), + ( + "splitter", + mount( + theme.clone(), + Splitter::new(SplitterModel::new(2, Orientation::Horizontal)) + .pane(leaf()) + .pane(leaf()), + ), + ), + ( + "list_container", + mount( + theme.clone(), + ListView::new(ListModel::from_vec(vec!["A", "B"]), |_, item, _| { + Box::new(StandardListItem::new(lit!(*item))) + }), + ), + ), + ("snackbar", snackbar_drawn(theme.clone())), + ("toast", mount(theme.clone(), toast_surface())), + ] { + assert!(drawn > 0, "{scheme}: {name} painted nothing"); + } + } +} + +/// A toast's surface as the toast host builds it for one entry: an +/// information toast titled "Saved", with a body and a close button. +fn toast_surface() -> teksilo_widgets::toast::ToastSurface { + use teksilo_widgets::toast::surface::ToastSurfaceData; + use teksilo_widgets::toast::{ToastInstallOptions, ToastRegistry, ToastSeverity}; + let data = ToastSurfaceData { + entry_id: 1, + severity: ToastSeverity::Info, + priority: teksilo_core::styles::ToastPriority::Normal, + title: lit!("Saved"), + body: Some(lit!("The file is on disk")), + announcement: None, + actions: Rc::new(Vec::new()), + show_close_button: true, + on_click: None, + style_override: None, + body_state: Signal::new(0), + }; + teksilo_widgets::toast::ToastSurface::new( + data, + None, + ToastRegistry::new(ToastInstallOptions::default()), + false, + ) +} + +/// Open a snackbar under `theme` through its trigger and render it: the +/// number of shapes painted at the bounds of its surface's frame, which the +/// snackbar style builds. +fn snackbar_drawn(theme: Theme) -> usize { + use teksilo_core::event::WidgetEvent; + use teksilo_widgets::Snackbar; + let mut tree = tree_with(theme); + tree.add(Snackbar::new(lit!("Show")).content(TextWidget::new(lit!("File deleted")))); + let proposal = SizeProposal::exact(400.0, 300.0); + tree.layout(proposal); + let trigger = tree.find_by_label("Show").expect("the snackbar's trigger"); + tree.dispatch_event(WidgetEvent::AccessAction { + action: teksilo_core::accesskit::Action::Click, + target: Some(trigger), + target_node: teksilo_core::accessibility::root_node_id(), + data: None, + }); + // Past the fade-in, so the surface paints at full opacity. + tree.advance_time(Duration::from_secs(1)); + tree.layout(proposal); + let frame = tree.render(); + let surfaces: Vec = tree + .roots() + .into_iter() + .flat_map(|root| nodes_named(&tree, root, "SnackbarFrame")) + .collect(); + assert_eq!(surfaces.len(), 1, "the snackbar is open"); + let at = tree.bounds(surfaces[0]).to_array(); + frame.shapes.iter().filter(|s| s.screen == at).count() +} + +/// A badge rounds at the 5 px frame radius rather than as a pill: Breeze +/// draws no pill. +#[test] +fn a_badge_rounds_at_the_frame_radius() { + use teksilo_widgets::Badge; + for (scheme, theme) in themes() { + let (mut tree, _) = placed(theme, Badge::new(lit!("12"))); + let frame = rerender(&mut tree); + assert!( + frame + .shapes + .iter() + .any(|s| s.stroke_width == 0.0 && s.corner_radii == [5.0; 4]), + "{scheme}: no fill at the frame radius" + ); + } +} + +// ── Breeze's colours, checked in the render frame ─────────────────────── + +/// A push button at rest: Plasma's button face and outline, and the shadow, +/// black at 12.5 %, which over the window is the line Plasma paints below +/// the button. +#[test] +fn the_push_button_paints_breezes_face_outline_and_shadow() { + for (scheme, theme) in themes() { + let window = rgba8(theme.colors.surface_main.to_array()); + let (mut tree, id) = placed(theme, Button::new(lit!("OK"))); + let frame = rerender(&mut tree); + assert!( + fills(&frame).contains(&sample(scheme, "push_button.normal", 42, 17)), + "{scheme}: no face {:?} in {:?}", + sample(scheme, "push_button.normal", 42, 17), + fills(&frame) + ); + assert!( + strokes(&frame).contains(&sample(scheme, "push_button.normal", 1, 17)), + "{scheme}: no outline in {:?}", + strokes(&frame) + ); + assert!(has_shadow(&frame), "{scheme}: no shadow"); + assert!(near( + over(SHADOW, window), + sample(scheme, "push_button.normal", 42, 33) + )); + // The outline is stroked on the node inset by 1.5 with radius 4.5, + // and the shadow is that outline one pixel lower. + let b = tree.bounds(id); + let outline = painted(&frame) + .into_iter() + .find(|p| p.color == sample(scheme, "push_button.normal", 1, 17) && p.stroke > 0.0) + .unwrap(); + assert_eq!( + outline.screen, + [b.x + 1.5, b.y + 1.5, b.width - 3.0, b.height - 3.0] + ); + let shadow = painted(&frame) + .into_iter() + .find(|p| p.color == SHADOW) + .unwrap(); + assert_eq!(shadow.screen[1], outline.screen[1] + 1.0); + assert_eq!(shadow.screen[0], outline.screen[0]); + } +} + +/// The default button's fill is the button colour 20 % of the way to the +/// highlight and its outline half-way from the highlight to the button +/// outline, as Plasma paints them. +#[test] +fn the_default_button_blends_the_button_toward_the_highlight() { + for (scheme, theme) in themes() { + let (mut tree, _) = placed( + theme.clone(), + Button::new(lit!("OK")).variant(ButtonVariant::Filled), + ); + let frame = rerender(&mut tree); + let face = sample(scheme, "push_button.default", 42, 17); + let outline = sample(scheme, "push_button.default", 1, 17); + assert!( + fills(&frame).contains(&face), + "{scheme}: {:?}", + fills(&frame) + ); + assert!(strokes(&frame).contains(&outline), "{scheme}"); + assert!(has_shadow(&frame), "{scheme}: the default button casts one"); + // Its label is the primary text, not the on-accent one. + let text = rgba8(theme.colors.text_primary.to_array()); + assert!( + frame.glyphs.iter().all(|g| rgba8(g.color) == text), + "{scheme}: the default button's label is not the primary text" + ); + + // In an inactive window it is a push button, and casts no shadow. + tree.set_window_active(false); + let frame = rerender(&mut tree); + assert!(!fills(&frame).contains(&face), "{scheme}"); + assert!(fills(&frame).contains(&sample(scheme, "push_button.normal", 42, 17))); + assert!(!has_shadow(&frame), "{scheme}"); + } +} + +/// Under the pointer the outline turns to the highlight, and the face stays. +#[test] +fn a_hovered_button_is_outlined_in_the_highlight() { + for (scheme, theme) in themes() { + let highlight = sample(scheme, "push_button.hover", 1, 17); + assert_eq!(highlight, rgba8(theme.colors.accent.to_array())); + let (mut tree, id) = placed(theme, Button::new(lit!("OK"))); + let frame = rerender(&mut tree); + assert!(!strokes(&frame).contains(&highlight), "{scheme}: at rest"); + + tree.pointer_move(tree.bounds(id).center()); + let frame = rerender(&mut tree); + assert!(strokes(&frame).contains(&highlight), "{scheme}: hovered"); + assert!(fills(&frame).contains(&sample(scheme, "push_button.hover", 42, 17))); + assert!( + has_shadow(&frame), + "{scheme}: a hovered button keeps its shadow" + ); + } +} + +/// Pressing fills the button 30 % toward the highlight and drops the +/// shadow. +#[test] +fn a_pressed_button_casts_no_shadow() { + for (scheme, theme) in themes() { + let (mut tree, id) = placed(theme, Button::new(lit!("OK"))); + let center = tree.bounds(id).center(); + tree.pointer_move(center); + tree.pointer_down_button(center, PointerButton::Primary); + let frame = rerender(&mut tree); + assert!( + fills(&frame).contains(&sample(scheme, "push_button.pressed", 42, 17)), + "{scheme}: {:?}", + fills(&frame) + ); + assert!(strokes(&frame).contains(&sample(scheme, "push_button.pressed", 1, 17))); + assert!( + !has_shadow(&frame), + "{scheme}: a pressed button cast a shadow" + ); + tree.pointer_up_button(center, PointerButton::Primary); + } +} + +/// The shadow is an active window's only. +#[test] +fn an_inactive_window_drops_the_shadow() { + for (scheme, theme) in themes() { + let (mut tree, _) = placed(theme, Button::new(lit!("OK"))); + assert!(has_shadow(&rerender(&mut tree)), "{scheme}"); + tree.set_window_active(false); + let frame = rerender(&mut tree); + assert!(!has_shadow(&frame), "{scheme}: inactive"); + // The rest is Plasma's inactive push button. + assert!(fills(&frame).contains(&sample(scheme, "push_button.inactive_window", 42, 17))); + tree.set_window_active(true); + assert!(has_shadow(&rerender(&mut tree)), "{scheme}: active again"); + } +} + +/// A flat button paints nothing at rest, never a shadow, and a highlight +/// outline with no fill under the pointer. +#[test] +fn a_flat_button_paints_nothing_until_hovered_and_never_a_shadow() { + for (scheme, theme) in themes() { + let (mut tree, id) = placed(theme, Button::new(lit!("OK")).variant(ButtonVariant::Ghost)); + let frame = rerender(&mut tree); + assert!(frame.shapes.is_empty(), "{scheme}: {:?}", painted(&frame)); + tree.pointer_move(tree.bounds(id).center()); + let frame = rerender(&mut tree); + assert!( + fills(&frame).is_empty(), + "{scheme}: a hovered flat button has no face" + ); + assert!(strokes(&frame).contains(&sample(scheme, "push_button.flat_hover", 1, 17))); + assert!(!has_shadow(&frame), "{scheme}"); + } +} + +/// An `IconButton` is the auto-raise tool button: invisible at rest, the +/// highlight outline under the pointer, and a toggle that is on fills with +/// the button text at 12.5 % in the plain outline, as Plasma paints a +/// checked tool button. +#[test] +fn an_icon_button_is_breezes_tool_button() { + for (scheme, theme) in themes() { + let window = rgba8(theme.colors.surface_main.to_array()); + let (mut tree, id) = placed(theme.clone(), IconButton::new(IconWidget::checkmark(12.0))); + let frame = rerender(&mut tree); + assert!( + frame.shapes.is_empty(), + "{scheme}: an idle tool button painted {:?}", + painted(&frame) + ); + tree.pointer_move(tree.bounds(id).center()); + let frame = rerender(&mut tree); + assert!(strokes(&frame).contains(&sample(scheme, "tool_button.hover", 1, 14))); + assert!(!has_shadow(&frame)); + + let (mut tree, _) = placed( + theme.clone(), + IconButton::new(IconWidget::checkmark(12.0)).toggle(Signal::new(true)), + ); + let frame = rerender(&mut tree); + let checked = painted(&frame) + .into_iter() + .find(|p| p.stroke == 0.0 && p.color[3] == 32) + .unwrap_or_else(|| panic!("{scheme}: no checked fill in {:?}", painted(&frame))); + assert!(near( + over(checked.color, window), + sample(scheme, "tool_button.checked", 14, 14) + )); + assert!(strokes(&frame).contains(&sample(scheme, "tool_button.checked", 1, 14))); + assert!(!has_shadow(&frame)); + } +} + +/// A split button is framed as a push button, its filled variants in the +/// accent so their on-accent label reads. +#[test] +fn a_split_button_is_framed_as_a_push_button() { + for (scheme, theme) in themes() { + let (mut tree, _) = placed( + theme.clone(), + SplitButton::new().item(MenuItem::new(lit!("Run"))), + ); + let frame = rerender(&mut tree); + assert!(fills(&frame).contains(&sample(scheme, "push_button.normal", 42, 17))); + assert!(strokes(&frame).contains(&sample(scheme, "push_button.normal", 1, 17))); + assert!(has_shadow(&frame)); + + let (mut tree, _) = placed( + theme.clone(), + SplitButton::new() + .item(MenuItem::new(lit!("Run"))) + .variant(ButtonVariant::Filled), + ); + let frame = rerender(&mut tree); + assert!(fills(&frame).contains(&rgba8(theme.colors.accent.to_array()))); + let on_accent: Color = theme.colors.text_on_accent; + assert!( + frame + .glyphs + .iter() + .any(|g| rgba8(g.color) == rgba8(on_accent.to_array())), + "{scheme}: the filled split button's label is not the on-accent text" + ); + } +} + +/// A filled split button's frame covers exactly what a push button's does: +/// its face and its outline, in the accent, lie on the same rectangles, so +/// the outline hides the shadow's sides and meets the shadow line below. +#[test] +fn a_filled_split_button_frame_has_the_push_buttons_bounds() { + // Each shape's screen rectangle relative to the node. + let local = |p: Painted, b: teksilo_canvas::Rect| { + [ + p.screen[0] - b.x, + p.screen[1] - b.y, + p.screen[2], + p.screen[3], + ] + }; + for (scheme, theme) in themes() { + let (mut tree, id) = placed( + theme.clone(), + SplitButton::new().item(MenuItem::new(lit!("Run"))), + ); + let frame = rerender(&mut tree); + let b = tree.bounds(id); + let face = sample(scheme, "push_button.normal", 42, 17); + let outline = sample(scheme, "push_button.normal", 1, 17); + let push = painted(&frame); + let push_face = *push + .iter() + .find(|p| p.color == face && p.stroke == 0.0) + .unwrap(); + let push_outline = *push + .iter() + .find(|p| p.color == outline && p.stroke > 0.0) + .unwrap(); + + let (mut tree, id) = placed( + theme.clone(), + SplitButton::new() + .item(MenuItem::new(lit!("Run"))) + .variant(ButtonVariant::Filled), + ); + let frame = rerender(&mut tree); + let fb = tree.bounds(id); + assert_eq!((fb.width, fb.height), (b.width, b.height), "{scheme}"); + let accent = rgba8(theme.colors.accent.to_array()); + let filled = painted(&frame); + let accent_face = *filled + .iter() + .find(|p| p.color == accent && p.stroke == 0.0) + .unwrap_or_else(|| panic!("{scheme}: no accent face in {filled:?}")); + let accent_outline = *filled + .iter() + .find(|p| p.color == accent && p.stroke > 0.0) + .unwrap_or_else(|| panic!("{scheme}: no accent outline in {filled:?}")); + assert_eq!(accent_outline.stroke, push_outline.stroke, "{scheme}"); + assert_eq!(local(accent_face, fb), local(push_face, b), "{scheme}"); + assert_eq!( + local(accent_outline, fb), + local(push_outline, b), + "{scheme}" + ); + // The shadow is the outline one pixel lower, as under a push button. + let shadow = *filled.iter().find(|p| p.color == SHADOW).unwrap(); + assert_eq!(shadow.screen[0], accent_outline.screen[0], "{scheme}"); + assert_eq!(shadow.screen[2], accent_outline.screen[2], "{scheme}"); + assert_eq!(shadow.screen[1], accent_outline.screen[1] + 1.0, "{scheme}"); + } +} + +/// Keyboard focus adds the 2 px focus frame, centred on the node edge, in +/// the theme's focus colour. +#[test] +fn keyboard_focus_draws_the_focus_frame() { + for (scheme, theme) in themes() { + let ring = rgba8(theme.colors.focus_ring.to_array()); + let mut tree = tree_with(theme); + let row = tree.add( + HStack::new() + .child(Button::new(lit!("One"))) + .child(Button::new(lit!("Two"))), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut tree); + let first = tree.bounds(tree.children(row)[0]); + let focus = painted(&frame) + .into_iter() + .find(|p| p.color == ring && p.stroke == 2.0) + .unwrap_or_else(|| panic!("{scheme}: no focus frame in {:?}", painted(&frame))); + assert_eq!(focus.screen, [first.x, first.y, first.width, first.height]); + } +} + +// ── Check boxes, radio buttons, switches and fields ───────────────────── + +/// The 15 x 15 shapes a painted box or disc lies on, filled or stroked. +fn box_shapes(frame: &RenderFrame) -> Vec { + painted(frame) + .into_iter() + .filter(|p| (p.screen[2] - 15.0).abs() < 0.01 && (p.screen[3] - 15.0).abs() < 0.01) + .collect() +} + +/// The centre of the painted box of the one indicator in `frame`. +fn box_centre(frame: &RenderFrame) -> teksilo_canvas::Point { + let b = box_shapes(frame) + .into_iter() + .next() + .expect("a 15 px box is painted"); + teksilo_canvas::Point::new(b.screen[0] + 7.5, b.screen[1] + 7.5) +} + +/// An unchecked box: Plasma's button-coloured face on the stroked 15 px +/// box at 2.5 inside the indicator, outlined in the lifted outline (D6). +#[test] +fn an_unchecked_box_is_breezes_face_in_the_lifted_outline() { + for (scheme, theme) in themes() { + let (mut tree, id) = in_column( + theme.clone(), + Checkbox::new(Signal::new(false)).label(lit!("Check")), + ); + let frame = rerender(&mut tree); + let shapes = box_shapes(&frame); + let face = sample(scheme, "check_box.off", 8, 8); + assert!( + shapes.iter().any(|p| p.stroke == 0.0 && p.color == face), + "{scheme}: no face {face:?} in {shapes:?}" + ); + let outline = rgba8(theme.colors.border_strong.to_array()); + let stroke = shapes + .iter() + .find(|p| p.stroke > 0.0) + .unwrap_or_else(|| panic!("{scheme}: no outline")); + assert_eq!(stroke.color, outline, "{scheme}"); + assert_eq!(stroke.stroke, 1.0); + // The indicator hugs the node's leading edge, 2 px down in the 24 px + // row: the box at 2.5 in it. + let b = tree.bounds(id); + assert_eq!(b.height, 24.0, "{scheme}: D8"); + assert_eq!( + [stroke.screen[0] - b.x, stroke.screen[1] - b.y], + [2.5, 4.5], + "{scheme}" + ); + // Plasma's own outline is the separator, which D6 lifts. + let plasma = sample(scheme, "check_box.off", 2, 8); + assert_ne!(plasma, outline, "{scheme}: the premise of D6"); + } +} + +/// A checked box: the highlight outline, the highlight at 30 % over the +/// face, and the check in the view's text, drawn with a 2 px pen and mitred +/// joins. +#[test] +fn a_checked_box_is_washed_in_the_highlight_and_marked_in_the_text() { + use teksilo_canvas::LineJoin; + for (scheme, theme) in themes() { + let (mut tree, id) = in_column( + theme.clone(), + Checkbox::new(Signal::new(true)).label(lit!("Check")), + ); + let frame = rerender(&mut tree); + let shapes = box_shapes(&frame); + // The wash is painted composited over the face, as Plasma paints + // it: one opaque fill. + let fills: Vec<_> = shapes.iter().filter(|p| p.stroke == 0.0).collect(); + assert_eq!(fills.len(), 1, "{scheme}: {shapes:?}"); + assert!( + near(fills[0].color, sample(scheme, "check_box.on", 8, 8)), + "{scheme}: {:?}", + fills[0].color + ); + // The outline is laid twice. + let highlight = sample(scheme, "check_box.on", 2, 8); + assert_eq!( + shapes + .iter() + .filter(|p| p.stroke > 0.0 && p.color == highlight) + .count(), + 2, + "{scheme}: {shapes:?}" + ); + let mark = frame + .paths + .iter() + .find(|p| p.stroke_style.width > 0.0) + .unwrap_or_else(|| panic!("{scheme}: no check mark")); + assert_eq!( + rgba8(mark.color), + sample(scheme, "check_box.on", 13, 8), + "{scheme}" + ); + assert_eq!(mark.stroke_style.width, 2.0); + assert_eq!(mark.stroke_style.line_join, LineJoin::Miter); + // The mark's three points, in the indicator. + let b = tree.bounds(id); + let points: Vec<[f32; 2]> = + mark.path + .commands() + .iter() + .filter_map(|c| match c { + teksilo_canvas::PathCommand::MoveTo(p) + | teksilo_canvas::PathCommand::LineTo(p) => Some([p.x - b.x, p.y - b.y]), + _ => None, + }) + .collect(); + assert_eq!( + points, + vec![[6.0, 12.0], [9.0, 15.0], [14.5, 9.5]], + "{scheme}: (6, 10), (9, 13), (14.5, 7.5) of an indicator 2 px down" + ); + } +} + +/// A partial box: three 2 px squares of the view's text, 4 px apart. +#[test] +fn a_partial_box_shows_three_squares() { + for (scheme, theme) in themes() { + let (mut tree, _) = placed( + theme, + Checkbox::tristate(Signal::new(CheckState::Indeterminate)).label(lit!("Some")), + ); + let frame = rerender(&mut tree); + let text = sample(scheme, "check_box.partial", 5, 10); + let squares: Vec<[f32; 4]> = frame + .decorations + .iter() + .filter(|d| rgba8(d.color) == text) + .map(|d| d.rect) + .collect(); + assert_eq!(squares.len(), 3, "{scheme}: {squares:?}"); + for s in &squares { + assert_eq!((s[2], s[3]), (2.0, 2.0)); + } + assert_eq!(squares[1][0] - squares[0][0], 4.0); + assert_eq!(squares[2][0] - squares[1][0], 4.0); + } +} + +/// Under the pointer the box gains a 1 px ring of the focus colour; pressed, +/// its face darkens to Plasma's pressed face. +#[test] +fn a_hovered_box_rings_and_a_pressed_one_darkens() { + for (scheme, theme) in themes() { + let (mut tree, _) = placed( + theme, + Checkbox::new(Signal::new(false)).label(lit!("Check")), + ); + let frame = rerender(&mut tree); + let ring = sample(scheme, "check_box.hover_off", 2, 8); + assert!(!strokes(&frame).contains(&ring), "{scheme}: at rest"); + let centre = box_centre(&frame); + tree.pointer_move(centre); + let frame = rerender(&mut tree); + assert!( + box_shapes(&frame) + .iter() + .any(|p| p.stroke > 0.0 && p.color == ring), + "{scheme}: no hover ring" + ); + tree.pointer_down_button(centre, PointerButton::Primary); + let frame = rerender(&mut tree); + let pressed = sample(scheme, "check_box.pressed_off", 8, 8); + assert!( + box_shapes(&frame) + .iter() + .any(|p| p.stroke == 0.0 && near(p.color, pressed)), + "{scheme}: no pressed face {pressed:?} in {:?}", + box_shapes(&frame) + ); + tree.pointer_up_button(centre, PointerButton::Primary); + } +} + +/// A checked box in a backdrop window is outlined in the scheme's muted +/// highlight, not the active one. +#[test] +fn a_checked_box_in_a_backdrop_window_uses_the_muted_highlight() { + for (scheme, theme) in themes() { + let active = rgba8(theme.colors.accent.to_array()); + let (mut tree, _) = placed( + theme.clone(), + Checkbox::new(Signal::new(true)).label(lit!("Check")), + ); + tree.set_window_active(false); + let frame = rerender(&mut tree); + let muted = theme.for_paint_state(teksilo_core::styles::PaintState { + window_active: false, + high_contrast: false, + }); + let muted = rgba8(muted.colors.accent.to_array()); + assert_ne!(muted, active, "{scheme}: premise"); + let outlines: Vec<[u8; 4]> = box_shapes(&frame) + .into_iter() + .filter(|p| p.stroke > 0.0) + .map(|p| p.color) + .collect(); + assert!(outlines.contains(&muted), "{scheme}: {outlines:?}"); + assert!(!outlines.contains(&active), "{scheme}"); + } +} + +/// Keyboard focus draws the 2 px focus frame outside the 16 px box. +#[test] +fn keyboard_focus_frames_the_box() { + for (scheme, theme) in themes() { + let ring = rgba8(theme.colors.focus_ring.to_array()); + let (mut tree, id) = in_column(theme, Checkbox::new(Signal::new(false)).label(lit!("A"))); + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut tree); + let focus = painted(&frame) + .into_iter() + .find(|p| p.color == ring && p.stroke == 2.0) + .unwrap_or_else(|| panic!("{scheme}: no focus frame in {:?}", painted(&frame))); + let b = tree.bounds(id); + // The indicator at (0, 2): the ring's centre line 1 px outside the + // box at (2, 2), so at (1, 3), 18 px wide. + assert_eq!( + [ + focus.screen[0] - b.x, + focus.screen[1] - b.y, + focus.screen[2], + focus.screen[3] + ], + [1.0, 3.0, 18.0, 18.0], + "{scheme}" + ); + } +} + +/// A radio button: the check box's face and lifted outline on a disc, and, +/// selected, the highlight outline, the wash and a 6 px dot of the view's +/// text. +#[test] +fn a_radio_button_is_breezes_disc_and_dot() { + for (scheme, theme) in themes() { + let selected = Signal::new(1_usize); + let (mut tree, _) = placed( + theme.clone(), + RadioButton::new(0, selected.clone()).label(lit!("One")), + ); + let frame = rerender(&mut tree); + let shapes = box_shapes(&frame); + assert!( + shapes + .iter() + .any(|p| p.stroke == 0.0 && p.color == sample(scheme, "radio_button.off", 10, 10)), + "{scheme}: no face in {shapes:?}" + ); + assert!( + shapes + .iter() + .any(|p| p.stroke > 0.0 && p.color == rgba8(theme.colors.border_strong.to_array())), + "{scheme}: no lifted outline" + ); + + let (mut tree, _) = placed( + theme.clone(), + RadioButton::new(0, Signal::new(0_usize)).label(lit!("One")), + ); + let frame = rerender(&mut tree); + let shapes = box_shapes(&frame); + let fills: Vec<_> = shapes.iter().filter(|p| p.stroke == 0.0).collect(); + assert_eq!( + fills.len(), + 1, + "{scheme}: the wash over the face is one fill" + ); + assert!(near( + fills[0].color, + sample(scheme, "radio_button.on", 5, 10) + )); + let highlight = sample(scheme, "radio_button.on", 2, 10); + assert_eq!( + shapes + .iter() + .filter(|p| p.stroke > 0.0 && p.color == highlight) + .count(), + 2, + "{scheme}: the highlight outline, laid twice" + ); + let dot = painted(&frame) + .into_iter() + .find(|p| (p.screen[2] - 6.0).abs() < 0.01 && (p.screen[3] - 6.0).abs() < 0.01) + .unwrap_or_else(|| panic!("{scheme}: no 6 px dot")); + assert_eq!(dot.color, sample(scheme, "radio_button.on", 10, 10)); + let disc = shapes[0]; + assert_eq!( + [dot.screen[0] + 3.0, dot.screen[1] + 3.0], + [disc.screen[0] + 7.5, disc.screen[1] + 7.5], + "{scheme}: the dot is centred" + ); + } +} + +/// A switch: the 12 px track in the button colour moved 10 % toward the +/// button text, an 18 px handle in the button colour, and, on, the +/// highlight at 50 % up to the handle, which sits at the trailing end; in +/// right-to-left at the leading one. +#[test] +fn a_switch_is_the_desktop_styles_track_and_handle() { + use teksilo_core::environment::LayoutDirection; + use teksilo_kde_scheme::KdeColor; + use teksilo_kde_scheme::math::mix; + for (scheme, theme) in themes() { + let p = theme.extension::().unwrap(); + let b = p.colors(teksilo_kde_scheme::ColorState::Active); + let track = mix( + KdeColor::from_color(b.button), + KdeColor::from_color(b.button_text), + 0.1, + ) + .to_color(); + let track = rgba8(track.to_array()); + let button = rgba8(b.button.to_array()); + for direction in [LayoutDirection::LeftToRight, LayoutDirection::RightToLeft] { + for on in [false, true] { + let mut tree = tree_with(theme.clone()); + tree.set_layout_direction(direction); + let row = tree.add(HStack::new().child(Toggle::new(Signal::new(on)))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let id = tree.children(row)[0]; + let node = tree.bounds(id); + let frame = rerender(&mut tree); + let all = painted(&frame); + let t = all + .iter() + .find(|s| s.stroke == 0.0 && s.screen[2] == 36.0 && s.screen[3] == 12.0) + .unwrap_or_else(|| panic!("{scheme}: no 36 x 12 track in {all:?}")); + assert_eq!(t.color, track, "{scheme}"); + let handle = all + .iter() + .find(|s| s.stroke == 0.0 && s.screen[2] == 18.0 && s.screen[3] == 18.0) + .unwrap_or_else(|| panic!("{scheme}: no 18 px handle")); + assert_eq!(handle.color, button, "{scheme}"); + let at_trailing = handle.screen[0] - t.screen[0] == 18.0; + let rtl = direction == LayoutDirection::RightToLeft; + assert_eq!(at_trailing, on != rtl, "{scheme} {direction:?} on {on}"); + let checked = all.iter().any(|s| s.stroke == 0.0 && s.color[3] == 128); + assert_eq!(checked, on, "{scheme}: the checked fill"); + // The node is the density's target; the indicator is centred. + assert_eq!((node.width, node.height), (36.0, 24.0)); + assert_eq!(t.screen[1] - node.y, 6.0); + } + } + } +} + +/// A line edit: Plasma's view fill on the node inset by 0.5 at radius 4.5, +/// the lifted outline at rest (D6) and the lifted focus colour while +/// focused, at the same 1 px. (The hover outline is the style's rule, tested +/// beside it: `TextInput` does not report the pointer over it.) +#[test] +fn a_field_is_breezes_line_edit() { + for (scheme, theme) in themes() { + let mut tree = tree_with(theme.clone()); + tree.add( + VStack::new() + .child(Button::new(lit!("Before"))) + .child(TextInput::new(Signal::new(String::new()))), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let frame = rerender(&mut tree); + let fill = sample(scheme, "line_edit.normal", 75, 16); + let rest = rgba8(theme.colors.border_strong.to_array()); + let outline = painted(&frame) + .into_iter() + .find(|p| p.stroke > 0.0 && p.color == rest) + .unwrap_or_else(|| panic!("{scheme}: no lifted outline in {:?}", painted(&frame))); + assert_eq!(outline.stroke, 1.0); + assert_eq!( + outline.screen[3], 31.0, + "{scheme}: a 32 px field inset by 0.5" + ); + assert!( + painted(&frame) + .iter() + .any(|p| p.stroke == 0.0 && p.color == fill && p.screen == outline.screen), + "{scheme}: no view fill on the frame" + ); + assert_ne!( + sample(scheme, "line_edit.normal", 0, 16), + rest, + "{scheme}: the premise of D6" + ); + + tree.press_key(Key::Tab, Modifiers::NONE); + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut tree); + let focus = rgba8(theme.colors.border_focused.to_array()); + let focused = painted(&frame) + .into_iter() + .find(|p| p.color == focus && p.screen == outline.screen) + .unwrap_or_else(|| panic!("{scheme}: no focus outline in {:?}", painted(&frame))); + assert_eq!(focused.stroke, 1.0, "{scheme}: focus keeps the 1 px width"); + } +} + +/// A disabled field: Plasma's disabled view fill in the disabled outline. +#[test] +fn a_disabled_field_paints_the_disabled_colours() { + for (scheme, theme) in themes() { + let (mut tree, _) = placed( + theme.clone(), + TextInput::new(Signal::new(String::new())).enabled(false), + ); + let frame = rerender(&mut tree); + let fill = sample(scheme, "line_edit.disabled", 75, 16); + assert!( + fills(&frame).contains(&fill), + "{scheme}: no {fill:?} in {:?}", + fills(&frame) + ); + let outline = rgba8(theme.colors.border_disabled.to_array()); + assert!(strokes(&frame).contains(&outline), "{scheme}"); + assert!(near(outline, sample(scheme, "line_edit.disabled", 0, 16))); + } +} + +/// An editable rich-text editor is framed as the line edit: the same +/// 1 px outline at the same radius, in `border_strong` at rest and +/// `border_focused` while focused, over the view fill. A viewer has no frame. +#[test] +fn an_editable_rich_text_editor_is_framed_as_the_line_edit() { + for (scheme, theme) in themes() { + let mut tree = tree_with(theme.clone()); + let column = tree.add( + VStack::new() + .child(TextInput::new(Signal::new(String::new()))) + .child(RichTextEditor::editor(TextDocument::new())), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let editor = tree.children(column)[1]; + let frame = rerender(&mut tree); + let rest = rgba8(theme.colors.border_strong.to_array()); + let fill = rgba8(theme.colors.surface_content.to_array()); + let mut outlines: Vec = painted(&frame) + .into_iter() + .filter(|p| p.stroke > 0.0 && p.color == rest) + .collect(); + outlines.sort_by(|a, b| a.screen[1].total_cmp(&b.screen[1])); + let [field, area] = outlines[..] else { + panic!("{scheme}: not two lifted outlines in {:?}", painted(&frame)); + }; + assert_eq!(area.stroke, field.stroke, "{scheme}: the line edit's pen"); + assert_eq!( + area.radius, field.radius, + "{scheme}: the line edit's radius" + ); + assert_eq!(area.radius, 4.5, "{scheme}"); + assert!( + painted(&frame) + .iter() + .any(|p| p.stroke == 0.0 && p.color == fill && p.screen == area.screen), + "{scheme}: no view fill on the editor's frame" + ); + + tree.focus(editor); + let frame = rerender(&mut tree); + let focus = rgba8(theme.colors.border_focused.to_array()); + let focused = painted(&frame) + .into_iter() + .find(|p| p.stroke > 0.0 && p.color == focus && p.screen == area.screen) + .unwrap_or_else(|| panic!("{scheme}: no focus outline in {:?}", painted(&frame))); + assert_eq!(focused.stroke, 1.0, "{scheme}: focus keeps the 1 px width"); + assert_eq!(focused.radius, 4.5, "{scheme}"); + + let (mut tree, _) = placed( + theme.clone(), + RichTextEditor::read_only(TextDocument::new()), + ); + let frame = rerender(&mut tree); + assert!( + strokes(&frame).is_empty(), + "{scheme}: a viewer is frameless, {:?}", + painted(&frame) + ); + } +} + +// ── Indicator geometry, focus, disabled and right-to-left ────────────── + +/// The colours the indicator styles paint from, in an active window. +fn indicator_colors(theme: &Theme, disabled: bool) -> BreezeIndicatorColors { + BreezeIndicatorColors::resolve(theme, true, false, disabled) +} + +fn rgba8_of(c: Color) -> [u8; 4] { + rgba8(c.to_array()) +} + +/// Every node under `root` whose type is `name`. +fn nodes_named(tree: &WidgetTree, root: WidgetId, name: &str) -> Vec { + fn walk(tree: &WidgetTree, id: WidgetId, name: &str, out: &mut Vec) { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(name)) + { + out.push(id); + } + for c in tree.children(id) { + walk(tree, c, name, out); + } + } + let mut out = Vec::new(); + walk(tree, root, name, &mut out); + out +} + +/// The track fill and the handle face of the one switch in `frame`. +fn switch_parts(frame: &RenderFrame) -> (Painted, Painted) { + let all = painted(frame); + let track = *all + .iter() + .find(|s| s.stroke == 0.0 && s.screen[2] == 36.0 && s.screen[3] == 12.0) + .unwrap_or_else(|| panic!("no 36 x 12 track in {all:?}")); + let handle = *all + .iter() + .find(|s| s.stroke == 0.0 && s.kind == ShapeKind::Circle && s.screen[2] == 18.0) + .unwrap_or_else(|| panic!("no 18 px handle in {all:?}")); + (track, handle) +} + +/// The handle's leading edge from the track's, left to right. +fn handle_offset(frame: &RenderFrame) -> f32 { + let (track, handle) = switch_parts(frame); + handle.screen[0] - track.screen[0] +} + +/// The handle's 1 px outline, a 17 px circle. +fn handle_outline(frame: &RenderFrame) -> Painted { + *painted(frame) + .iter() + .find(|s| s.stroke > 0.0 && s.kind == ShapeKind::Circle && s.screen[2] == 17.0) + .unwrap_or_else(|| panic!("no 17 px handle outline in {:?}", painted(frame))) +} + +/// The track's 1 px outline, 35 x 11. +fn track_outline(frame: &RenderFrame) -> Painted { + *painted(frame) + .iter() + .find(|s| s.stroke > 0.0 && s.screen[2] == 35.0 && s.screen[3] == 11.0) + .unwrap_or_else(|| panic!("no 35 x 11 track outline in {:?}", painted(frame))) +} + +/// The track's outline is inset by half its pen at radius 5.5, the handle's +/// is the 17 px circle inside the handle, and both take the D6 colours; the +/// track is outlined in the lifted highlight when on. Keyboard focus adds +/// the 2 px focus frame around the 36 x 18 indicator and turns the handle's +/// outline to the button set's hover colour. +#[test] +fn a_switch_is_outlined_and_framed_where_the_desktop_style_puts_it() { + for (scheme, theme) in themes() { + let c = indicator_colors(&theme, false); + let ring = rgba8_of(theme.colors.focus_ring); + for on in [false, true] { + let (mut tree, id) = on_the_clock(theme.clone(), false, Toggle::new(Signal::new(on))); + let frame = tick(&mut tree, 0); + let node = tree.bounds(id); + let (track, handle) = switch_parts(&frame); + let t = track_outline(&frame); + assert_eq!(t.kind, ShapeKind::RoundedRect, "{scheme}"); + assert_eq!( + t.screen, + [track.screen[0] + 0.5, track.screen[1] + 0.5, 35.0, 11.0], + "{scheme} on {on}" + ); + assert_eq!(t.radius, 5.5, "{scheme}"); + assert_eq!(t.stroke, 1.0); + let expected = if on { c.checked_outline } else { c.outline }; + assert_eq!(t.color, rgba8_of(expected), "{scheme} on {on}: D6"); + let h = handle_outline(&frame); + assert_eq!( + h.screen, + [handle.screen[0] + 0.5, handle.screen[1] + 0.5, 17.0, 17.0], + "{scheme}" + ); + assert_eq!(h.stroke, 1.0); + assert_eq!(h.color, rgba8_of(c.button_outline), "{scheme}: at rest"); + assert!( + !painted(&frame) + .iter() + .any(|p| p.color == ring && p.stroke == 2.0), + "{scheme}: no focus frame unfocused" + ); + + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = tick(&mut tree, 100); + let focus = painted(&frame) + .into_iter() + .find(|p| p.color == ring && p.stroke == 2.0) + .unwrap_or_else(|| panic!("{scheme}: no focus frame in {:?}", painted(&frame))); + // The indicator at (0, 3) of the 36 x 24 node: the frame's centre + // line 1 px outside it, at the indicator's radius plus 1. + assert_eq!( + [ + focus.screen[0] - node.x, + focus.screen[1] - node.y, + focus.screen[2], + focus.screen[3] + ], + [-1.0, 2.0, 38.0, 20.0], + "{scheme} on {on}" + ); + assert_eq!(focus.radius, 10.0, "{scheme}"); + assert_eq!( + handle_outline(&frame).color, + rgba8_of(c.button_hover), + "{scheme}: keyboard focus outlines the handle in the hover colour" + ); + } + } +} + +/// Keyboard focus draws the radio button's focus frame through the widget: +/// a circle 1 px outside the 16 px disc. +#[test] +fn keyboard_focus_frames_the_disc_in_a_circle() { + for (scheme, theme) in themes() { + let ring = rgba8_of(theme.colors.focus_ring); + let (mut tree, id) = in_column( + theme, + RadioButton::new(0, Signal::new(1_usize)).label(lit!("A")), + ); + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut tree); + let focus = painted(&frame) + .into_iter() + .find(|p| p.color == ring && p.stroke == 2.0) + .unwrap_or_else(|| panic!("{scheme}: no focus frame in {:?}", painted(&frame))); + let b = tree.bounds(id); + assert_eq!( + [ + focus.screen[0] - b.x, + focus.screen[1] - b.y, + focus.screen[2], + focus.screen[3] + ], + [1.0, 3.0, 18.0, 18.0], + "{scheme}" + ); + assert_eq!(focus.radius, 9.0, "{scheme}: half its side, a circle"); + } +} + +/// A disabled checked box and selected disc, against Plasma's disabled +/// samples: the disabled highlight's wash over the disabled face, the mark +/// and the dot in the disabled view text, the outline in the disabled +/// highlight, and no hover ring under the pointer. +#[test] +fn disabled_indicators_paint_plasmas_disabled_colours() { + for (scheme, theme) in themes() { + let c = indicator_colors(&theme, true); + let highlight = rgba8_of(theme.colors.accent_disabled); + + let id = "check_box.disabled_on"; + let (mut tree, _) = placed( + theme.clone(), + Checkbox::new(Signal::new(true)) + .label(lit!("Check")) + .enabled(false), + ); + let frame = rerender(&mut tree); + let shapes = box_shapes(&frame); + // The disabled highlight's wash over the disabled face, painted as + // one fill. + let fill = shapes + .iter() + .find(|p| p.stroke == 0.0) + .unwrap_or_else(|| panic!("{scheme}: no fill in {shapes:?}")); + assert!( + near(fill.color, sample(scheme, id, 4, 12)), + "{scheme}: {:?} against {:?}", + fill.color, + sample(scheme, id, 4, 12) + ); + assert!( + near( + over(rgba8_of(c.highlight.with_alpha(0.3)), rgba8_of(c.button)), + fill.color + ), + "{scheme}: the wash over the disabled face" + ); + let outlines: Vec<_> = shapes.iter().filter(|p| p.stroke > 0.0).collect(); + assert_eq!(outlines.len(), 2, "{scheme}: the outline, laid twice"); + assert!(outlines.iter().all(|p| p.color == highlight), "{scheme}"); + let mark = frame + .paths + .iter() + .find(|p| p.stroke_style.width > 0.0) + .unwrap_or_else(|| panic!("{scheme}: no check mark")); + assert!( + near(rgba8(mark.color), sample(scheme, id, 13, 8)), + "{scheme}: {:?} against {:?}", + rgba8(mark.color), + sample(scheme, id, 13, 8) + ); + tree.pointer_move(box_centre(&frame)); + let frame = rerender(&mut tree); + assert_eq!( + box_shapes(&frame).iter().filter(|p| p.stroke > 0.0).count(), + 2, + "{scheme}: no hover ring on a disabled box" + ); + + let id = "radio_button.disabled_on"; + let (mut tree, _) = placed( + theme.clone(), + RadioButton::new(0, Signal::new(0_usize)) + .label(lit!("One")) + .enabled(false), + ); + let frame = rerender(&mut tree); + let shapes = box_shapes(&frame); + let fill = shapes + .iter() + .find(|p| p.stroke == 0.0) + .unwrap_or_else(|| panic!("{scheme}: no disc fill in {shapes:?}")); + assert!( + near(fill.color, sample(scheme, id, 4, 10)), + "{scheme}: {:?} against {:?}", + fill.color, + sample(scheme, id, 4, 10) + ); + assert!( + shapes + .iter() + .any(|p| p.stroke > 0.0 && p.color == highlight), + "{scheme}: no disabled highlight outline" + ); + let dot = painted(&frame) + .into_iter() + .find(|p| p.kind == ShapeKind::Circle && (p.screen[2] - 6.0).abs() < 0.01) + .unwrap_or_else(|| panic!("{scheme}: no 6 px dot")); + assert!( + near(dot.color, sample(scheme, id, 10, 10)), + "{scheme}: {:?} against {:?}", + dot.color, + sample(scheme, id, 10, 10) + ); + } +} + +/// Right to left, the box and the disc hug the node's trailing (right) +/// edge, the label ends 24 px from it, and the partial squares are +/// centred on the box, the first to appear on the leading (right) side. +#[test] +fn right_to_left_mirrors_the_indicators() { + use teksilo_core::environment::LayoutDirection; + for (scheme, theme) in themes() { + let rows: [(&str, Box); 2] = [ + ( + "Checkbox", + Box::new(Checkbox::new(Signal::new(false)).label(lit!("Check"))), + ), + ( + "RadioButton", + Box::new(RadioButton::new(0, Signal::new(1_usize)).label(lit!("Radio"))), + ), + ]; + for (name, w) in rows { + let mut tree = tree_with(theme.clone()); + tree.set_layout_direction(LayoutDirection::RightToLeft); + let root = tree.add(VStack::new().child(HStack::new().child(w))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let node = nodes_named(&tree, root, name); + assert_eq!(node.len(), 1, "{scheme}: one {name}"); + let b = tree.bounds(node[0]); + let frame = rerender(&mut tree); + let stroke = box_shapes(&frame) + .into_iter() + .find(|p| p.stroke > 0.0) + .unwrap_or_else(|| panic!("{scheme}: no {name} outline")); + assert_eq!( + [ + b.x + b.width - (stroke.screen[0] + stroke.screen[2]), + stroke.screen[1] - b.y + ], + [2.5, 4.5], + "{scheme} {name}: the box mirrored" + ); + let label = nodes_named(&tree, node[0], "TextWidget"); + assert_eq!(label.len(), 1, "{scheme}: one label"); + let l = tree.bounds(label[0]); + assert_eq!( + b.x + b.width - (l.x + l.width), + 24.0, + "{scheme} {name}: the label 24 px from the trailing edge" + ); + } + + let state = Signal::new(CheckState::Unchecked); + let mut tree = tree_with(theme.clone()); + tree.set_layout_direction(LayoutDirection::RightToLeft); + tree.advance_time(Duration::ZERO); + tree.add( + VStack::new() + .child(HStack::new().child(Checkbox::tristate(state.clone()).label(lit!("Some")))), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.render(); + let text = sample(scheme, "check_box.partial", 5, 10); + state.set(CheckState::Indeterminate); + let squares = |f: &RenderFrame| -> Vec<[f32; 4]> { + f.decorations + .iter() + .filter(|d| rgba8(d.color) == text && d.rect[2] == 2.0) + .map(|d| d.rect) + .collect() + }; + tick(&mut tree, 0); + let frame = tick(&mut tree, 40); + let centre = box_centre(&frame); + let first = squares(&frame); + assert_eq!(first.len(), 1, "{scheme}: {first:?}"); + assert_eq!(first[0][0] + 1.0 - centre.x, 4.0, "{scheme}: on the right"); + let frame = tick(&mut tree, 60); + let all = squares(&frame); + assert_eq!(all.len(), 3, "{scheme}"); + let mut xs: Vec = all.iter().map(|s| s[0] + 1.0 - centre.x).collect(); + xs.sort_by(f32::total_cmp); + assert_eq!(xs, vec![-4.0, 0.0, 4.0], "{scheme}: centred on the box"); + } +} + +/// The hover ring is the view's focus decoration, not the highlight. With a +/// Plasma accent applied the two differ (the highlight is the accent at +/// 70 % over the view, the focus decoration the accent itself), so the +/// ring's colour tells them apart. +#[test] +fn the_hover_ring_is_the_focus_colour_not_the_highlight() { + for accent in PlasmaAccent::ALL { + for theme in [ + kde::light_with_accent(accent.color()), + kde::dark_with_accent(accent.color()), + ] { + let c = indicator_colors(&theme, false); + let focus = rgba8_of(c.focus); + let highlight = rgba8_of(c.highlight); + assert_ne!(focus, highlight, "{accent:?}: premise"); + for checked in [false, true] { + let (mut tree, _) = placed( + theme.clone(), + Checkbox::new(Signal::new(checked)).label(lit!("Check")), + ); + let frame = rerender(&mut tree); + tree.pointer_move(box_centre(&frame)); + let frame = rerender(&mut tree); + let outlines: Vec<[u8; 4]> = box_shapes(&frame) + .into_iter() + .filter(|p| p.stroke > 0.0) + .map(|p| p.color) + .collect(); + assert!( + outlines.contains(&focus), + "{accent:?} checked {checked}: {outlines:?}" + ); + assert_eq!( + outlines.contains(&highlight), + checked, + "{accent:?} checked {checked}: the highlight outlines only a checked box" + ); + } + let (mut tree, _) = placed( + theme.clone(), + RadioButton::new(0, Signal::new(1_usize)).label(lit!("One")), + ); + let frame = rerender(&mut tree); + tree.pointer_move(box_centre(&frame)); + let frame = rerender(&mut tree); + assert!( + box_shapes(&frame) + .iter() + .any(|p| p.stroke > 0.0 && p.color == focus), + "{accent:?}: no focus ring on the hovered disc" + ); + } + } +} + +// ── Motion, on the tree's simulated clock ─────────────────────────────── + +/// A tree on the simulated clock, with or without the reduced-motion +/// preference, holding `w` in a row in a column, laid out and rendered +/// once: the tree and the widget's id. +fn on_the_clock( + theme: Theme, + reduced_motion: bool, + w: impl Widget + 'static, +) -> (WidgetTree, WidgetId) { + let mut tree = tree_with(theme); + tree.set_accessibility_preferences(false, reduced_motion, 1.0); + tree.advance_time(Duration::ZERO); + let column = tree.add(VStack::new().child(HStack::new().child(w))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let row = tree.children(column)[0]; + let id = tree.children(row)[0]; + tree.render(); + (tree, id) +} + +/// Move the clock on by `ms` and render. An animation samples the clock +/// at most once a frame (about 16 ms), so the steps the tests take are at +/// least that long wherever the value is read. +fn tick(tree: &mut WidgetTree, ms: u64) -> Rc { + tree.advance_time(Duration::from_millis(ms)); + rerender(tree) +} + +/// The check mark's points, in the node's coordinates. +fn mark_points(frame: &RenderFrame, node: teksilo_canvas::Rect) -> Vec<[f32; 2]> { + frame + .paths + .iter() + .find(|p| p.stroke_style.width > 0.0) + .map(|mark| { + mark.path + .commands() + .iter() + .filter_map(|c| match c { + teksilo_canvas::PathCommand::MoveTo(p) + | teksilo_canvas::PathCommand::LineTo(p) => Some([p.x - node.x, p.y - node.y]), + _ => None, + }) + .collect() + }) + .unwrap_or_default() +} + +/// The diameter of the radio dot in `frame`, if one is painted. +fn dot_diameter(frame: &RenderFrame, text: [u8; 4]) -> Option { + painted(frame) + .into_iter() + .find(|p| p.kind == ShapeKind::Circle && p.stroke == 0.0 && p.color == text) + .map(|p| p.screen[2]) +} + +/// The whole mark, an indicator 2 px down in the 24 px row. +const WHOLE_MARK: [[f32; 2]; 3] = [[6.0, 12.0], [9.0, 15.0], [14.5, 9.5]]; + +/// The handle travels its 18 px in 90 ms (k.8: 100 ms, or 18 px at Qt's +/// 200 px a second if that is shorter), half way at the half time on its +/// ease-in-out, and back the same way. +#[test] +fn the_switch_handle_travels_in_ninety_milliseconds() { + for (scheme, theme) in themes() { + let on = Signal::new(false); + let (mut tree, _) = on_the_clock(theme, false, Toggle::new(on.clone())); + on.set(true); + assert_eq!(handle_offset(&tick(&mut tree, 0)), 0.0, "{scheme}: at 0"); + let mid = handle_offset(&tick(&mut tree, 45)); + assert!( + (8.5..9.5).contains(&mid), + "{scheme}: half way at 45 ms, {mid}" + ); + let late = handle_offset(&tick(&mut tree, 25)); + assert!( + late > mid && late < 18.0, + "{scheme}: still moving at 70 ms, {late}" + ); + assert_eq!( + handle_offset(&tick(&mut tree, 20)), + 18.0, + "{scheme}: at 90 ms" + ); + on.set(false); + tick(&mut tree, 0); + let back = handle_offset(&tick(&mut tree, 45)); + assert!((8.5..9.5).contains(&back), "{scheme}: {back}"); + assert_eq!( + handle_offset(&tick(&mut tree, 45)), + 0.0, + "{scheme}: back at 90 ms" + ); + } +} + +/// Under the pointer the handle's outline turns to the hover colour over the +/// short duration, 100 ms, on the ease-in cubic: an eighth of the way at +/// half the time. +#[test] +fn the_switch_handle_outline_turns_over_a_hundred_milliseconds() { + for (scheme, theme) in themes() { + let c = indicator_colors(&theme, false); + let rest = rgba8_of(c.button_outline); + let hover = rgba8_of(c.button_hover); + let (mut tree, _) = on_the_clock(theme, false, Toggle::new(Signal::new(false))); + let frame = tick(&mut tree, 0); + let (_, handle) = switch_parts(&frame); + assert_eq!(handle_outline(&frame).color, rest, "{scheme}"); + tree.pointer_move(teksilo_canvas::Point::new( + handle.screen[0] + 9.0, + handle.screen[1] + 9.0, + )); + assert_eq!(handle_outline(&tick(&mut tree, 0)).color, rest, "{scheme}"); + let half = handle_outline(&tick(&mut tree, 50)).color; + let eighth = rgba8_of(c.button_outline.mix(c.button_hover, 0.125)); + assert!(near(half, eighth), "{scheme}: {half:?}, not {eighth:?}"); + let late = handle_outline(&tick(&mut tree, 30)).color; + assert_ne!(late, hover, "{scheme}: still turning at 80 ms"); + assert_eq!( + handle_outline(&tick(&mut tree, 20)).color, + hover, + "{scheme}" + ); + } +} + +/// The check mark draws along its length over 100 ms, linearly: part of its +/// first stroke at 25 ms, into its second at 50, whole at 100. +#[test] +fn the_check_mark_draws_itself_over_a_hundred_milliseconds() { + for (scheme, theme) in themes() { + let checked = Signal::new(false); + let (mut tree, id) = on_the_clock( + theme, + false, + Checkbox::new(checked.clone()).label(lit!("Check")), + ); + let node = tree.bounds(id); + checked.set(true); + assert!( + mark_points(&tick(&mut tree, 0), node).is_empty(), + "{scheme}" + ); + let quarter = mark_points(&tick(&mut tree, 25), node); + assert_eq!(quarter.len(), 2, "{scheme}: {quarter:?}"); + assert_eq!(quarter[0], WHOLE_MARK[0]); + assert!( + quarter[1][0] > 6.0 && quarter[1][0] < 9.0, + "{scheme}: {quarter:?}" + ); + let half = mark_points(&tick(&mut tree, 25), node); + assert_eq!(half.len(), 3, "{scheme}: {half:?}"); + assert_eq!(&half[..2], &WHOLE_MARK[..2]); + assert!(half[2][0] > 9.0 && half[2][0] < 14.5, "{scheme}: {half:?}"); + let late = mark_points(&tick(&mut tree, 30), node); + assert_ne!(late, WHOLE_MARK.to_vec(), "{scheme}: not whole at 80 ms"); + assert_eq!(mark_points(&tick(&mut tree, 20), node), WHOLE_MARK.to_vec()); + } +} + +/// The partial squares appear one at each third of the 100 ms. +#[test] +fn the_partial_squares_appear_one_at_each_third() { + for (scheme, theme) in themes() { + let text = sample(scheme, "check_box.partial", 5, 10); + let state = Signal::new(CheckState::Unchecked); + let (mut tree, _) = on_the_clock( + theme, + false, + Checkbox::tristate(state.clone()).label(lit!("Some")), + ); + state.set(CheckState::Indeterminate); + let count = |f: &RenderFrame| { + f.decorations + .iter() + .filter(|d| rgba8(d.color) == text && d.rect[2] == 2.0) + .count() + }; + for (ms, shown) in [(0, 0), (20, 0), (20, 1), (30, 2), (30, 3)] { + let frame = tick(&mut tree, ms); + assert_eq!(count(&frame), shown, "{scheme}: after {ms} more ms"); + } + } +} + +/// The radio dot grows to its 6 px over 100 ms, linearly. +#[test] +fn the_radio_dot_grows_over_a_hundred_milliseconds() { + for (scheme, theme) in themes() { + let text = sample(scheme, "radio_button.on", 10, 10); + let selected = Signal::new(1_usize); + let (mut tree, _) = on_the_clock( + theme, + false, + RadioButton::new(0, selected.clone()).label(lit!("One")), + ); + selected.set(0); + assert_eq!(dot_diameter(&tick(&mut tree, 0), text), None, "{scheme}"); + let half = dot_diameter(&tick(&mut tree, 50), text).expect("a dot at 50 ms"); + assert!((half - 3.0).abs() < 0.01, "{scheme}: {half}"); + let late = dot_diameter(&tick(&mut tree, 30), text).expect("a dot at 80 ms"); + assert!((late - 4.8).abs() < 0.01, "{scheme}: {late}"); + let whole = dot_diameter(&tick(&mut tree, 20), text).expect("a dot at 100 ms"); + assert!((whole - 6.0).abs() < 0.01, "{scheme}: {whole}"); + } +} + +/// Under the reduced-motion preference every indicator snaps: the handle, +/// its outline, the mark, the partial squares and the dot reach their end +/// with no time passing. +#[test] +fn reduced_motion_snaps_every_indicator() { + for (scheme, theme) in themes() { + let c = indicator_colors(&theme, false); + + let on = Signal::new(false); + let (mut tree, _) = on_the_clock(theme.clone(), true, Toggle::new(on.clone())); + on.set(true); + let frame = tick(&mut tree, 0); + assert_eq!(handle_offset(&frame), 18.0, "{scheme}: the handle"); + let (_, handle) = switch_parts(&frame); + tree.pointer_move(teksilo_canvas::Point::new( + handle.screen[0] + 9.0, + handle.screen[1] + 9.0, + )); + assert_eq!( + handle_outline(&tick(&mut tree, 0)).color, + rgba8_of(c.button_hover), + "{scheme}: the handle's outline" + ); + + let checked = Signal::new(false); + let (mut tree, id) = on_the_clock( + theme.clone(), + true, + Checkbox::new(checked.clone()).label(lit!("Check")), + ); + let node = tree.bounds(id); + checked.set(true); + assert_eq!( + mark_points(&tick(&mut tree, 0), node), + WHOLE_MARK.to_vec(), + "{scheme}: the mark" + ); + + let state = Signal::new(CheckState::Unchecked); + let (mut tree, _) = on_the_clock( + theme.clone(), + true, + Checkbox::tristate(state.clone()).label(lit!("Some")), + ); + state.set(CheckState::Indeterminate); + let text = sample(scheme, "check_box.partial", 5, 10); + let frame = tick(&mut tree, 0); + let squares = frame + .decorations + .iter() + .filter(|d| rgba8(d.color) == text && d.rect[2] == 2.0) + .count(); + assert_eq!(squares, 3, "{scheme}: the partial squares"); + + let selected = Signal::new(1_usize); + let (mut tree, _) = on_the_clock( + theme.clone(), + true, + RadioButton::new(0, selected.clone()).label(lit!("One")), + ); + selected.set(0); + let text = sample(scheme, "radio_button.on", 10, 10); + let d = dot_diameter(&tick(&mut tree, 0), text).expect("a dot"); + assert!((d - 6.0).abs() < 0.01, "{scheme}: the dot, {d}"); + } +} + +// ── Combo boxes, spin boxes and sliders ───────────────────────────────── + +/// Every stroked path in `frame`: its colour and its points. +fn stroked_paths(frame: &RenderFrame) -> Vec<([u8; 4], Vec<[f32; 2]>)> { + frame + .paths + .iter() + .filter(|p| p.stroke_style.width > 0.0) + .map(|p| { + let points = p + .path + .commands() + .iter() + .filter_map(|c| match c { + teksilo_canvas::PathCommand::MoveTo(q) + | teksilo_canvas::PathCommand::LineTo(q) => Some([q.x, q.y]), + _ => None, + }) + .collect(); + (rgba8(p.color), points) + }) + .collect() +} + +/// The points of a Breeze arrow pointing `direction` in `rect`. +fn arrow_at( + rect: teksilo_canvas::Rect, + direction: teksilo_theme_kde::styles::arrow::ArrowDirection, +) -> Vec<[f32; 2]> { + teksilo_theme_kde::styles::arrow::arrow_points(rect, direction) + .iter() + .map(|p| [p.x, p.y]) + .collect() +} + +/// The one node of type `name` under `root`, and its bounds. +fn the_node(tree: &WidgetTree, root: WidgetId, name: &str) -> (WidgetId, teksilo_canvas::Rect) { + let nodes = nodes_named(tree, root, name); + assert_eq!(nodes.len(), 1, "one {name}"); + (nodes[0], tree.bounds(nodes[0])) +} + +fn combo() -> ComboBox { + ComboBox::new( + ["Item", "Longer item text"], + Signal::new(Some("Item".to_string())), + ) +} + +/// A combo box at rest is Plasma's push button, face, outline and shadow, +/// 32 px tall, with its arrow in the 20 px box 2 px inside the trailing +/// edge, in the arrow colour of the button text. +#[test] +fn a_combo_box_is_a_push_button_with_breezes_arrow() { + use teksilo_theme_kde::styles::arrow::ArrowDirection; + use teksilo_theme_kde::styles::combo_box::combo_arrow_color; + for (scheme, theme) in themes() { + let (mut tree, id) = placed(theme.clone(), combo()); + let frame = rerender(&mut tree); + let b = tree.bounds(id); + assert_eq!(b.height, 32.0, "{scheme}"); + assert!( + fills(&frame).contains(&sample(scheme, "combo_box.normal", 40, 16)), + "{scheme}: no face in {:?}", + fills(&frame) + ); + let outline = sample(scheme, "combo_box.normal", 1, 16); + let stroke = painted(&frame) + .into_iter() + .find(|p| p.stroke > 0.0 && p.color == outline) + .unwrap_or_else(|| panic!("{scheme}: no outline in {:?}", painted(&frame))); + assert_eq!( + stroke.screen, + [b.x + 1.5, b.y + 1.5, b.width - 3.0, b.height - 3.0] + ); + assert!(has_shadow(&frame), "{scheme}: no shadow"); + assert!(near( + over(SHADOW, rgba8_of(theme.colors.surface_main)), + sample(scheme, "combo_box.normal", 40, 31) + )); + let arrow_box = teksilo_canvas::Rect::new(b.x + b.width - 22.0, b.y + 6.0, 20.0, 20.0); + let rest = rgba8_of(combo_arrow_color(&theme, true, false, false, false)); + assert!( + stroked_paths(&frame).contains(&(rest, arrow_at(arrow_box, ArrowDirection::Down))), + "{scheme}: no arrow in {:?}", + stroked_paths(&frame) + ); + } +} + +/// Under the pointer the outline turns to the highlight and the arrow keeps +/// its colour; with keyboard focus the outline is the highlight, the arrow +/// the window text, and the focus frame is drawn around the node. +#[test] +fn a_hovered_or_focused_combo_box_is_outlined_in_the_highlight() { + use teksilo_theme_kde::styles::combo_box::combo_arrow_color; + for (scheme, theme) in themes() { + let (mut tree, id) = placed(theme.clone(), combo()); + let b = tree.bounds(id); + tree.pointer_move(b.center()); + let frame = rerender(&mut tree); + assert!( + strokes(&frame).contains(&sample(scheme, "combo_box.hover", 1, 16)), + "{scheme}: hovered" + ); + let rest = rgba8_of(combo_arrow_color(&theme, true, false, false, false)); + assert!( + stroked_paths(&frame).iter().any(|(c, _)| *c == rest), + "{scheme}: the arrow keeps its colour under the pointer" + ); + + let (mut tree, id) = placed(theme.clone(), combo()); + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut tree); + let b = tree.bounds(id); + assert!( + strokes(&frame).contains(&sample(scheme, "combo_box.focus", 1, 16)), + "{scheme}: focused" + ); + let text = rgba8_of(theme.colors.text_primary); + assert!( + stroked_paths(&frame).iter().any(|(c, _)| *c == text), + "{scheme}: the focused arrow is the window text" + ); + let ring = rgba8_of(theme.colors.focus_ring); + let focus = painted(&frame) + .into_iter() + .find(|p| p.color == ring && p.stroke == 2.0) + .unwrap_or_else(|| panic!("{scheme}: no focus frame")); + assert_eq!(focus.screen, [b.x, b.y, b.width, b.height]); + } +} + +/// Right to left, the arrow box is 2 px inside the left edge and the label +/// starts 6 px inside the right one. +#[test] +fn right_to_left_mirrors_the_combo_box() { + use teksilo_core::environment::LayoutDirection; + use teksilo_theme_kde::styles::arrow::ArrowDirection; + for (scheme, theme) in themes() { + let mut tree = tree_with(theme.clone()); + tree.set_layout_direction(LayoutDirection::RightToLeft); + let root = tree.add(VStack::new().child(HStack::new().child(combo()))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let (node, b) = the_node(&tree, root, "BreezeComboLayout"); + let frame = rerender(&mut tree); + let arrow_box = teksilo_canvas::Rect::new(b.x + 2.0, b.y + 6.0, 20.0, 20.0); + let expected = arrow_at(arrow_box, ArrowDirection::Down); + assert!( + stroked_paths(&frame).iter().any(|(_, p)| *p == expected), + "{scheme}: no mirrored arrow in {:?}", + stroked_paths(&frame) + ); + let (_, label) = the_node(&tree, node, "TextWidget"); + assert_eq!(b.x + b.width - (label.x + label.width), 6.0, "{scheme}"); + } +} + +/// A spin box is the line edit, 32 px tall, with its two arrows in the 20 x +/// 10 halves of the column 2 px inside the trailing edge, in the arrow +/// colour of the view's text; the step buttons paint nothing of their own. +#[test] +fn a_spin_box_is_a_line_edit_with_two_arrows() { + use teksilo_theme_kde::styles::arrow::ArrowDirection; + use teksilo_theme_kde::styles::spin_box::BreezeSpinArrowColors; + for (scheme, theme) in themes() { + let (mut tree, id) = placed(theme.clone(), SpinBox::new(Signal::new(3_i32), 0, 10)); + let (_, b) = the_node(&tree, id, "BreezeSpinLayout"); + assert_eq!(b.height, 32.0, "{scheme}"); + let frame = rerender(&mut tree); + let rest = rgba8_of(theme.colors.border_strong); + let outline = painted(&frame) + .into_iter() + .find(|p| p.stroke > 0.0 && p.color == rest) + .unwrap_or_else(|| panic!("{scheme}: no lifted outline in {:?}", painted(&frame))); + assert_eq!( + outline.screen, + [b.x + 0.5, b.y + 0.5, b.width - 1.0, b.height - 1.0] + ); + assert!( + painted(&frame).iter().any(|p| p.stroke == 0.0 + && p.color == sample(scheme, "spin_box.normal", 50, 16) + && p.screen == outline.screen), + "{scheme}: no view fill on the frame" + ); + assert_ne!( + sample(scheme, "spin_box.normal", 0, 16), + rest, + "{scheme}: the premise of D6" + ); + let arrow = rgba8_of(BreezeSpinArrowColors::resolve(&theme, true, false, false).arrow); + let x = b.x + b.width - 22.0; + let up = teksilo_canvas::Rect::new(x, b.y + 6.0, 20.0, 10.0); + let down = teksilo_canvas::Rect::new(x, b.y + 16.0, 20.0, 10.0); + let paths = stroked_paths(&frame); + assert_eq!( + paths, + vec![ + (arrow, arrow_at(up, ArrowDirection::Up)), + (arrow, arrow_at(down, ArrowDirection::Down)), + ], + "{scheme}: only Breeze's two arrows" + ); + // The step buttons press on Breeze's halves. + let steps = nodes_named(&tree, id, "StepButton"); + assert_eq!(steps.len(), 2, "{scheme}"); + assert_eq!(tree.bounds(steps[0]), up, "{scheme}: the up half"); + assert_eq!(tree.bounds(steps[1]), down, "{scheme}: the down half"); + } +} + +/// Under the pointer the frame is outlined in the hover colour and the +/// arrow of the half under it turns to the hover colour; a press there +/// still steps the value. +#[test] +fn a_hovered_spin_box_half_turns_to_the_hover_colour() { + use teksilo_theme_kde::styles::spin_box::BreezeSpinArrowColors; + for (scheme, theme) in themes() { + let value = Signal::new(3_i32); + let (mut tree, id) = placed(theme.clone(), SpinBox::new(value.clone(), 0, 10)); + let steps = nodes_named(&tree, id, "StepButton"); + let up = tree.bounds(steps[0]); + tree.pointer_move(up.center()); + let frame = rerender(&mut tree); + let c = BreezeSpinArrowColors::resolve(&theme, true, false, false); + let paths = stroked_paths(&frame); + assert_eq!(paths.len(), 2, "{scheme}"); + assert_eq!(paths[0].0, rgba8_of(c.hover), "{scheme}: the up arrow"); + assert_eq!(paths[1].0, rgba8_of(c.arrow), "{scheme}: the down arrow"); + assert!( + strokes(&frame).contains(&sample(scheme, "spin_box.hover", 0, 16)), + "{scheme}: the hover outline in {:?}", + strokes(&frame) + ); + tree.pointer_down_button(up.center(), PointerButton::Primary); + tree.pointer_up_button(up.center(), PointerButton::Primary); + assert_eq!(value.get(), 4, "{scheme}: the up half steps"); + } +} + +/// Right to left, the column is 2 px inside the left edge. +#[test] +fn right_to_left_mirrors_the_spin_box() { + use teksilo_core::environment::LayoutDirection; + for (scheme, theme) in themes() { + let mut tree = tree_with(theme.clone()); + tree.set_layout_direction(LayoutDirection::RightToLeft); + let root = tree.add(VStack::new().child(HStack::new().child(SpinBox::new( + Signal::new(3_i32), + 0, + 10, + )))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let (_, b) = the_node(&tree, root, "BreezeSpinLayout"); + let steps = nodes_named(&tree, root, "StepButton"); + assert_eq!( + tree.bounds(steps[0]), + teksilo_canvas::Rect::new(b.x + 2.0, b.y + 6.0, 20.0, 10.0), + "{scheme}" + ); + } +} + +/// A disabled spin box: the disabled view fill in the disabled outline, its +/// arrows in the arrow colour of the disabled view's text. +#[test] +fn a_disabled_spin_box_paints_the_disabled_colours() { + use teksilo_theme_kde::styles::spin_box::BreezeSpinArrowColors; + for (scheme, theme) in themes() { + let (mut tree, _) = placed( + theme.clone(), + SpinBox::new(Signal::new(3_i32), 0, 10).enabled(false), + ); + let frame = rerender(&mut tree); + assert!( + fills(&frame).contains(&sample(scheme, "line_edit.disabled", 75, 16)), + "{scheme}" + ); + assert!(strokes(&frame).contains(&rgba8_of(theme.colors.border_disabled))); + let arrow = rgba8_of(BreezeSpinArrowColors::resolve(&theme, true, false, true).arrow); + assert!( + stroked_paths(&frame).iter().all(|(c, _)| *c == arrow), + "{scheme}" + ); + } +} + +/// A slider at 30 % in a 200 px node, as Plasma's: the groove's two parts, +/// the handle's face, its D6 outline, its shadow ring. +fn slider_at( + theme: &Theme, + value: f32, + direction: teksilo_core::environment::LayoutDirection, + slider: impl FnOnce(Signal) -> Slider, +) -> (WidgetTree, WidgetId, Signal) { + let value = Signal::new(value); + let mut tree = tree_with(theme.clone()); + tree.set_layout_direction(direction); + let root = tree.add( + VStack::new().child( + teksilo_widgets::primitives::FixedSize::new() + .width(200.0) + .child(slider(value.clone())), + ), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + (tree, root, value) +} + +fn slider_parts( + tree: &WidgetTree, + root: WidgetId, + value: f32, + direction: teksilo_core::environment::LayoutDirection, +) -> ( + teksilo_canvas::Rect, + teksilo_theme_kde::styles::slider::SliderGeometry, +) { + use teksilo_core::styles::SliderOrientation; + let (_, b) = the_node(tree, root, "BreezeSliderBody"); + let g = teksilo_theme_kde::styles::slider::slider_geometry( + b, + value, + SliderOrientation::Horizontal, + direction, + false, + ); + (b, g) +} + +fn inset(r: teksilo_canvas::Rect) -> [f32; 4] { + [r.x + 0.5, r.y + 0.5, r.width - 1.0, r.height - 1.0] +} + +#[test] +fn a_slider_is_breezes_groove_and_handle() { + use teksilo_core::environment::LayoutDirection; + let ltr = LayoutDirection::LeftToRight; + for (scheme, theme) in themes() { + let (mut tree, root, _) = slider_at(&theme, 0.3, ltr, |v| Slider::new(v, 0.0, 1.0)); + let (b, g) = slider_parts(&tree, root, 0.3, ltr); + assert_eq!(b.height, 24.0, "{scheme}: raised to the target"); + assert_eq!(g.handle.width, 20.0); + let frame = rerender(&mut tree); + let all = painted(&frame); + let groove = all + .iter() + .find(|p| p.stroke == 0.0 && p.screen == inset(g.groove)) + .unwrap_or_else(|| panic!("{scheme}: no groove in {all:?}")); + assert!( + near(groove.color, sample(scheme, "slider.normal", 190, 10)), + "{scheme}: the groove {:?}", + groove.color + ); + // Breeze's radius 3, which the canvas holds to half the thickness of + // the 5 px groove (6, inset by half the pen). + assert_eq!(groove.radius, 2.5); + let fill = all + .iter() + .find(|p| p.stroke == 0.0 && p.screen == inset(g.fill)) + .unwrap_or_else(|| panic!("{scheme}: no fill in {all:?}")); + assert!( + near(fill.color, sample(scheme, "slider.normal", 10, 10)), + "{scheme}: the fill {:?}", + fill.color + ); + let circles: Vec<_> = all + .iter() + .filter(|p| p.kind == ShapeKind::Circle && p.screen[2] == 17.0) + .collect(); + let face = circles + .iter() + .find(|p| p.stroke == 0.0) + .unwrap_or_else(|| panic!("{scheme}: no handle face")); + assert_eq!( + face.color, + sample(scheme, "slider.normal", 63, 10), + "{scheme}" + ); + assert_eq!( + [face.screen[0], face.screen[1]], + [g.handle.x + 1.5, g.handle.y + 1.5], + "{scheme}: the box inset by 1, then stroked" + ); + let outline = rgba8_of(theme.colors.border_strong); + assert!( + circles + .iter() + .any(|p| p.stroke == 1.0 && p.color == outline), + "{scheme}: no lifted outline in {circles:?}" + ); + assert_ne!( + sample(scheme, "slider.normal", 63, 1), + outline, + "{scheme}: the premise of D6" + ); + assert!( + circles.iter().any(|p| p.stroke == 1.0 && p.color == SHADOW), + "{scheme}: no shadow ring" + ); + } +} + +/// Hovered, the handle is outlined in the hover colour; focused from the +/// keyboard, in the lifted focus colour, with the focus frame around it; +/// dragged, it casts no shadow. +#[test] +fn a_slider_handle_shows_hover_focus_and_press() { + use teksilo_core::environment::LayoutDirection; + let ltr = LayoutDirection::LeftToRight; + for (scheme, theme) in themes() { + let hover = theme + .extension::() + .unwrap() + .colors(teksilo_theme_kde::scheme::ColorState::Active) + .hover; + let (mut tree, root, _) = slider_at(&theme, 0.3, ltr, |v| Slider::new(v, 0.0, 1.0)); + let (_, g) = slider_parts(&tree, root, 0.3, ltr); + tree.pointer_move(g.handle.center()); + let frame = rerender(&mut tree); + assert!( + painted(&frame).iter().any(|p| p.kind == ShapeKind::Circle + && p.stroke == 1.0 + && p.color == rgba8_of(hover)), + "{scheme}: no hover outline" + ); + tree.pointer_down_button(g.handle.center(), PointerButton::Primary); + let c = g.handle.center(); + tree.pointer_move(teksilo_canvas::Point::new(c.x + 12.0, c.y)); + let frame = rerender(&mut tree); + assert!( + !strokes(&frame).contains(&SHADOW), + "{scheme}: a dragged handle casts no shadow" + ); + tree.pointer_up_button(g.handle.center(), PointerButton::Primary); + + let (mut tree, root, _) = slider_at(&theme, 0.3, ltr, |v| Slider::new(v, 0.0, 1.0)); + let (_, g) = slider_parts(&tree, root, 0.3, ltr); + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut tree); + let focus = rgba8_of(theme.colors.border_focused); + assert!( + painted(&frame) + .iter() + .any(|p| p.kind == ShapeKind::Circle && p.stroke == 1.0 && p.color == focus), + "{scheme}: no focus outline" + ); + let ring = rgba8_of(theme.colors.focus_ring); + let frame_ring = painted(&frame) + .into_iter() + .find(|p| p.color == ring && p.stroke == 2.0) + .unwrap_or_else(|| panic!("{scheme}: no focus frame")); + assert_eq!( + frame_ring.screen, + [g.handle.x, g.handle.y, 20.0, 20.0], + "{scheme}: from 1 px inside the box to 1 px outside it" + ); + } +} + +/// Disabled, only the groove's background part is painted, in the disabled +/// colours, and the handle is the disabled button colour. +#[test] +fn a_disabled_slider_paints_only_the_groove_in_the_disabled_colours() { + use teksilo_core::environment::LayoutDirection; + let ltr = LayoutDirection::LeftToRight; + for (scheme, theme) in themes() { + let (mut tree, root, _) = slider_at(&theme, 0.3, ltr, |v| { + Slider::new(v, 0.0, 1.0).enabled(false) + }); + let (_, g) = slider_parts(&tree, root, 0.3, ltr); + let frame = rerender(&mut tree); + let all = painted(&frame); + assert!( + !all.iter().any(|p| p.screen == inset(g.fill)), + "{scheme}: no fill part" + ); + let groove = all + .iter() + .find(|p| p.stroke == 0.0 && p.screen == inset(g.groove)) + .unwrap_or_else(|| panic!("{scheme}: no groove")); + assert!( + near(groove.color, sample(scheme, "slider.disabled", 190, 10)), + "{scheme}: {:?}", + groove.color + ); + assert!( + all.iter().any(|p| p.kind == ShapeKind::Circle + && p.stroke == 0.0 + && p.color == sample(scheme, "slider.disabled", 63, 10)), + "{scheme}: the disabled handle" + ); + } +} + +/// A vertical slider has its minimum at the bottom, and its groove is +/// filled from the top down to the handle's centre, as Breeze fills a +/// vertical slider that is not inverted (spec k.5.10); a press puts the +/// value where the handle is then painted. +#[test] +fn a_vertical_slider_fills_from_the_top_to_the_handle() { + use teksilo_core::environment::LayoutDirection; + use teksilo_core::styles::SliderOrientation; + use teksilo_tokens::Orientation; + let ltr = LayoutDirection::LeftToRight; + for (scheme, theme) in themes() { + let value = Signal::new(0.25_f32); + let mut tree = tree_with(theme.clone()); + let root = tree.add( + VStack::new().child( + teksilo_widgets::primitives::FixedSize::new() + .height(200.0) + .child( + Slider::new(value.clone(), 0.0, 1.0) + .orientation(Orientation::Vertical) + .jump_on_press(true), + ), + ), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let (_, b) = the_node(&tree, root, "BreezeSliderBody"); + assert_eq!(b.height, 200.0, "{scheme}"); + assert_eq!(b.width, 24.0, "{scheme}: raised to the target"); + let g = teksilo_theme_kde::styles::slider::slider_geometry( + b, + 0.25, + SliderOrientation::Vertical, + ltr, + false, + ); + assert_eq!( + g.handle.center().y, + b.y + b.height - 10.0 - 180.0 * 0.25, + "{scheme}: a quarter up from the bottom" + ); + let frame = rerender(&mut tree); + let fill = painted(&frame) + .into_iter() + .find(|p| p.stroke == 0.0 && p.screen == inset(g.fill)) + .unwrap_or_else(|| panic!("{scheme}: no fill")); + assert_eq!(fill.screen[1], b.y + 0.5, "{scheme}: from the top"); + assert_eq!( + fill.screen[1] + fill.screen[3], + g.handle.center().y - 0.5, + "{scheme}: down to the handle's centre" + ); + assert!( + near(fill.color, sample(scheme, "slider.normal", 10, 10)), + "{scheme}: the fill {:?}", + fill.color + ); + + // 55 px down from the top, a quarter of the 180 px travel past the + // handle's half from the maximum: 75 %. + let at = teksilo_canvas::Point::new(b.x + b.width * 0.5, b.y + 55.0); + tree.pointer_down_button(at, PointerButton::Primary); + tree.pointer_up_button(at, PointerButton::Primary); + let v = value.get(); + assert!( + (v - 0.75).abs() < 1e-4, + "{scheme}: the top is the high end, {v}" + ); + let frame = rerender(&mut tree); + let face = painted(&frame) + .into_iter() + .find(|p| p.kind == ShapeKind::Circle && p.stroke == 0.0 && p.screen[2] == 17.0) + .unwrap_or_else(|| panic!("{scheme}: no handle")); + assert!( + (face.screen[1] + 8.5 - at.y).abs() < 0.01, + "{scheme}: the handle is under the pointer" + ); + let g = teksilo_theme_kde::styles::slider::slider_geometry( + b, + v, + SliderOrientation::Vertical, + ltr, + false, + ); + let fill = painted(&frame) + .into_iter() + .find(|p| p.stroke == 0.0 && p.screen == inset(g.fill)) + .unwrap_or_else(|| panic!("{scheme}: no fill at {v}")); + assert_eq!(fill.screen[3], 55.0 - 1.0, "{scheme}: the top 55 px, inset"); + } +} + +/// Right to left, the handle, the fill and the ticks run from the right, +/// and a press puts the value where the handle is then painted: the paint +/// and the `Slider`'s pointer mapping agree. +#[test] +fn right_to_left_mirrors_the_slider_and_keeps_the_handle_under_the_pointer() { + use teksilo_core::environment::LayoutDirection; + let rtl = LayoutDirection::RightToLeft; + for (scheme, theme) in themes() { + let (mut tree, root, value) = slider_at(&theme, 0.25, rtl, |v| { + Slider::new(v, 0.0, 1.0).jump_on_press(true) + }); + let (b, g) = slider_parts(&tree, root, 0.25, rtl); + assert_eq!( + g.handle.x, + b.x + b.width - 20.0 - (b.width - 20.0) * 0.25, + "{scheme}: a quarter in from the right" + ); + let frame = rerender(&mut tree); + let fill = painted(&frame) + .into_iter() + .find(|p| p.stroke == 0.0 && p.screen == inset(g.fill)) + .unwrap_or_else(|| panic!("{scheme}: no mirrored fill")); + assert_eq!(fill.screen[0] + fill.screen[2], b.x + b.width - 0.5); + + // 55 px in from the left, a quarter of the 180 px travel past the + // handle's half: 75 %, on the slider's step. + let at = teksilo_canvas::Point::new(b.x + 55.0, b.y + b.height * 0.5); + tree.pointer_down_button(at, PointerButton::Primary); + tree.pointer_up_button(at, PointerButton::Primary); + let v = value.get(); + assert!( + (v - 0.75).abs() < 1e-4, + "{scheme}: the left is the high end, {v}" + ); + let frame = rerender(&mut tree); + let (_, g) = slider_parts(&tree, root, v, rtl); + let face = painted(&frame) + .into_iter() + .find(|p| p.kind == ShapeKind::Circle && p.stroke == 0.0 && p.screen[2] == 17.0) + .unwrap_or_else(|| panic!("{scheme}: no handle")); + assert!( + (face.screen[0] + 8.5 - at.x).abs() < 0.01, + "{scheme}: the handle is under the pointer" + ); + assert!((g.handle.center().x - at.x).abs() < 0.01); + } + + // The ticks of a discrete slider mirror with it: the minimum's tick is at + // the right, in the highlight up to the value. + let theme = kde::light(); + let (mut tree, root, _) = + slider_at(&theme, 0.5, rtl, |v| Slider::new(v, 0.0, 1.0).tick_count(3)); + let (_, b) = the_node(&tree, root, "BreezeSliderBody"); + let frame = rerender(&mut tree); + let colors = + teksilo_theme_kde::styles::slider::BreezeSliderColors::resolve(&theme, true, false, false); + let mut ticks: Vec<([u8; 4], f32)> = frame + .decorations + .iter() + .filter(|d| d.rect[2] == 1.0 && d.rect[3] == 8.0) + .map(|d| (rgba8(d.color), d.rect[0] + 0.5 - b.x)) + .collect(); + ticks.sort_by(|a, b| a.1.total_cmp(&b.1)); + assert_eq!( + ticks, + vec![ + (rgba8_of(colors.tick_off), 10.0), + (rgba8_of(colors.tick_on), 100.0), + (rgba8_of(colors.tick_on), 190.0), + ] + ); +} + +// ── Menus, popups and tooltips ────────────────────────────────────────── + +/// A menu of every kind of row Breeze has, in a 200 px column. +fn menu() -> MenuList { + MenuList::new() + .item(MenuItem::new(lit!("Open"))) + .item(MenuItem::new(lit!("Save")).shortcut_label("Ctrl+S")) + .separator() + .header(TextWidget::new(lit!("Section"))) + .item(MenuItem::new(lit!("Checkable")).checked(Signal::new(true))) + .item(MenuItem::new(lit!("Option")).radio(0, Signal::new(0_usize))) + .item(MenuItem::submenu(lit!("More"), || { + Box::new(MenuList::new().item(MenuItem::new(lit!("Inner")))) + })) + .item(MenuItem::new(lit!("Unavailable")).enabled(false)) +} + +/// A tree holding [`menu`] 200 px wide, laid out: the tree, the menu's id +/// and the ids of its rows, in order. +fn menu_tree(theme: Theme) -> (WidgetTree, WidgetId, Vec) { + let mut tree = tree_with(theme); + let column = tree.add(VStack::new().child(FixedSize::new().width(200.0).child(menu()))); + tree.layout(SizeProposal::exact(400.0, 400.0)); + let rows = nodes_named(&tree, column, "MenuItem"); + (tree, column, rows) +} + +/// Builds the theme's popover style around a label, as a popover does. +#[derive(Debug)] +struct PopoverProbe { + variant: PopoverVariant, + root: Option, +} + +impl Widget for PopoverProbe { + fn build(&mut self, ctx: &mut BuildContext) -> Vec { + let content = ctx.add(TextWidget::new(lit!("Content"))); + let style = ctx + .theme() + .style_slots + .popover + .clone() + .expect("the preset installs a popover style"); + let root = style.make_body( + &PopoverStyleConfig { + content, + variant: self.variant, + name: "Probe".to_string(), + placement: OverlayPlacement::Below, + show_caret: true, + caret_size: 6.0, + }, + ctx, + ); + self.root = Some(root); + vec![root] + } + + fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { + self.root + .and_then(|id| ctx.child_size(id, proposal)) + .unwrap_or_else(|| proposal.resolve(0.0, 0.0)) + .into() + } + + fn children(&self) -> Vec { + self.root.into_iter().collect() + } +} + +fn popover(variant: PopoverVariant) -> PopoverProbe { + PopoverProbe { + variant, + root: None, + } +} + +#[test] +fn menus_popovers_and_tooltips_mount_in_every_variant() { + for (_, theme) in themes() { + assert!(mount(theme.clone(), menu()) > 0); + assert!(mount(theme.clone(), TooltipWidget::new(lit!("Tooltip text"))) > 0); + for variant in [ + PopoverVariant::Default, + PopoverVariant::Menu, + PopoverVariant::Tooltip, + ] { + assert!(mount(theme.clone(), popover(variant)) > 0, "{variant:?}"); + } + let (mut tree, _, rows) = menu_tree(theme.clone()); + // Hover, then press, every row: none panics, each paints. + for row in rows { + let at = tree.bounds(row).center(); + tree.pointer_move(at); + assert!(!rerender(&mut tree).draw_order.is_empty()); + tree.pointer_down_button(at, PointerButton::Primary); + assert!(!rerender(&mut tree).draw_order.is_empty()); + tree.pointer_up_button(at, PointerButton::Primary); + } + } +} + +#[test] +fn menus_and_tooltips_survive_a_theme_swap_and_a_missing_palette() { + let mut tree = tree_with(kde::light()); + tree.add( + VStack::new() + .child(menu()) + .child(TooltipWidget::new(lit!("Tooltip text"))) + .child(popover(PopoverVariant::Default)), + ); + let mut bare = kde::light(); + bare.extensions.remove::(); + for theme in [ + kde::light(), + kde::dark(), + teksilo_core::presets::intui::light(), + bare, + kde::light(), + ] { + tree.set_theme(theme); + assert!(!rerender(&mut tree).draw_order.is_empty()); + } + for accent in PlasmaAccent::ALL { + for theme in [ + kde::light_with_accent(accent.color()), + kde::dark_with_accent(accent.color()), + ] { + assert!(mount(theme, menu()) > 0, "{accent:?}"); + } + } +} + +/// The menu's frame is Plasma's: the frame background in the frame outline, +/// over both layers of Breeze's Large window shadow on the 5 px window. +#[test] +fn a_menu_is_framed_as_breezes_popup_menu() { + for (scheme, theme) in themes() { + let (mut tree, column, _) = menu_tree(theme); + let (_, frame_bounds) = the_node(&tree, column, "BreezePopupFrame"); + let frame = rerender(&mut tree); + let all = painted(&frame); + let panel = inset(frame_bounds); + let face = all + .iter() + .find(|p| p.stroke == 0.0 && p.screen == panel) + .unwrap_or_else(|| panic!("{scheme}: no panel face in {all:?}")); + assert_eq!( + face.color, + sample(scheme, "menu.panel", 100, 50), + "{scheme}" + ); + assert_eq!(face.radius, 4.5); + let outline = all + .iter() + .find(|p| p.stroke == 1.0 && p.screen == panel) + .unwrap_or_else(|| panic!("{scheme}: no panel outline")); + assert_eq!( + outline.color, + sample(scheme, "menu.panel", 0, 50), + "{scheme}" + ); + assert_window_shadow(&frame, frame_bounds, scheme); + } +} + +/// Both layers of Breeze's Large shadow, on the 5 px window `bounds`. +fn assert_window_shadow(frame: &RenderFrame, bounds: teksilo_canvas::Rect, scheme: &str) { + let window = [bounds.x, bounds.y, bounds.width, bounds.height]; + let layers: Vec<_> = frame + .shadows + .iter() + .filter(|s| s.shape_rect == window) + .collect(); + assert_eq!(layers.len(), 2, "{scheme}: two shadow layers"); + for (layer, (offset, blur, alpha)) in layers.iter().zip([(5.0, 20.0, 0.22), (2.0, 10.0, 0.12)]) + { + assert_eq!(layer.blur_radius, blur, "{scheme}"); + assert!((layer.color[3] - alpha).abs() < 1e-6, "{scheme}"); + assert_eq!(&layer.color[..3], &[0.0, 0.0, 0.0], "{scheme}: black"); + assert_eq!(layer.corner_radii, [5.0; 4], "{scheme}"); + // The quad covers the blur around the window moved down by the + // layer's offset. + assert_eq!(layer.screen[1], bounds.y + offset - blur, "{scheme}"); + assert_eq!(layer.screen[0], bounds.x - blur, "{scheme}"); + } +} + +/// Every row sits 4 px inside the frame, Breeze's menu margin, and is 29 px +/// tall; a separator is 5 px. +#[test] +fn a_menus_rows_sit_in_breezes_margin() { + let (tree, column, rows) = menu_tree(kde::light()); + let (_, frame) = the_node(&tree, column, "BreezePopupFrame"); + let first = tree.bounds(rows[0]); + assert_eq!((first.x - frame.x, first.y - frame.y), (4.0, 4.0)); + assert_eq!(first.width, frame.width - 8.0); + for row in &rows { + assert_eq!(tree.bounds(*row).height, 29.0); + } + let (_, separator) = the_node(&tree, column, "MenuSeparator"); + assert_eq!(separator.height, 5.0); + assert_eq!(separator.y, tree.bounds(rows[1]).bottom()); +} + +/// The highlighted row's rectangle and the shapes painted on it. +fn row_shapes(frame: &RenderFrame, row: teksilo_canvas::Rect) -> Vec { + painted(frame) + .into_iter() + .filter(|p| p.screen == inset(row)) + .collect() +} + +/// Under the pointer a row is washed with the focus colour at 30 % and +/// outlined at radius 4.5 on the row inset by half the pen: Plasma's +/// highlight. Breeze Dark's outline is Plasma's; Breeze Light's is lifted to +/// 3:1 on the menu (D5), its own falling short. +#[test] +fn a_hovered_menu_row_is_breezes_highlight() { + for (scheme, theme) in themes() { + let window = sample(scheme, "menu_item.normal", 100, 14); + let (mut tree, _, rows) = menu_tree(theme.clone()); + let row = tree.bounds(rows[0]); + assert!( + row_shapes(&rerender(&mut tree), row).is_empty(), + "{scheme}: at rest" + ); + + tree.pointer_move(row.center()); + let frame = rerender(&mut tree); + let shapes = row_shapes(&frame, row); + let wash = shapes + .iter() + .find(|p| p.stroke == 0.0) + .unwrap_or_else(|| panic!("{scheme}: no wash in {shapes:?}")); + assert!( + near( + over(wash.color, window), + sample(scheme, "menu_item.selected", 100, 14) + ), + "{scheme}: the wash {:?}", + wash.color + ); + assert_eq!(wash.radius, 4.5); + let outline = shapes + .iter() + .find(|p| p.stroke == 1.0) + .unwrap_or_else(|| panic!("{scheme}: no outline in {shapes:?}")); + assert_eq!(outline.radius, 4.5); + let plasma = sample(scheme, "menu_item.selected", 100, 0); + let panel = theme.colors.surface_raised; + let contrast = |c: [u8; 4]| { + teksilo_tokens::contrast::composite_contrast( + Color::new( + f32::from(c[0]) / 255.0, + f32::from(c[1]) / 255.0, + f32::from(c[2]) / 255.0, + f32::from(c[3]) / 255.0, + ), + panel, + ) + }; + if scheme == "BreezeDark" { + assert_eq!(outline.color, plasma, "{scheme}: Breeze's own outline"); + } else { + assert!(contrast(plasma) < 3.0, "{scheme}: the premise of D5"); + assert_ne!(outline.color, plasma); + } + assert!(contrast(outline.color) >= 3.0, "{scheme}"); + // The label stays the window text while highlighted. + let text = rgba8_of(theme.colors.text_primary); + assert!(frame.glyphs.iter().any(|g| rgba8(g.color) == text)); + } +} + +/// Held down, the row is filled and outlined with the accent and its label +/// turns to the text on it. +#[test] +fn a_pressed_menu_row_is_filled_and_its_label_flips() { + for (scheme, theme) in themes() { + let (mut tree, _, rows) = menu_tree(theme.clone()); + let row = tree.bounds(rows[1]); + tree.pointer_move(row.center()); + tree.pointer_down_button(row.center(), PointerButton::Primary); + let frame = rerender(&mut tree); + let shapes = row_shapes(&frame, row); + let accent = rgba8_of(theme.colors.accent); + assert!( + shapes.iter().any(|p| p.stroke == 0.0 && p.color == accent), + "{scheme}: {shapes:?}" + ); + assert!(shapes.iter().any(|p| p.stroke == 1.0 && p.color == accent)); + let on_accent = rgba8_of(theme.colors.text_on_accent); + let in_row: Vec<_> = frame + .glyphs + .iter() + .filter(|g| g.screen[1] >= row.y && g.screen[1] < row.bottom()) + .map(|g| rgba8(g.color)) + .collect(); + assert!(!in_row.is_empty()); + assert!( + in_row.iter().all(|c| *c == on_accent), + "{scheme}: the label and its shortcut on the fill, {in_row:?}" + ); + tree.pointer_up_button(row.center(), PointerButton::Primary); + } +} + +/// A disabled row is never highlighted. +#[test] +fn a_disabled_menu_row_is_not_highlighted() { + let (mut tree, _, rows) = menu_tree(kde::light()); + let row = tree.bounds(*rows.last().unwrap()); + tree.pointer_move(row.center()); + assert!(row_shapes(&rerender(&mut tree), row).is_empty()); +} + +/// A one-line tooltip is its text's height plus 2 x 4 px, and that height is +/// the text backend's own for one line, not the small text style's line box: +/// `TextWidget` does not read `TextStyle::line_height`. The mock backend's +/// line is 16 px and the small style's line box is 15 px, so a text that +/// honoured the style would be told apart. (With the real backend the same +/// relation gives 21.31 px for Inter at 11 px; the visual check measures it.) +#[test] +fn a_one_line_tooltip_is_its_text_height_plus_eight_not_the_style_line_box() { + for (scheme, theme) in themes() { + let small = theme.typography.small.clone(); + let style_line = small.size * small.line_height; + assert!((style_line - 15.0).abs() < 1e-3, "{scheme}: {style_line}"); + let mut tree = tree_with(theme); + let root = tree.add( + VStack::new().child(HStack::new().child(TooltipWidget::new(lit!("Tooltip text")))), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let (frame_id, bounds) = the_node(&tree, root, "BreezePopupFrame"); + let text = tree.bounds(tree.children(frame_id)[0]); + assert_eq!(text.height, 16.0, "{scheme}: the backend's one line"); + assert_ne!(text.height, style_line, "{scheme}"); + assert_eq!(bounds.height, text.height + 8.0, "{scheme}"); + } +} + +/// A tooltip is Plasma's: the tooltip background in its outline, over the +/// window shadow, the text 4 px inside the frame. +#[test] +fn a_tooltip_is_framed_as_breezes() { + for (scheme, theme) in themes() { + let mut tree = tree_with(theme); + let root = tree.add( + VStack::new().child(HStack::new().child(TooltipWidget::new(lit!("Tooltip text")))), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let (frame_id, bounds) = the_node(&tree, root, "BreezePopupFrame"); + let text = tree.bounds(tree.children(frame_id)[0]); + assert_eq!((text.x - bounds.x, text.y - bounds.y), (4.0, 4.0)); + assert_eq!(bounds.height, text.height + 8.0); + let frame = rerender(&mut tree); + let all = painted(&frame); + let face = all + .iter() + .find(|p| p.stroke == 0.0 && p.screen == inset(bounds)) + .unwrap_or_else(|| panic!("{scheme}: no face in {all:?}")); + assert_eq!( + face.color, + sample(scheme, "tooltip.panel", 50, 15), + "{scheme}" + ); + assert_eq!(face.radius, 4.5); + let outline = all + .iter() + .find(|p| p.stroke == 1.0 && p.screen == inset(bounds)) + .unwrap_or_else(|| panic!("{scheme}: no outline")); + assert_eq!( + outline.color, + sample(scheme, "tooltip.panel", 0, 15), + "{scheme}" + ); + assert_window_shadow(&frame, bounds, scheme); + } +} + +/// A popover that is not a menu pads its content 4 px and draws no caret; +/// its `Tooltip` variant takes the tooltip's colours, and a menu's frame +/// has no padding. +#[test] +fn a_popover_is_a_breeze_popup_with_no_caret() { + for (variant, padding, face) in [ + (PopoverVariant::Default, 4.0, "menu.panel"), + (PopoverVariant::Tooltip, 4.0, "tooltip.panel"), + (PopoverVariant::Menu, 0.0, "menu.panel"), + ] { + for (scheme, theme) in themes() { + let (mut tree, root) = placed(theme, popover(variant)); + let (frame_id, bounds) = the_node(&tree, root, "BreezePopupFrame"); + let content = tree.bounds(tree.children(frame_id)[0]); + assert_eq!(content.x - bounds.x, padding, "{variant:?}"); + assert_eq!(content.y - bounds.y, padding, "{variant:?}: no caret above"); + let frame = rerender(&mut tree); + assert!(frame.paths.is_empty(), "{variant:?}: a caret was drawn"); + let all = painted(&frame); + let fill_at = if face == "tooltip.panel" { + (50, 15) + } else { + (100, 50) + }; + assert!( + all.iter().any(|p| p.stroke == 0.0 + && p.screen == inset(bounds) + && p.color == sample(scheme, face, fill_at.0, fill_at.1)), + "{scheme} {variant:?}: {all:?}" + ); + } + } +} + +// ── Rows, tables, tabs, scroll bars and progress bars ─────────────────── + +/// Every plain rectangle in `frame` (`Canvas::fill_rect`): its colour in 8 +/// bits and its rectangle. +fn decorations(frame: &RenderFrame) -> Vec<([u8; 4], [f32; 4])> { + frame + .decorations + .iter() + .map(|d| (rgba8(d.color), d.rect)) + .collect() +} + +/// A two-tab bar, the first tab selected, laid out at its own height. +fn tab_bar(vertical: bool) -> TabBar<(TabId, &'static str)> { + let first = TabId::fresh(); + let model = ListModel::from_vec(vec![(first, "Tab"), (TabId::fresh(), "Second")]); + let delegate = TabDelegate::new(|_, t: &(TabId, &'static str)| lit!(t.1)); + let selected: Signal> = Signal::new(Some(first)); + let bar = if vertical { + TabBar::vertical(model, delegate, selected, |_, t| t.0) + } else { + TabBar::horizontal(model, delegate, selected, |_, t| t.0) + }; + bar.tab_sizing(TabSizing::Independent) + .show_scroll_arrows(false) + .show_overflow_dropdown(false) +} + +fn name_column(label: fn(&T) -> String) -> Column { + Column::new("name", lit!("Name"), move |row: &T, _| { + Box::new(TextWidget::new(lit!(label(row)))) + }) + .width(ColumnWidth::Fixed(160.0)) +} + +fn table() -> TableView { + let model = ListModel::from_vec(vec!["alpha".to_string(), "beta".into(), "gamma".into()]); + TableView::new(model).add_column(name_column(|s: &String| s.clone())) +} + +fn tree_table() -> TreeTableView { + let model: TreeModel = TreeModel::new(); + for (i, name) in ["alpha", "beta"].into_iter().enumerate() { + model.insert_root(i, name.to_string()); + } + TreeTableView::new(model).add_column(name_column(|s: &String| s.clone())) +} + +fn scroll_bar(orientation: ScrollBarOrientation, variant: ScrollBarVariant) -> ScrollBar { + ScrollBar::new( + orientation, + Signal::new(0.0), + Signal::new(100.0), + Signal::new(0.16), + ) + .thickness(21.0) + .variant(variant) +} + +/// Every widget this section restyles, in a column, for the swap, the +/// palette-less and the swatch tests. +fn rows_and_bars() -> VStack { + VStack::new() + .child( + FixedSize::new() + .width(300.0) + .child(StandardListItem::new(lit!("Row"))), + ) + .child( + FixedSize::new() + .width(300.0) + .child(StandardListItem::new(lit!("Picked")).selected(Signal::new(true))), + ) + .child( + FixedSize::new() + .width(300.0) + .child(StandardTreeItem::new(lit!("Node")).depth(1)), + ) + .child(FixedSize::new().width(300.0).height(120.0).child(table())) + .child(tab_bar(false)) + .child(FixedSize::new().width(21.0).height(100.0).child(scroll_bar( + ScrollBarOrientation::Vertical, + ScrollBarVariant::Permanent, + ))) + .child(FixedSize::new().width(200.0).child(ProgressBar::new(0.4))) + .child( + FixedSize::new() + .width(200.0) + .child(ProgressBar::indeterminate()), + ) +} + +#[test] +fn rows_tables_tabs_and_bars_mount_in_every_state_and_variant() { + for (_, theme) in themes() { + for row in [ + StandardListItem::new(lit!("Row")), + StandardListItem::new(lit!("Row")).selected(Signal::new(true)), + StandardListItem::new(lit!("Row")).checkbox(Signal::new(true)), + StandardListItem::new(lit!("Row")) + .leading_slot(IconWidget::checkmark(16.0)) + .subtitle(lit!("Below")), + StandardListItem::new(lit!("Row")) + .selected(Signal::new(true)) + .enabled(Signal::new(false)), + ] { + assert!(mount(theme.clone(), FixedSize::new().width(300.0).child(row)) > 0); + } + assert!( + mount( + theme.clone(), + FixedSize::new() + .width(300.0) + .child(StandardTreeItem::new(lit!("Node")).selected(Signal::new(true))) + ) > 0 + ); + assert!(mount(theme.clone(), table()) > 0); + assert!(mount(theme.clone(), tree_table()) > 0); + for vertical in [false, true] { + assert!(mount(theme.clone(), tab_bar(vertical)) > 0); + assert!( + mount( + theme.clone(), + tab_bar(vertical).active_indicator(TabIndicatorPosition::InnerEdge) + ) > 0 + ); + } + for orientation in [ + ScrollBarOrientation::Vertical, + ScrollBarOrientation::Horizontal, + ] { + for variant in [ + ScrollBarVariant::Permanent, + ScrollBarVariant::Overlay, + ScrollBarVariant::Thin, + ] { + assert!(mount(theme.clone(), scroll_bar(orientation, variant)) > 0); + } + } + for orientation in [Orientation::Horizontal, Orientation::Vertical] { + assert!( + mount( + theme.clone(), + ProgressBar::new(0.4).orientation(orientation) + ) > 0 + ); + assert!( + mount( + theme.clone(), + ProgressBar::indeterminate().orientation(orientation) + ) > 0 + ); + } + } +} + +#[test] +fn the_rows_and_bars_survive_a_theme_swap_and_every_swatch() { + let mut tree = tree_with(kde::light()); + tree.add(rows_and_bars()); + for theme in [ + kde::light(), + kde::dark(), + teksilo_core::presets::intui::light(), + kde::light(), + ] { + tree.set_theme(theme); + assert!(!rerender(&mut tree).draw_order.is_empty()); + } + for accent in PlasmaAccent::ALL { + for theme in [ + kde::light_with_accent(accent.color()), + kde::dark_with_accent(accent.color()), + ] { + assert!(mount(theme, rows_and_bars()) > 0, "{accent:?}"); + } + } + let mut bare = kde::light(); + bare.extensions.remove::(); + assert!(mount(bare, rows_and_bars()) > 0); +} + +/// A row of `width` holding `row`, alone at the top of a tree under `theme`. +fn row_tree(theme: Theme, width: f32, row: StandardListItem) -> (WidgetTree, WidgetId) { + let mut tree = tree_with(theme); + let column = tree.add(VStack::new().child(FixedSize::new().width(width).child(row))); + tree.layout(SizeProposal::exact(600.0, 300.0)); + let id = nodes_named(&tree, column, "StandardListItem")[0]; + (tree, id) +} + +/// The selection inside the row `b`: the row inset by the 2 px and 1 px +/// item margins, then by half the pen. +fn row_selection(b: teksilo_canvas::Rect) -> [f32; 4] { + [b.x + 2.5, b.y + 1.5, b.width - 5.0, b.height - 3.0] +} + +/// A selected row is filled and outlined with Plasma's highlight inside its +/// item margins, its label in the text on the accent, and the selection +/// spans the whole width the row is given. +#[test] +fn a_selected_row_is_the_highlight_with_on_accent_glyphs() { + for (scheme, theme) in themes() { + let accent = rgba8_of(theme.colors.accent); + assert_eq!(accent, sample(scheme, "item_view_row.selected", 100, 12)); + let on_accent = rgba8_of(theme.colors.text_on_accent); + let primary = rgba8_of(theme.colors.text_primary); + assert_ne!(on_accent, primary, "the flip is visible"); + for width in [200.0, 480.0] { + let (mut tree, id) = row_tree( + theme.clone(), + width, + StandardListItem::new(lit!("Row")).selected(Signal::new(true)), + ); + let b = tree.bounds(id); + assert_eq!((b.width, b.height), (width, 24.0), "{scheme}: the row"); + let frame = rerender(&mut tree); + let all = painted(&frame); + let fill = all + .iter() + .find(|p| p.stroke == 0.0 && p.color == accent) + .unwrap_or_else(|| panic!("{scheme}: no highlight in {all:?}")); + assert_eq!(fill.screen, row_selection(b), "{scheme}: fills the row"); + assert_eq!(fill.radius, 5.0, "{scheme}: Plasma's measured radius"); + assert!( + all.iter() + .any(|p| p.stroke == 1.0 && p.color == accent && p.screen == fill.screen), + "{scheme}: the opaque outline" + ); + let glyphs: Vec<_> = frame.glyphs.iter().map(|g| rgba8(g.color)).collect(); + assert!(!glyphs.is_empty()); + assert!( + glyphs.iter().all(|g| *g == on_accent), + "{scheme}: {glyphs:?}" + ); + } + } +} + +/// An unselected row at rest paints nothing but its label. +#[test] +fn an_unselected_row_paints_nothing() { + for (scheme, theme) in themes() { + let (mut tree, _) = row_tree(theme.clone(), 300.0, StandardListItem::new(lit!("Row"))); + let frame = rerender(&mut tree); + assert!( + painted(&frame).iter().all(|p| p.color[3] == 0), + "{scheme}: {:?}", + painted(&frame) + ); + assert!( + decorations(&frame).iter().all(|(c, _)| c[3] == 0), + "{scheme}: {:?}", + decorations(&frame) + ); + assert!(frame.paths.is_empty()); + let primary = rgba8_of(theme.colors.text_primary); + assert!(frame.glyphs.iter().all(|g| rgba8(g.color) == primary)); + } +} + +/// A hovered row is washed with the highlight at 30 % over the view and +/// outlined in it at 80 %, as Plasma paints it. +#[test] +fn a_hovered_row_is_washed_and_outlined_in_the_highlight() { + for (scheme, theme) in themes() { + let (mut tree, id) = row_tree(theme.clone(), 200.0, StandardListItem::new(lit!("Row"))); + tree.pointer_move(tree.bounds(id).center()); + let frame = rerender(&mut tree); + let b = tree.bounds(id); + let all = painted(&frame); + let wash = all + .iter() + .find(|p| p.stroke == 0.0 && p.screen == row_selection(b)) + .unwrap_or_else(|| panic!("{scheme}: no wash in {all:?}")); + assert!( + near(wash.color, sample(scheme, "item_view_row.hover", 100, 12)), + "{scheme}: {:?}", + wash.color + ); + let outline = all + .iter() + .find(|p| p.stroke == 1.0 && p.screen == row_selection(b)) + .expect("an outline"); + let mut want = rgba8_of(theme.colors.accent); + want[3] = 204; + assert_eq!(outline.color, want, "{scheme}: the highlight at 80 %"); + } +} + +/// In a backdrop window a selected row is Plasma's inactive highlight, the +/// window colour tinted toward the selection, and its label the window +/// text. +#[test] +fn a_selected_row_in_a_backdrop_window_is_the_inactive_highlight() { + for (scheme, theme) in themes() { + let (mut tree, _) = row_tree( + theme.clone(), + 200.0, + StandardListItem::new(lit!("Row")).selected(Signal::new(true)), + ); + tree.set_window_active(false); + let frame = rerender(&mut tree); + let want = sample(scheme, "item_view_row.selected_inactive_window", 100, 12); + assert!( + fills(&frame).contains(&want), + "{scheme}: {:?}", + fills(&frame) + ); + let primary = rgba8_of(theme.colors.text_primary); + assert!(frame.glyphs.iter().all(|g| rgba8(g.color) == primary)); + } +} + +/// Every filled path in `frame`: its colour in 8 bits and the path. +fn filled_paths(frame: &RenderFrame) -> Vec<([u8; 4], Path)> { + frame + .paths + .iter() + .filter(|p| p.stroke_style.width == 0.0) + .map(|p| (rgba8(p.color), p.path.clone())) + .collect() +} + +fn covers(path: &Path, x: f32, y: f32) -> bool { + path.contains_point(teksilo_canvas::Point::new(x, y), FillRule::Winding, 0.01) +} + +/// Whether `r` is `[x, y, w, h]` to a hundredth of a pixel. +fn about(r: teksilo_canvas::Rect, want: [f32; 4]) -> bool { + [r.x, r.y, r.width, r.height] + .iter() + .zip(want) + .all(|(a, b)| (a - b).abs() < 0.01) +} + +/// The headers of a two-tab bar: the selected one first. +fn tab_headers(theme: Theme, vertical: bool) -> (WidgetTree, Vec) { + let mut tree = tree_with(theme); + let bar = tree.add(tab_bar(vertical)); + tree.layout(SizeProposal { + width: Some(800.0), + height: if vertical { Some(400.0) } else { None }, + }); + let headers = nodes_named(&tree, bar, "TabHeader") + .into_iter() + .map(|h| tree.bounds(h)) + .collect(); + (tree, headers) +} + +/// The selected tab: the highlight in a 3 px bar along its top, its +/// rounded outer corners at the frame radius, the window colour below it +/// and the frame outline down its two sides, both still turning through the +/// bar's corner in rows 3 and 4; the unselected tab the window darkened, and +/// hovered washed toward the hover colour. Against Plasma's samples of an +/// 80 x 30 tab. +#[test] +fn the_selected_tab_has_breezes_three_pixel_bar() { + for (scheme, theme) in themes() { + let (mut tree, headers) = tab_headers(theme, false); + let (sel, other) = (headers[0], headers[1]); + assert_eq!(sel.height, 30.0, "{scheme}"); + let frame = tree.render(); + let paths = filled_paths(&frame); + let rects = decorations(&frame); + let highlight = sample(scheme, "tab.selected", 40, 1); + let bar = &paths + .iter() + .find(|(color, _)| *color == highlight) + .unwrap_or_else(|| panic!("{scheme}: no bar in {paths:?}")) + .1; + let bb = bar.exact_bounds(0.01); + assert!((bb.y - sel.y).abs() < 0.01 && (bb.height - 3.0).abs() < 0.01); + assert!(bb.x > sel.x && bb.x < sel.x + 0.5, "{scheme}: {bb:?}"); + assert!(covers(bar, sel.x + 40.0, sel.y + 1.5)); + assert!(!covers(bar, sel.x + 0.3, sel.y + 0.3), "the outer corner"); + let window = sample(scheme, "tab.selected", 40, 15); + let face = &paths + .iter() + .find(|(color, _)| *color == window) + .unwrap_or_else(|| panic!("{scheme}: no tab face in {paths:?}")) + .1; + assert!( + about( + face.bounds(), + [sel.x + 1.0, sel.y + 3.0, sel.width - 2.0, sel.height - 3.0] + ), + "{scheme}: the bar shows 3 px: {:?}", + face.bounds() + ); + let outline = sample(scheme, "tab.selected", 0, 15); + let sides = &paths + .iter() + .find(|(color, _)| near(*color, outline)) + .unwrap_or_else(|| panic!("{scheme}: no outline in {paths:?}")) + .1; + assert!(about( + sides.bounds(), + [sel.x, sel.y + 3.0, sel.width, sel.height - 3.0] + )); + for x in [sel.x + 0.5, sel.x + sel.width - 0.5] { + assert!( + covers(sides, x, sel.y + 15.0) && !covers(face, x, sel.y + 15.0), + "{scheme}: the outline down both sides" + ); + } + // Rows 3 and 4: the corner is still turning, as Plasma's column 0 + // is still blended there; the body is not square below the bar. + assert!(!covers(sides, sel.x + 0.1, sel.y + 3.1), "{scheme}"); + assert!(covers(sides, sel.x + 0.5, sel.y + 4.5), "{scheme}"); + assert!(!covers(face, sel.x + 0.9, sel.y + 4.5), "{scheme}"); + assert_ne!( + sample(scheme, "tab.selected", 0, 4), + outline, + "{scheme}: Plasma's corner at row 4" + ); + let other_rect = [other.x, other.y, other.width, other.height]; + let unselected = rects + .iter() + .find(|(_, r)| *r == other_rect) + .unwrap_or_else(|| panic!("{scheme}: no unselected tab")); + assert!(near(unselected.0, sample(scheme, "tab.unselected", 40, 15))); + tree.pointer_move(other.center()); + tree.layout(SizeProposal { + width: Some(800.0), + height: None, + }); + let frame = tree.render(); + let hovered = decorations(&frame) + .into_iter() + .find(|(_, r)| *r == other_rect) + .unwrap(); + assert!( + near(hovered.0, sample(scheme, "tab.hover", 40, 15)), + "{scheme}: {:?}", + hovered.0 + ); + } +} + +/// A vertical bar's selected tab carries its bar on the leading side, the +/// left, and on the right in a right-to-left window. +#[test] +fn a_vertical_tabs_bar_is_on_its_leading_side() { + for rtl in [false, true] { + let mut tree = tree_with(kde::light()); + if rtl { + tree.set_layout_direction(teksilo_core::environment::LayoutDirection::RightToLeft); + } + let bar = tree.add(tab_bar(true)); + tree.layout(SizeProposal::exact(400.0, 400.0)); + let sel = tree.bounds(nodes_named(&tree, bar, "TabHeader")[0]); + let frame = tree.render(); + let window = sample("BreezeLight", "tab.selected", 40, 15); + let (_, face) = filled_paths(&frame) + .into_iter() + .find(|(color, _)| *color == window) + .expect("a tab face"); + let x = if rtl { sel.x } else { sel.x + 3.0 }; + assert!( + about( + face.bounds(), + [x, sel.y + 1.0, sel.width - 3.0, sel.height - 2.0] + ), + "rtl {rtl}: {:?}", + face.bounds() + ); + } +} + +/// A 21 px scroll bar: the separator in its leading column in the view's +/// text at the frame contrast, Plasma's pixels, and the handle 7 px from it +/// in the lifted thumb colour, 8 px wide painted 7 at radius 4, turning to +/// the lifted hover colour under the pointer. No track. +#[test] +fn the_scroll_bar_is_a_separator_and_a_handle_over_no_track() { + for (scheme, theme) in themes() { + let mut tree = tree_with(theme.clone()); + let bar = tree.add(scroll_bar( + ScrollBarOrientation::Vertical, + ScrollBarVariant::Permanent, + )); + tree.layout(SizeProposal::exact(21.0, 200.0)); + let frame = tree.render(); + let all = painted(&frame); + let window = rgba8_of(theme.colors.surface_main); + let separator = decorations(&frame) + .into_iter() + .find(|(_, r)| *r == [0.0, 0.0, 1.0, 200.0]) + .unwrap_or_else(|| panic!("{scheme}: no separator")); + assert!(near( + over(separator.0, window), + sample(scheme, "scroll_bar.normal", 0, 150) + )); + let thumb = rgba8_of(theme.colors.scrollbar_thumb); + let handle = all + .iter() + .find(|p| p.stroke == 0.0 && p.color == thumb) + .unwrap_or_else(|| panic!("{scheme}: no handle")); + assert_eq!(handle.screen, [7.5, 0.5, 7.0, 31.0], "{scheme}"); + // Breeze's radius 4, which the renderer holds to half the painted + // width. + assert_eq!(handle.radius, 3.5); + assert!( + all.iter() + .any(|p| p.stroke == 1.0 && p.screen == handle.screen), + "{scheme}: the handle's outline" + ); + assert_eq!(all.len(), 2, "{scheme}: no track: {all:?}"); + assert_eq!(frame.decorations.len(), 1, "{scheme}: the separator alone"); + tree.pointer_move(tree.bounds(bar).center()); + tree.layout(SizeProposal::exact(21.0, 200.0)); + let frame = tree.render(); + let hover = rgba8_of(theme.colors.scrollbar_thumb_hover); + assert!(fills(&frame).contains(&hover), "{scheme}"); + } +} + +/// A determinate bar is Qt's 26 px, its 6 px groove 10 px down, the groove +/// and the contents to the half way point in Plasma's colours; right to +/// left the contents start at the right. +#[test] +fn the_progress_bar_is_breezes_groove_and_contents() { + for (scheme, theme) in themes() { + for rtl in [false, true] { + let mut tree = tree_with(theme.clone()); + if rtl { + tree.set_layout_direction(teksilo_core::environment::LayoutDirection::RightToLeft); + } + let column = tree.add( + VStack::new().child(FixedSize::new().width(200.0).child(ProgressBar::new(0.5))), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let bar = nodes_named(&tree, column, "ProgressBar")[0]; + let b = tree.bounds(bar); + assert_eq!(b.height, 26.0, "{scheme}: Qt's hint"); + let frame = rerender(&mut tree); + let all = painted(&frame); + let groove = all + .iter() + .find(|p| p.stroke == 0.0 && p.screen == [b.x + 0.5, b.y + 10.5, 199.0, 5.0]) + .unwrap_or_else(|| panic!("{scheme}: no groove in {all:?}")); + if scheme == "BreezeLight" { + assert!(near( + groove.color, + sample(scheme, "progress_bar.half", 150, 9) + )); + } + // Breeze's radius 3, held to half the stroked groove. + assert_eq!(groove.radius, 2.5); + let x = if rtl { b.x + 100.5 } else { b.x + 0.5 }; + let contents = all + .iter() + .find(|p| p.stroke == 0.0 && p.screen == [x, b.y + 10.5, 99.0, 5.0]) + .unwrap_or_else(|| panic!("{scheme} rtl {rtl}: no contents in {all:?}")); + if scheme == "BreezeLight" { + assert!(near( + contents.color, + sample(scheme, "progress_bar.half", 50, 9) + )); + } + assert!(frame.animated_quads.is_empty()); + } + } +} + +/// A busy bar is the style's stripes, 14 px of the highlight every 28 px, +/// which move forward 7 px in 200 ms and 14 px in 400 ms on the tree's clock and stand still under +/// reduced motion. The bar draws no sweep of its own. +#[test] +fn a_busy_progress_bar_moves_its_stripes() { + let theme = kde::light(); + let highlight = rgba8_of(theme.colors.accent); + let stripes = |frame: &RenderFrame| -> Vec<[f32; 4]> { + painted(frame) + .into_iter() + .filter(|p| p.stroke == 0.0 && p.color == highlight) + .map(|p| p.screen) + .collect() + }; + let (mut tree, bar) = on_the_clock( + theme.clone(), + false, + FixedSize::new() + .width(200.0) + .child(ProgressBar::indeterminate()), + ); + let x0 = tree.bounds(bar).x; + let first = stripes(&rerender(&mut tree)); + assert!(rerender(&mut tree).animated_quads.is_empty()); + assert_eq!(first[0][0] - x0, 0.0); + assert_eq!(first[0][2], 14.0); + assert_eq!(first[1][0] - first[0][0], 28.0); + // A quarter of a period on, the stripes have moved 7 px forward, toward + // the end of the groove: moving back would put a stripe at the start. + let quarter = stripes(&tick(&mut tree, 200)); + assert!( + (quarter[0][0] - x0 - 7.0).abs() < 1.0, + "a quarter period on: {quarter:?}" + ); + assert!((quarter[0][2] - 14.0).abs() < 1.0, "{quarter:?}"); + let later = stripes(&tick(&mut tree, 200)); + assert!( + (later[0][0] - x0 - 14.0).abs() < 1.0 || (later[1][0] - x0 - 14.0).abs() < 1.0, + "half a period on: {later:?}" + ); + let (mut still, _) = on_the_clock( + theme, + true, + FixedSize::new() + .width(200.0) + .child(ProgressBar::indeterminate()), + ); + let at_rest = stripes(&rerender(&mut still)); + assert_eq!(stripes(&tick(&mut still, 400)), at_rest); +} + +/// A table's header sections are the button colour, outlined at the bottom +/// and trailing edge in the window text at the frame contrast, 30 px tall +/// over 24 px rows; hovered and resized they are Plasma's hovered and +/// sunken sections, and right to left the trailing line is on the left. +#[test] +fn a_table_header_is_breezes_section() { + for (scheme, theme) in themes() { + for rtl in [false, true] { + let mut tree = tree_with(theme.clone()); + if rtl { + tree.set_layout_direction(teksilo_core::environment::LayoutDirection::RightToLeft); + } + let root = tree.add(table()); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let header = nodes_named(&tree, root, "BreezeHeaderSection"); + assert_eq!(header.len(), 1); + let h = tree.bounds(header[0]); + assert_eq!(h.height, 30.0); + let frame = tree.render(); + let rects = decorations(&frame); + let face = rects + .iter() + .find(|(_, r)| *r == [h.x, h.y, h.width, h.height]) + .unwrap_or_else(|| panic!("{scheme}: no section face in {rects:?}")); + assert_eq!(face.0, sample(scheme, "header.normal", 50, 15), "{scheme}"); + let line = rects + .iter() + .find(|(_, r)| *r == [h.x, h.y + 29.0, h.width, 1.0]) + .expect("the bottom line"); + assert!(near( + over(line.0, face.0), + sample(scheme, "header.normal", 50, 29) + )); + let x = if rtl { h.x } else { h.x + h.width - 1.0 }; + let trailing = rects + .iter() + .find(|(_, r)| *r == [x, h.y, 1.0, 30.0]) + .unwrap_or_else(|| panic!("{scheme} rtl {rtl}: no trailing line in {rects:?}")); + assert_eq!(trailing.0, line.0); + } + } +} + +/// The face of the one header section of `root`. +fn header_face(tree: &mut WidgetTree, root: WidgetId) -> [u8; 4] { + tree.layout(SizeProposal::exact(400.0, 300.0)); + let h = tree.bounds(nodes_named(tree, root, "BreezeHeaderSection")[0]); + let frame = tree.render(); + decorations(&frame) + .into_iter() + .find(|(_, r)| *r == [h.x, h.y, h.width, h.height]) + .unwrap_or_else(|| panic!("no section face at {h:?}")) + .0 +} + +/// Hover the section of a table in `theme`, then grab its trailing edge and +/// drag it: the face hovered, and the face while the column resizes. +fn hovered_and_resizing_faces(theme: Theme) -> ([u8; 4], [u8; 4]) { + use teksilo_core::event::WidgetEvent; + let mut tree = tree_with(theme); + let root = tree.add(table()); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let h = tree.bounds(nodes_named(&tree, root, "BreezeHeaderSection")[0]); + tree.pointer_move(h.center()); + let hovered = header_face(&mut tree, root); + let grip = teksilo_canvas::Point::new(h.x + h.width - 1.0, h.y + h.height * 0.5); + let to = teksilo_canvas::Point::new(grip.x + 10.0, grip.y); + tree.pointer_move(grip); + tree.dispatch_event(WidgetEvent::pointer_down( + grip, + PointerButton::Primary, + Modifiers::NONE, + )); + tree.dispatch_event(WidgetEvent::pointer_move(to)); + let resizing = header_face(&mut tree, root); + tree.dispatch_event(WidgetEvent::pointer_up( + to, + PointerButton::Primary, + Modifiers::NONE, + )); + (hovered, resizing) +} + +/// Under the pointer the section is Plasma's hovered one; grabbed by its +/// trailing edge to resize the column, Plasma's sunken one. +#[test] +fn a_hovered_or_resized_header_section_is_plasmas() { + for (scheme, theme) in themes() { + let (hovered, resizing) = hovered_and_resizing_faces(theme); + assert_eq!(hovered, sample(scheme, "header.hover", 50, 15), "{scheme}"); + assert_eq!( + resizing, + sample(scheme, "header.sunken", 50, 15), + "{scheme}" + ); + } +} + +/// Breeze's schemes give the view's focus and hover decorations one +/// colour, so Plasma's hovered and sunken sections match. In a scheme where +/// they differ, the hovered section moves toward the hover colour and the +/// one being resized toward the focus colour. +#[test] +fn a_resized_header_section_turns_toward_the_focus_colour() { + let (scheme, _) = teksilo_kde_scheme::KdeColorScheme::parse( + "[Colors:View]\nDecorationFocus=61,174,233\nDecorationHover=246,116,0\n", + ); + let theme = kde::from_scheme(&scheme); + let colors = BreezeHeaderColors::resolve(&theme, true, false); + assert_ne!(colors.hovered, colors.pressed, "the premise"); + let (hovered, resizing) = hovered_and_resizing_faces(theme); + assert_eq!(hovered, rgba8_of(colors.hovered)); + assert_eq!(resizing, rgba8_of(colors.pressed)); +} + +/// The column sorted down, Qt's indicator for an ascending sort, is +/// painted as Plasma's pressed section; a descending column is not. +#[test] +fn the_column_sorted_down_is_a_pressed_section() { + for (scheme, theme) in themes() { + for (direction, want) in [ + (SortDirection::Ascending, "header.sunken"), + (SortDirection::Descending, "header.normal"), + ] { + let mut tree = tree_with(theme.clone()); + let model = ListModel::from_vec(vec!["alpha".to_string(), "beta".into()]); + let root = tree.add( + TableView::new(model) + .add_column(name_column(|s: &String| s.clone()).sortable(true)) + .bind_sort(Signal::new(Some(("name".to_string(), direction)))), + ); + assert_eq!( + header_face(&mut tree, root), + sample(scheme, want, 50, 15), + "{scheme} {direction:?}" + ); + } + } +} diff --git a/crates/teksilo-theme-kde/tests/native.rs b/crates/teksilo-theme-kde/tests/native.rs new file mode 100644 index 000000000..5cb0bdec7 --- /dev/null +++ b/crates/teksilo-theme-kde/tests/native.rs @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The live desktop: `from_desktop` follows a KDE desktop's fonts, +//! animation speed and `breezerc`, and `native_theme` answers only for a +//! desktop styled with Breeze, through `teksilo-app`'s native-theme seam. + +use std::cell::RefCell; +use std::rc::Rc; +use std::time::Duration; + +use teksilo_app::{NativeThemeFactory, NativeThemeRequest, resolve_native_theme}; +use teksilo_canvas::{MockTextBackend, RenderFrame, SizeProposal}; +use teksilo_core::styles::Theme; +use teksilo_core::widget::Widget; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_i18n::lit; +use teksilo_platform::os_theme::NativeDesktop; +use teksilo_theme_kde::scheme::{KdeColor, KdeColorScheme, KdeDesktop, KdeFont, ShadowSize}; +use teksilo_theme_kde::styles::popup::{BreezePopupColors, BreezePopupKind}; +use teksilo_theme_kde::{self as kde, BreezeStyleConfig, KdeFontRoles, KdePalette}; +use teksilo_tokens::{Color, FontWeight, TargetDensity}; +use teksilo_widgets::primitives::{FixedSize, HStack}; +use teksilo_widgets::{Button, MenuItem, MenuList, ProgressBar, VStack}; + +fn light_desktop() -> KdeDesktop { + KdeDesktop::new(KdeColorScheme::breeze_light()) +} + +fn font(qt: &str) -> KdeFont { + KdeFont::parse_qfont(qt).unwrap_or_else(|| panic!("{qt} does not parse")) +} + +fn line_box(style: &teksilo_tokens::TextStyle) -> f32 { + style.size * style.line_height +} + +fn tree_with(theme: Theme) -> WidgetTree { + WidgetTree::new() + .with_theme(theme) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))) +} + +fn rgba8(c: [f32; 4]) -> [u8; 4] { + c.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) +} + +fn ms(d: Duration) -> u128 { + d.as_millis() +} + +// ── Fonts ──────────────────────────────────────────────────────────────── + +/// A desktop whose five fonts are all set: a Qt 6 general font of a family +/// the preset has no metrics for, a 9 pt smallest readable font, an 11 pt +/// Hack, a 12 pt menu font and a Qt 5 toolbar font. +fn desktop_with_fonts() -> KdeDesktop { + let mut d = light_desktop(); + d.fonts.general = font("Cantarell,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"); + d.fonts.small = font("Noto Sans,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"); + d.fonts.fixed = font("Hack,11,-1,7,400,0,0,0,1,0,0,0,0,0,0,1"); + d.fonts.menu = font("Noto Sans,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"); + d.fonts.toolbar = font("Ubuntu,10,-1,5,50,0,0,0,0,0"); + d +} + +/// Each text style takes its font's family and the whole pixel size Qt +/// draws it at, on Qt's line box for Noto Sans and Hack and on +/// `round(P x 1.362)` for any other family; the weights stay the slots'. +#[test] +fn the_desktops_fonts_become_the_text_styles() { + let t = kde::from_desktop(&desktop_with_fonts(), TargetDensity::Compact).typography; + // Cantarell 11 pt: a 15 px face, 15 x 1.362 = 20.43. + for (style, weight) in [ + (&t.body, FontWeight::REGULAR), + (&t.body_bold, FontWeight::SEMI_BOLD), + ] { + assert_eq!(style.family, "Cantarell"); + assert_eq!(style.size, 15.0); + assert!((line_box(style) - 20.0).abs() < 1e-4); + assert_eq!(style.weight, weight); + assert_eq!(style.letter_spacing, 0.0); + } + // Noto Sans 9 pt: a 12 px face on Qt's 17 px line. + for style in [&t.small, &t.small_bold, &t.tiny] { + assert_eq!(style.family, "Noto Sans"); + assert_eq!(style.size, 12.0); + assert!((line_box(style) - 17.0).abs() < 1e-4); + } + assert_eq!(t.small_bold.weight, FontWeight::SEMI_BOLD); + // Hack 11 pt: a 15 px face on Qt's 18 px line. + assert_eq!(t.mono.family, "Hack"); + assert_eq!(t.mono.size, 15.0); + assert!((line_box(&t.mono) - 18.0).abs() < 1e-4); +} + +/// The menu and toolbar fonts, which have no text style, ride on the theme +/// as `KdeFontRoles`; a Qt 5 font string is read as Plasma 5 wrote it. +#[test] +fn the_menu_and_toolbar_fonts_ride_on_the_theme() { + let theme = kde::from_desktop(&desktop_with_fonts(), TargetDensity::Compact); + let roles = theme.extension::().expect("the font roles"); + assert_eq!(roles.menu.family, "Noto Sans"); + assert_eq!(roles.menu.size, 16.0); + assert!((line_box(&roles.menu) - 22.0).abs() < 1e-4); + assert_eq!(roles.toolbar.family, "Ubuntu"); + assert_eq!(roles.toolbar.size, 13.0); + assert!((line_box(&roles.toolbar) - 18.0).abs() < 1e-4); + assert_eq!(roles.toolbar.weight, FontWeight::REGULAR); +} + +/// The control heights follow the general font's line box, and a menu row +/// the taller of the menu font's and the body's. +#[test] +fn the_controls_follow_the_desktops_fonts() { + let theme = kde::from_desktop(&desktop_with_fonts(), TargetDensity::Compact); + // A 22 px menu line: max(22, 20, 16) + 1 + 2 x 4. + let menu = theme.style_slots.menu_item.as_ref().expect("a menu style"); + assert_eq!(menu.metrics().item_height, 31.0); + // A 20 px body line: the push button is H + 16. + let mut tree = tree_with(theme); + let row = tree.add(HStack::new().child(Button::new(lit!("OK")))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let button = tree.children(row)[0]; + assert_eq!(tree.bounds(button).height, 36.0); + + // A menu font smaller than the body leaves the rows sized for the body, + // which their labels are written in: a 20 px line gives 29. + let mut d = desktop_with_fonts(); + d.fonts.menu = font("Noto Sans,7,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"); + let theme = kde::from_desktop(&d, TargetDensity::Compact); + let menu = theme.style_slots.menu_item.as_ref().unwrap(); + assert_eq!(menu.metrics().item_height, 29.0); +} + +/// The density the theme is asked at reprojects the rows built for the +/// desktop's menu font too. +#[test] +fn the_menu_font_survives_a_density_projection() { + let theme = kde::from_desktop(&desktop_with_fonts(), TargetDensity::Touch); + assert_eq!(theme.input.density, TargetDensity::Touch); + let back = theme.with_density(TargetDensity::Compact); + let menu = back.style_slots.menu_item.as_ref().unwrap(); + assert_eq!(menu.metrics().item_height, 31.0); +} + +// ── Animation speed ────────────────────────────────────────────────────── + +/// A factor of 2 doubles Kirigami's long duration and everything derived +/// from it, and leaves the busy period and the tooltip delays alone. +#[test] +fn a_factor_of_two_slows_the_transitions() { + let mut d = light_desktop(); + d.animation_duration_factor = Some(2.0); + let m = kde::from_desktop(&d, TargetDensity::Compact).motion; + assert_eq!(ms(m.duration_fast), 200); + assert_eq!(ms(m.duration_normal), 400); + assert_eq!(ms(m.duration_slow), 800); + assert_eq!(ms(m.duration_collapse), 400); + assert_eq!(ms(m.duration_indeterminate_sweep), 800); + assert_eq!(ms(m.tooltip_delay), 700); + assert_eq!(ms(m.tooltip_delay_heavy), 700); + assert_eq!(m.duration_instant, Duration::ZERO); + assert_eq!(m.easing_standard, kde::light().motion.easing_standard); +} + +/// A factor of 0 is the desktop asking for reduced motion: the theme +/// reports it, and its transitions are 0 or 1 ms long. +#[test] +fn a_factor_of_zero_is_reduced_motion() { + let mut d = light_desktop(); + d.animation_duration_factor = Some(0.0); + let theme = kde::from_desktop(&d, TargetDensity::Compact); + let config = theme.extension::().expect("the config"); + assert!(config.prefers_reduced_motion()); + assert!(d.prefers_reduced_motion()); + let m = &theme.motion; + assert_eq!(ms(m.duration_fast), 0); + assert_eq!(ms(m.duration_normal), 1); + assert_eq!(ms(m.duration_slow), 2); + assert_eq!(ms(m.duration_collapse), 1); + assert_eq!(ms(m.duration_indeterminate_sweep), 800); + assert_eq!(ms(m.tooltip_delay), 700); +} + +/// A desktop that sets no factor animates at Breeze's own speed, and does +/// not ask for reduced motion. +#[test] +fn no_factor_is_the_default_speed() { + let theme = kde::from_desktop(&light_desktop(), TargetDensity::Compact); + assert_eq!(theme.motion, kde::light().motion); + let config = theme.extension::().unwrap(); + assert!(!config.prefers_reduced_motion()); + assert_eq!(config.animation_factor, None); +} + +// ── breezerc ───────────────────────────────────────────────────────────── + +/// A menu under `MenuOpacity` 80 is filled at 80 % and outlined in the +/// window text at 25 %; at the default 100 it is the opaque panel. +#[test] +fn a_translucent_menu_follows_menu_opacity() { + let mut d = light_desktop(); + d.breeze.menu_opacity = 80; + let theme = kde::from_desktop(&d, TargetDensity::Compact); + let config = theme.extension::().unwrap(); + assert_eq!(config.menu_opacity(), 0.8); + let colors = BreezePopupColors::resolve(&theme, BreezePopupKind::Menu); + let fill = rgba8(colors.fill.to_array()); + assert_eq!(fill, [244, 245, 245, 204]); + let outline = theme.colors.text_primary.with_alpha(0.25); + assert_eq!(colors.outline, outline); + + // Painted: the menu's panel carries those colours. + let mut tree = tree_with(theme.clone()); + tree.add( + VStack::new().child( + FixedSize::new() + .width(200.0) + .child(MenuList::new().item(MenuItem::new(lit!("Open")))), + ), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let frame = tree.render(); + let shapes: Vec<([u8; 4], f32)> = frame + .shapes + .iter() + .map(|s| (rgba8(s.color), s.stroke_width)) + .collect(); + assert!( + shapes.contains(&(fill, 0.0)), + "no translucent panel in {shapes:?}" + ); + assert!( + shapes.contains(&(rgba8(outline.to_array()), 1.0)), + "no window-text outline in {shapes:?}" + ); + + // A popover is not a menu, and stays opaque. + let popover = BreezePopupColors::resolve(&theme, BreezePopupKind::Panel); + assert_eq!(popover.fill.a(), 1.0); + // The stock opacity paints the opaque panel. + let stock = kde::from_desktop(&light_desktop(), TargetDensity::Compact); + assert_eq!( + BreezePopupColors::resolve(&stock, BreezePopupKind::Menu), + BreezePopupColors::resolve(&stock, BreezePopupKind::Panel) + ); +} + +/// The window shadow under a popup is the size, colour and strength +/// `breezerc` asks for. +#[test] +fn the_window_shadow_follows_breezerc() { + let mut d = light_desktop(); + d.breeze.shadow_size = ShadowSize::Small; + d.breeze.shadow_strength = 102; + d.breeze.shadow_color = KdeColor::rgb8(0, 0, 128); + let shape = kde::from_desktop(&d, TargetDensity::Compact).shape; + let navy = KdeColor::rgb8(0, 0, 128).to_color(); + assert_eq!( + (shape.shadow_lg.offset_y, shape.shadow_lg.blur), + (3.0, 12.0) + ); + assert!((shape.shadow_lg.color.a() - 0.26 * 0.4).abs() < 1e-6); + assert_eq!(shape.shadow_lg.color.with_alpha(1.0), navy); + assert_eq!( + (shape.shadow_inner_lg.offset_y, shape.shadow_inner_lg.blur), + (1.0, 6.0) + ); + assert!((shape.shadow_inner_lg.color.a() - 0.16 * 0.4).abs() < 1e-6); + // The rest of the shape is Breeze's. + let stock = kde::light().shape; + assert_eq!(shape.radius_control, stock.radius_control); + assert_eq!(shape.shadow_xs, stock.shadow_xs); + + d.breeze.shadow_size = ShadowSize::None; + let bare = kde::from_desktop(&d, TargetDensity::Compact).shape; + assert_eq!(bare.shadow_lg.color.a(), 0.0); + assert_eq!(bare.shadow_inner_lg.color.a(), 0.0); + assert_eq!( + kde::from_desktop(&light_desktop(), TargetDensity::Compact).shape, + stock + ); +} + +/// A tree holding `w` on the simulated clock, laid out and rendered once: +/// the tree and the widget's id. +fn on_the_clock(theme: Theme, w: impl Widget + 'static) -> (WidgetTree, WidgetId) { + let mut tree = tree_with(theme); + tree.advance_time(Duration::ZERO); + let column = tree.add(VStack::new().child(HStack::new().child(w))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let row = tree.children(column)[0]; + let id = tree.children(row)[0]; + tree.render(); + (tree, id) +} + +fn tick(tree: &mut WidgetTree, ms: u64) -> Rc { + tree.advance_time(Duration::from_millis(ms)); + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.render() +} + +/// The leading edges of a busy bar's highlight stripes, from the bar's. +fn stripe_starts(frame: &RenderFrame, highlight: Color, x0: f32) -> Vec { + let highlight = rgba8(highlight.to_array()); + frame + .shapes + .iter() + .filter(|s| s.stroke_width == 0.0 && rgba8(s.color) == highlight) + .map(|s| s.screen[0] - x0) + .collect() +} + +/// `ProgressBarBusyStepDuration` is the stripes' period, and +/// `ProgressBarAnimated` off stands them still. +#[test] +fn the_busy_bar_follows_breezerc() { + let mut d = light_desktop(); + d.breeze.progress_bar_busy_step_duration_ms = 400; + let theme = kde::from_desktop(&d, TargetDensity::Compact); + assert_eq!(ms(theme.motion.duration_indeterminate_sweep), 400); + let highlight = theme.colors.accent; + let bar = || { + FixedSize::new() + .width(200.0) + .child(ProgressBar::indeterminate()) + }; + let (mut tree, id) = on_the_clock(theme.clone(), bar()); + let x0 = tree.bounds(id).x; + // A quarter of the 400 ms period on, the stripes have moved 7 px. + let quarter = stripe_starts(&tick(&mut tree, 100), highlight, x0); + assert!( + (quarter[0] - 7.0).abs() < 1.0, + "a quarter period on: {quarter:?}" + ); + + d.breeze.progress_bar_animated = false; + let theme = kde::from_desktop(&d, TargetDensity::Compact); + let (mut still, id) = on_the_clock(theme.clone(), bar()); + let x0 = still.bounds(id).x; + let at_rest = stripe_starts(&tick(&mut still, 16), highlight, x0); + assert_eq!(at_rest[0], 0.0); + assert_eq!( + stripe_starts(&tick(&mut still, 300), highlight, x0), + at_rest + ); +} + +// ── native_theme ───────────────────────────────────────────────────────── + +/// Fusion, Oxygen and Kvantum desktops get no Breeze theme, in any case. +#[test] +fn another_widget_style_gets_no_breeze_theme() { + for style in [ + "Fusion", + "fusion", + "Oxygen", + "kvantum", + "Breeze-Dark", + "Windows", + ] { + let mut d = light_desktop(); + d.widget_style = Some(style.to_owned()); + assert!( + kde::native_theme(&d, TargetDensity::Compact).is_none(), + "{style}" + ); + } +} + +/// An absent or empty widget style is Breeze, and so is `breeze` in any +/// case: the theme is `from_desktop`'s, at the density asked for. +#[test] +fn a_breeze_desktop_gets_its_theme() { + for style in [ + None, + Some(""), + Some("Breeze"), + Some("breeze"), + Some("BREEZE"), + ] { + for (scheme, id) in [ + (KdeColorScheme::breeze_light(), "kde.light"), + (KdeColorScheme::breeze_dark(), "kde.dark"), + ] { + let mut d = KdeDesktop::new(scheme); + d.widget_style = style.map(str::to_owned); + d.animation_duration_factor = Some(0.5); + let theme = kde::native_theme(&d, TargetDensity::Comfortable) + .unwrap_or_else(|| panic!("{style:?}: no theme")); + assert_eq!(theme.id.as_str(), id, "{style:?}"); + assert_eq!(theme.input.density, TargetDensity::Comfortable); + assert_eq!(ms(theme.motion.duration_normal), 100); + let direct = kde::from_desktop(&d, TargetDensity::Comfortable); + assert_eq!(theme.colors, direct.colors); + assert_eq!(theme.typography, direct.typography); + assert_eq!(theme.input, direct.input); + assert_eq!( + theme.extension::(), + direct.extension::() + ); + } + } +} + +/// The report the platform builds for a KDE session: the scheme's colours +/// and the desktop itself. +fn native_desktop(desktop: KdeDesktop) -> NativeDesktop { + let mut native = NativeDesktop::new(desktop.scheme.os_theme_colors()); + native.kde = Some(desktop); + native +} + +/// The factory an app installs with `TeksiloAppBuilder::native_theme`. +fn factory() -> NativeThemeFactory { + Box::new(|request: &NativeThemeRequest<'_>| { + let desktop = request.desktop.kde.as_ref()?; + kde::native_theme(desktop, request.density) + }) +} + +/// Through the seam, a Breeze desktop's theme comes back as the factory +/// built it: `resolve_native_theme` sets no id and applies no density, so +/// it is `kde.dark` at the request's density until the window manager +/// names it `system`, which changes nothing else. +#[test] +fn the_theme_composes_through_the_native_seam() { + let mut d = KdeDesktop::new(KdeColorScheme::breeze_dark()); + d.breeze.menu_opacity = 90; + let native = native_desktop(d.clone()); + let factory = factory(); + let request = NativeThemeRequest::new(&native, true, TargetDensity::Touch); + let theme = resolve_native_theme(Some(&factory), &request); + assert_eq!(theme.id.as_str(), "kde.dark"); + assert_eq!(theme.input.density, TargetDensity::Touch); + assert!(theme.extension::().is_some()); + assert_eq!( + theme + .extension::() + .map(|c| c.menu_opacity()), + Some(0.9) + ); + + // What the window manager makes of it for a Touch window. + let shown = theme.with_density(TargetDensity::Touch).with_id("system"); + assert_eq!(shown.id.as_str(), "system"); + assert_eq!(shown.colors, theme.colors); + assert_eq!(shown.typography, theme.typography); + assert_eq!(shown.shape, theme.shape); + assert_eq!(shown.motion, theme.motion); + assert_eq!(shown.input, theme.input); + assert_eq!(shown.style_slots.installed(), theme.style_slots.installed()); + assert_eq!( + shown.extension::(), + theme.extension::() + ); + assert_eq!( + shown.extension::(), + theme.extension::() + ); + let menu = shown.style_slots.menu_item.as_ref().unwrap(); + assert_eq!(menu.metrics().item_height, 44.0); +} + +/// A desktop styled otherwise falls back to the framework's IntUI coloured +/// by the desktop's report, which carries no Breeze palette or chrome; so +/// does a report without KDE settings. +#[test] +fn another_style_falls_back_through_the_seam() { + let mut d = KdeDesktop::new(KdeColorScheme::breeze_dark()); + d.widget_style = Some("Fusion".to_owned()); + let native = native_desktop(d.clone()); + let factory = factory(); + let request = NativeThemeRequest::new(&native, true, TargetDensity::Compact); + let theme = resolve_native_theme(Some(&factory), &request); + assert!(theme.extension::().is_none()); + assert!(theme.extension::().is_none()); + let intui = teksilo_core::presets::intui::from_os(&native.os, true); + assert_eq!(theme.id, intui.id); + assert_eq!(theme.colors, intui.colors); + assert!(theme.is_dark()); + + let bare = NativeDesktop::new(d.scheme.os_theme_colors()); + let request = NativeThemeRequest::new(&bare, true, TargetDensity::Compact); + let theme = resolve_native_theme(Some(&factory), &request); + assert!(theme.extension::().is_none()); +} diff --git a/crates/teksilo-theme-kde/tests/preset_metrics.rs b/crates/teksilo-theme-kde/tests/preset_metrics.rs new file mode 100644 index 000000000..b4ab30c08 --- /dev/null +++ b/crates/teksilo-theme-kde/tests/preset_metrics.rs @@ -0,0 +1,1521 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! A dimension this preset writes must be the dimension the widget renders, +//! and at the Compact density it must be the one Plasma's Breeze reports. +//! +//! The unit tests beside each style check the numbers the styles are built +//! with. These mount the real widgets under the real preset, at every +//! density, inside a tappable row (the geometry that denies the miss-only +//! slop pass, so a target too small fails rather than being topped up), and +//! measure the laid-out nodes. The Compact sizes are checked against what Qt +//! measured for Breeze at Plasma's default font +//! (`tools/kde_golden/vectors/sizes.json`); this file reads that file from +//! the repository, so it is left out of the published package (the +//! `exclude` list in `Cargo.toml`). + +use std::cell::RefCell; +use std::rc::Rc; + +use serde_json::Value; +use teksilo_canvas::{MockTextBackend, SizeProposal}; +use teksilo_core::accessibility::target_audit::{ + PIN_TOLERANCE, gate::ALL_DENSITIES, measure_targets, target_audit, +}; +use teksilo_core::signal::Signal; +use teksilo_core::styles::Theme; +use teksilo_core::widget::Widget; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_i18n::lit; +use teksilo_target_conformance::in_tappable_row; +use teksilo_theme_kde::geometry::BreezeGeometry; +use teksilo_theme_kde::{self as kde}; +use teksilo_tokens::TargetDensity; +use teksilo_widgets::primitives::{FixedSize, HStack, IconWidget, VStack}; +use teksilo_widgets::{ + Button, ButtonVariant, Checkbox, ComboBox, IconButton, IconButtonSize, MenuItem, MenuList, + RadioButton, Slider, SpinBox, SplitButton, TextInput, Toggle, +}; + +const SIZES_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../tools/kde_golden/vectors/sizes.json" +)); + +/// The size hint Qt reported for the Breeze control `id` labelled `text` +/// (or unlabelled) at Plasma's default Noto Sans 10. +fn qt_size(id: &str, text: Option<&str>) -> (f32, f32) { + let all: Value = serde_json::from_str(SIZES_JSON).expect("sizes.json parses"); + let widget = all["widgets"] + .as_array() + .expect("the measured widgets") + .iter() + .find(|w| { + w["id"] == id + && w["font"] == "Noto Sans 10" + && text.is_none_or(|t| w["config"]["text"] == t) + }) + .unwrap_or_else(|| panic!("sizes.json has no {id}")); + let hint = &widget["size_hint"]; + ( + hint[0].as_f64().unwrap() as f32, + hint[1].as_f64().unwrap() as f32, + ) +} + +/// A tree under `theme` at `density` holding `w` in a tappable row, laid out. +fn in_a_row( + theme: &Theme, + density: TargetDensity, + w: impl Widget + 'static, +) -> (WidgetTree, WidgetId) { + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let root = in_tappable_row(&mut tree, w); + tree.layout(SizeProposal::exact(600.0, 200.0)); + (tree, root) +} + +/// Every node under `root` whose type name is `name`, in tree order. +fn nodes_named(tree: &WidgetTree, root: WidgetId, name: &str) -> Vec { + let mut out = Vec::new(); + fn walk(tree: &WidgetTree, id: WidgetId, name: &str, out: &mut Vec) { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(name)) + { + out.push(id); + } + for c in tree.children(id) { + walk(tree, c, name, out); + } + } + walk(tree, root, name, &mut out); + out +} + +/// The laid-out `(width, height)` of the one `name` node in a row holding +/// `w`, built at `density`, and whether the row passes the conformance gate. +fn measured( + theme: &Theme, + density: TargetDensity, + name: &str, + w: impl Widget + 'static, +) -> ((f32, f32), usize) { + let (tree, root) = in_a_row(theme, density, w); + let nodes = nodes_named(&tree, root, name); + assert_eq!(nodes.len(), 1, "one {name} in the row"); + let b = tree.bounds(nodes[0]); + let failures = target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(); + ((b.width, b.height), failures) +} + +fn themes() -> [Theme; 2] { + [kde::light(), kde::dark()] +} + +// ── Push buttons ─────────────────────────────────────────────────────── + +/// A push button is 84 x 34 at Compact, Qt's size for Breeze's "OK" and +/// "Cancel" buttons, and keeps that size whether it is the default button or +/// a flat one. Above Compact its height rises to the density's target size +/// (34 already clears Comfortable's 32, and Touch makes it 44); the 84 px +/// minimum width is above every target. +/// +/// The labels are short enough to sit inside the minimum width under the +/// mock text metrics, so the width measured is Breeze's own floor rather +/// than the mock's glyph widths. +#[test] +fn a_push_button_measures_breezes_size_at_every_density() { + let compact = qt_size("push_button", Some("OK")); + assert_eq!(compact, (84.0, 34.0)); + assert_eq!(qt_size("push_button", Some("Cancel")), compact); + assert_eq!(qt_size("push_button.default", Some("OK")), compact); + assert_eq!(qt_size("push_button.flat", Some("OK")), compact); + let expected = [ + (TargetDensity::Compact, compact), + (TargetDensity::Comfortable, (84.0, 34.0)), + (TargetDensity::Touch, (84.0, 44.0)), + ]; + assert_eq!(expected.len(), ALL_DENSITIES.len(), "every density"); + for (density, size) in expected { + for theme in themes() { + for variant in [ + ButtonVariant::Plain, + ButtonVariant::Filled, + ButtonVariant::Ghost, + ButtonVariant::Destructive, + ] { + for label in ["OK", "Cancel"] { + let (got, failures) = measured( + &theme, + density, + "Button", + Button::new(lit!(label)).variant(variant), + ); + assert_eq!(got, size, "{density:?} {variant:?} {label}"); + assert_eq!( + failures, 0, + "{density:?} {variant:?}: a conformance failure" + ); + } + } + } + } +} + +/// A label wider than the minimum grows the button by Breeze's 8 px gutter +/// on each side, scaled by the density's spacing. +#[test] +fn a_long_label_grows_the_button_by_the_breeze_gutters() { + // Under the mock metrics a character is 8 px wide. + let label = "Apply all the changes"; + let text = label.chars().count() as f32 * 8.0; + for (density, gutter) in [ + (TargetDensity::Compact, 8.0), + (TargetDensity::Comfortable, 9.2), + (TargetDensity::Touch, 10.4), + ] { + let ((w, _), _) = measured(&kde::light(), density, "Button", Button::new(lit!(label))); + assert!( + (w - (text + 2.0 * gutter)).abs() < 1e-3, + "{density:?}: {w}, want {}", + text + 2.0 * gutter + ); + } +} + +// ── Tool buttons ─────────────────────────────────────────────────────── + +/// An `IconButton` is Breeze's auto-raise tool button: 28 px around a 16 px +/// icon and 34 px around the 22 px toolbar icon at Compact, as Qt measures +/// them, rising to the density's target size above it. Every rung clears the +/// conformance floor at every density. +#[test] +fn an_icon_button_measures_breezes_tool_button_at_every_density() { + assert_eq!(qt_size("tool_button.auto_raise.16", None), (28.0, 28.0)); + assert_eq!(qt_size("tool_button.auto_raise.22", None), (34.0, 34.0)); + let expected = [ + (TargetDensity::Compact, [24.0, 28.0, 34.0, 44.0, 60.0]), + (TargetDensity::Comfortable, [32.0, 32.0, 34.0, 44.0, 60.0]), + (TargetDensity::Touch, [44.0, 44.0, 44.0, 44.0, 60.0]), + ]; + let sizes = [ + IconButtonSize::Compact, + IconButtonSize::Default, + IconButtonSize::Toolbar, + IconButtonSize::Large, + IconButtonSize::Hero, + ]; + for (density, squares) in expected { + for theme in themes() { + for (size, square) in sizes.into_iter().zip(squares) { + let (tree, _) = in_a_row( + &theme, + density, + IconButton::new(IconWidget::checkmark(12.0)).size(size), + ); + let button = measure_targets(&tree, density) + .into_iter() + .find(|m| m.widget.ends_with("IconButton") && m.part.is_none()) + .unwrap_or_else(|| panic!("{density:?}: the row holds an icon button")); + assert!( + (button.size.width - square).abs() < 1e-3 + && (button.size.height - square).abs() < 1e-3, + "{density:?} {size:?}: {:?}, want {square}", + button.size + ); + let floor = button.conformance_floor; + assert!( + button.expanded.width + PIN_TOLERANCE >= floor + && button.expanded.height + PIN_TOLERANCE >= floor, + "{density:?} {size:?}: {:?} against {floor}", + button.expanded + ); + assert_eq!( + target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(), + 0, + "{density:?} {size:?}: a conformance failure", + ); + } + } + } + // The two rungs Qt measured are the geometry's. + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.tool_button_height, 28.0); + assert_eq!(g.tool_button_height_medium, 34.0); +} + +/// The framed tool button, which `IconButton` has no form of, is still +/// recorded at Qt's sizes. +#[test] +fn the_framed_tool_button_heights_are_qts() { + let g = BreezeGeometry::breeze_default(); + assert_eq!( + qt_size("tool_button.framed.16", None), + (g.framed_tool_button_height, g.framed_tool_button_height) + ); + assert_eq!( + qt_size("tool_button.framed.22", None), + ( + g.framed_tool_button_height_medium, + g.framed_tool_button_height_medium + ) + ); +} + +// ── Split buttons ────────────────────────────────────────────────────── + +/// A `SplitButton` is framed as a push button: as tall as one at every +/// density (34, 34, 44), and at least its 84 px wide. +#[test] +fn a_split_button_is_as_tall_as_a_push_button_at_every_density() { + for (density, height) in [ + (TargetDensity::Compact, 34.0), + (TargetDensity::Comfortable, 34.0), + (TargetDensity::Touch, 44.0), + ] { + for theme in themes() { + for variant in [ + ButtonVariant::Plain, + ButtonVariant::Filled, + ButtonVariant::Ghost, + ] { + let ((w, h), failures) = measured( + &theme, + density, + "SplitButton", + SplitButton::new() + .item(MenuItem::new(lit!("Run"))) + .variant(variant), + ); + assert_eq!(h, height, "{density:?} {variant:?}"); + assert!(w >= 84.0, "{density:?} {variant:?}: {w} wide"); + assert_eq!( + failures, 0, + "{density:?} {variant:?}: a conformance failure" + ); + } + } + } +} + +// ── Check boxes and radio buttons ────────────────────────────────────── + +/// The row height a check box or radio button is raised to at `density`: +/// the density's target size, which the widgets' own minimum gives them. +const INDICATOR_ROWS: [(TargetDensity, f32); 3] = [ + (TargetDensity::Compact, 24.0), + (TargetDensity::Comfortable, 32.0), + (TargetDensity::Touch, 44.0), +]; + +/// Measure the indicator control `name` in a row holding `w`: its node, the +/// leading edge of its label from the node's, the painted 15 px box from the +/// node's corner, and the number of conformance failures. +/// +/// The row is laid out with no height proposed, so it takes its natural +/// height: in a taller one the widget fills the height it is given, whatever +/// the theme. +fn indicator_row( + theme: &Theme, + density: TargetDensity, + name: &str, + w: impl Widget + 'static, +) -> ((f32, f32), f32, [f32; 2], usize) { + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let root = in_tappable_row(&mut tree, w); + tree.layout(SizeProposal { + width: Some(600.0), + height: None, + }); + let node = nodes_named(&tree, root, name); + assert_eq!(node.len(), 1, "one {name} in the row"); + let b = tree.bounds(node[0]); + let label = nodes_named(&tree, node[0], "TextWidget"); + assert_eq!(label.len(), 1, "one label"); + let label_x = tree.bounds(label[0]).x - b.x; + let failures = target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(); + let frame = tree.render(); + let painted = frame + .shapes + .iter() + .find(|s| (s.screen[2] - 15.0).abs() < 0.01 && (s.screen[3] - 15.0).abs() < 0.01) + .unwrap_or_else(|| panic!("{density:?}: no 15 px box painted")); + let at = [painted.screen[0] - b.x, painted.screen[1] - b.y]; + ((b.width, b.height), label_x, at, failures) +} + +/// D8. Qt lays a labelled Breeze check box out in a 22 px row, its 20 px +/// indicator 1 px down. The node here is the density's target size, 24 px at +/// Compact, raised by the widget's own minimum; the indicator is still +/// painted at 20 (its 15 px box at 2.5 inside it), now centred 2 px down, and +/// the label starts 24 px from the leading edge, as Qt measures it, at every +/// density. +#[test] +fn d8_a_check_box_row_is_raised_and_its_indicator_is_breezes() { + assert_eq!(qt_size("check_box", Some("Check")).1, 22.0, "Breeze's row"); + assert_eq!(qt_size("check_box.no_text", None), (20.0, 20.0)); + let qt_contents_x = 24.0; + for (density, row) in INDICATOR_ROWS { + for theme in themes() { + let ((_, h), label_x, at, failures) = indicator_row( + &theme, + density, + "Checkbox", + Checkbox::new(Signal::new(false)).label(lit!("Check")), + ); + assert_eq!(h, row, "{density:?}"); + assert_eq!(label_x, qt_contents_x, "{density:?}: the label"); + assert_eq!(at, [2.5, (row - 20.0) * 0.5 + 2.5], "{density:?}: the box"); + assert_eq!(failures, 0, "{density:?}: a conformance failure"); + } + } +} + +/// The radio button is the check box's twin: Qt's 22 px row raised the same +/// way, the same indicator, the label at 24. +#[test] +fn d8_a_radio_button_row_is_raised_and_its_indicator_is_breezes() { + assert_eq!(qt_size("radio_button", Some("Radio")).1, 22.0); + assert_eq!(qt_size("radio_button.no_text", None), (20.0, 20.0)); + for (density, row) in INDICATOR_ROWS { + for theme in themes() { + let ((_, h), label_x, at, failures) = indicator_row( + &theme, + density, + "RadioButton", + RadioButton::new(0, Signal::new(1_usize)).label(lit!("Radio")), + ); + assert_eq!(h, row, "{density:?}"); + assert_eq!(label_x, 24.0, "{density:?}: the label"); + assert_eq!(at, [2.5, (row - 20.0) * 0.5 + 2.5], "{density:?}: the disc"); + assert_eq!(failures, 0, "{density:?}: a conformance failure"); + } + } +} + +// ── Switches ─────────────────────────────────────────────────────────── + +/// The switch's 36 x 18 indicator is painted unchanged, its 36 x 12 track +/// centred, in a node raised to the density's target on both axes. +#[test] +fn a_switch_paints_its_indicator_in_a_node_at_the_target_size() { + for (density, node) in [ + (TargetDensity::Compact, (36.0, 24.0)), + (TargetDensity::Comfortable, (36.0, 32.0)), + (TargetDensity::Touch, (44.0, 44.0)), + ] { + for theme in themes() { + let (mut tree, root) = in_a_row(&theme, density, Toggle::new(Signal::new(true))); + let toggle = nodes_named(&tree, root, "Toggle"); + assert_eq!(toggle.len(), 1); + let b = tree.bounds(toggle[0]); + assert_eq!((b.width, b.height), node, "{density:?}"); + assert_eq!( + target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(), + 0, + "{density:?}: a conformance failure" + ); + let frame = tree.render(); + let track = frame + .shapes + .iter() + .find(|s| s.stroke_width == 0.0 && s.screen[2] == 36.0 && s.screen[3] == 12.0) + .unwrap_or_else(|| panic!("{density:?}: no 36 x 12 track")); + assert_eq!( + [track.screen[0] - b.x, track.screen[1] - b.y], + [(node.0 - 36.0) * 0.5, (node.1 - 12.0) * 0.5], + "{density:?}: centred" + ); + } + } +} + +// ── Text fields ──────────────────────────────────────────────────────── + +/// A text field's frame is 32 px at Compact, Qt's line edit, rising to the +/// density's target size above it (32 already clears Comfortable's 32, and +/// Touch makes it 44). +#[test] +fn a_text_field_measures_breezes_line_edit_at_every_density() { + assert_eq!(qt_size("line_edit", None).1, 32.0); + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.line_edit_height, 32.0); + for (density, height) in [ + (TargetDensity::Compact, 32.0), + (TargetDensity::Comfortable, 32.0), + (TargetDensity::Touch, 44.0), + ] { + for theme in themes() { + let (tree, root) = + in_a_row(&theme, density, TextInput::new(Signal::new(String::new()))); + let input = nodes_named(&tree, root, "TextInput"); + assert_eq!(input.len(), 1); + // The framed field is the first child of the composite's root, + // above the validation strip it reserves. + let composite = tree.children(input[0])[0]; + let frame = tree.children(composite)[0]; + assert_eq!(tree.bounds(frame).height, height, "{density:?}"); + assert_eq!( + target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(), + 0, + "{density:?}: a conformance failure" + ); + } + } +} + +// ── Combo boxes ──────────────────────────────────────────────────────── + +/// A combo box is 32 px tall at Compact, Qt's Breeze combo box, rising to +/// the density's target size above it, with its arrow box 2 px inside the +/// trailing edge and its label 6 px inside the leading one (more at the +/// denser ladders, by their spacing). +#[test] +fn a_combo_box_measures_breezes_at_every_density() { + assert_eq!(qt_size("combo_box", None).1, 32.0); + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.combo_box_height, 32.0); + for (density, height, leading) in [ + (TargetDensity::Compact, 32.0, 6.0), + (TargetDensity::Comfortable, 32.0, 6.9), + (TargetDensity::Touch, 44.0, 7.8), + ] { + for theme in themes() { + let (tree, root) = in_a_row( + &theme, + density, + ComboBox::new(["Item", "Other"], Signal::new(Some("Item".to_string()))), + ); + let layout = nodes_named(&tree, root, "BreezeComboLayout"); + assert_eq!(layout.len(), 1); + let b = tree.bounds(layout[0]); + assert_eq!(b.height, height, "{density:?}"); + let arrow = nodes_named(&tree, layout[0], "BreezeComboArrow"); + let a = tree.bounds(arrow[0]); + assert_eq!( + [b.x + b.width - (a.x + a.width), a.width, a.height], + [2.0, 20.0, 20.0], + "{density:?}: the arrow box" + ); + assert_eq!(a.y - b.y, (height - 20.0) * 0.5, "{density:?}: centred"); + let label = nodes_named(&tree, layout[0], "TextWidget"); + assert!( + (tree.bounds(label[0]).x - b.x - leading).abs() < 1e-3, + "{density:?}: the label" + ); + assert_eq!( + target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(), + 0, + "{density:?}: a conformance failure" + ); + } + } +} + +// ── Spin boxes ───────────────────────────────────────────────────────── + +/// A spin box's frame is 32 px at Compact, Qt's Breeze spin box, rising to +/// the density's target size, and its arrow column is Breeze's at every +/// density: 20 px wide, 2 px inside the trailing edge, two 10 px halves +/// centred in the frame, where the step buttons press. +/// +/// The halves are under the target size, at every density; the value has +/// keyboard, wheel and assistive-action equivalents, so they are the only +/// conformance failures the row reports, one per half. +#[test] +fn a_spin_box_measures_breezes_at_every_density() { + assert_eq!(qt_size("spin_box.0_99", None).1, 32.0); + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.spin_box_height, 32.0); + for (density, height) in [ + (TargetDensity::Compact, 32.0), + (TargetDensity::Comfortable, 32.0), + (TargetDensity::Touch, 44.0), + ] { + for theme in themes() { + let (tree, root) = in_a_row(&theme, density, SpinBox::new(Signal::new(3_i32), 0, 10)); + let layout = nodes_named(&tree, root, "BreezeSpinLayout"); + assert_eq!(layout.len(), 1); + let b = tree.bounds(layout[0]); + assert_eq!(b.height, height, "{density:?}"); + let steps = nodes_named(&tree, layout[0], "StepButton"); + assert_eq!(steps.len(), 2); + let top = (height - 20.0) * 0.5; + for (step, y) in steps.iter().zip([top, top + 10.0]) { + let s = tree.bounds(*step); + assert_eq!( + [ + b.x + b.width - (s.x + s.width), + s.y - b.y, + s.width, + s.height + ], + [2.0, y, 20.0, 10.0], + "{density:?}: a half" + ); + } + let failures: Vec<_> = target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .collect(); + assert_eq!(failures.len(), 2, "{density:?}: {failures:?}"); + assert!( + failures.iter().all(|v| v.widget.ends_with("StepButton")), + "{density:?}: {failures:?}" + ); + } + } +} + +// ── Sliders ──────────────────────────────────────────────────────────── + +/// A slider is 84 px long when nothing proposes a length, Qt's size hint, +/// and 20 px thick (23 with a row of ticks), raised to the density's target +/// size across the groove: 24, 32 and 44. +#[test] +fn a_slider_measures_breezes_at_every_density() { + assert_eq!(qt_size("slider.horizontal", None), (84.0, 20.0)); + assert_eq!(qt_size("slider.horizontal.ticks_below", None), (84.0, 23.0)); + assert_eq!(qt_size("slider.vertical", None), (20.0, 84.0)); + use teksilo_tokens::Orientation; + for (density, cross) in [ + (TargetDensity::Compact, 24.0), + (TargetDensity::Comfortable, 32.0), + (TargetDensity::Touch, 44.0), + ] { + for theme in themes() { + for ticks in [None, Some(5)] { + for orientation in [Orientation::Horizontal, Orientation::Vertical] { + let mut slider = + Slider::new(Signal::new(0.5), 0.0, 1.0).orientation(orientation); + if let Some(n) = ticks { + slider = slider.tick_count(n); + } + let (tree, root) = in_a_row(&theme, density, slider); + let body = nodes_named(&tree, root, "BreezeSliderBody"); + assert_eq!(body.len(), 1); + let b = tree.bounds(body[0]); + let (along, across) = match orientation { + Orientation::Horizontal => (b.width, b.height), + Orientation::Vertical => (b.height, b.width), + }; + assert_eq!(across, cross, "{density:?} {orientation:?} {ticks:?}"); + if orientation == Orientation::Horizontal { + assert_eq!(along, 84.0, "{density:?} {ticks:?}"); + } + assert_eq!( + target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(), + 0, + "{density:?} {orientation:?}: a conformance failure" + ); + } + } + } + } +} + +// ── Menus ────────────────────────────────────────────────────────────── + +/// The rows Qt laid out for Breeze's menu with icons at Plasma's default +/// font: each row's kind and its `(x, y, height)` in the menu. +fn qt_menu_rows() -> Vec<(String, (f32, f32, f32))> { + let all: Value = serde_json::from_str(SIZES_JSON).expect("sizes.json parses"); + let menu = all["widgets"] + .as_array() + .expect("the measured widgets") + .iter() + .find(|w| w["id"] == "menu.with_icon" && w["font"] == "Noto Sans 10") + .expect("sizes.json has the menu"); + menu["rows"] + .as_array() + .expect("the menu's rows") + .iter() + .map(|r| { + let g = |i: usize| r["geometry"][i].as_f64().unwrap() as f32; + (r["kind"].as_str().unwrap().to_string(), (g(0), g(1), g(3))) + }) + .collect() +} + +/// A menu's rows are 29 px at Compact, Qt's height for every Breeze menu +/// item, and rise to the density's target size above it; a separator is +/// Breeze's 5 px, its gap scaled by the density's spacing; the rows sit +/// 4 px inside the frame, the menu margin, and every label starts after +/// the 12 px margin, the 16 px icon column and 3 px more. No row fails the +/// conformance gate at any density. +#[test] +fn a_menu_measures_breezes_rows_at_every_density() { + let qt = qt_menu_rows(); + let height_of = |kind: &str| { + qt.iter() + .find(|(k, _)| k == kind) + .unwrap_or_else(|| panic!("Qt measured no {kind} row")) + .1 + }; + for kind in ["item", "shortcut", "checkable", "submenu"] { + assert_eq!(height_of(kind).2, 29.0, "{kind}"); + } + assert_eq!(height_of("separator").2, 5.0); + assert_eq!(height_of("section").2, 28.0); + let (x, y, _) = height_of("item"); + assert_eq!((x, y), (4.0, 4.0), "Qt's menu margin"); + let compact = teksilo_theme_kde::styles::metrics::breeze_menu_metrics(); + assert_eq!(compact.section_height, height_of("section").2); + + for (density, item, separator, spacing) in [ + (TargetDensity::Compact, 29.0, 5.0, 1.0), + (TargetDensity::Comfortable, 32.0, 5.75, 1.15), + (TargetDensity::Touch, 44.0, 6.5, 1.3), + ] { + for theme in themes() { + let menu = MenuList::new() + .item(MenuItem::new(lit!("Open"))) + .item(MenuItem::new(lit!("Save")).shortcut_label("Ctrl+S")) + .separator() + .item(MenuItem::new(lit!("Checkable")).checked(Signal::new(true))) + .item(MenuItem::submenu(lit!("Submenu"), || { + Box::new(MenuList::new().item(MenuItem::new(lit!("Inner")))) + })); + let (mut tree, root) = + in_a_row(&theme, density, FixedSize::new().width(200.0).child(menu)); + let rows = nodes_named(&tree, root, "MenuItem"); + assert_eq!(rows.len(), 4); + for row in &rows { + assert_eq!(tree.bounds(*row).height, item, "{density:?}"); + } + let sep = nodes_named(&tree, root, "MenuSeparator"); + assert!( + (tree.bounds(sep[0]).height - separator).abs() < 1e-4, + "{density:?}" + ); + let frame = nodes_named(&tree, root, "BreezePopupFrame"); + let (f, first) = (tree.bounds(frame[0]), tree.bounds(rows[0])); + assert_eq!((first.x - f.x, first.y - f.y), (4.0, 4.0), "{density:?}"); + // The first row's label. + let rendered = tree.render(); + let label_x = rendered + .glyphs + .iter() + .filter(|g| g.screen[1] >= first.y && g.screen[1] < first.bottom()) + .map(|g| g.screen[0]) + .fold(f32::INFINITY, f32::min); + let expected = (12.0 + 3.0) * spacing + 16.0; + assert!( + (label_x - first.x - expected).abs() < 1e-3, + "{density:?}: the label at {}, want {expected}", + label_x - first.x + ); + assert_eq!( + target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(), + 0, + "{density:?}: a conformance failure" + ); + } + } +} + +// ── List and tree rows ───────────────────────────────────────────────── + +/// The `size_hint_for_row` Qt reported for the Breeze item view `id` at +/// Plasma's default font. +fn qt_row(id: &str) -> f32 { + let all: Value = serde_json::from_str(SIZES_JSON).expect("sizes.json parses"); + all["widgets"] + .as_array() + .expect("the measured widgets") + .iter() + .find(|w| w["id"] == id && w["font"] == "Noto Sans 10") + .unwrap_or_else(|| panic!("sizes.json has no {id}"))["size_hint_for_row"] + .as_f64() + .expect("a row height") as f32 +} + +/// The laid-out height of every `name` row under `root`. +fn heights(tree: &WidgetTree, root: WidgetId, name: &str) -> Vec { + nodes_named(tree, root, name) + .into_iter() + .map(|id| tree.bounds(id).height) + .collect() +} + +/// A column of list rows at `density`: text only, with an icon, with a check +/// box, and with a subtitle; then the same as tree rows. Laid out with no +/// height proposed, so each row takes its own. +fn rows_at(theme: &Theme, density: TargetDensity) -> (Vec, Vec, usize) { + use teksilo_widgets::{StandardListItem, StandardTreeItem}; + let icon = || IconWidget::checkmark(16.0); + let column = teksilo_widgets::VStack::new() + .child(StandardListItem::new(lit!("Text"))) + .child(StandardListItem::new(lit!("Icon")).leading_slot(icon())) + .child(StandardListItem::new(lit!("Check")).checkbox(Signal::new(true))) + .child(StandardListItem::new(lit!("Two")).subtitle(lit!("Lines"))) + .child(StandardTreeItem::new(lit!("Text")).depth(1)) + .child( + StandardTreeItem::new(lit!("Icon")) + .depth(1) + .leading_slot(icon()), + ) + .child( + StandardTreeItem::new(lit!("Check")) + .depth(1) + .checkbox(Signal::new(true)), + ) + .child( + StandardTreeItem::new(lit!("Two")) + .depth(1) + .subtitle(lit!("Lines")), + ); + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let root = in_tappable_row(&mut tree, FixedSize::new().width(400.0).child(column)); + tree.layout(SizeProposal { + width: Some(600.0), + height: None, + }); + let failures = target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .count(); + ( + heights(&tree, root, "StandardListItem"), + heights(&tree, root, "StandardTreeItem"), + failures, + ) +} + +/// A row is Qt's at Compact: 24 px of text, 26 with a 16 px icon or a check +/// box, measured on the laid-out rows; a row with a subtitle is 42. Above +/// Compact every row rises to the density's target size, and a check box +/// row is its check box's node, itself at the target size, with the 1 px +/// item margin above and below it: 34 and 46. A tree row lays out through +/// the list row it wraps. +#[test] +fn a_row_measures_breezes_at_every_density() { + assert_eq!(qt_row("list_view"), 24.0); + assert_eq!(qt_row("list_view.icon"), 26.0); + assert_eq!(qt_row("list_view.check"), 26.0); + assert_eq!(qt_row("tree_view"), 24.0); + assert_eq!(qt_row("tree_view.icon"), 26.0); + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.list_row_height, qt_row("list_view")); + let expected = [ + (TargetDensity::Compact, [24.0, 26.0, 26.0, 42.0]), + (TargetDensity::Comfortable, [32.0, 32.0, 34.0, 42.0]), + (TargetDensity::Touch, [44.0, 44.0, 46.0, 44.0]), + ]; + assert_eq!(expected.len(), ALL_DENSITIES.len(), "every density"); + for (density, rows) in expected { + for theme in themes() { + let (list, tree, failures) = rows_at(&theme, density); + assert_eq!(list, rows.to_vec(), "{density:?}: the list rows"); + assert_eq!(tree, rows.to_vec(), "{density:?}: the tree rows"); + assert_eq!(failures, 0, "{density:?}: a conformance failure"); + } + } +} + +/// The installed row style reports what the rows lay out. +#[test] +fn the_installed_row_style_reports_the_breeze_rows() { + for (density, floors) in [ + (TargetDensity::Compact, (24.0, 26.0, 42.0)), + (TargetDensity::Comfortable, (32.0, 32.0, 42.0)), + (TargetDensity::Touch, (44.0, 44.0, 44.0)), + ] { + let theme = kde::light().with_density(density); + let slot = theme + .style_slots + .standard_item + .clone() + .expect("the preset installs a row style"); + let t = &theme.input; + assert_eq!( + ( + slot.min_height_single_line(t), + slot.min_height_decorated(t), + slot.min_height_two_line(t) + ), + floors, + "{density:?}" + ); + } +} + +// ── Tables ───────────────────────────────────────────────────────────── + +/// The laid-out `(header height, row height)` of a `TableView` and of a +/// `TreeTableView` at `density`, each measured on its hosted header and its +/// first two realized rows, the first starting under the header. +fn table_bands(theme: &Theme, density: TargetDensity) -> [(f32, f32); 2] { + use teksilo_data::{ListModel, TreeModel}; + use teksilo_widgets::table_view::{Column, ColumnWidth}; + use teksilo_widgets::{TableView, TreeTableView}; + + fn bands(tree: &WidgetTree, root: WidgetId) -> (f32, f32) { + let header = nodes_named(tree, root, "TableHeader"); + assert_eq!(header.len(), 1, "one hosted header"); + let mut rows = nodes_named(tree, root, "BodyRow"); + rows.sort_by(|a, b| tree.bounds(*a).y.total_cmp(&tree.bounds(*b).y)); + assert!(rows.len() >= 2, "at least two realized rows"); + let h = tree.bounds(header[0]); + let first = tree.bounds(rows[0]); + assert_eq!( + first.y, + h.y + h.height, + "the first row starts under the header" + ); + assert_eq!(tree.bounds(rows[1]).y - first.y, first.height, "the pitch"); + (h.height, first.height) + } + fn column(label: fn(&T) -> String) -> Column { + Column::new("name", lit!("Name"), move |row: &T, _| { + Box::new(teksilo_widgets::primitives::TextWidget::new(lit!(label( + row + )))) + }) + .width(ColumnWidth::Fixed(200.0)) + } + let mount = |w: Box| { + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let root = tree.add_boxed(w); + tree.layout(SizeProposal::exact(400.0, 300.0)); + bands(&tree, root) + }; + let model = ListModel::from_vec(vec!["alpha".to_string(), "beta".into(), "gamma".into()]); + let flat = mount(Box::new( + TableView::new(model).add_column(column(|s: &String| s.clone())), + )); + let model: TreeModel = TreeModel::new(); + for (i, name) in ["alpha", "beta", "gamma"].into_iter().enumerate() { + model.insert_root(i, name.to_string()); + } + let nested = mount(Box::new( + TreeTableView::new(model).add_column(column(|s: &String| s.clone())), + )); + [flat, nested] +} + +/// D7. Qt's Breeze table row is 23 px with its grid line, under the 24 px +/// target size; the laid-out row is 24, the header Qt's 30, at Compact, and +/// both rise to the density's target size above it. +#[test] +fn d7_a_table_row_is_raised_to_the_target_size() { + assert_eq!(qt_row("table_view"), 23.0, "Qt's row, with its grid line"); + assert_eq!(qt_size("header.horizontal", None).1, 30.0); + let g = BreezeGeometry::breeze_default(); + assert_eq!(g.table_row_height, qt_row("table_view")); + assert_eq!(g.header_height, 30.0); + for (density, bands) in [ + (TargetDensity::Compact, (30.0, 24.0)), + (TargetDensity::Comfortable, (32.0, 32.0)), + (TargetDensity::Touch, (44.0, 44.0)), + ] { + for theme in themes() { + assert_eq!(table_bands(&theme, density), [bands; 2], "{density:?}"); + } + let theme = kde::light().with_density(density); + let slot = theme.style_slots.table.clone().expect("a table style"); + assert_eq!( + ( + slot.header_height(&theme.input), + slot.row_height(&theme.input) + ), + bands, + "{density:?}: the installed slot" + ); + } +} + +// ── Tabs ─────────────────────────────────────────────────────────────── + +/// A tab is Qt's 30 px at Compact, its title 8 px inside its edges, rising +/// to the density's target size above it and its padding by the spacing. +#[test] +fn a_tab_measures_breezes_at_every_density() { + use teksilo_data::ListModel; + use teksilo_widgets::tab_widget::{TabBar, TabDelegate, TabId, TabSizing}; + assert_eq!(qt_size("tab_bar", None).1, 30.0); + for (density, height, padding) in [ + (TargetDensity::Compact, 30.0, 8.0), + (TargetDensity::Comfortable, 32.0, 9.2), + (TargetDensity::Touch, 44.0, 10.4), + ] { + for theme in themes() { + let model = + ListModel::from_vec(vec![(TabId::fresh(), "Tab"), (TabId::fresh(), "Third")]); + let delegate = TabDelegate::new(|_, t: &(TabId, &'static str)| lit!(t.1)); + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let bar = tree.add( + TabBar::horizontal(model, delegate, Signal::new(None), |_, t| t.0) + .tab_sizing(TabSizing::Independent) + .show_scroll_arrows(false) + .show_overflow_dropdown(false), + ); + tree.layout(SizeProposal { + width: Some(800.0), + height: None, + }); + let headers = nodes_named(&tree, bar, "TabHeader"); + assert_eq!(headers.len(), 2); + for header in headers { + assert_eq!(tree.bounds(header).height, height, "{density:?}"); + let pad = nodes_named(&tree, header, "Padding"); + let outer = tree.bounds(pad[0]); + let row = tree.bounds(tree.children(pad[0])[0]); + assert!( + (row.x - outer.x - padding).abs() < 1e-3, + "{density:?}: padding {}", + row.x - outer.x + ); + } + assert_eq!(tree.bounds(bar).height, height, "{density:?}: the strip"); + } + } +} + +// ── Scroll bars and progress bars ────────────────────────────────────── + +/// A scroll bar's handle is never shorter than Breeze's 20 px, at every +/// density: measured on the thumb the mounted bar reports, the rectangle its +/// hit test and its drag use, over a document fifty viewports long. +#[test] +fn a_scroll_bar_floors_its_handle_at_twenty_pixels_at_every_density() { + use teksilo_widgets::scroll_bar::{SCROLLBAR_PART_THUMB, ScrollBar, ScrollBarOrientation}; + for density in ALL_DENSITIES.iter().copied() { + for theme in themes() { + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let bar = tree.add( + ScrollBar::new( + ScrollBarOrientation::Vertical, + Signal::new(0.0), + Signal::new(500.0), + Signal::new(0.02), + ) + .thickness(21.0), + ); + tree.layout(SizeProposal::exact(21.0, 400.0)); + let thumb = tree + .widget_target_regions(bar) + .into_iter() + .find(|r| r.part == SCROLLBAR_PART_THUMB) + .expect("the bar reports its thumb"); + assert_eq!(thumb.rect.height, 20.0, "{density:?}"); + } + } +} + +/// A progress bar is Qt's 26 px at every density, its 6 px groove 10 px +/// down, Qt's groove; a thickness the app sets wins. +#[test] +fn a_progress_bar_is_qts_hint_around_breezes_groove() { + use teksilo_widgets::ProgressBar; + assert_eq!(qt_size("progress_bar.no_text", None).1, 26.0); + assert_eq!(qt_size("progress_bar.vertical", None).0, 26.0); + for density in ALL_DENSITIES.iter().copied() { + for theme in themes() { + let ((_, h), _) = measured(&theme, density, "ProgressBar", ProgressBar::new(0.3)); + assert_eq!(h, 26.0, "{density:?}"); + let ((_, h), _) = measured( + &theme, + density, + "ProgressBar", + ProgressBar::new(0.3).thickness(6.0), + ); + assert_eq!(h, 6.0, "{density:?}: the app's thickness"); + } + } +} + +// ── Recipe slots built with Breeze's metrics ────────────────────────── + +/// The spacing factor of each density's ladder, which every Breeze gap the +/// recipe slots set is scaled by. +const SPACING: [(TargetDensity, f32); 3] = [ + (TargetDensity::Compact, 1.0), + (TargetDensity::Comfortable, 1.15), + (TargetDensity::Touch, 1.3), +]; + +fn near(a: f32, b: f32) -> bool { + (a - b).abs() < 1e-3 +} + +/// A tree under `theme` at `density` holding `w` at its natural size, laid +/// out and rendered: the tree, the widget's root and the render frame. +fn natural( + theme: &Theme, + density: TargetDensity, + w: impl Widget + 'static, +) -> (WidgetTree, WidgetId, Rc) { + natural_in(theme.with_density(density), w) +} + +/// [`natural`] under `theme` as it is, already at its density. +fn natural_in( + theme: Theme, + w: impl Widget + 'static, +) -> (WidgetTree, WidgetId, Rc) { + let mut tree = WidgetTree::new() + .with_theme(theme) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let root = tree.add(VStack::new().child(HStack::new().child(w))); + tree.layout(SizeProposal::exact(800.0, 600.0)); + let frame = tree.render(); + (tree, root, frame) +} + +/// The one `name` node under `root`. +fn the(tree: &WidgetTree, root: WidgetId, name: &str) -> WidgetId { + let nodes = nodes_named(tree, root, name); + assert_eq!(nodes.len(), 1, "one {name}, found {}", nodes.len()); + nodes[0] +} + +/// How far `inner` lies inside `outer` on each side: leading, top, +/// trailing, bottom. +fn insets(tree: &WidgetTree, outer: WidgetId, inner: WidgetId) -> [f32; 4] { + let o = tree.bounds(outer); + let i = tree.bounds(inner); + [ + i.x - o.x, + i.y - o.y, + (o.x + o.width) - (i.x + i.width), + (o.y + o.height) - (i.y + i.height), + ] +} + +/// Whether the frame strokes a rounded rectangle at `radius` in a pen of +/// `pen`. +fn strokes_at(frame: &teksilo_canvas::RenderFrame, radius: f32, pen: f32) -> bool { + frame + .shapes + .iter() + .any(|s| s.stroke_width == pen && s.corner_radii == [radius; 4]) +} + +/// A leaf of a fixed size, the content the frames are measured around. +fn leaf() -> FixedSize { + FixedSize::new().width(300.0).height(40.0) +} + +/// A `Panel` and an outlined `Card` are Breeze's group box: their content +/// 6 px inside the frame, the margin of a nested layout, scaled by the +/// density's spacing, and the frame stroked at the 5 px radius in the 1 px +/// pen. +#[test] +fn a_panel_and_a_card_frame_their_content_as_a_group_box_at_every_density() { + use teksilo_core::styles::CardVariant; + use teksilo_widgets::{Card, Panel}; + for (density, factor) in SPACING { + for theme in themes() { + let (tree, root, frame) = natural(&theme, density, Panel::new().child(leaf())); + let pad = insets( + &tree, + the(&tree, root, "PanelFrame"), + the(&tree, root, "FixedSize"), + ); + assert!( + pad.iter().all(|p| near(*p, 6.0 * factor)), + "{density:?}: the panel's content lies {pad:?} inside it" + ); + assert!(strokes_at(&frame, 5.0, 1.0), "{density:?}: the panel frame"); + + let (tree, root, frame) = natural( + &theme, + density, + Card::new().variant(CardVariant::Outlined).content(leaf()), + ); + let pad = insets( + &tree, + the(&tree, root, "CardFrame"), + the(&tree, root, "FixedSize"), + ); + assert!( + pad.iter().all(|p| near(*p, 6.0 * factor)), + "{density:?}: the card's content lies {pad:?} inside it" + ); + assert!(strokes_at(&frame, 5.0, 1.0), "{density:?}: the card frame"); + } + } +} + +/// A dialog's content lies Breeze's 10 px dialog margin inside its panel, +/// the top-level layout margin, scaled by the density's spacing, and the +/// panel is rounded at the 5 px window radius. +#[test] +fn a_dialog_holds_its_content_inside_breezes_dialog_margin_at_every_density() { + use teksilo_widgets::ModalContainer; + for (density, factor) in SPACING { + for theme in themes() { + let (tree, root, frame) = natural(&theme, density, ModalContainer::new(leaf())); + let pad = insets( + &tree, + the(&tree, root, "DialogPanel"), + the(&tree, root, "FixedSize"), + ); + assert!( + pad.iter().all(|p| near(*p, 10.0 * factor)), + "{density:?}: the dialog's content lies {pad:?} inside it" + ); + assert!( + strokes_at(&frame, 5.0, 1.0), + "{density:?}: the dialog panel" + ); + } + } +} + +/// A banner lays its glyph and text out 6 px inside its frame and 6 px +/// apart, Breeze's child margin and layout spacing, scaled by the density's +/// spacing. +#[test] +fn a_banner_lays_out_on_breezes_margin_and_spacing_at_every_density() { + use teksilo_widgets::Banner; + for (density, factor) in SPACING { + for theme in themes() { + let (tree, root, _) = natural(&theme, density, Banner::info(lit!("Saved"))); + let padding = nodes_named(&tree, root, "Padding") + .into_iter() + .find(|p| { + tree.children(*p).first().is_some_and(|c| { + tree.widget_type_name(*c) + .is_some_and(|t| t.ends_with("HStack")) + }) + }) + .expect("the banner pads its row"); + let row = tree.children(padding)[0]; + let pad = insets(&tree, padding, row); + assert!( + pad.iter().all(|p| near(*p, 6.0 * factor)), + "{density:?}: the banner's row lies {pad:?} inside it" + ); + let items = tree.children(row); + assert_eq!(items.len(), 2, "the glyph and the text"); + let glyph = tree.bounds(items[0]); + let gap = tree.bounds(items[1]).x - (glyph.x + glyph.width); + assert!(near(gap, 6.0 * factor), "{density:?}: a gap of {gap}"); + } + } +} + +/// The `(horizontal, vertical)` gutter inside a suggestion row of a search +/// field under `theme` at `density`, measured on the open suggestion list. +fn suggestion_row_gutters(theme: &Theme, density: TargetDensity) -> (f32, f32) { + use teksilo_core::event::{Key, Modifiers}; + use teksilo_widgets::SearchField; + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let query = Signal::new(String::new()); + tree.add( + SearchField::new(query.clone()) + .with_suggestions(|_| vec!["alpha".to_string(), "apricot".to_string()]), + ); + let proposal = SizeProposal::exact(400.0, 300.0); + tree.layout(proposal); + // The provider runs off the query, so the list is empty until it + // changes. + query.set("a".to_string()); + tree.layout(proposal); + let mut fields = Vec::new(); + for root in tree.roots() { + fields.extend(nodes_named(&tree, root, "TextInputField")); + } + tree.focus(*fields.first().expect("the field's editable surface")); + tree.press_key(Key::ArrowDown, Modifiers::default()); + tree.layout(proposal); + let mut rows = Vec::new(); + for root in tree.roots() { + rows.extend(nodes_named(&tree, root, "SuggestionRow")); + } + assert_eq!(rows.len(), 2, "two suggestions, two rows"); + let padding = the(&tree, rows[0], "Padding"); + let pad = tree.bounds(padding); + let label = tree.bounds(tree.children(padding)[0]); + ( + (pad.width - label.width) / 2.0, + (pad.height - label.height) / 2.0, + ) +} + +/// A suggestion's text lies where Breeze puts the text of a menu item +/// without icons: 16 px across and the 4 px item margin down, scaled by the +/// density's spacing. +#[test] +fn a_search_field_lays_its_suggestions_out_as_menu_items_at_every_density() { + for (density, factor) in SPACING { + let (h, v) = suggestion_row_gutters(&kde::light(), density); + assert!( + near(h, 16.0 * factor) && near(v, 4.0 * factor), + "{density:?}: ({h}, {v})" + ); + } +} + +/// A calendar marks today with a ring in the 1 px pen at the 5 px frame +/// radius, at every density. Its header keeps the recipe's gap between the +/// arrows, so the month title between them stays a target: no part of the +/// calendar is a conformance failure. +#[test] +fn a_calendar_rings_today_in_the_pen_and_keeps_its_targets_at_every_density() { + use teksilo_widgets::Calendar; + use teksilo_widgets::styles::CalendarRecipe; + for (density, _) in SPACING { + for theme in themes() { + let (tree, root, frame) = natural(&theme, density, Calendar::single(Signal::new(None))); + assert!(strokes_at(&frame, 5.0, 1.0), "{density:?}: today's ring"); + let arrows = nodes_named(&tree, root, "NavArrow"); + assert_eq!(arrows.len(), 4, "prev-year / prev / next / next-year"); + let a = tree.bounds(arrows[0]); + let gap = tree.bounds(arrows[1]).x - (a.x + a.width); + let stock = CalendarRecipe::for_tokens(&theme.with_density(density).input).header_gap; + assert!(near(gap, stock), "{density:?}: a gap of {gap}"); + let failures: Vec<_> = target_audit(&tree, density) + .into_iter() + .filter(|v| v.rule.is_conformance_failure()) + .collect(); + assert!(failures.is_empty(), "{density:?}: {failures:#?}"); + } + } +} + +/// A segmented control's frame is a Breeze button's, at the 5 px radius in +/// the 1 px pen. Its height is the one the stock recipe gives under the same +/// theme, at every density: the control lays it out from the recipe +/// module's constants, which the preset therefore leaves alone, around +/// Breeze's 18 px line, 30 px at every density. +#[test] +fn a_segmented_control_takes_breezes_frame_and_keeps_its_height() { + use teksilo_widgets::SegmentedControl; + use teksilo_widgets::styles::RecipeSegmentedControlStyle; + let control = || { + SegmentedControl::indexed(Signal::new(0_usize)) + .segment(lit!("One")) + .segment(lit!("Two")) + }; + for (density, _) in SPACING { + for theme in themes() { + let (tree, root, frame) = natural(&theme, density, control()); + let got = tree.bounds(the(&tree, root, "SegmentedControl")).height; + assert!(strokes_at(&frame, 5.0, 1.0), "{density:?}: the frame"); + let mut stock = theme.with_density(density); + stock.style_slots.segmented_control = Some(Rc::new( + RecipeSegmentedControlStyle::for_tokens(&stock.input), + )); + let (tree, root, _) = natural_in(stock, control()); + let want = tree.bounds(the(&tree, root, "SegmentedControl")).height; + assert_eq!(got, want, "{density:?}: the stock recipe's height"); + assert_eq!(got, 30.0, "{density:?}"); + } + } +} + +/// A focused link is ringed as Breeze frames keyboard focus (spec k.5.8): +/// a 2 px ring from 1 px inside the link to 1 px outside it, its inner edge +/// rounded at the 5 px frame radius and its outer edge at 7. The recipe +/// strokes it centred on the link's bounds, so the stroke's centre line is +/// the bounds, rounded at 6. An unfocused link draws no ring. +#[test] +fn a_focused_links_ring_is_breezes_focus_frame_at_every_density() { + use teksilo_core::event::{Key, Modifiers}; + use teksilo_widgets::Link; + for (density, _) in SPACING { + for theme in themes() { + let (mut tree, root, frame) = natural(&theme, density, Link::new(lit!("Docs"))); + let ring_width = theme.shape.focus_ring_width; + assert_eq!(ring_width, 2.0, "Breeze's ring is 2 px wide"); + assert!( + !frame.shapes.iter().any(|s| s.stroke_width == ring_width), + "{density:?}: a ring before focus" + ); + let link = the(&tree, root, "Link"); + // Tab, as a keyboard user does: the ring shows on keyboard focus. + tree.press_key(Key::Tab, Modifiers::NONE); + assert_eq!( + tree.focused(), + Some(link), + "{density:?}: Tab reaches the link" + ); + tree.layout(SizeProposal::exact(800.0, 600.0)); + let frame = tree.render(); + let rings: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.stroke_width == ring_width) + .collect(); + assert_eq!(rings.len(), 1, "{density:?}: one focus ring"); + let ring = rings[0]; + assert_eq!( + ring.screen, + tree.bounds(link).to_array(), + "{density:?}: the ring is centred on the link's bounds" + ); + let half = ring_width / 2.0; + for r in ring.corner_radii { + assert!( + near(r - half, 5.0) && near(r + half, 7.0), + "{density:?}: a ring rounded at {r}, edges at {} and {}", + r - half, + r + half + ); + } + } + } +} + +/// A snackbar's surface rounds at the 5 px window radius, as every Breeze +/// window does, at every density. +#[test] +fn a_snackbar_rounds_at_the_window_radius_at_every_density() { + use teksilo_core::event::WidgetEvent; + use teksilo_widgets::Snackbar; + for (density, _) in SPACING { + for theme in themes() { + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + tree.add(Snackbar::new(lit!("Show")).content(leaf())); + let proposal = SizeProposal::exact(800.0, 600.0); + tree.layout(proposal); + let trigger = tree.find_by_label("Show").expect("the snackbar's trigger"); + tree.dispatch_event(WidgetEvent::AccessAction { + action: teksilo_core::accesskit::Action::Click, + target: Some(trigger), + target_node: teksilo_core::accessibility::root_node_id(), + data: None, + }); + // Past the fade-in, so the surface paints at full opacity. + tree.advance_time(std::time::Duration::from_secs(1)); + tree.layout(proposal); + let frame = tree.render(); + let surfaces: Vec = tree + .roots() + .into_iter() + .flat_map(|root| nodes_named(&tree, root, "SnackbarFrame")) + .collect(); + assert_eq!(surfaces.len(), 1, "{density:?}: the snackbar is open"); + let at = tree.bounds(surfaces[0]).to_array(); + let fill = frame + .shapes + .iter() + .find(|s| s.screen == at && s.stroke_width == 0.0) + .unwrap_or_else(|| panic!("{density:?}: no snackbar surface")); + assert_eq!(fill.corner_radii, [5.0; 4], "{density:?}: the surface"); + assert!( + frame + .shapes + .iter() + .any(|s| s.screen == at && s.stroke_width == 1.0 && s.corner_radii == [5.0; 4]), + "{density:?}: the surface's outline" + ); + } + } +} + +/// A toast's surface rounds at the 5 px window radius, and its glyph, its +/// text and its close button stand Breeze's 6 px layout spacing apart, +/// scaled by the density's spacing. +#[test] +fn a_toast_rounds_at_the_window_radius_and_spaces_its_row_at_every_density() { + use teksilo_widgets::toast::surface::ToastSurfaceData; + use teksilo_widgets::toast::{ToastInstallOptions, ToastRegistry, ToastSeverity, ToastSurface}; + for (density, factor) in SPACING { + for theme in themes() { + let data = ToastSurfaceData { + entry_id: 1, + severity: ToastSeverity::Info, + priority: teksilo_core::styles::ToastPriority::Normal, + title: lit!("Saved"), + body: None, + announcement: None, + actions: Rc::new(Vec::new()), + show_close_button: true, + on_click: None, + style_override: None, + body_state: Signal::new(0), + }; + let surface = ToastSurface::new( + data, + None, + ToastRegistry::new(ToastInstallOptions::default()), + false, + ); + let (tree, root, frame) = natural(&theme, density, surface); + let toast = the(&tree, root, "ToastSurface"); + let body = tree.children(toast)[0]; + let background = tree.children(body)[0]; + assert_eq!( + tree.widget_type_name(background) + .and_then(|t| t.rsplit("::").next()), + Some("RectWidget"), + "the surface's background" + ); + let at = tree.bounds(background).to_array(); + let fill = frame + .shapes + .iter() + .find(|s| s.screen == at && s.stroke_width == 0.0) + .unwrap_or_else(|| panic!("{density:?}: no toast surface")); + assert_eq!(fill.corner_radii, [5.0; 4], "{density:?}: the surface"); + + let padding = tree.children(body)[1]; + let row = tree.children(padding)[0]; + let items = tree.children(row); + assert_eq!(items.len(), 3, "the glyph, the text and the close button"); + for pair in items.windows(2) { + let before = tree.bounds(pair[0]); + let gap = tree.bounds(pair[1]).x - (before.x + before.width); + assert!(near(gap, 6.0 * factor), "{density:?}: a gap of {gap}"); + } + } + } +} + +/// A link's underline is the 1 px pen at every density. +#[test] +fn a_links_underline_is_the_pen_at_every_density() { + use teksilo_widgets::Link; + for (density, _) in SPACING { + let (tree, root, _) = natural(&kde::light(), density, Link::new(lit!("Docs"))); + let underline = nodes_named(&tree, root, "FixedSize"); + assert_eq!(underline.len(), 1, "one underline"); + assert_eq!(tree.bounds(underline[0]).height, 1.0, "{density:?}"); + } +} diff --git a/crates/teksilo-theme-kde/tests/row_hit.rs b/crates/teksilo-theme-kde/tests/row_hit.rs new file mode 100644 index 000000000..1f2f926c3 --- /dev/null +++ b/crates/teksilo-theme-kde/tests/row_hit.rs @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! A check box inside a list row is ticked by a press where its box is +//! painted. +//! +//! The Breeze check box paints its 20 px indicator unchanged inside a node +//! the widget raises to the density's target size, and lays its body out +//! 18 px wide so its label lands where Breeze puts it. Either could leave +//! the painted box outside the node that takes the press, or under another +//! part of the row. The press here aims at the centre of the box as it is +//! painted, read from the render frame, not at the centre of a node, in a +//! selected row (whose selection is painted over the row) and an unselected +//! one, at the Compact density and at Touch. + +use std::cell::RefCell; +use std::rc::Rc; + +use teksilo_canvas::{MockTextBackend, Point, SizeProposal}; +use teksilo_core::event::{Modifiers, PointerButton, WidgetEvent}; +use teksilo_core::signal::Signal; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_i18n::lit; +use teksilo_tokens::TargetDensity; +use teksilo_widgets::StandardListItem; + +/// A Breeze list row carrying a checkbox, laid out at `density`. +fn row(density: TargetDensity, checked: &Signal, selected: bool) -> WidgetTree { + let mut tree = WidgetTree::new() + .with_theme(teksilo_theme_kde::light().with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + tree.add( + StandardListItem::new(lit!("Row")) + .checkbox(checked.clone()) + .selected(Signal::new(selected)), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree +} + +/// The centre of the 15 px box the check box painted. +fn painted_box_centre(tree: &mut WidgetTree) -> Point { + let frame = tree.render(); + let boxes: Vec<[f32; 4]> = frame + .shapes + .iter() + .map(|s| s.screen) + .filter(|s| (s[2] - 15.0).abs() < 0.01 && (s[3] - 15.0).abs() < 0.01) + .collect(); + assert!(!boxes.is_empty(), "the row paints a 15 px box"); + let b = boxes[0]; + Point::new(b[0] + b[2] * 0.5, b[1] + b[3] * 0.5) +} + +/// A mouse press at the centre of the painted box ticks it, and a second +/// one clears it. +/// +/// Asserts the check box's own signal, never the row's: the row answers a +/// press that never reached the check box, so a row-level assertion is +/// satisfied by the defect. +#[test] +fn a_press_on_the_painted_box_ticks_it_in_a_breeze_row() { + for selected in [false, true] { + for density in [TargetDensity::Compact, TargetDensity::Touch] { + let checked = Signal::new(false); + let mut tree = row(density, &checked, selected); + for expected in [true, false] { + let at = painted_box_centre(&mut tree); + tree.pointer_move(at); + tree.dispatch_event(WidgetEvent::pointer_down( + at, + PointerButton::Primary, + Modifiers::default(), + )); + tree.dispatch_event(WidgetEvent::pointer_up( + at, + PointerButton::Primary, + Modifiers::default(), + )); + assert_eq!( + checked.get(), + expected, + "a press at the painted box (selected: {selected}) at \ + {density:?} did not reach the check box", + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + } + } + } +} + +/// The painted box lies inside the check box's own node at both densities, +/// so the press above lands on the check box rather than near it. +#[test] +fn the_painted_box_lies_inside_the_check_box_node() { + use teksilo_core::accessibility::target_audit::measure_targets; + for selected in [false, true] { + for density in [TargetDensity::Compact, TargetDensity::Touch] { + let checked = Signal::new(false); + let mut tree = row(density, &checked, selected); + let node = measure_targets(&tree, density) + .into_iter() + .find(|m| m.widget.ends_with("Checkbox") && m.part.is_none()) + .unwrap_or_else(|| panic!("the row builds a check box at {density:?}")); + let b = tree.bounds(node.node); + let at = painted_box_centre(&mut tree); + let inside = |p: Point, half: f32| { + p.x - half >= b.x + && p.x + half <= b.x + b.width + && p.y - half >= b.y + && p.y + half <= b.y + b.height + }; + assert!( + inside(at, 7.5), + "the box around {at:?} leaves the check box node {b:?} at \ + {density:?} (selected: {selected})" + ); + } + } +} diff --git a/crates/teksilo-theme-kde/tests/text_input_min_height.rs b/crates/teksilo-theme-kde/tests/text_input_min_height.rs new file mode 100644 index 000000000..d7c8e2c9b --- /dev/null +++ b/crates/teksilo-theme-kde/tests/text_input_min_height.rs @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! `TextInput::min_height` reaches this preset's field: its style replaces +//! the Breeze line edit's height with the one the app asked for. + +use teksilo_canvas::SizeProposal; +use teksilo_core::signal::Signal; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_widgets::TextInput; +use teksilo_widgets::primitives::VStack; + +/// The framed field's height: the first child of the composite's root. +fn frame_height(input: TextInput) -> f32 { + let mut tree = WidgetTree::new().with_theme(teksilo_theme_kde::light()); + // In a `VStack`, so the composite is laid out at the height it asks for. + let id = tree.add(VStack::new().child(input)); + tree.layout(SizeProposal::exact(300.0, 200.0)); + let input_id = tree.children(id)[0]; + let root = tree.children(input_id)[0]; + let frame = tree.children(root)[0]; + tree.bounds(frame).height +} + +fn input() -> TextInput { + TextInput::new(Signal::new(String::new())) +} + +fn small_style() -> teksilo_tokens::TextStyle { + teksilo_tokens::TextStyle { + family: "Inter".to_string(), + size: 11.0, + weight: teksilo_tokens::FontWeight::REGULAR, + line_height: 1.3, + letter_spacing: 0.0, + } +} + +/// Without one, the field is Breeze's 32 px line edit. +#[test] +fn the_field_is_the_breeze_line_edit_by_default() { + assert_eq!(frame_height(input()), 32.0); +} + +/// Below the line edit's height: only the style can stand in the way there, +/// since the widget's floor already follows the app's value. +#[test] +fn an_apps_min_height_replaces_the_presets_height() { + let preset = frame_height(input()); + let asked = 23.0; + assert!(asked < preset, "the preset's height is {preset}"); + let short = frame_height(input().text_style(small_style()).min_height(asked)); + assert!( + (short - asked).abs() < 0.01, + "asked for {asked}, got {short}" + ); +} + +/// Above it too. +#[test] +fn a_taller_min_height_raises_the_field() { + let tall = frame_height(input().min_height(40.0)); + assert!((tall - 40.0).abs() < 0.01, "asked for 40, got {tall}"); +} diff --git a/crates/teksilo-theme-macos/src/lib.rs b/crates/teksilo-theme-macos/src/lib.rs index 5ecdfe35f..65b1468fd 100644 --- a/crates/teksilo-theme-macos/src/lib.rs +++ b/crates/teksilo-theme-macos/src/lib.rs @@ -236,7 +236,9 @@ fn install_styles(theme: &mut Theme) { slots.radio = Some(Rc::new(styles::radio::MacOsRadioStyle)); slots.text_input = Some(Rc::new(styles::text_input::MacOsTextInputStyle)); slots.slider = Some(Rc::new(styles::slider::MacOsSliderStyle)); - slots.menu_item = Some(Rc::new(styles::menu_item::MacOsMenuItemStyle)); + slots.menu_item = Some(Rc::new(styles::menu_item::MacOsMenuItemStyle::for_tokens( + &tokens, + ))); slots.standard_item = Some(Rc::new(styles::standard_item::MacOsStandardItemStyle)); // macOS metrics over the shipped composition. @@ -276,6 +278,8 @@ fn install_styles(theme: &mut Theme) { mod tests { use super::*; use crate::palette::hex; + use std::collections::BTreeSet; + use teksilo_core::styles::ComponentStyleSlots; #[test] fn light_reports_light_appearance() { @@ -368,42 +372,99 @@ mod tests { assert!(d.bezel.inner_light.a() > 0.0); } + /// The slots macOS deliberately leaves to the widget's default + /// `Recipe*Style`, each with the reason it is inherited rather than filled. + const MACOS_UNFILLED: &[(&str, &str)] = &[ + ( + "split_button", + "no AppKit split-button bezel is transcribed; the recipe frame paints from the theme surface and border roles", + ), + ( + "splitter", + "the recipe's thin resting divider in the theme border role already reads as a split-view divider", + ), + ( + "radio_tile", + "AppKit has no selectable card; the recipe surface reads the preset's surface and accent roles", + ), + ( + "chart", + "AppKit has no chart chrome; the teksilo-charts default reads the series colours and the theme border role", + ), + ( + "color_picker", + "NSColorPanel is a system panel with no in-window chrome to transcribe; the recipe reads the theme roles", + ), + ( + "spin_box", + "no NSStepper chrome is transcribed; the recipe frames the widget's field and step buttons in theme roles", + ), + ( + "date_edit", + "the recipe is a passthrough around the TextInput, which the text_input slot already styles", + ), + ( + "rich_text_editor", + "the recipe frames the viewport in theme border and surface roles; the text is document content", + ), + ( + "list_container", + "the recipe is only the drag-insertion line, drawn in the accent role this preset maps", + ), + ( + "drop_zone", + "AppKit has no drop-zone control; the recipe tints its region from the theme roles", + ), + ( + "drop_target", + "AppKit has no drop-target control; the recipe border reads the theme roles and its hints are cards, which the card slot styles", + ), + ( + "grid_view", + "the recipe's focus ring, marquee and insertion bar use the focused and accent roles this preset maps", + ), + ( + "web_view", + "the page paints itself; the recipe only tints the overlay shown over it, from theme roles", + ), + ( + "text_selection", + "touch chrome on a platform without touch; its geometry is teksilo_core::text_touch's, shared with hit testing", + ), + ]; + + /// Whether the installed slots and [`MACOS_UNFILLED`] are exactly + /// [`ComponentStyleSlots::NAMES`], with no slot in both: a slot added to the + /// framework must be either filled by this preset or listed with a reason. #[test] - fn every_installed_style_slot_is_populated() { - for t in [light(), dark()] { - let s = &t.style_slots; - for (name, present) in [ - ("button", s.button.is_some()), - ("toggle", s.toggle.is_some()), - ("checkbox", s.checkbox.is_some()), - ("radio", s.radio.is_some()), - ("text_input", s.text_input.is_some()), - ("slider", s.slider.is_some()), - ("menu_item", s.menu_item.is_some()), - ("standard_item", s.standard_item.is_some()), - ("card", s.card.is_some()), - ("panel", s.panel.is_some()), - ("popover", s.popover.is_some()), - ("tooltip", s.tooltip.is_some()), - ("dialog", s.dialog.is_some()), - ("snackbar", s.snackbar.is_some()), - ("toast", s.toast.is_some()), - ("banner", s.banner.is_some()), - ("combo_box", s.combo_box.is_some()), - ("icon_button", s.icon_button.is_some()), - ("link", s.link.is_some()), - ("segmented_control", s.segmented_control.is_some()), - ("badge", s.badge.is_some()), - ("progress_bar", s.progress_bar.is_some()), - ("scroll_bar", s.scroll_bar.is_some()), - ("tab", s.tab.is_some()), - ("table", s.table.is_some()), - ("calendar", s.calendar.is_some()), - ("search_field", s.search_field.is_some()), - ("avatar", s.avatar.is_some()), - ] { - assert!(present, "{name} slot not installed"); - } + fn installed_and_unfilled_slots_partition_the_roster() { + let unfilled: BTreeSet<&str> = MACOS_UNFILLED.iter().map(|(name, _)| *name).collect(); + assert_eq!( + unfilled.len(), + MACOS_UNFILLED.len(), + "a slot is listed twice" + ); + for (name, reason) in MACOS_UNFILLED { + assert!( + !reason.trim().is_empty(), + "{name} is listed without a reason" + ); + } + let names: BTreeSet<&str> = ComponentStyleSlots::NAMES.iter().copied().collect(); + for t in [light(), dark(), light().with_density(TargetDensity::Touch)] { + let installed: BTreeSet<&str> = t.style_slots.installed().into_iter().collect(); + let both: Vec<_> = installed.intersection(&unfilled).collect(); + assert!( + both.is_empty(), + "installed yet listed as unfilled: {both:?}" + ); + let covered: BTreeSet<&str> = installed.union(&unfilled).copied().collect(); + let missing: Vec<_> = names.difference(&covered).collect(); + let unknown: Vec<_> = covered.difference(&names).collect(); + assert!( + missing.is_empty() && unknown.is_empty(), + "neither installed nor listed: {missing:?}; not a slot: {unknown:?}" + ); } } @@ -515,4 +576,46 @@ mod tests { assert_eq!(touch.colors, base.colors); assert!(touch.extension::().is_some()); } + + /// The menu slot is rebuilt for the density too, so the metrics + /// `MenuItem` and `MenuSeparator` read follow it, and switching back to + /// Compact gives the Compact metrics again. + #[test] + fn a_density_switch_reprojects_the_menu_metrics() { + use teksilo_core::styles::MenuItemStyle; + + let base = light(); + let metrics = |t: &Theme| { + t.style_slots + .menu_item + .as_ref() + .expect("slot installed") + .metrics() + }; + for density in [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ] { + let t = base.with_density(density); + assert_eq!( + metrics(&t), + styles::menu_item::MacOsMenuItemStyle::for_tokens(&InputTokens::for_density( + density + )) + .metrics(), + "{density:?}", + ); + } + let touch = metrics(&base.with_density(TargetDensity::Touch)); + let compact = metrics(&base); + assert!(touch.separator_height > compact.separator_height); + assert!(touch.trailing_column_width > compact.trailing_column_width); + // AppKit's 22 dp row is the preset's decision at every density. + assert_eq!(touch.item_height, compact.item_height); + let back = base + .with_density(TargetDensity::Touch) + .with_density(TargetDensity::Compact); + assert_eq!(metrics(&back), compact); + } } diff --git a/crates/teksilo-theme-macos/src/styles/chrome.rs b/crates/teksilo-theme-macos/src/styles/chrome.rs index a82c0992a..ebb43c0ae 100644 --- a/crates/teksilo-theme-macos/src/styles/chrome.rs +++ b/crates/teksilo-theme-macos/src/styles/chrome.rs @@ -49,6 +49,7 @@ use teksilo_core::accessibility::AccessNodeBuilder; use teksilo_core::binding::BindingLevel; use teksilo_core::build_context::BuildContext; use teksilo_core::signal::Signal; +use teksilo_core::styles::FocusRingStroke; use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; use teksilo_core::widget_id::WidgetId; use teksilo_tokens::{Color, CornerRadius, Shadow}; @@ -350,23 +351,40 @@ pub fn paint_ring( // Halo first, so the solid band lands on top of its inner edge and // the two read as one graded ring rather than two outlines. - let halo_center = offset + solid_w + MACOS_FOCUS_RING_HALO_WIDTH * 0.5; + // A negative offset pulls both bands inward; `FocusRingStroke` keeps + // the rectangles and radii from inverting on a small control. + let (halo, solid) = ring_bands(bounds, radius, offset, solid_w); canvas.stroke_rounded_rect( - inflate(bounds, halo_center), - CornerRadius::uniform(radius + halo_center), + halo.rect, + CornerRadius::uniform(halo.corner_radius), color.with_alpha(color.a() * MACOS_FOCUS_RING_HALO_ALPHA), MACOS_FOCUS_RING_HALO_WIDTH, ); - let solid_center = offset + solid_w * 0.5; canvas.stroke_rounded_rect( - inflate(bounds, solid_center), - CornerRadius::uniform(radius + solid_center), + solid.rect, + CornerRadius::uniform(solid.corner_radius), color, solid_w, ); } +/// The halo and solid-band centre lines of [`paint_ring`]: the solid band's +/// inner edge `offset` from the control, the halo just outside it. +fn ring_bands( + bounds: Rect, + radius: f32, + offset: f32, + solid_w: f32, +) -> (FocusRingStroke, FocusRingStroke) { + let halo_center = offset + solid_w + MACOS_FOCUS_RING_HALO_WIDTH * 0.5; + let solid_center = offset + solid_w * 0.5; + ( + FocusRingStroke::at_distance(bounds, radius, halo_center), + FocusRingStroke::at_distance(bounds, radius, solid_center), + ) +} + /// Paint the macOS focus ring in the accent — the usual case. pub fn paint_focus_ring(canvas: &mut Canvas, bounds: Rect, radius: f32, ctx: &PaintContext) { // `focus_ring` is `controlAccentColor`, already desaturated by the @@ -576,4 +594,23 @@ mod tests { let halo_inner_edge = MACOS_FOCUS_RING_WIDTH; assert!((solid_outer_edge - halo_inner_edge).abs() < 1e-6); } + + #[test] + fn a_negative_offset_pulls_both_bands_in_without_inverting() { + let control = Rect::new(10.0, 10.0, 40.0, 22.0); + let w = MACOS_FOCUS_RING_WIDTH; + let (out_halo, out_solid) = ring_bands(control, 6.0, 0.0, w); + let (in_halo, in_solid) = ring_bands(control, 6.0, -1.0, w); + assert!(in_solid.rect.width < out_solid.rect.width); + assert!(in_solid.rect.height < out_solid.rect.height); + assert!(in_halo.rect.width < out_halo.rect.width); + assert!(in_halo.rect.height < out_halo.rect.height); + + // A tiny control under an offset deeper than it is wide. + let (halo, solid) = ring_bands(Rect::new(0.0, 0.0, 2.0, 2.0), 1.0, -8.0, w); + for band in [halo, solid] { + assert!(band.rect.width >= 0.0 && band.rect.height >= 0.0); + assert!(band.corner_radius >= 0.0); + } + } } diff --git a/crates/teksilo-theme-macos/src/styles/menu_item.rs b/crates/teksilo-theme-macos/src/styles/menu_item.rs index 54a0a45b0..529a8e4fe 100644 --- a/crates/teksilo-theme-macos/src/styles/menu_item.rs +++ b/crates/teksilo-theme-macos/src/styles/menu_item.rs @@ -27,6 +27,12 @@ //! macOS metrics. The delegate's own interaction tint is suppressed by //! handing it signals that never fire, exactly as the Fluent menu row //! does, so the highlight painted here is the only one. +//! +//! The style stores the recipe it lays out, built from the theme's input +//! tokens ([`MacOsMenuItemStyle::for_tokens`]), so +//! [`MenuItemStyle::metrics`], which `MenuItem` and `MenuSeparator` read for +//! the parts they build themselves, follows the density the theme was +//! projected to, as the painted row does. use teksilo_core::build_context::BuildContext; use teksilo_core::signal::Signal; @@ -88,8 +94,33 @@ pub fn macos_menu_item_recipe_for(tokens: &InputTokens) -> MenuItemRecipe { } /// macOS `MenuItemStyle`. -#[derive(Debug, Default, Clone, Copy)] -pub struct MacOsMenuItemStyle; +/// +/// [`Default`] is the Compact density; the preset installs +/// [`MacOsMenuItemStyle::for_tokens`] of the theme it builds, and builds it +/// again when the theme is projected to another density. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct MacOsMenuItemStyle { + /// The row dimensions this style lays out and reports. + pub recipe: MenuItemRecipe, +} + +impl MacOsMenuItemStyle { + /// The style with `recipe`'s dimensions. + pub fn new(recipe: MenuItemRecipe) -> Self { + Self { recipe } + } + + /// The style at the density of `tokens`: [`macos_menu_item_recipe_for`]. + pub fn for_tokens(tokens: &InputTokens) -> Self { + Self::new(macos_menu_item_recipe_for(tokens)) + } +} + +impl Default for MacOsMenuItemStyle { + fn default() -> Self { + Self::new(macos_menu_item_recipe()) + } +} impl MenuItemStyle for MacOsMenuItemStyle { fn make_body(&self, cfg: &MenuItemStyleConfig, ctx: &mut BuildContext) -> WidgetId { @@ -131,18 +162,19 @@ impl MenuItemStyle for MacOsMenuItemStyle { is_disabled: cfg.is_disabled.clone(), is_highlighted: quiet, }; - let row = RecipeMenuItemStyle::new(macos_menu_item_recipe_for(&ctx.theme().input)) - .make_body(&inner_cfg, ctx); + let row = RecipeMenuItemStyle::new(self.recipe).make_body(&inner_cfg, ctx); ctx.add(ZStack::new().child(inset_backdrop).child(row)) } - /// Delegate the metrics alongside the row. `MenuItem` builds the icon and - /// chevron columns before any style runs and `MenuSeparator` is a - /// different widget entirely, so without this the slots would keep the - /// IntUI sizes while the row around them used ours. + /// Delegate the metrics alongside the row, from the same recipe. + /// `MenuItem` builds the icon and chevron columns before any style runs + /// and `MenuSeparator` is a different widget entirely, so without this + /// the slots would keep the IntUI sizes while the row around them used + /// ours, and a metric read at Compact would sit in a row laid out at + /// another density. fn metrics(&self) -> MenuItemMetrics { - RecipeMenuItemStyle::new(macos_menu_item_recipe()).metrics() + RecipeMenuItemStyle::new(self.recipe).metrics() } fn highlighted_label_role(&self) -> Option { @@ -194,6 +226,37 @@ mod tests { assert_eq!(r.item_corner_radius, HIGHLIGHT_RADIUS); } + /// The metrics `MenuItem` and `MenuSeparator` read are the stored + /// recipe's, at the density it was built for: the row keeps AppKit's + /// 22 dp, the gutters and the separator follow the spacing factor, and + /// Compact is the recipe this module documents. + #[test] + fn the_metrics_are_the_recipes_at_every_density() { + use teksilo_tokens::TargetDensity; + + assert_eq!( + MacOsMenuItemStyle::default().recipe, + macos_menu_item_recipe() + ); + for (density, separator, trailing) in [ + (TargetDensity::Compact, 11.0, 10.0), + (TargetDensity::Comfortable, 12.65, 11.5), + (TargetDensity::Touch, 14.3, 13.0), + ] { + let tokens = InputTokens::for_density(density); + let style = MacOsMenuItemStyle::for_tokens(&tokens); + assert_eq!(style.recipe, macos_menu_item_recipe_for(&tokens)); + let m = style.metrics(); + assert_eq!(m.item_height, ITEM_HEIGHT, "{density:?}"); + assert!((m.separator_height - separator).abs() < 1e-4, "{density:?}"); + assert!( + (m.trailing_column_width - trailing).abs() < 1e-4, + "{density:?}" + ); + assert_eq!(m.icon_column_width, ICON_COLUMN, "{density:?}"); + } + } + /// A macOS menu is *tighter* than a Fluent one (37 dp) and near the /// IntUI default — density is the whole point of the platform's menus. #[test] @@ -241,7 +304,7 @@ mod tests { #[test] fn the_highlighted_label_clears_contrast_on_the_fill() { assert_eq!( - MacOsMenuItemStyle.highlighted_label_role(), + MacOsMenuItemStyle::default().highlighted_label_role(), Some(TextRole::OnAccent) ); for theme in [crate::light(), crate::dark()] { diff --git a/crates/teksilo-theme-macos/src/styles/metrics.rs b/crates/teksilo-theme-macos/src/styles/metrics.rs index f2ff6acdf..1e7e1fcd7 100644 --- a/crates/teksilo-theme-macos/src/styles/metrics.rs +++ b/crates/teksilo-theme-macos/src/styles/metrics.rs @@ -266,7 +266,6 @@ pub fn macos_scroll_bar_style_for(tokens: &InputTokens) -> RecipeScrollBarStyle pub fn macos_tab_style_for(tokens: &InputTokens) -> RecipeTabStyle { RecipeTabStyle::new(TabRecipe { editor_height: dp(26.0, TargetRole::Target, tokens), - tool_window_height: H_CONTROL, padding_horizontal: spacing(12.0, tokens), underline_active: 2.0, underline_hover: 2.0, @@ -276,10 +275,12 @@ pub fn macos_tab_style_for(tokens: &InputTokens) -> RecipeTabStyle { /// `TableView` / `TreeTableView` — rows and header on the 24 dp /// `NSTableView` rhythm, with the same 8 dp cell gutter the row styles use. +/// Both heights are raised to the density's target size, as the list row +/// they share a rhythm with is. pub fn macos_table_style_for(tokens: &InputTokens) -> RecipeTableStyle { RecipeTableStyle::new(TableRecipe { - row_height: MACOS_ROW_HEIGHT, - header_height: MACOS_ROW_HEIGHT, + row_height: dp(MACOS_ROW_HEIGHT, TargetRole::Target, tokens), + header_height: dp(MACOS_ROW_HEIGHT, TargetRole::Target, tokens), cell_padding_horizontal: 8.0, corner_radius: R_CONTROL, tree_indent_per_level: 16.0, @@ -457,7 +458,6 @@ mod tests { assert_eq!(macos_icon_button_style().recipe.size_default, H_CONTROL); assert_eq!(macos_segmented_control_style().recipe.height, H_CONTROL); assert_eq!(macos_search_field_style().recipe.row_height, H_CONTROL); - assert_eq!(macos_tab_style().recipe.tool_window_height, H_CONTROL); } /// The density claim, pinned against the sibling preset. @@ -468,7 +468,7 @@ mod tests { macos_combo_box_style().recipe.height, macos_icon_button_style().recipe.size_default, macos_segmented_control_style().recipe.height, - macos_tab_style().recipe.tool_window_height, + macos_tab_style().recipe.editor_height, macos_table_style().recipe.row_height, ] { assert!(h < FLUENT_CONTROL_HEIGHT, "{h} is not denser than Fluent"); @@ -513,11 +513,19 @@ mod tests { let t = macos_table_style().recipe; assert_eq!(t.row_height, MACOS_ROW_HEIGHT); assert_eq!(t.header_height, t.row_height); - assert_eq!( - t.row_height, - crate::styles::standard_item::macos_standard_item_recipe().min_height_single_line, - "a TableView row and a ListView row must share one rhythm" - ); + for density in [ + teksilo_tokens::TargetDensity::Compact, + teksilo_tokens::TargetDensity::Comfortable, + teksilo_tokens::TargetDensity::Touch, + ] { + let tokens = InputTokens::for_density(density); + assert_eq!( + macos_table_style_for(&tokens).recipe.row_height, + crate::styles::standard_item::macos_standard_item_recipe_for(&tokens) + .min_height_single_line, + "{density:?}: a TableView row and a ListView row must share one rhythm" + ); + } assert_eq!( t.tree_indent_per_level, crate::styles::standard_item::macos_standard_item_recipe().tree_indent_step, diff --git a/crates/teksilo-theme-macos/src/styles/slider.rs b/crates/teksilo-theme-macos/src/styles/slider.rs index 7131bf1b0..8d386c773 100644 --- a/crates/teksilo-theme-macos/src/styles/slider.rs +++ b/crates/teksilo-theme-macos/src/styles/slider.rs @@ -18,11 +18,19 @@ //! //! Discrete sliders get tick marks on the far side of the track, as AppKit //! draws them for an `NSSlider` with `numberOfTickMarks` set. +//! +//! Right to left, a horizontal slider runs from the right: its minimum is at +//! the leading edge, so the knob, the fill and the ticks are mirrored, as +//! the `Slider` mirrors its pointer and arrow keys. A vertical slider is laid +//! out the same way in either direction, with its minimum at the bottom, as +//! the `Slider` reads its pointer and `ArrowUp` raises the value: the fill +//! rises from the bottom to the knob. use teksilo_canvas::{Canvas, Point, Rect, Size, SizeProposal}; use teksilo_core::accessibility::AccessNodeBuilder; use teksilo_core::binding::BindingLevel; use teksilo_core::build_context::BuildContext; +use teksilo_core::environment::LayoutDirection; use teksilo_core::focus::FocusOrigin; use teksilo_core::signal::Signal; use teksilo_core::styles::{SliderOrientation, SliderStyle, SliderStyleConfig}; @@ -90,24 +98,38 @@ struct SliderAxis { /// Resolve a slider's track geometry and value axis. /// -/// **The vertical axis runs top → bottom**: `value == 0` is at the top and -/// `value == 1` at the bottom, with the fill growing downward. That is not -/// the visual convention one might reach for (a "level" that rises), and -/// getting it backwards is invisible until you grab the knob — it then -/// travels *away* from the pointer, because a style does not own the -/// mapping. `Slider`'s own hit-testing computes -/// `t = (y - thumb_radius) / usable` from the widget-local pointer -/// position; a style that disagrees is simply wrong, however it looks -/// standing still. +/// **The vertical axis runs bottom to top**: `value == 0` is at the bottom +/// and `value == 1` at the top, with the fill rising from the bottom. +/// Getting it backwards is invisible until you grab the knob: it then sits +/// opposite to where the press put the value, and `ArrowUp` moves it down, +/// because a style does not own the mapping. `Slider`'s own hit-testing +/// computes `t = 1 - (y - thumb_radius) / usable` from the widget-local +/// pointer position, and its `thumb_rect` and the shipped +/// `RecipeSliderStyle` put the thumb there; a style that disagrees is +/// simply wrong, however it looks standing still. /// /// The track is inset by the knob's radius at both ends so its travel -/// never overhangs the control — the same inset `Slider` subtracts when it +/// never overhangs the control: the same inset `Slider` subtracts when it /// maps a position to a value, which is what keeps the two in step. -fn slider_axis(orientation: SliderOrientation, bounds: Rect) -> SliderAxis { +/// +/// **A horizontal axis follows `direction`**: its minimum is at the leading +/// edge, the right one under right to left, so there `start` is the right +/// end and `end` the left one, as `Slider` reads its pointer and arrow keys. +/// The vertical axis does not depend on the direction. +fn slider_axis( + orientation: SliderOrientation, + direction: LayoutDirection, + bounds: Rect, +) -> SliderAxis { let half = THUMB * 0.5; match orientation { SliderOrientation::Horizontal => { let cy = bounds.y + bounds.height * 0.5; + let (left, right) = (bounds.x + half, bounds.x + bounds.width - half); + let (start, end) = match direction { + LayoutDirection::LeftToRight => (left, right), + LayoutDirection::RightToLeft => (right, left), + }; SliderAxis { track: Rect::new( bounds.x + half, @@ -115,8 +137,8 @@ fn slider_axis(orientation: SliderOrientation, bounds: Rect) -> SliderAxis { (bounds.width - THUMB).max(0.0), TRACK, ), - start: bounds.x + half, - end: bounds.x + bounds.width - half, + start, + end, centre_cross: cy, } } @@ -129,8 +151,8 @@ fn slider_axis(orientation: SliderOrientation, bounds: Rect) -> SliderAxis { TRACK, (bounds.height - THUMB).max(0.0), ), - start: bounds.y + half, - end: bounds.y + bounds.height - half, + start: bounds.y + bounds.height - half, + end: bounds.y + half, centre_cross: cx, } } @@ -207,7 +229,7 @@ impl Widget for MacOsSliderBody { start, end, centre_cross, - } = slider_axis(self.orientation, bounds); + } = slider_axis(self.orientation, ctx.layout_direction, bounds); let pill = CornerRadius::uniform(TRACK * 0.5); // No hairline on the rail, unlike the switch track and the // unchecked checkbox and radio, which all carry a `border_strong` @@ -220,29 +242,21 @@ impl Widget for MacOsSliderBody { // deliberately faint. canvas.fill_rounded_rect(track, pill, rail); - // Filled portion, from the track's origin to the knob — rightward - // when horizontal, *downward* when vertical (see `slider_axis`). - match self.orientation { - SliderOrientation::Horizontal => { - let w = track.width * t; - if w > 0.0 { - canvas.fill_rounded_rect( - Rect::new(track.x, track.y, w, track.height), - pill, - fill, - ); + // Filled portion, from the minimum's end of the track to the knob: + // from the leading edge when horizontal, from the bottom when + // vertical (see `slider_axis`). + let knob = lerp(start, end, t); + let extent = (knob - start).abs(); + if extent > 0.0 { + let filled = match self.orientation { + SliderOrientation::Horizontal => { + Rect::new(start.min(knob), track.y, extent, track.height) } - } - SliderOrientation::Vertical => { - let h = track.height * t; - if h > 0.0 { - canvas.fill_rounded_rect( - Rect::new(track.x, track.y, track.width, h), - pill, - fill, - ); + SliderOrientation::Vertical => { + Rect::new(track.x, start.min(knob), track.width, extent) } - } + }; + canvas.fill_rounded_rect(filled, pill, fill); } // Discrete ticks, on the far side of the track. @@ -280,10 +294,9 @@ impl Widget for MacOsSliderBody { // The knob: the same bezel a push button wears, at 18 dp and round. let half = THUMB * 0.5; - let pos = lerp(start, end, t); let centre = match self.orientation { - SliderOrientation::Horizontal => Point::new(pos, centre_cross), - SliderOrientation::Vertical => Point::new(centre_cross, pos), + SliderOrientation::Horizontal => Point::new(knob, centre_cross), + SliderOrientation::Vertical => Point::new(centre_cross, knob), }; paint_knob(canvas, centre, half, state, ctx); @@ -342,21 +355,89 @@ fn paint_knob( mod tests { use super::*; - /// The bug this guards: a vertical slider whose paint ran bottom-to-top - /// while `Slider`'s hit-testing ran top-to-bottom. Everything looked - /// plausible until you grabbed the knob, which then slid the opposite - /// way to the pointer. + const LTR: LayoutDirection = LayoutDirection::LeftToRight; + const RTL: LayoutDirection = LayoutDirection::RightToLeft; + + /// The bug this guards: a vertical slider painted with its minimum at + /// the top while `Slider` puts it at the bottom, so the knob sat + /// opposite to where a press put the value and `ArrowUp` moved it down. + /// + /// This mounts the real `Slider` under this style at several values, in + /// both orientations and both directions, and asks it where its thumb is + /// (`target_regions`, from the same `thumb_rect` its value readout uses + /// and that agrees with its pointer mapping). The axis has to put the + /// knob's centre there. #[test] fn the_value_axis_runs_the_same_way_the_widget_maps_the_pointer() { + use teksilo_tokens::Orientation; + use teksilo_widgets::Slider; + use teksilo_widgets::slider::SLIDER_PART_THUMB; + + for (orientation, widget_orientation) in [ + (SliderOrientation::Horizontal, Orientation::Horizontal), + (SliderOrientation::Vertical, Orientation::Vertical), + ] { + for direction in [LTR, RTL] { + for value in [0.0_f32, 0.2, 0.5, 0.9, 1.0] { + let mut tree = teksilo_core::widget_tree::WidgetTree::new() + .with_theme(crate::light()) + .with_text_backend(std::rc::Rc::new(std::cell::RefCell::new( + teksilo_canvas::MockTextBackend::new(), + ))); + tree.set_layout_direction(direction); + let slider = tree.add( + Slider::new(Signal::new(value), 0.0, 1.0) + .orientation(widget_orientation) + .style(MacOsSliderStyle), + ); + tree.layout(SizeProposal::exact(200.0, 200.0)); + + let bounds = tree.bounds(slider); + let thumb = tree + .widget_target_regions(slider) + .into_iter() + .find(|r| r.part == SLIDER_PART_THUMB) + .expect("the slider reports its thumb") + .rect; + let axis = slider_axis(orientation, direction, bounds); + let painted = lerp(axis.start, axis.end, value); + let reported = match orientation { + SliderOrientation::Horizontal => thumb.x + thumb.width * 0.5, + SliderOrientation::Vertical => thumb.y + thumb.height * 0.5, + }; + assert!( + (painted - reported).abs() < 1e-3, + "{orientation:?} {direction:?} at {value}: the knob is \ + painted at {painted}, the widget's thumb is at {reported}" + ); + } + } + } + } + + /// Right to left, a horizontal axis runs from the right end to the left + /// one over the same track, as `Slider` reads its pointer from the other + /// end; a vertical axis is the same in both directions. + #[test] + fn right_to_left_mirrors_the_horizontal_axis_only() { let bounds = Rect::new(10.0, 20.0, 200.0, 300.0); - for orientation in [SliderOrientation::Horizontal, SliderOrientation::Vertical] { - let axis = slider_axis(orientation, bounds); + let l = slider_axis(SliderOrientation::Horizontal, LTR, bounds); + let r = slider_axis(SliderOrientation::Horizontal, RTL, bounds); + assert_eq!((r.start, r.end), (l.end, l.start)); + assert_eq!(r.track, l.track); + assert_eq!(r.centre_cross, l.centre_cross); + let mid = bounds.x + bounds.width * 0.5; + for t in [0.0, 0.25, 0.5, 1.0] { + let (a, b) = (lerp(l.start, l.end, t), lerp(r.start, r.end, t)); assert!( - axis.end > axis.start, - "{orientation:?}: value must grow with the coordinate, as \ - Slider's own position→value mapping does" + (a - mid + (b - mid)).abs() < 1e-4, + "the knob at {t} is mirrored about the centre: {a} and {b}" ); } + + let lv = slider_axis(SliderOrientation::Vertical, LTR, bounds); + let rv = slider_axis(SliderOrientation::Vertical, RTL, bounds); + assert_eq!((rv.start, rv.end, rv.track), (lv.start, lv.end, lv.track)); } /// `Slider` subtracts exactly one knob diameter from the extent when it @@ -366,12 +447,12 @@ mod tests { fn painted_travel_matches_the_widgets_usable_extent() { let bounds = Rect::new(10.0, 20.0, 200.0, 300.0); - let h = slider_axis(SliderOrientation::Horizontal, bounds); + let h = slider_axis(SliderOrientation::Horizontal, LTR, bounds); assert!((h.end - h.start - (bounds.width - THUMB)).abs() < 1e-4); assert!((h.track.width - (bounds.width - THUMB)).abs() < 1e-4); - let v = slider_axis(SliderOrientation::Vertical, bounds); - assert!((v.end - v.start - (bounds.height - THUMB)).abs() < 1e-4); + let v = slider_axis(SliderOrientation::Vertical, LTR, bounds); + assert!((v.start - v.end - (bounds.height - THUMB)).abs() < 1e-4); assert!((v.track.height - (bounds.height - THUMB)).abs() < 1e-4); } @@ -379,11 +460,11 @@ mod tests { fn the_track_is_centred_on_the_cross_axis() { let bounds = Rect::new(10.0, 20.0, 200.0, 300.0); - let h = slider_axis(SliderOrientation::Horizontal, bounds); + let h = slider_axis(SliderOrientation::Horizontal, LTR, bounds); assert_eq!(h.centre_cross, bounds.y + bounds.height * 0.5); assert!((h.track.y + h.track.height * 0.5 - h.centre_cross).abs() < 1e-4); - let v = slider_axis(SliderOrientation::Vertical, bounds); + let v = slider_axis(SliderOrientation::Vertical, LTR, bounds); assert_eq!(v.centre_cross, bounds.x + bounds.width * 0.5); assert!((v.track.x + v.track.width * 0.5 - v.centre_cross).abs() < 1e-4); } @@ -393,7 +474,7 @@ mod tests { fn a_slot_smaller_than_the_knob_clamps_to_zero() { let tiny = Rect::new(0.0, 0.0, 4.0, 4.0); for orientation in [SliderOrientation::Horizontal, SliderOrientation::Vertical] { - let axis = slider_axis(orientation, tiny); + let axis = slider_axis(orientation, LTR, tiny); assert!(axis.track.width >= 0.0 && axis.track.height >= 0.0); } } diff --git a/crates/teksilo-theme-macos/src/styles/standard_item.rs b/crates/teksilo-theme-macos/src/styles/standard_item.rs index ac9efc133..d1c98455a 100644 --- a/crates/teksilo-theme-macos/src/styles/standard_item.rs +++ b/crates/teksilo-theme-macos/src/styles/standard_item.rs @@ -115,7 +115,9 @@ impl StandardItemStyle for MacOsStandardItemStyle { is_hovered: cfg.is_hovered.clone(), is_pressed: cfg.is_pressed.clone(), is_disabled: cfg.is_disabled.clone(), - emphasised: cfg.is_focused.and(&cfg.is_window_active), + emphasised: self + .selection_emphasis() + .emphasised(&cfg.is_focused, &cfg.is_window_active), show_ring: cfg .is_selected .zip3(&cfg.is_focused, &cfg.is_focus_visible) @@ -147,6 +149,18 @@ impl StandardItemStyle for MacOsStandardItemStyle { // done from `make_body`. Some(TextRole::OnAccent) } + + /// `NSTableView.rowHeight`, 24 dp at Compact, raised to the density's + /// target size above it. The row measures itself against this, so it is + /// the height a macOS list row renders. + fn min_height_single_line(&self, tokens: &InputTokens) -> f32 { + macos_standard_item_recipe_for(tokens).min_height_single_line + } + + /// The two-line row height of [`macos_standard_item_recipe_for`]. + fn min_height_two_line(&self, tokens: &InputTokens) -> f32 { + macos_standard_item_recipe_for(tokens).min_height_two_line + } } /// Stacks the selection capsule under the delegated row, stretching diff --git a/crates/teksilo-theme-macos/src/typography.rs b/crates/teksilo-theme-macos/src/typography.rs index 5fadc0615..4e782b28c 100644 --- a/crates/teksilo-theme-macos/src/typography.rs +++ b/crates/teksilo-theme-macos/src/typography.rs @@ -26,7 +26,7 @@ //! //! ## Tracking is the interesting part //! -//! macOS is the only one of the three design languages Teksilo ships that +//! macOS is the only one of the design languages Teksilo ships that //! tracks text **non-uniformly and with a sign change**. Apple publishes a //! tracking value for every integer point size from 6 to 96; the region //! this ramp lives in reads: diff --git a/crates/teksilo-theme-macos/tests/high_contrast.rs b/crates/teksilo-theme-macos/tests/high_contrast.rs new file mode 100644 index 000000000..2e9527369 --- /dev/null +++ b/crates/teksilo-theme-macos/tests/high_contrast.rs @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! macOS Aqua and Dark Aqua under the OS "increase contrast" preference. +//! +//! The preset registers no `PaintProjection`, so it is painted through +//! `ColorTokens::for_high_contrast`, which lifts its own accent, ring, +//! secondary text and selection. Every pair that projection documents is +//! measured on both appearances and each of the eight System Settings +//! accents, for an active and an inactive window. + +use teksilo_core::styles::{PaintState, Theme}; +use teksilo_theme_macos::{self as macos, SystemAccent}; +use teksilo_tokens::contrast::composite_contrast; +use teksilo_tokens::{Color, ColorTokens}; + +fn themes() -> Vec<(String, Theme)> { + let mut out = vec![ + ("Aqua".to_owned(), macos::light()), + ("Dark Aqua".to_owned(), macos::dark()), + ]; + for accent in SystemAccent::ALL { + out.push(( + format!("Aqua, {}", accent.as_str()), + macos::light_with_accent(accent.light()), + )); + out.push(( + format!("Dark Aqua, {}", accent.as_str()), + macos::dark_with_accent(accent.dark()), + )); + } + out +} + +/// `[wcag]` SC 1.4.6 for text, and the 4.5:1 the projection holds every +/// non-text pair it changes to; 1.5:1 is how far it moves the selection off +/// `surface_content`. +const TEXT: f32 = 7.0; +const NON_TEXT: f32 = 4.5; +const SELECTION: f32 = 1.5; + +fn high_contrast(theme: &Theme, window_active: bool) -> ColorTokens { + theme + .for_paint_state(PaintState { + window_active, + high_contrast: true, + }) + .colors +} + +/// Every pair `ColorTokens::for_high_contrast` documents, below its floor in +/// `c`, named with its ratio. +fn shortfalls(c: &ColorTokens) -> Vec { + let surfaces = [ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ("surface_raised", c.surface_raised), + ]; + let mut pairs: Vec<(String, Color, Color, f32)> = Vec::new(); + for (fill, bg) in [ + ("accent", c.accent), + ("accent_hover", c.accent_hover), + ("accent_pressed", c.accent_pressed), + ] { + pairs.push(( + format!("text_on_accent on {fill}"), + c.text_on_accent, + bg, + TEXT, + )); + // White text in a light palette, black in a dark one: the accent + // clears the surfaces as well. + let text_light = c.text_on_accent.relative_luminance() > 0.5; + let surfaces_light = c.surface_main.relative_luminance() > 0.5; + if text_light == surfaces_light { + for (s, s_bg) in surfaces { + pairs.push((format!("{fill} on {s}"), bg, s_bg, NON_TEXT)); + } + } + } + for (s, bg) in surfaces { + pairs.push((format!("focus_ring on {s}"), c.focus_ring, bg, NON_TEXT)); + pairs.push(( + format!("border_focused on {s}"), + c.border_focused, + bg, + NON_TEXT, + )); + pairs.push((format!("text_secondary on {s}"), c.text_secondary, bg, TEXT)); + } + // The focused selection, and the one a row keeps while its view is + // unfocused or its window inactive. + for (fill, bg) in [ + ("surface_selected", c.surface_selected), + ("surface_selected_inactive", c.surface_selected_inactive), + ] { + pairs.push(( + format!("text_secondary on {fill}"), + c.text_secondary, + bg, + TEXT, + )); + pairs.push((format!("text_primary on {fill}"), c.text_primary, bg, TEXT)); + pairs.push(( + format!("{fill} on surface_content"), + bg, + c.surface_content, + SELECTION, + )); + } + pairs + .into_iter() + .filter_map(|(name, fg, bg, floor)| { + let r = composite_contrast(fg, bg); + (r < floor).then(|| format!("{name} = {r:.2}, floor {floor}")) + }) + .collect() +} + +#[test] +fn every_high_contrast_pair_meets_its_floor() { + for (name, theme) in themes() { + for window_active in [true, false] { + let short = shortfalls(&high_contrast(&theme, window_active)); + assert!( + short.is_empty(), + "{name}, window active {window_active}: {short:#?}" + ); + } + } +} + +#[test] +fn high_contrast_keeps_the_accent_hue() { + // The projection used to swap IntUI's teal into every dark palette. + for (name, theme) in themes() { + for window_active in [true, false] { + let before = theme + .for_paint_state(PaintState { + window_active, + high_contrast: false, + }) + .colors + .accent; + let after = high_contrast(&theme, window_active).accent; + let (h0, s0, _) = before.to_hsv(); + let (h1, s1, _) = after.to_hsv(); + if s0 > 0.05 && s1 > 0.05 { + let d = (h0 - h1).abs(); + let d = d.min(360.0 - d); + assert!( + d < 1.0, + "{name}, window active {window_active}: hue {h0} became {h1}" + ); + } + } + } +} diff --git a/crates/teksilo-theme-macos/tests/mount.rs b/crates/teksilo-theme-macos/tests/mount.rs index 1c90a8753..b6ac8ca30 100644 --- a/crates/teksilo-theme-macos/tests/mount.rs +++ b/crates/teksilo-theme-macos/tests/mount.rs @@ -463,7 +463,7 @@ fn the_menu_row_declares_an_on_accent_highlight() { use teksilo_core::styles::MenuItemStyle; use teksilo_tokens::TextRole; - let style = teksilo_theme_macos::styles::menu_item::MacOsMenuItemStyle; + let style = teksilo_theme_macos::styles::menu_item::MacOsMenuItemStyle::default(); assert_eq!(style.highlighted_label_role(), Some(TextRole::OnAccent)); // …and the theme actually installs that style rather than a recipe. for theme in themes() { diff --git a/crates/teksilo-theme-macos/tests/preset_metrics.rs b/crates/teksilo-theme-macos/tests/preset_metrics.rs index 04d252bac..4e09cf508 100644 --- a/crates/teksilo-theme-macos/tests/preset_metrics.rs +++ b/crates/teksilo-theme-macos/tests/preset_metrics.rs @@ -518,3 +518,563 @@ fn a_macos_switch_track_is_still_thirty_eight_by_twenty_two() { frame.shapes.iter().map(|s| s.screen).collect::>(), ); } + +// ── StandardListItem row height ──────────────────────────────────────── + +/// The laid-out `(single-line, two-line)` heights of a `StandardListItem` row +/// in a plain column, built at `density` under `theme`. +/// +/// Built *at* the density, never switched into it, for the same reason +/// [`nav_arrow_targets`] is: a row resolves its style in `build()`. +fn standard_row_heights(theme: &Theme, density: TargetDensity) -> (f32, f32) { + use teksilo_i18n::lit; + use teksilo_widgets::StandardListItem; + use teksilo_widgets::primitives::VStack; + + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let root = tree.add( + VStack::new() + .child(StandardListItem::new(lit!("Documents"))) + .child(StandardListItem::new(lit!("Downloads")).subtitle(lit!("12 items"))), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let rows = nodes_named(&tree, root, "StandardListItem"); + assert_eq!(rows.len(), 2, "one single-line row, one two-line row"); + (tree.bounds(rows[0]).height, tree.bounds(rows[1]).height) +} + +/// `NSTableView.rowHeight` is 24 dp, which is what the macOS recipe writes, +/// and a laid-out row must measure it. The row used to floor itself on the +/// shipped 28 dp constant whatever the style declared, so every macOS list +/// row rendered 4 dp taller than Apple's. +/// +/// Above Compact the preset's 24 rises to the density's target size (32 at +/// Comfortable, 44 at Touch), so the row tracks the ladder too. The IntUI arm +/// is the control: 28 / 32 / 44 for a single line, 44 for two lines, which is +/// what the row rendered before the style was consulted. The one exception is +/// IntUI's two-line row at Touch, whose content (44.4 under the mock text +/// metrics, with Touch's wider padding) already clears its 44 dp floor: a +/// floor only ever raises a row. +#[test] +fn a_standard_list_row_measures_the_macos_row_height_at_every_density() { + let expected = [ + (TargetDensity::Compact, (24.0, 40.0), (28.0, 44.0)), + (TargetDensity::Comfortable, (32.0, 40.0), (32.0, 44.0)), + (TargetDensity::Touch, (44.0, 44.0), (44.0, 44.4)), + ]; + assert_eq!( + expected.len(), + ALL_DENSITIES.len(), + "every density is listed" + ); + for (density, macos, intui) in expected { + assert_eq!( + standard_row_heights(&teksilo_core::presets::intui::light(), density), + intui, + "{density:?}: IntUI must be unchanged", + ); + for theme in [teksilo_theme_macos::light(), teksilo_theme_macos::dark()] { + assert_eq!( + standard_row_heights(&theme, density), + macos, + "{density:?}: the macOS recipe's row heights must reach the row", + ); + } + // The accessor half: what the installed slot tells a row asking it. + let theme = teksilo_theme_macos::light().with_density(density); + let slot = theme + .style_slots + .standard_item + .clone() + .expect("the preset installs a standard-item slot"); + assert_eq!( + ( + slot.min_height_single_line(&theme.input), + slot.min_height_two_line(&theme.input), + ), + macos, + "{density:?}: the installed slot reports the macOS row heights", + ); + } +} + +/// A `StandardTreeItem` row measures the macOS row height too. The tree row +/// lays out through the `StandardListItem` it wraps, so the floor it reports +/// is the one the list row resolves from the same style: 24 dp for a single +/// line and 40 dp with a subtitle at Compact, rising to the density's target +/// size above it, whatever the row's depth. +#[test] +fn a_standard_tree_row_measures_the_macos_row_height_at_every_density() { + use teksilo_i18n::lit; + use teksilo_widgets::StandardTreeItem; + use teksilo_widgets::primitives::VStack; + + let expected = [ + (TargetDensity::Compact, (24.0, 40.0)), + (TargetDensity::Comfortable, (32.0, 40.0)), + (TargetDensity::Touch, (44.0, 44.0)), + ]; + assert_eq!( + expected.len(), + ALL_DENSITIES.len(), + "every density is listed" + ); + for (density, macos) in expected { + for theme in [teksilo_theme_macos::light(), teksilo_theme_macos::dark()] { + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let root = tree.add( + VStack::new() + .child(StandardTreeItem::new(lit!("Documents")).depth(1)) + .child( + StandardTreeItem::new(lit!("Downloads")) + .depth(2) + .subtitle(lit!("12 items")), + ), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let rows = nodes_named(&tree, root, "StandardTreeItem"); + assert_eq!(rows.len(), 2, "one single-line row, one two-line row"); + assert_eq!( + (tree.bounds(rows[0]).height, tree.bounds(rows[1]).height), + macos, + "{density:?}: the macOS recipe's row heights must reach a tree row", + ); + } + } +} + +// ── TableView / TreeTableView row and header heights ─────────────────── + +/// The laid-out `(header height, row height)` of a `TableView` and of a +/// `TreeTableView` over the same three rows, built at `density` under `theme` +/// with no row or header height of their own. Each pair is measured on the +/// hosted `TableHeader` and on the first realized `BodyRow`, and the first +/// row must start where the header ends, so the band the virtualization +/// arithmetic steps by is the one painted. +/// +/// Built *at* the density, never switched into it: a view reads its style's +/// heights in `build()`. +fn table_bands(theme: &Theme, density: TargetDensity) -> [(f32, f32); 2] { + use teksilo_data::{ListModel, TreeModel}; + use teksilo_i18n::lit; + use teksilo_widgets::TableView; + use teksilo_widgets::TreeTableView; + use teksilo_widgets::table_view::{Column, ColumnWidth}; + + fn bands(tree: &WidgetTree, root: WidgetId) -> (f32, f32) { + let header = nodes_named(tree, root, "TableHeader"); + assert_eq!(header.len(), 1, "one hosted header"); + let mut rows = nodes_named(tree, root, "BodyRow"); + rows.sort_by(|a, b| tree.bounds(*a).y.total_cmp(&tree.bounds(*b).y)); + assert!(rows.len() >= 2, "at least two realized rows"); + let h = tree.bounds(header[0]); + let first = tree.bounds(rows[0]); + assert_eq!( + first.y, + h.y + h.height, + "the first row starts under the header" + ); + assert_eq!( + tree.bounds(rows[1]).y - first.y, + first.height, + "the row pitch is the row height", + ); + (h.height, first.height) + } + fn column(label: fn(&T) -> String) -> Column { + Column::new("name", lit!("Name"), move |row: &T, _| { + Box::new(teksilo_widgets::primitives::TextWidget::new(lit!(label( + row + )))) + }) + .width(ColumnWidth::Fixed(200.0)) + } + let theme = theme.with_density(density); + + let model = ListModel::from_vec(vec!["alpha".to_string(), "beta".into(), "gamma".into()]); + let (tree, root) = mounted( + theme.clone(), + TableView::new(model).add_column(column(|s: &String| s.clone())), + (400.0, 300.0), + ); + let flat = bands(&tree, root); + + let model: TreeModel = TreeModel::new(); + for (i, name) in ["alpha", "beta", "gamma"].into_iter().enumerate() { + model.insert_root(i, name.to_string()); + } + let (tree, root) = mounted( + theme, + TreeTableView::new(model).add_column(column(|s: &String| s.clone())), + (400.0, 300.0), + ); + [flat, bands(&tree, root)] +} + +/// `NSTableView.rowHeight` is 24 dp, and the macOS recipe writes it for a +/// table row and its header; a laid-out `TableView` and `TreeTableView` must +/// measure it. Both views used to read the shipped 28 dp row and 32 dp header +/// constants whatever the style declared. +/// +/// Above Compact the 24 rises to the density's target size (32 at +/// Comfortable, 44 at Touch), as the list row it shares a rhythm with does. +/// The IntUI arm is the control: 32 / 28 at Compact, which is what the views +/// laid out before the style was consulted, then the recipe's own ladder. +#[test] +fn a_table_measures_the_macos_row_and_header_heights_at_every_density() { + let expected = [ + (TargetDensity::Compact, (24.0, 24.0), (32.0, 28.0)), + (TargetDensity::Comfortable, (32.0, 32.0), (32.0, 32.0)), + (TargetDensity::Touch, (44.0, 44.0), (44.0, 44.0)), + ]; + assert_eq!( + expected.len(), + ALL_DENSITIES.len(), + "every density is listed" + ); + for (density, macos, intui) in expected { + assert_eq!( + table_bands(&teksilo_core::presets::intui::light(), density), + [intui; 2], + "{density:?}: IntUI follows its recipe", + ); + for theme in [teksilo_theme_macos::light(), teksilo_theme_macos::dark()] { + assert_eq!( + table_bands(&theme, density), + [macos; 2], + "{density:?}: the macOS recipe's heights must reach both views", + ); + } + // The accessor half: what the installed slot tells a view asking it. + let theme = teksilo_theme_macos::light().with_density(density); + let slot = theme + .style_slots + .table + .clone() + .expect("the preset installs a table slot"); + assert_eq!( + ( + slot.header_height(&theme.input), + slot.row_height(&theme.input) + ), + macos, + "{density:?}: the installed slot reports the macOS heights", + ); + } +} + +// ── TabBar tab height and header padding ─────────────────────────────── + +/// The laid-out `(strip height, tab height, header padding)` of a horizontal +/// two-tab `TabBar` built at `density` under `theme`, with no tab height of +/// its own. The padding is the inset of each header's content row inside the +/// `Padding` that wraps it, and it must be the same on both edges and on both +/// headers, so the number measured is the one the header's natural width was +/// sized from. +/// +/// Built *at* the density, never switched into it: a bar reads its style's +/// metrics when it builds. +fn tab_metrics(theme: &Theme, density: TargetDensity) -> (f32, f32, f32) { + use teksilo_core::signal::Signal; + use teksilo_data::ListModel; + use teksilo_i18n::lit; + use teksilo_widgets::tab_widget::{TabBar, TabDelegate, TabId, TabSizing}; + + let model = ListModel::from_vec(vec![ + (TabId::fresh(), "Documents"), + (TabId::fresh(), "Downloads"), + ]); + let delegate = TabDelegate::new(|_, t: &(TabId, &'static str)| lit!(t.1)); + let selected: Signal> = Signal::new(None); + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let bar = tree.add( + TabBar::horizontal(model, delegate, selected, |_, t| t.0) + .tab_sizing(TabSizing::Independent) + .show_scroll_arrows(false) + .show_overflow_dropdown(false), + ); + tree.layout(SizeProposal { + width: Some(800.0), + height: None, + }); + + let headers = nodes_named(&tree, bar, "TabHeader"); + assert_eq!(headers.len(), 2, "two tab headers"); + let tab_h = tree.bounds(headers[0]).height; + let mut pads = Vec::new(); + for header in headers { + assert_eq!(tree.bounds(header).height, tab_h, "one tab height"); + let padding = nodes_named(&tree, header, "Padding"); + assert_eq!(padding.len(), 1, "one padded content row per header"); + let outer = tree.bounds(padding[0]); + let row = tree.bounds(tree.children(padding[0])[0]); + let leading = row.x - outer.x; + let trailing = (outer.x + outer.width) - (row.x + row.width); + assert!( + (leading - trailing).abs() < 1e-3, + "{density:?}: the padding is symmetric ({leading} / {trailing})", + ); + pads.push(leading); + } + assert!((pads[0] - pads[1]).abs() < 1e-3, "one padding: {pads:?}"); + (tree.bounds(bar).height, tab_h, pads[0]) +} + +/// Assert `(strip, tab, padding)` measures `want` within float tolerance. +fn assert_tab_metrics(got: (f32, f32, f32), want: (f32, f32, f32), what: &str) { + assert!( + (got.0 - want.0).abs() < 1e-3 && (got.1 - want.1).abs() < 1e-3, + "{what}: strip and tab heights {got:?}, want {want:?}", + ); + assert!( + (got.2 - want.2).abs() < 1e-3, + "{what}: header padding {got:?}, want {want:?}", + ); +} + +/// `NSTabView`'s header strip is 26 dp in the macOS recipe and its titles sit +/// 12 dp in from each edge, and a laid-out bar must measure both. The bar used +/// to reserve the shipped 50 dp strip and pad by the shipped 12 dp whatever +/// the style declared, so every macOS tab strip rendered 24 dp taller than +/// its recipe. +/// +/// Above Compact the 26 rises to the density's target size (32 at +/// Comfortable, 44 at Touch) and the padding scales by the spacing factor +/// (13.8, 15.6). The IntUI arm is the control: its 50 dp tab clears every +/// target, and its padding walks the same spacing ladder. +#[test] +fn a_tab_bar_measures_the_macos_tab_height_and_padding_at_every_density() { + let expected = [ + ( + TargetDensity::Compact, + (26.0, 26.0, 12.0), + (50.0, 50.0, 12.0), + ), + ( + TargetDensity::Comfortable, + (32.0, 32.0, 13.8), + (50.0, 50.0, 13.8), + ), + (TargetDensity::Touch, (44.0, 44.0, 15.6), (50.0, 50.0, 15.6)), + ]; + assert_eq!( + expected.len(), + ALL_DENSITIES.len(), + "every density is listed" + ); + for (density, macos, intui) in expected { + assert_tab_metrics( + tab_metrics(&teksilo_core::presets::intui::light(), density), + intui, + &format!("{density:?}: IntUI"), + ); + for theme in [teksilo_theme_macos::light(), teksilo_theme_macos::dark()] { + assert_tab_metrics( + tab_metrics(&theme, density), + macos, + &format!("{density:?}: the macOS recipe's tab metrics must reach the bar"), + ); + } + // The accessor half: what the installed slot tells a bar asking it. + let theme = teksilo_theme_macos::light().with_density(density); + let slot = theme + .style_slots + .tab + .clone() + .expect("the preset installs a tab slot"); + assert!((slot.editor_height(&theme.input) - macos.1).abs() < 1e-3); + assert!((slot.padding_horizontal(&theme.input) - macos.2).abs() < 1e-3); + } +} + +// ── ScrollBar minimum thumb length ───────────────────────────────────── + +/// The laid-out thumb length of a vertical `ScrollBar` over a 400 dp track +/// built at `density` under `theme`, with no floor of its own. The content is +/// fifty viewports long (viewport ratio 0.02), so the unfloored thumb would be +/// 8 dp and the length measured is the floor alone. Read from the mounted bar's +/// reported thumb, which is the rectangle its hit test and its drag use. +/// +/// Built *at* the density, never switched into it: a bar reads its style's +/// floor when it builds. +fn scroll_thumb_length(theme: &Theme, density: TargetDensity) -> f32 { + use teksilo_core::signal::Signal; + use teksilo_widgets::scroll_bar::{SCROLLBAR_PART_THUMB, ScrollBar, ScrollBarOrientation}; + + let mut tree = WidgetTree::new() + .with_theme(theme.with_density(density)) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let bar = tree.add( + ScrollBar::new( + ScrollBarOrientation::Vertical, + Signal::new(0.0), + Signal::new(500.0), + Signal::new(0.02), + ) + .thickness(12.0), + ); + tree.layout(SizeProposal::exact(12.0, 400.0)); + tree.widget_target_regions(bar) + .into_iter() + .find(|r| r.part == SCROLLBAR_PART_THUMB) + .expect("the bar reports its thumb") + .rect + .height +} + +/// The macOS recipe floors a scroller's thumb at 28 dp, and a laid-out bar +/// must measure it. The bar used to recompute the shipped 24 dp floor whatever +/// the style declared, so every macOS thumb on a long document rendered 4 dp +/// shorter than its recipe. +/// +/// Above Compact the 28 rises to the density's target size (32 at +/// Comfortable, 44 at Touch). The IntUI arm is the control: its 24 dp floor +/// walks the same ladder. +#[test] +fn a_scroll_bar_floors_its_thumb_at_the_macos_minimum_at_every_density() { + let expected = [ + (TargetDensity::Compact, 28.0, 24.0), + (TargetDensity::Comfortable, 32.0, 32.0), + (TargetDensity::Touch, 44.0, 44.0), + ]; + for (density, preset, intui) in expected { + let got = scroll_thumb_length(&teksilo_core::presets::intui::light(), density); + assert!( + (got - intui).abs() < 1e-3, + "{density:?}: IntUI thumb {got}, want {intui}", + ); + for theme in [teksilo_theme_macos::light(), teksilo_theme_macos::dark()] { + let got = scroll_thumb_length(&theme, density); + assert!( + (got - preset).abs() < 1e-3, + "{density:?}: the macOS recipe's thumb floor must reach the bar: \ + {got}, want {preset}", + ); + } + // The accessor half: what the installed slot tells a bar asking it. + let theme = teksilo_theme_macos::light().with_density(density); + let slot = theme + .style_slots + .scroll_bar + .clone() + .expect("the preset installs a scroll bar slot"); + assert!((slot.min_thumb_length(&theme.input) - preset).abs() < 1e-3); + } +} + +// ── Menu rows ────────────────────────────────────────────────────────── + +/// A `MenuList` at `density` under `theme`, in a tappable row: the laid-out +/// height of each of its four `MenuItem` rows, of its separator, and the +/// `(width, right edge minus the row's right edge)` of the submenu row's +/// chevron column, the trailing column `MenuItem` builds from the style's +/// metrics. +/// +/// Built *at* the density, never switched into it: a row reads its style in +/// `build()`. +fn menu_geometry(theme: &Theme, density: TargetDensity) -> (Vec, f32, (f32, f32)) { + use teksilo_i18n::lit; + use teksilo_widgets::primitives::FixedSize; + use teksilo_widgets::{MenuItem, MenuList}; + + let theme = theme.with_density(density); + let icon_column = theme + .style_slots + .menu_item + .as_ref() + .expect("the preset installs a menu-item slot") + .metrics() + .icon_column_width; + let mut tree = WidgetTree::new() + .with_theme(theme) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + let menu = MenuList::new() + .item(MenuItem::new(lit!("Open"))) + .item(MenuItem::new(lit!("Save")).shortcut_label("Cmd+S")) + .separator() + .item(MenuItem::new(lit!("Checkable")).checked(Signal::new(true))) + .item(MenuItem::submenu(lit!("Submenu"), || { + Box::new(MenuList::new().item(MenuItem::new(lit!("Inner")))) + })); + let root = in_tappable_row(&mut tree, FixedSize::new().width(240.0).child(menu)); + tree.layout(SizeProposal::exact(600.0, 400.0)); + + let rows = nodes_named(&tree, root, "MenuItem"); + assert_eq!(rows.len(), 4, "four rows"); + let heights = rows.iter().map(|r| tree.bounds(*r).height).collect(); + let separators = nodes_named(&tree, root, "MenuSeparator"); + assert_eq!(separators.len(), 1, "one separator"); + let separator = tree.bounds(separators[0]).height; + + let submenu = tree.bounds(rows[3]); + let chevron = nodes_named(&tree, rows[3], "FixedSize") + .into_iter() + .map(|id| tree.bounds(id)) + .filter(|b| (b.height - icon_column).abs() < 1e-4) + .max_by(|a, b| a.x.total_cmp(&b.x)) + .expect("the submenu row has a chevron column"); + ( + heights, + separator, + (chevron.width, chevron.right() - submenu.right()), + ) +} + +/// A macOS menu row is AppKit's `[measured]` 22 dp at every density, the +/// preset's decision (the conformance gate pins it); what follows the +/// density is the gaps around it: the separator, 11 dp at Compact, and the +/// trailing column, 10 dp, both scaled by the density's spacing factor +/// (1.15 at Comfortable, 1.3 at Touch). The menu style used to report its +/// Compact metrics at every density, so a Touch separator and chevron +/// column kept their Compact sizes inside rows laid out for Touch. +#[test] +fn a_menu_measures_the_macos_rows_at_every_density() { + let expected = [ + (TargetDensity::Compact, 11.0, 10.0), + (TargetDensity::Comfortable, 12.65, 11.5), + (TargetDensity::Touch, 14.3, 13.0), + ]; + assert_eq!( + expected.len(), + ALL_DENSITIES.len(), + "every density is listed" + ); + for (density, separator, trailing) in expected { + for theme in [teksilo_theme_macos::light(), teksilo_theme_macos::dark()] { + let (rows, sep, (chevron, chevron_offset)) = menu_geometry(&theme, density); + assert_eq!(rows, vec![22.0; 4], "{density:?}: AppKit's row"); + assert!( + (sep - separator).abs() < 1e-3, + "{density:?}: the separator is {sep}, want {separator}" + ); + assert!( + (chevron - trailing).abs() < 1e-3, + "{density:?}: the chevron column is {chevron}, want {trailing}" + ); + assert!( + chevron_offset.abs() < 1e-3, + "{density:?}: the chevron column ends {chevron_offset} from the row's edge" + ); + } + // The accessor half: what the installed slot tells a row asking it. + let theme = teksilo_theme_macos::light().with_density(density); + let m = theme + .style_slots + .menu_item + .clone() + .expect("the preset installs a menu-item slot") + .metrics(); + assert_eq!(m.item_height, 22.0, "{density:?}"); + assert!((m.separator_height - separator).abs() < 1e-3, "{density:?}"); + assert!( + (m.trailing_column_width - trailing).abs() < 1e-3, + "{density:?}" + ); + } +} diff --git a/crates/teksilo-theme-macos/tests/slider_rtl.rs b/crates/teksilo-theme-macos/tests/slider_rtl.rs new file mode 100644 index 000000000..a8d37bab5 --- /dev/null +++ b/crates/teksilo-theme-macos/tests/slider_rtl.rs @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The macOS slider under right-to-left layout. +//! +//! A horizontal `Slider` reads its pointer and its arrow keys from the +//! leading edge, the right one under right to left. The style has to paint +//! the knob and the fill from the same edge, or the knob slides away from +//! the pointer dragging it and the arrow that raises the value moves the knob +//! towards the minimum. These tests mount the real widget under the macOS +//! theme in both directions and read what was painted. + +use std::cell::RefCell; +use std::rc::Rc; + +use teksilo_canvas::{MockTextBackend, Point, Rect, RenderFrame, ShapeKind, SizeProposal}; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::event::{Key, Modifiers, PointerButton}; +use teksilo_core::signal::Signal; +use teksilo_core::styles::Theme; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_tokens::Orientation; +use teksilo_widgets::primitives::FixedSize; +use teksilo_widgets::{Slider, VStack}; + +/// The knob's diameter (dp), `MacOsSliderStyle`'s. +const KNOB: f32 = 18.0; +/// The track's thickness (dp). +const TRACK: f32 = 4.0; +/// The slider's length along its main axis (dp). +const LENGTH: f32 = 200.0; +/// The type name of the node the style paints. +const BODY: &str = "MacOsSliderBody"; + +const LTR: LayoutDirection = LayoutDirection::LeftToRight; +const RTL: LayoutDirection = LayoutDirection::RightToLeft; + +fn themes() -> [(&'static str, Theme); 2] { + [ + ("Aqua", teksilo_theme_macos::light()), + ("Dark Aqua", teksilo_theme_macos::dark()), + ] +} + +fn rgba8(c: [f32; 4]) -> [u8; 4] { + c.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) +} + +/// A tree holding a `LENGTH` dp slider at `value` (0 to 1) laid out in +/// `direction`, `orientation`: the tree, the style's node bounds and the +/// value. +fn slider_tree( + theme: &Theme, + value: f32, + direction: LayoutDirection, + orientation: Orientation, +) -> (WidgetTree, Rect, Signal) { + let value = Signal::new(value); + let mut tree = WidgetTree::new() + .with_theme(theme.clone()) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + tree.set_layout_direction(direction); + let slider = Slider::new(value.clone(), 0.0, 1.0) + .orientation(orientation) + .jump_on_press(true); + let sized = match orientation { + Orientation::Horizontal => FixedSize::new().width(LENGTH).child(slider), + Orientation::Vertical => FixedSize::new().height(LENGTH).child(slider), + }; + let root = tree.add(VStack::new().child(sized)); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let bounds = body_bounds(&tree, root); + (tree, bounds, value) +} + +/// The bounds of the one node the style paints under `root`. +fn body_bounds(tree: &WidgetTree, root: WidgetId) -> Rect { + fn walk(tree: &WidgetTree, id: WidgetId, out: &mut Vec) { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(BODY)) + { + out.push(id); + } + for c in tree.children(id) { + walk(tree, c, out); + } + } + let mut out = Vec::new(); + walk(tree, root, &mut out); + assert_eq!(out.len(), 1, "one {BODY}"); + tree.bounds(out[0]) +} + +fn rerender(tree: &mut WidgetTree) -> Rc { + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.render() +} + +/// The centre of the painted knob: the one filled disc of its diameter. +fn knob_centre(frame: &RenderFrame) -> Point { + let discs: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.shape == ShapeKind::Circle && s.stroke_width == 0.0) + .filter(|s| (s.screen[2] - KNOB).abs() < 1e-3) + .collect(); + assert_eq!(discs.len(), 1, "one knob face in {:?}", frame.shapes); + let s = discs[0].screen; + Point::new(s[0] + s[2] * 0.5, s[1] + s[3] * 0.5) +} + +/// The painted fill: the accent part of the track, `[x, y, width, height]`. +fn fill_rect(frame: &RenderFrame, theme: &Theme, orientation: Orientation) -> [f32; 4] { + let accent = rgba8(theme.colors.accent.to_array()); + let fills: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.shape != ShapeKind::Circle && s.stroke_width == 0.0) + .filter(|s| rgba8(s.color) == accent) + .filter(|s| { + let cross = match orientation { + Orientation::Horizontal => s.screen[3], + Orientation::Vertical => s.screen[2], + }; + (cross - TRACK).abs() < 1e-3 + }) + .collect(); + assert_eq!(fills.len(), 1, "one fill in {:?}", frame.shapes); + fills[0].screen +} + +/// The same value paints the knob and the fill mirrored about the slider's +/// centre: the fill runs from the right end of the track to the knob. +#[test] +fn right_to_left_mirrors_the_knob_and_the_fill() { + let half = KNOB * 0.5; + for (scheme, theme) in themes() { + for value in [0.0_f32, 0.3, 0.75, 1.0] { + let (mut l_tree, lb, _) = slider_tree(&theme, value, LTR, Orientation::Horizontal); + let (mut r_tree, rb, _) = slider_tree(&theme, value, RTL, Orientation::Horizontal); + assert_eq!(rb.width, LENGTH, "{scheme}"); + let l_frame = rerender(&mut l_tree); + let r_frame = rerender(&mut r_tree); + + let (l, r) = (knob_centre(&l_frame), knob_centre(&r_frame)); + let from_left = l.x - lb.x; + let from_right = rb.x + rb.width - r.x; + assert!( + (from_left - from_right).abs() < 1e-3, + "{scheme} at {value}: the knob is {from_left} from the left LTR \ + and {from_right} from the right RTL" + ); + assert!( + (from_right - half - (LENGTH - KNOB) * value).abs() < 1e-3, + "{scheme} at {value}: the minimum is at the right" + ); + assert!((l.y - lb.y - (r.y - rb.y)).abs() < 1e-3, "{scheme}"); + + if value == 0.0 { + continue; + } + let lf = fill_rect(&l_frame, &theme, Orientation::Horizontal); + let rf = fill_rect(&r_frame, &theme, Orientation::Horizontal); + assert!( + (lf[0] - (lb.x + half)).abs() < 1e-3, + "{scheme} at {value}: LTR fills from the left end of the track" + ); + assert!( + (rf[0] + rf[2] - (rb.x + rb.width - half)).abs() < 1e-3, + "{scheme} at {value}: RTL fills from the right end of the track" + ); + assert!((rf[0] - r.x).abs() < 1e-3, "{scheme}: up to the knob"); + assert!((lf[2] - rf[2]).abs() < 1e-3, "{scheme}: the same length"); + assert!( + (lf[1] - lb.y - (rf[1] - rb.y)).abs() < 1e-3 && lf[3] == rf[3], + "{scheme}: on the same line" + ); + } + } +} + +/// A press puts the value where the knob is then painted, and a drag +/// leftward raises the value with the knob under the pointer. +#[test] +fn right_to_left_keeps_the_knob_under_the_pointer() { + let half = KNOB * 0.5; + let usable = LENGTH - KNOB; + for (scheme, theme) in themes() { + let (mut tree, b, value) = slider_tree(&theme, 0.5, RTL, Orientation::Horizontal); + rerender(&mut tree); + + // A quarter of the travel in from the left is three quarters of the + // range under right to left. + let at = Point::new(b.x + half + usable * 0.25, b.y + b.height * 0.5); + tree.pointer_move(at); + tree.pointer_down_button(at, PointerButton::Primary); + let v = value.get(); + assert!( + (v - 0.75).abs() < 1e-3, + "{scheme}: the left is the high end, {v}" + ); + let knob = knob_centre(&rerender(&mut tree)); + assert!( + (knob.x - at.x).abs() < 0.01, + "{scheme}: the knob at {} is under the pointer at {}", + knob.x, + at.x + ); + + // Dragging further left raises the value, and the knob follows. The + // first move past the slop starts the drag at the press; the second + // moves it, to 90 %, a value on the 1 % grid a stepless slider snaps + // to. + let to = Point::new(at.x - usable * 0.15, at.y); + tree.pointer_move(Point::new(at.x - usable * 0.05, at.y)); + tree.pointer_move(to); + let raised = value.get(); + assert!( + raised > v, + "{scheme}: a leftward drag raises, {v} to {raised}" + ); + let knob = knob_centre(&rerender(&mut tree)); + assert!( + (knob.x - to.x).abs() < 0.01, + "{scheme}: the knob at {} follows the pointer to {}", + knob.x, + to.x + ); + tree.pointer_up_button(to, PointerButton::Primary); + } +} + +/// The arrow that raises the value under right to left (the leftward one) +/// moves the knob left, and the fill grows from the right with it. +#[test] +fn right_to_left_arrow_keys_move_the_knob_the_way_they_point() { + for (scheme, theme) in themes() { + let (mut tree, _, value) = slider_tree(&theme, 0.5, RTL, Orientation::Horizontal); + rerender(&mut tree); + tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut tree); + let start = knob_centre(&frame); + let start_fill = fill_rect(&frame, &theme, Orientation::Horizontal); + + tree.press_key(Key::ArrowLeft, Modifiers::NONE); + assert!( + value.get() > 0.5, + "{scheme}: ArrowLeft raises, {}", + value.get() + ); + let frame = rerender(&mut tree); + let left = knob_centre(&frame); + assert!(left.x < start.x, "{scheme}: and moves the knob left"); + let fill = fill_rect(&frame, &theme, Orientation::Horizontal); + assert!(fill[2] > start_fill[2], "{scheme}: the fill grows"); + assert!( + (fill[0] + fill[2] - (start_fill[0] + start_fill[2])).abs() < 1e-3, + "{scheme}: from the same right end" + ); + + tree.press_key(Key::ArrowRight, Modifiers::NONE); + tree.press_key(Key::ArrowRight, Modifiers::NONE); + assert!( + value.get() < 0.5, + "{scheme}: ArrowRight lowers, {}", + value.get() + ); + let right = knob_centre(&rerender(&mut tree)); + assert!(right.x > start.x, "{scheme}: and moves the knob right"); + } +} + +/// A vertical slider has no leading edge: it paints the same in both +/// directions. +#[test] +fn a_vertical_slider_paints_the_same_in_both_directions() { + for (scheme, theme) in themes() { + let (mut l_tree, lb, _) = slider_tree(&theme, 0.3, LTR, Orientation::Vertical); + let (mut r_tree, rb, _) = slider_tree(&theme, 0.3, RTL, Orientation::Vertical); + let l_frame = rerender(&mut l_tree); + let r_frame = rerender(&mut r_tree); + let (l, r) = (knob_centre(&l_frame), knob_centre(&r_frame)); + assert!( + (l.x - lb.x - (r.x - rb.x)).abs() < 1e-3 && (l.y - lb.y - (r.y - rb.y)).abs() < 1e-3, + "{scheme}: the knob at {l:?} in {lb:?} and {r:?} in {rb:?}" + ); + let lf = fill_rect(&l_frame, &theme, Orientation::Vertical); + let rf = fill_rect(&r_frame, &theme, Orientation::Vertical); + assert!( + (lf[0] - lb.x - (rf[0] - rb.x)).abs() < 1e-3 + && (lf[1] - lb.y - (rf[1] - rb.y)).abs() < 1e-3 + && lf[2] == rf[2] + && lf[3] == rf[3], + "{scheme}: the fill {lf:?} in {lb:?} and {rf:?} in {rb:?}" + ); + } +} diff --git a/crates/teksilo-theme-macos/tests/slider_vertical.rs b/crates/teksilo-theme-macos/tests/slider_vertical.rs new file mode 100644 index 000000000..922c95df5 --- /dev/null +++ b/crates/teksilo-theme-macos/tests/slider_vertical.rs @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! The macOS slider standing vertical. +//! +//! A vertical `Slider` puts its minimum at the bottom and its maximum at the +//! top: a press near the top sets a value near the maximum, `ArrowUp` raises +//! the value, and the thumb it reports through `target_regions` rises with +//! it. The style has to paint the knob and the fill the same way, or the +//! knob sits opposite to where a press puts the value and `ArrowUp` moves it +//! down. These tests mount the real widget under the macOS theme and read +//! what was painted. + +use std::cell::RefCell; +use std::rc::Rc; + +use teksilo_canvas::{MockTextBackend, Point, Rect, RenderFrame, ShapeKind, SizeProposal}; +use teksilo_core::environment::LayoutDirection; +use teksilo_core::event::{Key, Modifiers, PointerButton}; +use teksilo_core::signal::Signal; +use teksilo_core::styles::Theme; +use teksilo_core::widget_id::WidgetId; +use teksilo_core::widget_tree::WidgetTree; +use teksilo_tokens::Orientation; +use teksilo_widgets::primitives::FixedSize; +use teksilo_widgets::slider::SLIDER_PART_THUMB; +use teksilo_widgets::{Slider, VStack}; + +/// The knob's diameter (dp), `MacOsSliderStyle`'s. +const KNOB: f32 = 18.0; +/// The track's thickness (dp). +const TRACK: f32 = 4.0; +/// The slider's height (dp). +const LENGTH: f32 = 200.0; +/// The type name of the node the style paints. +const BODY: &str = "MacOsSliderBody"; +/// The type name of the widget that maps the pointer and the keys. +const SLIDER: &str = "Slider"; + +fn themes() -> [(&'static str, Theme); 2] { + [ + ("Aqua", teksilo_theme_macos::light()), + ("Dark Aqua", teksilo_theme_macos::dark()), + ] +} + +fn rgba8(c: [f32; 4]) -> [u8; 4] { + c.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8) +} + +/// A mounted vertical slider, `LENGTH` dp tall. +struct Mounted { + tree: WidgetTree, + /// The `Slider` itself. + slider: WidgetId, + /// The bounds of the node the style paints. + body: Rect, + value: Signal, +} + +/// A tree holding a vertical slider at `value` (0 to 1) laid out in +/// `direction`. +fn mount(theme: &Theme, value: f32, direction: LayoutDirection) -> Mounted { + let value = Signal::new(value); + let mut tree = WidgetTree::new() + .with_theme(theme.clone()) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + tree.set_layout_direction(direction); + let slider = Slider::new(value.clone(), 0.0, 1.0) + .orientation(Orientation::Vertical) + .jump_on_press(true); + let root = tree.add(VStack::new().child(FixedSize::new().height(LENGTH).child(slider))); + tree.layout(SizeProposal::exact(400.0, 300.0)); + let body = tree.bounds(only(&tree, root, BODY)); + let slider = only(&tree, root, SLIDER); + Mounted { + tree, + slider, + body, + value, + } +} + +/// The one node named `name` under `root`. +fn only(tree: &WidgetTree, root: WidgetId, name: &str) -> WidgetId { + fn walk(tree: &WidgetTree, id: WidgetId, name: &str, out: &mut Vec) { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(name)) + { + out.push(id); + } + for c in tree.children(id) { + walk(tree, c, name, out); + } + } + let mut out = Vec::new(); + walk(tree, root, name, &mut out); + assert_eq!(out.len(), 1, "one {name}"); + out[0] +} + +fn rerender(tree: &mut WidgetTree) -> Rc { + tree.layout(SizeProposal::exact(400.0, 300.0)); + tree.render() +} + +/// The centre of the thumb the widget reports, the one its pointer mapping +/// and its value readout agree with. +fn widget_thumb_centre(m: &Mounted) -> Point { + let regions = m.tree.widget_target_regions(m.slider); + let thumb = regions + .iter() + .find(|r| r.part == SLIDER_PART_THUMB) + .expect("the slider reports its thumb"); + Point::new( + thumb.rect.x + thumb.rect.width * 0.5, + thumb.rect.y + thumb.rect.height * 0.5, + ) +} + +/// The centre of the painted knob: the one filled disc of its diameter. +fn knob_centre(frame: &RenderFrame) -> Point { + let discs: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.shape == ShapeKind::Circle && s.stroke_width == 0.0) + .filter(|s| (s.screen[2] - KNOB).abs() < 1e-3) + .collect(); + assert_eq!(discs.len(), 1, "one knob face in {:?}", frame.shapes); + let s = discs[0].screen; + Point::new(s[0] + s[2] * 0.5, s[1] + s[3] * 0.5) +} + +/// The painted fill, the accent part of the track, `[x, y, width, height]`; +/// `None` when nothing is filled. +fn fill_rect(frame: &RenderFrame, theme: &Theme) -> Option<[f32; 4]> { + let accent = rgba8(theme.colors.accent.to_array()); + let fills: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.shape != ShapeKind::Circle && s.stroke_width == 0.0) + .filter(|s| rgba8(s.color) == accent) + .filter(|s| (s.screen[2] - TRACK).abs() < 1e-3) + .collect(); + assert!(fills.len() <= 1, "at most one fill in {:?}", frame.shapes); + fills.first().map(|s| s.screen) +} + +/// At every value, and in either direction, the knob is painted where the +/// widget reports its thumb: the maximum at the top, the minimum at the +/// bottom. +#[test] +fn the_painted_knob_is_the_thumb_the_widget_reports() { + let half = KNOB * 0.5; + for (scheme, theme) in themes() { + for direction in [LayoutDirection::LeftToRight, LayoutDirection::RightToLeft] { + for value in [0.0_f32, 0.25, 0.5, 0.8, 1.0] { + let mut m = mount(&theme, value, direction); + assert_eq!(m.body.height, LENGTH, "{scheme}"); + assert_eq!(m.tree.bounds(m.slider), m.body, "{scheme}"); + let knob = knob_centre(&rerender(&mut m.tree)); + let thumb = widget_thumb_centre(&m); + assert!( + (knob.x - thumb.x).abs() < 1e-3 && (knob.y - thumb.y).abs() < 1e-3, + "{scheme} {direction:?} at {value}: the knob is painted at \ + {knob:?}, the widget's thumb is at {thumb:?}" + ); + let from_bottom = m.body.y + m.body.height - knob.y; + assert!( + (from_bottom - half - (LENGTH - KNOB) * value).abs() < 1e-3, + "{scheme} {direction:?} at {value}: the minimum is at the \ + bottom, the knob {from_bottom} from it" + ); + } + } + } +} + +/// The fill runs from the bottom end of the track, the minimum's, up to the +/// knob. +#[test] +fn the_fill_rises_from_the_bottom_to_the_knob() { + let half = KNOB * 0.5; + for (scheme, theme) in themes() { + let mut m = mount(&theme, 0.0, LayoutDirection::LeftToRight); + assert_eq!( + fill_rect(&rerender(&mut m.tree), &theme), + None, + "{scheme}: nothing is filled at the minimum" + ); + for value in [0.25_f32, 0.5, 0.8, 1.0] { + let mut m = mount(&theme, value, LayoutDirection::LeftToRight); + let frame = rerender(&mut m.tree); + let knob = knob_centre(&frame); + let fill = fill_rect(&frame, &theme).expect("a fill"); + let bottom = m.body.y + m.body.height - half; + assert!( + (fill[1] + fill[3] - bottom).abs() < 1e-3, + "{scheme} at {value}: the fill {fill:?} ends at the track's \ + bottom end {bottom}" + ); + assert!( + (fill[1] - knob.y).abs() < 1e-3, + "{scheme} at {value}: the fill {fill:?} reaches the knob at {}", + knob.y + ); + assert!( + (fill[3] - (LENGTH - KNOB) * value).abs() < 1e-3, + "{scheme} at {value}: the fill is as long as the value" + ); + } + } +} + +/// A press near the top sets a value near the maximum, and the knob is then +/// painted under the pointer; a drag down lowers the value and the knob +/// follows. +#[test] +fn a_press_near_the_top_puts_the_knob_under_the_pointer_near_the_maximum() { + let half = KNOB * 0.5; + let usable = LENGTH - KNOB; + for (scheme, theme) in themes() { + let mut m = mount(&theme, 0.5, LayoutDirection::LeftToRight); + rerender(&mut m.tree); + + let at = Point::new( + m.body.x + m.body.width * 0.5, + m.body.y + half + usable * 0.1, + ); + m.tree.pointer_move(at); + m.tree.pointer_down_button(at, PointerButton::Primary); + let v = m.value.get(); + assert!( + (v - 0.9).abs() < 1e-3, + "{scheme}: a press a tenth of the way down is 90 %, {v}" + ); + let knob = knob_centre(&rerender(&mut m.tree)); + assert!( + (knob.y - at.y).abs() < 0.01, + "{scheme}: the knob at {} is under the pointer at {}", + knob.y, + at.y + ); + + // The first move past the slop starts the drag at the press; the + // second moves it, to 70 %, on the 1 % grid a stepless slider snaps + // to. + let to = Point::new(at.x, at.y + usable * 0.2); + m.tree.pointer_move(Point::new(at.x, at.y + usable * 0.05)); + m.tree.pointer_move(to); + let lowered = m.value.get(); + assert!( + lowered < v, + "{scheme}: a downward drag lowers, {v} to {lowered}" + ); + let knob = knob_centre(&rerender(&mut m.tree)); + assert!( + (knob.y - to.y).abs() < 0.01, + "{scheme}: the knob at {} follows the pointer to {}", + knob.y, + to.y + ); + m.tree.pointer_up_button(to, PointerButton::Primary); + } +} + +/// `ArrowUp` raises the value and moves the painted knob up, the fill +/// growing from the same bottom end; `ArrowDown` the opposite. +#[test] +fn arrow_up_moves_the_painted_knob_up() { + for (scheme, theme) in themes() { + let mut m = mount(&theme, 0.5, LayoutDirection::LeftToRight); + rerender(&mut m.tree); + m.tree.press_key(Key::Tab, Modifiers::NONE); + let frame = rerender(&mut m.tree); + let start = knob_centre(&frame); + let start_fill = fill_rect(&frame, &theme).expect("a fill"); + + m.tree.press_key(Key::ArrowUp, Modifiers::NONE); + assert!( + m.value.get() > 0.5, + "{scheme}: ArrowUp raises, {}", + m.value.get() + ); + let frame = rerender(&mut m.tree); + let up = knob_centre(&frame); + assert!(up.y < start.y, "{scheme}: and moves the knob up"); + let fill = fill_rect(&frame, &theme).expect("a fill"); + assert!(fill[3] > start_fill[3], "{scheme}: the fill grows"); + assert!( + (fill[1] + fill[3] - (start_fill[1] + start_fill[3])).abs() < 1e-3, + "{scheme}: from the same bottom end" + ); + + m.tree.press_key(Key::ArrowDown, Modifiers::NONE); + m.tree.press_key(Key::ArrowDown, Modifiers::NONE); + assert!( + m.value.get() < 0.5, + "{scheme}: ArrowDown lowers, {}", + m.value.get() + ); + let down = knob_centre(&rerender(&mut m.tree)); + assert!(down.y > start.y, "{scheme}: and moves the knob down"); + } +} diff --git a/crates/teksilo-theme-material3/src/lib.rs b/crates/teksilo-theme-material3/src/lib.rs index c341ae0bd..8b71399ea 100644 --- a/crates/teksilo-theme-material3/src/lib.rs +++ b/crates/teksilo-theme-material3/src/lib.rs @@ -191,6 +191,8 @@ pub fn font_registrar() -> impl teksilo_text::FontRegistrar { #[cfg(test)] mod tests { use super::*; + use std::collections::BTreeSet; + use teksilo_core::styles::ComponentStyleSlots; use teksilo_tokens::Color; #[test] @@ -238,15 +240,64 @@ mod tests { ); } - #[test] - fn filled_button_hover_is_a_state_layer() { - use teksilo_core::styles::{ButtonVariant, FillRecipe}; + /// The fill the Filled button paints in `state` under `colors`, as the + /// button resolves it on every paint. + fn filled_fill( + state: teksilo_core::styles::WidgetState, + colors: &teksilo_tokens::ColorTokens, + ) -> Color { + use teksilo_core::styles::ButtonVariant; let style = styles::button::m3_button_style(&InputTokens::default()); - let filled = &style.recipes[&ButtonVariant::Filled]; - assert!(matches!( - filled.fill.hover, - Some(FillRecipe::StateLayer { .. }) - )); + style.recipes[&ButtonVariant::Filled] + .fill + .resolve(state) + .resolve_flat(colors) + .expect("a flat fill") + } + + #[test] + fn filled_button_hover_and_pressed_are_the_m3_state_layers() { + use teksilo_core::styles::WidgetState; + for theme in [light(), dark()] { + let c = &theme.colors; + for (state, alpha) in [ + (WidgetState::Idle, 0.0), + (WidgetState::Hovered, 0.08), + (WidgetState::Pressed, 0.12), + ] { + let want = c.accent.mix(c.text_on_accent, alpha); + let got = filled_fill(state, c); + for (g, w) in got.to_array().iter().zip(want.to_array()) { + assert!((g - w).abs() < 1e-6, "{state:?}: {got:?} != {want:?}"); + } + } + } + } + + #[test] + fn filled_button_label_reads_7_to_1_in_every_state_under_high_contrast() { + use teksilo_core::styles::{PaintState, WidgetState}; + use teksilo_tokens::contrast::composite_contrast; + for (name, theme) in [("light", light()), ("dark", dark())] { + for window_active in [true, false] { + let c = theme.paint_colors(PaintState { + window_active, + high_contrast: true, + }); + for state in [ + WidgetState::Idle, + WidgetState::Hovered, + WidgetState::Pressed, + WidgetState::Focused, + ] { + let r = composite_contrast(c.text_on_accent, filled_fill(state, &c)); + assert!( + r >= 7.0, + "{name}, window active {window_active}, {state:?}: {r:.2}" + ); + } + } + } } #[test] @@ -276,12 +327,102 @@ mod tests { assert_eq!(p.tertiary, Color::from_hex("#EFB8C8")); } + /// A Material 3 component this preset has not transcribed: the widget's + /// default recipe paints it, with the M3-mapped colour roles. + const NOT_TRANSCRIBED: &str = + "the M3 component is not transcribed; the recipe paints with the M3-mapped colour roles"; + /// A slot Material 3 specifies no component for. + const NO_M3_COMPONENT: &str = + "M3 specifies no such component; the recipe paints with the M3-mapped colour roles"; + + /// The slots Material 3 deliberately leaves to the widget's default + /// `Recipe*Style`, each with the reason it is inherited rather than filled. + const MATERIAL3_UNFILLED: &[(&str, &str)] = &[ + ("split_button", NOT_TRANSCRIBED), + ("splitter", NO_M3_COMPONENT), + ("icon_button", NOT_TRANSCRIBED), + ("checkbox", NOT_TRANSCRIBED), + ("radio", NOT_TRANSCRIBED), + ("radio_tile", NO_M3_COMPONENT), + ("slider", NOT_TRANSCRIBED), + ("text_input", NOT_TRANSCRIBED), + ("combo_box", NOT_TRANSCRIBED), + ("menu_item", NOT_TRANSCRIBED), + ("panel", NO_M3_COMPONENT), + ("chart", NO_M3_COMPONENT), + ("popover", NOT_TRANSCRIBED), + ("tooltip", NOT_TRANSCRIBED), + ("scroll_bar", NO_M3_COMPONENT), + ("standard_item", NOT_TRANSCRIBED), + ("tab", NOT_TRANSCRIBED), + ("dialog", NOT_TRANSCRIBED), + ("snackbar", NOT_TRANSCRIBED), + ("toast", NO_M3_COMPONENT), + ("banner", NO_M3_COMPONENT), + ("badge", NOT_TRANSCRIBED), + ("progress_bar", NOT_TRANSCRIBED), + ("link", NO_M3_COMPONENT), + ("segmented_control", NOT_TRANSCRIBED), + ("avatar", NO_M3_COMPONENT), + ("calendar", NOT_TRANSCRIBED), + ("color_picker", NO_M3_COMPONENT), + ("spin_box", NO_M3_COMPONENT), + ( + "date_edit", + "the recipe is a passthrough around the TextInput, so it follows whatever the text_input slot paints", + ), + ("search_field", NOT_TRANSCRIBED), + ("rich_text_editor", NO_M3_COMPONENT), + ("table", NO_M3_COMPONENT), + ( + "list_container", + "the recipe is only the drag-insertion line, drawn in the M3-mapped accent role", + ), + ("drop_zone", NO_M3_COMPONENT), + ("drop_target", NO_M3_COMPONENT), + ("grid_view", NO_M3_COMPONENT), + ( + "web_view", + "the page paints itself; the recipe only tints the overlay shown over it, from theme roles", + ), + ( + "text_selection", + "its geometry is teksilo_core::text_touch's, shared with hit testing, and its colours follow the M3-mapped accent role", + ), + ]; + + /// Whether the installed slots and [`MATERIAL3_UNFILLED`] are exactly + /// [`ComponentStyleSlots::NAMES`], with no slot in both: a slot added to the + /// framework must be either filled by this preset or listed with a reason. #[test] - fn widget_style_slots_installed() { - for t in [light(), dark()] { - assert!(t.style_slots.button.is_some(), "button slot"); - assert!(t.style_slots.toggle.is_some(), "toggle slot"); - assert!(t.style_slots.card.is_some(), "card slot"); + fn installed_and_unfilled_slots_partition_the_roster() { + let unfilled: BTreeSet<&str> = MATERIAL3_UNFILLED.iter().map(|(name, _)| *name).collect(); + assert_eq!( + unfilled.len(), + MATERIAL3_UNFILLED.len(), + "a slot is listed twice" + ); + for (name, reason) in MATERIAL3_UNFILLED { + assert!( + !reason.trim().is_empty(), + "{name} is listed without a reason" + ); + } + let names: BTreeSet<&str> = ComponentStyleSlots::NAMES.iter().copied().collect(); + for t in [light(), dark(), light().with_density(TargetDensity::Touch)] { + let installed: BTreeSet<&str> = t.style_slots.installed().into_iter().collect(); + let both: Vec<_> = installed.intersection(&unfilled).collect(); + assert!( + both.is_empty(), + "installed yet listed as unfilled: {both:?}" + ); + let covered: BTreeSet<&str> = installed.union(&unfilled).copied().collect(); + let missing: Vec<_> = names.difference(&covered).collect(); + let unknown: Vec<_> = covered.difference(&names).collect(); + assert!( + missing.is_empty() && unknown.is_empty(), + "neither installed nor listed: {missing:?}; not a slot: {unknown:?}" + ); } } diff --git a/crates/teksilo-theme-material3/src/styles/button.rs b/crates/teksilo-theme-material3/src/styles/button.rs index fee20f646..ef44e3009 100644 --- a/crates/teksilo-theme-material3/src/styles/button.rs +++ b/crates/teksilo-theme-material3/src/styles/button.rs @@ -15,9 +15,16 @@ //! | `Ghost` / `Link` | Text | //! | `Destructive` | Filled (error) | //! -//! The filled button's hover/pressed use exact M3 state layers -//! ([`FillRecipe::state_layer`] — an 8 % / 12 % on-primary overlay over -//! the primary fill); the error fill reuses `TextRole::Error` (which +//! The filled button's hover/pressed are the exact M3 state layers, an +//! 8 % / 12 % on-primary overlay over the primary fill, which the preset's +//! palette carries as `accent_hover` / `accent_pressed` and the recipe +//! paints through [`SurfaceRole::AccentHover`] / +//! [`SurfaceRole::AccentPressed`]. Painting the tokens rather than a +//! [`FillRecipe::state_layer`] mixed at paint time is what lets the +//! high-contrast projection, which lifts those tokens until the label reads +//! 7:1 on them, reach the hovered and pressed button; a layer mixed over the +//! lifted accent moves it back toward its own label. The error fill reuses +//! `TextRole::Error` (which //! carries the M3 error color in both schemes). Outlined/text/plain //! labels are redirected to the accent color, and the Destructive label //! to `TextRole::OnError`, via [`RecipeButtonStyle::label_roles`]. @@ -85,18 +92,10 @@ fn filled(tokens: &InputTokens) -> ButtonRecipe { shape: ShapeRecipe::Pill, fill: PerStateRecipe { idle: FillRecipe::solid(SurfaceRole::Accent), - // Exact M3 state layers: an 8 % / 12 % on-primary overlay - // composited over the primary fill at paint time. - hover: Some(FillRecipe::state_layer( - SurfaceRole::Accent, - TextRole::OnAccent, - 0.08, - )), - pressed: Some(FillRecipe::state_layer( - SurfaceRole::Accent, - TextRole::OnAccent, - 0.12, - )), + // The M3 state layers (8 % / 12 % on-primary over the primary), + // as the palette's own tokens: see the module doc. + hover: Some(FillRecipe::solid(SurfaceRole::AccentHover)), + pressed: Some(FillRecipe::solid(SurfaceRole::AccentPressed)), focused: None, disabled: Some(FillRecipe::solid(SurfaceRole::AccentDisabled)), }, diff --git a/crates/teksilo-theme-material3/src/styles/toggle.rs b/crates/teksilo-theme-material3/src/styles/toggle.rs index 316c954a4..c3f6cd8c4 100644 --- a/crates/teksilo-theme-material3/src/styles/toggle.rs +++ b/crates/teksilo-theme-material3/src/styles/toggle.rs @@ -16,10 +16,10 @@ use teksilo_core::binding::BindingLevel; use teksilo_core::build_context::BuildContext; use teksilo_core::focus::FocusOrigin; use teksilo_core::signal::Signal; -use teksilo_core::styles::{ToggleStyle, ToggleStyleConfig}; +use teksilo_core::styles::{FocusRingStroke, ToggleStyle, ToggleStyleConfig}; use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; use teksilo_core::widget_id::WidgetId; -use teksilo_tokens::{Color, CornerRadius, lerp}; +use teksilo_tokens::{Color, CornerRadius, ShapeTokens, lerp}; use crate::Material3Palette; @@ -179,16 +179,10 @@ impl Widget for M3SwitchBody { // Keyboard focus ring, drawn outside the track. if self.focus_origin.get() == Some(FocusOrigin::Keyboard) { - let offset = ctx.theme.shape.focus_ring_offset + ctx.theme.shape.focus_ring_width / 2.0; - let ring_rect = Rect::new( - track_rect.x - offset, - track_rect.y - offset, - track_rect.width + offset * 2.0, - track_rect.height + offset * 2.0, - ); + let ring = focus_ring_stroke(track_rect, &ctx.theme.shape); canvas.stroke_rounded_rect( - ring_rect, - CornerRadius::uniform(TRACK_H / 2.0 + offset), + ring.rect, + CornerRadius::uniform(ring.corner_radius), ctx.theme.colors.focus_ring, ctx.theme.shape.focus_ring_width, ); @@ -217,3 +211,36 @@ impl Widget for M3SwitchBody { // The parent Toggle owns accessibility; this body is presentational. } } + +/// The keyboard focus ring's centre line around the pill track. A negative +/// `focus_ring_offset` draws it partly inside the track. +fn focus_ring_stroke(track_rect: Rect, shape: &ShapeTokens) -> FocusRingStroke { + FocusRingStroke::around(track_rect, TRACK_H / 2.0, shape) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn shape(offset: f32) -> ShapeTokens { + ShapeTokens { + focus_ring_offset: offset, + focus_ring_width: 2.0, + ..ShapeTokens::default() + } + } + + #[test] + fn a_negative_focus_ring_offset_shrinks_the_ring_without_inverting() { + let track = Rect::new(0.0, 0.0, TRACK_W, TRACK_H); + let outer = focus_ring_stroke(track, &shape(2.0)); + let inner = focus_ring_stroke(track, &shape(-1.0)); + assert!(inner.rect.width < outer.rect.width); + assert!(inner.rect.height < outer.rect.height); + assert!(inner.rect.width >= 0.0 && inner.rect.height >= 0.0); + + let deep = focus_ring_stroke(track, &shape(-40.0)); + assert!(deep.rect.width >= 0.0 && deep.rect.height >= 0.0); + assert!(deep.corner_radius >= 0.0); + } +} diff --git a/crates/teksilo-theme-material3/tests/high_contrast.rs b/crates/teksilo-theme-material3/tests/high_contrast.rs new file mode 100644 index 000000000..5e93ab5d3 --- /dev/null +++ b/crates/teksilo-theme-material3/tests/high_contrast.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! Material 3 light and dark under the OS "increase contrast" preference. +//! +//! The preset registers no `PaintProjection`, so it is painted through +//! `ColorTokens::for_high_contrast`, which lifts its own accent, ring, +//! secondary text and selection. Every pair that projection documents is +//! measured on both appearances, for an active and an inactive window. + +use teksilo_core::styles::{PaintState, Theme}; +use teksilo_theme_material3 as material3; +use teksilo_tokens::contrast::composite_contrast; +use teksilo_tokens::{Color, ColorTokens}; + +fn themes() -> Vec<(String, Theme)> { + vec![ + ("Material 3 light".to_owned(), material3::light()), + ("Material 3 dark".to_owned(), material3::dark()), + ] +} + +/// `[wcag]` SC 1.4.6 for text, and the 4.5:1 the projection holds every +/// non-text pair it changes to; 1.5:1 is how far it moves the selection off +/// `surface_content`. +const TEXT: f32 = 7.0; +const NON_TEXT: f32 = 4.5; +const SELECTION: f32 = 1.5; + +fn high_contrast(theme: &Theme, window_active: bool) -> ColorTokens { + theme + .for_paint_state(PaintState { + window_active, + high_contrast: true, + }) + .colors +} + +/// Every pair `ColorTokens::for_high_contrast` documents, below its floor in +/// `c`, named with its ratio. +fn shortfalls(c: &ColorTokens) -> Vec { + let surfaces = [ + ("surface_main", c.surface_main), + ("surface_content", c.surface_content), + ("surface_raised", c.surface_raised), + ]; + let mut pairs: Vec<(String, Color, Color, f32)> = Vec::new(); + for (fill, bg) in [ + ("accent", c.accent), + ("accent_hover", c.accent_hover), + ("accent_pressed", c.accent_pressed), + ] { + pairs.push(( + format!("text_on_accent on {fill}"), + c.text_on_accent, + bg, + TEXT, + )); + // White text in a light palette, black in a dark one: the accent + // clears the surfaces as well. + let text_light = c.text_on_accent.relative_luminance() > 0.5; + let surfaces_light = c.surface_main.relative_luminance() > 0.5; + if text_light == surfaces_light { + for (s, s_bg) in surfaces { + pairs.push((format!("{fill} on {s}"), bg, s_bg, NON_TEXT)); + } + } + } + for (s, bg) in surfaces { + pairs.push((format!("focus_ring on {s}"), c.focus_ring, bg, NON_TEXT)); + pairs.push(( + format!("border_focused on {s}"), + c.border_focused, + bg, + NON_TEXT, + )); + pairs.push((format!("text_secondary on {s}"), c.text_secondary, bg, TEXT)); + } + // The focused selection, and the one a row keeps while its view is + // unfocused or its window inactive. + for (fill, bg) in [ + ("surface_selected", c.surface_selected), + ("surface_selected_inactive", c.surface_selected_inactive), + ] { + pairs.push(( + format!("text_secondary on {fill}"), + c.text_secondary, + bg, + TEXT, + )); + pairs.push((format!("text_primary on {fill}"), c.text_primary, bg, TEXT)); + pairs.push(( + format!("{fill} on surface_content"), + bg, + c.surface_content, + SELECTION, + )); + } + pairs + .into_iter() + .filter_map(|(name, fg, bg, floor)| { + let r = composite_contrast(fg, bg); + (r < floor).then(|| format!("{name} = {r:.2}, floor {floor}")) + }) + .collect() +} + +#[test] +fn every_high_contrast_pair_meets_its_floor() { + for (name, theme) in themes() { + for window_active in [true, false] { + let short = shortfalls(&high_contrast(&theme, window_active)); + assert!( + short.is_empty(), + "{name}, window active {window_active}: {short:#?}" + ); + } + } +} + +#[test] +fn high_contrast_keeps_the_accent_hue() { + // The projection used to swap IntUI's teal into every dark palette. + for (name, theme) in themes() { + for window_active in [true, false] { + let before = theme + .for_paint_state(PaintState { + window_active, + high_contrast: false, + }) + .colors + .accent; + let after = high_contrast(&theme, window_active).accent; + let (h0, s0, _) = before.to_hsv(); + let (h1, s1, _) = after.to_hsv(); + if s0 > 0.05 && s1 > 0.05 { + let d = (h0 - h1).abs(); + let d = d.min(360.0 - d); + assert!( + d < 1.0, + "{name}, window active {window_active}: hue {h0} became {h1}" + ); + } + } + } +} diff --git a/crates/teksilo-tokens/src/contrast.rs b/crates/teksilo-tokens/src/contrast.rs new file mode 100644 index 000000000..520b00107 --- /dev/null +++ b/crates/teksilo-tokens/src/contrast.rs @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! WCAG contrast helpers for colours a theme does not choose itself. +//! +//! A palette read from the desktop, or derived from a user's accent, can put +//! a foreground on a backdrop it was never checked against. These helpers +//! measure such a pair the way it is painted ([`composite_contrast`]) and, +//! when it falls short, move the foreground just far enough to read +//! ([`lift_until_readable`]). +//! +//! The usual floors are WCAG 2.x's: 4.5:1 for normal text (SC 1.4.3) and +//! 3:1 for the boundary of a control or a graphical object (SC 1.4.11). + +use crate::color::Color; + +/// The number of equal steps [`lift_until_readable`] takes between the +/// foreground and black or white. A step of 1 % moves an 8-bit channel by at +/// most 2.55 levels, so the lift overshoots its floor by very little. +const LIFT_STEPS: u32 = 100; + +/// The WCAG 2.x contrast ratio of `fg` painted over `bg`, in `1.0..=21.0`. +/// +/// Unlike [`Color::contrast_ratio`], which assumes both colours are already +/// opaque, this composites a translucent `fg` over `bg` first (straight +/// alpha, in sRGB, as the renderer blends), so a 40 % ink is measured as the +/// lighter colour it actually paints. `bg` is the backdrop and is treated as +/// opaque: its alpha is ignored. +pub fn composite_contrast(fg: Color, bg: Color) -> f32 { + composite_over(fg, bg).contrast_ratio(bg.with_alpha(1.0)) +} + +/// `fg`, moved toward black or toward white until it reaches `floor` against +/// every colour in `on`. +/// +/// Contrast is measured with [`composite_contrast`], so `fg`'s alpha is +/// honoured and kept: only its red, green and blue move. The direction is +/// whichever of black or white gives the higher minimum contrast over `on`, +/// so a colour on a light backdrop darkens and one on a dark backdrop +/// lightens. The colour moves in small steps and stops at the first that +/// passes, so it changes no more than it has to. +/// +/// Returns `fg` unchanged when it already passes, or when `on` is empty. When +/// no step passes (a floor above what black or white can reach on these +/// backdrops, a very translucent `fg`, or backdrops on both sides of the +/// midpoint), returns the step with the highest minimum contrast, which for a +/// single backdrop is the saturated end: black or white. +pub fn lift_until_readable(fg: Color, on: &[Color], floor: f32) -> Color { + let worst = |c: Color| -> f32 { + on.iter() + .map(|&bg| composite_contrast(c, bg)) + .fold(f32::INFINITY, f32::min) + }; + if on.is_empty() || worst(fg) >= floor { + return fg; + } + let toward = |target: Color, t: f32| -> Color { + let inv = 1.0 - t; + Color::new( + fg.r() * inv + target.r() * t, + fg.g() * inv + target.g() * t, + fg.b() * inv + target.b() * t, + fg.a(), + ) + }; + let target = if worst(toward(Color::BLACK, 1.0)) >= worst(toward(Color::WHITE, 1.0)) { + Color::BLACK + } else { + Color::WHITE + }; + let mut best = (worst(fg), fg); + for step in 1..=LIFT_STEPS { + let candidate = toward(target, step as f32 / LIFT_STEPS as f32); + let score = worst(candidate); + if score >= floor { + return candidate; + } + if score > best.0 { + best = (score, candidate); + } + } + best.1 +} + +/// `fg` composited over an opaque `bg`. +pub(crate) fn composite_over(fg: Color, bg: Color) -> Color { + // A NaN alpha would poison every channel; read it as opaque, the safest + // meaning of an undefined alpha for a colour meant to be read. + let a = if fg.a().is_nan() { + 1.0 + } else { + fg.a().clamp(0.0, 1.0) + }; + let inv = 1.0 - a; + Color::new( + fg.r() * a + bg.r() * inv, + fg.g() * a + bg.g() * inv, + fg.b() * a + bg.b() * inv, + 1.0, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn hex(s: &str) -> Color { + Color::from_hex(s) + } + + #[test] + fn an_opaque_foreground_measures_as_contrast_ratio() { + for (fg, bg) in [ + ("#000000", "#FFFFFF"), + ("#3DAEE9", "#EFF0F1"), + ("#DA4453", "#232629"), + ] { + let (fg, bg) = (hex(fg), hex(bg)); + assert!((composite_contrast(fg, bg) - fg.contrast_ratio(bg)).abs() < 1e-5); + } + assert!((composite_contrast(Color::BLACK, Color::WHITE) - 21.0).abs() < 1e-3); + } + + #[test] + fn a_translucent_foreground_is_composited_first() { + let fg = Color::BLACK.with_alpha(0.5); + let bg = Color::WHITE; + let painted = Color::new(0.5, 0.5, 0.5, 1.0); + assert!((composite_contrast(fg, bg) - painted.contrast_ratio(bg)).abs() < 1e-5); + // `contrast_ratio` alone ignores the alpha and overstates it. + assert!(composite_contrast(fg, bg) < fg.contrast_ratio(bg)); + // A fully transparent ink is invisible. + assert!((composite_contrast(Color::BLACK.with_alpha(0.0), bg) - 1.0).abs() < 1e-5); + } + + #[test] + fn the_backdrop_alpha_is_ignored() { + let fg = hex("#336699"); + let bg = hex("#EEEEEE"); + assert_eq!( + composite_contrast(fg, bg), + composite_contrast(fg, bg.with_alpha(0.2)) + ); + } + + #[test] + fn a_readable_colour_is_returned_unchanged() { + let fg = hex("#232629"); + assert_eq!(lift_until_readable(fg, &[hex("#FFFFFF")], 4.5), fg); + assert_eq!(lift_until_readable(fg, &[], 21.0), fg); + } + + #[test] + fn a_colour_on_a_light_backdrop_darkens_just_enough() { + // Breeze's light negative text, #DA4453, is about 4.0:1 on white. + let fg = hex("#DA4453"); + let bg = Color::WHITE; + assert!(composite_contrast(fg, bg) < 4.5); + let lifted = lift_until_readable(fg, &[bg], 4.5); + let ratio = composite_contrast(lifted, bg); + assert!(ratio >= 4.5, "{ratio}"); + assert!(ratio < 4.8, "lifted further than it had to: {ratio}"); + assert!(lifted.relative_luminance() < fg.relative_luminance()); + // The hue survives: red stays the dominant channel. + assert!(lifted.r() > lifted.g() && lifted.r() > lifted.b()); + } + + #[test] + fn a_colour_on_a_dark_backdrop_lightens() { + let fg = hex("#2980B9"); + let bg = hex("#1B1E20"); + assert!(composite_contrast(fg, bg) < 4.5); + let lifted = lift_until_readable(fg, &[bg], 4.5); + assert!(composite_contrast(lifted, bg) >= 4.5); + assert!(lifted.relative_luminance() > fg.relative_luminance()); + } + + #[test] + fn every_backdrop_must_pass() { + let fg = hex("#7F8C8D"); + let on = [hex("#FFFFFF"), hex("#EFF0F1"), hex("#DEE0E2")]; + let lifted = lift_until_readable(fg, &on, 4.5); + for bg in on { + assert!(composite_contrast(lifted, bg) >= 4.5); + } + } + + #[test] + fn the_alpha_is_kept_and_counted() { + let fg = hex("#000000").with_alpha(0.35); + let bg = Color::WHITE; + let lifted = lift_until_readable(fg, &[bg], 3.0); + assert_eq!(lifted.a(), fg.a()); + // Already black: no step can darken it further, so it saturates. + assert_eq!(lifted, fg); + let grey = hex("#888888").with_alpha(0.8); + let lifted = lift_until_readable(grey, &[bg], 4.5); + assert_eq!(lifted.a(), 0.8); + assert!(composite_contrast(lifted, bg) >= 4.5); + } + + #[test] + fn an_unreachable_floor_saturates() { + let fg = hex("#808080"); + let bg = Color::WHITE; + // Nothing reaches 25:1; the lift ends at black. + assert_eq!(lift_until_readable(fg, &[bg], 25.0), Color::BLACK); + let dark = hex("#000000"); + assert_eq!(lift_until_readable(fg, &[dark], 25.0), Color::WHITE); + } + + #[test] + fn backdrops_on_both_sides_keep_the_best_step() { + // Mid grey between white and black can never reach 4.5:1 on both; + // the result is the best compromise and never worse than the input. + let fg = hex("#606060"); + let on = [Color::WHITE, Color::BLACK]; + let worst = |c: Color| { + on.iter() + .map(|&bg| composite_contrast(c, bg)) + .fold(f32::INFINITY, f32::min) + }; + let lifted = lift_until_readable(fg, &on, 4.5); + assert!(worst(lifted) >= worst(fg)); + } + + #[test] + fn a_nan_alpha_reads_as_opaque() { + let fg = Color::new(0.0, 0.0, 0.0, f32::NAN); + assert!((composite_contrast(fg, Color::WHITE) - 21.0).abs() < 1e-3); + } +} diff --git a/crates/teksilo-tokens/src/lib.rs b/crates/teksilo-tokens/src/lib.rs index 8bf80703d..c23a6a251 100644 --- a/crates/teksilo-tokens/src/lib.rs +++ b/crates/teksilo-tokens/src/lib.rs @@ -3,6 +3,7 @@ pub mod alignment; pub mod color; +pub mod contrast; pub mod input; pub mod layout; pub mod motion; diff --git a/crates/teksilo-tokens/src/motion.rs b/crates/teksilo-tokens/src/motion.rs index 4060ac93f..508dde0d1 100644 --- a/crates/teksilo-tokens/src/motion.rs +++ b/crates/teksilo-tokens/src/motion.rs @@ -188,6 +188,58 @@ impl Default for MotionTokens { } } +impl MotionTokens { + /// These tokens with every animation duration multiplied by `factor`: + /// the shape of a desktop's global animation-speed setting (KDE's + /// `AnimationDurationFactor`, where 2 is twice as slow and 0.5 twice as + /// fast). + /// + /// Scales `duration_instant`, `duration_fast`, `duration_normal`, + /// `duration_slow`, `duration_collapse` and + /// `duration_indeterminate_sweep`. The three tooltip delays are **not** + /// scaled: they are how long the pointer must rest before a tip shows, a + /// dwell rather than an animation, and slowing animations must not make + /// tooltips harder to summon. The easing is kept. + /// + /// A scaled duration is rounded to the millisecond, and a duration that + /// was not zero stays at least 1 ms, so a fast factor never turns a + /// tween into a snap by rounding. A zero duration stays zero. + /// + /// `factor` 0 (or a negative one) makes every scaled duration zero, + /// which is how the framework already reads "no animation": a + /// zero-duration tween snaps to its target, and the looping consumers + /// of `duration_indeterminate_sweep` clamp a zero period rather than + /// divide by it. A factor that is not finite (NaN or infinity) leaves + /// the tokens unchanged. Durations are capped at `u32::MAX` ms. + pub fn scaled(&self, factor: f32) -> MotionTokens { + if !factor.is_finite() { + return self.clone(); + } + let factor = f64::from(factor.max(0.0)); + let scale = |d: Duration| -> Duration { + if factor == 0.0 || d.is_zero() { + return Duration::ZERO; + } + let ms = (d.as_secs_f64() * 1000.0 * factor) + .round() + .clamp(1.0, f64::from(u32::MAX)); + Duration::from_millis(ms as u64) + }; + MotionTokens { + duration_instant: scale(self.duration_instant), + duration_fast: scale(self.duration_fast), + duration_normal: scale(self.duration_normal), + duration_slow: scale(self.duration_slow), + duration_collapse: scale(self.duration_collapse), + duration_indeterminate_sweep: scale(self.duration_indeterminate_sweep), + tooltip_delay: self.tooltip_delay, + tooltip_delay_heavy: self.tooltip_delay_heavy, + tooltip_reshow_delay: self.tooltip_reshow_delay, + easing_standard: self.easing_standard, + } + } +} + mod duration_millis { use std::time::Duration; @@ -228,6 +280,78 @@ mod tests { assert_eq!(m.tooltip_reshow_delay, Duration::from_millis(100)); } + #[test] + fn scaled_by_one_is_identity() { + let m = MotionTokens::default(); + assert_eq!(m.scaled(1.0), m); + } + + #[test] + fn scaled_multiplies_every_animation_duration() { + let m = MotionTokens::default(); + let s = m.scaled(2.0); + assert_eq!(s.duration_fast, Duration::from_millis(240)); + assert_eq!(s.duration_normal, Duration::from_millis(400)); + assert_eq!(s.duration_slow, Duration::from_millis(600)); + assert_eq!(s.duration_collapse, Duration::from_millis(400)); + assert_eq!(s.duration_indeterminate_sweep, Duration::from_millis(1800)); + let h = m.scaled(0.5); + assert_eq!(h.duration_fast, Duration::from_millis(60)); + assert_eq!(h.duration_indeterminate_sweep, Duration::from_millis(450)); + assert_eq!(h.easing_standard, m.easing_standard); + } + + #[test] + fn scaled_leaves_the_tooltip_delays_alone() { + let m = MotionTokens::default(); + for f in [0.0, 0.25, 2.0, 10.0] { + let s = m.scaled(f); + assert_eq!(s.tooltip_delay, m.tooltip_delay); + assert_eq!(s.tooltip_delay_heavy, m.tooltip_delay_heavy); + assert_eq!(s.tooltip_reshow_delay, m.tooltip_reshow_delay); + } + } + + #[test] + fn scaled_rounds_and_keeps_a_non_zero_duration_non_zero() { + let m = MotionTokens { + duration_fast: Duration::from_millis(3), + duration_normal: Duration::from_micros(2500), + ..MotionTokens::default() + }; + let s = m.scaled(0.01); + assert_eq!(s.duration_fast, Duration::from_millis(1)); + assert_eq!(s.duration_slow, Duration::from_millis(3)); + // Rounded to the millisecond, half away from zero: 2.5 ms gives 3. + assert_eq!(m.scaled(1.0).duration_normal, Duration::from_millis(3)); + // A zero duration stays zero at any factor. + assert_eq!(m.scaled(4.0).duration_instant, Duration::ZERO); + } + + #[test] + fn scaled_by_zero_disables_animation() { + let s = MotionTokens::default().scaled(0.0); + for d in [ + s.duration_instant, + s.duration_fast, + s.duration_normal, + s.duration_slow, + s.duration_collapse, + s.duration_indeterminate_sweep, + ] { + assert_eq!(d, Duration::ZERO); + } + assert_eq!(MotionTokens::default().scaled(-3.0), s); + } + + #[test] + fn scaled_by_a_non_finite_factor_is_identity() { + let m = MotionTokens::default(); + assert_eq!(m.scaled(f32::NAN), m); + assert_eq!(m.scaled(f32::INFINITY), m); + assert_eq!(m.scaled(f32::NEG_INFINITY), m); + } + #[test] fn easing_boundaries() { for easing in [ diff --git a/crates/teksilo-tokens/src/os_theme_colors.rs b/crates/teksilo-tokens/src/os_theme_colors.rs index d7ec61d1a..811d40840 100644 --- a/crates/teksilo-tokens/src/os_theme_colors.rs +++ b/crates/teksilo-tokens/src/os_theme_colors.rs @@ -29,14 +29,26 @@ impl ColorSchemePreference { /// Colors read from the OS desktop environment. /// -/// All fields except `color_scheme` are `Option` — the factory method +/// All fields except `color_scheme` are `Option`: the factory method /// `ColorTokens::from_os_colors()` fills missing values from the matching -/// built-in light or dark theme. -#[derive(Debug, Clone, Default)] +/// built-in light or dark theme, and an absent field leaves its tokens +/// exactly as that theme has them. +/// +/// The struct is `#[non_exhaustive]` so a desktop reader can learn to report +/// a new colour without breaking anyone: outside this crate, start from +/// [`OsThemeColors::new`] (or `Default`) and assign the fields you have. +/// +/// Each field names where it comes from: the KDE colour-scheme key +/// (`[Colors:] ` in `kdeglobals`) and, where one exists, the GTK +/// CSS `@define-color` name a GTK theme publishes for it. +#[derive(Debug, Clone, Default, PartialEq)] +#[non_exhaustive] pub struct OsThemeColors { /// Light or dark preference. pub color_scheme: ColorSchemePreference, - /// Desktop accent / primary color (e.g., GNOME accent, KDE DecorationFocus). + /// Desktop accent / primary color. KDE `[Colors:Selection] + /// BackgroundNormal` (the colour the portal reports as `accent-color`); + /// GNOME's accent colour. pub accent: Option, /// Window background color. pub window_bg: Option, @@ -54,6 +66,51 @@ pub struct OsThemeColors { pub tooltip_bg: Option, /// Tooltip foreground. pub tooltip_fg: Option, + /// Background of a content view: a list, a tree, a text field, an + /// editor. KDE `[Colors:View] BackgroundNormal`; GTK `theme_base_color` + /// (libadwaita `view_bg_color`). + pub view_bg: Option, + /// Text of a content view. KDE `[Colors:View] ForegroundNormal`; GTK + /// `theme_text_color` (libadwaita `view_fg_color`). + pub view_fg: Option, + /// The alternate background of a striped list or table row. KDE + /// `[Colors:View] BackgroundAlternate`; GTK publishes no equivalent. + pub alternate_bg: Option, + /// Secondary text: less important labels, comments, placeholder-like + /// hints. Not disabled text. KDE `[Colors:View] ForegroundInactive`; GTK + /// publishes no equivalent (its `insensitive_fg_color` is disabled text). + pub inactive_fg: Option, + /// An unvisited link. KDE `[Colors:View] ForegroundLink`; GTK + /// `link_color`. + pub link: Option, + /// A visited link. KDE `[Colors:View] ForegroundVisited`; GTK + /// `link_visited_color`. + pub link_visited: Option, + /// Error text and indicators. KDE `[Colors:View] ForegroundNegative`; + /// GTK `error_color`. + pub error: Option, + /// Warning text and indicators. KDE `[Colors:View] ForegroundNeutral`; + /// GTK `warning_color`. + pub warning: Option, + /// Success text and indicators. KDE `[Colors:View] ForegroundPositive`; + /// GTK `success_color`. + pub success: Option, + /// The keyboard focus indicator. KDE `[Colors:View] DecorationFocus`; + /// GTK publishes no standard name (the Breeze GTK theme calls it + /// `theme_button_decoration_focus_breeze`). + pub focus: Option, +} + +impl OsThemeColors { + /// A report carrying only the light or dark preference, every colour + /// absent. The way to build one outside this crate, since the struct is + /// `#[non_exhaustive]`; assign the colours the desktop reports afterwards. + pub fn new(color_scheme: ColorSchemePreference) -> Self { + Self { + color_scheme, + ..Self::default() + } + } } #[cfg(test)] @@ -66,4 +123,21 @@ mod tests { assert!(!ColorSchemePreference::Light.is_dark()); assert!(!ColorSchemePreference::NoPreference.is_dark()); } + + #[test] + fn new_carries_the_scheme_and_no_colours() { + let os = OsThemeColors::new(ColorSchemePreference::Dark); + assert_eq!(os.color_scheme, ColorSchemePreference::Dark); + assert_eq!( + os, + OsThemeColors { + color_scheme: ColorSchemePreference::Dark, + ..OsThemeColors::default() + } + ); + assert_eq!( + OsThemeColors::new(ColorSchemePreference::NoPreference), + OsThemeColors::default() + ); + } } diff --git a/crates/teksilo-tokens/src/shape.rs b/crates/teksilo-tokens/src/shape.rs index 654006268..a8f17fc4b 100644 --- a/crates/teksilo-tokens/src/shape.rs +++ b/crates/teksilo-tokens/src/shape.rs @@ -104,9 +104,24 @@ pub struct ShapeTokens { pub radius_pill: f32, /// 1 dp — universal border width. Int UI has no thicker variant. pub border_width: f32, - /// 2 dp — focus ring outline width, drawn outside the control. + /// 2 dp: focus ring outline width. pub focus_ring_width: f32, - /// 2 dp — gap between control edge and focus ring. + /// 2 dp: signed distance from the control edge to the inner edge of + /// the focus ring. + /// + /// Positive leaves a gap between the control and the ring. Zero puts + /// the ring flush against the control. A negative offset draws the + /// ring partly (or, below `-focus_ring_width`, wholly) inside the + /// control: `-1` with a 2 dp width runs from 1 dp inside the edge to + /// 1 dp outside it. Every focus-ring painter accepts a negative value; + /// the space a control reserves outside itself for the ring is + /// [`focus_ring_envelope`](Self::focus_ring_envelope), which never goes + /// below zero. + /// + /// Tabs are the exception: they sit flush against each other, so a tab + /// draws its ring inside itself, insets it by the offset, and clamps it + /// to the tab edge. Every offset at or below zero gives a tab the same + /// ring, flush with its edge. pub focus_ring_offset: f32, /// Tooltips — outer soft halo. pub shadow_xs: Shadow, @@ -128,6 +143,24 @@ pub struct ShapeTokens { } impl ShapeTokens { + /// How far the focus ring reaches outside the control edge: + /// `focus_ring_offset + focus_ring_width`, floored at zero. + /// + /// This is the space a control reserves around its visual for the + /// ring. A ring drawn wholly inside the control (an offset at or below + /// `-focus_ring_width`) needs none, and a negative envelope would + /// shrink the control's frame instead. + pub fn focus_ring_envelope(&self) -> f32 { + (self.focus_ring_offset + self.focus_ring_width).max(0.0) + } + + /// Signed distance from the control edge to the centre line of the + /// focus-ring stroke: `focus_ring_offset + focus_ring_width / 2`. + /// Negative when the ring's centre line lies inside the control. + pub fn focus_ring_stroke_distance(&self) -> f32 { + self.focus_ring_offset + self.focus_ring_width * 0.5 + } + /// Light-theme shadows. /// Outer alphas: xs 85%, sm 22%, md 28%, lg 36%. /// Inner alphas (density-1.0 max): xs 85%, sm 50%, md 55%, lg 60%. @@ -248,4 +281,21 @@ mod tests { assert!(s.focus_ring_offset > 0.0); assert!(s.focus_ring_width > 0.0); } + + #[test] + fn focus_ring_envelope_follows_a_negative_offset_and_stops_at_zero() { + let mut s = ShapeTokens::default(); + assert_eq!(s.focus_ring_envelope(), 4.0); + assert_eq!(s.focus_ring_stroke_distance(), 3.0); + + // Straddling the edge: 1 dp inside, 1 dp outside. + s.focus_ring_offset = -1.0; + assert_eq!(s.focus_ring_envelope(), 1.0); + assert_eq!(s.focus_ring_stroke_distance(), 0.0); + + // Wholly inside: nothing to reserve outside the control. + s.focus_ring_offset = -5.0; + assert_eq!(s.focus_ring_envelope(), 0.0); + assert_eq!(s.focus_ring_stroke_distance(), -4.0); + } } diff --git a/crates/teksilo-tokens/src/theme.rs b/crates/teksilo-tokens/src/theme.rs index 02002a0f4..402c90598 100644 --- a/crates/teksilo-tokens/src/theme.rs +++ b/crates/teksilo-tokens/src/theme.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::color::Color; +use crate::contrast::{composite_contrast, composite_over, lift_until_readable}; use crate::os_theme_colors::OsThemeColors; /// Semantic color tokens for a theme, structured around the JetBrains Int UI design system. @@ -137,7 +138,7 @@ pub struct ColorTokens { // ── Cross-design-language roles ─────────────────────────────────────────── // Universal slots every design language (Material 3, Fluent, macOS, - // GTK4/Adwaita) needs but IntUI's flat role model didn't carry. Design- + // KDE, GTK4/Adwaita) needs but IntUI's flat role model didn't carry. Design- // language-specific colors (M3 secondary/tertiary triads, the full tonal // ladder, inverse colors) stay in per-theme extensions, not here. /// Text/icon painted on top of an error surface (white on the error fill). @@ -167,6 +168,50 @@ pub struct ColorTokens { pub chart_palette: Vec, } +/// WCAG SC 1.4.6's floor for text, which [`ColorTokens::for_high_contrast`] +/// holds every text pair it changes to. +const HC_TEXT: f32 = 7.0; + +/// The floor [`ColorTokens::for_high_contrast`] holds the focus indicator and, +/// where its text allows, the accent to on the surfaces: SC 1.4.6's 4.5:1, +/// beyond SC 1.4.11's 3:1. +const HC_NON_TEXT: f32 = 4.5; + +/// How far [`ColorTokens::for_high_contrast`] moves the selection fill off +/// `surface_content`: farther than a default wash (IntUI's stands 1.19:1 off +/// white), and no farther than leaves secondary text room to differ from the +/// primary. On a fill 1.5:1 off white a text reaches 7:1 at a luminance of +/// 0.05 and below; on one 3:1 off white only black does. +const HC_SELECTION: f32 = 1.5; + +/// The steps [`ColorTokens::for_high_contrast`] takes where it walks a colour +/// itself, as [`lift_until_readable`] does: 1 % of the way per step. +const HC_STEPS: u32 = 100; + +/// `fg` lifted until it reaches `floor` on every colour in `on`, as +/// [`lift_until_readable`] lifts it, gaining opacity in 1 % steps when no +/// lift at its own alpha can get there (a 58 % black secondary text is +/// already as dark as that alpha allows). Returns the most opaque attempt +/// when even an opaque lift falls short. +fn strengthen(fg: Color, on: &[Color], floor: f32) -> Color { + let reaches = |c: Color| on.iter().all(|&bg| composite_contrast(c, bg) >= floor); + let lifted = lift_until_readable(fg, on, floor); + // A NaN alpha composites as opaque (see `composite_contrast`). + let translucent = fg.a() < 1.0; + if reaches(lifted) || !translucent { + return lifted; + } + let mut attempt = lifted; + for step in 1..=HC_STEPS { + let alpha = fg.a() + (1.0 - fg.a()) * step as f32 / HC_STEPS as f32; + attempt = lift_until_readable(fg.with_alpha(alpha), on, floor); + if reaches(attempt) { + break; + } + } + attempt +} + impl ColorTokens { /// Fraction by which the accent family is desaturated toward graphite for /// an inactive window. `0.0` keeps the accent, `1.0` is fully gray. See @@ -237,49 +282,137 @@ impl ColorTokens { /// Project this palette into a **high-contrast** variant for the OS /// "increase contrast" preference (WCAG SC 1.4.6 Enhanced tier / EN 301 549 - /// §11.7). Targets >= 7:1 for text and >= 4.5:1 for non-text UI components — - /// beyond the AA floor the default theme already meets. Applied at paint - /// time when `prefers_high_contrast` is set (parallel to - /// [`for_inactive_window`](Self::for_inactive_window)). + /// §11.7), applied at paint time when `prefers_high_contrast` is set + /// (parallel to [`for_inactive_window`](Self::for_inactive_window)). + /// + /// The projection keeps the palette's own colours and moves each one only + /// as far as a floor requires, so a preset keeps its accent's hue and its + /// ring's character (an accent ring stays the accent, a neutral ring stays + /// neutral). Every move is a [`lift_until_readable`] step toward black or + /// white in sRGB, which keeps the hue, and contrast is measured in WCAG + /// relative luminance with [`composite_contrast`], so a translucent token + /// is measured as the colour it paints. "The surfaces" below are + /// `surface_main`, `surface_content` and `surface_raised`. The pairs it + /// guarantees, and the only tokens it changes: /// - /// The light and dark bases need different overrides (detected from surface - /// luminance): in light mode `accent` and the focus indicator must diverge, - /// because no single hue clears >= 7:1 black-text AND >= 4.5:1 ring-on-white - /// at once. All values are verified against - /// [`Color::contrast_ratio`](crate::color::Color::contrast_ratio). + /// - **Text on the accent, 7:1.** `text_on_accent` is the palette's own + /// when it reaches 7:1 on `accent`, otherwise whichever of black and + /// white contrasts more with it. `accent`, `accent_hover` and + /// `accent_pressed` are each lifted until that text reaches 7:1 on them. + /// Each is lifted further, until it reaches 4.5:1 on the surfaces, when + /// the text still reaches 7:1 afterwards. + /// - **The focus indicator, 4.5:1.** `focus_ring` and `border_focused` are + /// each lifted until they reach 4.5:1 on the surfaces. A translucent one + /// gains opacity when no lift at its own alpha can get there. + /// - **The selection, 1.5:1, under text at 7:1.** `surface_selected` and + /// `surface_selected_inactive` (the fill a row keeps while its view is + /// unfocused or its window inactive) are each composited over + /// `surface_content` into an opaque fill and moved away from + /// `surface_content` until they stand 1.5:1 off it, stopping short of + /// that where `text_primary` would fall below 7:1 on them. A palette + /// whose selection already fails that text moves it toward + /// `surface_content` instead, until the text reaches 7:1. + /// - **Secondary text, 7:1.** `text_secondary` is lifted, gaining opacity + /// as the focus indicator does, until it reaches 7:1 on the surfaces and + /// on both projected selections, so a selected row's subtitle stays + /// readable whether its view has focus or not. + /// + /// The accent and the focus indicator stay separate tokens because one + /// colour cannot always serve both. Whether it can depends on the tone of + /// the accent's text against the surfaces: where they are alike (white + /// text in a light theme, black text in a dark one) the accent clears both + /// floors; where they are opposite, the usual light-theme case of black + /// text on a light accent, the accent must stay light for its text while + /// a ring on near-white surfaces must be dark (black text at 7:1 needs a + /// luminance of at least 0.30, a ring at 4.5:1 on white one of at most + /// 0.18), so the accent is held to its text and the ring alone carries + /// 4.5:1. + /// + /// Applied after [`for_inactive_window`](Self::for_inactive_window) by + /// the generic paint chain, so an inactive window's desaturated accent is + /// lifted to the same floors. + /// + /// [`lift_until_readable`]: crate::contrast::lift_until_readable + /// [`composite_contrast`]: crate::contrast::composite_contrast pub fn for_high_contrast(&self) -> ColorTokens { - if self.surface_main.relative_luminance() < 0.5 { - // Dark base: a brighter accent clears both text-on-accent and - // ring-on-surface against the dark surfaces. - ColorTokens { - accent: Color::from_hex("#4FCCE0"), - accent_hover: Color::from_hex("#4FCCE0"), - accent_pressed: Color::from_hex("#3FB8CC"), - border_focused: Color::from_hex("#4FCCE0"), - focus_ring: Color::from_hex("#4FCCE0"), - text_on_accent: Color::BLACK, - text_secondary: Color::from_hex("#C7CBD1"), - // Stronger selection fill than the default pale wash so - // low-vision users can clearly see the selection (still keeps - // the light `text_primary` readable on top). - surface_selected: Color::from_hex("#2E6575"), - ..self.clone() - } + let surfaces = [self.surface_main, self.surface_content, self.surface_raised]; + // The accent family is a fill the text is read on: measure it as the + // opaque colour it paints over the content surface (unchanged when it + // is already opaque). + let opaque = |c: Color| composite_over(c, self.surface_content); + let accent = opaque(self.accent); + let text_on_accent = if composite_contrast(self.text_on_accent, accent) >= HC_TEXT { + self.text_on_accent } else { - // Light base: black text on the (unchanged) accent gives 8.5:1; the - // focus indicator moves to a much darker teal for >= 4.5:1 on white. - ColorTokens { - border_focused: Color::from_hex("#0A7B8B"), - focus_ring: Color::from_hex("#0A7B8B"), - text_on_accent: Color::BLACK, - text_secondary: Color::from_hex("#3A3D45"), - // Stronger selection fill (still light enough for black text). - surface_selected: Color::from_hex("#A8DDE5"), - ..self.clone() + accent.best_contrast_text() + }; + let fill = |c: Color| { + let readable = lift_until_readable(opaque(c), &[text_on_accent], HC_TEXT); + let standing = lift_until_readable(readable, &surfaces, HC_NON_TEXT); + if composite_contrast(text_on_accent, standing) >= HC_TEXT { + standing + } else { + readable } + }; + let surface_selected = self.high_contrast_selection(self.surface_selected); + let surface_selected_inactive = + self.high_contrast_selection(self.surface_selected_inactive); + let text_surfaces = [ + self.surface_main, + self.surface_content, + self.surface_raised, + surface_selected, + surface_selected_inactive, + ]; + ColorTokens { + accent: fill(self.accent), + accent_hover: fill(self.accent_hover), + accent_pressed: fill(self.accent_pressed), + border_focused: strengthen(self.border_focused, &surfaces, HC_NON_TEXT), + focus_ring: strengthen(self.focus_ring, &surfaces, HC_NON_TEXT), + text_on_accent, + text_secondary: strengthen(self.text_secondary, &text_surfaces, HC_TEXT), + surface_selected, + surface_selected_inactive, + ..self.clone() } } + /// A selection fill of [`for_high_contrast`](Self::for_high_contrast) + /// (`surface_selected` or `surface_selected_inactive`): + /// `selected` made opaque over `surface_content` and moved toward + /// black or white, whichever is farther from `surface_content`, until it + /// stands [`HC_SELECTION`]:1 off it, never past the last step on which + /// `text_primary` keeps [`HC_TEXT`]:1. A fill on which that text already + /// falls short moves toward `surface_content` instead, until the text + /// reaches [`HC_TEXT`]:1 on it. + fn high_contrast_selection(&self, selected: Color) -> Color { + let content = self.surface_content.with_alpha(1.0); + let base = composite_over(selected, content); + let reads = |c: Color| composite_contrast(self.text_primary, c) >= HC_TEXT; + let step = |to: Color, i: u32| base.mix(to, i as f32 / HC_STEPS as f32); + if !reads(base) { + return (1..=HC_STEPS) + .map(|i| step(content, i)) + .find(|&c| reads(c)) + .unwrap_or(content); + } + let toward = content.best_contrast_text(); + let mut chosen = base; + for i in 1..=HC_STEPS { + if chosen.contrast_ratio(content) >= HC_SELECTION { + break; + } + let candidate = step(toward, i); + if !reads(candidate) { + break; + } + chosen = candidate; + } + chosen + } + pub fn light_default() -> Self { Self { // Surfaces @@ -559,6 +692,21 @@ impl ColorTokens { /// Starts from the matching built-in theme (light or dark) based on /// `color_scheme`, then overlays any color fields actually reported by /// the OS. Missing fields keep their built-in default values. + /// + /// The content-view and semantic fields map as follows. A foreground is + /// lifted to WCAG's floor on `surface_main` and `surface_content` when + /// the OS colour falls short, and the editor's on `editor_bg`: + /// + /// | Field | Tokens | + /// |---|---| + /// | `view_bg` | `editor_bg` (and the editor's current-line and code-block tints, `editor_code_block_fg` lifted to 4.5:1 on the latter); `surface_content` too when the window's text can reach 4.5:1 on it and on `surface_main`, `text_primary` and `text_secondary` then lifted to that floor on both, while a view in the window's opposite tone stays the editor's alone | + /// | `view_fg` | `editor_fg` (4.5:1), `editor_caret` (3:1) | + /// | `alternate_bg` | `surface_alt_row` | + /// | `inactive_fg` | `text_secondary` (4.5:1) | + /// | `link` | `text_link`, `text_link_hover` (4.5:1) | + /// | `link_visited` | `text_link_visited` (4.5:1) | + /// | `error`, `warning`, `success` | `text_error` / `_warning` / `_success` (4.5:1), and `status_*_fg` (3:1, also on its `status_*_bg`) | + /// | `focus` | `focus_ring`, `border_focused` (3:1) | pub fn from_os_colors(os: &OsThemeColors) -> Self { let is_dark = os.color_scheme.is_dark(); let mut tokens = if is_dark { @@ -724,8 +872,145 @@ impl ColorTokens { tokens.scrim = Color::new(0.0, 0.0, 0.0, if is_dark { 0.64 } else { 0.32 }); } + tokens.apply_os_view_colors(os, is_dark); tokens } + + /// Overlay the content-view and semantic colours of an OS report, after + /// the window, selection and accent families are in place (they win + /// over the derivations above, which they refine). + /// + /// Each field is used only when present, so a report without them leaves + /// the tokens exactly as before. A foreground the desktop supplies is + /// checked against the surfaces it is painted on and lifted to WCAG's + /// floor when it falls short (4.5:1 for text, 3:1 for the focus ring and + /// the status glyphs): a desktop scheme was tuned for its own widgets, + /// not for every Teksilo surface. The same goes for the text already in + /// place when the view background replaces a surface under it: the + /// window's text on `surface_content`, the editor's on `editor_bg`. + fn apply_os_view_colors(&mut self, os: &OsThemeColors, is_dark: bool) { + use crate::contrast::lift_until_readable; + const TEXT: f32 = 4.5; + const NON_TEXT: f32 = 3.0; + + if let Some(bg) = os.view_bg { + // `text_primary` and `text_secondary` are painted on + // `surface_content` as well as on `surface_main`, so the view + // background becomes the content surface only where the window + // text can be read on both. A view in the window's tone takes it, + // the text lifted as far as the new surface needs; a view in the + // opposite tone (a dark view in a light scheme), on which no one + // colour reads alongside the window, is left to the editor, whose + // own text is checked against it below. + let surfaces = [self.surface_main, bg]; + let primary = lift_until_readable(self.text_primary, &surfaces, TEXT); + if surfaces + .iter() + .all(|&s| composite_contrast(primary, s) >= TEXT) + { + self.surface_content = bg; + self.text_primary = primary; + self.text_secondary = lift_until_readable(self.text_secondary, &surfaces, TEXT); + } + self.editor_bg = bg; + // The current-line and code-block tints follow the editor + // surface, as they do from the window background. + self.editor_current_line_bg = if is_dark { + bg.lighten(0.06) + } else { + bg.darken(0.03) + }; + self.editor_code_block_bg = if is_dark { + bg.lighten(0.08) + } else { + bg.darken(0.05) + }; + } + if let Some(fg) = os.view_fg { + self.editor_fg = fg; + self.editor_caret = fg; + } + // The editor's text, the desktop's or the one derived from the + // window, on the editor surface the desktop supplied or the one it + // already had. + if os.view_bg.is_some() || os.view_fg.is_some() { + let editor = [self.editor_bg]; + self.editor_fg = lift_until_readable(self.editor_fg, &editor, TEXT); + self.editor_caret = lift_until_readable(self.editor_caret, &editor, NON_TEXT); + self.editor_code_block_fg = lift_until_readable( + self.editor_code_block_fg, + &[self.editor_code_block_bg], + TEXT, + ); + } + if let Some(alt) = os.alternate_bg { + self.surface_alt_row = alt; + } + + let surfaces = [self.surface_main, self.surface_content]; + if let Some(fg) = os.inactive_fg { + self.text_secondary = lift_until_readable(fg, &surfaces, TEXT); + } + if let Some(link) = os.link { + let link = lift_until_readable(link, &surfaces, TEXT); + self.text_link = link; + // Hover moves away from the surface, so it reads at least as + // well as the link itself. + self.text_link_hover = lift_until_readable( + if is_dark { + link.lighten(0.10) + } else { + link.darken(0.10) + }, + &surfaces, + TEXT, + ); + } + if let Some(visited) = os.link_visited { + self.text_link_visited = lift_until_readable(visited, &surfaces, TEXT); + } + if let Some(error) = os.error { + self.text_error = lift_until_readable(error, &surfaces, TEXT); + self.status_error_fg = lift_until_readable( + error, + &[ + self.status_error_bg, + self.surface_main, + self.surface_content, + ], + NON_TEXT, + ); + } + if let Some(warning) = os.warning { + self.text_warning = lift_until_readable(warning, &surfaces, TEXT); + self.status_warning_fg = lift_until_readable( + warning, + &[ + self.status_warning_bg, + self.surface_main, + self.surface_content, + ], + NON_TEXT, + ); + } + if let Some(success) = os.success { + self.text_success = lift_until_readable(success, &surfaces, TEXT); + self.status_success_fg = lift_until_readable( + success, + &[ + self.status_success_bg, + self.surface_main, + self.surface_content, + ], + NON_TEXT, + ); + } + if let Some(focus) = os.focus { + let ring = lift_until_readable(focus, &surfaces, NON_TEXT); + self.focus_ring = ring; + self.border_focused = ring; + } + } } /// Okabe-Ito 8-color colorblind-safe palette (Okabe & Ito 2008). @@ -829,30 +1114,252 @@ mod tests { assert_ne!(light.surface_main, dark.surface_main); } + /// Every pair [`ColorTokens::for_high_contrast`] documents, below its + /// floor in `hc`, named with its ratio. + fn high_contrast_shortfalls(hc: &ColorTokens) -> Vec { + use crate::contrast::composite_contrast; + let surfaces = [ + ("surface_main", hc.surface_main), + ("surface_content", hc.surface_content), + ("surface_raised", hc.surface_raised), + ]; + let mut out = Vec::new(); + let mut check = |name: String, fg: Color, bg: Color, floor: f32| { + let r = composite_contrast(fg, bg); + if r < floor { + out.push(format!("{name} = {r:.2}, floor {floor}")); + } + }; + for (fill, c) in [ + ("accent", hc.accent), + ("accent_hover", hc.accent_hover), + ("accent_pressed", hc.accent_pressed), + ] { + check( + format!("text_on_accent on {fill}"), + hc.text_on_accent, + c, + 7.0, + ); + } + for (s, bg) in surfaces { + check(format!("focus_ring on {s}"), hc.focus_ring, bg, 4.5); + check(format!("border_focused on {s}"), hc.border_focused, bg, 4.5); + check(format!("text_secondary on {s}"), hc.text_secondary, bg, 7.0); + } + for (fill, c) in [ + ("surface_selected", hc.surface_selected), + ("surface_selected_inactive", hc.surface_selected_inactive), + ] { + check( + format!("text_secondary on {fill}"), + hc.text_secondary, + c, + 7.0, + ); + check(format!("text_primary on {fill}"), hc.text_primary, c, 7.0); + check( + format!("{fill} on surface_content"), + c, + hc.surface_content, + 1.5, + ); + } + out + } + #[test] fn high_contrast_variant_exceeds_aa_on_both_presets() { // Audit G13 (WCAG 1.4.6 Enhanced / EN 301 549 11.7): the opt-in // high-contrast projection clears >= 7:1 text and >= 4.5:1 non-text on - // both the light and dark bases. + // both the light and dark bases, for an active and an inactive window + // (the paint chain projects the inactive tokens). for (name, base) in [ ("light", ColorTokens::light_default()), ("dark", ColorTokens::dark_default()), ] { - let hc = base.for_high_contrast(); - let ta = hc.text_on_accent.contrast_ratio(hc.accent); - assert!(ta >= 7.0, "{name} HC: text_on_accent vs accent = {ta:.2}"); - for (sname, s) in [ - ("surface_content", hc.surface_content), - ("surface_main", hc.surface_main), + for (state, tokens) in [ + ("active", base.clone()), + ("inactive", base.for_inactive_window()), ] { - let ts = hc.text_secondary.contrast_ratio(s); - assert!(ts >= 7.0, "{name} HC: text_secondary vs {sname} = {ts:.2}"); - let fr = hc.focus_ring.contrast_ratio(s); - assert!(fr >= 4.5, "{name} HC: focus_ring vs {sname} = {fr:.2}"); + let short = high_contrast_shortfalls(&tokens.for_high_contrast()); + assert!(short.is_empty(), "{name} {state}: {short:#?}"); } } } + #[test] + fn high_contrast_keeps_an_accent_that_already_meets_its_floors() { + // IntUI's accents already carry their black text at 7:1 (8.50 light, + // 9.27 dark), and the dark one stands 4.5:1 on the dark surfaces, so + // neither moves. Before the projection read the palette, dark mode + // swapped in #4FCCE0 whatever the accent was. + for base in [ColorTokens::light_default(), ColorTokens::dark_default()] { + let hc = base.for_high_contrast(); + assert_eq!(hc.accent, base.accent); + assert_eq!(hc.text_on_accent, base.text_on_accent); + } + // A token the projection does not document is never touched. + let base = ColorTokens::dark_default(); + let hc = base.for_high_contrast(); + assert_eq!(hc.text_primary, base.text_primary); + assert_eq!(hc.text_link, base.text_link); + assert_eq!(hc.accent_subtle_bg, base.accent_subtle_bg); + assert_eq!(hc.surface_main, base.surface_main); + assert_eq!(hc.chart_palette, base.chart_palette); + } + + #[test] + fn high_contrast_lifts_a_short_accent_and_keeps_its_hue() { + // macOS's Aqua accent, #0063E1 under white text, measures 3.87:1 with + // the black text the old projection forced on it. + let base = ColorTokens { + accent: Color::from_hex("#0063E1"), + accent_hover: Color::from_hex("#005ED6"), + accent_pressed: Color::from_hex("#0054BF"), + text_on_accent: Color::WHITE, + ..ColorTokens::light_default() + }; + let hc = base.for_high_contrast(); + assert_eq!(hc.text_on_accent, Color::WHITE); + for (before, after) in [ + (base.accent, hc.accent), + (base.accent_hover, hc.accent_hover), + (base.accent_pressed, hc.accent_pressed), + ] { + let ratio = Color::WHITE.contrast_ratio(after); + assert!((7.0..7.3).contains(&ratio), "lifted to {ratio:.2}"); + let (h0, ..) = before.to_hsv(); + let (h1, ..) = after.to_hsv(); + assert!((h0 - h1).abs() < 0.5, "hue {h0} became {h1}"); + } + // White text and light surfaces are alike in tone, so the accent + // also stands 4.5:1 on them. + for s in [hc.surface_main, hc.surface_content, hc.surface_raised] { + assert!(hc.accent.contrast_ratio(s) >= 4.5); + } + } + + #[test] + fn high_contrast_holds_a_light_accent_to_its_black_text() { + // The documented divergence: black text on IntUI's light accent + // needs it at a luminance of 0.30 or more, a ring at 4.5:1 on white + // one of 0.18 or less, so the accent stays light and only the ring + // darkens. + let hc = ColorTokens::light_default().for_high_contrast(); + assert!(hc.accent.relative_luminance() >= 0.30); + assert!(hc.focus_ring.relative_luminance() <= 0.183); + assert!(hc.accent.contrast_ratio(Color::WHITE) < 4.5); + assert!(hc.focus_ring.contrast_ratio(Color::WHITE) >= 4.5); + } + + #[test] + fn high_contrast_keeps_a_neutral_ring_neutral() { + // Fluent's ring is 89 % black, never the accent: it already clears + // 4.5:1 and is kept as it is, alpha included. + let ring = Color::BLACK.with_alpha(0.89); + let base = ColorTokens { + focus_ring: ring, + border_focused: ring, + ..ColorTokens::light_default() + }; + let hc = base.for_high_contrast(); + assert_eq!(hc.focus_ring, ring); + assert_eq!(hc.border_focused, ring); + } + + #[test] + fn high_contrast_gives_translucent_secondary_text_more_opacity() { + // macOS's secondary label is 58 % black, about 5.3:1 on white: + // already black, so only its alpha can carry it to 7:1. + let base = ColorTokens { + text_secondary: Color::BLACK.with_alpha(0.58), + ..ColorTokens::light_default() + }; + let hc = base.for_high_contrast(); + assert!(hc.text_secondary.a() > 0.58 && hc.text_secondary.a() < 1.0); + assert_eq!( + [ + hc.text_secondary.r(), + hc.text_secondary.g(), + hc.text_secondary.b() + ], + [0.0, 0.0, 0.0] + ); + assert!(high_contrast_shortfalls(&hc).is_empty()); + } + + #[test] + fn high_contrast_selection_moves_off_the_surface_and_keeps_its_text() { + use crate::contrast::composite_contrast; + let base = ColorTokens::light_default(); + let hc = base.for_high_contrast(); + // IntUI's wash stands 1.19:1 off white; the projection moves it to + // 1.5:1 and no farther than one step past. + let before = base.surface_selected.contrast_ratio(base.surface_content); + let after = hc.surface_selected.contrast_ratio(hc.surface_content); + assert!(before < 1.2, "{before}"); + assert!((1.5..1.55).contains(&after), "{after}"); + // A translucent wash (Fluent's 9 % black) becomes an opaque fill. + let wash = ColorTokens { + surface_selected: Color::BLACK.with_alpha(0.09), + ..ColorTokens::light_default() + } + .for_high_contrast(); + assert_eq!(wash.surface_selected.a(), 1.0); + assert!(wash.surface_selected.contrast_ratio(wash.surface_content) >= 1.5); + // A selection the primary text already falls short on moves toward + // the surface until the text reads. + let dark = ColorTokens::dark_default(); + let short = ColorTokens { + surface_selected: Color::from_hex("#3A6A7A"), + ..dark.clone() + }; + assert!(composite_contrast(short.text_primary, short.surface_selected) < 7.0); + let hc = short.for_high_contrast(); + let r = composite_contrast(hc.text_primary, hc.surface_selected); + assert!((7.0..7.3).contains(&r), "{r}"); + } + + #[test] + fn high_contrast_moves_the_inactive_selection_too() { + // A row in an unfocused view, or in an inactive window, paints + // `surface_selected_inactive`; it must stand off the page as the + // focused selection does. IntUI's light wash stands 1.24:1 off + // white, so it moves to 1.5:1 and no farther than one step past. + let light = ColorTokens::light_default(); + let before = light + .surface_selected_inactive + .contrast_ratio(light.surface_content); + assert!(before < 1.25, "{before}"); + for tokens in [light.clone(), light.for_inactive_window()] { + let hc = tokens.for_high_contrast(); + let after = hc + .surface_selected_inactive + .contrast_ratio(hc.surface_content); + assert!((1.5..1.55).contains(&after), "{after}"); + assert!(high_contrast_shortfalls(&hc).is_empty()); + } + // The dark one already stands 1.74:1 off the dark content surface + // under text at 7:1, so it is kept. + let dark = ColorTokens::dark_default(); + for tokens in [dark.clone(), dark.for_inactive_window()] { + let hc = tokens.for_high_contrast(); + assert_eq!(hc.surface_selected_inactive, dark.surface_selected_inactive); + assert!(high_contrast_shortfalls(&hc).is_empty()); + } + // A translucent inactive wash becomes an opaque fill, and the + // secondary text is lifted on it. + let wash = ColorTokens { + surface_selected_inactive: Color::BLACK.with_alpha(0.06), + text_secondary: Color::BLACK.with_alpha(0.58), + ..ColorTokens::light_default() + } + .for_high_contrast(); + assert_eq!(wash.surface_selected_inactive.a(), 1.0); + assert!(high_contrast_shortfalls(&wash).is_empty()); + } + #[test] fn for_inactive_window_desaturates_accent_not_selection() { for c in [ColorTokens::light_default(), ColorTokens::dark_default()] { @@ -1053,6 +1560,326 @@ mod tests { assert_eq!(ColorTokens::dark_default().chart_palette.len(), 8); } + use crate::contrast::composite_contrast; + use crate::os_theme_colors::ColorSchemePreference as Scheme; + + fn os(color_scheme: Scheme) -> OsThemeColors { + OsThemeColors::new(color_scheme) + } + + /// The names of the tokens that differ between `a` and `b`, sorted, so + /// a test pins both what a field changes and that it changes nothing + /// else. + fn changed_fields(a: &ColorTokens, b: &ColorTokens) -> Vec { + let a = serde_json::to_value(a).unwrap(); + let b = serde_json::to_value(b).unwrap(); + let (a, b) = (a.as_object().unwrap(), b.as_object().unwrap()); + let mut out: Vec = a + .iter() + .filter(|(k, v)| b.get(k.as_str()) != Some(v)) + .map(|(k, _)| k.clone()) + .collect(); + out.sort_unstable(); + out + } + + #[test] + fn from_os_colors_view_bg_overrides_the_content_surface() { + // A view in the scheme's own tone. + for (scheme, bg) in [(Scheme::Light, "#FCFCFC"), (Scheme::Dark, "#1B1E20")] { + let base = ColorTokens::from_os_colors(&os(scheme)); + let bg = Color::from_hex(bg); + let mut report = os(scheme); + report.view_bg = Some(bg); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.surface_content, bg); + assert_eq!(t.editor_bg, bg); + assert_ne!(t.editor_current_line_bg, bg); + assert_eq!( + changed_fields(&t, &base), + [ + "editor_bg", + "editor_code_block_bg", + "editor_current_line_bg", + "surface_content" + ] + ); + } + } + + #[test] + fn from_os_colors_view_bg_wins_over_the_window_derivation() { + let mut report = os(Scheme::Light); + report.window_bg = Some(Color::from_hex("#EFF0F1")); + report.view_bg = Some(Color::from_hex("#FFFFFF")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.surface_main, Color::from_hex("#EFF0F1")); + assert_eq!(t.surface_content, Color::from_hex("#FFFFFF")); + assert_eq!(t.editor_bg, Color::from_hex("#FFFFFF")); + } + + #[test] + fn from_os_colors_view_fg_overrides_the_editor_text() { + let base = ColorTokens::from_os_colors(&os(Scheme::Dark)); + let fg = Color::from_hex("#FCFCFC"); + let mut report = os(Scheme::Dark); + report.view_fg = Some(fg); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.editor_fg, fg); + assert_eq!(t.editor_caret, fg); + assert_eq!(changed_fields(&t, &base), ["editor_caret", "editor_fg"]); + } + + /// Each text pair a view colour can put on a surface, below 4.5:1 in + /// `t`, named with its ratio. + fn view_text_shortfalls(t: &ColorTokens) -> Vec { + [ + ( + "text_primary on surface_main", + t.text_primary, + t.surface_main, + ), + ( + "text_primary on surface_content", + t.text_primary, + t.surface_content, + ), + ( + "text_secondary on surface_main", + t.text_secondary, + t.surface_main, + ), + ( + "text_secondary on surface_content", + t.text_secondary, + t.surface_content, + ), + ("editor_fg on editor_bg", t.editor_fg, t.editor_bg), + ( + "editor_code_block_fg on editor_code_block_bg", + t.editor_code_block_fg, + t.editor_code_block_bg, + ), + ] + .into_iter() + .filter_map(|(name, fg, bg)| { + let r = composite_contrast(fg, bg); + (r < 4.5).then(|| format!("{name} = {r:.2}")) + }) + .chain( + (composite_contrast(t.editor_caret, t.editor_bg) < 3.0) + .then(|| "editor_caret on editor_bg".to_owned()), + ) + .collect() + } + + #[test] + fn from_os_colors_view_fg_is_lifted_on_the_view() { + let mut report = os(Scheme::Light); + report.view_bg = Some(Color::WHITE); + report.view_fg = Some(Color::from_hex("#BBBBBB")); + let t = ColorTokens::from_os_colors(&report); + assert_ne!(t.editor_fg, Color::from_hex("#BBBBBB")); + assert_eq!(view_text_shortfalls(&t), Vec::::new()); + // Lifted toward black, so it stays a grey. + assert!((t.editor_fg.r() - t.editor_fg.b()).abs() < 1e-3); + } + + #[test] + fn from_os_colors_a_view_in_the_windows_tone_lifts_the_window_text() { + // #767676 reads at 4.54:1 on white, not on a #E0E0E0 view. + let mut report = os(Scheme::Light); + report.window_bg = Some(Color::WHITE); + report.window_fg = Some(Color::from_hex("#767676")); + report.view_bg = Some(Color::from_hex("#E0E0E0")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.surface_content, Color::from_hex("#E0E0E0")); + assert_ne!(t.text_primary, Color::from_hex("#767676")); + assert_eq!(view_text_shortfalls(&t), Vec::::new()); + } + + #[test] + fn from_os_colors_a_view_in_the_opposite_tone_stays_the_editors() { + // A light scheme with a dark view, alone and with its own text. + let dark_view = Color::from_hex("#202020"); + let mut alone = os(Scheme::Light); + alone.view_bg = Some(dark_view); + let mut full = os(Scheme::Light); + full.window_bg = Some(Color::from_hex("#EFF0F1")); + full.window_fg = Some(Color::from_hex("#232629")); + full.view_bg = Some(Color::from_hex("#232629")); + full.view_fg = Some(Color::from_hex("#FCFCFC")); + for report in [alone, full] { + let without_view = ColorTokens::from_os_colors(&OsThemeColors { + view_bg: None, + view_fg: None, + ..report.clone() + }); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.editor_bg, report.view_bg.unwrap()); + assert_eq!(t.surface_content, without_view.surface_content); + assert_eq!(t.text_primary, without_view.text_primary); + // The view adds no shortfall to the window's own (this window's + // derived secondary text reads at 4.24:1 on its background). + assert_eq!( + view_text_shortfalls(&t), + view_text_shortfalls(&without_view) + ); + } + } + + #[test] + fn from_os_colors_alternate_bg_overrides_the_alt_row() { + let base = ColorTokens::from_os_colors(&os(Scheme::Light)); + let alt = Color::from_hex("#F7F7F7"); + let mut report = os(Scheme::Light); + report.alternate_bg = Some(alt); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.surface_alt_row, alt); + assert_eq!(changed_fields(&t, &base), ["surface_alt_row"]); + } + + #[test] + fn from_os_colors_inactive_fg_overrides_secondary_text_and_is_lifted() { + let base = ColorTokens::from_os_colors(&os(Scheme::Light)); + // Readable already: taken as is. + let mut report = os(Scheme::Light); + report.inactive_fg = Some(Color::from_hex("#4D4F52")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.text_secondary, Color::from_hex("#4D4F52")); + assert_eq!(changed_fields(&t, &base), ["text_secondary"]); + // Breeze's #707D8A on a white view is about 4.3:1: lifted. + report.view_bg = Some(Color::WHITE); + report.inactive_fg = Some(Color::from_hex("#707D8A")); + let t = ColorTokens::from_os_colors(&report); + assert_ne!(t.text_secondary, Color::from_hex("#707D8A")); + for bg in [t.surface_main, t.surface_content] { + assert!(composite_contrast(t.text_secondary, bg) >= 4.5); + } + } + + #[test] + fn from_os_colors_links_override_and_are_lifted() { + for scheme in [Scheme::Light, Scheme::Dark] { + let base = ColorTokens::from_os_colors(&os(scheme)); + let mut report = os(scheme); + report.link = Some(Color::from_hex("#2980B9")); + report.link_visited = Some(Color::from_hex("#9B59B6")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!( + changed_fields(&t, &base), + ["text_link", "text_link_hover", "text_link_visited"] + ); + for c in [t.text_link, t.text_link_hover, t.text_link_visited] { + for bg in [t.surface_main, t.surface_content] { + assert!(composite_contrast(c, bg) >= 4.5, "{scheme:?} {c:?}"); + } + } + assert_ne!(t.text_link, t.text_link_hover); + } + // A readable link is taken exactly. + let mut report = os(Scheme::Light); + report.link = Some(Color::from_hex("#1D5E8A")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.text_link, Color::from_hex("#1D5E8A")); + } + + #[test] + fn from_os_colors_semantic_colours_override_text_and_status() { + for scheme in [Scheme::Light, Scheme::Dark] { + let base = ColorTokens::from_os_colors(&os(scheme)); + let mut report = os(scheme); + report.error = Some(Color::from_hex("#DA4453")); + report.warning = Some(Color::from_hex("#F67400")); + report.success = Some(Color::from_hex("#27AE60")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!( + changed_fields(&t, &base), + [ + "status_error_fg", + "status_success_fg", + "status_warning_fg", + "text_error", + "text_success", + "text_warning" + ] + ); + for (text, status, status_bg) in [ + (t.text_error, t.status_error_fg, t.status_error_bg), + (t.text_warning, t.status_warning_fg, t.status_warning_bg), + (t.text_success, t.status_success_fg, t.status_success_bg), + ] { + for bg in [t.surface_main, t.surface_content] { + assert!(composite_contrast(text, bg) >= 4.5, "{scheme:?} {text:?}"); + assert!( + composite_contrast(status, bg) >= 3.0, + "{scheme:?} {status:?}" + ); + } + assert!(composite_contrast(status, status_bg) >= 3.0); + } + } + // Readable values are taken exactly. + let mut report = os(Scheme::Light); + report.error = Some(Color::from_hex("#A0202C")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.text_error, Color::from_hex("#A0202C")); + assert_eq!(t.status_error_fg, Color::from_hex("#A0202C")); + } + + #[test] + fn from_os_colors_focus_overrides_the_ring_and_is_lifted() { + for scheme in [Scheme::Light, Scheme::Dark] { + let base = ColorTokens::from_os_colors(&os(scheme)); + let mut report = os(scheme); + report.focus = Some(Color::from_hex("#3DAEE9")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(changed_fields(&t, &base), ["border_focused", "focus_ring"]); + assert_eq!(t.focus_ring, t.border_focused); + for bg in [t.surface_main, t.surface_content] { + assert!(composite_contrast(t.focus_ring, bg) >= 3.0); + } + } + // The focus field wins over the accent's ring. + let mut report = os(Scheme::Dark); + report.accent = Some(Color::from_hex("#FF5500")); + report.focus = Some(Color::from_hex("#3DAEE9")); + let t = ColorTokens::from_os_colors(&report); + assert_eq!(t.accent, Color::from_hex("#FF5500")); + assert_eq!(t.focus_ring, Color::from_hex("#3DAEE9")); + } + + #[test] + fn from_os_colors_without_the_new_fields_is_unchanged() { + // The pre-existing fields alone give the same tokens whether the + // report is built with `new` or as a literal: no new field leaks in. + let mut report = os(Scheme::Dark); + report.window_bg = Some(Color::from_hex("#202326")); + report.window_fg = Some(Color::from_hex("#FCFCFC")); + report.selection_bg = Some(Color::from_hex("#3DAEE9")); + report.accent = Some(Color::from_hex("#3DAEE9")); + let literal = OsThemeColors { + color_scheme: Scheme::Dark, + accent: Some(Color::from_hex("#3DAEE9")), + window_bg: Some(Color::from_hex("#202326")), + window_fg: Some(Color::from_hex("#FCFCFC")), + selection_bg: Some(Color::from_hex("#3DAEE9")), + ..Default::default() + }; + assert_eq!( + ColorTokens::from_os_colors(&report), + ColorTokens::from_os_colors(&literal) + ); + assert_eq!( + ColorTokens::from_os_colors(&os(Scheme::Light)), + ColorTokens::light_default() + ); + assert_eq!( + ColorTokens::from_os_colors(&os(Scheme::Dark)), + ColorTokens::dark_default() + ); + } + #[test] fn from_os_colors_accent_takes_precedence_over_selection() { let accent = Color::from_hex("#FF0000"); diff --git a/crates/teksilo-tokens/tests/prop_color.rs b/crates/teksilo-tokens/tests/prop_color.rs index a171ebfa2..e250d8713 100644 --- a/crates/teksilo-tokens/tests/prop_color.rs +++ b/crates/teksilo-tokens/tests/prop_color.rs @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2026 FernTech -//! Property tests for `Color` (crates/teksilo-tokens/src/color.rs) and the -//! `ColorTokens::for_inactive_window` accent-desaturation transform +//! Property tests for `Color` (crates/teksilo-tokens/src/color.rs), the +//! `ColorTokens::for_inactive_window` accent-desaturation transform and the +//! `ColorTokens::for_high_contrast` projection //! (crates/teksilo-tokens/src/theme.rs). //! //! `Color` is the lowest-level public type in the styling ladder — every @@ -22,6 +23,7 @@ //! restating them. use proptest::prelude::*; +use teksilo_tokens::contrast::composite_contrast; use teksilo_tokens::{Color, ColorTokens}; /// Channel values clamped to the valid `0.0..=1.0` range that `Color` @@ -345,3 +347,158 @@ proptest! { prop_assert_eq!(inactive.status_error_bg.to_array(), base.status_error_bg.to_array()); } } + +/// An opaque colour: the accent family is a fill text is read on, which +/// `for_high_contrast` measures as the opaque colour it paints, so a +/// translucent accent would be measured over a backdrop the property does not +/// choose. Its alpha is the one channel held fixed. +fn arb_opaque_color() -> impl Strategy { + (arb_channel(), arb_channel(), arb_channel()).prop_map(|(r, g, b)| Color::from_rgb(r, g, b)) +} + +/// An accent family and a focus colour for `for_high_contrast`: the accent, +/// its hover and pressed fills drawn independently (a preset derives them, +/// but nothing requires it to), and a focus ring and focused border of any +/// alpha (Fluent's ring is 89 % black). +fn arb_accent_family() -> impl Strategy { + ( + arb_opaque_color(), + arb_opaque_color(), + arb_opaque_color(), + arb_color(), + arb_color(), + ) +} + +/// One of IntUI's two bases, so the property covers a light and a dark +/// palette. +fn arb_base() -> impl Strategy { + prop_oneof![ + Just(("light", ColorTokens::light_default())), + Just(("dark", ColorTokens::dark_default())), + ] +} + +// ── 13. for_high_contrast meets every floor it documents for an arbitrary +// accent family and focus colour, on a light and a dark base, for an +// active and an inactive window, and changes no token it does not +// document ── +// The unit tests in theme.rs check the shipped IntUI accents and the four +// presets' crates check theirs; this is the claim that the projection is a +// rule over the palette, not a table of known-good accents. + +proptest! { + #[test] + fn for_high_contrast_meets_every_documented_floor_for_any_accent( + (name, base) in arb_base(), + (accent, accent_hover, accent_pressed, focus_ring, border_focused) in arb_accent_family(), + window_active in any::(), + ) { + let tokens = ColorTokens { + accent, + accent_hover, + accent_pressed, + focus_ring, + border_focused, + ..base + }; + let tokens = if window_active { tokens } else { tokens.for_inactive_window() }; + let hc = tokens.for_high_contrast(); + let surfaces = [hc.surface_main, hc.surface_content, hc.surface_raised]; + for (fill, c) in [("accent", hc.accent), ("accent_hover", hc.accent_hover), ("accent_pressed", hc.accent_pressed)] { + let r = composite_contrast(hc.text_on_accent, c); + prop_assert!( + r >= 7.0, + "{} active={}: text_on_accent {:?} on {} {:?} = {:.3} (from {:?})", + name, window_active, hc.text_on_accent, fill, c, r, tokens.accent + ); + // White text in a light palette, black in a dark one: the + // accent clears the surfaces too. + let text_light = hc.text_on_accent.relative_luminance() > 0.5; + let surfaces_light = hc.surface_main.relative_luminance() > 0.5; + if text_light == surfaces_light { + for s in surfaces { + let r = composite_contrast(c, s); + prop_assert!( + r >= 4.5, + "{} active={}: {} {:?} under {:?} on {:?} = {:.3}", + name, window_active, fill, c, hc.text_on_accent, s, r + ); + } + } + } + for (ring, c) in [("focus_ring", hc.focus_ring), ("border_focused", hc.border_focused)] { + for s in surfaces { + let r = composite_contrast(c, s); + prop_assert!( + r >= 4.5, + "{} active={}: {} {:?} on {:?} = {:.3}", + name, window_active, ring, c, s, r + ); + } + } + for s in surfaces.into_iter().chain([hc.surface_selected, hc.surface_selected_inactive]) { + let r = composite_contrast(hc.text_secondary, s); + prop_assert!(r >= 7.0, "{} active={}: text_secondary on {:?} = {:.3}", name, window_active, s, r); + } + for (fill, c) in [("surface_selected", hc.surface_selected), ("surface_selected_inactive", hc.surface_selected_inactive)] { + let r = composite_contrast(hc.text_primary, c); + prop_assert!(r >= 7.0, "{} active={}: text_primary on {} = {:.3}", name, window_active, fill, r); + } + // Only the documented tokens move. + let expected = ColorTokens { + accent: hc.accent, + accent_hover: hc.accent_hover, + accent_pressed: hc.accent_pressed, + focus_ring: hc.focus_ring, + border_focused: hc.border_focused, + text_on_accent: hc.text_on_accent, + text_secondary: hc.text_secondary, + surface_selected: hc.surface_selected, + surface_selected_inactive: hc.surface_selected_inactive, + ..tokens.clone() + }; + prop_assert!(hc == expected, "{} active={}: an undocumented token moved", name, window_active); + } +} + +// ── 14. for_high_contrast keeps the hue of every accent fill and focus +// colour it lifts ── +// A lift is a straight sRGB move toward black or white, which scales every +// channel's distance from that end by one factor and so leaves the HSV hue +// where it was. An achromatic colour has no hue to keep, and one whose +// saturation the lift drives to zero (lifted all the way to black or white) +// has none left, so the hue is compared only where both sides have some. + +proptest! { + #[test] + fn for_high_contrast_keeps_the_hue_of_what_it_lifts( + (_name, base) in arb_base(), + (accent, accent_hover, accent_pressed, focus_ring, border_focused) in arb_accent_family(), + ) { + let tokens = ColorTokens { + accent, + accent_hover, + accent_pressed, + focus_ring: focus_ring.with_alpha(1.0), + border_focused: border_focused.with_alpha(1.0), + ..base + }; + let hc = tokens.for_high_contrast(); + for (name, before, after) in [ + ("accent", tokens.accent, hc.accent), + ("accent_hover", tokens.accent_hover, hc.accent_hover), + ("accent_pressed", tokens.accent_pressed, hc.accent_pressed), + ("focus_ring", tokens.focus_ring, hc.focus_ring), + ("border_focused", tokens.border_focused, hc.border_focused), + ] { + let (h0, s0, v0) = before.to_hsv(); + let (h1, s1, v1) = after.to_hsv(); + if s0 > 0.05 && s1 > 0.05 && v0 > 0.05 && v1 > 0.05 { + let d = (h0 - h1).abs(); + let d = d.min(360.0 - d); + prop_assert!(d < 1.0, "{}: {:?} (hue {}) lifted to {:?} (hue {})", name, before, h0, after, h1); + } + } + } +} diff --git a/crates/teksilo-widgets/src/breadcrumb.rs b/crates/teksilo-widgets/src/breadcrumb.rs index e356b5105..0e908544d 100644 --- a/crates/teksilo-widgets/src/breadcrumb.rs +++ b/crates/teksilo-widgets/src/breadcrumb.rs @@ -64,6 +64,7 @@ use crate::menu_item::MenuItem; use crate::menu_list::MenuList; use crate::popover_widget::PopoverButton; use crate::primitives::{HStack, IconWidget, Spacer}; +use teksilo_core::styles::FocusRingStroke; use teksilo_core::styles::density::spacing; use teksilo_i18n::LocalizedString; @@ -285,7 +286,7 @@ impl BreadcrumbSegment { fn estimate_width(&self, ctx: &LayoutContext) -> f32 { let pad_h = breadcrumb_item_padding_horizontal(&ctx.theme.input); - let envelope = ctx.theme.shape.focus_ring_offset + ctx.theme.shape.focus_ring_width; + let envelope = ctx.theme.shape.focus_ring_envelope(); let resolved = self.label.resolve_now(); let text_width = if let Some(backend) = ctx.text_backend { backend @@ -424,7 +425,7 @@ impl Widget for BreadcrumbSegment { proposal: SizeProposal, ctx: &LayoutContext, ) -> teksilo_core::widget::LayoutResponse { - let envelope = ctx.theme.shape.focus_ring_offset + ctx.theme.shape.focus_ring_width; + let envelope = ctx.theme.shape.focus_ring_envelope(); let width = proposal.width.unwrap_or_else(|| self.estimate_width(ctx)); let text_height = if let Some(backend) = ctx.text_backend { backend @@ -462,17 +463,13 @@ impl Widget for BreadcrumbSegment { self.painted_size.set(bounds.size()); let colors = &ctx.theme.colors; let shape = &ctx.theme.shape; - let envelope = shape.focus_ring_offset + shape.focus_ring_width; + let envelope = shape.focus_ring_envelope(); let interaction = self.interaction.get(); - let interactive = self.is_interactive(); + // Not `is_interactive()`: `build` has moved the action out by now. + let interactive = self.navigates.get(); // Visual bounds — inset by the focus-ring envelope. - let visual = Rect::new( - bounds.x + envelope, - bounds.y + envelope, - (bounds.width - envelope * 2.0).max(0.0), - (bounds.height - envelope * 2.0).max(0.0), - ); + let visual = bounds.inset(envelope, envelope, envelope, envelope); if interactive { let background = if interaction == SegmentInteraction::Hovered { @@ -489,19 +486,13 @@ impl Widget for BreadcrumbSegment { background, ); } - // Focus ring — drawn outside the visual, inside the reserved envelope. + // Focus ring: drawn around the visual, inside the reserved + // envelope. A negative offset lays it over the visual's edge. if interaction == SegmentInteraction::Focused { - let half_stroke = shape.focus_ring_width * 0.5; - let ring_rect = Rect::new( - bounds.x + half_stroke, - bounds.y + half_stroke, - (bounds.width - half_stroke * 2.0).max(0.0), - (bounds.height - half_stroke * 2.0).max(0.0), - ); - let ring_radius = BREADCRUMB_CORNER_RADIUS + shape.focus_ring_offset + half_stroke; + let ring = FocusRingStroke::around(visual, BREADCRUMB_CORNER_RADIUS, shape); canvas.stroke_rounded_rect( - ring_rect, - CornerRadius::uniform(ring_radius), + ring.rect, + CornerRadius::uniform(ring.corner_radius), colors.focus_ring, shape.focus_ring_width, ); @@ -544,7 +535,8 @@ impl Widget for BreadcrumbSegment { builder.set_name(self.label.resolve_now()); if self.current { builder.set_aria_current(teksilo_core::accesskit::AriaCurrent::Page); - } else if self.is_interactive() { + } else if self.navigates.get() { + // Not `is_interactive()`: `build` has moved the action out by now. builder.add_action(teksilo_core::accesskit::Action::Click); builder.add_action(teksilo_core::accesskit::Action::Focus); } @@ -1071,6 +1063,57 @@ mod tests { bc } + // ── focus ring ─────────────────────────────────────────────────────────── + + /// The focused crumb's ring and its visual (the focused background + /// fill) under a theme whose `focus_ring_offset` is `offset` (width 2), + /// each as `[x, y, width, height]`. + fn crumb_ring_and_visual(offset: f32) -> ([f32; 4], [f32; 4]) { + let mut theme = teksilo_core::presets::intui::light(); + theme.shape.focus_ring_offset = offset; + theme.shape.focus_ring_width = 2.0; + let ring_color = theme.colors.focus_ring.to_array(); + let fill_color = theme.colors.accent.with_alpha(0.12).to_array(); + let mut tree = WidgetTree::new() + .with_theme(theme) + .with_text_backend(Rc::new(RefCell::new(MockTextBackend::new()))); + tree.add(trail(2)); + tree.layout(SizeProposal::exact(400.0, 60.0)); + let crumb = tree + .find_by_label("Crumb 0") + .expect("the first crumb renders"); + tree.focus(crumb); + tree.layout(SizeProposal::exact(400.0, 60.0)); + let frame = tree.render(); + let ring = frame + .shapes + .iter() + .find(|s| s.color == ring_color && s.stroke_width > 0.0) + .expect("a focused crumb strokes its ring") + .screen; + let visual = frame + .shapes + .iter() + .find(|s| s.color == fill_color && s.stroke_width == 0.0) + .expect("a focused crumb fills its visual") + .screen; + (ring, visual) + } + + #[test] + fn a_negative_focus_ring_offset_lays_the_ring_over_the_crumb_edge() { + let (outer, outer_visual) = crumb_ring_and_visual(2.0); + let (inner, inner_visual) = crumb_ring_and_visual(-1.0); + // Offset 2: centre line 3 dp outside the visual. Offset -1, width + // 2: centred on the visual's edge. + assert!((outer[2] - outer_visual[2] - 6.0).abs() < 1e-3); + assert!((inner[2] - inner_visual[2]).abs() < 1e-3); + assert!((inner[3] - inner_visual[3]).abs() < 1e-3); + + let (deep, _) = crumb_ring_and_visual(-100.0); + assert!(deep[2] >= 0.0 && deep[3] >= 0.0, "ring inverted: {deep:?}"); + } + // ── tooltips ───────────────────────────────────────────────────────────── #[test] @@ -1390,6 +1433,36 @@ mod tests { ); } + /// A built crumb that navigates still offers AT a way to activate it, and + /// the current one offers none. Read off the **mounted** crumbs: `build` + /// moves the action out, so a check that asked for it would see none. + #[test] + fn a_built_navigating_crumb_advertises_click_and_focus() { + use teksilo_core::accesskit::Action; + + let mut tree = themed_tree(); + let trail = tree.add(trail(2)); + tree.layout(SizeProposal::exact(300.0, 40.0)); + let _ = tree.render(); + + let crumbs = segment_ids(&tree, trail); + assert_eq!(crumbs.len(), 2, "two crumbs, got {crumbs:?}"); + let navigating = tree.accessibility_node(crumbs[0]); + assert!( + navigating.actions().contains(&Action::Click), + "a navigating crumb must advertise Click, or AT cannot follow it", + ); + assert!( + navigating.actions().contains(&Action::Focus), + "a navigating crumb must advertise Focus", + ); + let current = tree.accessibility_node(crumbs[1]); + assert!( + !current.actions().contains(&Action::Click), + "the current crumb navigates nowhere and must not advertise Click", + ); + } + /// A mouse is exact: the same press lands on the path bar, exactly as it /// did before the touch programme. #[test] diff --git a/crates/teksilo-widgets/src/button.rs b/crates/teksilo-widgets/src/button.rs index 00d008905..1c040c08b 100644 --- a/crates/teksilo-widgets/src/button.rs +++ b/crates/teksilo-widgets/src/button.rs @@ -86,7 +86,7 @@ use crate::primitives::{HStack, TextWidget, VStack}; /// dialogs where the title/body carry the warning. The IntUI default /// `RecipeButtonStyle` collapses `Destructive → Filled`, `Tinted / /// Outlined → Plain`, and `Link → Ghost` accordingly. Other design -/// languages (Material 3, macOS) honour the variants distinctly. +/// languages (Material 3, Fluent, macOS, KDE) honour the variants distinctly. pub use teksilo_core::styles::ButtonVariant; use teksilo_i18n::LocalizedString; @@ -1413,7 +1413,14 @@ mod tests { // window-active palette to grey out like the paint-resolving controls. let theme = teksilo_core::presets::intui::light(); let accent = theme.colors.accent.to_array(); - let inactive_accent = theme.colors.for_inactive_window().accent.to_array(); + let inactive_accent = theme + .for_paint_state(teksilo_core::styles::PaintState { + window_active: false, + high_contrast: false, + }) + .colors + .accent + .to_array(); assert_ne!(accent, inactive_accent); let mut tree = WidgetTree::new().with_theme(theme); @@ -1443,6 +1450,75 @@ mod tests { assert!(frame_has_color(&tree.render(), accent)); } + #[test] + fn filled_button_fill_follows_the_high_contrast_preference() { + // The baked fill resolves through `Theme::paint_colors`, so it takes + // the high-contrast accent the paint walker gives every + // paint-resolving control. The projection keeps an accent that + // already carries its text at 7:1, as both IntUI accents do, so the + // theme gets one that does not: IntUI dark's pressed fill, whose + // black text measures 4.4:1, which the projection lifts. + use teksilo_core::styles::PaintState; + let mut theme = teksilo_core::presets::intui::dark(); + theme.colors.accent = theme.colors.accent_pressed; + let accent = theme.colors.accent.to_array(); + let hc_accent = theme + .for_paint_state(PaintState { + window_active: true, + high_contrast: true, + }) + .colors + .accent + .to_array(); + assert_ne!(accent, hc_accent); + + let mut tree = WidgetTree::new().with_theme(theme); + tree.add(Button::new(lit!("Save")).variant(ButtonVariant::Filled)); + tree.layout(SizeProposal::exact(200.0, 80.0)); + assert!(frame_has_color(&tree.render(), accent)); + + tree.set_accessibility_preferences(true, false, 1.0); + let frame = tree.render(); + assert!( + frame_has_color(&frame, hc_accent), + "high contrast: the Filled button fill takes the high-contrast accent" + ); + assert!(!frame_has_color(&frame, accent)); + + tree.set_accessibility_preferences(false, false, 1.0); + assert!(frame_has_color(&tree.render(), accent)); + } + + #[test] + fn filled_button_fill_follows_a_themes_paint_projection() { + // A theme's `PaintProjection` reaches the colours a recipe bakes at + // build time, not only the ones resolved at paint. + use teksilo_core::styles::{PaintState, Theme}; + fn project(theme: &Theme, state: PaintState) -> Theme { + let mut out = theme.clone(); + if !state.window_active { + out.colors.accent = teksilo_tokens::Color::from_hex("#FF00FF"); + } + out + } + let theme = teksilo_core::presets::intui::light().with_paint_projection(project); + let accent = theme.colors.accent.to_array(); + let hooked = teksilo_tokens::Color::from_hex("#FF00FF").to_array(); + + let mut tree = WidgetTree::new().with_theme(theme); + tree.add(Button::new(lit!("Save")).variant(ButtonVariant::Filled)); + tree.layout(SizeProposal::exact(200.0, 80.0)); + assert!(frame_has_color(&tree.render(), accent)); + + tree.set_window_active(false); + let frame = tree.render(); + assert!( + frame_has_color(&frame, hooked), + "inactive window: the fill is the hook's colour" + ); + assert!(!frame_has_color(&frame, accent)); + } + #[test] fn keyup_without_keydown_does_not_fire() { // Regression for the MessageBox reopen bug: when a shortcut diff --git a/crates/teksilo-widgets/src/color_picker/swatch.rs b/crates/teksilo-widgets/src/color_picker/swatch.rs index 713cba2a9..9bbef8556 100644 --- a/crates/teksilo-widgets/src/color_picker/swatch.rs +++ b/crates/teksilo-widgets/src/color_picker/swatch.rs @@ -35,6 +35,7 @@ use teksilo_core::accesskit::{Action, Role}; use teksilo_core::build_context::BuildContext; use teksilo_core::event::{EventResponse, Key, WidgetEvent}; use teksilo_core::focus::FocusOrigin; +use teksilo_core::styles::FocusRingStroke; use teksilo_core::widget::{ CursorIcon, EventContext, LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement, }; @@ -340,20 +341,14 @@ impl Widget for ColorSwatch { // Focus ring (keyboard). if self.focus_origin.get() == Some(FocusOrigin::Keyboard) { - let offset = ctx.theme.shape.focus_ring_offset; - let half = ctx.theme.shape.focus_ring_width * 0.5; - let inset = offset + half; - let ring = Rect::new( - bounds.x - inset, - bounds.y - inset, - bounds.width + inset * 2.0, - bounds.height + inset * 2.0, + let ring = FocusRingStroke::around( + bounds, + self.corner_radius.unwrap_or(cp::SWATCH_CORNER_RADIUS), + &ctx.theme.shape, ); canvas.stroke_rounded_rect( - ring, - CornerRadius::uniform( - self.corner_radius.unwrap_or(cp::SWATCH_CORNER_RADIUS) + inset, - ), + ring.rect, + CornerRadius::uniform(ring.corner_radius), ctx.theme.colors.focus_ring, ctx.theme.shape.focus_ring_width, ); diff --git a/crates/teksilo-widgets/src/color_picker/tests.rs b/crates/teksilo-widgets/src/color_picker/tests.rs index 07f7bb46c..3ca641c38 100644 --- a/crates/teksilo-widgets/src/color_picker/tests.rs +++ b/crates/teksilo-widgets/src/color_picker/tests.rs @@ -1074,3 +1074,38 @@ fn a_mouse_just_outside_a_swatch_lands_on_the_row() { ); assert_eq!(row_taps.get(), 1); } + +/// The keyboard ring of a focused swatch under a theme whose +/// `focus_ring_offset` is `offset` (width 2), plus the swatch bounds. +fn swatch_focus_ring(offset: f32) -> (teksilo_canvas::Rect, [f32; 4]) { + let mut theme = teksilo_core::presets::intui::light(); + theme.shape.focus_ring_offset = offset; + theme.shape.focus_ring_width = 2.0; + let mut tree = WidgetTree::new().with_theme(theme); + let id = tree.add(ColorSwatch::new(Color::RED).on_activate_fn(|_| {})); + tree.layout(SizeProposal::exact(40.0, 40.0)); + tree.focus(id); + tree.press_key(Key::ArrowDown, Modifiers::NONE); + tree.layout(SizeProposal::exact(40.0, 40.0)); + let frame = tree.render(); + // The unselected swatch strokes a 1 dp hairline; the ring is the + // only 2 dp stroke. + let ring = frame + .shapes + .iter() + .find(|s| s.stroke_width == 2.0) + .expect("a focused swatch strokes its focus ring"); + (tree.bounds(id), ring.screen) +} + +#[test] +fn a_negative_focus_ring_offset_draws_the_swatch_ring_on_its_edge() { + let (bounds, inner) = swatch_focus_ring(-1.0); + let (_, outer) = swatch_focus_ring(2.0); + // Offset -1, width 2: the stroke is centred on the swatch edge. + assert_eq!(inner, bounds.to_array()); + assert!(inner[2] < outer[2] && inner[3] < outer[3]); + + let (_, deep) = swatch_focus_ring(-60.0); + assert!(deep[2] >= 0.0 && deep[3] >= 0.0, "ring inverted: {deep:?}"); +} diff --git a/crates/teksilo-widgets/src/menu_item/tests.rs b/crates/teksilo-widgets/src/menu_item/tests.rs index 09a48f53e..93c0a5288 100644 --- a/crates/teksilo-widgets/src/menu_item/tests.rs +++ b/crates/teksilo-widgets/src/menu_item/tests.rs @@ -122,6 +122,187 @@ fn the_hook_does_not_touch_an_idle_row() { assert!(!colors.contains(&on_accent)); } +// --- `MenuItemStyle::pressed_label_role` --- + +/// A style whose hover is a pale wash (no highlighted role) but whose press +/// is a solid fill, so only the press flips the label. +#[derive(Debug, Default, Clone, Copy)] +struct OnAccentPressStyle; + +impl teksilo_core::styles::MenuItemStyle for OnAccentPressStyle { + fn make_body( + &self, + cfg: &MenuItemStyleConfig, + ctx: &mut teksilo_core::build_context::BuildContext, + ) -> WidgetId { + crate::styles::RecipeMenuItemStyle::for_tokens(&ctx.theme().input).make_body(cfg, ctx) + } + + fn pressed_label_role(&self) -> Option { + Some(TextRole::OnAccent) + } +} + +/// How a row is driven before its glyph colours are read. +#[derive(Clone, Copy)] +enum Drive { + Idle, + Hover, + /// Enter on the focused row: a plain row with no action stays pressed. + Press, + /// The primary button held down over the row, not yet released. + PointerHold, + /// The primary button pressed over the row and released over it. + PointerClick, +} + +/// Build a menu row with a shortcut chip under `style`, drive it, and +/// report the glyph colours it paints. +fn driven_row_glyph_colors(styled: bool, drive: Drive) -> Vec<[u8; 4]> { + let mut t = WidgetTree::new() + .with_theme(discriminating_theme()) + .with_text_backend(std::rc::Rc::new(std::cell::RefCell::new( + teksilo_canvas::MockTextBackend::new(), + ))); + let mut item = MenuItem::new(lit!("Open")).shortcut_label("Ctrl+O"); + if styled { + item = item.style(OnAccentPressStyle); + } + let id = t.add(item); + layout(&mut t); + match drive { + Drive::Idle => {} + Drive::Hover => { + t.pointer_move(t.bounds(id).center()); + layout(&mut t); + } + Drive::Press => { + t.focus(id); + t.press_key(Key::Enter, Modifiers::NONE); + layout(&mut t); + } + Drive::PointerHold | Drive::PointerClick => { + let at = t.bounds(id).center(); + t.pointer_move(at); + layout(&mut t); + t.pointer_down_button(at, teksilo_core::event::PointerButton::Primary); + layout(&mut t); + if matches!(drive, Drive::PointerClick) { + t.pointer_up_button(at, teksilo_core::event::PointerButton::Primary); + layout(&mut t); + } + } + } + glyph_colors(&mut t) +} + +/// The default is `None`: a pressed row under a style without the hook +/// keeps its own mapping. +#[test] +fn a_style_without_the_pressed_hook_leaves_the_pressed_label_alone() { + let on_accent = rgba8(discriminating_theme().colors.text_on_accent); + let colors = driven_row_glyph_colors(false, Drive::Press); + assert!(!colors.is_empty()); + assert!(!colors.contains(&on_accent), "painted {colors:?}"); +} + +/// A style that declares the hook flips the label and the shortcut of a +/// pressed row. +#[test] +fn the_pressed_hook_flips_the_label_of_a_pressed_row() { + let theme = discriminating_theme(); + let primary = rgba8(theme.colors.text_primary); + let on_accent = rgba8(theme.colors.text_on_accent); + let colors = driven_row_glyph_colors(true, Drive::Press); + assert!(colors.contains(&on_accent), "painted {colors:?}"); + assert!( + !colors.contains(&primary), + "the label kept its idle colour under the press; painted {colors:?}" + ); +} + +/// A row held under the pointer is pressed too: the press shows from the +/// pointer-down, not only from the keyboard, so the hook reaches a mouse. +#[test] +fn the_pressed_hook_flips_the_label_of_a_row_held_under_the_pointer() { + let theme = discriminating_theme(); + let primary = rgba8(theme.colors.text_primary); + let on_accent = rgba8(theme.colors.text_on_accent); + let colors = driven_row_glyph_colors(true, Drive::PointerHold); + assert!(colors.contains(&on_accent), "painted {colors:?}"); + assert!( + !colors.contains(&primary), + "the label kept its idle colour under the pointer press; painted {colors:?}" + ); + // A style without the hook keeps its own label under the same press. + let colors = driven_row_glyph_colors(false, Drive::PointerHold); + assert!(!colors.is_empty()); + assert!(!colors.contains(&on_accent), "painted {colors:?}"); +} + +/// The pointer press ends with the release: a clicked row no longer reads +/// as pressed. +#[test] +fn the_pressed_hook_lets_go_of_the_label_on_the_release() { + let theme = discriminating_theme(); + let primary = rgba8(theme.colors.text_primary); + let on_accent = rgba8(theme.colors.text_on_accent); + let colors = driven_row_glyph_colors(true, Drive::PointerClick); + assert!(colors.contains(&primary), "painted {colors:?}"); + assert!(!colors.contains(&on_accent), "painted {colors:?}"); +} + +/// The pressed role applies only while pressed: an idle row and a hovered +/// one keep their normal label. +#[test] +fn the_pressed_hook_does_not_touch_an_idle_or_hovered_row() { + let theme = discriminating_theme(); + let primary = rgba8(theme.colors.text_primary); + let on_accent = rgba8(theme.colors.text_on_accent); + for drive in [Drive::Idle, Drive::Hover] { + let colors = driven_row_glyph_colors(true, drive); + assert!(colors.contains(&primary), "painted {colors:?}"); + assert!(!colors.contains(&on_accent), "painted {colors:?}"); + } +} + +/// The pressed role wins over the highlighted one on a pressed row, and the +/// highlighted one still applies to a hovered row. +#[test] +fn the_pressed_role_takes_precedence_over_the_highlighted_role() { + let rest = TextRole::Primary; + use super::widget_impl::StateRoles; + let roles = StateRoles { + highlighted: Some(TextRole::Secondary), + pressed: Some(TextRole::OnAccent), + }; + assert_eq!( + roles.resolve(MenuItemState::Pressed, rest), + TextRole::OnAccent + ); + assert_eq!( + roles.resolve(MenuItemState::Hovered, rest), + TextRole::Secondary + ); + assert_eq!(roles.resolve(MenuItemState::Idle, rest), rest); + + let highlight_only = StateRoles { + highlighted: Some(TextRole::Secondary), + pressed: None, + }; + assert_eq!( + highlight_only.resolve(MenuItemState::Pressed, rest), + TextRole::Secondary + ); +} + +/// The trait default is `None`. +#[test] +fn the_pressed_label_role_defaults_to_none() { + use teksilo_core::styles::MenuItemStyle as _; + assert_eq!(OnAccentHighlightStyle.pressed_label_role(), None); +} + // --- Role coverage --- fn a11y_node( diff --git a/crates/teksilo-widgets/src/menu_item/widget_impl.rs b/crates/teksilo-widgets/src/menu_item/widget_impl.rs index 052bdb3c8..cf7d39b4a 100644 --- a/crates/teksilo-widgets/src/menu_item/widget_impl.rs +++ b/crates/teksilo-widgets/src/menu_item/widget_impl.rs @@ -30,6 +30,28 @@ fn is_highlight(state: MenuItemState) -> bool { matches!(state, MenuItemState::Hovered | MenuItemState::Pressed) } +/// The roles a style asks a row's foregrounds to take in its interaction +/// states, read once per build. +#[derive(Clone, Copy)] +pub(super) struct StateRoles { + /// [`MenuItemStyle::highlighted_label_role`](teksilo_core::styles::MenuItemStyle::highlighted_label_role). + pub(super) highlighted: Option, + /// [`MenuItemStyle::pressed_label_role`](teksilo_core::styles::MenuItemStyle::pressed_label_role). + pub(super) pressed: Option, +} + +impl StateRoles { + /// The role a foreground reads in `state`: the pressed role while + /// pressed, else the highlighted role while highlighted (a pressed row + /// included), else `rest`, the row's own mapping for the state. + pub(super) fn resolve(self, state: MenuItemState, rest: TextRole) -> TextRole { + self.pressed + .filter(|_| state == MenuItemState::Pressed) + .or_else(|| self.highlighted.filter(|_| is_highlight(state))) + .unwrap_or(rest) + } +} + impl Widget for MenuItem { fn build(&mut self, ctx: &mut BuildContext) -> Vec { let self_id = ctx.self_id(); @@ -45,6 +67,30 @@ impl Widget for MenuItem { // leaves' role substitution. let interaction = ctx.signal(MenuItemState::Idle); self.interaction = interaction.clone(); + // The row's pressed state, driven by the router's press record while + // a pointer holds the row. It was previously written by the keyboard + // path alone (and by `on_tap`, which runs on the release and resets + // to `Idle` in the same handler), so a style's pressed chrome and its + // `pressed_label_role` were unreachable for a mouse and for a finger. + // `hovered` is where the hover truth lives while `interaction` holds + // `Pressed`, so a press that ends without activating (it slid off) + // returns the row to where the pointer is. + let hovered = Rc::new(std::cell::Cell::new(false)); + { + let hovered = hovered.clone(); + crate::common::interaction::bind_press_state( + ctx, + interaction.clone(), + MenuItemState::Pressed, + move || { + if hovered.get() { + MenuItemState::Hovered + } else { + MenuItemState::Idle + } + }, + ); + } // Resolved here rather than at `make_body` because the label is // built long before the chrome, and a style whose highlight is a @@ -59,7 +105,10 @@ impl Widget for MenuItem { &ctx.theme().input, )) }); - let highlighted_role = style.highlighted_label_role(); + let state_roles = StateRoles { + highlighted: style.highlighted_label_role(), + pressed: style.pressed_label_role(), + }; // The slot sizes the active style owns. `MenuItem` builds the leading // icon/check column and the trailing chevron column itself — they are // slot *contents*, handed to `make_body` already sized — so they have @@ -77,9 +126,7 @@ impl Widget for MenuItem { if !*on { TextRole::Disabled } else { - highlighted_role - .filter(|_| is_highlight(*s)) - .unwrap_or_else(|| resolve_text_role(*s)) + state_roles.resolve(*s, resolve_text_role(*s)) } }); @@ -278,11 +325,8 @@ impl Widget for MenuItem { }; let has_shortcut = shortcut.is_some(); if let Some(shortcut) = shortcut { - let shortcut_role = interaction.map(move |s| { - highlighted_role - .filter(|_| is_highlight(*s)) - .unwrap_or_else(|| resolve_shortcut_role(*s)) - }); + let shortcut_role = + interaction.map(move |s| state_roles.resolve(*s, resolve_shortcut_role(*s))); trailing_row = trailing_row.child( shortcut .style(TextStyleRole::Body) @@ -304,11 +348,8 @@ impl Widget for MenuItem { crate::primitives::FixedSize::new().width(metrics.trailing_column_width), ); } - let hint_role = interaction.map(move |s| { - highlighted_role - .filter(|_| is_highlight(*s)) - .unwrap_or_else(|| resolve_shortcut_role(*s)) - }); + let hint_role = + interaction.map(move |s| state_roles.resolve(*s, resolve_shortcut_role(*s))); trailing_row = trailing_row.child( TextWidget::new(hint) .style(TextStyleRole::Body) @@ -622,7 +663,9 @@ impl Widget for MenuItem { }) .on_hover({ let int_hover = int_hover.clone(); + let hovered = hovered.clone(); move |entered: bool, ctx: &mut EventContext| { + hovered.set(entered); if entered { int_hover.set(MenuItemState::Hovered); open_for_hover.set(true); @@ -723,7 +766,9 @@ impl Widget for MenuItem { }) .on_hover({ let safe_triangle_sibling = self.safe_triangle.clone(); + let hovered = hovered.clone(); move |entered: bool, ctx: &mut EventContext| { + hovered.set(entered); if entered { // Safe-triangle gate: while a traversal // toward an open submenu in this list is diff --git a/crates/teksilo-widgets/src/notification/log.rs b/crates/teksilo-widgets/src/notification/log.rs index ad06c0acc..d4a0ddf80 100644 --- a/crates/teksilo-widgets/src/notification/log.rs +++ b/crates/teksilo-widgets/src/notification/log.rs @@ -518,20 +518,22 @@ impl Widget for NotificationLog { // `shrink = 0`, so on its own the list would keep its full // wanted height and spill out the bottom of a short panel. // `Shrinkable` preserves the inner flex weight and adds the - // compression path, floored at one two-line row. + // compression path, floored at one two-line row: the + // height the active `StandardItemStyle` gives a row with a + // subtitle, which is what every log row is. + let one_row = + si::resolve_standard_item_style(ctx).min_height_two_line(&ctx.theme().input); let scrollable = ScrollArea::new() .preferred_height(self.preferred_height) .child(sections); column = column.child( ctx.add( - Shrinkable::new() - .min_height(si::STANDARD_ITEM_MIN_HEIGHT_TWO_LINE) - .child( - Expand::vertical() - .flex(1.0) - .respect_intrinsic() - .child(scrollable), - ), + Shrinkable::new().min_height(one_row).child( + Expand::vertical() + .flex(1.0) + .respect_intrinsic() + .child(scrollable), + ), ), ); } @@ -924,6 +926,44 @@ mod tests { ); } + /// In a host far too short for it, the list stops compressing at one + /// row: the two-line row height the active `StandardItemStyle` declares, + /// not a constant. A style declaring 90 dp rows floors the list at 90. + #[test] + fn the_compressed_list_floors_at_the_styles_two_line_row_height() { + use teksilo_core::styles::{StandardItemStyle, StandardItemStyleConfig}; + + struct TallRows; + impl StandardItemStyle for TallRows { + fn make_body(&self, cfg: &StandardItemStyleConfig, ctx: &mut BuildContext) -> WidgetId { + si::RecipeStandardItemStyle::default().make_body(cfg, ctx) + } + fn min_height_two_line(&self, _tokens: &InputTokens) -> f32 { + 90.0 + } + } + + let archive = fresh_archive(); + for i in 0..8 { + archive.push(entry(&format!("Notice {i}"), Some("body"), Vec::new())); + } + let mut theme = teksilo_core::presets::intui::light(); + theme.style_slots.standard_item = Some(Rc::new(TallRows)); + let mut tree = WidgetTree::new() + .with_theme(theme) + .with_text_backend(Rc::new(std::cell::RefCell::new( + teksilo_canvas::MockTextBackend::new(), + ))); + let root = tree.add(NotificationLog::new(archive)); + tree.layout(SizeProposal::exact(480.0, 20.0)); + let floor = find_by_type(&tree, root, "Shrinkable").expect("the list is Shrinkable"); + assert!( + (tree.bounds(floor).height - 90.0).abs() < 0.01, + "the list must floor at the style's two-line row height, got {}", + tree.bounds(floor).height + ); + } + /// The readability regression: a title longer than the row is /// elided inside it. With `StandardListItem`'s wrapping default the /// label reported its full one-line intrinsic width, over- diff --git a/crates/teksilo-widgets/src/primitives/rect_widget.rs b/crates/teksilo-widgets/src/primitives/rect_widget.rs index c01339b71..2c8f17709 100644 --- a/crates/teksilo-widgets/src/primitives/rect_widget.rs +++ b/crates/teksilo-widgets/src/primitives/rect_widget.rs @@ -270,7 +270,14 @@ mod tests { let theme = teksilo_core::presets::intui::light(); let accent = theme.colors.accent.to_array(); - let inactive_accent = theme.colors.for_inactive_window().accent.to_array(); + let inactive_accent = theme + .for_paint_state(teksilo_core::styles::PaintState { + window_active: false, + high_contrast: false, + }) + .colors + .accent + .to_array(); assert_ne!(accent, inactive_accent); let mut tree = WidgetTree::new().with_theme(theme); diff --git a/crates/teksilo-widgets/src/progress_bar.rs b/crates/teksilo-widgets/src/progress_bar.rs index ebe4ee556..e54c3363a 100644 --- a/crates/teksilo-widgets/src/progress_bar.rs +++ b/crates/teksilo-widgets/src/progress_bar.rs @@ -68,7 +68,12 @@ pub struct ProgressBar { value: Prop, indeterminate: bool, orientation: Orientation, - thickness: f32, + /// The app's thickness, if it set one. + thickness: Option, + /// The thickness the style resolved at the last build declares + /// ([`ProgressBarStyle::thickness`](teksilo_core::styles::ProgressBarStyle::thickness)), + /// used when the app set none. + style_thickness: Option, track_color: Option, fill_color: Option, label: Option, @@ -84,7 +89,8 @@ impl ProgressBar { value: Prop::Static(value.clamp(0.0, 1.0)), indeterminate: false, orientation: Orientation::Horizontal, - thickness: DEFAULT_THICKNESS, + thickness: None, + style_thickness: None, track_color: None, fill_color: None, label: None, @@ -99,7 +105,8 @@ impl ProgressBar { value: Prop::Static(0.0), indeterminate: true, orientation: Orientation::Horizontal, - thickness: DEFAULT_THICKNESS, + thickness: None, + style_thickness: None, track_color: None, fill_color: None, label: None, @@ -123,12 +130,21 @@ impl ProgressBar { } /// Set the bar's narrow dimension in logical pixels. For horizontal bars - /// this is the height; for vertical bars this is the width. Default is 4.0. + /// this is the height; for vertical bars this is the width. Default is the + /// active style's thickness, 4.0 for the shipped style. pub fn thickness(mut self, thickness: f32) -> Self { - self.thickness = thickness; + self.thickness = Some(thickness); self } + /// The narrow dimension laid out: the app's, else the style's, else the + /// shipped 4.0. + fn effective_thickness(&self) -> f32 { + self.thickness + .or(self.style_thickness) + .unwrap_or(DEFAULT_THICKNESS) + } + /// Override the track background. Default (unset) is `SurfaceRole::Sunken`. /// Accepts `Color`, roles, or `Signal`. pub fn track_color(mut self, color: impl Into) -> Self { @@ -164,7 +180,7 @@ impl ProgressBar { impl std::fmt::Debug for ProgressBar { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ProgressBar") - .field("thickness", &self.thickness) + .field("thickness", &self.effective_thickness()) .finish() } } @@ -175,8 +191,6 @@ impl Widget for ProgressBar { // when reduced-motion is on, fall through to a static // signal-driven path that never animates (pos stays at 0). let reduced_motion = ctx.prefers_reduced_motion(); - let animate = self.indeterminate && !reduced_motion; - let use_shader_path = animate && matches!(self.orientation, Orientation::Horizontal); let sweep_period = ctx.theme().motion.duration_indeterminate_sweep; let style: SharedProgressBarStyle = self @@ -188,6 +202,12 @@ impl Widget for ProgressBar { &ctx.theme().input, )) }); + self.style_thickness = style.thickness(&ctx.theme().input); + // A style that paints its own busy indicator takes the indeterminate + // bar whole, in either orientation: no sweep is drawn over it. + let style_owns_busy = self.indeterminate && style.paints_busy_indicator(); + let animate = self.indeterminate && !reduced_motion && !style_owns_busy; + let use_shader_path = animate && matches!(self.orientation, Orientation::Horizontal); let cfg = ProgressBarStyleConfig { orientation: self.orientation, progress: if self.indeterminate { @@ -226,6 +246,8 @@ impl Widget for ProgressBar { fill_color: fill, }); ctx.add(IndeterminateSweepLeaf::shader(handle)) + } else if style_owns_busy { + style.make_body(&cfg, ctx) } else if self.indeterminate { let frame_id = style.make_body(&cfg, ctx); let pos = ctx.animated_signal(0.0); @@ -270,11 +292,11 @@ impl Widget for ProgressBar { match self.orientation { Orientation::Horizontal => { let width = proposal.width.unwrap_or(100.0); - Size::new(width, self.thickness) + Size::new(width, self.effective_thickness()) } Orientation::Vertical => { let height = proposal.height.unwrap_or(100.0); - Size::new(self.thickness, height) + Size::new(self.effective_thickness(), height) } } .into() @@ -425,6 +447,7 @@ impl Widget for IndeterminateSweepLeaf { #[cfg(test)] mod tests { use super::*; + use teksilo_core::styles::ProgressBarStyle; use teksilo_core::widget_tree::WidgetTree; #[test] @@ -534,4 +557,101 @@ mod tests { "animated-quad phase must advance between frames" ); } + + /// A style that sizes its bar and paints its own busy indicator: a red + /// rectangle for an indeterminate bar, so the test can find it. + #[derive(Debug, Default, Clone, Copy)] + struct BusyStyle; + + impl ProgressBarStyle for BusyStyle { + fn make_body( + &self, + cfg: &ProgressBarStyleConfig, + ctx: &mut teksilo_core::build_context::BuildContext, + ) -> WidgetId { + let color = match cfg.progress { + ProgressKind::Indeterminate => Color::RED, + ProgressKind::Determinate(_) => Color::BLUE, + }; + ctx.add(crate::primitives::RectWidget::new().background(color)) + } + + fn thickness(&self, _tokens: &teksilo_tokens::InputTokens) -> Option { + Some(9.0) + } + + fn paints_busy_indicator(&self) -> bool { + true + } + } + + /// `bar` laid out 200 long with its narrow dimension left to it. + fn laid_out(bar: ProgressBar) -> (WidgetTree, WidgetId) { + let proposal = match bar.orientation { + Orientation::Horizontal => SizeProposal { + width: Some(200.0), + height: None, + }, + Orientation::Vertical => SizeProposal { + width: None, + height: Some(200.0), + }, + }; + let mut tree = WidgetTree::new().with_theme(teksilo_core::presets::intui::light()); + let id = tree.add(bar); + tree.layout(proposal); + (tree, id) + } + + /// The style's thickness is the bar's when the app names none, across + /// either orientation, and the app's own thickness wins over it. + #[test] + fn the_styles_thickness_sizes_a_bar_the_app_did_not_size() { + let (tree, id) = laid_out(ProgressBar::new(0.5).style(BusyStyle)); + assert_eq!(tree.bounds(id).height, 9.0); + let (tree, id) = laid_out( + ProgressBar::new(0.5) + .orientation(Orientation::Vertical) + .style(BusyStyle), + ); + assert_eq!(tree.bounds(id).width, 9.0); + let (tree, id) = laid_out(ProgressBar::new(0.5).thickness(3.0).style(BusyStyle)); + assert_eq!(tree.bounds(id).height, 3.0); + // The shipped style declares none, which keeps the widget's 4. + assert_eq!( + crate::styles::RecipeProgressBarStyle::default() + .thickness(&teksilo_tokens::InputTokens::default()), + None + ); + let (tree, id) = laid_out(ProgressBar::new(0.5)); + assert_eq!(tree.bounds(id).height, DEFAULT_THICKNESS); + } + + /// A style that paints its own busy indicator is mounted alone for an + /// indeterminate bar in both orientations: no shader quad, no sweep. + #[test] + fn a_style_that_paints_the_busy_indicator_replaces_the_sweep() { + for orientation in [Orientation::Horizontal, Orientation::Vertical] { + let (mut tree, _) = laid_out( + ProgressBar::indeterminate() + .orientation(orientation) + .style(BusyStyle), + ); + let frame = tree.render(); + assert!(frame.animated_quads.is_empty(), "{orientation:?}"); + let red: Vec<_> = frame + .shapes + .iter() + .filter(|s| s.color == Color::RED.to_array()) + .collect(); + assert_eq!(red.len(), 1, "{orientation:?}: the style's body"); + assert_eq!( + frame.shapes.len(), + 1, + "{orientation:?}: nothing painted over it" + ); + } + // The shipped style keeps the widget's sweep. + assert!(!crate::styles::RecipeProgressBarStyle::default().paints_busy_indicator()); + } } diff --git a/crates/teksilo-widgets/src/radio_tile_group.rs b/crates/teksilo-widgets/src/radio_tile_group.rs index 1db97fe0a..4ec920ce8 100644 --- a/crates/teksilo-widgets/src/radio_tile_group.rs +++ b/crates/teksilo-widgets/src/radio_tile_group.rs @@ -39,11 +39,11 @@ use teksilo_core::binding::BindingLevel; use teksilo_core::build_context::BuildContext; use teksilo_core::event::{EventResponse, Key, WidgetEvent}; use teksilo_core::signal::{Prop, Signal}; -use teksilo_core::styles::SharedRadioTileStyle; +use teksilo_core::styles::{FocusRingStroke, SharedRadioTileStyle}; use teksilo_core::widget::{EventContext, LayoutContext, PaintContext, Widget, WidgetPlacement}; use teksilo_core::widget_builder::HandlerSet; use teksilo_core::widget_id::WidgetId; -use teksilo_tokens::CornerRadius; +use teksilo_tokens::{CornerRadius, ShapeTokens}; use crate::radio_tile::RadioTile; use crate::styles::{RADIO_TILE_CORNER_RADIUS, RADIO_TILE_VERTICAL_ROW_HEIGHT}; @@ -79,7 +79,16 @@ pub enum TileLayout { /// Space (logical px) reserved around the tiles for the whole-group keyboard /// focus ring — the SegmentedControl envelope model. fn focus_ring_envelope(theme: &teksilo_core::Theme) -> f32 { - theme.shape.focus_ring_offset + theme.shape.focus_ring_width + theme.shape.focus_ring_envelope() +} + +/// The whole-group ring's centre line: around the tiles' area (`bounds` +/// inset by the envelope), `focus_ring_offset` from its edge. A negative +/// offset lays the ring over the tiles' outer edge. +fn group_focus_ring(bounds: Rect, shape: &ShapeTokens) -> FocusRingStroke { + let env = shape.focus_ring_envelope(); + let tiles = bounds.inset(env, env, env, env); + FocusRingStroke::around(tiles, RADIO_TILE_CORNER_RADIUS, shape) } /// An N-ary, single-selection group of selectable-card radios. See the @@ -579,20 +588,12 @@ impl Widget for RadioTileGroup { if !self.ring_visible.get() { return; } - let shape = &ctx.theme.shape; - let half = shape.focus_ring_width * 0.5; - let ring_rect = Rect::new( - bounds.x + half, - bounds.y + half, - (bounds.width - half * 2.0).max(0.0), - (bounds.height - half * 2.0).max(0.0), - ); - let ring_radius = RADIO_TILE_CORNER_RADIUS + shape.focus_ring_offset + half; + let ring = group_focus_ring(bounds, &ctx.theme.shape); canvas.stroke_rounded_rect( - ring_rect, - CornerRadius::uniform(ring_radius), + ring.rect, + CornerRadius::uniform(ring.corner_radius), ctx.theme.colors.focus_ring, - shape.focus_ring_width, + ctx.theme.shape.focus_ring_width, ); } @@ -1025,4 +1026,32 @@ mod tests { outer_y.get() ); } + + #[test] + fn a_negative_focus_ring_offset_lays_the_group_ring_over_the_tiles_edge() { + let shape = |offset: f32| ShapeTokens { + focus_ring_offset: offset, + focus_ring_width: 2.0, + ..ShapeTokens::default() + }; + let bounds = Rect::new(0.0, 0.0, 300.0, 120.0); + // Offset 2: envelope 4, ring 3 dp outside the tiles' area. + let outer = group_focus_ring(bounds, &shape(2.0)); + assert_eq!(outer.rect, Rect::new(1.0, 1.0, 298.0, 118.0)); + // Offset -1: envelope 1, ring centred on the tiles' edge. + let inner = group_focus_ring(bounds, &shape(-1.0)); + assert_eq!(inner.rect, Rect::new(1.0, 1.0, 298.0, 118.0)); + assert_eq!(inner.corner_radius, RADIO_TILE_CORNER_RADIUS); + assert!(inner.corner_radius < outer.corner_radius); + + // Against the same tiles' area, the inner ring is the smaller one. + let tiles = Rect::new(4.0, 4.0, 292.0, 112.0); + let around_outer = FocusRingStroke::around(tiles, RADIO_TILE_CORNER_RADIUS, &shape(2.0)); + let around_inner = FocusRingStroke::around(tiles, RADIO_TILE_CORNER_RADIUS, &shape(-1.0)); + assert!(around_inner.rect.width < around_outer.rect.width); + + let deep = group_focus_ring(bounds, &shape(-500.0)); + assert!(deep.rect.width >= 0.0 && deep.rect.height >= 0.0); + assert!(deep.corner_radius >= 0.0); + } } diff --git a/crates/teksilo-widgets/src/scroll_bar.rs b/crates/teksilo-widgets/src/scroll_bar.rs index a466d0abe..9d9577418 100644 --- a/crates/teksilo-widgets/src/scroll_bar.rs +++ b/crates/teksilo-widgets/src/scroll_bar.rs @@ -30,8 +30,10 @@ //! about whether a press is on the thumb is taken **along the axis only** — a //! finger 15 dp inboard of an 8 dp bar is beside the thumb, not past it. //! -//! The minimum thumb length follows the density (24 dp Compact, 44 dp Touch), -//! so a short thumb on a long document is still something a finger can land on. +//! The minimum thumb length is the active style's +//! [`ScrollBarStyle::min_thumb_length`], which follows the density (24 dp +//! Compact, 44 dp Touch under the shipped style), so a short thumb on a long +//! document is still something a finger can land on. //! //! ## Accessibility //! @@ -65,12 +67,11 @@ use teksilo_core::event::{EventResponse, PointerButton, WidgetEvent}; use teksilo_core::gesture::DragPhase; use teksilo_core::partition::TargetRegion; use teksilo_core::signal::Signal; -use teksilo_core::styles::density::dp; use teksilo_core::styles::{ScrollBarStyle, ScrollBarStyleConfig, SharedScrollBarStyle}; use teksilo_core::widget::{LayoutContext, LayoutResponse, Widget, WidgetPlacement}; use teksilo_core::widget_builder::HandlerSet; use teksilo_core::widget_id::WidgetId; -use teksilo_tokens::{RevealPolicy, TargetRole}; +use teksilo_tokens::RevealPolicy; use crate::common::range_nav::{self, RangeAxis, RangeKind, RangeMove}; @@ -89,9 +90,16 @@ pub use teksilo_core::styles::ScrollBarVariant as ScrollBarVisual; /// [`Widget::hit_outset`], which moves nothing and repaints nothing. pub const SCROLLBAR_COARSE_TARGET: f32 = 48.0; -/// The shipped minimum thumb length, at Compact. Raised to the density's -/// `target_size` (44 dp at Touch) at build time; a -/// [`min_thumb_length`](ScrollBar::min_thumb_length) override wins over both. +/// The shipped minimum thumb length, at Compact. +/// +/// A bar takes its floor from the active style's +/// [`ScrollBarStyle::min_thumb_length`] at build time: the shipped style and +/// the trait default both answer this value raised to the density's +/// `target_size` (32 dp at Comfortable, 44 dp at Touch), and a preset answers +/// its own (28 dp under macOS, 30 dp under Fluent, at Compact). Before its +/// first build a bar uses this value. A +/// [`min_thumb_length`](ScrollBar::min_thumb_length) override wins over all of +/// them. pub const SCROLLBAR_MIN_THUMB_LENGTH: f32 = 24.0; /// Which part of the bar a [`TargetRegion`] describes. @@ -140,10 +148,11 @@ pub struct ScrollBar { // --- visual tuning --- /// Thickness of the scroll bar (width for vertical, height for horizontal). thickness: f32, - /// Minimum thumb length in pixels, or `None` to follow the density. + /// Minimum thumb length in pixels, or `None` to follow the active style. min_thumb_length: Option, - /// The density floor [`Self::min_thumb_length`] falls back to, resolved at - /// the last `build`. Shared with the event closures and read by the + /// The style's floor [`Self::min_thumb_length`] falls back to, resolved at + /// the last `build` from [`ScrollBarStyle::min_thumb_length`] at the tree's + /// density. Shared with the event closures and read by the /// geometry helpers, which run outside a build and have no tokens in scope. /// An explicit floor does not go through it — it is known from the moment /// it is set, so the geometry is right before the first build too. @@ -219,11 +228,13 @@ impl ScrollBar { self } - /// Set the minimum thumb length in pixels, overriding the density. + /// Set the minimum thumb length in pixels, overriding the active style. /// - /// Left unset the floor is [`SCROLLBAR_MIN_THUMB_LENGTH`] raised to the - /// density's target size — 24 dp at Compact, 44 dp at Touch — so a short - /// thumb on a long document stays something a finger can land on. + /// Left unset the floor is the active style's + /// [`ScrollBarStyle::min_thumb_length`] at the tree's density: under the + /// shipped style [`SCROLLBAR_MIN_THUMB_LENGTH`] raised to the density's + /// target size (24 dp at Compact, 44 dp at Touch), so a short thumb on a + /// long document stays something a finger can land on. pub fn min_thumb_length(mut self, len: f32) -> Self { self.min_thumb_length = Some(len); self @@ -349,18 +360,14 @@ impl Widget for ScrollBar { )) }); - // The minimum thumb length follows the density unless the caller named - // one. 24 dp at Compact is exactly today's constant, so `dp` here - // raises nothing at the density CI runs at — the P20 rule that a - // dimension may go through `dp` only when its Compact value already - // clears the conformance floor, which 24 dp does by being it. - let min_thumb_length = self.min_thumb_length.unwrap_or_else(|| { - dp( - SCROLLBAR_MIN_THUMB_LENGTH, - TargetRole::Target, - &ctx.theme().input, - ) - }); + // The minimum thumb length is the one the resolved style declares at + // the tree's density, unless the caller named one. The same number + // sizes the painted thumb (through the config) and this widget's own + // thumb geometry (hit test, drag, reported target), so the thumb drawn + // and the thumb a press lands on cannot disagree. + let min_thumb_length = self + .min_thumb_length + .unwrap_or_else(|| style.min_thumb_length(&ctx.theme().input)); self.resolved_min_thumb_length.set(min_thumb_length); // A density that reveals every affordance (Touch) shows the bar without @@ -1521,7 +1528,7 @@ mod touch_tests { use teksilo_core::event::{Modifiers, WidgetEvent}; use teksilo_core::widget::{LayoutContext, Widget}; use teksilo_core::widget_tree::WidgetTree; - use teksilo_tokens::{InputTokens, PenKind, PointerKind, TargetDensity}; + use teksilo_tokens::{InputTokens, PenKind, PointerKind, TargetDensity, TargetRole}; /// A 12 dp vertical bar over 400 dp of track, content twice the viewport — /// so the thumb is half the track and starts at the top. @@ -1784,6 +1791,115 @@ mod touch_tests { assert_eq!(thumb.rect.height, 44.0); } + /// A style that declares its own floor, records the floor `make_body` was + /// handed, and paints nothing. + struct FloorStyle { + floor: f32, + seen: std::rc::Rc>, + } + + impl ScrollBarStyle for FloorStyle { + fn make_body( + &self, + cfg: &ScrollBarStyleConfig, + ctx: &mut teksilo_core::build_context::BuildContext, + ) -> WidgetId { + self.seen.set(cfg.min_thumb_length); + ctx.add(crate::primitives::Spacer::new()) + } + + fn min_thumb_length(&self, _tokens: &InputTokens) -> f32 { + self.floor + } + } + + /// A long document's bar (viewport ratio 0.02, so the unfloored thumb is + /// 8 dp of a 400 dp track) mounted at `density`, with `style` installed + /// either per call or in the theme slot, and an optional explicit floor. + /// Returns the floor `make_body` saw and the height of the thumb the + /// mounted bar reports, which is the rectangle its hit test and drag use. + fn floor_and_thumb( + density: TargetDensity, + floor: f32, + in_theme_slot: bool, + explicit: Option, + ) -> (f32, f32) { + let seen = Rc::new(Cell::new(f32::NAN)); + let style = FloorStyle { + floor, + seen: seen.clone(), + }; + let mut bar = ScrollBar::new( + ScrollBarOrientation::Vertical, + Signal::new(0.0), + Signal::new(500.0), + Signal::new(0.02), + ); + let mut theme = teksilo_core::presets::intui::light().with_density(density); + if in_theme_slot { + theme.style_slots.scroll_bar = Some(Rc::new(style)); + } else { + bar = bar.style(style); + } + if let Some(explicit) = explicit { + bar = bar.min_thumb_length(explicit); + } + let mut tree = WidgetTree::new().with_theme(theme); + let id = tree.add(bar); + tree.layout(SizeProposal::exact(12.0, 400.0)); + let thumb = tree + .widget_target_regions(id) + .into_iter() + .find(|r| r.part == SCROLLBAR_PART_THUMB) + .expect("the thumb is reported"); + (seen.get(), thumb.rect.height) + } + + /// The floor is the one the active style declares, per call or from the + /// theme slot, at every density: the style's answer reaches the painters + /// through the config and the bar's own thumb geometry alike. A style that + /// declares 37 dp gets a 37 dp thumb, where the bar used to recompute the + /// shipped 24 dp ladder whatever the style said. + #[test] + fn the_minimum_thumb_is_the_one_the_style_declares() { + for density in [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ] { + for in_theme_slot in [false, true] { + assert_eq!( + floor_and_thumb(density, 37.0, in_theme_slot, None), + (37.0, 37.0), + "{density:?}, theme slot: {in_theme_slot}", + ); + } + } + } + + /// An app's own floor still wins over the style's, in the config and in + /// the geometry. + #[test] + fn an_explicit_minimum_thumb_wins_over_the_style() { + assert_eq!( + floor_and_thumb(TargetDensity::Compact, 37.0, false, Some(60.0)), + (60.0, 60.0), + ); + assert_eq!( + floor_and_thumb(TargetDensity::Touch, 37.0, true, Some(20.0)), + (20.0, 20.0), + ); + } + + /// The floor still never makes the thumb longer than its track. + #[test] + fn a_style_floor_longer_than_the_track_is_clamped_to_it() { + assert_eq!( + floor_and_thumb(TargetDensity::Compact, 900.0, false, None), + (900.0, 400.0), + ); + } + // -- grabbing and paging ---------------------------------------------- /// A press inside the thumb's own paint rectangle grabs it. The bar is diff --git a/crates/teksilo-widgets/src/segmented_control.rs b/crates/teksilo-widgets/src/segmented_control.rs index a0dd88fb3..586cfee52 100644 --- a/crates/teksilo-widgets/src/segmented_control.rs +++ b/crates/teksilo-widgets/src/segmented_control.rs @@ -636,13 +636,8 @@ impl SegmentedControl { /// `SegmentSlotGeometry::frame`, so children land where the chrome /// paints. fn compute_visual(bounds: Rect, theme: &teksilo_core::Theme) -> Rect { - let envelope = theme.shape.focus_ring_offset + theme.shape.focus_ring_width; - Rect::new( - bounds.x + envelope, - bounds.y + envelope, - (bounds.width - envelope * 2.0).max(0.0), - (bounds.height - envelope * 2.0).max(0.0), - ) + let envelope = theme.shape.focus_ring_envelope(); + bounds.inset(envelope, envelope, envelope, envelope) } /// The grid area inside the frame's stroke. @@ -1193,7 +1188,7 @@ impl Widget for SegmentedControl { proposal: SizeProposal, ctx: &LayoutContext, ) -> teksilo_core::widget::LayoutResponse { - let envelope = ctx.theme.shape.focus_ring_offset + ctx.theme.shape.focus_ring_width; + let envelope = ctx.theme.shape.focus_ring_envelope(); let chrome = envelope * 2.0 + SEGMENTED_CONTROL_BORDER_WIDTH * 2.0; // Real measurement, not a per-character guess: this is what makes diff --git a/crates/teksilo-widgets/src/segmented_control/tests.rs b/crates/teksilo-widgets/src/segmented_control/tests.rs index 64fb70acc..e61859690 100644 --- a/crates/teksilo-widgets/src/segmented_control/tests.rs +++ b/crates/teksilo-widgets/src/segmented_control/tests.rs @@ -1109,6 +1109,55 @@ fn the_focused_selection_paints_with_the_accent() { assert!(frame.shapes.iter().any(|s| s.color == accent)); } +/// The keyboard ring and the outer frame of a focused control under a +/// theme whose `focus_ring_offset` is `offset` (width 2), each as +/// `[x, y, width, height]`. +fn ring_and_frame(offset: f32) -> ([f32; 4], [f32; 4]) { + let mut theme = teksilo_core::presets::intui::light(); + theme.shape.focus_ring_offset = offset; + theme.shape.focus_ring_width = 2.0; + let ring_color = theme.colors.focus_ring.to_array(); + let frame_color = theme.colors.border_strong.to_array(); + let mut t = WidgetTree::new() + .with_theme(theme) + .with_text_backend(std::rc::Rc::new(std::cell::RefCell::new( + teksilo_canvas::MockTextBackend::new(), + ))); + let control = t.add(abc(Signal::new(Some(B)))); + settle(&mut t, 300.0, 60.0); + t.focus(control); + t.press_key(Key::ArrowRight, Modifiers::NONE); + settle(&mut t, 300.0, 60.0); + let frame = t.render(); + let stroke = |color: [f32; 4]| { + frame + .shapes + .iter() + .find(|s| s.color == color && s.stroke_width > 0.0) + .map(|s| s.screen) + }; + ( + stroke(ring_color).expect("the keyboard ring is stroked"), + stroke(frame_color).expect("the outer frame is stroked"), + ) +} + +#[test] +fn a_negative_focus_ring_offset_draws_the_ring_on_the_frame() { + // Offset 2: the ring's centre line is 3 dp outside the frame. + let (outer, outer_frame) = ring_and_frame(2.0); + assert!((outer[2] - outer_frame[2] - 6.0).abs() < 1e-3); + // Offset -1, width 2: centred on the frame edge, so smaller than the + // outer ring around the same frame and never inverted. + let (inner, inner_frame) = ring_and_frame(-1.0); + assert!((inner[2] - inner_frame[2]).abs() < 1e-3); + assert!((inner[3] - inner_frame[3]).abs() < 1e-3); + assert!(inner[2] - inner_frame[2] < outer[2] - outer_frame[2]); + + let (deep, _) = ring_and_frame(-200.0); + assert!(deep[2] >= 0.0 && deep[3] >= 0.0, "ring inverted: {deep:?}"); +} + #[test] fn an_unfocused_selection_uses_the_inactive_surface() { let selected = Signal::new(Some(B)); diff --git a/crates/teksilo-widgets/src/slider.rs b/crates/teksilo-widgets/src/slider.rs index dc3ef82c0..8240acd4d 100644 --- a/crates/teksilo-widgets/src/slider.rs +++ b/crates/teksilo-widgets/src/slider.rs @@ -1449,6 +1449,39 @@ mod tests { || frame.cosmetic_lines.iter().any(|l| l.color == color) } + /// The thumb's keyboard ring under a theme whose `focus_ring_offset` is + /// `offset` (width 2), as `[x, y, width, height]`. + fn thumb_ring(offset: f32) -> [f32; 4] { + let mut theme = teksilo_core::presets::intui::light(); + theme.shape.focus_ring_offset = offset; + theme.shape.focus_ring_width = 2.0; + let ring = theme.colors.focus_ring.to_array(); + let mut tree = WidgetTree::new().with_theme(theme); + let s = tree.add(Slider::new(Signal::new(50.0_f32), 0.0, 100.0)); + tree.layout(SizeProposal::exact(200.0, 60.0)); + tree.focus(s); + tree.press_key(Key::ArrowDown, Modifiers::NONE); + tree.layout(SizeProposal::exact(200.0, 60.0)); + let frame = tree.render(); + frame + .shapes + .iter() + .find(|s| s.color == ring && s.stroke_width > 0.0) + .expect("the keyboard ring is stroked") + .screen + } + + #[test] + fn a_negative_focus_ring_offset_shrinks_the_thumb_ring_without_inverting() { + let outer = thumb_ring(2.0); + let inner = thumb_ring(-1.0); + assert!(inner[2] < outer[2] && inner[3] < outer[3]); + assert!(inner[2] >= 0.0 && inner[3] >= 0.0); + + let deep = thumb_ring(-40.0); + assert!(deep[2] >= 0.0 && deep[3] >= 0.0, "ring inverted: {deep:?}"); + } + #[test] fn keyboard_adjusts_value() { let value = Signal::new(50.0_f32); diff --git a/crates/teksilo-widgets/src/standard_item.rs b/crates/teksilo-widgets/src/standard_item.rs index 4d7a84035..6d080c872 100644 --- a/crates/teksilo-widgets/src/standard_item.rs +++ b/crates/teksilo-widgets/src/standard_item.rs @@ -68,13 +68,11 @@ //! //! ## Touch and pen //! -//! A row's **height** is a target floor and follows the density ladder: the row -//! projects the raw module constants through `density::dp` at layout time. It does -//! **not** read `StandardItemRecipe::min_height_single_line` / -//! `min_height_two_line`, which have carried the same projected values since the -//! density sweep and have no reader — so retuning either of those two recipe -//! fields in a preset moves nothing. `layout_response` says the same thing at the -//! site; `docs/touch-and-pen.md` §10.1 carries it as an open finding. +//! A row's **height** is a target floor and follows the density ladder. The row +//! asks the style it was built with for that floor, through +//! [`StandardItemStyle::min_height_single_line`](teksilo_core::styles::StandardItemStyle::min_height_single_line) +//! and its two-line twin. The shipped recipe answers 28 / 44 dp at Compact, and +//! a preset that writes its own (macOS 24, Fluent 40) gets that height. //! //! A row's **press** is not the row's. Inside a `ListView` or a `TreeView` the //! body pane owns the tap, the double tap and the reorder drag, and resolves which @@ -158,6 +156,15 @@ pub struct StandardListItem { /// hover-revealed controls be reachable right now". reveal: Option>, style_override: Option, + /// The style this row was built with, kept so `layout_response` asks the + /// same style for its height floor that painted its chrome. `None` until + /// the first build. + resolved_style: Option, + /// Whether the row was built with a check box or a leading slot, kept so + /// `layout_response` asks the style for the floor of a decorated row + /// ([`StandardItemStyle::min_height_decorated`](teksilo_core::styles::StandardItemStyle::min_height_decorated)). + /// The build takes both slots, so it records this first. + decorated: bool, root_child_id: Option, /// Optional plain tooltip text shown after a hover delay. Mutually exclusive /// with the rich / composite slots — every setter clears the other two so @@ -195,6 +202,8 @@ impl StandardListItem { interaction: Signal::new(InteractionState::Idle), reveal: None, style_override: None, + resolved_style: None, + decorated: false, root_child_id: None, tooltip_text: None, rich_tooltip_source: None, @@ -526,27 +535,28 @@ struct RowRoles { /// language whose selection is a pale wash (IntUI, Fluent), which is /// also what keeps `emphasised` unbuilt. on_selected: Option, - /// Selected **and** view-focused **and** window-active — the same - /// condition the chrome uses to pick `SurfaceRole::Selected` over - /// `SelectedInactive`. `None` when no style asked for the flip. + /// The style's [`SelectionEmphasis`](teksilo_core::styles::SelectionEmphasis) + /// condition (view-focused and window-active by default, window-active + /// alone for a style that asks for it), the same condition the chrome + /// uses to pick `SurfaceRole::Selected` over `SelectedInactive`. `None` + /// when no style asked for the flip. emphasised: Option>, } impl StandardListItem { /// Per-call override > theme slot > the shipped recipe. - fn resolve_roles(&self, ctx: &mut BuildContext) -> RowRoles { - let style: SharedStandardItemStyle = self - .style_override - .clone() - .or_else(|| ctx.theme().style_slots.standard_item.clone()) - .unwrap_or_else(|| { - Rc::new(crate::styles::RecipeStandardItemStyle::for_tokens( - &ctx.theme().input, - )) - }); + /// + /// Also parks the resolved style on the row, so its height floor comes + /// from the style that built its chrome. + fn resolve_roles(&mut self, ctx: &mut BuildContext) -> RowRoles { + let style: SharedStandardItemStyle = self.style_override.clone().unwrap_or_else(|| { + crate::styles::recipe_standard_item_style::resolve_standard_item_style(ctx) + }); + self.resolved_style = Some(style.clone()); let on_selected = style.selected_label_role(); - let emphasised = - on_selected.map(|_| ctx.view_focus_active().and(&ctx.window_active_signal())); + let emphasis = style.selection_emphasis(); + let emphasised = on_selected + .map(|_| emphasis.emphasised(&ctx.view_focus_active(), &ctx.window_active_signal())); RowRoles { style, on_selected, @@ -592,6 +602,10 @@ impl StandardListItem { fn build_content(&mut self, ctx: &mut BuildContext, roles: &RowRoles) -> WidgetId { use crate::styles::recipe_standard_item_style as si; + // Recorded before the slots are taken below; a rebuild finds them + // gone and keeps what the first build saw. + self.decorated |= self.checkbox.is_some() || self.leading_slot.is_some(); + // A style whose selected row is a *solid* fill (macOS's accent // capsule) cannot recolour the label from `make_body` — the label // is already built by then — so it declares the role instead. @@ -869,22 +883,26 @@ impl Widget for StandardListItem { } fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { - use crate::styles::recipe_standard_item_style as si; - // A row is a target, so its floor follows the density ladder — 28 dp at - // Compact (unchanged: the floor only ever raises), 32 at Comfortable, 44 - // at Touch. `StandardItemRecipe` has carried both projected heights - // since the density sweep and nothing read them: the row measured - // itself against the raw Compact constants, so a Touch build laid out - // 28 dp list rows under a theme that had already decided on 44. - let min_height = teksilo_core::styles::density::dp( - if self.subtitle.is_some() { - si::STANDARD_ITEM_MIN_HEIGHT_TWO_LINE - } else { - si::STANDARD_ITEM_MIN_HEIGHT_SINGLE_LINE - }, - teksilo_tokens::TargetRole::Target, - &ctx.theme.input, - ); + // A row is a target, so its floor follows the density ladder. The + // floor is the active style's: the shipped recipe answers 28 dp at + // Compact, 32 at Comfortable and 44 at Touch, and a preset's recipe + // answers its own number (macOS 24, Fluent 40). Before the first build + // there is no resolved style, and the trait's default ladder stands in. + let input = &ctx.theme.input; + let two_line = self.subtitle.is_some(); + let min_height = match self.resolved_style.as_ref() { + Some(style) if two_line => style.min_height_two_line(input), + Some(style) if self.decorated => style.min_height_decorated(input), + Some(style) => style.min_height_single_line(input), + None => { + let fallback = crate::styles::RecipeStandardItemStyle::for_tokens(input); + if two_line { + fallback.recipe.min_height_two_line + } else { + fallback.recipe.min_height_single_line + } + } + }; let raw = self .root_child_id .and_then(|id| ctx.child_size(id, proposal)) @@ -1641,6 +1659,131 @@ mod tests { assert!(!colors.contains(&on_accent)); } + /// A solid-selection style that follows Qt's per-window colour groups: + /// only the window mutes its selection. + #[derive(Debug, Default, Clone, Copy)] + struct WindowActiveEmphasisStyle; + + impl StandardItemStyle for WindowActiveEmphasisStyle { + fn make_body( + &self, + cfg: &StandardItemStyleConfig, + ctx: &mut teksilo_core::build_context::BuildContext, + ) -> WidgetId { + crate::styles::RecipeStandardItemStyle::for_tokens(&ctx.theme().input) + .make_body(cfg, ctx) + } + + fn selected_label_role(&self) -> Option { + Some(TextRole::OnAccent) + } + + fn selection_emphasis(&self) -> teksilo_core::styles::SelectionEmphasis { + teksilo_core::styles::SelectionEmphasis::WindowActive + } + } + + /// A focus scope that never takes focus, standing in for a data view + /// (a `ListView` opens its scope the same way around its rows). + #[derive(Debug)] + struct UnfocusedView { + row: Option, + child: Option, + } + + impl Widget for UnfocusedView { + fn build(&mut self, ctx: &mut teksilo_core::build_context::BuildContext) -> Vec { + ctx.begin_view_focus(); + let id = ctx.add(self.row.take().expect("built once")); + ctx.end_view_focus(); + self.child = Some(id); + vec![id] + } + + fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { + self.child + .and_then(|id| ctx.child_size(id, proposal)) + .unwrap_or_else(|| proposal.resolve(0.0, 0.0)) + .into() + } + + fn children(&self) -> Vec { + self.child.into_iter().collect() + } + } + + /// The glyph colours of one selected row in a view that does not hold + /// focus, in a window that is active unless `window_active` says + /// otherwise. + fn unfocused_view_row_colors( + style: impl StandardItemStyle, + window_active: bool, + ) -> Vec<[u8; 4]> { + let mut tree = WidgetTree::new() + .with_theme(discriminating_theme()) + .with_text_backend(std::rc::Rc::new(std::cell::RefCell::new( + teksilo_canvas::MockTextBackend::new(), + ))); + tree.add(UnfocusedView { + row: Some( + StandardListItem::new(lit!("Row")) + .selected(Signal::new(true)) + .style(style), + ), + child: None, + }); + tree.layout(SizeProposal::exact(300.0, 40.0)); + if !window_active { + tree.set_window_active(false); + tree.layout(SizeProposal::exact(300.0, 40.0)); + } + glyph_colors(&mut tree) + } + + /// The default emphasis is unchanged: a selected row in a view that does + /// not hold focus is not emphasised, even in an active window. This also + /// proves the fixture's view really is unfocused. + #[test] + fn the_default_emphasis_mutes_a_selected_row_in_an_unfocused_view() { + let t = discriminating_theme(); + let primary = rgba8(t.colors.text_primary); + let on_accent = rgba8(t.colors.text_on_accent); + + let colors = unfocused_view_row_colors(OnAccentSelectionStyle, true); + assert!(colors.contains(&primary), "painted {colors:?}"); + assert!(!colors.contains(&on_accent)); + } + + /// A style returning `SelectionEmphasis::WindowActive` emphasises the same + /// row: the view is unfocused, but its window is active. + #[test] + fn window_active_emphasis_emphasises_a_selected_row_in_an_unfocused_view() { + let on_accent = rgba8(discriminating_theme().colors.text_on_accent); + let colors = unfocused_view_row_colors(WindowActiveEmphasisStyle, true); + assert!(colors.contains(&on_accent), "painted {colors:?}"); + } + + /// …and still mutes it once the window goes inactive. + #[test] + fn window_active_emphasis_mutes_a_selected_row_in_an_inactive_window() { + let t = discriminating_theme(); + let primary = rgba8(t.colors.text_primary); + let on_accent = rgba8(t.colors.text_on_accent); + + let colors = unfocused_view_row_colors(WindowActiveEmphasisStyle, false); + assert!(colors.contains(&primary), "painted {colors:?}"); + assert!(!colors.contains(&on_accent)); + } + + /// A style that does not override the hook keeps the rule it had. + #[test] + fn the_selection_emphasis_hook_defaults_to_view_focus_and_window_active() { + assert_eq!( + OnAccentSelectionStyle.selection_emphasis(), + teksilo_core::styles::SelectionEmphasis::ViewFocusedAndWindowActive + ); + } + #[test] fn list_item_layout_single_line() { let mut tree = WidgetTree::new().with_theme(theme()); @@ -2187,4 +2330,115 @@ mod tests { ); assert!(tree.find_by_label("TreeTip").is_some()); } + + /// A style whose decorated rows are taller than its rows of text. + #[derive(Debug, Default, Clone, Copy)] + struct DecoratedRowStyle; + + impl StandardItemStyle for DecoratedRowStyle { + fn make_body( + &self, + cfg: &StandardItemStyleConfig, + ctx: &mut teksilo_core::build_context::BuildContext, + ) -> WidgetId { + crate::styles::RecipeStandardItemStyle::for_tokens(&ctx.theme().input) + .make_body(cfg, ctx) + } + + fn min_height_single_line(&self, _tokens: &teksilo_tokens::InputTokens) -> f32 { + 31.0 + } + + fn min_height_decorated(&self, _tokens: &teksilo_tokens::InputTokens) -> f32 { + 37.0 + } + + fn min_height_two_line(&self, _tokens: &teksilo_tokens::InputTokens) -> f32 { + 53.0 + } + } + + /// The laid-out height of `row` alone in a tree. + fn row_height(row: impl Widget + 'static) -> f32 { + let mut tree = WidgetTree::new() + .with_theme(theme()) + .with_text_backend(std::rc::Rc::new(std::cell::RefCell::new( + teksilo_canvas::MockTextBackend::new(), + ))); + let id = tree.add(row); + tree.layout(SizeProposal { + width: Some(300.0), + height: None, + }); + tree.bounds(id).height + } + + /// A row with a check box or a leading slot takes the style's decorated + /// floor, a row of text alone the single-line one, and a row with a + /// subtitle the two-line one whatever it carries. A tree row lays out + /// through the list row it wraps, so it follows the same floors. + #[test] + fn a_decorated_row_takes_the_styles_decorated_floor() { + let icon = || crate::primitives::RectWidget::new(); + assert_eq!( + row_height(StandardListItem::new(lit!("Row")).style(DecoratedRowStyle)), + 31.0 + ); + assert_eq!( + row_height( + StandardListItem::new(lit!("Row")) + .checkbox(Signal::new(false)) + .style(DecoratedRowStyle) + ), + 37.0 + ); + assert_eq!( + row_height( + StandardListItem::new(lit!("Row")) + .leading_slot(icon()) + .style(DecoratedRowStyle) + ), + 37.0 + ); + assert_eq!( + row_height( + StandardListItem::new(lit!("Row")) + .leading_slot(icon()) + .subtitle(lit!("Below")) + .style(DecoratedRowStyle) + ), + 53.0 + ); + assert_eq!( + row_height( + StandardTreeItem::new(lit!("Node")) + .leading_slot(icon()) + .style(DecoratedRowStyle) + ), + 37.0 + ); + assert_eq!( + row_height(StandardTreeItem::new(lit!("Node")).style(DecoratedRowStyle)), + 31.0 + ); + } + + /// The hook defaults to the single-line floor, so the shipped recipe + /// lays a decorated row out exactly as a row of text. + #[test] + fn the_decorated_floor_defaults_to_the_single_line_one() { + let recipe = crate::styles::RecipeStandardItemStyle::default(); + let tokens = teksilo_tokens::InputTokens::default(); + assert_eq!( + recipe.min_height_decorated(&tokens), + recipe.min_height_single_line(&tokens) + ); + assert_eq!( + row_height( + StandardListItem::new(lit!("Row")) + .leading_slot(crate::primitives::RectWidget::new()) + ), + row_height(StandardListItem::new(lit!("Row"))), + ); + } } diff --git a/crates/teksilo-widgets/src/styles.rs b/crates/teksilo-widgets/src/styles.rs index 5b890dfd4..bc1de45ac 100644 --- a/crates/teksilo-widgets/src/styles.rs +++ b/crates/teksilo-widgets/src/styles.rs @@ -98,32 +98,38 @@ pub use recipe_tooltip_style::{RecipeTooltipStyle, TooltipRecipe}; use teksilo_core::build_context::BuildContext; use teksilo_core::signal::Signal; +use teksilo_core::styles::PaintState; use teksilo_tokens::ColorTokens; -/// Reactive resolution palette for recipe colours: the live theme palette while -/// the host window is active, the accent-desaturated projection -/// ([`ColorTokens::for_inactive_window`]) while it is inactive. +/// Reactive resolution palette for recipe colours: the palette the paint +/// walker paints with, [`Theme::paint_colors`](teksilo_core::styles::Theme::paint_colors) for the host window's +/// current [`PaintState`] (window activation and the high-contrast +/// preference). /// /// A recipe style that **bakes** a colour into a `ColorProp::Bound(Signal)` / /// `PaintProp` at build time (resolving a `RecipeColor` against the theme) must /// resolve against this signal rather than a plain `ctx.theme_signal()`. A /// `Bound` colour is a concrete value that `ColorProp::resolve` returns verbatim -/// — it ignores the paint walker's window-inactive theme projection — so accent -/// chrome baked from `theme_signal` (a Filled button fill, an accent focus -/// border) would stay vivid in a background window while paint-resolving -/// controls (Toggle, Checkbox, which read `colors.accent` in `paint()`) greyed -/// out. Resolving against this greys them out uniformly. +/// and so ignores the paint walker's theme projection: accent chrome baked from +/// `theme_signal` (a Filled button fill, an accent focus border) would stay +/// vivid in a background window, and keep its normal colours under high +/// contrast, while paint-resolving controls (Toggle, Checkbox, which read +/// `colors.accent` in `paint()`) followed the projection. Resolving against +/// this keeps them in step, including a theme's own +/// [`PaintProjection`](teksilo_core::styles::PaintProjection). /// -/// Role-preserving paint paths — passing a `SurfaceRole` / `BorderRole` / +/// Role-preserving paint paths (passing a `SurfaceRole` / `BorderRole` / /// `Signal` straight to a widget so it resolves against `ctx.theme` at -/// paint — already follow the projection and need no change. +/// paint) already follow the projection and need no change. pub(crate) fn window_resolution_colors(ctx: &BuildContext) -> Signal { let wa = ctx.window_active_signal(); - ctx.theme_signal().zip(&wa).map(|(theme, active)| { - if *active { - theme.colors.clone() - } else { - theme.colors.for_inactive_window() - } - }) + let hc = ctx.high_contrast_signal(); + ctx.theme_signal() + .zip3(&wa, &hc) + .map(|(theme, window_active, high_contrast)| { + theme.paint_colors(PaintState { + window_active: *window_active, + high_contrast: *high_contrast, + }) + }) } diff --git a/crates/teksilo-widgets/src/styles/recipe_avatar_style.rs b/crates/teksilo-widgets/src/styles/recipe_avatar_style.rs index 31220d6ad..b0365778e 100644 --- a/crates/teksilo-widgets/src/styles/recipe_avatar_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_avatar_style.rs @@ -25,6 +25,7 @@ use teksilo_core::signal::Signal; use teksilo_core::styles::density::{dp, spacing}; use teksilo_core::styles::{ AvatarCorner, AvatarPresence, AvatarShape, AvatarSize, AvatarStyle, AvatarStyleConfig, + FocusRingStroke, }; use teksilo_core::widget::{ LayoutContext, LayoutResponse, PaintContext, PendingChild, Widget, WidgetPlacement, @@ -118,15 +119,10 @@ pub fn paint_focus_ring( width: f32, color: Color, ) { - let outset = offset + width / 2.0; - let outer = Rect::new( - bounds.x - outset, - bounds.y - outset, - bounds.width + outset * 2.0, - bounds.height + outset * 2.0, - ); + let ring = focus_ring_stroke(bounds, shape, rounded_radius_ratio, offset, width); match shape { AvatarShape::Circle => { + let outer = ring.rect; let radius = outer.width.min(outer.height) / 2.0; let center = Point::new(outer.x + outer.width / 2.0, outer.y + outer.height / 2.0); canvas.stroke_circle( @@ -136,22 +132,40 @@ pub fn paint_focus_ring( StrokeStyle::solid(width), ); } - AvatarShape::RoundedSquare => { - let r = bounds.width.min(bounds.height) * rounded_radius_ratio + outset; + AvatarShape::RoundedSquare | AvatarShape::Square => { canvas.stroke_rounded_rect( - outer, - CornerRadius::uniform(r), + ring.rect, + CornerRadius::uniform(ring.corner_radius), color, StrokeStyle::solid(width), ); } - AvatarShape::Square => { - canvas.stroke_rounded_rect( - outer, - CornerRadius::uniform(0.0), - color, - StrokeStyle::solid(width), - ); + } +} + +/// The centre line of [`paint_focus_ring`]: `offset + width / 2` outside +/// `bounds`, hugging the avatar's shape. A negative `offset` draws the ring +/// partly inside the avatar; the rectangle and radius never invert. +fn focus_ring_stroke( + bounds: Rect, + shape: AvatarShape, + rounded_radius_ratio: f32, + offset: f32, + width: f32, +) -> FocusRingStroke { + let distance = offset + width / 2.0; + match shape { + // A square ring keeps sharp corners at every distance. + AvatarShape::Square => FocusRingStroke { + corner_radius: 0.0, + ..FocusRingStroke::at_distance(bounds, 0.0, distance) + }, + AvatarShape::RoundedSquare => { + let r = bounds.width.min(bounds.height) * rounded_radius_ratio; + FocusRingStroke::at_distance(bounds, r, distance) + } + AvatarShape::Circle => { + FocusRingStroke::at_distance(bounds, bounds.width.min(bounds.height) / 2.0, distance) } } } @@ -460,3 +474,35 @@ impl Widget for AvatarChromeFrame { self.child_id.into_iter().collect() } } + +#[cfg(test)] +mod focus_ring_tests { + use super::*; + + #[test] + fn a_negative_focus_ring_offset_shrinks_the_ring_without_inverting() { + let bounds = Rect::new(0.0, 0.0, 32.0, 32.0); + for shape in [ + AvatarShape::Circle, + AvatarShape::RoundedSquare, + AvatarShape::Square, + ] { + let outer = focus_ring_stroke(bounds, shape, 0.25, 2.0, 2.0); + let inner = focus_ring_stroke(bounds, shape, 0.25, -1.0, 2.0); + assert!(inner.rect.width < outer.rect.width, "{shape:?}"); + assert!(inner.rect.height < outer.rect.height, "{shape:?}"); + assert!(inner.rect.width >= 0.0 && inner.rect.height >= 0.0); + + let deep = focus_ring_stroke(bounds, shape, 0.25, -40.0, 2.0); + assert!(deep.rect.width >= 0.0 && deep.rect.height >= 0.0); + assert!(deep.corner_radius >= 0.0); + } + } + + #[test] + fn a_square_ring_keeps_sharp_corners() { + let bounds = Rect::new(0.0, 0.0, 32.0, 32.0); + let ring = focus_ring_stroke(bounds, AvatarShape::Square, 0.25, 2.0, 2.0); + assert_eq!(ring.corner_radius, 0.0); + } +} diff --git a/crates/teksilo-widgets/src/styles/recipe_button_style.rs b/crates/teksilo-widgets/src/styles/recipe_button_style.rs index 99b55619a..cac5e1d9f 100644 --- a/crates/teksilo-widgets/src/styles/recipe_button_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_button_style.rs @@ -206,14 +206,11 @@ fn bind_fill( || recipe.disabled.as_ref().is_some_and(is_gradient); if any_gradient { // Gradient fills bake to a non-reactive PaintProp. Resolve against the - // window-active palette at build so a window that *starts* inactive is - // already desaturated; live flips of a gradient fill are a known gap - // (no IntUI / Material accent button uses a gradient fill). - let colors = if ctx.window_active() { - ctx.theme().colors.clone() - } else { - ctx.theme().colors.for_inactive_window() - }; + // paint state at build so a window that *starts* inactive, or under + // high contrast, already paints the projected palette; live flips of + // a gradient fill are a known gap (no IntUI / Material accent button + // uses a gradient fill). + let colors = ctx.theme().paint_colors(ctx.paint_state()); return PaintProp::from_fill(&recipe.idle, &colors); } let recipe = recipe.clone(); diff --git a/crates/teksilo-widgets/src/styles/recipe_scroll_bar_style.rs b/crates/teksilo-widgets/src/styles/recipe_scroll_bar_style.rs index 14453d367..68638d3b8 100644 --- a/crates/teksilo-widgets/src/styles/recipe_scroll_bar_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_scroll_bar_style.rs @@ -90,14 +90,13 @@ fn resolve_thumb_color( pub struct ScrollBarRecipe { pub thickness_idle: f32, pub thickness_hover: f32, - /// The density-resolved floor a thumb may not be shorter than. + /// The density-resolved floor a thumb may not be shorter than. Read + /// through [`ScrollBarStyle::min_thumb_length`]. /// - /// The **widget** decides the number the painters actually use — it resolves - /// the same `dp(SCROLLBAR_MIN_THUMB_LENGTH, Target, ..)` unless the caller - /// named a floor of their own, and passes the answer down as - /// `ScrollBarStyleConfig::min_thumb_length`. This field is the same value, - /// kept here so a custom style building its own painters has it in hand - /// without reaching for the tokens again. + /// The **widget** decides the number the painters actually use: it asks + /// the active style for this floor unless the caller named one of their + /// own, sizes its thumb geometry from the answer, and passes the same + /// answer down as `ScrollBarStyleConfig::min_thumb_length`. pub min_thumb_length: f32, pub corner_radius: f32, } @@ -251,6 +250,15 @@ impl ScrollBarStyle for RecipeScrollBarStyle { } } } + + /// This style's own thumb floor, so a preset's (macOS's 28 dp, Fluent's + /// 30 dp `ScrollBarHorizontalThumbMinWidth`) is the one a bar lays out. + /// `tokens` is unused: the recipe was already resolved against a density + /// when the style was made (`for_tokens`, or a preset's own factory), and + /// a second projection would apply the ladder twice. + fn min_thumb_length(&self, _tokens: &InputTokens) -> f32 { + self.recipe.min_thumb_length + } } /// Paints the thin resting indicator. Used as the only body in `Thin` diff --git a/crates/teksilo-widgets/src/styles/recipe_segmented_control_style.rs b/crates/teksilo-widgets/src/styles/recipe_segmented_control_style.rs index 8f86e6108..4b45d9238 100644 --- a/crates/teksilo-widgets/src/styles/recipe_segmented_control_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_segmented_control_style.rs @@ -19,7 +19,9 @@ use teksilo_core::build_context::BuildContext; use teksilo_core::focus::FocusOrigin; use teksilo_core::signal::Signal; use teksilo_core::styles::density::{dp, spacing}; -use teksilo_core::styles::{SegmentSlots, SegmentedControlStyle, SegmentedControlStyleConfig}; +use teksilo_core::styles::{ + FocusRingStroke, SegmentSlots, SegmentedControlStyle, SegmentedControlStyleConfig, +}; use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget}; use teksilo_core::widget_id::WidgetId; use teksilo_tokens::{CornerRadius, InputTokens, TargetRole}; @@ -248,17 +250,15 @@ impl Widget for SegmentedControlChrome { // envelope. Painted whether or not there are segments, so a // focused empty control still shows where focus is. if keyboard_focused { - let half_stroke = shape.focus_ring_width * 0.5; - let ring_rect = Rect::new( - bounds.x + half_stroke, - bounds.y + half_stroke, - (bounds.width - half_stroke * 2.0).max(0.0), - (bounds.height - half_stroke * 2.0).max(0.0), - ); - let ring_radius = self.recipe.corner_radius + shape.focus_ring_offset + half_stroke; + // Around the visual, not the bounds: under a negative offset + // the ring overlaps the frame, and when the offset is deep + // enough the envelope is zero and the visual fills the bounds. + let envelope = shape.focus_ring_envelope(); + let visual = bounds.inset(envelope, envelope, envelope, envelope); + let ring = FocusRingStroke::around(visual, self.recipe.corner_radius, shape); canvas.stroke_rounded_rect( - ring_rect, - CornerRadius::uniform(ring_radius), + ring.rect, + CornerRadius::uniform(ring.corner_radius), colors.focus_ring, shape.focus_ring_width, ); diff --git a/crates/teksilo-widgets/src/styles/recipe_slider_style.rs b/crates/teksilo-widgets/src/styles/recipe_slider_style.rs index ecb946ea9..acae54c9e 100644 --- a/crates/teksilo-widgets/src/styles/recipe_slider_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_slider_style.rs @@ -21,7 +21,9 @@ use teksilo_core::build_context::BuildContext; use teksilo_core::environment::LayoutDirection; use teksilo_core::focus::FocusOrigin; use teksilo_core::signal::Signal; -use teksilo_core::styles::{SliderOrientation, SliderStyle, SliderStyleConfig, SliderVariant}; +use teksilo_core::styles::{ + FocusRingStroke, SliderOrientation, SliderStyle, SliderStyleConfig, SliderVariant, +}; use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; use teksilo_core::widget_id::WidgetId; use teksilo_tokens::{CornerRadius, InputTokens}; @@ -156,7 +158,7 @@ impl Widget for SliderBody { } fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { - let envelope = ctx.theme.shape.focus_ring_offset + ctx.theme.shape.focus_ring_width; + let envelope = ctx.theme.shape.focus_ring_envelope(); let cross = (self.recipe.thumb_diameter + envelope * 2.0).max(MIN_CROSS_SIZE); match self.orientation { SliderOrientation::Horizontal => { @@ -325,18 +327,10 @@ impl Widget for SliderBody { // Focus ring — keyboard-only, drawn outside the thumb in the // theme-defined gap. if self.focus_origin.get() == Some(FocusOrigin::Keyboard) { - let offset = shape.focus_ring_offset; - let half_stroke = shape.focus_ring_width * 0.5; - let ring_inset = offset + half_stroke; - let ring_rect = Rect::new( - thumb_rect.x - ring_inset, - thumb_rect.y - ring_inset, - thumb_rect.width + ring_inset * 2.0, - thumb_rect.height + ring_inset * 2.0, - ); + let ring = FocusRingStroke::around(thumb_rect, thumb_radius, shape); canvas.stroke_rounded_rect( - ring_rect, - CornerRadius::uniform(thumb_radius + ring_inset), + ring.rect, + CornerRadius::uniform(ring.corner_radius), colors.focus_ring, shape.focus_ring_width, ); diff --git a/crates/teksilo-widgets/src/styles/recipe_standard_item_style.rs b/crates/teksilo-widgets/src/styles/recipe_standard_item_style.rs index ee162600a..e626327bc 100644 --- a/crates/teksilo-widgets/src/styles/recipe_standard_item_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_standard_item_style.rs @@ -17,7 +17,7 @@ use teksilo_core::build_context::BuildContext; use teksilo_core::signal::Signal; -use teksilo_core::styles::{StandardItemStyle, StandardItemStyleConfig}; +use teksilo_core::styles::{SharedStandardItemStyle, StandardItemStyle, StandardItemStyleConfig}; use teksilo_core::widget_id::WidgetId; use teksilo_tokens::{BorderRole, CornerRadius, InputTokens, SurfaceRole, TargetRole}; @@ -152,8 +152,9 @@ impl StandardItemStyle for RecipeStandardItemStyle { &cfg.is_pressed, &cfg.is_hovered, &cfg.is_disabled, - &cfg.is_focused, - &cfg.is_window_active, + &self + .selection_emphasis() + .emphasised(&cfg.is_focused, &cfg.is_window_active), ); // Keyboard-focus ring: drawn on the *current* item (selected, in a @@ -213,6 +214,33 @@ impl StandardItemStyle for RecipeStandardItemStyle { ctx.add(ZStack::new().child(bg_padded).child(content_padded)) } + + /// This recipe's own floor. The recipe was resolved against a density + /// when the style was built, so `tokens` is not consulted again: a preset + /// that wrote its own `min_height_single_line` gets exactly that number. + fn min_height_single_line(&self, _tokens: &InputTokens) -> f32 { + self.recipe.min_height_single_line + } + + /// This recipe's own floor for a two-line row, on the same terms as + /// [`min_height_single_line`](Self::min_height_single_line). + fn min_height_two_line(&self, _tokens: &InputTokens) -> f32 { + self.recipe.min_height_two_line + } +} + +/// The `StandardItemStyle` a row built here renders with when it carries no +/// per-call override: the theme's slot, else the shipped recipe resolved +/// against the theme's density. +pub(crate) fn resolve_standard_item_style(ctx: &BuildContext) -> SharedStandardItemStyle { + ctx.theme() + .style_slots + .standard_item + .clone() + .unwrap_or_else(|| { + std::rc::Rc::new(RecipeStandardItemStyle::for_tokens(&ctx.theme().input)) + as SharedStandardItemStyle + }) } fn bg_signal( @@ -220,15 +248,14 @@ fn bg_signal( is_pressed: &Signal, is_hovered: &Signal, is_disabled: &Signal, - is_focused: &Signal, - is_window_active: &Signal, + emphasised: &Signal, ) -> Signal { - // Effective focus = the view holds keyboard focus AND the host window is - // active. A selected row in an inactive window desaturates exactly as it - // does when focus moves elsewhere in the same window. - let effective_focus = is_focused.and(is_window_active); + // `emphasised` is the style's `SelectionEmphasis` condition; by default + // the view holds keyboard focus AND the host window is active, so a + // selected row in an inactive window desaturates exactly as it does when + // focus moves elsewhere in the same window. let combined = is_selected.zip3(is_pressed, is_hovered); - combined.zip3(is_disabled, &effective_focus).map( + combined.zip3(is_disabled, emphasised).map( |((selected, pressed, hovered), disabled, focused)| { if *disabled { SurfaceRole::Transparent @@ -254,6 +281,7 @@ fn bg_signal( #[cfg(test)] mod tests { use super::*; + use teksilo_core::styles::SelectionEmphasis; use teksilo_tokens::SurfaceRole; #[test] @@ -270,8 +298,7 @@ mod tests { &pressed, &hovered, &disabled, - &focused, - &window_active, + &SelectionEmphasis::default().emphasised(&focused, &window_active), ); // Selected, view-focused, window-active → vivid. @@ -290,4 +317,41 @@ mod tests { focused.set(true); assert_eq!(role.get(), SurfaceRole::Selected); } + + /// The default style keeps the rule it had before the hook existed. + #[test] + fn the_recipe_keeps_the_default_selection_emphasis() { + assert_eq!( + RecipeStandardItemStyle::default().selection_emphasis(), + SelectionEmphasis::ViewFocusedAndWindowActive + ); + } + + /// Under `WindowActive` only the window mutes a selection: a view that + /// loses keyboard focus inside an active window stays vivid. + #[test] + fn window_active_emphasis_ignores_view_focus() { + let selected = Signal::new(true); + let pressed = Signal::new(false); + let hovered = Signal::new(false); + let disabled = Signal::new(false); + let focused = Signal::new(false); + let window_active = Signal::new(true); + + let role = bg_signal( + &selected, + &pressed, + &hovered, + &disabled, + &SelectionEmphasis::WindowActive.emphasised(&focused, &window_active), + ); + + assert_eq!(role.get(), SurfaceRole::Selected); + window_active.set(false); + assert_eq!(role.get(), SurfaceRole::SelectedInactive); + focused.set(true); + assert_eq!(role.get(), SurfaceRole::SelectedInactive); + window_active.set(true); + assert_eq!(role.get(), SurfaceRole::Selected); + } } diff --git a/crates/teksilo-widgets/src/styles/recipe_tab_style.rs b/crates/teksilo-widgets/src/styles/recipe_tab_style.rs index 43a6aae6f..d6a349398 100644 --- a/crates/teksilo-widgets/src/styles/recipe_tab_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_tab_style.rs @@ -32,7 +32,8 @@ use teksilo_core::binding::BindingLevel; use teksilo_core::build_context::BuildContext; use teksilo_core::signal::Signal; use teksilo_core::styles::{ - TabBarChromeConfig, TabBarOrientation, TabIndicatorPosition, TabStyle, TabStyleConfig, + FocusRingStroke, SharedTabStyle, TabBarChromeConfig, TabBarOrientation, TabIndicatorPosition, + TabStyle, TabStyleConfig, }; use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; use teksilo_core::widget_id::WidgetId; @@ -44,7 +45,6 @@ use teksilo_core::styles::density::{dp, spacing}; // IntUI design tokens for Tab. The recipe and parent TabHeader own // their own dimensions. pub const TAB_EDITOR_HEIGHT: f32 = 50.0; -pub const TAB_TOOL_WINDOW_HEIGHT: f32 = 28.0; pub const TAB_PADDING_HORIZONTAL: f32 = 12.0; pub const TAB_UNDERLINE_ACTIVE: f32 = 2.0; pub const TAB_UNDERLINE_HOVER: f32 = 2.0; @@ -58,8 +58,12 @@ const DROP_INDICATOR_WIDTH: f32 = 2.0; /// [`RecipeTabStyle::default()`] reproduces the built-in IntUI look. #[derive(Debug, Clone, Copy, PartialEq)] pub struct TabRecipe { + /// The outer height of one tab header: the strip height of a horizontal + /// bar, the pill height of a vertical one. Read through + /// [`TabStyle::editor_height`]. pub editor_height: f32, - pub tool_window_height: f32, + /// The gap between a tab header's edges and its content along the bar's + /// main axis. Read through [`TabStyle::padding_horizontal`]. pub padding_horizontal: f32, pub underline_active: f32, pub underline_hover: f32, @@ -75,7 +79,6 @@ impl TabRecipe { pub fn for_tokens(tokens: &InputTokens) -> Self { Self { editor_height: dp(TAB_EDITOR_HEIGHT, TargetRole::Target, tokens), - tool_window_height: dp(TAB_TOOL_WINDOW_HEIGHT, TargetRole::Target, tokens), padding_horizontal: spacing(TAB_PADDING_HORIZONTAL, tokens), underline_active: TAB_UNDERLINE_ACTIVE, underline_hover: TAB_UNDERLINE_HOVER, @@ -177,6 +180,58 @@ impl TabStyle for RecipeTabStyle { content: cfg.content, }) } + + /// This style's own tab height, so a preset's header (macOS's 26 dp + /// `NSTabView` strip, Fluent's 32 dp `TabViewItem`) is the one a bar lays + /// out. `tokens` is unused: the recipe was already resolved against a + /// density when the style was made (`for_tokens`, or a preset's own + /// factory), and a second projection would apply the ladder twice. + fn editor_height(&self, _tokens: &InputTokens) -> f32 { + self.recipe.editor_height + } + + /// This style's own header padding. See + /// [`editor_height`](Self::editor_height) for why `tokens` is unused. + fn padding_horizontal(&self, _tokens: &InputTokens) -> f32 { + self.recipe.padding_horizontal + } +} + +/// The `TabStyle` a `TabBar` and its headers build against: the bar's +/// per-call `.style(..)` if it has one, else the theme-wide `style_slots.tab` +/// an app or a preset installed, else this module's default resolved against +/// the active density. +/// +/// Shared by the bar and every header it hosts, so the strip height the bar +/// reserves and the height each header measures come from one style. +pub(crate) fn resolve_tab_style( + style_override: Option<&SharedTabStyle>, + ctx: &BuildContext, +) -> SharedTabStyle { + style_override + .cloned() + .or_else(|| ctx.theme().style_slots.tab.clone()) + .unwrap_or_else(|| { + std::rc::Rc::new(RecipeTabStyle::for_tokens(&ctx.theme().input)) as SharedTabStyle + }) +} + +/// The tab height `style` declares at `tokens`, or the shipped recipe's when +/// no style has been resolved yet (a header measured before its first build). +pub(crate) fn tab_editor_height(style: Option<&SharedTabStyle>, tokens: &InputTokens) -> f32 { + match style { + Some(style) => style.editor_height(tokens), + None => TabRecipe::for_tokens(tokens).editor_height, + } +} + +/// The header padding `style` declares at `tokens`, or the shipped recipe's +/// when no style has been resolved yet. +pub(crate) fn tab_padding_horizontal(style: Option<&SharedTabStyle>, tokens: &InputTokens) -> f32 { + match style { + Some(style) => style.padding_horizontal(tokens), + None => TabRecipe::for_tokens(tokens).padding_horizontal, + } } /// Bar-chrome container produced by [`RecipeTabStyle::make_bar`]. @@ -281,7 +336,7 @@ impl Widget for TabBarChromePainter { // the separator in its own column (the "tab merges into // content pane" effect). let border_width = ctx.theme.shape.border_width; - let envelope = ctx.theme.shape.focus_ring_offset + ctx.theme.shape.focus_ring_width; + let envelope = ctx.theme.shape.focus_ring_envelope(); let separator = match self.orientation { TabBarOrientation::Horizontal => Rect::new( bounds.x, @@ -453,19 +508,11 @@ impl Widget for TabBodyPainter { } // Focus ring — keyboard focus only. Drawn inside `bounds` - // (inset by `focus_ring_width / 2 + focus_ring_offset`) so - // adjacent tabs aren't visually overlapped by the ring. + // (see `focus_ring_rect`) so adjacent tabs aren't visually + // overlapped by the ring. if focused { - let half_stroke = shape.focus_ring_width * 0.5; - let inset = half_stroke + shape.focus_ring_offset; - let ring_rect = Rect::new( - bounds.x + inset, - bounds.y + inset, - (bounds.width - inset * 2.0).max(0.0), - (bounds.height - inset * 2.0).max(0.0), - ); canvas.stroke_rounded_rect( - ring_rect, + focus_ring_rect(bounds, shape), CornerRadius::uniform(shape.radius_control), colors.focus_ring, shape.focus_ring_width, @@ -480,6 +527,19 @@ impl Widget for TabBodyPainter { } } +/// The centre line of a tab's focus ring: `bounds` inset by +/// `focus_ring_width / 2 + focus_ring_offset`. +/// +/// Tabs sit flush against each other, so the ring never leaves `bounds`: +/// a negative `focus_ring_offset` brings it out to the tab's edge and no +/// further (the inset is floored at half the stroke), and the rectangle +/// never inverts on a tab narrower than the inset. +fn focus_ring_rect(bounds: Rect, shape: &teksilo_tokens::ShapeTokens) -> Rect { + let half_stroke = shape.focus_ring_width * 0.5; + let inset = (half_stroke + shape.focus_ring_offset).max(half_stroke); + FocusRingStroke::at_distance(bounds, 0.0, -inset).rect +} + #[cfg(test)] mod tests { use super::*; @@ -560,4 +620,63 @@ mod tests { Rect::new(B.x, B.y, T, B.height) ); } + + fn shape(offset: f32) -> teksilo_tokens::ShapeTokens { + teksilo_tokens::ShapeTokens { + focus_ring_offset: offset, + focus_ring_width: 2.0, + ..teksilo_tokens::ShapeTokens::default() + } + } + + #[test] + fn the_focus_ring_follows_the_offset_inside_the_tab() { + // Offset 2: 3 dp in from the tab edge. + assert_eq!( + focus_ring_rect(B, &shape(2.0)), + Rect::new(13.0, 23.0, 94.0, 34.0) + ); + // Offset 0: flush with the tab edge. + assert_eq!( + focus_ring_rect(B, &shape(0.0)), + Rect::new(11.0, 21.0, 98.0, 38.0) + ); + } + + #[test] + fn a_negative_focus_ring_offset_keeps_the_ring_inside_the_tab() { + for offset in [-1.0, -5.0, -100.0] { + let ring = focus_ring_rect(B, &shape(offset)); + // Outer edge of the 2 dp stroke stays on or inside `bounds`. + assert!( + ring.x - 1.0 >= B.x && ring.y - 1.0 >= B.y, + "{offset}: {ring:?}" + ); + assert!(ring.right() + 1.0 <= B.right(), "{offset}: {ring:?}"); + assert!(ring.bottom() + 1.0 <= B.bottom(), "{offset}: {ring:?}"); + } + } + + /// The clamp is deliberate: below zero the offset no longer moves a + /// tab's ring, which stays flush with the tab edge rather than spilling + /// into the neighbouring tab. + #[test] + fn every_offset_at_or_below_zero_draws_the_same_tab_ring() { + let flush = focus_ring_rect(B, &shape(0.0)); + for offset in [-1.0, -5.0, -100.0] { + assert_eq!(focus_ring_rect(B, &shape(offset)), flush, "{offset}"); + } + } + + #[test] + fn the_focus_ring_never_inverts_on_a_narrow_tab() { + let narrow = Rect::new(0.0, 0.0, 4.0, 3.0); + for offset in [-1.0, 2.0, 50.0] { + let ring = focus_ring_rect(narrow, &shape(offset)); + assert!( + ring.width >= 0.0 && ring.height >= 0.0, + "{offset}: {ring:?}" + ); + } + } } diff --git a/crates/teksilo-widgets/src/styles/recipe_table_style.rs b/crates/teksilo-widgets/src/styles/recipe_table_style.rs index dd03dda57..c3f000a2b 100644 --- a/crates/teksilo-widgets/src/styles/recipe_table_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_table_style.rs @@ -274,15 +274,32 @@ impl TableStyle for RecipeTableStyle { fn cell_padding_vertical(&self, _tokens: &InputTokens) -> f32 { self.recipe.cell_padding_vertical } + + /// This style's own body row height, so a preset's row (macOS's 24 dp + /// `NSTableView` row, Fluent's 40 dp `ListViewItem`) is the one a + /// uniform-height table lays out. See + /// [`cell_padding_horizontal`](Self::cell_padding_horizontal) for why + /// `tokens` is unused. + fn row_height(&self, _tokens: &InputTokens) -> f32 { + self.recipe.row_height + } + + /// This style's own column header height. See + /// [`row_height`](Self::row_height). + fn header_height(&self, _tokens: &InputTokens) -> f32 { + self.recipe.header_height + } } /// The `TableStyle` a `TableView` / `TreeTableView` builds against: the /// theme-wide `style_slots.table` if an app or a preset installed one, else /// this module's default resolved against the active density. /// -/// Shared by the three sites that need it — the header cell's chrome and its -/// gutter, and the two views' filter-zone arithmetic — so the gutter the cell -/// pads by and the gutter the hit zone is measured from cannot disagree. +/// Shared by every site that needs it (the header cell's chrome and its +/// gutter, the two views' filter-zone arithmetic, and the row and header +/// heights the views and a standalone `TableHeader` lay out), so the gutter +/// the cell pads by and the gutter the hit zone is measured from cannot +/// disagree. pub fn resolve_table_style(ctx: &BuildContext) -> SharedTableStyle { ctx.theme().style_slots.table.clone().unwrap_or_else(|| { std::rc::Rc::new(RecipeTableStyle::for_tokens(&ctx.theme().input)) as SharedTableStyle diff --git a/crates/teksilo-widgets/src/styles/recipe_toggle_style.rs b/crates/teksilo-widgets/src/styles/recipe_toggle_style.rs index ddcfa90b3..e163efa8a 100644 --- a/crates/teksilo-widgets/src/styles/recipe_toggle_style.rs +++ b/crates/teksilo-widgets/src/styles/recipe_toggle_style.rs @@ -25,10 +25,10 @@ use teksilo_core::build_context::BuildContext; use teksilo_core::focus::FocusOrigin; use teksilo_core::signal::Signal; use teksilo_core::styles::density::spacing; -use teksilo_core::styles::{ToggleStyle, ToggleStyleConfig, ToggleVariant}; +use teksilo_core::styles::{FocusRingStroke, ToggleStyle, ToggleStyleConfig, ToggleVariant}; use teksilo_core::widget::{LayoutContext, LayoutResponse, PaintContext, Widget, WidgetPlacement}; use teksilo_core::widget_id::WidgetId; -use teksilo_tokens::{Color, CornerRadius, InputTokens}; +use teksilo_tokens::{Color, CornerRadius, InputTokens, ShapeTokens}; // IntUI design tokens for the Toggle chrome — the `Default` source for // [`ToggleRecipe`]. Custom design languages either construct a @@ -234,23 +234,10 @@ impl Widget for ToggleBody { // Focus ring — keyboard-only, drawn outside the track in the // theme-defined gap. if self.focus_origin.get() == Some(FocusOrigin::Keyboard) { - let offset = ctx.theme.shape.focus_ring_offset + ctx.theme.shape.focus_ring_width / 2.0; - let ring_rect = Rect::new( - track_rect.x - offset, - track_rect.y - offset, - track_rect.width + offset * 2.0, - track_rect.height + offset * 2.0, - ); - let ring_corner = match self.variant { - ToggleVariant::Switch | ToggleVariant::Pill => { - CornerRadius::uniform(track_h / 2.0 + offset) - } - ToggleVariant::Square => CornerRadius::uniform(offset), - ToggleVariant::Inset => CornerRadius::uniform(4.0 + offset), - }; + let ring = focus_ring_stroke(self.variant, track_rect, &ctx.theme.shape); canvas.stroke_rounded_rect( - ring_rect, - ring_corner, + ring.rect, + CornerRadius::uniform(ring.corner_radius), colors.focus_ring, ctx.theme.shape.focus_ring_width, ); @@ -283,3 +270,57 @@ impl Widget for ToggleBody { // the Toggle node directly. } } + +/// The keyboard focus ring's centre line around the track, hugging the +/// variant's corner shape. A negative `focus_ring_offset` draws it partly +/// inside the track. +fn focus_ring_stroke( + variant: ToggleVariant, + track_rect: Rect, + shape: &ShapeTokens, +) -> FocusRingStroke { + let track_radius = match variant { + ToggleVariant::Switch | ToggleVariant::Pill => track_rect.height / 2.0, + ToggleVariant::Square => 0.0, + ToggleVariant::Inset => 4.0, + }; + FocusRingStroke::around(track_rect, track_radius, shape) +} + +#[cfg(test)] +mod focus_ring_tests { + use super::*; + + fn shape(offset: f32) -> ShapeTokens { + ShapeTokens { + focus_ring_offset: offset, + focus_ring_width: 2.0, + ..ShapeTokens::default() + } + } + + #[test] + fn a_negative_focus_ring_offset_shrinks_the_ring_without_inverting() { + let track = Rect::new(0.0, 0.0, 40.0, 20.0); + for variant in [ + ToggleVariant::Switch, + ToggleVariant::Pill, + ToggleVariant::Square, + ToggleVariant::Inset, + ] { + let outer = focus_ring_stroke(variant, track, &shape(2.0)); + let inner = focus_ring_stroke(variant, track, &shape(-1.0)); + assert!(inner.rect.width < outer.rect.width, "{variant:?}"); + assert!(inner.rect.height < outer.rect.height, "{variant:?}"); + assert!(inner.rect.width >= 0.0 && inner.rect.height >= 0.0); + // The square variant's ring at offset -1 is centred on the + // track edge, so its corners stay sharp instead of going + // negative. + assert!(inner.corner_radius >= 0.0, "{variant:?}"); + + let deep = focus_ring_stroke(variant, track, &shape(-30.0)); + assert!(deep.rect.width >= 0.0 && deep.rect.height >= 0.0); + assert!(deep.corner_radius >= 0.0); + } + } +} diff --git a/crates/teksilo-widgets/src/tab_widget.rs b/crates/teksilo-widgets/src/tab_widget.rs index a3c366b81..c5d5e7908 100644 --- a/crates/teksilo-widgets/src/tab_widget.rs +++ b/crates/teksilo-widgets/src/tab_widget.rs @@ -247,7 +247,7 @@ pub struct TabWidget { // Bar configuration — forwarded to the inner TabBar. /// Optional tab-strip height override (the strip's cross-axis extent). - /// `None` keeps the style's `editor_tab_height`. Set via + /// `None` keeps the style's `editor_height`. Set via /// [`Self::tab_bar_height`] / [`Self::compact_bar`]. tab_bar_height: Option, /// Reactive sizing strategy. `None` until `.tab_sizing(...)` @@ -444,7 +444,7 @@ impl TabWidget { } /// Override the tab-strip height (its cross-axis extent). `None` / - /// unset keeps the style's `editor_tab_height` (50 dp). Use for a denser + /// unset keeps the style's `editor_height` (50 dp under Int UI). Use for a denser /// strip — e.g. dock side panels. pub fn tab_bar_height(mut self, dp: f32) -> Self { self.tab_bar_height = Some(dp.max(0.0)); diff --git a/crates/teksilo-widgets/src/tab_widget/bar.rs b/crates/teksilo-widgets/src/tab_widget/bar.rs index 3712a951e..d1ea3d928 100644 --- a/crates/teksilo-widgets/src/tab_widget/bar.rs +++ b/crates/teksilo-widgets/src/tab_widget/bar.rs @@ -663,7 +663,7 @@ impl TabBar { /// cross-axis width, so the same knob defines the bar's minimum /// width — the sidebar adapts to the widest piece of bar content /// (tab labels or a slot widget) and never shrinks below this floor. - /// Vertical pill heights stay at the tab style's `editor_tab_height` + /// Vertical pill heights stay at the tab style's `editor_height` /// regardless of this knob. /// /// Under [`TabSizing::Fill`] a **vertical** bar takes the width it is @@ -677,7 +677,7 @@ impl TabBar { /// Override the tab-strip cross-axis extent (the strip height for a /// horizontal bar; the per-tab pill height for a vertical one). `None` - /// keeps the style's `editor_tab_height`. Use for a compact bar. + /// keeps the style's `editor_height`. Use for a compact bar. pub fn tab_bar_height(mut self, dp: f32) -> Self { self.tab_height = Some(dp.max(0.0)); self @@ -1539,16 +1539,24 @@ impl Widget for TabBar { // headers reserve. Snapshot the theme values up front — we // don't want to hold a borrow on `ctx` while later code // mutates the arena. + // + // The active `TabStyle`: per-call override > theme slot > + // built-in `RecipeTabStyle`. Resolved here, once, because the + // strip height below, the header row's per-tab extent and the + // bar chrome further down all come from it; each header resolves + // the same style from the same override and theme. + let tab_style = + crate::styles::recipe_tab_style::resolve_tab_style(self.style_override.as_ref(), ctx); let (header_min_height, motion_duration_normal, motion_easing_standard) = { let theme = ctx.theme(); - // `editor_tab_height` is the outer bounds height of a - // tab header — the focus-ring envelope is reserved - // inside (see `TabHeader::intrinsic_height`), so the - // bar's preferred row height is exactly the token (or the - // `tab_bar_height` override for a compact strip). + // The style's `editor_height` is the outer bounds height of + // a tab header (the focus-ring envelope is reserved inside, + // see `TabHeader::intrinsic_height`), so the bar's preferred + // row height is exactly the style's (or the `tab_bar_height` + // override for a compact strip). ( self.tab_height - .unwrap_or(crate::styles::recipe_tab_style::TAB_EDITOR_HEIGHT), + .unwrap_or_else(|| tab_style.editor_height(&theme.input)), theme.motion.duration_normal, theme.motion.easing_standard, ) @@ -1579,6 +1587,7 @@ impl Widget for TabBar { max_extent: self.max_tab_width, spacing: self.spacing, tab_height: self.tab_height, + style: RowTabStyle(tab_style.clone()), header_bounds_buf: header_bounds_buf.clone(), row_bounds_buf: row_bounds_buf.clone(), divider: divider_prop.clone().map(|c| (c, self.spacing)), @@ -1801,20 +1810,11 @@ impl Widget for TabBar { } }; self.outer_stack_id = Some(root_id); - // Resolve the active `TabStyle` and let it wrap the bar + // Let the active `TabStyle` (resolved above) wrap the bar // content with the strip chrome — backdrop fill, content-pane - // separator, drag-reorder drop indicator. Per-call override > - // theme slot > built-in `RecipeTabStyle`. This replaces the + // separator, drag-reorder drop indicator. This replaces the // old `TabBar::paint`: the bar is now pure composition. - let style: teksilo_core::styles::SharedTabStyle = self - .style_override - .clone() - .or_else(|| ctx.theme().style_slots.tab.clone()) - .unwrap_or_else(|| { - Rc::new(crate::styles::RecipeTabStyle::for_tokens( - &ctx.theme().input, - )) - }); + let style = tab_style; let chrome_cfg = teksilo_core::styles::TabBarChromeConfig { content: root_id, orientation: self.orientation.into(), @@ -2224,6 +2224,17 @@ impl Widget for TabBar { // ─── Internal: the headers row / column ───────────────────────────── +/// The bar's resolved `TabStyle`, held by [`TabHeaderRow`]. A newtype only +/// so the row can keep deriving `Debug`, which `dyn TabStyle` does not +/// implement. +struct RowTabStyle(teksilo_core::styles::SharedTabStyle); + +impl std::fmt::Debug for RowTabStyle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("RowTabStyle(..)") + } +} + /// The headers run — a horizontal row in [`TabBarOrientation::Horizontal`] /// mode, a vertical column in [`TabBarOrientation::Vertical`] mode. /// Owns the `Shared`/`Independent` sizing math and exposes per-tab @@ -2236,15 +2247,18 @@ struct TabHeaderRow { /// Min extent, in whichever sense the axis gives it: the per-tab /// width for a horizontal bar, and the *bar's own* minimum width /// for a vertical one (vertical pill extents come from the style's - /// `editor_tab_height`, never from this knob). Reuses the bar's + /// `editor_height`, never from this knob). Reuses the bar's /// `min_tab_width` in both cases. min_extent: f32, max_extent: f32, spacing: f32, /// Optional per-tab extent override along the bar's cross axis (the tab /// strip height for a horizontal bar; the per-tab pill height for a - /// vertical one). `None` → the style's `editor_tab_height`. + /// vertical one). `None` → the style's `editor_height`. tab_height: Option, + /// The bar's resolved `TabStyle`, asked for the per-tab extent when + /// `tab_height` is `None`. + style: RowTabStyle, /// Per-tab bounds in world coords, populated by `place_children`. /// Shared with the bar's drop handlers via `Rc>`; /// the bar reads this to compute drop-insertion position for an @@ -2268,10 +2282,10 @@ struct TabHeaderRow { impl TabHeaderRow { /// The per-tab cross-axis extent: the explicit override (compact bars) or - /// the style's `editor_tab_height`. + /// the style's `editor_height`. fn tab_extent(&self, ctx: &LayoutContext) -> f32 { self.tab_height - .unwrap_or_else(|| TabHeader::intrinsic_height(ctx)) + .unwrap_or_else(|| self.style.0.editor_height(&ctx.theme.input)) } fn compute_extents(&self, viewport_main: Option, ctx: &LayoutContext) -> Vec { @@ -2306,7 +2320,7 @@ impl TabHeaderRow { // tab bands, which neither Firefox / Chrome // (no native vertical mode) nor VS Code / // IntelliJ do. Use the intrinsic per-tab - // height (`editor_tab_height`) so vertical + // height (`editor_height`) so vertical // tabs match horizontal tabs in size. `Fill` // is no different here: in a vertical bar it // stretches the pill *width* (see @@ -2451,7 +2465,7 @@ impl Widget for TabHeaderRow { match self.axis { TabBarOrientation::Horizontal => { // Cap the row's height at one tab header's intrinsic - // height (= `editor_tab_height`). If the surrounding + // height (= `editor_height`). If the surrounding // outer HStack proposes a taller height because a // sibling (toolbar button, dropdown trigger) wants // more room, the row should NOT stretch — it would diff --git a/crates/teksilo-widgets/src/tab_widget/delegate.rs b/crates/teksilo-widgets/src/tab_widget/delegate.rs index 8a452ec76..7b468c443 100644 --- a/crates/teksilo-widgets/src/tab_widget/delegate.rs +++ b/crates/teksilo-widgets/src/tab_widget/delegate.rs @@ -103,7 +103,7 @@ pub enum TabSizing { /// - **Vertical:** every pill takes the bar's full proposed width /// (the widest-label clamp is bypassed), so the tabs span the /// sidebar. Pill height is unchanged (the intrinsic - /// `editor_tab_height`, or the `tab_bar_height` override). + /// `editor_height`, or the `tab_bar_height` override). /// /// With no width proposed at all (an unbounded measure — a /// `Center`, an `HStack` asking for the natural size), there is diff --git a/crates/teksilo-widgets/src/tab_widget/header.rs b/crates/teksilo-widgets/src/tab_widget/header.rs index 14c09ac14..443b3c5fb 100644 --- a/crates/teksilo-widgets/src/tab_widget/header.rs +++ b/crates/teksilo-widgets/src/tab_widget/header.rs @@ -12,10 +12,14 @@ //! row forces a width (Shared-sizing path). When the parent leaves //! width unspecified, the header reports its natural content width //! clamped to `[min_tab_width, max_tab_width]` (Independent path). -//! - Height is the tab style's `editor_tab_height` — the **outer** -//! measurement, with the focus-ring envelope reserved *inside* it -//! rather than added on top — and the row's place_children -//! uniforms the height across all headers. +//! - Height is the tab style's +//! [`editor_height`](teksilo_core::styles::TabStyle::editor_height), the +//! **outer** measurement, with the focus-ring envelope reserved *inside* +//! it rather than added on top, and the row's place_children uniforms +//! the height across all headers. The content sits +//! [`padding_horizontal`](teksilo_core::styles::TabStyle::padding_horizontal) +//! in from both main-axis edges. Both come from the style the header +//! built its chrome with. //! //! Paint contract (Int UI / IntelliJ): //! - Selected: `text_primary` label, 2 dp accent bar at the top @@ -259,6 +263,10 @@ pub(crate) struct TabHeader { /// `.style(impl TabStyle)` builder. `None` means "use the theme /// slot or the bundled `RecipeTabStyle`". pub(crate) style_override: Option, + /// The style this header was built with, kept so `layout_response` asks + /// the same style for its height and padding that built its chrome. + /// `None` until the first build, when the shipped recipe stands in. + resolved_style: Option, inner_root_id: Option, } @@ -359,6 +367,7 @@ impl TabHeader { idle_text_role: cfg.idle_text_role, active_indicator: cfg.active_indicator, style_override: cfg.style_override, + resolved_style: None, inner_root_id: None, } } @@ -378,7 +387,10 @@ impl TabHeader { } fn estimate_natural_width(&self, ctx: &LayoutContext) -> f32 { - let pad_h = crate::styles::recipe_tab_style::TAB_PADDING_HORIZONTAL; + let pad_h = crate::styles::recipe_tab_style::tab_padding_horizontal( + self.resolved_style.as_ref(), + &ctx.theme.input, + ); use crate::styles::recipe_button_style as btn; // Icon-only: a square-ish tab — the glyph + horizontal padding only (no @@ -446,15 +458,16 @@ impl TabHeader { content.max(natural_min_width(&ctx.theme.input)) } - pub(crate) fn intrinsic_height(_ctx: &LayoutContext) -> f32 { - // `editor_tab_height` is the **outer** measurement (the + fn intrinsic_height(&self, ctx: &LayoutContext) -> f32 { + // The style's `editor_height` is the **outer** measurement (the // total bounds height of one tab header). The focus-ring - // envelope is reserved *inside* this — not added on top — - // so the tab strip sits at exactly the token's value - // regardless of focus-ring tokens. The visible pill rect - // inside the bounds is `editor_tab_height - envelope*2`, - // which `place_children` and `paint` shrink to. - crate::styles::recipe_tab_style::TAB_EDITOR_HEIGHT + // envelope is reserved *inside* this, not added on top, so the + // tab strip sits at exactly the style's value regardless of + // focus-ring tokens. + crate::styles::recipe_tab_style::tab_editor_height( + self.resolved_style.as_ref(), + &ctx.theme.input, + ) } } @@ -645,7 +658,14 @@ impl Widget for TabHeader { // The tab sizes to `icon + 2·pad_h`, so the lone icon fills the padded // box exactly and the symmetric padding centres it (no extra `Center` // wrapper — that layer broke the icon's paint). - let pad_h = crate::styles::recipe_tab_style::TAB_PADDING_HORIZONTAL; + // Resolve the active style: per-call override > theme slot > + // built-in `RecipeTabStyle` default. Resolved before the padding, + // which is the style's, and parked so `layout_response` measures + // the header with the style that builds its chrome below. + let style: SharedTabStyle = + crate::styles::recipe_tab_style::resolve_tab_style(self.style_override.as_ref(), ctx); + self.resolved_style = Some(style.clone()); + let pad_h = style.padding_horizontal(&ctx.theme().input); let inner_id = if self.pinned { let centered = crate::primitives::Center::new().child(row); let padded = crate::Padding::symmetric(header_padding_v(&ctx.theme().input), 0.0) @@ -683,21 +703,10 @@ impl Widget for TabHeader { } }; - // Resolve the active style: per-call override > theme slot > - // built-in `RecipeTabStyle` default. The style wraps `inner_id` - // with the accent indicator + focus ring chrome; the - // per-state tab background (which the trait config doesn't - // carry through) stays as a RectWidget sibling underneath. - let style: SharedTabStyle = self - .style_override - .clone() - .or_else(|| ctx.theme().style_slots.tab.clone()) - .unwrap_or_else(|| { - Rc::new(crate::styles::RecipeTabStyle::for_tokens( - &ctx.theme().input, - )) - }); - + // The style resolved above wraps `inner_id` with the accent + // indicator + focus ring chrome; the per-state tab background + // (which the trait config doesn't carry through) stays as a + // RectWidget sibling underneath. let cfg = TabStyleConfig { label: inner_id, leading: None, @@ -1151,7 +1160,7 @@ impl Widget for TabHeader { fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse { let height = proposal .height - .unwrap_or_else(|| Self::intrinsic_height(ctx)); + .unwrap_or_else(|| self.intrinsic_height(ctx)); // Pinned tabs are always rendered at exactly `min_width` // (the bar's `pinned_tab_width`), regardless of any width // proposal — they're icon-only squares and shouldn't stretch. diff --git a/crates/teksilo-widgets/src/tab_widget/tests.rs b/crates/teksilo-widgets/src/tab_widget/tests.rs index fa02b23e4..769ff07fd 100644 --- a/crates/teksilo-widgets/src/tab_widget/tests.rs +++ b/crates/teksilo-widgets/src/tab_widget/tests.rs @@ -1960,12 +1960,12 @@ fn vertical_shared_sizing_uses_intrinsic_pill_height() { ); } // Vertical Shared does NOT divide the viewport — sidebar pills - // stay at the intrinsic per-tab height (`editor_tab_height`) + // stay at the intrinsic per-tab height (`editor_height`) // regardless of bar height. A 800 dp bar with 4 tabs gives 4 // pills of ~50 dp each, not 4 × ~200 dp bands. assert!( (target - intrinsic).abs() < 0.5, - "expected ~{intrinsic} dp per tab (editor_tab_height), got {target}" + "expected ~{intrinsic} dp per tab (editor_height), got {target}" ); } @@ -3309,3 +3309,205 @@ fn the_content_of_the_tab_on_show_reaches_a_screen_reader() { "the button inside it: {found:?}" ); } + +// ─── The tab style's height and padding reach the layout ───────────── + +/// A tab style declaring a 37 dp tab and a 30 dp header padding, numbers no +/// shipped constant carries, so a header that still read a constant would be +/// told apart. +fn tall_padded_tab_style() -> crate::styles::RecipeTabStyle { + crate::styles::RecipeTabStyle::new(crate::styles::TabRecipe { + editor_height: 37.0, + padding_horizontal: 30.0, + ..crate::styles::TabRecipe::default() + }) +} + +fn two_tab_model() -> ListModel { + ListModel::from_vec(vec![ + TabHandle::dynamic( + TabId::fresh(), + "doc", + TabInfo::new().title(label("Documents")), + (), + ), + TabHandle::dynamic( + TabId::fresh(), + "doc", + TabInfo::new().title(label("Downloads")), + (), + ), + ]) +} + +fn title_delegate() -> TabDelegate { + TabDelegate::new(|_, h: &TabHandle| h.info.title.clone().unwrap_or_else(|| label(""))) +} + +/// `(bar height, per-header (width, height))` of a horizontal, independently +/// sized two-tab bar laid out at its natural height under `theme`, with +/// `style` as its per-call style when given. +fn horizontal_bar_geometry( + theme: teksilo_core::styles::Theme, + style: Option, + tab_bar_height: Option, +) -> (f32, Vec<(f32, f32)>) { + let selected: Signal> = Signal::new(None); + let mut bar = TabBar::horizontal(two_tab_model(), title_delegate(), selected, |_, h| h.id) + .tab_sizing(TabSizing::Independent) + .tab_spacing(0.0) + .show_scroll_arrows(false) + .show_overflow_dropdown(false); + if let Some(style) = style { + bar = bar.style(style); + } + if let Some(h) = tab_bar_height { + bar = bar.tab_bar_height(h); + } + let mut tree = WidgetTree::new().with_theme(theme); + let bar_id = tree.add(bar); + tree.layout(SizeProposal { + width: Some(600.0), + height: None, + }); + let row = data_source_header_row(&tree, bar_id); + let headers = tree + .children(row) + .into_iter() + .map(|h| { + let b = tree.bounds(h); + (b.width, b.height) + }) + .collect(); + (tree.bounds(bar_id).height, headers) +} + +/// A bar with no tab height of its own lays out the height its style +/// declares, and each header pads its content by the style's padding: a +/// "Documents" header measures 9 × 8 dp of mock text, one 6 dp gap before the +/// trailing spacer and twice the padding. +#[test] +fn a_tab_style_sets_the_strip_height_and_the_header_padding() { + let theme = teksilo_core::presets::intui::light(); + + let (bar_h, headers) = horizontal_bar_geometry(theme.clone(), None, None); + assert!( + (bar_h - 50.0).abs() < 0.5, + "Int UI strip is 50 dp, got {bar_h}" + ); + for (w, h) in &headers { + assert!((h - 50.0).abs() < 0.5, "Int UI tab is 50 dp, got {h}"); + assert!((w - (72.0 + 6.0 + 24.0)).abs() < 0.5, "got {w}"); + } + + let (bar_h, headers) = horizontal_bar_geometry(theme, Some(tall_padded_tab_style()), None); + assert!((bar_h - 37.0).abs() < 0.5, "the style's strip, got {bar_h}"); + assert_eq!(headers.len(), 2); + for (w, h) in &headers { + assert!((h - 37.0).abs() < 0.5, "the style's tab height, got {h}"); + assert!( + (w - (72.0 + 6.0 + 60.0)).abs() < 0.5, + "the style's padding, got {w}" + ); + } +} + +/// `tab_bar_height(..)` is the app's, and it still wins over the style's. +#[test] +fn a_bars_own_tab_height_wins_over_its_styles() { + let (bar_h, headers) = horizontal_bar_geometry( + teksilo_core::presets::intui::light(), + Some(tall_padded_tab_style()), + Some(44.0), + ); + assert!((bar_h - 44.0).abs() < 0.5, "got {bar_h}"); + for (_, h) in &headers { + assert!((h - 44.0).abs() < 0.5, "got {h}"); + } +} + +/// A vertical bar's pills take the style's tab height too. +#[test] +fn a_vertical_bars_pills_take_the_styles_tab_height() { + let selected: Signal> = Signal::new(None); + let mut tree = WidgetTree::new().with_theme(teksilo_core::presets::intui::light()); + let bar_id = tree.add( + TabBar::vertical(two_tab_model(), title_delegate(), selected, |_, h| h.id) + .tab_sizing(TabSizing::Shared) + .tab_spacing(0.0) + .show_scroll_arrows(false) + .show_overflow_dropdown(false) + .style(tall_padded_tab_style()), + ); + tree.layout(SizeProposal::exact(200.0, 800.0)); + let row = data_source_header_row(&tree, bar_id); + let heights: Vec = tree + .children(row) + .iter() + .map(|&h| tree.bounds(h).height) + .collect(); + assert_eq!(heights.len(), 2); + for h in &heights { + assert!((h - 37.0).abs() < 0.5, "got {heights:?}"); + } +} + +/// A `TabWidget` reaches its style through the theme slot: the bar it hosts +/// lays out the slot's tab height. +#[test] +fn a_tab_widget_takes_its_tab_height_from_the_theme_slot() { + let mut theme = teksilo_core::presets::intui::light(); + theme.style_slots.tab = Some(Rc::new(tall_padded_tab_style())); + let selected: Signal> = Signal::new(None); + let mut tree = WidgetTree::new().with_theme(theme); + let widget_id = tree.add( + TabWidget::new(selected) + .static_tab(TabInfo::new().title(label("A")), FixedLeaf(120.0, 48.0)) + .static_tab(TabInfo::new().title(label("B")), FixedLeaf(120.0, 48.0)) + .show_scroll_arrows(false) + .show_overflow_dropdown(false), + ); + tree.layout(SizeProposal::exact(800.0, 600.0)); + + fn tab_heights(tree: &WidgetTree, id: WidgetId, out: &mut Vec) { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some("TabHeader")) + { + out.push(tree.bounds(id).height); + } + for c in tree.children(id) { + tab_heights(tree, c, out); + } + } + let mut heights = Vec::new(); + tab_heights(&tree, widget_id, &mut heights); + assert_eq!(heights.len(), 2, "two tab headers"); + for h in &heights { + assert!((h - 37.0).abs() < 0.5, "got {heights:?}"); + } +} + +/// The shipped recipe's header padding follows the density ladder (12 / 13.8 +/// / 15.6 dp), and so does the inner gap, while the 50 dp tab clears every +/// target already: a "Documents" header measures `72 + gap + 2 · padding`. +#[test] +fn the_int_ui_header_padding_follows_the_density_ladder() { + use teksilo_tokens::TargetDensity; + let expected = [ + (TargetDensity::Compact, 72.0 + 6.0 + 24.0), + (TargetDensity::Comfortable, 72.0 + 6.9 + 27.6), + (TargetDensity::Touch, 72.0 + 7.8 + 31.2), + ]; + for (density, width) in expected { + let theme = teksilo_core::presets::intui::light().with_density(density); + let (bar_h, headers) = horizontal_bar_geometry(theme, None, None); + assert!((bar_h - 50.0).abs() < 0.5, "{density:?}: got {bar_h}"); + for (w, _) in &headers { + assert!( + (w - width).abs() < 0.01, + "{density:?}: got {w}, want {width}" + ); + } + } +} diff --git a/crates/teksilo-widgets/src/table_view.rs b/crates/teksilo-widgets/src/table_view.rs index 817a616d8..d0e33fb86 100644 --- a/crates/teksilo-widgets/src/table_view.rs +++ b/crates/teksilo-widgets/src/table_view.rs @@ -303,6 +303,11 @@ pub struct TableView { /// Row geometry — shared with `BodyPane` and the keyboard handler. row_metrics: SharedRowMetrics, header_height: Option, + /// The active `TableStyle`'s row and header heights at the density + /// in force, read at build by `adopt_style_heights`. Until then they + /// hold IntUI's Compact values, the module constants. + style_row_height: f32, + style_header_height: f32, show_header: bool, selection_mode: TableSelectionMode, /// Row selection — index-based `SelectionModel` or keyed @@ -646,6 +651,8 @@ impl TableView { height_source: HeightSource::Uniform, row_metrics: Rc::new(RefCell::new(RowMetrics::uniform(cp::ROW_HEIGHT, 0.0))), header_height: None, + style_row_height: cp::ROW_HEIGHT, + style_header_height: cp::HEADER_HEIGHT, show_header: true, selection_mode: TableSelectionMode::default(), row_selection: None, @@ -802,8 +809,30 @@ impl TableView { .make_metrics(self.effective_row_height(), 0.0); } - /// Fixed row height (default: the table style's 28 px) — the - /// uniform fast path. Mutually exclusive with + /// Read the active `TableStyle`'s row and header heights at the density + /// in force. Called first thing in `build`, so every height the build and + /// the later layout passes read is the style's, unless the app set its + /// own through [`row_height`](Self::row_height) / + /// [`header_height`](Self::header_height), which win. A uniform-height + /// view with no row height of its own re-materializes its metrics when + /// the style's row height differs from the one they were made with. + fn adopt_style_heights(&mut self, ctx: &BuildContext) { + let style = crate::styles::recipe_table_style::resolve_table_style(ctx); + let tokens = &ctx.theme().input; + self.style_header_height = style.header_height(tokens); + let row = style.row_height(tokens); + if row != self.style_row_height { + self.style_row_height = row; + if self.row_height.is_none() && matches!(self.height_source, HeightSource::Uniform) { + self.remake_metrics(); + } + } + } + + /// Fixed row height, the uniform fast path. Default: the active + /// `TableStyle`'s + /// [`row_height`](teksilo_core::styles::TableStyle::row_height), 28 px + /// under IntUI at the Compact density. Mutually exclusive with /// [`row_height_fn`](Self::row_height_fn) and /// [`auto_row_height`](Self::auto_row_height); the last mode setter /// wins. @@ -838,7 +867,10 @@ impl TableView { self } - /// Override the column header row height in logical pixels. Default: the table style's `HEADER_HEIGHT`. + /// Override the column header row height in logical pixels. Default: the + /// active `TableStyle`'s + /// [`header_height`](teksilo_core::styles::TableStyle::header_height), + /// 32 px under IntUI at the Compact density. pub fn header_height(mut self, height: f32) -> Self { self.header_height = Some(height); self @@ -1505,18 +1537,18 @@ impl TableView { }); } - /// The configured row height (override) or the table style's 28 px - /// fallback. In the non-uniform modes this is the seed estimate; - /// real geometry lives in `row_metrics`. + /// The configured row height (override) or the active table style's + /// (see `adopt_style_heights`). In the non-uniform modes this is only the + /// scroll step; real geometry lives in `row_metrics`. fn effective_row_height(&self) -> f32 { - self.row_height.unwrap_or(cp::ROW_HEIGHT) + self.row_height.unwrap_or(self.style_row_height) } fn effective_header_height(&self) -> f32 { if !self.show_header { 0.0 } else { - self.header_height.unwrap_or(cp::HEADER_HEIGHT) + self.header_height.unwrap_or(self.style_header_height) } } diff --git a/crates/teksilo-widgets/src/table_view/table_header.rs b/crates/teksilo-widgets/src/table_view/table_header.rs index a3f1b8c15..c38ad8077 100644 --- a/crates/teksilo-widgets/src/table_view/table_header.rs +++ b/crates/teksilo-widgets/src/table_view/table_header.rs @@ -170,6 +170,9 @@ pub struct TableHeader { /// module docs for why the split exists). bands: Option<[Option; 3]>, boundaries: PaneBoundaries, + /// The active `TableStyle`'s header height, read at build: the height a + /// standalone header takes when its parent leaves the height free. + style_height: f32, } impl TableHeader { @@ -192,6 +195,7 @@ impl TableHeader { cells: Vec::new(), bands: None, boundaries: PaneBoundaries::default(), + style_height: cp::HEADER_HEIGHT, } } @@ -503,6 +507,8 @@ impl std::fmt::Debug for TableHeader { impl Widget for TableHeader { fn build(&mut self, ctx: &mut BuildContext) -> Vec { let self_id = ctx.self_id(); + self.style_height = crate::styles::recipe_table_style::resolve_table_style(ctx) + .header_height(&ctx.theme().input); // `paint` draws the column separators from the widths and `scroll_x`, // but neither a column resize nor a horizontal scroll changes the // strip's own bounds — and the render walker replays a clean node's @@ -689,7 +695,7 @@ impl Widget for TableHeader { self.resolve_widths(0.0).iter().sum() } }); - let height = proposal.height.unwrap_or(cp::HEADER_HEIGHT); + let height = proposal.height.unwrap_or(self.style_height); Size::new(width, height).into() } diff --git a/crates/teksilo-widgets/src/table_view/tests.rs b/crates/teksilo-widgets/src/table_view/tests.rs index 97f62f9a5..91cf06b90 100644 --- a/crates/teksilo-widgets/src/table_view/tests.rs +++ b/crates/teksilo-widgets/src/table_view/tests.rs @@ -6538,6 +6538,26 @@ mod standalone_header { ); } + /// A standalone header given a free height takes the one the active + /// `TableStyle` declares, not the IntUI constant. + #[test] + fn a_standalone_header_takes_its_styles_header_height_in_a_stack() { + use crate::styles::recipe_table_style::{RecipeTableStyle, TableRecipe}; + let mut theme = teksilo_core::presets::intui::light(); + theme.style_slots.table = Some(std::rc::Rc::new(RecipeTableStyle::new(TableRecipe { + header_height: 41.0, + ..TableRecipe::default() + }))); + let mut tree = WidgetTree::new().with_theme(theme); + let header = tree.add(TableHeader::new(vec![fixed("a", 100.0), fixed("b", 100.0)])); + let _root = tree.add(VStack::new().child(header)); + tree.layout(SizeProposal { + width: Some(400.0), + height: Some(300.0), + }); + assert_eq!(tree.bounds(header).height, 41.0); + } + #[test] fn a_click_on_a_sortable_header_cycles_the_headers_own_sort() { // No `.sort(..)`: the header owns the signal, and the getter hands it @@ -6894,3 +6914,116 @@ mod standalone_header { ); } } + +// -- Row and header heights come from the style ------------------------------- + +/// Every node under `root` whose type name ends in `name`, sorted top to +/// bottom. +fn nodes_typed_by_y(tree: &WidgetTree, root: WidgetId, name: &str) -> Vec { + let mut out = Vec::new(); + let mut walker = vec![root]; + while let Some(id) = walker.pop() { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(name)) + { + out.push(id); + } + walker.extend(tree.children(id)); + } + out.sort_by(|a, b| tree.bounds(*a).y.total_cmp(&tree.bounds(*b).y)); + out +} + +/// `(header height, first row's offset below the table's top, the first +/// three row heights, the pitch between the first two rows)`, as laid out. +fn table_band_geometry(tree: &WidgetTree, table: WidgetId) -> (f32, f32, Vec, f32) { + let top = tree.bounds(table).y; + let headers = nodes_typed_by_y(tree, table, "TableHeader"); + assert_eq!(headers.len(), 1, "one hosted header"); + let rows = nodes_typed_by_y(tree, table, "BodyRow"); + assert!(rows.len() >= 3, "at least three realized rows"); + let heights = rows[..3].iter().map(|r| tree.bounds(*r).height).collect(); + ( + tree.bounds(headers[0]).height, + tree.bounds(rows[0]).y - top, + heights, + tree.bounds(rows[1]).y - tree.bounds(rows[0]).y, + ) +} + +/// A theme whose table style declares heights no constant carries. +fn theme_with_table_heights(row: f32, header: f32) -> teksilo_core::styles::Theme { + use crate::styles::recipe_table_style::{RecipeTableStyle, TableRecipe}; + let mut theme = teksilo_core::presets::intui::light(); + theme.style_slots.table = Some(std::rc::Rc::new(RecipeTableStyle::new(TableRecipe { + row_height: row, + header_height: header, + ..TableRecipe::default() + }))); + theme +} + +/// A table with no row or header height of its own lays out the ones its +/// style declares: the header band, the first row's offset below it, every +/// row's height and the pitch the virtualization arithmetic steps by. +#[test] +fn a_table_lays_out_the_row_and_header_heights_its_style_declares() { + let mut tree = WidgetTree::new().with_theme(theme_with_table_heights(37.0, 41.0)); + let table = tree.add( + TableView::new(rows(20)) + .add_column(id_col()) + .add_column(name_col()), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + assert_eq!( + table_band_geometry(&tree, table), + (41.0, 41.0, vec![37.0; 3], 37.0) + ); +} + +/// The view's own `row_height(..)` and `header_height(..)` win over the +/// style. +#[test] +fn a_tables_own_row_and_header_heights_win_over_the_style() { + let mut tree = WidgetTree::new().with_theme(theme_with_table_heights(37.0, 41.0)); + let table = tree.add( + TableView::new(rows(20)) + .add_column(id_col()) + .add_column(name_col()) + .row_height(20.0) + .header_height(30.0), + ); + tree.layout(SizeProposal::exact(400.0, 300.0)); + assert_eq!( + table_band_geometry(&tree, table), + (30.0, 30.0, vec![20.0; 3], 20.0) + ); +} + +/// Under IntUI the default table follows the recipe's density ladder: 28 / 32 +/// / 44 for a row and 32 / 32 / 44 for the header. Compact is what the table +/// laid out before its style was consulted. +#[test] +fn an_int_ui_table_follows_the_density_ladder() { + use teksilo_tokens::TargetDensity; + for (density, row, header) in [ + (TargetDensity::Compact, 28.0, 32.0), + (TargetDensity::Comfortable, 32.0, 32.0), + (TargetDensity::Touch, 44.0, 44.0), + ] { + let theme = teksilo_core::presets::intui::light().with_density(density); + let mut tree = WidgetTree::new().with_theme(theme); + let table = tree.add( + TableView::new(rows(20)) + .add_column(id_col()) + .add_column(name_col()), + ); + tree.layout(SizeProposal::exact(400.0, 400.0)); + assert_eq!( + table_band_geometry(&tree, table), + (header, header, vec![row; 3], row), + "{density:?}", + ); + } +} diff --git a/crates/teksilo-widgets/src/table_view/widget_impl.rs b/crates/teksilo-widgets/src/table_view/widget_impl.rs index c79b4a1ff..c68720170 100644 --- a/crates/teksilo-widgets/src/table_view/widget_impl.rs +++ b/crates/teksilo-widgets/src/table_view/widget_impl.rs @@ -11,6 +11,7 @@ impl Widget for TableView { let self_id = ctx.self_id(); ctx.enabled_when(self_id, self.enabled.clone()); + self.adopt_style_heights(ctx); let row_h = self.effective_row_height(); let header_h = self.effective_header_height(); diff --git a/crates/teksilo-widgets/src/theme_switcher.rs b/crates/teksilo-widgets/src/theme_switcher.rs index 555ea200c..c0fedc7cf 100644 --- a/crates/teksilo-widgets/src/theme_switcher.rs +++ b/crates/teksilo-widgets/src/theme_switcher.rs @@ -30,7 +30,7 @@ //! `I18nManager` still reads "Light / Dark / System" rather than raw keys. //! //! Custom themes: `.themes([(label, theme), …])` replaces Light/Dark with an -//! app-supplied set (e.g. the `teksilo-theme-{fluent,macos,material3}` presets); +//! app-supplied set (e.g. the `teksilo-theme-{fluent,kde,macos,material3}` presets); //! `.system(false)` drops the System entry. use std::rc::Rc; diff --git a/crates/teksilo-widgets/src/tree_table_view.rs b/crates/teksilo-widgets/src/tree_table_view.rs index d65f348ba..2da9f8574 100644 --- a/crates/teksilo-widgets/src/tree_table_view.rs +++ b/crates/teksilo-widgets/src/tree_table_view.rs @@ -279,6 +279,11 @@ pub struct TreeTableView { /// pane. row_metrics: SharedRowMetrics, header_height: Option, + /// The active `TableStyle`'s row and header heights at the density + /// in force, read at build by `adopt_style_heights`. Until then they + /// hold IntUI's Compact values, the module constants. + style_row_height: f32, + style_header_height: f32, show_header: bool, selection_mode: TableSelectionMode, /// Row selection — index-based `SelectionModel` or keyed @@ -579,6 +584,8 @@ impl TreeTableView { height_source: HeightSource::Uniform, row_metrics: Rc::new(RefCell::new(RowMetrics::uniform(cp::ROW_HEIGHT, 0.0))), header_height: None, + style_row_height: cp::ROW_HEIGHT, + style_header_height: cp::HEADER_HEIGHT, show_header: true, selection_mode: TableSelectionMode::default(), row_selection: None, @@ -990,8 +997,30 @@ impl TreeTableView { .make_metrics(self.effective_row_height(), 0.0); } - /// Fixed row height (default: the table style's 28 px) — the - /// uniform fast path. Mutually exclusive with + /// Read the active `TableStyle`'s row and header heights at the density + /// in force. Called first thing in `build`, so every height the build and + /// the later layout passes read is the style's, unless the app set its + /// own through [`row_height`](Self::row_height) / + /// [`header_height`](Self::header_height), which win. A uniform-height + /// view with no row height of its own re-materializes its metrics when + /// the style's row height differs from the one they were made with. + fn adopt_style_heights(&mut self, ctx: &BuildContext) { + let style = crate::styles::recipe_table_style::resolve_table_style(ctx); + let tokens = &ctx.theme().input; + self.style_header_height = style.header_height(tokens); + let row = style.row_height(tokens); + if row != self.style_row_height { + self.style_row_height = row; + if self.row_height.is_none() && matches!(self.height_source, HeightSource::Uniform) { + self.remake_metrics(); + } + } + } + + /// Fixed row height, the uniform fast path. Default: the active + /// `TableStyle`'s + /// [`row_height`](teksilo_core::styles::TableStyle::row_height), 28 px + /// under IntUI at the Compact density. Mutually exclusive with /// [`row_height_fn`](Self::row_height_fn) and /// [`auto_row_height`](Self::auto_row_height); the last mode setter /// wins. @@ -1026,7 +1055,10 @@ impl TreeTableView { self } - /// Override the header row height in logical pixels. + /// Override the header row height in logical pixels. Default: the active + /// `TableStyle`'s + /// [`header_height`](teksilo_core::styles::TableStyle::header_height), + /// 32 px under IntUI at the Compact density. pub fn header_height(mut self, height: f32) -> Self { self.header_height = Some(height); self @@ -1729,7 +1761,7 @@ impl TreeTableView { // ── Internals ────────────────────────────────────────────────────── fn effective_row_height(&self) -> f32 { - self.row_height.unwrap_or(cp::ROW_HEIGHT) + self.row_height.unwrap_or(self.style_row_height) } /// The full-width rows and the delegate that draws them, or `None` when @@ -1766,7 +1798,7 @@ impl TreeTableView { fn effective_header_height(&self) -> f32 { if self.show_header { - self.header_height.unwrap_or(cp::HEADER_HEIGHT) + self.header_height.unwrap_or(self.style_header_height) } else { 0.0 } diff --git a/crates/teksilo-widgets/src/tree_table_view/tests.rs b/crates/teksilo-widgets/src/tree_table_view/tests.rs index c9a3ca9cb..285fafde9 100644 --- a/crates/teksilo-widgets/src/tree_table_view/tests.rs +++ b/crates/teksilo-widgets/src/tree_table_view/tests.rs @@ -6516,3 +6516,91 @@ mod full_width_and_pinned { ); } } + +// -- Row and header heights come from the style ------------------------------- + +/// `(header height, first row's offset below the view's top, the first three +/// row heights)`, as laid out, for a tree table over [`sample_tree`] with every +/// row expanded. +fn tree_table_band_geometry( + theme: teksilo_core::styles::Theme, + explicit: bool, +) -> (f32, f32, Vec) { + fn typed_by_y(tree: &WidgetTree, root: WidgetId, name: &str) -> Vec { + let mut out = Vec::new(); + let mut walker = vec![root]; + while let Some(id) = walker.pop() { + if tree + .widget_type_name(id) + .is_some_and(|t| t.rsplit("::").next() == Some(name)) + { + out.push(id); + } + walker.extend(tree.children(id)); + } + out.sort_by(|a, b| tree.bounds(*a).y.total_cmp(&tree.bounds(*b).y)); + out + } + let mut tree = WidgetTree::new().with_theme(theme); + let mut view = TreeTableView::new(sample_tree()) + .add_column(name_col()) + .add_column(size_col()); + if explicit { + view = view.row_height(20.0).header_height(30.0); + } + let view = tree.add(view); + tree.layout(SizeProposal::exact(400.0, 400.0)); + let top = tree.bounds(view).y; + let headers = typed_by_y(&tree, view, "TableHeader"); + assert_eq!(headers.len(), 1, "one hosted header"); + let rows = typed_by_y(&tree, view, "BodyRow"); + assert!(rows.len() >= 2, "at least the two roots are realized"); + ( + tree.bounds(headers[0]).height, + tree.bounds(rows[0]).y - top, + rows[..2].iter().map(|r| tree.bounds(*r).height).collect(), + ) +} + +/// A tree table with no row or header height of its own lays out the ones +/// its style declares, and its own `row_height(..)` / `header_height(..)` win +/// over them. +#[test] +fn a_tree_table_lays_out_the_row_and_header_heights_its_style_declares() { + use crate::styles::recipe_table_style::{RecipeTableStyle, TableRecipe}; + let mut theme = teksilo_core::presets::intui::light(); + theme.style_slots.table = Some(std::rc::Rc::new(RecipeTableStyle::new(TableRecipe { + row_height: 37.0, + header_height: 41.0, + ..TableRecipe::default() + }))); + assert_eq!( + tree_table_band_geometry(theme.clone(), false), + (41.0, 41.0, vec![37.0; 2]), + "the style's heights", + ); + assert_eq!( + tree_table_band_geometry(theme, true), + (30.0, 30.0, vec![20.0; 2]), + "the view's own heights win", + ); +} + +/// Under IntUI the default tree table follows the recipe's density ladder, +/// unchanged at Compact. +#[test] +fn an_int_ui_tree_table_follows_the_density_ladder() { + use teksilo_tokens::TargetDensity; + for (density, row, header) in [ + (TargetDensity::Compact, 28.0, 32.0), + (TargetDensity::Comfortable, 32.0, 32.0), + (TargetDensity::Touch, 44.0, 44.0), + ] { + let theme = teksilo_core::presets::intui::light().with_density(density); + assert_eq!( + tree_table_band_geometry(theme, false), + (header, header, vec![row; 2]), + "{density:?}", + ); + } +} diff --git a/crates/teksilo-widgets/src/tree_table_view/widget_impl.rs b/crates/teksilo-widgets/src/tree_table_view/widget_impl.rs index 1f26418b5..f2fbe8dee 100644 --- a/crates/teksilo-widgets/src/tree_table_view/widget_impl.rs +++ b/crates/teksilo-widgets/src/tree_table_view/widget_impl.rs @@ -11,6 +11,7 @@ impl Widget for TreeTableView { let self_id = ctx.self_id(); ctx.enabled_when(self_id, self.enabled.clone()); + self.adopt_style_heights(ctx); let row_h = self.effective_row_height(); let header_h = self.effective_header_height(); let indent_per_level = self.effective_indent(); diff --git a/crates/teksilo-widgets/tests/density_projection.rs b/crates/teksilo-widgets/tests/density_projection.rs index e4a228ade..870b3ed56 100644 --- a/crates/teksilo-widgets/tests/density_projection.rs +++ b/crates/teksilo-widgets/tests/density_projection.rs @@ -491,18 +491,22 @@ fn every_fixed_row_says_it_is_fixed() { // 5. The style traits' metric defaults restate the module constants // ─────────────────────────────────────────────────────────────────────────── // -// Three style traits carry a defaulted metrics accessor for a dimension the -// widget composes itself and the trait's `make_*` hooks never see — a table +// Six style traits carry a defaulted metrics accessor for a dimension the +// widget composes itself and the trait's `make_*` hooks never see: a table // cell's gutter, a calendar nav arrow's footprint, a search suggestion row's -// gutter. Each default is the module constant put through the same helper -// `for_tokens` uses, restated as a literal because `teksilo-core` sits below +// gutter, a list row's height floor, a table's row and header heights, a +// tab's height and its header padding, a scroll-bar thumb's minimum length. +// Each +// default is the module constant put through the same helper `for_tokens` +// uses, restated as a literal because `teksilo-core` sits below // `teksilo-widgets` and cannot name the constant. A literal in one crate and a // constant in another is exactly the shape that drifts, so it is pinned here — // and the tests are named in the trait doc comments, so a reader who finds the // literal finds the pin. -/// The `TableStyle` gutter defaults equal -/// `recipe_table_style::CELL_PADDING_{HORIZONTAL,VERTICAL}` at every density. +/// The `TableStyle` gutter and height defaults equal +/// `recipe_table_style::CELL_PADDING_{HORIZONTAL,VERTICAL}` and +/// `ROW_HEIGHT` / `HEADER_HEIGHT` at every density. #[test] fn the_table_trait_defaults_restate_the_module_constants() { use teksilo_core::styles::TableStyle; @@ -549,7 +553,27 @@ fn the_table_trait_defaults_restate_the_module_constants() { spacing(cp::CELL_PADDING_VERTICAL, &t), "the TableStyle default drifted from CELL_PADDING_VERTICAL", ); + assert_eq!( + BareTableStyle.row_height(&t), + dp(cp::ROW_HEIGHT, TargetRole::Target, &t), + "the TableStyle default drifted from ROW_HEIGHT", + ); + assert_eq!( + BareTableStyle.header_height(&t), + dp(cp::HEADER_HEIGHT, TargetRole::Target, &t), + "the TableStyle default drifted from HEADER_HEIGHT", + ); + // And the shipped recipe at that density agrees with the default, so + // an out-of-tree style and the shipped one lay out the same rows. + let shipped = RecipeTableStyle::for_tokens(&t); + assert_eq!(shipped.row_height(&t), BareTableStyle.row_height(&t)); + assert_eq!(shipped.header_height(&t), BareTableStyle.header_height(&t)); } + // At Compact the defaults are the module constants themselves, so a + // Compact table lays out exactly what it did before the accessors existed. + let t = compact(); + assert_eq!(BareTableStyle.row_height(&t), cp::ROW_HEIGHT); + assert_eq!(BareTableStyle.header_height(&t), cp::HEADER_HEIGHT); } /// The `CalendarStyle` arrow default equals @@ -628,6 +652,163 @@ fn the_search_field_trait_defaults_restate_the_module_constants() { } } +/// The `StandardItemStyle` row-height defaults equal +/// `recipe_standard_item_style::STANDARD_ITEM_MIN_HEIGHT_{SINGLE,TWO}_LINE` at +/// every density. +#[test] +fn the_standard_item_trait_defaults_restate_the_module_constants() { + use teksilo_core::styles::StandardItemStyle; + use teksilo_widgets::styles::recipe_standard_item_style as si; + + /// A `StandardItemStyle` that overrides nothing but `make_body`. + struct BareStandardItemStyle; + impl StandardItemStyle for BareStandardItemStyle { + fn make_body( + &self, + cfg: &teksilo_core::styles::StandardItemStyleConfig, + _ctx: &mut teksilo_core::BuildContext, + ) -> teksilo_core::WidgetId { + cfg.content + } + } + + for t in [compact(), comfortable(), touch()] { + assert_eq!( + BareStandardItemStyle.min_height_single_line(&t), + dp( + si::STANDARD_ITEM_MIN_HEIGHT_SINGLE_LINE, + TargetRole::Target, + &t + ), + "the StandardItemStyle default drifted from STANDARD_ITEM_MIN_HEIGHT_SINGLE_LINE", + ); + assert_eq!( + BareStandardItemStyle.min_height_two_line(&t), + dp( + si::STANDARD_ITEM_MIN_HEIGHT_TWO_LINE, + TargetRole::Target, + &t + ), + "the StandardItemStyle default drifted from STANDARD_ITEM_MIN_HEIGHT_TWO_LINE", + ); + // And the shipped recipe at that density agrees with the default, so + // an out-of-tree style and the shipped one draw the same row. + let shipped = RecipeStandardItemStyle::for_tokens(&t); + assert_eq!( + shipped.min_height_single_line(&t), + BareStandardItemStyle.min_height_single_line(&t), + ); + assert_eq!( + shipped.min_height_two_line(&t), + BareStandardItemStyle.min_height_two_line(&t), + ); + } +} + +/// The `TabStyle` height and padding defaults equal +/// `recipe_tab_style::TAB_EDITOR_HEIGHT` and `TAB_PADDING_HORIZONTAL` at every +/// density. +#[test] +fn the_tab_trait_defaults_restate_the_module_constants() { + use teksilo_core::styles::TabStyle; + use teksilo_widgets::styles::recipe_tab_style as tab; + + /// A `TabStyle` that overrides nothing but its two `make_*` hooks. + struct BareTabStyle; + impl TabStyle for BareTabStyle { + fn make_body( + &self, + cfg: &teksilo_core::styles::TabStyleConfig, + _ctx: &mut teksilo_core::BuildContext, + ) -> teksilo_core::WidgetId { + cfg.label + } + fn make_bar( + &self, + cfg: &teksilo_core::styles::TabBarChromeConfig, + _ctx: &mut teksilo_core::BuildContext, + ) -> teksilo_core::WidgetId { + cfg.content + } + } + + for t in [compact(), comfortable(), touch()] { + assert_eq!( + BareTabStyle.editor_height(&t), + dp(tab::TAB_EDITOR_HEIGHT, TargetRole::Target, &t), + "the TabStyle default drifted from TAB_EDITOR_HEIGHT", + ); + assert_eq!( + BareTabStyle.padding_horizontal(&t), + spacing(tab::TAB_PADDING_HORIZONTAL, &t), + "the TabStyle default drifted from TAB_PADDING_HORIZONTAL", + ); + // And the shipped recipe at that density agrees with the default, so + // an out-of-tree style and the shipped one lay out the same tabs. + let shipped = RecipeTabStyle::for_tokens(&t); + assert_eq!(shipped.editor_height(&t), BareTabStyle.editor_height(&t)); + assert_eq!( + shipped.padding_horizontal(&t), + BareTabStyle.padding_horizontal(&t) + ); + } + // At Compact the defaults are the module constants themselves, so a + // Compact bar lays out exactly what it did before the accessors existed. + let t = compact(); + assert_eq!(BareTabStyle.editor_height(&t), tab::TAB_EDITOR_HEIGHT); + assert_eq!( + BareTabStyle.padding_horizontal(&t), + tab::TAB_PADDING_HORIZONTAL + ); +} + +/// The `ScrollBarStyle` thumb-floor default equals +/// `recipe_scroll_bar_style::SCROLLBAR_MIN_THUMB_LENGTH`, and the widget's own +/// `scroll_bar::SCROLLBAR_MIN_THUMB_LENGTH` (the floor before a first build), +/// at every density. +#[test] +fn the_scroll_bar_trait_default_restates_the_module_constant() { + use teksilo_core::styles::ScrollBarStyle; + use teksilo_widgets::styles::recipe_scroll_bar_style as bar; + + /// A `ScrollBarStyle` that overrides nothing but its `make_body` hook. + struct BareScrollBarStyle; + impl ScrollBarStyle for BareScrollBarStyle { + fn make_body( + &self, + _cfg: &teksilo_core::styles::ScrollBarStyleConfig, + ctx: &mut teksilo_core::BuildContext, + ) -> teksilo_core::WidgetId { + ctx.add(teksilo_widgets::primitives::Spacer::new()) + } + } + + assert_eq!( + bar::SCROLLBAR_MIN_THUMB_LENGTH, + teksilo_widgets::scroll_bar::SCROLLBAR_MIN_THUMB_LENGTH, + "the recipe's constant and the widget's pre-build floor drifted apart", + ); + for t in [compact(), comfortable(), touch()] { + assert_eq!( + BareScrollBarStyle.min_thumb_length(&t), + dp(bar::SCROLLBAR_MIN_THUMB_LENGTH, TargetRole::Target, &t), + "the ScrollBarStyle default drifted from SCROLLBAR_MIN_THUMB_LENGTH", + ); + // And the shipped recipe at that density agrees with the default, so + // an out-of-tree style and the shipped one floor the same thumb. + assert_eq!( + RecipeScrollBarStyle::for_tokens(&t).min_thumb_length(&t), + BareScrollBarStyle.min_thumb_length(&t), + ); + } + // At Compact the default is the module constant itself, so a Compact bar + // floors its thumb exactly where it did before the accessor existed. + assert_eq!( + BareScrollBarStyle.min_thumb_length(&compact()), + bar::SCROLLBAR_MIN_THUMB_LENGTH + ); +} + /// The shipped `Recipe*Style` answers from *its own recipe*, not from the /// trait default — which is the half of the mechanism that lets a preset's /// number reach the screen at all. @@ -637,10 +818,20 @@ fn the_search_field_trait_defaults_restate_the_module_constants() { /// constant carries. #[test] fn a_recipe_style_answers_from_its_own_recipe() { - use teksilo_core::styles::{CalendarStyle, SearchFieldStyle, TableStyle}; + use teksilo_core::styles::{ + CalendarStyle, ScrollBarStyle, SearchFieldStyle, StandardItemStyle, TabStyle, TableStyle, + }; let t = compact(); + let item = RecipeStandardItemStyle::new(StandardItemRecipe { + min_height_single_line: 23.0, + min_height_two_line: 39.0, + ..StandardItemRecipe::for_tokens(&t) + }); + assert_eq!(item.min_height_single_line(&t), 23.0); + assert_eq!(item.min_height_two_line(&t), 39.0); + let table = RecipeTableStyle::new(TableRecipe { cell_padding_horizontal: 12.0, cell_padding_vertical: 7.0, @@ -649,6 +840,28 @@ fn a_recipe_style_answers_from_its_own_recipe() { assert_eq!(table.cell_padding_horizontal(&t), 12.0); assert_eq!(table.cell_padding_vertical(&t), 7.0); + let table = RecipeTableStyle::new(TableRecipe { + row_height: 23.0, + header_height: 27.0, + ..TableRecipe::for_tokens(&t) + }); + assert_eq!(table.row_height(&t), 23.0); + assert_eq!(table.header_height(&t), 27.0); + + let tab = RecipeTabStyle::new(TabRecipe { + editor_height: 31.0, + padding_horizontal: 9.0, + ..TabRecipe::for_tokens(&t) + }); + assert_eq!(tab.editor_height(&t), 31.0); + assert_eq!(tab.padding_horizontal(&t), 9.0); + + let bar = RecipeScrollBarStyle::new(ScrollBarRecipe { + min_thumb_length: 19.0, + ..ScrollBarRecipe::for_tokens(&t) + }); + assert_eq!(bar.min_thumb_length(&t), 19.0); + let calendar = RecipeCalendarStyle::new(CalendarRecipe { nav_arrow_size: 20.0, ..CalendarRecipe::for_tokens(&t) diff --git a/crates/teksilo/Cargo.toml b/crates/teksilo/Cargo.toml index 8b906cb52..f013e1d65 100644 --- a/crates/teksilo/Cargo.toml +++ b/crates/teksilo/Cargo.toml @@ -148,12 +148,17 @@ telemetry = ["teksilo-app/telemetry", "teksilo-widgets?/telemetry"] # preset (`teksilo::prelude::intui::{light, dark}`). Each exposes a # stable import path (`teksilo::prelude::material3::{light, dark}`, # etc.), so an app's call sites never change as a preset fills in. -# `theme-material3`, `theme-fluent` and `theme-macos` are all complete -# design languages: colour / shape / typography / motion tokens plus -# Tier-3 widget chrome. +# `theme-material3`, `theme-fluent`, `theme-macos` and `theme-kde` are all +# complete design languages: colour / shape / typography / motion tokens +# plus Tier-3 widget chrome. `theme-kde` is KDE Plasma's Breeze, and also +# brings `native_theme_kde()`, which gives Native mode on a Breeze desktop +# Breeze's geometry over the desktop's own colours. Like Material 3's, its +# font features (`bundled-fonts`, `system-fonts`) are not forwarded: an app +# that wants Plasma's faces depends on `teksilo-theme-kde` directly. theme-material3 = ["dep:teksilo-theme-material3"] theme-macos = ["dep:teksilo-theme-macos"] theme-fluent = ["dep:teksilo-theme-fluent"] +theme-kde = ["dep:teksilo-theme-kde"] # Font bundle features — forwarded to `teksilo-text`'s own feature set. # Applications enable the scripts they ship for and disable the rest. @@ -214,6 +219,7 @@ teksilo-resources = { workspace = true } teksilo-theme-material3 = { workspace = true, optional = true } teksilo-theme-macos = { workspace = true, optional = true } teksilo-theme-fluent = { workspace = true, optional = true } +teksilo-theme-kde = { workspace = true, optional = true } [dev-dependencies] trybuild = "1" @@ -225,7 +231,7 @@ tempfile = { workspace = true } # Everything except the WebView engine backends, which need # WebKitGTK / Servo system libraries docs.rs does not have. features = ["automation", "async", "terminal", "theme-fluent", - "theme-macos", "theme-material3", "web-view-headless"] + "theme-kde", "theme-macos", "theme-material3", "web-view-headless"] # The workspace has ONE changelog, at the repository root, so these run from # the umbrella crate rather than from `release.toml`: a root-level diff --git a/crates/teksilo/README.md b/crates/teksilo/README.md index 47638e976..1ac62f0b4 100644 --- a/crates/teksilo/README.md +++ b/crates/teksilo/README.md @@ -41,7 +41,7 @@ State is reactive through `Signal` and `Prop`; a derived signal - **Layout** — SwiftUI-style negotiation with grow *and* shrink weights, and correct height-for-width. - **Theming** — tokens → variants → recipes → style protocols, with Int-UI, - Material 3, Fluent and macOS Aqua presets. + Material 3, Fluent, macOS Aqua and KDE Plasma (Breeze) presets. - **i18n** — Fluent messages with compile-time key checking, plus ICU-backed number and date formatting. - **Headless tests** — the same widget tree runs with no window, no GPU and no @@ -52,7 +52,7 @@ State is reactive through `Signal` and `Prop`; a derived signal `widgets`, `i18n`, `inspector`, `toast`, `file-dialog`, `clipboard` and the Arabic and Hebrew fallback fonts (`fonts-arabic`, `fonts-hebrew`) are on by default. The text stack is not a feature: every build has it. Opt in to -`theme-material3`, `theme-fluent`, `theme-macos`, `terminal`, `web-view`, +`theme-material3`, `theme-fluent`, `theme-macos`, `theme-kde`, `terminal`, `web-view`, `async` (with `tokio` or `async-std`), `automation`, `telemetry`, and the other bundled script fonts (`fonts-*`). diff --git a/crates/teksilo/src/app_guide.md b/crates/teksilo/src/app_guide.md index 57967a1d5..6518a5a45 100644 --- a/crates/teksilo/src/app_guide.md +++ b/crates/teksilo/src/app_guide.md @@ -67,7 +67,7 @@ opt-outs: | `async` / `tokio` / `async-std` | Optional main-thread async executor (`.install_async()`, `ctx.spawn_local`, `spawn_blocking`). `tokio` / `async-std` imply `async` and add the reactor (`.install_async_tokio()` / `.install_async_async_std()`) so a handler can `.await` that runtime's futures: timers, sockets, `reqwest`. Off by default. See `cargo teksilo show docs/async.md` | | `terminal` | Embeddable `Terminal` widget: a real shell over a PTY. Off by default | | `automation` | Debug-only automation bridge (`.install_automation_bridge_in_debug()`) for agents and tests. Off by default | -| `theme-material3` / `theme-fluent` / `theme-macos` | Material 3, Fluent (Windows 11) and macOS Aqua presets, as `material3::light()` etc. from the prelude | +| `theme-material3` / `theme-fluent` / `theme-macos` / `theme-kde` | Material 3, Fluent (Windows 11), macOS Aqua and KDE Plasma (Breeze) presets, as `material3::light()` etc. from the prelude. `theme-kde` also adds `.native_theme_kde()` (see Theming). Its font features (`bundled-fonts`, `system-fonts`) are not forwarded: depend on `teksilo-theme-kde` directly for Plasma's own faces | | `telemetry` | Privacy-respecting analytics wiring + `PrivacySettings` widget | | `fonts-cjk-sc` / `fonts-thai` / `fonts-all` / `system-emoji` | Extra bundled script fonts / runtime color-emoji fallback | @@ -363,6 +363,17 @@ let theme = intui::light(); // or intui::dark() `ctx.set_theme(...)` updates an internal signal and dirty-marks nodes — no rebuild, and focus/scroll/interaction state survive. +**Following the desktop.** `.theme_mode(ThemeMode::FollowSystem)` picks Int UI light or +dark by the desktop's preference; `ThemeMode::Native` adopts the desktop's own colours +(accent, surfaces, selection; on a KDE Plasma session its colour scheme) in Int UI's +shapes, and both track the desktop while the app runs. A handler +switches to Native with `ctx.follow_system_theme()` and back with `ctx.set_theme(..)`. With +the `theme-kde` feature, `.native_theme_kde()` (the prelude's `TeksiloAppBuilderKdeExt`) +makes a window *built* in Native mode on a Breeze desktop use the desktop's fonts and +animation speed and Breeze's shapes as well; any other desktop keeps Int UI in its colours. A theme change after the window is built is a +re-tint: the shapes stay those it was built with. `.native_theme(|request| ..)` installs +a factory of your own. Reference: `cargo teksilo show docs/native-theme.md`. + **Variants** select a widget's look (Tier 1): ```rust,ignore diff --git a/crates/teksilo/src/kde_native_install.rs b/crates/teksilo/src/kde_native_install.rs new file mode 100644 index 000000000..08dfbc423 --- /dev/null +++ b/crates/teksilo/src/kde_native_install.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! `native_theme_kde`: give [`ThemeMode::Native`](teksilo_app::ThemeMode::Native) +//! Breeze's geometry on a KDE Plasma desktop. +//! +//! Mirrors the other install hooks in this crate (`toast_install`, +//! `webview_install`): an extension trait on [`TeksiloAppBuilder`](teksilo_app::TeksiloAppBuilder) declared +//! next to its impl, here over `TeksiloAppBuilder::native_theme`, the seam +//! that asks the app for a theme whenever Native mode needs one. +//! +//! ```ignore +//! use teksilo::prelude::*; +//! +//! TeksiloAppBuilder::new() +//! .theme_mode(ThemeMode::Native) +//! .native_theme_kde() +//! .initial_window(WindowConfig::new() +//! .root(|tree, _state| tree.add(MyRoot::new()))) +//! .run(); +//! ``` +//! +//! The factory answers with [`kde::native_theme`](teksilo_theme_kde::native_theme): +//! the desktop's own colour scheme, fonts and animation speed in Breeze's +//! shapes, at the density the window is for. It answers only when the +//! desktop reports KDE settings and uses the Breeze widget style. On any +//! other desktop, and on a KDE desktop styled with Fusion, Oxygen or +//! Kvantum, it answers `None`, and the seam falls back to IntUI coloured by +//! the desktop's report. The theme a window shows is given the id `system`, +//! as every theme that follows the OS is. +//! +//! Setting the factory does not select Native mode; combine it with +//! [`theme_mode`](teksilo_app::TeksiloAppBuilder::theme_mode). A widget picks +//! its chrome when it is built, so the factory's shapes reach only a window +//! built while the app is in Native mode; a switch to Native at runtime applies +//! the factory theme's colours and keeps the shapes the windows were built +//! with, and [`ThemeMode::FollowSystem`](teksilo_app::ThemeMode::FollowSystem) +//! never asks it. + +use teksilo_app::{NativeThemeRequest, TeksiloAppBuilder}; +use teksilo_core::Theme; + +/// Extension trait on [`TeksiloAppBuilder`] that installs the KDE Plasma +/// native theme. +pub trait TeksiloAppBuilderKdeExt { + /// Make Native mode show Breeze on a Breeze desktop. + /// + /// Installs [`kde_native_theme`] as the builder's native-theme factory, + /// replacing any factory set before. + fn native_theme_kde(self) -> Self; +} + +impl TeksiloAppBuilderKdeExt for TeksiloAppBuilder { + fn native_theme_kde(self) -> Self { + self.native_theme(kde_native_theme) + } +} + +/// The factory [`native_theme_kde`](TeksiloAppBuilderKdeExt::native_theme_kde) +/// installs: the Breeze theme of the request's KDE desktop at the request's +/// density, or `None` when the desktop reports no KDE settings or does not +/// use the Breeze widget style. +/// +/// Public so an app that composes its own factory (for example one that +/// answers for another desktop too) can call it from there. +pub fn kde_native_theme(request: &NativeThemeRequest<'_>) -> Option { + let desktop = request.desktop.kde.as_ref()?; + teksilo_theme_kde::native_theme(desktop, request.density) +} diff --git a/crates/teksilo/src/lib.rs b/crates/teksilo/src/lib.rs index faecf55f6..3260be6e1 100644 --- a/crates/teksilo/src/lib.rs +++ b/crates/teksilo/src/lib.rs @@ -134,6 +134,12 @@ pub use teksilo_automation as automation; #[cfg(feature = "automation")] pub mod automation_install; +/// KDE Plasma native-theme hook. The `native_theme_kde()` method comes from +/// [`TeksiloAppBuilderKdeExt`](kde_native_install::TeksiloAppBuilderKdeExt), +/// re-exported through [`prelude`]; the preset itself is `teksilo::prelude::kde`. +#[cfg(feature = "theme-kde")] +pub mod kde_native_install; + pub mod prelude { // DSL entry point pub use teksilo_macros::teksu; @@ -182,9 +188,9 @@ pub mod prelude { // Sibling preset crates — opt-in alternatives to the bundled IntUI // preset, each behind its own Cargo feature and reachable by a stable - // path (`material3::light()`, `fluent::dark()`, `macos::light()`, …). - // All three are complete design languages: tokens plus Tier-3 widget - // chrome. + // path (`material3::light()`, `fluent::dark()`, `macos::light()`, + // `kde::dark()`, …). All four are complete design languages: tokens plus + // Tier-3 widget chrome. #[cfg(feature = "theme-material3")] pub use teksilo_theme_material3 as material3; @@ -194,11 +200,21 @@ pub mod prelude { #[cfg(feature = "theme-fluent")] pub use teksilo_theme_fluent as fluent; + #[cfg(feature = "theme-kde")] + pub use teksilo_theme_kde as kde; + + // `native_theme_kde()`: Breeze geometry for Native mode on a Breeze + // desktop. + #[cfg(feature = "theme-kde")] + pub use crate::kde_native_install::TeksiloAppBuilderKdeExt; + // Reactive color / style props — unified input types for widget builders. pub use teksilo_core::color_prop::{ColorProp, TextStyleProp}; // App - pub use teksilo_app::{TeksiloAppBuilder, ThemeMode}; + pub use teksilo_app::{ + NativeThemeFactory, NativeThemeRequest, TeksiloAppBuilder, ThemeMode, resolve_native_theme, + }; // Settings (persistence layer) pub use teksilo_settings::{ diff --git a/crates/teksilo/tests/native_kde.rs b/crates/teksilo/tests/native_kde.rs new file mode 100644 index 000000000..2a631603f --- /dev/null +++ b/crates/teksilo/tests/native_kde.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +//! `native_theme_kde`: the factory it installs gives Breeze for a Breeze +//! desktop and nothing for any other. + +#![cfg(feature = "theme-kde")] + +use teksilo::kde_native_install::kde_native_theme; +use teksilo::platform::os_theme::NativeDesktop; +use teksilo::prelude::kde::scheme::{KdeColorScheme, KdeDesktop}; +use teksilo::prelude::*; +use teksilo::tokens::{ColorSchemePreference, OsThemeColors, TargetDensity}; + +fn desktop(scheme: KdeColorScheme, widget_style: Option<&str>) -> NativeDesktop { + let mut kde = KdeDesktop::new(scheme); + kde.widget_style = widget_style.map(str::to_owned); + let mut native = NativeDesktop::new(OsThemeColors::new(ColorSchemePreference::Light)); + native.kde = Some(kde); + native +} + +#[test] +fn a_breeze_desktop_gets_breeze_geometry_at_the_requested_density() { + for style in [None, Some("Breeze"), Some("breeze")] { + for density in [ + TargetDensity::Compact, + TargetDensity::Comfortable, + TargetDensity::Touch, + ] { + let native = desktop(KdeColorScheme::breeze_light(), style); + let request = NativeThemeRequest::new(&native, false, density); + let theme = kde_native_theme(&request) + .unwrap_or_else(|| panic!("a Breeze desktop ({style:?}) must be answered")); + assert_eq!(theme.input.density, density, "{style:?}"); + assert_eq!( + theme.shape, + kde::light().shape, + "Breeze's shapes for {style:?}" + ); + } + } +} + +#[test] +fn a_fusion_desktop_is_not_answered() { + let native = desktop(KdeColorScheme::breeze_light(), Some("Fusion")); + let request = NativeThemeRequest::new(&native, false, TargetDensity::Compact); + assert!(kde_native_theme(&request).is_none()); +} + +#[test] +fn a_desktop_without_kde_settings_is_not_answered() { + let native = NativeDesktop::new(OsThemeColors::new(ColorSchemePreference::Dark)); + let request = NativeThemeRequest::new(&native, true, TargetDensity::Compact); + assert!(kde_native_theme(&request).is_none()); +} + +#[test] +fn the_theme_carries_the_id_of_the_scheme_kde_judges() { + let light = desktop(KdeColorScheme::breeze_light(), None); + let dark = desktop(KdeColorScheme::breeze_dark(), None); + let id = |native: &NativeDesktop, is_dark| { + let request = NativeThemeRequest::new(native, is_dark, TargetDensity::Compact); + kde_native_theme(&request).expect("Breeze").id + }; + assert_eq!(id(&light, false).as_str(), "kde.light"); + assert_eq!(id(&dark, true).as_str(), "kde.dark"); +} + +#[test] +fn native_theme_kde_installs_the_kde_factory_on_the_builder() { + let bare = TeksiloAppBuilder::new(); + assert!( + bare.configured_native_theme().is_none(), + "a builder starts with no native-theme factory" + ); + + let builder = bare.native_theme_kde(); + let factory = builder + .configured_native_theme() + .expect("native_theme_kde installs a factory"); + + // The installed factory answers a Breeze desktop with Breeze's shapes at + // the requested density, in the id of the scheme KDE judges ... + let breeze = desktop(KdeColorScheme::breeze_dark(), None); + let request = NativeThemeRequest::new(&breeze, true, TargetDensity::Touch); + let theme = factory(&request).expect("a Breeze desktop is answered"); + assert_eq!(theme.shape, kde::dark().shape); + assert_eq!(theme.input.density, TargetDensity::Touch); + assert_eq!(theme.id.as_str(), "kde.dark"); + + // ... and declines a desktop that is not Breeze, so the seam falls back. + let fusion = desktop(KdeColorScheme::breeze_light(), Some("Fusion")); + let request = NativeThemeRequest::new(&fusion, false, TargetDensity::Compact); + assert!(factory(&request).is_none()); +} + +#[test] +fn a_later_factory_replaces_the_kde_one() { + let builder = TeksiloAppBuilder::new() + .native_theme_kde() + .native_theme(|_| None); + let factory = builder.configured_native_theme().expect("a factory"); + let breeze = desktop(KdeColorScheme::breeze_light(), None); + let request = NativeThemeRequest::new(&breeze, false, TargetDensity::Compact); + assert!(factory(&request).is_none()); +} diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 249f1f5c5..c48aae774 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -26,6 +26,7 @@ - [Events & gestures](events-and-gestures.md) - [Styling system](styling-system.md) - [Reactive theme](reactive-theme.md) +- [Native theme](native-theme.md) - [Animation](animation.md) - [Idle & animation](idle-and-animation.md) - [Accessibility overrides](accessibility-overrides.md) diff --git a/docs/a11y/explore-by-touch.md b/docs/a11y/explore-by-touch.md index 470104f88..35634ee68 100644 --- a/docs/a11y/explore-by-touch.md +++ b/docs/a11y/explore-by-touch.md @@ -86,7 +86,7 @@ So the flag comes from the platform, through | Platform | Source | Caveat | | --- | --- | --- | -| Linux | AT-SPI `org.a11y.Status.ScreenReaderEnabled` on `org.a11y.Bus` — the same status object `accesskit_unix` watches for the neighbouring `IsEnabled` | Advisory. Orca sets it; an assistive technology that never touches the status object will not. No bus, no `busctl`, or a sandbox that cannot reach the session bus all answer `Unknown`. | +| Linux | AT-SPI `org.a11y.Status.ScreenReaderEnabled` on `org.a11y.Bus`, the same status object `accesskit_unix` watches for the neighbouring `IsEnabled` | Advisory. Orca sets it; an assistive technology that never touches the status object will not. No bus, a sandbox that cannot reach the session bus, or a bus that does not answer within the reader's 500 ms call timeout all answer `Unknown`. | | Windows | `SPI_GETSCREENREADER` | Narrator / NVDA / JAWS set it, Magnifier does not — the discrimination we want. Windows does not reliably clear it if an assistive technology terminates without doing so itself, so a stale `true` is the failure mode. | | macOS | `NSWorkspace::isVoiceOverEnabled` | VoiceOver only, which is the only screen reader on the platform. Zoom and Voice Control do not set it. | | other | — | `Unknown` | @@ -184,8 +184,11 @@ that no Linux CI host can execute, and belong on a hardware sign-off checklist: tested headless in `teksilo-platform`; that the adapter invokes them at all needs a real window. -The Linux `busctl` reply parser **is** tested, over recorded reply strings -rather than a live bus. +The Linux reply decoding **is** tested without a bus: `decode_bool` and the +other zbus decoders in `teksilo-platform`'s `linux_portal` module are checked +over constructed reply values. The reads themselves are tested against a +private `dbus-daemon` serving a fake AT-SPI status object, when +`TEKSILO_DBUS_TESTS=1` is set. ## See also diff --git a/docs/agent-tooling.md b/docs/agent-tooling.md index adbf4bf86..acc6f6a9d 100644 --- a/docs/agent-tooling.md +++ b/docs/agent-tooling.md @@ -66,10 +66,48 @@ when its default features leave teksilo out, the commands resolve with every feature enabled, and `status` names the feature that turns teksilo on. `status` reports the resolved app version and installed tooling without -changing the lockfile. `--verbose` adds paths and diagnostics. `--quiet` +changing the lockfile. When this tool, or the automation server or formatter on +PATH, is older than the app's release, it prints the command that updates it. `--verbose` adds paths and diagnostics. `--quiet` suppresses informational output while preserving results, errors, and the line that says which release answered. +## Updating after a framework upgrade + +After moving the app to a new Teksilo release, install the matching tool, then +update what it installed: + +```sh +cargo install cargo-teksilo --version --locked +cargo teksilo update +``` + +`update` lists what differs from this release and asks before changing it: the +probe harness, the project's agent instructions, and the +`teksilo-automation-mcp` and `cargo-teksilo-fmt` binaries on PATH, which it +reinstalls with `cargo install` into the root they came from. Nothing absent is +installed. Use `-y` in scripts, `-i` to confirm each action (it also shows the +equivalent command, such as `cargo teksilo agent install claude --force`), or +`--dry-run` to list the changes and make none. + +```sh +cargo teksilo update --agent # the project's agent instructions only +cargo teksilo update --user # the instructions under your home directory instead +``` + +An agent copy that differs from this release may hold local edits, which +`update` replaces once confirmed. Probe files with local edits are listed by +name; with `-y`, they are replaced only with `--force`. A tool older than the +app is refused, because it would write an older release's files; `status` says +so and prints the `cargo install` command. + +`update` and `init` never wait for an answer nobody can give. When stdin is not +a terminal, or `$CI` or `$CLAUDECODE` is set, they print the listing and exit +with an error that names `-y`, and `-i` refuses before printing anything. The +shipped instructions tell an agent to run `--dry-run`, show the user the list, +and pass `-y` once they agree. A session that is already running keeps the +instructions it started with, and an MCP client keeps the server it spawned, so +`update` ends by saying which to restart. + ## Semantic search Download the model explicitly: diff --git a/docs/density-and-targets.md b/docs/density-and-targets.md index b4afacfb6..0129b34d7 100644 --- a/docs/density-and-targets.md +++ b/docs/density-and-targets.md @@ -103,7 +103,7 @@ recipe constructor, because there is nothing in such a theme to re-run, every `Recipe*Style` lazily at its own build site from `ctx.theme().input`. A slot an app installed itself is preserved as it is, so a hand-written Tier-3 style keeps whatever metrics its author gave it. A preset that installs slots of its own -(Material 3, Fluent, macOS) registers a `DensityProjection`, and `with_density` +(Material 3, Fluent, macOS, KDE) registers a `DensityProjection`, and `with_density` takes that function's answer instead, so the preset's own chrome follows the density. diff --git a/docs/features.md b/docs/features.md index 852ab5102..0a40ef981 100644 --- a/docs/features.md +++ b/docs/features.md @@ -20,7 +20,7 @@ **Internationalization.** Translations are checked at compile time via macro on top of Fluent: missing or misspelled keys are build errors. Right-to-left layout, locale-aware number and date formatting, and re-rendering on locale change are built in. -**Themes.** Default light and dark, inspired by JetBrains' Int UI; switching is instant and preserves focus, scroll position, and selection. On Linux, the active palette (accent, surface, selection, tooltip colors) follows the desktop environment (GNOME, KDE, Cinnamon). Material 3, Fluent (Windows 11) and macOS Aqua presets ship behind the `theme-material3` / `theme-fluent` / `theme-macos` features. Apps can override anything from a single color to a whole widget's chrome via the four-tier styling system (tokens → variants → recipes → style protocols), described in [`docs/styling-system.md`](styling-system.md). +**Themes.** Default light and dark, inspired by JetBrains' Int UI; switching is instant and preserves focus, scroll position, and selection. Native mode adopts the live desktop colours: on Linux the active palette (accent, surface, selection, tooltip colors) follows the desktop environment (GNOME, KDE, Cinnamon) and is re-read when the desktop changes it, with the shapes still Int UI's (see [`docs/native-theme.md`](native-theme.md)). The presets are a separate thing: Material 3, Fluent (Windows 11), macOS Aqua and KDE Plasma (Breeze) ship behind the `theme-material3` / `theme-fluent` / `theme-macos` / `theme-kde` features, each with its own geometry and widget chrome, and the KDE preset also gives Native mode on a Breeze desktop Breeze's shapes through `native_theme_kde()`. Apps can override anything from a single color to a whole widget's chrome via the four-tier styling system (tokens → variants → recipes → style protocols), described in [`docs/styling-system.md`](styling-system.md). **Input.** Keyboard shortcuts, menus, and accessibility actions flow through one rebindable pipeline; a user remap updates every surface that mentions the binding. External-source events (databases, file watchers) bypass it; widgets subscribe directly. diff --git a/docs/milestones.md b/docs/milestones.md index ea326f7df..985eb4d47 100644 --- a/docs/milestones.md +++ b/docs/milestones.md @@ -33,7 +33,7 @@ in each area. All dates below are in 2026. | Framework foundations | Retained widget tree, reactive state, layout, GPU rendering, focus, events, scrolling, and animation. Later work improved API consistency and GPU/backend fallbacks. | March to April; refinements through September | | Desktop widgets and application integration | Controls, menus, dialogs, tabs, pickers, actions and shortcuts, multiple windows, settings, async execution, native desktop integration, and OS drag-and-drop. | April to July | | Rich text and internationalization | Rich-text editing, clipboard interchange, IME integration, bidirectional caret movement, search, inline images, translation checks, reactive language changes, and locale-aware formatting. | April to August | -| Styling and themes | Reactive themes, semantic colors, configurable paint recipes, and widget style protocols. Material 3, Fluent, and macOS presets alongside Int UI. | April to August | +| Styling and themes | Reactive themes, semantic colors, configurable paint recipes, and widget style protocols. Material 3, Fluent, macOS, and KDE Plasma presets alongside Int UI, and a Native mode that follows the live desktop. | April to October | | Data views, docking, and graphics | Virtualized data views, keyed selection, pluggable sources, editable tables, docking, charts, and a scene canvas with selection, snapping, transforms, and ink. Specialized surfaces include code editing, terminal, and log viewing. | April to July; scene extensions in September | | Developer tooling and automation | Widget previewer, runtime inspector, DSL formatter and language server, screenshots, cross-platform MCP automation, and cargo teksilo. Documentation and onboarding consolidated in September and October. | April to October | | Accessibility and input | Keyboard navigation, focus scopes, contrast checks, accessible text and actions, and screen-reader corrections. Unified touch, pen, and trackpad support shipped in 0.10.0; physical hardware validation remains pending. | Foundations in April; major extensions June to September | diff --git a/docs/native-theme.md b/docs/native-theme.md new file mode 100644 index 000000000..7bdb0c402 --- /dev/null +++ b/docs/native-theme.md @@ -0,0 +1,221 @@ + + + +# Native theme + +An app can let the desktop decide how it looks. `ThemeMode` says how: + +| Mode | The theme is | Set with | +| --- | --- | --- | +| `Manual` (default) | the one the app picked | `.theme(intui::light())` | +| `FollowSystem` | Int UI light or dark, as the desktop's light or dark preference says | `.theme_mode(ThemeMode::FollowSystem)` | +| `Native` | Int UI in the desktop's own colours, or a theme of the app's own making | `.theme_mode(ThemeMode::Native)` | + +At runtime a handler switches to `Native` with `ctx.follow_system_theme()`, which +is what the "System" entry of a [`ThemeSwitcher`](widgets/theme_switcher.md) +calls, and back to `Manual` with `ctx.set_theme(..)`. + +This page is about `Native`: what it reads on each platform, how an app gives it +shapes as well as colours, and what it does when the desktop changes. + +## What Native mode reads + +`teksilo_platform::os_theme::query_native_desktop()` answers with a +`NativeDesktop`: `os`, the colour report (`OsThemeColors`: light or dark +preference, accent, window, button, selection and tooltip colours, the +content-view colours, links and status colours), and `kde`, the KDE desktop +itself on a KDE Plasma session (`None` elsewhere). Every colour is an +`Option`; a colour the desktop does not state is left to the Int UI default. + +| Platform | What the report holds | +| --- | --- | +| Linux, KDE Plasma | everything below | +| Linux, GNOME | light or dark and the accent colour from the desktop portal (the GNOME named accent as a fallback), and the surface, selection and tooltip colours of the active GTK theme's CSS | +| Linux, Cinnamon | the accent inferred from a Mint-Y theme's name, and the colours of the GTK theme | +| macOS, Windows | only a light or dark state, from the window system (`window.theme()`); no colours | + +Where the desktop states no light or dark preference of its own (macOS, +Windows), the window system's state decides, and a window with none reads as +light. Where it states one, the desktop wins. + +### The KDE reader + +A session is a KDE session when `XDG_CURRENT_DESKTOP` lists `KDE` or +`KDE_FULL_SESSION` is `true`. The reader is `teksilo-kde-scheme`: it does no +I/O itself, and `teksilo-platform` hands it the environment and the contents of +the files it asks for. It reads: + +- the `kdeglobals` cascade (system layers, `kdedefaults`, the user's file), + with KDE's immutable (Kiosk) entries honoured; +- the **colour scheme in effect**: the `[Colors:*]` groups of `kdeglobals`, + which already carry any accent the user chose; when there are none, the + look-and-feel package's colours, else the scheme named by + `[General] ColorScheme`, else Breeze Light, with `[General] AccentColor` + applied to it as Plasma applies it; +- the five desktop **fonts**, `[KDE] AnimationDurationFactor` and + `[KDE] widgetStyle`; +- the **`breezerc`** cascade: every Breeze widget-style key, carried as + `BreezeStyleConfig` (the preset paints from the menu opacity, the window + shadow and the busy progress bar). + +The light or dark preference is KDE's own judgement of the scheme (the window +background's grey value), the rule KDE reports to applications. On a session +whose `kdeglobals` cannot be read at all, the report falls back to the +desktop portal's preference and accent and there is no `kde` value. + +An animation speed of 0 (`AnimationDurationFactor`) also turns on the +reduced-motion preference. `forceFontDPI` is not read: no current Plasma +component honours it. + +## From a report to a theme + +When Native mode needs a theme (a window opening, the desktop changing, a +switch to Native mode) it builds a `NativeThemeRequest { desktop, dark, +density }` and calls `resolve_native_theme(factory, &request)`: + +1. if the app installed a factory with `TeksiloAppBuilder::native_theme` and it + answers `Some(theme)`, that is the theme; +2. otherwise it is `presets::intui::from_os(&desktop.os, dark)`: Int UI + with its colours replaced by the desktop's. + +`NativeThemeRequest` is `#[non_exhaustive]`; build one with +`NativeThemeRequest::new(&desktop, dark, density)`. + +`resolve_native_theme` sets no id and applies no density. The window manager +does both for every theme that follows the OS (Native and FollowSystem alike): +the theme gets the id `system`, so a `ThemeSwitcher` shows "System" as selected, +and is projected onto the density of the window it is for. + +A factory is for an app that wants the desktop's shapes too: + +```rust,ignore +TeksiloAppBuilder::new() + .theme_mode(ThemeMode::Native) + .native_theme(|request| { + // `None` keeps Int UI in the desktop's colours. + let kde = request.desktop.kde.as_ref()?; + my_theme_for(kde, request.dark, request.density) + }) + .run(); +``` + +`configured_native_theme()` returns the installed factory, for an install hook +or a test. + +### `native_theme_kde()` + +With the `theme-kde` feature the prelude has `TeksiloAppBuilderKdeExt`, and + +```rust,ignore +TeksiloAppBuilder::new() + .theme_mode(ThemeMode::Native) + .native_theme_kde() + .initial_window(WindowConfig::new().root(|tree, _state| tree.add(MyRoot::new()))) + .run(); +``` + +installs `kde_native_theme` as the factory. It answers +`kde::native_theme(desktop, density)`: the desktop's colour scheme with the +user's accent, its fonts, its animation speed and its `breezerc` settings, in +Breeze's geometry and widget chrome. It answers **only for a Breeze desktop**: +`kdeglobals [KDE] widgetStyle` absent, empty or `Breeze`. For a KDE desktop +styled with Fusion, Oxygen or Kvantum, for a KDE desktop with no settings and +for every other desktop it answers `None`, and Native mode keeps Int UI in the +desktop's colours, because a Teksilo app in Breeze's shapes would not match the +other applications there. + +Setting a factory does not select Native mode; combine it with `.theme_mode`. +`ThemeMode::FollowSystem` never asks the factory. + +### Native mode and the KDE preset are two things + +- **Native mode adopts the live desktop colours.** With no factory it works on + GNOME, Cinnamon and KDE alike, and the shapes are Int UI's. +- **The KDE preset is Breeze.** `kde::light()` and `kde::dark()` are the stock + Breeze Light and Dark in Breeze's shapes, with no desktop involved, on any + platform. `kde::from_scheme`, `kde::from_desktop` and + `kde::native_theme` build the theme of a given scheme or desktop. See + [the styling system](styling-system.md#built-in-presets). + +`native_theme_kde()` is the two together. + +## Following the desktop while the app runs + +Under `Native` and `FollowSystem`, the window manager starts an +`OsThemeWatch` (Linux; elsewhere `OsThemeWatch::new` answers `None` and the +window system's own light or dark notification is all there is). It listens on +the D-Bus session bus, on a thread of its own, for: + +- the desktop portal's `SettingChanged` for the appearance namespace (colour + scheme, accent colour, contrast, reduced motion) and for KDE's + `org.kde.kdeglobals.*` namespaces; +- KDE's `org.kde.KGlobalSettings.notifyChange`; +- KDE's `org.kde.kconfig.notify.ConfigChanged` for `kdeglobals` and `breezerc`. + +A change that arrives as several signals within a few milliseconds is reported +once, 150 ms after the first. The window manager then reads the desktop again +and gives each window the theme it now resolves to; a re-read that changes +nothing a theme was resolved from sets no theme. The reduced-motion preference is +refreshed from the same signal. On a session with no bus, or a signal that +never came, a change to `kdeglobals` or `breezerc` is found by modification time +the next time a window takes focus. Under `Manual` nothing is watched. + +## Known limitation: switching re-tints, the shapes stay + +A widget's chrome (which `Tier-3` style paints it) is chosen when the widget is +built, from the theme the tree has then. Changing the theme later is a colour +swap, with no rebuild, which is why it keeps focus and scroll position. So: + +- a window built while the app is in Native mode asks the factory, and shows + Breeze's shapes on a Breeze desktop; +- switching to Native mode at runtime, picking "System" in a `ThemeSwitcher`, or + the desktop changing its colours re-tints the windows that are open, but keeps + the shapes they were built with. If the desktop moves from Breeze to another + widget style while the app runs, the open windows keep Breeze's shapes until + they are built again; +- the same holds between presets: picking `kde-dark` in the title-bar switcher + of `widget-catalog` over a tree built as Int UI changes the colours only. + `cargo run -p widget-catalog -- --theme kde-dark` builds the tree in Breeze's + chrome, and `--theme system` builds it in Native mode. + +See [the reactive theme](reactive-theme.md) for why a theme swap never rebuilds. + +## Testing + +`NativeDesktop::new(OsThemeColors::new(..))` builds a report with no KDE +settings, and `KdeDesktop::new(KdeColorScheme::breeze_dark())` a KDE desktop at +Plasma's defaults, so a test can call `resolve_native_theme` or a factory +without a desktop. `teksilo-kde-scheme` takes the environment and the files as +arguments for the same reason. + +### The Breeze preset in the running catalog + +`tools/reader/kde_visual_check.sh` checks the KDE preset as it renders in the +real `widget-catalog`, and in Native mode on a KDE desktop. It builds the debug +catalog and `teksilo-automation-mcp`, then runs everything in a private session +(`tools/reader/private_session.sh`: its own D-Bus bus, `kwin_wayland --virtual` +and configuration home). The session keeps the desktop's `XDG_CONFIG_DIRS`, +which on Plasma lists `~/.config/kdedefaults` and `/etc/xdg`, so the check +gives the catalog and Plasma's tools a configuration list of their own, one +private directory, and sets `KDE_SKIP_KDERC`: no `kdeglobals` of the machine +(its widget style, fonts, animation factor or default scheme) reaches what is +measured, and neither the catalog nor Plasma's tools write to the desktop or +`~/.config`. For `--theme kde-light`, `kde-dark` and `system` in turn, it gives +the private configuration a Breeze scheme with `plasma-apply-colorscheme`, +starts the catalog as on a KDE session, and measures over the automation bridge +the push button, the tooltip of an icon button, line edit, combo box, spin box, +check box and radio button rows, switch, slider, progress bar, list and tree rows, +table header, menu row, tab and scroll bars. It prints each height beside +Plasma 6.8's own (`tools/kde_golden/vectors/sizes.json`), the specification's +and the preset's, with the documented deviations, and fails on a control more +than 1 px from the preset's height or missing from the catalog. The table row, +and the `ListView` and `TreeView` rows, are printed as INFO and not judged: the +catalog sets their heights itself. In `system` mode it then switches the desktop +to Breeze Dark and to another accent while the catalog runs. For each change it +checks that the window presented a frame without any input (the catalog's +`TEKSILO_IDLE_TRACE` counts the frames drawn to the window, and the span after +the change is compared with the same span when nothing changed), that the +widget tree, rendered offscreen by the bridge's screenshots, paints in the new +colours, and that it is the same widget tree. The screenshots do not read the +frame on screen. It needs `kwin_wayland` and Plasma's +`plasma-apply-colorscheme`, so it is not part of `cargo test`. diff --git a/docs/project-information.md b/docs/project-information.md index a2dd9c62f..d3c589964 100644 --- a/docs/project-information.md +++ b/docs/project-information.md @@ -155,7 +155,7 @@ See [TRADEMARKS.md](https://github.com/ferntech-eu/teksilo/blob/main/TRADEMARKS. Teksilo builds on the work of others: AccessKit; winit and wgpu; HarfBuzz (via harfrust), swash, fontdb, etagere, and ICU4X; unicode-bidi; Fluent and the Mozilla l10n team; the published design notes of the Druid, Masonry, and Xilem projects; and SwiftUI's layout protocol. Anthropic and Mistral provided the language models whose code generation contributed substantially under human review. -The theme presets follow design languages published by others: JetBrains' Int UI, which the default light and dark themes are drawn from; Microsoft's Fluent and the WinUI theme resources; Google's Material 3; and Apple's macOS Human Interface Guidelines. Each preset is an independent implementation, and none of those vendors is affiliated with or endorses Teksilo. See [TRADEMARKS.md](https://github.com/ferntech-eu/teksilo/blob/main/TRADEMARKS.md) and [NOTICE](https://github.com/ferntech-eu/teksilo/blob/main/NOTICE). +The theme presets follow design languages published by others: JetBrains' Int UI, which the default light and dark themes are drawn from; Microsoft's Fluent and the WinUI theme resources; Google's Material 3; Apple's macOS Human Interface Guidelines; and the Breeze style of KDE Plasma. Each preset is an independent implementation, and none of those vendors or projects is affiliated with or endorses Teksilo. See [TRADEMARKS.md](https://github.com/ferntech-eu/teksilo/blob/main/TRADEMARKS.md) and [NOTICE](https://github.com/ferntech-eu/teksilo/blob/main/NOTICE). ## Maintaining these pages diff --git a/docs/reactive-theme.md b/docs/reactive-theme.md index e9fc19f4b..ee3b8752a 100644 --- a/docs/reactive-theme.md +++ b/docs/reactive-theme.md @@ -107,6 +107,14 @@ let dark = intui::dark(); Both are neutral Int UI baselines, not visually distinctive, designed to be customized. Apps usually start from one of them and override the slots they care about. For the full styling picture (variants, recipes, style traits) see [`styling-system.md`](styling-system.md). +The sibling presets (`material3`, `fluent`, `macos`, `kde`) follow the same shape, behind the umbrella features `theme-material3`, `theme-fluent`, `theme-macos` and `theme-kde`. + +### Themes that follow the desktop + +`TeksiloAppBuilder::theme_mode(ThemeMode::FollowSystem)` and `ThemeMode::Native` hand the choice to the desktop, and the app's `WindowManager` keeps every window's theme in step with it. Each change is an ordinary `set_theme` on each window's tree, so it is a re-tint with no rebuild: focus and scroll position survive, and so does the shape of every widget that was already built. The desktop's changes are heard as they happen (an `OsThemeWatch` on Linux) and re-read; a re-read that changes nothing a theme was resolved from sets no theme. A theme built from the desktop carries the id `system`. + +The consequence worth knowing is the one `set_theme` always has: a swap changes the theme's tokens, but a widget chose its chrome (which style slot paints it) when it was built. A tree built as Int UI that is switched to Breeze, or to Native mode on a Breeze desktop, is Breeze's colours on Int UI's shapes; build the window in the mode to get both. What each mode reads, `native_theme_kde()`, and the live watch are in [`native-theme.md`](native-theme.md). + ### Programmatic customization via struct spread `Theme` (`teksilo-core::styles`) and the token structs `ColorTokens`, `TypographyTokens`, `ShapeTokens`, `LayoutTokens`, `MotionTokens` (`teksilo-tokens`) are plain structs. Override the fields you want and spread the rest from a preset base: @@ -177,7 +185,7 @@ Filled-area color (panel backgrounds, button fills, selection highlights). `Main` (default), `Content`, `Raised`, `Sunken`, `Hover`, `Pressed`, `Selected`, `SelectedInactive`, `AltRow`, `Accent`, `AccentHover`, `AccentPressed`, `AccentDisabled`, `Field`, `Disabled`, `AccentSubtle`, `StatusInfo`, `StatusSuccess`, `StatusWarning`, `StatusError`, `ErrorContainer`, `Container`, `ContainerRaised`, `ContainerSunken`, `TooltipBg`, `EditorBg`, `EditorCaret`, `EditorCurrentLineBg`, `EditorSelectionBg`, `Scrim`, **`Transparent`** (paints nothing, the "no surface" slot in interaction chains). -The `OnError`/`*Container` roles are the cross-design-language slots shared by Material 3 / Fluent / macOS / GTK4-Adwaita; design-language-specific colors (M3 secondary/tertiary triads, full tonal ladder) live in per-theme extensions instead. +The `OnError`/`*Container` roles are the cross-design-language slots shared by Material 3 / Fluent / macOS / KDE / GTK4-Adwaita; design-language-specific colors (M3 secondary/tertiary triads, full tonal ladder) live in per-theme extensions instead. ### `BorderRole` Stroke color. diff --git a/docs/scroll-area.md b/docs/scroll-area.md index e2c03e5ee..a4b2501ce 100644 --- a/docs/scroll-area.md +++ b/docs/scroll-area.md @@ -483,6 +483,8 @@ Scroll-bar chrome is a Tier-3 style protocol, ```rust pub trait ScrollBarStyle: 'static { fn make_body(&self, cfg: &ScrollBarStyleConfig, ctx: &mut BuildContext) -> WidgetId; + // Defaulted: 24 at the compact density, 32 comfortable, 44 touch. + fn min_thumb_length(&self, tokens: &InputTokens) -> f32 { /* ... */ } } ``` @@ -493,6 +495,13 @@ override. The default `RecipeScrollBarStyle` ([recipe](../crates/teksilo-widgets/src/styles/recipe_scroll_bar_style.rs)) paints all three variants from IntUI tokens. +`min_thumb_length` is the shortest the thumb may get on a long document. The +bar asks its style for it at build time, sizes its thumb from the answer, and +takes its hit test and its drag from the same thumb, so the config's +`min_thumb_length` is that answer. The macOS preset floors the thumb at 28 and +Fluent at 30 at the compact density, both raised to the density's target size +above it. A bar's own `.min_thumb_length(..)` wins over the style. + Install it theme-wide: ```rust diff --git a/docs/styling-system.md b/docs/styling-system.md index c72641192..ca5fde4a8 100644 --- a/docs/styling-system.md +++ b/docs/styling-system.md @@ -66,7 +66,7 @@ let theme = intui::light(); // or intui::dark() ``` Other presets ship as opt-in Cargo features (Material 3, macOS, -Fluent), only IntUI is bundled by default. +Fluent, KDE), only IntUI is bundled by default. **Reactive.** `Theme` lives behind a `Signal` on `WidgetTree`, `set_theme(...)` dirty-marks every widget for relayout @@ -218,7 +218,7 @@ button styling, destructive actions live in confirmation dialogs where the body carries the warning, not the button. So the IntUI `RecipeButtonStyle` collapses several variants: `Destructive` → Filled, `Tinted`/`Outlined` → Plain, `Link` → Ghost. -Other design languages (Material 3, Fluent, macOS) honour them +Other design languages (Material 3, Fluent, macOS, KDE) honour them distinctly. ## Tier 2: Recipes @@ -438,8 +438,48 @@ under `theme_slot_supplies_button_style_when_no_override` / | `material3::light` / `material3::dark` | `teksilo-theme-material3` crate | shipped, Material 3 | | `fluent::light` / `fluent::dark` | `teksilo-theme-fluent` crate | shipped, Windows 11 / WinUI 3 | | `macos::light` / `macos::dark` | `teksilo-theme-macos` crate | shipped, macOS Aqua / Dark Aqua | +| `kde::light` / `kde::dark` | `teksilo-theme-kde` crate | shipped, KDE Plasma's Breeze | | Image-backed themes | `teksilo-image-theme` crate | not yet shipped | +The four sibling presets are behind the umbrella features `theme-material3`, +`theme-fluent`, `theme-macos` and `theme-kde`, and the prelude re-exports each +crate under its short name (`material3`, `fluent`, `macos`, `kde`). + +**KDE Plasma's Breeze** (`kde`, `teksilo-theme-kde`) differs from the others in +where its colours come from. They are a KDE colour scheme, read and resolved by +`teksilo-kde-scheme` exactly as KDE resolves it: `kde::light()` and +`kde::dark()` are the stock Breeze Light and Dark, `kde::from_scheme(&scheme)` +the theme of any scheme (light or dark as KDE judges it), +`kde::light_with_accent(PlasmaAccent::Purple.color())` the stock scheme with +Plasma's accent applied, and `kde::from_desktop(&desktop, density)` the theme +of a KDE desktop as it is configured (its scheme with the user's accent, its +fonts, its animation speed, its `breezerc`). The scheme's own colours stay on +the `KdePalette` theme extension. Where the scheme falls short of WCAG, the +colour tokens are lifted by rule and the extension keeps the vendor colour. The +geometry is Breeze's (a 5 px radius, text-driven control heights, a 2 px focus +ring half inside the control), the motion Breeze's 100 ms linear, and the +text sizes and line boxes Noto Sans's and Hack's at Plasma's defaults. The +default build draws them in Teksilo's bundled faces; the font features +`bundled-fonts` (embeds Noto Sans and Hack) and `system-fonts` (names them) are +the crate's own and are not forwarded by the umbrella, so an app that wants +Plasma's faces depends on `teksilo-theme-kde` directly and passes +`kde::font_registrar()` to `TeksiloAppBuilder::register_fonts`. + +It uses three hooks the other presets do not need. Its `PaintProjection` paints +the scheme's own Inactive and high-contrast colours instead of the generic +desaturation (see [window-activation.md](window-activation.md)), its list +rows return `SelectionEmphasis::WindowActive` from +`StandardItemStyle::selection_emphasis`, because Qt mutes a selection per +window and not per focused view, and its menu rows return a label role from +`MenuItemStyle::pressed_label_role` (a held-down menu row is filled with the +accent, so its label takes the on-accent text role). Which widgets carry Breeze's own chrome and +which keep Teksilo's recipe styles built with Breeze's metrics is listed under +"Known limitations" in the crate documentation. + +The colours of the desktop a program is running on, as opposed to a preset +that reproduces a design language, are the job of Native mode; see +[native-theme.md](native-theme.md). + Each preset is just a function returning `Theme`. Apps can write their own without depending on any sibling crate: diff --git a/docs/tab-widget.md b/docs/tab-widget.md index 7f785dfb8..a204faebd 100644 --- a/docs/tab-widget.md +++ b/docs/tab-widget.md @@ -329,7 +329,7 @@ pub enum TabSizing { | Orientation | "Layout axis" | Default | Meaning | |--------------|---------------|---------------|-------------------------------------------------------------------| | `Horizontal` | width | `Shared` | Uniform tab widths (Firefox / Chrome convention). | -| `Vertical` | height | `Shared` | Uniform pill heights, fixed at `editor_tab_height`. | +| `Vertical` | height | `Shared` | Uniform pill heights, fixed at the style's `editor_height`. | Pinned tabs are **always fixed-extent** (`pinned_tab_width`) regardless of `TabSizing`, that's what "pinned" means visually. @@ -353,8 +353,8 @@ The two orientations apply Shared sizing differently: wheel remap, dropdown engage normally). - **Vertical does NOT divide the viewport.** Sidebar pills stay at - the intrinsic per-tab height (`TAB_EDITOR_HEIGHT`, default 50 dp) - regardless of how tall the bar is. A 800 dp bar with + the intrinsic per-tab height (the tab style's `editor_height`, 50 dp + under Int UI) regardless of how tall the bar is. A 800 dp bar with 4 tabs gives 4 pills of 50 dp at the top, not 4 × 200 dp bands. This matches VS Code, IntelliJ tool-window tabs, and the user expectation of sidebar tabs being short pills. The `min_tab_width` @@ -907,14 +907,24 @@ editor-strip convention); `idle_text_role` defaults to the default cascade reads with insufficient contrast. Disabled tabs always render at `TextRole::Disabled`. -Static numbers are `pub const`s in +The tab geometry comes from the active `TabStyle`, through two +defaulted accessors a bar and its headers read at layout time: + +- `TabStyle::editor_height(tokens)`, the outer height of one tab: the + strip height of a horizontal bar and the pill height of a vertical one. + Int UI's `RecipeTabStyle` answers `TAB_EDITOR_HEIGHT` (50 dp at every + density); the macOS preset answers 26 dp and the Fluent preset 32 dp at + the compact density, both raised to the density's target size above it. + `TabWidget::tab_bar_height(dp)` / `compact_bar()` (38 dp) override it. +- `TabStyle::padding_horizontal(tokens)`, the gap between a tab's edges + and its content along the bar. Int UI answers `TAB_PADDING_HORIZONTAL` + (12 dp at the compact density, scaled by the spacing factor above it); + Fluent answers 8 dp. Pinned tabs centre their icon and ignore it. + +A custom `impl TabStyle` that overrides neither gets the Int UI numbers. +The remaining static number is a `pub const` in [`recipe_tab_style`](../crates/teksilo-widgets/src/styles/recipe_tab_style.rs): -- `TAB_EDITOR_HEIGHT` (default 50 dp), height of horizontal bar tabs. -- `TAB_TOOL_WINDOW_HEIGHT` (default 28 dp), reserved for future - tool-window tab variant; not currently consumed by vertical bars. - `TabWidget::tab_bar_height(dp)` / `compact_bar()` (38 dp) override the - 50 dp strip height instead. - `TAB_UNDERLINE_ACTIVE` (default 2 dp), thickness of the selection indicator. The indicator's color comes from `theme.colors.accent`. @@ -975,10 +985,8 @@ TabWidget::new(selected) hit the existing `ScrollDelta::Pixels` path with `Easing::EaseOut` animation; touch flicks would need `ScrollArea` ↔ `SwipeRecognizer` wiring, ~150 LOC, separate task) -- `TabRecipe::tool_window_height` (`TAB_TOOL_WINDOW_HEIGHT`, 28 dp) is - reserved on the default `RecipeTabStyle` but not yet consumed by - vertical bars, they currently pick up the editor-tab height - (`TAB_EDITOR_HEIGHT`) like horizontal bars +- a separate tool-window tab height: vertical bars use the same + `editor_height` as horizontal ones --- diff --git a/docs/table-view.md b/docs/table-view.md index b03c89be4..4819a8765 100644 --- a/docs/table-view.md +++ b/docs/table-view.md @@ -92,7 +92,10 @@ Three mutually exclusive modes (the last builder call wins), identical on - **Uniform** (`row_height`), every row is the same height. Pure arithmetic, no allocation; this is the historical behavior and stays - the default (28 px from the table style). + the default, at the active `TableStyle`'s `row_height` (28 px under + IntUI at Compact, raised to the density's target size above it; 24 px + under macOS and 40 px under Fluent at Compact). A `row_height(..)` the + app sets wins over the style. - **Exact** (`row_height_fn`), a pure callback `fn(visible_index) -> f32` seeds a prefix-sum offset table (O(log n) row↔y lookups). No measurement pass, exact scrollbar, zero jitter. The callback is @@ -541,8 +544,8 @@ header by a frame, and a headless test needs a second `layout()` before it lines up. On its own the header resolves its widths -against its own bounds, clips its cells to them, takes `HEADER_HEIGHT` when its -parent leaves the height free, and paints the `ColumnResizePolicy::OnRelease` +against its own bounds, clips its cells to them, takes the active +`TableStyle`'s `header_height` when its parent leaves the height free, and paints the `ColumnResizePolicy::OnRelease` guide line within the strip only. **Accessibility.** The strip is the `Role::Row` with row index 1 and its cells @@ -1021,7 +1024,10 @@ PgDn handler uses. Static numbers (`ROW_HEIGHT`, `HEADER_HEIGHT`, `RESIZE_HANDLE_WIDTH`, `GRID_LINE_THICKNESS`, `TREE_INDENT_PER_LEVEL`, …) are `pub const`s in [`recipe_table_style`](../crates/teksilo-widgets/src/styles/recipe_table_style.rs) -They are snapshot at build time, like every other widget. +They are snapshot at build time, like every other widget. The row and header +heights a view lays out come from the active `TableStyle`'s `row_height` and +`header_height`, which `RecipeTableStyle` answers from its `TableRecipe`; +`ROW_HEIGHT` and `HEADER_HEIGHT` are the IntUI values that recipe starts from. --- diff --git a/docs/widgets-overview.md b/docs/widgets-overview.md index 3541316cc..ba822bb18 100644 --- a/docs/widgets-overview.md +++ b/docs/widgets-overview.md @@ -49,11 +49,11 @@ were deleted; their IntUI constants now live in the matching `teksilo-widgets/src/styles/recipe_*_style.rs` modules. The dimension data for non-themable widgets (toolbar, status bar, dialog, accordion, badge, progress bar, table, …) lives directly in those same -`recipe_*_style.rs` modules as `pub const` blocks. Three sibling preset +`recipe_*_style.rs` modules as `pub const` blocks. Four sibling preset crates ship, Material 3 (`theme-material3`), Fluent / Windows 11 -(`theme-fluent`) and macOS Aqua (`theme-macos`). Image-backed styles, -the `ImageTheme` TOML loader, and a GTK4-Adwaita preset are still -pending. +(`theme-fluent`), macOS Aqua (`theme-macos`) and KDE Plasma's Breeze +(`theme-kde`). Image-backed styles, the `ImageTheme` TOML loader, and a +GTK4-Adwaita preset are still pending. End-to-end demo of the slot bag + per-call override: see [`examples/theme_styles/`](../examples/theme_styles/). diff --git a/docs/widgets/bar.md b/docs/widgets/bar.md index 70260635e..cfa0410ae 100644 --- a/docs/widgets/bar.md +++ b/docs/widgets/bar.md @@ -350,7 +350,7 @@ In **vertical** orientation every tab is forced to the bar's cross-axis width, so the same knob defines the bar's minimum width — the sidebar adapts to the widest piece of bar content (tab labels or a slot widget) and never shrinks below this floor. -Vertical pill heights stay at the tab style's `editor_tab_height` +Vertical pill heights stay at the tab style's `editor_height` regardless of this knob. Under `TabSizing::Fill` a **vertical** bar takes the width it is @@ -364,7 +364,7 @@ scroll rather than squeeze below it). Override the tab-strip cross-axis extent (the strip height for a horizontal bar; the per-tab pill height for a vertical one). `None` -keeps the style's `editor_tab_height`. Use for a compact bar. +keeps the style's `editor_height`. Use for a compact bar. diff --git a/docs/widgets/progress_bar.md b/docs/widgets/progress_bar.md index db5454b91..a7cb6593c 100644 --- a/docs/widgets/progress_bar.md +++ b/docs/widgets/progress_bar.md @@ -117,7 +117,8 @@ vertical indeterminate bars use the signal-driven path instead. #### `pub fn thickness(mut self, thickness: f32) -> Self` Set the bar's narrow dimension in logical pixels. For horizontal bars -this is the height; for vertical bars this is the width. Default is 4.0. +this is the height; for vertical bars this is the width. Default is the +active style's thickness, 4.0 for the shipped style. diff --git a/docs/widgets/scroll_bar.md b/docs/widgets/scroll_bar.md index 05ff27aa0..879a1aa35 100644 --- a/docs/widgets/scroll_bar.md +++ b/docs/widgets/scroll_bar.md @@ -64,8 +64,10 @@ Because the outset widens the bar *across* the scroll axis, every decision about whether a press is on the thumb is taken **along the axis only** — a finger 15 dp inboard of an 8 dp bar is beside the thumb, not past it. -The minimum thumb length follows the density (24 dp Compact, 44 dp Touch), -so a short thumb on a long document is still something a finger can land on. +The minimum thumb length is the active style's +`ScrollBarStyle::min_thumb_length`, which follows the density (24 dp +Compact, 44 dp Touch under the shipped style), so a short thumb on a long +document is still something a finger can land on. #### Accessibility @@ -123,9 +125,16 @@ pub const SCROLLBAR_COARSE_TARGET: f32 = 48.0; **Import:** `use teksilo::widgets::scroll_bar::SCROLLBAR_MIN_THUMB_LENGTH;` -The shipped minimum thumb length, at Compact. Raised to the density's -`target_size` (44 dp at Touch) at build time; a -`min_thumb_length` override wins over both. +The shipped minimum thumb length, at Compact. + +A bar takes its floor from the active style's +`ScrollBarStyle::min_thumb_length` at build time: the shipped style and +the trait default both answer this value raised to the density's +`target_size` (32 dp at Comfortable, 44 dp at Touch), and a preset answers +its own (28 dp under macOS, 30 dp under Fluent, at Compact). Before its +first build a bar uses this value. A +`min_thumb_length` override wins over all of +them. ```rust pub const SCROLLBAR_MIN_THUMB_LENGTH: f32 = 24.0; @@ -195,11 +204,13 @@ Set the bar thickness (width for vertical, height for horizontal). #### `pub fn min_thumb_length(mut self, len: f32) -> Self` (default: `None`) -Set the minimum thumb length in pixels, overriding the density. +Set the minimum thumb length in pixels, overriding the active style. -Left unset the floor is `SCROLLBAR_MIN_THUMB_LENGTH` raised to the -density's target size — 24 dp at Compact, 44 dp at Touch — so a short -thumb on a long document stays something a finger can land on. +Left unset the floor is the active style's +`ScrollBarStyle::min_thumb_length` at the tree's density: under the +shipped style `SCROLLBAR_MIN_THUMB_LENGTH` raised to the density's +target size (24 dp at Compact, 44 dp at Touch), so a short thumb on a +long document stays something a finger can land on. diff --git a/docs/widgets/standard_item.md b/docs/widgets/standard_item.md index c777c0b4a..2bdfda223 100644 --- a/docs/widgets/standard_item.md +++ b/docs/widgets/standard_item.md @@ -165,13 +165,11 @@ the wrapper. #### Touch and pen -A row's **height** is a target floor and follows the density ladder: the row -projects the raw module constants through `density::dp` at layout time. It does -**not** read `StandardItemRecipe::min_height_single_line` / -`min_height_two_line`, which have carried the same projected values since the -density sweep and have no reader — so retuning either of those two recipe -fields in a preset moves nothing. `layout_response` says the same thing at the -site; `docs/touch-and-pen.md` §10.1 carries it as an open finding. +A row's **height** is a target floor and follows the density ladder. The row +asks the style it was built with for that floor, through +`StandardItemStyle::min_height_single_line` +and its two-line twin. The shipped recipe answers 28 / 44 dp at Compact, and +a preset that writes its own (macOS 24, Fluent 40) gets that height. A row's **press** is not the row's. Inside a `ListView` or a `TreeView` the body pane owns the tap, the double tap and the reorder drag, and resolves which diff --git a/docs/widgets/tab_widget.md b/docs/widgets/tab_widget.md index e5c7d1e16..3a464bbb1 100644 --- a/docs/widgets/tab_widget.md +++ b/docs/widgets/tab_widget.md @@ -291,7 +291,7 @@ resolves through to the mutable roots and never calls `observe`. #### `pub fn tab_bar_height(mut self, dp: f32) -> Self` Override the tab-strip height (its cross-axis extent). `None` / -unset keeps the style's `editor_tab_height` (50 dp). Use for a denser +unset keeps the style's `editor_height` (50 dp under Int UI). Use for a denser strip — e.g. dock side panels. diff --git a/docs/widgets/table_view.md b/docs/widgets/table_view.md index e3874eb24..924c05cc1 100644 --- a/docs/widgets/table_view.md +++ b/docs/widgets/table_view.md @@ -463,8 +463,10 @@ Append multiple `Column` definitions from an iterator. #### `pub fn row_height(mut self, height: f32) -> Self` -Fixed row height (default: the table style's 28 px) — the -uniform fast path. Mutually exclusive with +Fixed row height, the uniform fast path. Default: the active +`TableStyle`'s +`row_height`, 28 px +under IntUI at the Compact density. Mutually exclusive with `row_height_fn` and `auto_row_height`; the last mode setter wins. @@ -495,7 +497,10 @@ measurement change. #### `pub fn header_height(mut self, height: f32) -> Self` -Override the column header row height in logical pixels. Default: the table style's `HEADER_HEIGHT`. +Override the column header row height in logical pixels. Default: the +active `TableStyle`'s +`header_height`, +32 px under IntUI at the Compact density. diff --git a/docs/widgets/theme_switcher.md b/docs/widgets/theme_switcher.md index 2b8476c95..7a411aeb8 100644 --- a/docs/widgets/theme_switcher.md +++ b/docs/widgets/theme_switcher.md @@ -54,7 +54,7 @@ with an English literal fallback so a host app that hasn't installed an `I18nManager` still reads "Light / Dark / System" rather than raw keys. Custom themes: `.themes([(label, theme), …])` replaces Light/Dark with an -app-supplied set (e.g. the `teksilo-theme-{fluent,macos,material3}` presets); +app-supplied set (e.g. the `teksilo-theme-{fluent,kde,macos,material3}` presets); `.system(false)` drops the System entry. ## Density diff --git a/docs/widgets/tree_table_view.md b/docs/widgets/tree_table_view.md index d8caf3686..a4e49edba 100644 --- a/docs/widgets/tree_table_view.md +++ b/docs/widgets/tree_table_view.md @@ -609,8 +609,10 @@ inside it shows on the next frame. #### `pub fn row_height(mut self, height: f32) -> Self` -Fixed row height (default: the table style's 28 px) — the -uniform fast path. Mutually exclusive with +Fixed row height, the uniform fast path. Default: the active +`TableStyle`'s +`row_height`, 28 px +under IntUI at the Compact density. Mutually exclusive with `row_height_fn` and `auto_row_height`; the last mode setter wins. @@ -641,7 +643,10 @@ frame after a measurement change. #### `pub fn header_height(mut self, height: f32) -> Self` -Override the header row height in logical pixels. +Override the header row height in logical pixels. Default: the active +`TableStyle`'s +`header_height`, +32 px under IntUI at the Compact density. diff --git a/docs/window-activation.md b/docs/window-activation.md index ed12bf77f..d70653a09 100644 --- a/docs/window-activation.md +++ b/docs/window-activation.md @@ -43,7 +43,8 @@ These are correctness, not features, so they are **on by default**: - **Accent desaturation (theme-side, covers every control).** When the window is inactive, the paint walker swaps in a theme projection - (`ColorTokens::for_inactive_window`) whose **accent family and focus indicators + (`ColorTokens::for_inactive_window`, unless the theme registers its own + `PaintProjection`, see below) whose **accent family and focus indicators are desaturated toward graphite**, the macOS / Qt `QPalette::Inactive` model. Because every themed control resolves its accent from the live `ColorTokens` at paint time, this single @@ -76,6 +77,36 @@ Keyboard focus rings grey out (not hide) in an inactive window, uniformly, via the theme-side accent projection above, no per-widget check. `MenuList` is excluded by design, an open menu is always active. +### A style chooses what mutes a list row's selection + +For `StandardListItem` / `StandardTreeItem` the "view focused **and** window +active" rule above is the default of a style hook, +`StandardItemStyle::selection_emphasis() -> SelectionEmphasis`: + +| `SelectionEmphasis` | A selected row is emphasised while | Used by | +| --- | --- | --- | +| `ViewFocusedAndWindowActive` (default) | its view holds keyboard focus and its window is active | IntUI, Fluent, macOS, Material 3 | +| `WindowActive` | its window is active, whichever view holds focus | the KDE preset (Breeze), and any style following Qt's per-window colour groups | + +Qt picks a palette colour group per *window*: in an active window every view +paints its selection with the active `Highlight`, focused or not, so only the +window going inactive mutes it. A style that returns `WindowActive` gets that; +the KDE preset's list and tree rows do. What they are muted *to* is the +`SelectedInactive` token, which the KDE preset fills with the scheme's own +Inactive highlight (see below) rather than Int UI's hand-tuned grey. + +The row reads the hook for its foregrounds: the label, the subtitle and a tree +row's chevron take `selected_label_role` only while the row is emphasised. A +style reads the same hook for its chrome through +`SelectionEmphasis::emphasised(&cfg.is_focused, &cfg.is_window_active)`, so a +solid selection fill and the text on it change together. The shipped +`RecipeStandardItemStyle` keeps the default, so a style that delegates its chrome +to it and returns `WindowActive` has to paint its own selection to follow its +rule. + +Tests that assume a selected row mutes when its view loses focus therefore hold +for the presets on the default only; scope them per preset. + ### Single-line fields hide their selection when *they* lose focus The table above is the **window** axis. On the **view-focus** axis the @@ -135,6 +166,64 @@ of that shape comes back. It is layout- and a11y-transparent, and the opacity **snaps** (no tween), correct under `prefers-reduced-motion`, since window activation is an OS state change, not a user-initiated motion. +## A theme's own inactive and high-contrast colours: `PaintProjection` + +The accent desaturation above is a fixed transform of the tokens. It suits a +palette that defines no inactive colours of its own, which is every palette +built from tokens alone. A theme whose source palette does define them (a +desktop colour scheme with an inactive colour group, say) registers a +`PaintProjection` instead, and paints those colours: + +```rust +use teksilo::core::styles::{PaintState, Theme}; + +fn project(theme: &Theme, state: PaintState) -> Theme { + let mut out = theme.clone(); + if !state.window_active { + out.colors = my_inactive_colors(theme); + } + if state.high_contrast { + out = out.for_high_contrast(); // keep the generic part it wants + } + out +} + +let theme = my_theme().with_paint_projection(project); +``` + +The paint walker asks `Theme::for_paint_state(PaintState { window_active, +high_contrast })` for the theme of each frame. With a `PaintProjection` +registered, that is the hook's answer; without one it is the generic chain, +`for_inactive_window` when the window is inactive and then `for_high_contrast` +when the "increase contrast" preference is set. That last step lifts the +theme's own accent, focus ring, secondary text and selection fills (focused +and unfocused) until they reach 7:1 for text and 4.5:1 for the ring, keeping +their hue. The hook receives the active theme and both flags at once, so it +decides how they combine, and it replaces the generic chain entirely: +`Theme::for_inactive_window` and `Theme::for_high_contrast` stay the generic +transforms and do not consult it. Only change colours in a hook. The swap is +repaint-only, so geometry a hook changes is not honoured. + +**The KDE preset is the example.** A KDE colour scheme defines an Inactive +colour group, so `teksilo-theme-kde` registers a `PaintProjection` that paints +it: for an inactive window the theme takes the tokens it computed from the +scheme's Inactive state, and for the high-contrast preference its own +high-contrast tokens, in the combination the state names. For Breeze that is a +selection and accent muted toward the window colour (the window colour tinted +toward the selection colour, as Plasma paints a backdrop window) with the focus +ring unchanged, where the generic chain would desaturate the accent toward +graphite and dim the ring. The scheme's colours therefore decide what muting +looks like, which is why the muting is "scheme-driven". A theme whose +colours an app changed after building it gets the generic chain over the app's +colours instead, so an app's override is never painted over. Combined with `SelectionEmphasis::WindowActive` above, a Breeze list's +selection stays vivid while its view lacks focus and mutes only with its window. + +Colours a recipe bakes at build time (a `Filled` button's fill and focus +border, held as a `ColorProp::Bound`) resolve through the same call, +`Theme::paint_colors`, against `BuildContext::window_active_signal` and +`BuildContext::high_contrast_signal`, so they follow the hook, and the +high-contrast preference, like the colours resolved at paint. + ## Keeping a widget vivid when inactive There is no need to opt out of the automatic behaviour for normal apps. If a diff --git a/engineering/docs/accessibility-internal-audit.md b/engineering/docs/accessibility-internal-audit.md index e47293cdc..cd17ee08a 100644 --- a/engineering/docs/accessibility-internal-audit.md +++ b/engineering/docs/accessibility-internal-audit.md @@ -178,7 +178,7 @@ The rightmost column records what the previous revision got wrong about each. | G9 | 1.3.1 / 3.3.2 — DateEdit collapses its redundant middle node | [date_edit.rs](../../crates/teksilo-widgets/src/date_edit.rs); test `date_edit_collapses_middle_container_node` | Line drift only | | G10 | 1.3.5 Input purpose → specialised AT roles | `InputPurpose` at [text_input_field.rs:120-149](../../crates/teksilo-widgets/src/primitives/text_input_field.rs) | Line drift, **and the stated blocker is wrong** — see §5.14 | | G12 | 1.4.13 Content on hover or focus | No-dismiss-on-anchor-leave at [overlay_impl.rs:1615-1645](../../crates/teksilo-core/src/widget_tree/overlay_impl.rs); 100 ms grace at `:486-494`; bounds check at [widget_tree.rs:1029-1055](../../crates/teksilo-core/src/widget_tree.rs) | **The verdict was wrong when written.** G12 closed *hoverable*; *dismissible* and *persistent* were both still broken and were fixed a month later by `55eb6de4` — see §7 | -| G13 | 1.4.1 / 1.4.11 + high contrast | `for_high_contrast()` at [theme.rs:230-259](../../crates/teksilo-tokens/src/theme.rs); OS re-query in [window_manager.rs](../../crates/teksilo-app/src/window_manager.rs); list/tree selection boundary at [recipe_standard_item_style.rs:133-166](../../crates/teksilo-widgets/src/styles/recipe_standard_item_style.rs) | Line drift. **New problem:** `for_high_contrast()` hardcodes IntUI hexes and is applied to whatever preset is active — see §5.6 | +| G13 | 1.4.1 / 1.4.11 + high contrast | `for_high_contrast()` at [theme.rs:230-259](../../crates/teksilo-tokens/src/theme.rs); OS re-query in [window_manager.rs](../../crates/teksilo-app/src/window_manager.rs); list/tree selection boundary at [recipe_standard_item_style.rs:133-166](../../crates/teksilo-widgets/src/styles/recipe_standard_item_style.rs) | Line drift. `for_high_contrast()` hardcoded IntUI hexes and applied them to whatever preset was active; it now lifts the active palette's own colours, see §5.6 | | G14 | 3.2.1 On focus — debug warning for context change | [focus_impl.rs](../../crates/teksilo-core/src/widget_tree/focus_impl.rs), [event_context.rs](../../crates/teksilo-core/src/widget/event_context.rs) | Line drift only | | G15 | 4.1.2 — bound `access_*` props at `AccessibilityOnly` | `register_access_prop_bindings` at [widget_tree.rs:2298-2314](../../crates/teksilo-core/src/widget_tree.rs); test `bound_access_label_change_dirties_accessibility_tree` in [pointer_router.rs](../../crates/teksilo-core/src/widget_tree/pointer_router.rs) | Line drift. G15 covered *override* props only — the editors' own caret signals had the identical defect and were fixed later (`2f305370`) | | G16 | 2.3.3 — overlay fades snap under reduced motion | [overlay_impl.rs](../../crates/teksilo-core/src/widget_tree/overlay_impl.rs) (`attach_overlay_fade`, `process_tooltips_impl`) | Previous revision cited a file `process_tooltips_impl.rs` that does not exist — it is a function inside `overlay_impl.rs` | @@ -269,7 +269,7 @@ mislabelled 2.4.13 as Level AA when it is AAA. They are separated here. | 2.4.12 Focus Not Obscured (Enhanced) | AAA | ❌ | Follows from the above | | 2.4.13 Focus Appearance | AAA | ✅ | 2.0 dp stroke at ≥3:1 (G3). **Level corrected from AA** | | 2.5.7 Dragging Movements | AA | 🟡 | G6 (scene Alt+Arrow nudge), plus two further alternatives since: Splitter keyboard resize, and the scene magnetism keyboard connect flow ([magnetism.rs:172-240](../../crates/teksilo-scene/src/view/magnetism.rs)). GridView's Alt+Arrow reorder is a validated same-view drop. **Docking drag-to-dock is the gap:** its only non-drag route is a context menu. That menu is now keyboard-reachable on a focused rail item (§5.8), but the `split_into_tab`/`stack_into_tab` drop zones still have no menu equivalent | -| 2.5.8 Target Size (Minimum) | AA | 🟡 | The floor is **24 dp at every density and never scaled** (`InputTokens::min_target_conformance`), and it is now enforced two ways rather than asserted nowhere: `dp(.., TargetRole::Target, ..)` clamps upward to it, asserted by `a_target_never_lands_below_the_conformance_floor` in [density.rs](../../crates/teksilo-core/src/styles/density.rs) at all three densities; and [target_audit.rs](../../crates/teksilo-core/src/accessibility/target_audit.rs) measures each target's *reachable* extent — painted size plus whatever `hit_outset`, `target_regions`, `partition_targets` and the miss-only slop pass actually deliver — with named-fixture gates at all three densities, the stock catalog's in [teksilo-target-conformance/tests/conformance.rs](../../crates/teksilo-target-conformance/tests/conformance.rs) and two more in `teksilo-charts` and `teksilo-scene`. **The preset limit this row used to carry is closed:** the widget gate sweeps **all four shipped presets** (Int UI, macOS, Fluent, Material 3), one family representative each. That sweep found 64 failures no Int UI gate could see, of which three were preset defects and are **fixed** — Fluent's list selection pill owned every press in a row, so a checkbox in a Fluent list could not be ticked by a mouse; the macOS icon button and switch sat under the floor at every density and now carry a conformance box around unchanged Apple chrome. **Three limits keep this 🟡 and not ✅.** (i) Coverage is whatever the three fixture lists name, and **four crates that own pointer targets have no list at all** (`teksilo-inspector`, `teksilo-terminal`, `teksilo-preview-ui`, `teksilo-webview`); §3.7 gives each one's feasibility. Charts and scene sweep Int UI alone, each for a written reason. (ii) **Fourteen** allow-listed findings remain, each carrying a written justification, a **pinned** measurement (the geometry *is* the matcher, so a figure cannot drift from what the gate excuses), an explicit theme axis, and an owner wherever there is a decision left to take — thirteen of the fourteen name one. **Five** rest on a 2.5.8 exception rather than on geometry: the `SpinBox` step buttons (18 × 13 dp), the `ColorPicker` swatch (22 × 22 dp), the `TreeTableView` chevron (12 × 12 dp, whose row expands and collapses on ArrowLeft / ArrowRight) and the window frame's diagonal corner grips (6 × 6 dp) under *Equivalent*, and `Link` (text-height, 17 dp) under *Inline* — four of those five still name an owner for the geometry, because an exception discharges the criterion without making the shape good. **Nine** are real shortfalls escalated rather than excused: a `SpinBox`'s editable field is 20 dp tall at Compact; a `SearchField`'s clear slot keeps its press and its pointer cursor while the query is empty and reaches 22 × 24 there; the tree chevron's `hit_outset` is inert because `StandardTreeItem` wraps it in a `FixedSize` its own size, so it reaches 16 dp in a `TreeView` at every density where the same chevron unwrapped reaches 24; at Touch a dock's tab strip is unreachable at its centre line because `DockResizeHandle`'s hit outset covers it — the outset inflates the 6 dp divider across its thickness to the density's **`target_size`** (24 dp at Compact, 44 at Touch: the AAA/HIG figure, *not* `grab_size`, which is 16 dp at Touch), which `grab_outset` in [docking/resize_handle.rs](../../crates/teksilo-widgets/src/docking/resize_handle.rs) computes; a window's top resize ring swallows the top band of its own content at Touch, reported under Material 3 and present under all four; a Material 3 calendar's header title is 11.2 dp wide between four 48 dp nav arrows; a menu row comes out under the `item_height` its own recipe asked for; and the table header's resize grip and filter glyph reach exactly their own paint. The split is a field on each entry rather than a sentence here, and `the_allow_lists_roster_is_what_it_says_it_is` asserts both counts — the paragraph they replaced carried four numbers and every one was wrong. The header grip and the filter glyph were invisible until P40's mutation round, because the `table_view` fixture built its columns from the defaults and a `HeaderCell` reports **no** regions unless a column is filterable: a column's resize grip is a reported `Grab` reaching exactly its own paint, and the paint is itself only half the divider band, which straddles the boundary with `resize_grip` inside each of the two cells; and a filterable column's filter glyph reaches exactly its paint, the header cell around it owning every near miss at distance zero. Neither is excused by an exception: [drag-operation-census.md](drag-operation-census.md) row 7 rates column resize *Partial* — `Increment`/`Decrement` assistive actions and **no keyboard route at all**, the header cell being `.focusable(false)` — and there is no larger equivalent control for opening a filter. Both wait on that row's own remediation, a focusable header row with a roving tab index. The dock one is still the first to read: it is not a size failure at all but a gap in A10's outset-versus-target precedence, and it costs half a 38 dp tab strip at every dock side. (iii) Reaching the floor for a mouse is a *paint* question, and every hit-widening mechanism is 0 dp for a precise pointer on purpose, so a preset that paints under 24 dp fails for a mouse user unless 2.5.8's *Spacing* exception carries it | +| 2.5.8 Target Size (Minimum) | AA | 🟡 | The floor is **24 dp at every density and never scaled** (`InputTokens::min_target_conformance`), and it is now enforced two ways rather than asserted nowhere: `dp(.., TargetRole::Target, ..)` clamps upward to it, asserted by `a_target_never_lands_below_the_conformance_floor` in [density.rs](../../crates/teksilo-core/src/styles/density.rs) at all three densities; and [target_audit.rs](../../crates/teksilo-core/src/accessibility/target_audit.rs) measures each target's *reachable* extent: painted size plus whatever `hit_outset`, `target_regions`, `partition_targets` and the miss-only slop pass actually deliver, with named-fixture gates at all three densities, the stock catalog's in [teksilo-target-conformance/tests/conformance.rs](../../crates/teksilo-target-conformance/tests/conformance.rs) and two more in `teksilo-charts` and `teksilo-scene`. **The preset limit this row used to carry is closed:** the widget gate sweeps **all five shipped presets** (Int UI, macOS, Fluent, Material 3, KDE's Breeze), one family representative each. That sweep found 64 failures no Int UI gate could see, of which three were preset defects and are **fixed**: Fluent's list selection pill owned every press in a row, so a checkbox in a Fluent list could not be ticked by a mouse; the macOS icon button and switch sat under the floor at every density and now carry a conformance box around unchanged Apple chrome. **Three limits keep this 🟡 and not ✅.** (i) Coverage is whatever the three fixture lists name, and **four crates that own pointer targets have no list at all** (`teksilo-inspector`, `teksilo-terminal`, `teksilo-preview-ui`, `teksilo-webview`); §3.7 gives each one's feasibility. Charts and scene sweep Int UI alone, each for a written reason. (ii) **Fifteen** allow-listed findings remain, each carrying a written justification, a **pinned** measurement (the geometry *is* the matcher, so a figure cannot drift from what the gate excuses), an explicit theme axis, and an owner wherever there is a decision left to take: fourteen of the fifteen name one. **Six** rest on a 2.5.8 exception rather than on geometry: the `SpinBox` step buttons (18 × 13 dp), the `ColorPicker` swatch (22 × 22 dp), the `TreeTableView` chevron (12 × 12 dp, whose row expands and collapses on ArrowLeft / ArrowRight) and the window frame's diagonal corner grips (6 × 6 dp) under *Equivalent*, `Link` (text-height, 17 dp) under *Inline*, and the part of a table's scroll-bar track below the thumb (11.5 dp tall under macOS and 16.9 under KDE, where the content barely overflows, which dragging the thumb, the wheel and PageDown also reach) under *Equivalent*, five of those six still name an owner for the geometry, because an exception discharges the criterion without making the shape good. **Nine** are real shortfalls escalated rather than excused: a `SpinBox`'s editable field is 20 dp tall at Compact; a `SearchField`'s clear slot keeps its press and its pointer cursor while the query is empty and reaches 22 × 24 there; the tree chevron's `hit_outset` is inert because `StandardTreeItem` wraps it in a `FixedSize` its own size, so it reaches 16 dp in a `TreeView` at every density where the same chevron unwrapped reaches 24; at Touch a dock's tab strip is unreachable at its centre line because `DockResizeHandle`'s hit outset covers it: the outset inflates the 6 dp divider across its thickness to the density's **`target_size`** (24 dp at Compact, 44 at Touch: the AAA/HIG figure, *not* `grab_size`, which is 16 dp at Touch), which `grab_outset` in [docking/resize_handle.rs](../../crates/teksilo-widgets/src/docking/resize_handle.rs) computes; a window's top resize ring swallows the top band of its own content at Touch, reported under Material 3 and present under all five; a Material 3 calendar's header title is 11.2 dp wide between four 48 dp nav arrows; a menu row comes out under the `item_height` its own recipe asked for; and the table header's resize grip and filter glyph reach exactly their own paint. The split is a field on each entry rather than a sentence here, and `the_allow_lists_roster_is_what_it_says_it_is` asserts both counts; the paragraph they replaced carried four numbers and every one was wrong. The header grip and the filter glyph were invisible until P40's mutation round, because the `table_view` fixture built its columns from the defaults and a `HeaderCell` reports **no** regions unless a column is filterable: a column's resize grip is a reported `Grab` reaching exactly its own paint, and the paint is itself only half the divider band, which straddles the boundary with `resize_grip` inside each of the two cells; and a filterable column's filter glyph reaches exactly its paint, the header cell around it owning every near miss at distance zero. Neither is excused by an exception: [drag-operation-census.md](drag-operation-census.md) row 7 rates column resize *Partial*: `Increment`/`Decrement` assistive actions and **no keyboard route at all**, the header cell being `.focusable(false)`, and there is no larger equivalent control for opening a filter. Both wait on that row's own remediation, a focusable header row with a roving tab index. The dock one is still the first to read: it is not a size failure at all but a gap in A10's outset-versus-target precedence, and it costs half a 38 dp tab strip at every dock side. (iii) Reaching the floor for a mouse is a *paint* question, and every hit-widening mechanism is 0 dp for a precise pointer on purpose, so a preset that paints under 24 dp fails for a mouse user unless 2.5.8's *Spacing* exception carries it | | 3.2.6 Consistent Help | A | ➖ | Author-scope | | 3.3.7 Redundant Entry | A | author | ➖ | Framework hooks exist (`Wizard`/`Stepper` for multi-step processes; `MruList`/`SettingsStore` for re-entry avoidance) but the criterion is satisfied at application level | | 3.3.8 Accessible Authentication (Min) | AA | ✅ | **`PasswordField` gets the asymmetry right, which is worth stating explicitly.** `copy_allowed()` is consulted at exactly two sites — `clipboard_copy` and `clipboard_cut` ([keyboard.rs:445, :460](../../crates/teksilo-widgets/src/primitives/text_input_field/keyboard.rs)). `clipboard_paste` has no such guard, and the context-menu Paste row is the only one of the four built without an `.enabled(..)` clause. So a password manager's paste into a masked field works, while plaintext cannot be copied out. The reveal toggle is an additional transcription aid | @@ -304,7 +304,7 @@ Chapter 5 applies to all ICT including software and is not subsumed by either. | 11.5.2.15 | State exposed to AT | 🟡 | Core reactive state is correct. Two residuals: `WidgetBuilder::access_disabled` takes a plain `bool`, not `impl Into>`, unlike every sibling override ([widget_builder.rs:1170, :1771](../../crates/teksilo-core/src/widget_builder.rs)); and until `ee3d406f`/`4769528f` the *visual* half was broken framework-wide — nine form fields and every ancestor-disabled widget rendered pixel-identical to enabled while correctly reporting `disabled` to AT | | 11.5.2.x | AT platform-API binding | ✅ | Real `accesskit_winit` bridges on AT-SPI, NSAccessibility and UIA. `accesskit_winit` 0.34.0, `accesskit` 0.25.0 | | 11.6.2 | Accessible end-user documentation | ➖ | Applies to shipped products, not the toolkit | -| 11.7(a) | Contrast preference | 🟡 | Applied at both main and overlay paint passes — but `for_high_contrast()` substitutes literal IntUI hexes into whatever preset is active (§5.6) | +| 11.7(a) | Contrast preference | ✅ | Applied at both main and overlay paint passes. `for_high_contrast()` lifts the active palette's own accent, ring, secondary text and both selection fills (focused and unfocused) to 7:1 text and 4.5:1 rings, and each preset crate measures its pairs (§5.6) | | 11.7(b) | Reduced motion preference | 🟡 | G16, and the unified `AccessibilityPreferences` pipeline. **The terminal visual bell is a motion path this pipeline does not reach** (2.3.1) | | 11.7(c) | Text scaling preference | ✅ | Applied at launch and on every focus-triggered refresh | | 11.7 | Re-query granularity | 🟡 | Focus-transition-triggered, not an OS push subscription — a deliberate zero-idle-cost choice. A user toggling "increase contrast" while the app holds focus continuously sees no change until a focus transition | @@ -325,9 +325,9 @@ allow-list entries*. It does not say "the framework conforms". teksilo-widgets' public API and was an integration test of that crate until the gate needed it under more than one theme — a preset crate sits *above* teksilo-widgets in the crate graph, so no test binary inside teksilo-widgets can -install one. The list, the allow-list and all four preset gates therefore live in +install one. The list, the allow-list and all five preset gates therefore live in `crates/teksilo-target-conformance`, which is `publish = false` and dev-depends -on the three presets; **no published manifest changed.** The cost is that `cargo +on the four preset crates; **no published manifest changed.** The cost is that `cargo test -p teksilo-widgets` no longer catches an undersized control — `cargo test --workspace` does, and there is deliberately **no stub** left in `teksilo-widgets/tests/` to stand in for the gate, because a test that passes by @@ -345,13 +345,13 @@ rather than an omission: | `teksilo-webview` | One `WebView` node. It declares neither hit hook and routes no dimension through `dp`; the page's own targets are the page's, and the widget is a native subview the walker cannot see into. | **Not worth a list**, and this is the reason rather than an excuse: a fixture would measure one large rectangle and assert that it clears the floor. Stated here so the crate's absence is a decision. | **The preset axis, which used to be a boundary and is now covered.** The widget -list sweeps **all four shipped presets** — Int UI, macOS, Fluent and Material 3 — +list sweeps **all five shipped presets** (Int UI, macOS, Fluent, Material 3 and KDE's Breeze) at all three densities, one family representative each (light and dark are geometrically identical in every one of them, which the gate asserts rather than assumes). Charts and scene sweep Int UI alone, and that is a decision with a reason at each: a chart carries no preset chrome at all, and a scene's heavyweight tier is whatever widget the app puts in it, measured by the widget -list under all four. +list under all five. **What the preset sweep cost when it was first run: 64 failures no Int UI gate could see.** Three were preset defects and are fixed — the Fluent selection pill @@ -632,9 +632,9 @@ solve it to three different standards: navigation. - **`CommandPalette`** — solved. See §5.4. -### 5.6 High contrast overwrites the active preset's palette — EN 11.7(a) · framework +### 5.6 High contrast overwrote the active preset's palette: EN 11.7(a) · framework · **FIXED** -`ColorTokens::for_high_contrast()` +**As found.** `ColorTokens::for_high_contrast()` ([theme.rs:230-259](../../crates/teksilo-tokens/src/theme.rs)) branches only on `surface_main` luminance and substitutes literal IntUI hexes (`#4FCCE0`, `#0A7B8B`, …) for the accent family, focus indicators and selection surface. The paint walker applies @@ -646,6 +646,19 @@ No preset crate overrides it, and no test covers high-contrast contrast for thos **Recommendation:** make it a per-preset hook (a style slot or theme extension), or derive the high-contrast values from the live tokens rather than hardcoding one preset's. +**Fixed.** Both halves of the recommendation exist now. A preset whose source palette +defines its own high-contrast colours registers a `PaintProjection` (KDE does). Every +other theme gets a `for_high_contrast()` that derives from the live tokens: it lifts +the theme's own accent family, focus indicators, secondary text and selection toward +black or white, keeping their hue, until text on the accent reaches 7:1, the ring 4.5:1 +on the surfaces, the secondary text 7:1, and both selection fills (`surface_selected`, +and the `surface_selected_inactive` a row keeps in an unfocused view or an inactive +window) 1.5:1 off the content surface under primary text at 7:1. Measured before the fix: black text on macOS Aqua's accent +3.87:1, on Fluent light's 3.70:1, a ring 4.21:1 on Aqua's window; every dark preset +painted IntUI's teal. `tests/high_contrast.rs` in the macOS, Fluent and Material 3 crates +measures each pair for both appearances, active and inactive, and `prop_color.rs` +checks the rule over random accents. + ### 5.7 `teksilo-theme-material3` has no contrast assertion of any kind — 1.4.3 / 1.4.11 · framework Fluent ships 14 contrast assertions using an alpha-compositing-aware helper; macOS ships @@ -654,8 +667,11 @@ has none — its tests check appearance flags, hex values, typography sizes and ordering, never a ratio. Hand-measured, its baseline tokens pass today (on-accent text 6.44:1, secondary text 8.88:1, focus ring 6.12:1), but nothing holds them there. -There is also no cross-preset gate: a fourth preset can be added tomorrow with zero -contrast coverage and CI will not notice. +There is also no cross-preset gate: a new preset can be added tomorrow with zero +contrast coverage and CI will not notice. The KDE preset, added since, carries its own: +every colour it lifts to a WCAG floor has a test that the lifted colour passes and a test +that the scheme's own colour fails, and a property test checks every declared text and +backdrop pair over random coherent schemes. That is the preset's discipline, not a gate. **Recommendation:** port Fluent's `contrast_on` suite to Material 3, and consider a shared contrast-conformance test helper the preset crates each invoke. diff --git a/engineering/docs/density-inventory.md b/engineering/docs/density-inventory.md index 7f462403a..f1659457d 100644 --- a/engineering/docs/density-inventory.md +++ b/engineering/docs/density-inventory.md @@ -3,7 +3,7 @@ # Density inventory -Every dp dimension that `teksilo-widgets`, the three theme presets and +Every dp dimension that `teksilo-widgets`, the four theme presets and `teksilo-preview-ui` bake in, with the density treatment package **P20** applied to each — the route by which that dimension reaches a density, or the reason it has none. This is the artifact to audit conformance against, and the fixture list P40's @@ -34,7 +34,7 @@ Three things make that work: site that already existed. 3. A preset that installs Tier-3 slots of its own registers a `teksilo_core::styles::DensityProjection` in its theme extensions. - `Theme::with_density` calls it when present, so Fluent, macOS and Material 3 + `Theme::with_density` calls it when present, so Fluent, macOS, Material 3 and KDE *rebuild* their slots for the new density instead of carrying Compact dimensions across. A slot an **app** installed stays untouched — a hand-written style owns its own metrics — which is documented behaviour, not an oversight. @@ -153,7 +153,7 @@ grep -rnE '^\s*const [A-Z_0-9]+: f32' --include=*.rs . \ # §4 — theme presets and the previewer UI grep -rnE '^\s*(pub )?const [A-Z_0-9]+: f32' --include=*.rs \ - crates/teksilo-theme-{fluent,macos,material3}/src \ + crates/teksilo-theme-{fluent,kde,macos,material3}/src \ crates/teksilo-preview-ui/src | grep -v tests.rs | wc -l # 133 ``` @@ -164,6 +164,11 @@ grep -rnE '^\s*(pub )?const [A-Z_0-9]+: f32' --include=*.rs \ > preset + previewer constants. A row that no longer matches the code is drift in this > record, not a claim about the current tree; `tests/density_projection.rs` holds the > rows that *are* here to their class rules, not the tables to completeness. +> +> **Note (2026-10-17).** The §4 grep now sweeps `teksilo-theme-kde` as well, and +> returns **257** with it (136 without). Its 121 constants were added to §4 as of +> that date, and its four `MinSize::new` sites to §1; the counts above and the +> totals below stay the P02 figures, and the KDE rows are counted apart under them. ## Totals @@ -185,6 +190,12 @@ Of those, **228 rows carry a live density route** (97 through `dp(.., Target, .. or `density_min_size`, 128 through `spacing(..)`, plus the three `MinSize` floor sites); the rest are fixed, each with its reason in its own row. +The KDE preset's rows, added on 2026-10-17, are not in that table. §4 holds 121 of +them (Target 12, Grab 4, Spacing 37, Decoration 68), of which 26 carry a live density +route (2 through `dp(.., Target, ..)`, 1 through `dp(.., Grab, ..)`, 23 through +`spacing(..)`), and §1 holds its four `MinSize::new` sites, all at or above the floor +at Compact. + The §2 and §3b row counts are one below their grep counts (233 and 113): each table drops a single-letter local alias declared inside a function body — `recipe_tab_style.rs:484` (`const T: f32 = TAB_UNDERLINE_ACTIVE;`) and @@ -308,6 +319,10 @@ change marks the tree at `BindingLevel::Rebuild`. It is not a fixed 24 dp. | `crates/teksilo-preview-ui/src/navigator.rs` | 192 | `MinSize::new(0.0, 22.0)` | 22 | Target | scales with `target_size` — **below the 24 dp AA floor today** | | `crates/teksilo-theme-fluent/src/styles/button.rs` | 96 | `MinSize::new(0.0, MIN_HEIGHT)` | 32 | Target | scales with `target_size` — `density_min_size(.., &InputTokens)` | | `crates/teksilo-theme-fluent/src/styles/text_input.rs` | 83 | `MinSize::new(0.0, MIN_HEIGHT)` | 32 | Target | scales with `target_size` — `density_min_size(.., &InputTokens)` | +| `crates/teksilo-theme-kde/src/styles/button.rs` | 96 | `MinSize::new(m.min_width, m.height)` | 84 × 34 | Target | scales with `target_size`: the height through `dp(.., Target)` in `breeze_button_metrics_for(&InputTokens)`; the width is Breeze's 84 at every density | +| `crates/teksilo-theme-kde/src/styles/menu_item.rs` | 185 | `MinSize::new(0.0, self.recipe.item_height)` | 29 | Target | scales with `target_size`: `dp(.., Target)` in `breeze_menu_metrics_for(&InputTokens)` | +| `crates/teksilo-theme-kde/src/styles/split_button.rs` | 77 | `MinSize::new(m.min_width, m.height)` | 84 × 34 | Target | scales with `target_size`: the height through `dp(.., Target)` in `breeze_button_metrics_for(&InputTokens)`; the width is Breeze's 84 at every density | +| `crates/teksilo-theme-kde/src/styles/text_input.rs` | 90 | `MinSize::new(0.0, cfg.min_height.unwrap_or(m.height))` | 32 | Target | scales with `target_size`: `dp(.., Target)` in `breeze_field_metrics_for(&InputTokens)`, unless the widget asks for its own minimum height | | `crates/teksilo-theme-macos/src/styles/button.rs` | 107 | `MinSize::new(0.0, MACOS_CONTROL_HEIGHT)` | 22 | Target | scales with `target_size` — **below the 24 dp AA floor today** | | `crates/teksilo-theme-macos/src/styles/text_input.rs` | 81 | `MinSize::new(0.0, MACOS_CONTROL_HEIGHT)` | 22 | Target | scales with `target_size` — **below the 24 dp AA floor today** | | `crates/teksilo-widgets/src/accordion.rs` | 464 | `MinSize::new(ACCORDION_FILL_HEADER_EXTENT, 0.0)` | 30 | Target | scales with `target_size` (horizontal dock strip) — `accordion_fill_header_extent(&InputTokens)` | @@ -396,7 +411,7 @@ alone. | `recipe_split_button_style.rs` | *(none — see §2.1)* | 0 | | `recipe_splitter_style.rs` | `SPLITTER_DIVIDER_LINE_THICKNESS`, `HOVER_DWELL_DELAY_FRAC` | 2 | | `recipe_standard_item_style.rs` | `STANDARD_ITEM_ICON_SIZE`, `STANDARD_ITEM_SUBTITLE_ICON_SIZE`, `STANDARD_ITEM_SLOT_GAP`, `STANDARD_ITEM_SUBTITLE_SLOT_GAP`, `STANDARD_ITEM_LABEL_SUBTITLE_GAP`, `STANDARD_ITEM_PADDING_HORIZONTAL`, `STANDARD_ITEM_PADDING_VERTICAL`, `STANDARD_ITEM_MIN_HEIGHT_SINGLE_LINE`, `STANDARD_ITEM_MIN_HEIGHT_TWO_LINE`, `STANDARD_ITEM_LABEL_COLUMN_MIN_WIDTH`, `STANDARD_ITEM_CHEVRON_COLUMN_WIDTH`, `STANDARD_ITEM_TREE_INDENT_STEP`, `STANDARD_ITEM_ITEM_CORNER_RADIUS`, `STANDARD_ITEM_BG_HORIZONTAL_INSET`, `STANDARD_ITEM_FOCUS_RING_WIDTH`, `STANDARD_ITEM_SELECTION_EDGE_WIDTH` | 16 | -| `recipe_tab_style.rs` | `TAB_EDITOR_HEIGHT`, `TAB_TOOL_WINDOW_HEIGHT`, `TAB_PADDING_HORIZONTAL`, `TAB_UNDERLINE_ACTIVE`, `TAB_UNDERLINE_HOVER`, `TAB_CLOSE_BUTTON_SIZE`, `DROP_INDICATOR_WIDTH` | 7 | +| `recipe_tab_style.rs` | `TAB_EDITOR_HEIGHT`, `TAB_TOOL_WINDOW_HEIGHT` (**deleted**, see §2.2), `TAB_PADDING_HORIZONTAL`, `TAB_UNDERLINE_ACTIVE`, `TAB_UNDERLINE_HOVER`, `TAB_CLOSE_BUTTON_SIZE`, `DROP_INDICATOR_WIDTH` | 7 | | `recipe_table_style.rs` | `ROW_HEIGHT`, `HEADER_HEIGHT`, `CELL_PADDING_HORIZONTAL`, `CELL_PADDING_VERTICAL`, `RESIZE_HANDLE_WIDTH`, `COLUMN_RESIZE_STEP`, `GRID_LINE_THICKNESS`, `CORNER_RADIUS`, `SORT_INDICATOR_SIZE`, `FILTER_INDICATOR_SIZE`, `HEADER_INTER_CELL_SPACING`, `FOCUS_RING_INSET`, `MIN_COLUMN_WIDTH_DEFAULT`, `TREE_INDENT_PER_LEVEL`, `TREE_TWIST_SIZE`, `TREE_TWIST_LABEL_GAP` | 16 | | `recipe_text_input_style.rs` | `TEXT_FIELD_HEIGHT`, `TEXT_FIELD_PADDING_HORIZONTAL`, `TEXT_FIELD_PADDING_VERTICAL`, `TEXT_FIELD_BORDER_WIDTH`, `TEXT_FIELD_CORNER_RADIUS`, `TEXT_FIELD_CARET_WIDTH`, `TEXT_FIELD_VALIDATION_STRIP_GAP` | 7 | | `recipe_toast_style.rs` | `TOAST_PADDING_HORIZONTAL`, `TOAST_PADDING_VERTICAL`, `TOAST_CORNER_RADIUS`, `TOAST_GLYPH_SIZE`, `TOAST_CONTENT_GAP`, `TOAST_TITLE_BODY_GAP`, `TOAST_BODY_ACTIONS_GAP` | 7 | @@ -596,7 +611,7 @@ Five recipes carry no dimension constant of their own: | `crates/teksilo-widgets/src/styles/recipe_standard_item_style.rs` | 47 | `STANDARD_ITEM_FOCUS_RING_WIDTH` | `1.5` | Decoration | fixed — decorative geometry, no hit consequence | | `crates/teksilo-widgets/src/styles/recipe_standard_item_style.rs` | 54 | `STANDARD_ITEM_SELECTION_EDGE_WIDTH` | `1.0` | Decoration | fixed — decorative geometry, no hit consequence | | `crates/teksilo-widgets/src/styles/recipe_tab_style.rs` | 45 | `TAB_EDITOR_HEIGHT` | `50.0` | Target | scales with `target_size` — `TabRecipe::for_tokens` | -| `crates/teksilo-widgets/src/styles/recipe_tab_style.rs` | 46 | `TAB_TOOL_WINDOW_HEIGHT` | `28.0` | Target | scales with `target_size` — `TabRecipe::for_tokens` | +| `crates/teksilo-widgets/src/styles/recipe_tab_style.rs` | 46 | `TAB_TOOL_WINDOW_HEIGHT` | `28.0` | Target | **deleted**, with `TabRecipe::tool_window_height`: nothing read either, and no tab strip has a tool-window height to give them | | `crates/teksilo-widgets/src/styles/recipe_tab_style.rs` | 47 | `TAB_PADDING_HORIZONTAL` | `12.0` | Spacing | scales with `spacing_factor` — `TabRecipe::for_tokens` | | `crates/teksilo-widgets/src/styles/recipe_tab_style.rs` | 48 | `TAB_UNDERLINE_ACTIVE` | `2.0` | Decoration | fixed — decorative geometry, no hit consequence | | `crates/teksilo-widgets/src/styles/recipe_tab_style.rs` | 49 | `TAB_UNDERLINE_HOVER` | `2.0` | Decoration | fixed — decorative geometry, no hit consequence | @@ -854,8 +869,8 @@ from its *items* — `DockRailItemSize` resolves through `IconButtonRecipe`, whi ## §4 — theme presets and the previewer UI -`teksilo-theme-fluent`, `teksilo-theme-macos` and `teksilo-theme-material3` install -their own Tier-3 styles with their own constants, so P20's projection has to reach +`teksilo-theme-fluent`, `teksilo-theme-kde`, `teksilo-theme-macos` and +`teksilo-theme-material3` install their own Tier-3 styles with their own constants, so P20's projection has to reach them or a preset silently stays Compact under Touch. `teksilo-preview-ui` is included because its navigator rows (22 / 28 dp) are the smallest interactive rows in the workspace. @@ -869,7 +884,7 @@ own slots against the new tokens while colours, id, palette extension and any sl the *app* installed ride across untouched. Its own tests assert this (`a_density_switch_re_derives_the_installed_slots`). -**The three presets do not share a ladder.** Material 3 keeps its published **48 dp** +**The four sibling presets do not share a ladder.** Material 3 keeps its published **48 dp** touch target (`material3::input_tokens`), so an M3 button is 40 dp at Compact and 48 dp at Touch, not 44. Fluent has no published touch ladder — WinUI's 40 px / 7.5 mm is a recommendation, not a metric its controls scale by — so it uses the generic one. @@ -879,6 +894,19 @@ controls stay at Apple's numbers **in the paint** at every density: growing them would falsify a preset whose whole purpose is to reproduce those numbers. What rises on macOS is the minimum *hit box* around a control — the two `MinSize` sites in §1. +**KDE**, added after the three, has no published touch ladder either +(`[unpublished]`), so its projection takes the generic one. Every Breeze control +height that is a target goes through `dp(.., Target, ..)` in `styles/metrics.rs` +and is Breeze's own at Compact (the progress bar's 26 dp is not a target and stays); +most gaps and paddings go through `spacing(..)`, and the rows say which do not. The parts +Breeze paints under 24 dp stay at Breeze's numbers at every density: the 20 dp check +box and radio indicator (whose node the widget's own `MinSize` floors to 24), the +18 dp switch and the 20 dp slider handle box (whose nodes the styles raise with +`density_min_size`), and the spin box's 20 dp arrow column, whose two 20 x 10 halves +the conformance gate pins under SC 2.5.8 *Equivalent*. The rows class the preset's +unitless constants (alphas, a speed) as Decoration, with a treatment that says they +are not a dimension. + | File | Line | Identifier | Value | Class | P20 treatment | | --- | ---: | --- | ---: | --- | --- | | `crates/teksilo-preview-ui/src/doc_export.rs` | 42 | `MIN_INK_RATIO` | `0.002` | — not a dimension | fixed — unitless (ratio / alpha / duration / epsilon) | @@ -895,12 +923,12 @@ on macOS is the minimum *hit box* around a control — the two `MinSize` sites i | `crates/teksilo-theme-fluent/src/styles/checkbox.rs` | 37 | `BOX_SIZE` | `20.0` | Target | visual fixed at `[WinUI]` 20 dp (below the floor); coarse hit via `hit_outset` | | `crates/teksilo-theme-fluent/src/styles/checkbox.rs` | 39 | `GLYPH_SIZE` | `12.0` | Decoration | fixed — glyph metric; follows text scale, not target density | | `crates/teksilo-theme-fluent/src/styles/checkbox.rs` | 41 | `STROKE` | `1.0` | Decoration | fixed — hairline stroke; scaling it thickens the design language | -| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 36 | `ITEM_HEIGHT` | `8.0 + 20.0 + 9.0` | Target | scales with `target_size` — `fluent_menu_item_recipe_for(&InputTokens)` | -| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 38 | `PADDING_H` | `11.0` | Spacing | scales with `spacing_factor` — `fluent_menu_item_recipe_for(&InputTokens)` | -| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 40 | `ICON_COLUMN` | `16.0` | Decoration | fixed — decorative geometry, no hit consequence | -| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 43 | `ICON_LABEL_GAP` | `12.0` | Spacing | scales with `spacing_factor` — `fluent_menu_item_recipe_for(&InputTokens)` | -| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 45 | `SHORTCUT_GAP` | `24.0` | Spacing | scales with `spacing_factor` — `fluent_menu_item_recipe_for(&InputTokens)` | -| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 47 | `SEPARATOR_HEIGHT` | `3.0` | Spacing | scales with `spacing_factor` — `fluent_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 43 | `ITEM_HEIGHT` | `8.0 + 20.0 + 9.0` | Target | scales with `target_size`: `fluent_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 45 | `PADDING_H` | `11.0` | Spacing | scales with `spacing_factor`: `fluent_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 47 | `ICON_COLUMN` | `16.0` | Decoration | fixed: decorative geometry, no hit consequence | +| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 50 | `ICON_LABEL_GAP` | `12.0` | Spacing | scales with `spacing_factor`: `fluent_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 52 | `SHORTCUT_GAP` | `24.0` | Spacing | scales with `spacing_factor`: `fluent_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-fluent/src/styles/menu_item.rs` | 54 | `SEPARATOR_HEIGHT` | `3.0` | Spacing | scales with `spacing_factor`: `fluent_menu_item_recipe_for(&InputTokens)` | | `crates/teksilo-theme-fluent/src/styles/metrics.rs` | 50 | `FLUENT_CONTROL_HEIGHT` | `32.0` | Target | scales with `target_size` — the preset's `fluent_*_style_for(&InputTokens)` factories | | `crates/teksilo-theme-fluent/src/styles/radio.rs` | 45 | `OUTER` | `20.0` | Target | visual fixed at `[WinUI]` 20 dp (below the floor); coarse hit via `hit_outset` | | `crates/teksilo-theme-fluent/src/styles/radio.rs` | 47 | `STROKE` | `1.0` | Decoration | fixed — hairline stroke; scaling it thickens the design language | @@ -937,6 +965,127 @@ on macOS is the minimum *hit box* around a control — the two `MinSize` sites i | `crates/teksilo-theme-fluent/src/styles/toggle.rs` | 65 | `ROW_H` | `32.0` | Target | scales with `target_size` — the style's own `make_body`, from `ctx.theme().input` | | `crates/teksilo-theme-fluent/src/typography.rs` | 54 | `FLUENT_BODY_SIZE` | `14.0` | Decoration | fixed — decorative geometry, no hit consequence | | `crates/teksilo-theme-fluent/src/typography.rs` | 56 | `FLUENT_CAPTION_SIZE` | `12.0` | Decoration | fixed — decorative geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/geometry.rs` | 22 | `FRAME_WIDTH` | `2.0` | Spacing | scales with `spacing_factor` in the button's horizontal padding (`breeze_button_metrics_for(&InputTokens)`); fixed where it holds a frame (the combo and spin boxes' trailing margin) and inside the font-driven height rules | +| `crates/teksilo-theme-kde/src/geometry.rs` | 24 | `LAYOUT_TOP_LEVEL_MARGIN` | `10.0` | Spacing | scales with `spacing_factor`: `spacing(..)` in the preset's recipe factories, `styles/recipes.rs` | +| `crates/teksilo-theme-kde/src/geometry.rs` | 26 | `LAYOUT_CHILD_MARGIN` | `6.0` | Spacing | scales with `spacing_factor`: `spacing(..)` in the preset's recipe factories, `styles/recipes.rs` | +| `crates/teksilo-theme-kde/src/geometry.rs` | 28 | `LAYOUT_SPACING` | `6.0` | Spacing | scales with `spacing_factor`: `spacing(..)` in the preset's recipe factories, `styles/recipes.rs` | +| `crates/teksilo-theme-kde/src/geometry.rs` | 30 | `LINE_EDIT_FRAME_WIDTH` | `6.0` | Spacing | scales with `spacing_factor` as the field's horizontal padding (`breeze_field_metrics_for(&InputTokens)`); inside the 32 dp height rule it is part of the field's target floor | +| `crates/teksilo-theme-kde/src/geometry.rs` | 32 | `COMBO_BOX_FRAME_WIDTH` | `6.0` | Spacing | scales with `spacing_factor` as the leading padding (`breeze_combo_metrics_for(&InputTokens)`); inside the 32 dp height rule it is part of the combo box's target floor | +| `crates/teksilo-theme-kde/src/geometry.rs` | 34 | `SPIN_BOX_FRAME_WIDTH` | `6.0` | Spacing | scales with `spacing_factor` as the leading padding (`breeze_spin_metrics_for(&InputTokens)`); inside the 32 dp height rule it is part of the spin box's target floor | +| `crates/teksilo-theme-kde/src/geometry.rs` | 36 | `SPIN_BOX_ARROW_BUTTON_WIDTH` | `20.0` | Target | fixed: visual Breeze 20 dp arrow column, painted without button chrome at every density; its two halves are below the floor under SC 2.5.8 *Equivalent* (the conformance gate's `StepButton` pin, 20 x 10) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 38 | `BUTTON_MIN_WIDTH` | `80.0` | Target | fixed: Breeze's minimum width (84 with the frame margins) at every density; the height carries the target (`breeze_button_metrics_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 40 | `BUTTON_MARGIN_WIDTH` | `6.0` | Spacing | scales with `spacing_factor` in the horizontal padding (`breeze_button_metrics_for(&InputTokens)`); fixed above and below, inside the height rule, whose floor rises with the density instead | +| `crates/teksilo-theme-kde/src/geometry.rs` | 42 | `BUTTON_ITEM_SPACING` | `4.0` | Spacing | fixed: recorded from Breeze, read by no style | +| `crates/teksilo-theme-kde/src/geometry.rs` | 44 | `TOOL_BUTTON_MARGIN_WIDTH` | `6.0` | Spacing | fixed: part of the icon-button squares, which rise with the density's target size as a whole (`breeze_tool_button_sizes_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 46 | `MENU_BUTTON_INDICATOR_WIDTH` | `20.0` | Decoration | fixed: recorded from Breeze, read by no style | +| `crates/teksilo-theme-kde/src/geometry.rs` | 48 | `CHECKBOX_SIZE` | `20.0` | Target | fixed: visual Breeze 20 dp, node floored to 24 by the widget's own `MinSize` | +| `crates/teksilo-theme-kde/src/geometry.rs` | 50 | `CHECKBOX_ITEM_SPACING` | `4.0` | Spacing | fixed: places the label at `INDICATOR_LABEL_X`, where Breeze puts it (`indicator_body_width`) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 52 | `CHECKBOX_FOCUS_MARGIN` | `2.0` | Decoration | fixed: the box's inset inside the indicator, painted geometry | +| `crates/teksilo-theme-kde/src/geometry.rs` | 54 | `MENU_ITEM_MARGIN` | `4.0` | Spacing | scales with `spacing_factor` in the menu row's paddings (`breeze_menu_metrics_for(&InputTokens)`) and the suggestion rows; fixed as the menu's popup margin and inside the row height rule | +| `crates/teksilo-theme-kde/src/geometry.rs` | 56 | `MENU_ITEM_TEXT_LEFT_MARGIN` | `8.0` | Spacing | scales with `spacing_factor`: the menu row's leading padding (`breeze_menu_metrics_for(&InputTokens)`) and the suggestion rows' inset | +| `crates/teksilo-theme-kde/src/geometry.rs` | 58 | `MENU_ITEM_EXTRA_LEFT_MARGIN` | `4.0` | Spacing | scales with `spacing_factor` inside the suggestion rows' inset (`SUGGESTION_TEXT_INSET`) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 60 | `MENU_ITEM_ACCELERATOR_SPACE` | `16.0` | Spacing | scales with `spacing_factor`: the shortcut gap (`breeze_menu_metrics_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 62 | `MENU_BAR_ITEM_MARGIN_WIDTH` | `10.0` | Spacing | fixed: recorded from Breeze, read by no style | +| `crates/teksilo-theme-kde/src/geometry.rs` | 64 | `MENU_BAR_ITEM_MARGIN_HEIGHT` | `6.0` | Spacing | fixed: recorded in the menu-bar item height rule, which no style reads | +| `crates/teksilo-theme-kde/src/geometry.rs` | 66 | `SCROLL_BAR_EXTENT` | `21.0` | Target | fixed: recorded, not laid out: the widgets choose a scroll bar's thickness themselves (`BreezeScrollBarMetrics::extent`) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 68 | `SCROLL_BAR_HANDLE_WIDTH` | `8.0` | Grab | fixed: the painted handle width; the `ScrollBar`'s own hit geometry carries the target | +| `crates/teksilo-theme-kde/src/geometry.rs` | 70 | `SCROLL_BAR_MIN_HANDLE_LENGTH` | `20.0` | Grab | scales with `grab_size` through `dp(.., Grab)` in `breeze_scroll_bar_metrics_for(&InputTokens)`, which 20 clears at every density, so it is 20 at all three | +| `crates/teksilo-theme-kde/src/geometry.rs` | 72 | `PROGRESS_BAR_THICKNESS` | `6.0` | Decoration | fixed: the painted groove; a progress bar is not a target | +| `crates/teksilo-theme-kde/src/geometry.rs` | 74 | `PROGRESS_BAR_BUSY_INDICATOR_SIZE` | `14.0` | Decoration | fixed: recorded from Breeze, read by no style | +| `crates/teksilo-theme-kde/src/geometry.rs` | 76 | `PROGRESS_BAR_BUSY_PERIOD` | `28.0` | Decoration | fixed: the busy bar's stripe period, painted geometry | +| `crates/teksilo-theme-kde/src/geometry.rs` | 78 | `SLIDER_GROOVE_THICKNESS` | `6.0` | Grab | fixed: the groove is decoration; the handle box carries the target | +| `crates/teksilo-theme-kde/src/geometry.rs` | 80 | `SLIDER_CONTROL_THICKNESS` | `20.0` | Grab | fixed: visual Breeze 20 dp handle box, `thumb_diameter_for` at every density; the node is raised to the density's target size across the groove by `density_min_size` | +| `crates/teksilo-theme-kde/src/geometry.rs` | 82 | `SLIDER_TICK_LENGTH` | `8.0` | Decoration | fixed: painted geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/geometry.rs` | 84 | `SLIDER_TICK_MARGIN` | `2.0` | Decoration | fixed: painted geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/geometry.rs` | 87 | `SLIDER_HANDLE_PAINTED` | `17.0` | Decoration | fixed: the handle as painted inside its 20 dp box, no hit consequence | +| `crates/teksilo-theme-kde/src/geometry.rs` | 89 | `TAB_MARGIN_HEIGHT` | `4.0` | Spacing | fixed: part of the tab height rule, which rises with the density's target size as a whole | +| `crates/teksilo-theme-kde/src/geometry.rs` | 91 | `TAB_MARGIN_WIDTH` | `8.0` | Spacing | scales with `spacing_factor`: the tab's horizontal padding (`breeze_tab_metrics_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 93 | `TAB_MIN_WIDTH` | `80.0` | Target | fixed: recorded, not laid out: the bar applies its own minimum tab width | +| `crates/teksilo-theme-kde/src/geometry.rs` | 95 | `TAB_MIN_HEIGHT` | `30.0` | Target | scales with `target_size`: the tab height `max(H + 8, 30)` through `dp(.., Target)` in `breeze_tab_metrics_for(&InputTokens)` | +| `crates/teksilo-theme-kde/src/geometry.rs` | 98 | `STATIC_TAB_MIN_HEIGHT` | `34.0` | Target | scales with `target_size` through `dp(.., Target)` in `breeze_tab_metrics_for(&InputTokens)`; recorded, not laid out (a `TabStyle` is not told whether its bar is static) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 100 | `TAB_ITEM_SPACING` | `8.0` | Spacing | fixed: recorded from Breeze, read by no style | +| `crates/teksilo-theme-kde/src/geometry.rs` | 102 | `TAB_ACTIVE_INDICATOR` | `3.0` | Decoration | fixed: the painted highlight bar, no hit consequence | +| `crates/teksilo-theme-kde/src/geometry.rs` | 104 | `HEADER_MARGIN` | `6.0` | Spacing | scales with `spacing_factor`: the header padding (`breeze_table_metrics_for(&InputTokens)`); inside the header height rule it is part of the header's target floor | +| `crates/teksilo-theme-kde/src/geometry.rs` | 106 | `HEADER_ARROW_SIZE` | `10.0` | Decoration | fixed: glyph metric, the header's sort arrow | +| `crates/teksilo-theme-kde/src/geometry.rs` | 108 | `ARROW_SIZE` | `10.0` | Decoration | fixed: glyph metric, the chevron every arrow is drawn at | +| `crates/teksilo-theme-kde/src/geometry.rs` | 110 | `ITEM_VIEW_MARGIN_WIDTH` | `2.0` | Spacing | scales with `spacing_factor` inside the row's content inset (`ROW_CONTENT_INSET`); fixed as the selection's inset, which is painted | +| `crates/teksilo-theme-kde/src/geometry.rs` | 112 | `ITEM_VIEW_MARGIN_HEIGHT` | `1.0` | Spacing | fixed: holds the selection's outline above and below the row; the row's height floor rises with the density instead (`breeze_row_metrics_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/geometry.rs` | 114 | `ITEM_VIEW_FIRST_ITEM_TOP_MARGIN` | `2.0` | Spacing | fixed: recorded in the first-row rule, which no style reads | +| `crates/teksilo-theme-kde/src/geometry.rs` | 116 | `ITEM_VIEW_PADDING` | `2.0` | Spacing | scales with `spacing_factor` inside the row's content inset (`ROW_CONTENT_INSET`); inside the row height rule it is part of the row's target floor | +| `crates/teksilo-theme-kde/src/geometry.rs` | 118 | `TOOLTIP_FRAME_WIDTH` | `3.0` | Spacing | scales with `spacing_factor`: the tooltip's padding, `spacing(..)` in `styles/tooltip.rs` | +| `crates/teksilo-theme-kde/src/geometry.rs` | 120 | `SPLITTER_WIDTH` | `1.0` | Decoration | fixed: hairline stroke; scaling it thickens the design language | +| `crates/teksilo-theme-kde/src/geometry.rs` | 122 | `GROUP_BOX_TITLE_MARGIN` | `4.0` | Spacing | fixed: recorded from Breeze, read by no style | +| `crates/teksilo-theme-kde/src/geometry.rs` | 124 | `SMALL_ICON_SIZE` | `16.0` | Decoration | fixed: icon size; the menu's icon column and a row's icon are painted at it, and the icon-button squares are built around it | +| `crates/teksilo-theme-kde/src/geometry.rs` | 126 | `SMALL_MEDIUM_ICON_SIZE` | `22.0` | Decoration | fixed: icon size; the toolbar icon-button square is built around it | +| `crates/teksilo-theme-kde/src/geometry.rs` | 128 | `MEDIUM_ICON_SIZE` | `32.0` | Decoration | fixed: icon size; the large icon-button square is built around it | +| `crates/teksilo-theme-kde/src/geometry.rs` | 130 | `LARGE_ICON_SIZE` | `48.0` | Decoration | fixed: icon size; the hero icon-button square is built around it | +| `crates/teksilo-theme-kde/src/lib.rs` | 347 | `BREEZE_CONTROL_GAP` | `6.0` | Spacing | fixed: `LayoutTokens::control_gap`, which no density projection touches | +| `crates/teksilo-theme-kde/src/lib.rs` | 349 | `BREEZE_SECTION_GAP` | `8.0` | Spacing | fixed: `LayoutTokens::section_gap`, which no density projection touches | +| `crates/teksilo-theme-kde/src/palette.rs` | 49 | `CONTROL_SHADOW_ALPHA` | `0.125` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/shape.rs` | 44 | `BREEZE_CONTROL_RADIUS` | `5.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/shape.rs` | 47 | `BREEZE_POPUP_RADIUS` | `5.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/shape.rs` | 51 | `BREEZE_STROKED_FRAME_RADIUS` | `4.5` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/shape.rs` | 53 | `BREEZE_CHECKBOX_RADIUS` | `4.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/shape.rs` | 55 | `BREEZE_GROOVE_RADIUS` | `3.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/shape.rs` | 57 | `BREEZE_SCROLL_HANDLE_RADIUS` | `4.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/shape.rs` | 60 | `BREEZE_PEN_WIDTH` | `1.0` | Decoration | fixed: hairline stroke; scaling it thickens the design language | +| `crates/teksilo-theme-kde/src/shape.rs` | 62 | `BREEZE_FOCUS_RING_WIDTH` | `2.0` | Decoration | fixed: decorative geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/shape.rs` | 65 | `BREEZE_FOCUS_RING_OFFSET` | `-1.0` | Decoration | fixed: decorative geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/shape.rs` | 67 | `BREEZE_FOCUS_RING_ALPHA` | `0.3` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/shape.rs` | 70 | `BREEZE_FOCUS_RING_OUTER_RADIUS` | `7.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/shape.rs` | 74 | `BREEZE_BUTTON_SHADOW_ALPHA` | `0.125` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/shape.rs` | 77 | `BREEZE_SHADOW_OVERLAP` | `2.0` | Decoration | fixed: elevation, not geometry | +| `crates/teksilo-theme-kde/src/styles/arrow.rs` | 29 | `ARROW_PEN` | `1.0` | Decoration | fixed: hairline stroke; scaling it thickens the design language | +| `crates/teksilo-theme-kde/src/styles/checkbox.rs` | 74 | `ROUNDED_RADIUS` | `6.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/styles/chrome.rs` | 86 | `FLAT_PRESSED_ALPHA` | `0.3` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/chrome.rs` | 89 | `CHECKED_FILL` | `0.125` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/chrome.rs` | 92 | `SHADOW_INSET` | `1.0` | Decoration | fixed: elevation, not geometry | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 31 | `INDICATOR_SIZE` | `CHECKBOX_SIZE` | Target | fixed: visual Breeze 20 dp, node floored to 24 by the widget's own `MinSize` | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 34 | `INDICATOR_BOX_INSET` | `CHECKBOX_FOCUS_MARGIN` | Decoration | fixed: the box's inset inside the indicator, painted geometry | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 37 | `INDICATOR_BOX_STROKED` | `INDICATOR_SIZE - 2.0 * INDICATOR_BOX_INSET - BREEZE_PEN_WIDTH` | Decoration | fixed: visual Breeze 20 dp indicator (the 15 dp box inside it), node floored to 24 by the widget's own `MinSize` | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 41 | `INDICATOR_LABEL_X` | `CHECKBOX_SIZE + CHECKBOX_ITEM_SPACING` | Spacing | fixed: where Breeze starts the label, from the indicator's leading edge (`indicator_body_width`) | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 43 | `CHECK_MARK_PEN` | `2.0` | Decoration | fixed: stroke width of the check mark, painted geometry | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 45 | `PARTIAL_DOT_SIZE` | `2.0` | Decoration | fixed: painted geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 47 | `PARTIAL_DOT_SPACING` | `4.0` | Decoration | fixed: painted geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 50 | `RADIO_DOT_RADIUS` | `3.0` | Decoration | fixed: visual Breeze 20 dp indicator (the 6 dp dot inside it), node floored to 24 by the widget's own `MinSize` | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 53 | `INDICATOR_CHECKED_WASH` | `0.3` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 59 | `SWITCH_HEIGHT` | `18.0` | Target | fixed: visual Breeze 18 dp switch, Kirigami's grid unit; the node is raised to the density's target size on both axes by `density_min_size` in the style's `layout_response` | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 61 | `SWITCH_WIDTH` | `2.0 * SWITCH_HEIGHT` | Target | fixed: visual Breeze 36 dp, twice the height (`const _: () = assert!` ties them); the node is floored to the density's target size by `density_min_size` | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 64 | `SWITCH_TRACK_INSET` | `3.0` | Decoration | fixed: part of the one coupled switch shape (`const _: () = assert!` ties track to height) | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 66 | `SWITCH_TRACK_RADIUS` | `6.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 69 | `SWITCH_HANDLE_RADIUS` | `9.0` | Decoration | fixed: the handle is a circle of the switch's full height, part of the one coupled shape | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 75 | `SWITCH_CHECKED_ALPHA` | `0.5` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 79 | `SWITCH_HANDLE_VELOCITY` | `200.0` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 231 | `COMBO_ARROW_BOX` | `20.0` | Decoration | fixed: the painted arrow box; the whole combo box is the target | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 234 | `COMBO_CHROME_WIDTH` | `2.0 * COMBO_BOX_FRAME_WIDTH + COMBO_ARROW_BOX + 2.0 + 4.0` | Spacing | fixed: the Compact sum of the combo box's chrome; laid out through `BreezeComboMetrics::chrome_width`, whose gaps scale with `spacing_factor` | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 238 | `COMBO_ARROW_GAP` | `COMBO_CHROME_WIDTH - COMBO_BOX_FRAME_WIDTH - COMBO_ARROW_BOX - FRAME_WIDTH` | Spacing | scales with `spacing_factor`: the arrow gap (`breeze_combo_metrics_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 241 | `SPIN_ARROW_MAX_HEIGHT` | `20.0` | Target | fixed: visual Breeze 20 dp arrow column height, split 10 + 10 by `BreezeSpinMetrics::arrow_half` at every density; the halves are below the floor under SC 2.5.8 *Equivalent* (the conformance gate's `StepButton` pin, 20 x 10) | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 245 | `SLIDER_GROOVE_RADIUS` | `3.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 254 | `SLIDER_DEFAULT_LENGTH` | `84.0` | Target | fixed: Qt's size hint along the groove, used only when nothing proposes a length | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 258 | `SLIDER_TICK_OVERHANG` | `SLIDER_GROOVE_THICKNESS * 0.5 + SLIDER_TICK_MARGIN + SLIDER_TICK_LENGTH - SLIDER_CONTROL_THICKNESS * 0.5` | Decoration | fixed: painted geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 375 | `MENU_ICON_TEXT_GAP` | `3.0` | Spacing | scales with `spacing_factor`: the icon-label gap (`breeze_menu_metrics_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 378 | `MENU_ARROW_BOX` | `20.0` | Decoration | fixed: the painted submenu arrow box; the row is the target | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 381 | `MENU_ARROW_TEXT_GAP` | `3.0` | Spacing | scales with `spacing_factor` inside the trailing column (`breeze_menu_metrics_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 384 | `MENU_HIGHLIGHT_ALPHA` | `0.3` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 387 | `POPUP_PADDING` | `MENU_ITEM_MARGIN` | Spacing | scales with `spacing_factor`: the popover's padding, `spacing(..)` in `styles/popover.rs` | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 391 | `TOOLTIP_MAX_WIDTH` | `320.0` | Decoration | fixed: a wrap width, no hit consequence | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 467 | `ROW_CONTENT_INSET` | `ITEM_VIEW_MARGIN_WIDTH + ITEM_VIEW_PADDING + 3.0` | Spacing | scales with `spacing_factor`: the row's horizontal padding (`breeze_row_metrics_for(&InputTokens)`) | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 470 | `ROW_HOVER_ALPHA` | `0.3` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 473 | `ROW_HOVER_OUTLINE_ALPHA` | `0.8` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 593 | `TAB_BAR_RADIUS` | `5.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 597 | `TAB_OUTLINE_RADIUS` | `4.5` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/styles/metrics.rs` | 646 | `SCROLL_BAR_SEPARATOR` | `1.0` | Decoration | fixed: hairline stroke; scaling it thickens the design language | +| `crates/teksilo-theme-kde/src/styles/popup.rs` | 58 | `TRANSLUCENT_MENU_OUTLINE_ALPHA` | `0.25` | Decoration | fixed: unitless (ratio / alpha / speed), not a dimension | +| `crates/teksilo-theme-kde/src/styles/recipes.rs` | 97 | `SUGGESTION_TEXT_INSET` | `MENU_ITEM_MARGIN + MENU_ITEM_TEXT_LEFT_MARGIN + MENU_ITEM_EXTRA_LEFT_MARGIN` | Spacing | scales with `spacing_factor`: the suggestion row's horizontal padding, `spacing(..)` in `styles/recipes.rs` | +| `crates/teksilo-theme-kde/src/styles/recipes.rs` | 107 | `LINK_FOCUS_RING_RADIUS` | `BREEZE_CONTROL_RADIUS + BREEZE_FOCUS_RING_WIDTH / 2.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-kde/src/styles/slider.rs` | 97 | `HANDLE_INSET` | `1.0` | Decoration | fixed: painted geometry, no hit consequence | +| `crates/teksilo-theme-kde/src/styles/slider.rs` | 102 | `SHADOW_OFFSET` | `0.5` | Decoration | fixed: elevation, not geometry | +| `crates/teksilo-theme-kde/src/styles/slider.rs` | 104 | `TICK_PEN` | `1.0` | Decoration | fixed: hairline stroke; scaling it thickens the design language | +| `crates/teksilo-theme-kde/src/styles/table.rs` | 65 | `HEADER_LINE_WIDTH` | `1.0` | Decoration | fixed: hairline stroke; scaling it thickens the design language | +| `crates/teksilo-theme-kde/src/styles/toggle.rs` | 84 | `TRAVEL` | `SWITCH_WIDTH - SWITCH_HEIGHT` | Decoration | fixed: the handle's travel, the switch's width less its height, part of the one coupled switch shape | +| `crates/teksilo-theme-kde/src/typography.rs` | 94 | `BREEZE_BODY_PT` | `10.0` | Decoration | fixed: a font size in points; follows the desktop font and the text scale, not target density | +| `crates/teksilo-theme-kde/src/typography.rs` | 96 | `BREEZE_SMALL_PT` | `8.0` | Decoration | fixed: a font size in points; follows the desktop font and the text scale, not target density | +| `crates/teksilo-theme-kde/src/typography.rs` | 98 | `BREEZE_MONO_PT` | `10.0` | Decoration | fixed: a font size in points; follows the desktop font and the text scale, not target density | | `crates/teksilo-theme-macos/src/palette.rs` | 104 | `SECONDARY_LABEL_ALPHA_LIGHT` | `0.50` | — not a dimension | fixed — unitless (ratio / alpha / duration / epsilon) | | `crates/teksilo-theme-macos/src/palette.rs` | 109 | `SECONDARY_LABEL_ALPHA_DARK` | `0.55` | — not a dimension | fixed — unitless (ratio / alpha / duration / epsilon) | | `crates/teksilo-theme-macos/src/palette.rs` | 298 | `FLOOR` | `4.5` | — not a dimension | fixed — unitless (ratio / alpha / duration / epsilon) | @@ -958,14 +1107,14 @@ on macOS is the minimum *hit box* around a control — the two `MinSize` sites i | `crates/teksilo-theme-macos/src/styles/chrome.rs` | 62 | `CONTROL_SHADOW_OFFSET_Y` | `0.5` | Decoration | fixed — elevation, not geometry | | `crates/teksilo-theme-macos/src/styles/chrome.rs` | 66 | `CONTROL_SHADOW_BLUR` | `1.5` | Decoration | fixed — elevation, not geometry | | `crates/teksilo-theme-macos/src/styles/chrome.rs` | 68 | `CATCH_LIGHT_THICKNESS` | `1.0` | Decoration | fixed — hairline rule | -| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 41 | `ITEM_HEIGHT` | `22.0` | Target | visual fixed at AppKit's `[measured]` 22 dp; coarse hit via `hit_outset` — the gaps around it scale, `macos_menu_item_recipe_for` | -| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 43 | `PADDING_H` | `10.0` | Spacing | scales with `spacing_factor` — `macos_menu_item_recipe_for(&InputTokens)` | -| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 45 | `ICON_COLUMN` | `14.0` | Decoration | fixed — decorative geometry, no hit consequence | -| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 48 | `ICON_LABEL_GAP` | `8.0` | Spacing | scales with `spacing_factor` — `macos_menu_item_recipe_for(&InputTokens)` | -| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 51 | `SHORTCUT_GAP` | `32.0` | Spacing | scales with `spacing_factor` — `macos_menu_item_recipe_for(&InputTokens)` | -| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 53 | `SEPARATOR_HEIGHT` | `11.0` | Spacing | scales with `spacing_factor` — `macos_menu_item_recipe_for(&InputTokens)` | -| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 55 | `HIGHLIGHT_RADIUS` | `4.0` | Decoration | fixed — corner radius is shape identity, not a hit target | -| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 57 | `HIGHLIGHT_INSET` | `5.0` | Spacing | scales with `spacing_factor` — `macos_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 48 | `ITEM_HEIGHT` | `22.0` | Target | visual fixed at AppKit's `[measured]` 22 dp; coarse hit via `hit_outset`; the gaps around it scale: `macos_menu_item_recipe_for` | +| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 50 | `PADDING_H` | `10.0` | Spacing | scales with `spacing_factor`: `macos_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 52 | `ICON_COLUMN` | `14.0` | Decoration | fixed: decorative geometry, no hit consequence | +| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 55 | `ICON_LABEL_GAP` | `8.0` | Spacing | scales with `spacing_factor`: `macos_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 58 | `SHORTCUT_GAP` | `32.0` | Spacing | scales with `spacing_factor`: `macos_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 60 | `SEPARATOR_HEIGHT` | `11.0` | Spacing | scales with `spacing_factor`: `macos_menu_item_recipe_for(&InputTokens)` | +| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 62 | `HIGHLIGHT_RADIUS` | `4.0` | Decoration | fixed: corner radius is shape identity, not a hit target | +| `crates/teksilo-theme-macos/src/styles/menu_item.rs` | 64 | `HIGHLIGHT_INSET` | `5.0` | Spacing | scales with `spacing_factor`: `macos_menu_item_recipe_for(&InputTokens)` | | `crates/teksilo-theme-macos/src/styles/metrics.rs` | 368 | `FLUENT_CONTROL_HEIGHT` | `32.0` | Target | scales with `target_size` — the preset's `macos_*_style_for(&InputTokens)` factories | | `crates/teksilo-theme-macos/src/styles/metrics.rs` | 52 | `MACOS_HELP_TAG_CORNER_RADIUS` | `5.0` | Decoration | fixed — corner radius is shape identity, not a hit target | | `crates/teksilo-theme-macos/src/styles/metrics.rs` | 56 | `MACOS_ROW_HEIGHT` | `24.0` | Target | scales with `target_size` — the preset's `macos_*_style_for(&InputTokens)` factories | diff --git a/engineering/docs/density-projection-gaps.md b/engineering/docs/density-projection-gaps.md index 8daeb9d7d..4165ca5d3 100644 --- a/engineering/docs/density-projection-gaps.md +++ b/engineering/docs/density-projection-gaps.md @@ -8,7 +8,7 @@ struct, and `for_tokens(&InputTokens)` projects each one onto the active density — `dp` for a target, `spacing` for a gap, `density_min_size` for a minimum box. A theme preset then builds that same struct with its own numbers. -**Forty-nine of those projected fields have no reader.** The widget composes the +**Forty-one of those projected fields have no reader.** The widget composes the dimension from the raw `pub const` in the recipe's module instead, so the projected field is written by `for_tokens`, written again by every preset, and consulted by nothing. Retuning one changes nothing on screen. @@ -47,14 +47,14 @@ and is therefore inside the scope; the rule finds both its projected fields read | | | |---|---| -| projected fields in scope | 103 | -| of those, unread by the rule | 51 | +| projected fields in scope | 102 | +| of those, unread by the rule | 43 | | false positives of the rule (§4) | 2 | -| **real gap** | **49** | -| — inert even if wired (§1) | 10 | -| — a shipped preset already sets a different value (§2) | 14 | -| — a preset sets the same value the constant carries (§3a) | 3 | -| — density only, no preset (§3b) | 22 | +| **real gap** | **41** | +| of which inert even if wired (§1) | 10 | +| of which a shipped preset already sets a different value (§2) | 6 | +| of which a preset sets the same value the constant carries (§3a) | 3 | +| of which density only, no preset (§3b) | 22 | Only the **denominator** is pinned by the guard. Every other number here falls when someone wires one of these fields, which is what the page is for, so making @@ -62,14 +62,20 @@ those cost a test failure would discourage the one change it invites: the tables are the record, the guard keeps their *membership* honest, and the arithmetic is a snapshot of the day it was taken. -Five further fields were on this list until the metrics-accessor work that -produced this page wired them, and they are the worked examples for what wiring -one looks like: `TableRecipe::cell_padding_horizontal` / `cell_padding_vertical` -(`TableStyle`), `CalendarRecipe::nav_arrow_size` (`CalendarStyle`) and +Twelve further fields were on this list until a metrics accessor wired them, +and they are the worked examples for what wiring one looks like: +`TableRecipe::cell_padding_horizontal` / `cell_padding_vertical` +(`TableStyle`), `CalendarRecipe::nav_arrow_size` (`CalendarStyle`), `SearchFieldRecipe::row_padding_horizontal` / `row_padding_vertical` -(`SearchFieldStyle`) — a defaulted metrics accessor on the style trait, defaulted -so an out-of-tree style keeps compiling, overridden by the `Recipe*Style` from -its own recipe so a preset's number takes effect. +(`SearchFieldStyle`), `StandardItemRecipe::min_height_single_line` / +`min_height_two_line` (`StandardItemStyle`), `TableRecipe::row_height` / +`header_height` (`TableStyle`), `TabRecipe::editor_height` / +`padding_horizontal` (`TabStyle`) and `ScrollBarRecipe::min_thumb_length` +(`ScrollBarStyle`): a defaulted metrics accessor on +the style trait, defaulted so an out-of-tree style keeps compiling, overridden +by the `Recipe*Style` from its own recipe so a preset's number takes effect. +A thirteenth, `TabRecipe::tool_window_height`, was deleted instead: neither it nor +its constant had a reader, and no tab strip has a tool-window height to give it. **A verdict of "safe to wire" means the mechanism is available and the change is local.** It never means the resulting dimension is right: a preset's number @@ -110,7 +116,7 @@ unwired one. ## 2. A shipped preset already sets a different value -The fourteen that cost something today. Each is written by Fluent or macOS with +The six that cost something today. Each is written by Fluent or macOS with a number chosen for that design language, and each renders the IntUI constant instead. The **renders** column is what a user sees under any theme. @@ -118,49 +124,22 @@ instead. The **renders** column is what a user sees under any theme. |---|---|---|---|---|---| | `CalendarRecipe` | `cell_size` | 32 | — | 28 | `calendar.rs` → `Calendar::build`, `build_weekday_row` | | `CalendarRecipe` | `header_height` | 28 | — | 26 | none — see below | -| `ScrollBarRecipe` | `min_thumb_length` | 24 | 30 | 28 | `scroll_bar.rs` → `ScrollBar::build` | | `SearchFieldRecipe` | `row_height` | 26 | — | 22 | `search_field.rs` → `SuggestionPanel::build` | | `SegmentedControlRecipe` | `height` | 24 | 32 | 22 | `segmented_control.rs` → `layout_response` | | `SegmentedControlRecipe` | `padding_horizontal` | 12 | — | 10 | `segmented_control.rs` → `layout_response`; `segmented_control/cell.rs` → `layout_response` | | `SegmentedControlRecipe` | `padding_vertical` | 6 | — | 3 | `segmented_control.rs` → `layout_response` | -| `StandardItemRecipe` | `min_height_single_line` | 28 | 40 | 24 | `standard_item.rs` → `layout_response` | -| `StandardItemRecipe` | `min_height_two_line` | 44 | 60 | 40 | `standard_item.rs` → `layout_response`; `notification/log.rs` → `build` | -| `TabRecipe` | `editor_height` | 50 | 32 | 26 | `tab_widget/bar.rs` → `build`; `tab_widget/header.rs` → `intrinsic_height` | -| `TabRecipe` | `padding_horizontal` | 12 | 8 | 12 | `tab_widget/header.rs` → `build`, `estimate_natural_width` | -| `TabRecipe` | `tool_window_height` | 28 | 32 | 22 | none — see below | -| `TableRecipe` | `header_height` | 32 | 40 | 24 | `table_view.rs` → `effective_header_height`; `tree_table_view.rs` → `effective_header_height` | -| `TableRecipe` | `row_height` | 28 | 40 | 24 | `table_view.rs` → `effective_row_height`; `tree_table_view.rs` → `effective_row_height` | Ladders, for the ones whose density projection also moves (Compact / Comfortable / Touch): `cell_size` 32/32/44, `CalendarRecipe::header_height` 28/32/44, -`min_thumb_length` 24/32/44, `SearchFieldRecipe::row_height` 26/32/44, -`SegmentedControlRecipe::height` 24/32/44 and `padding_horizontal` 12/13.8/15.6 -and `padding_vertical` 6/6.9/7.8, `min_height_single_line` 28/32/44, -`TabRecipe::padding_horizontal` 12/13.8/15.6, `tool_window_height` 28/32/44, -`TableRecipe::header_height` 32/32/44, `row_height` 28/32/44. -`min_height_two_line` (44) and `TabRecipe::editor_height` (50) are flat — their -cost is the preset alone, not the density. +`SearchFieldRecipe::row_height` 26/32/44, `SegmentedControlRecipe::height` +24/32/44 and `padding_horizontal` 12/13.8/15.6 and `padding_vertical` +6/6.9/7.8. ### Verdicts -**Safe to wire, `TableStyle` already has the seam.** `TableRecipe::row_height` -and `header_height` — both already flow through an `Option` override -(`effective_row_height` / `effective_header_height`), so the change is swapping -the `unwrap_or` default for a style accessor at one site each, in the same shape -`cell_padding_horizontal` just took. Note they feed the virtualization -arithmetic, so a test should assert a laid-out row band and not only the -accessor. - -**Safe to wire, one accessor each.** `ScrollBarRecipe::min_thumb_length` — -`ScrollBar::build` already resolves the style and then *recomputes* the floor -from the constant three lines later, so the value is already in hand. -`SearchFieldRecipe::row_height` — the same `SuggestionPanel::build` that now -takes its row gutters from `SearchFieldStyle`. -`StandardItemRecipe::min_height_single_line` / `min_height_two_line` — the one -gap already documented at its own site (`standard_item.rs`'s module header says -the row does not read them); `StandardItemStyle` is the trait, and the row's -`layout_response` is the single call site. This is the highest-visibility row in -the table: it is the height of every list and tree row in a Fluent or macOS app. +**Safe to wire, one accessor.** `SearchFieldRecipe::row_height`, read by the +same `SuggestionPanel::build` that now takes its row gutters from +`SearchFieldStyle`. **Needs care — the widget owns a composition the field cannot express.** `SegmentedControlRecipe::height` / `padding_horizontal` / `padding_vertical`: @@ -168,14 +147,10 @@ the segmented control's `layout_response` and its overflow planner compute cell extents from these three together, and the planner is also fed by `measure_intrinsic` on dormant cells. Wiring them means threading the style into the planner, not just into a `Padding`. -`TabRecipe::editor_height` / `padding_horizontal`: the tab bar's header widths -come from `estimate_natural_width`, which runs before a style is resolved in the -bar's own build; the seam has to be chosen before the numbers can move. **Needs care — nothing applies the dimension at all.** -`CalendarRecipe::header_height` and `TabRecipe::tool_window_height`: their -constants have no reader either, so the calendar's header row and the -tool-window tab strip take their height from their content. Wiring the field is +`CalendarRecipe::header_height`: its constant has no reader either, so the +calendar's header row takes its height from its content. Wiring the field is therefore not "read the style instead of the constant" but "give this row a fixed height", which is a design decision and not a mechanical one. @@ -250,9 +225,9 @@ to — moving it at Touch changes what a drag can do, not only what is painted, so it wants a resize test rather than a layout one. **Nothing to wire.** `DateEditRecipe::calendar_button_width` and `segment_gap`, -`SearchFieldRecipe::input_panel_gap`: like the two in §2, their constants have -no reader either. The dimension is not applied anywhere, so there is nothing to -route through a style. +`SearchFieldRecipe::input_panel_gap`: like `CalendarRecipe::header_height` in +§2, their constants have no reader either. The dimension is not applied +anywhere, so there is nothing to route through a style. ## 4. The rule's false positives @@ -271,6 +246,20 @@ these two also show: reading the *default* recipe is not the same as reading the *active style*, so a preset that set `box_hit_area` would still not be honoured. Neither preset sets either, so nothing is lost today. +## A preset must not write a field on this page + +Writing a field that no widget reads is writing a number nobody renders, and a +preset whose design language publishes the number would look faithful in its own +tests while painting the IntUI constant. The KDE preset is held to that from the +page itself: `crates/teksilo-theme-kde/tests/density_gaps.rs` parses sections 1 +to 3 and fails when a recipe struct literal in the crate sets a listed field, or +when a factory that builds such a recipe gives one a value other than the stock +recipe's at any density. A row added here is covered there with no edit, and a +field wired by a metrics accessor (the twelve above) leaves the page and becomes +one KDE may set. That is what let Breeze's list, table, tab and scroll-bar +numbers (row and header heights, the tab height and padding, the minimum thumb +length) take effect rather than only be written. + ## The guard `crates/teksilo-widgets/tests/density_projection_gaps.rs` re-derives the unread diff --git a/engineering/docs/hover-affordance-census.md b/engineering/docs/hover-affordance-census.md index daf69013a..6c8a5ab6a 100644 --- a/engineering/docs/hover-affordance-census.md +++ b/engineering/docs/hover-affordance-census.md @@ -82,7 +82,7 @@ interaction-state mirror: `split_button.rs:732` (`hover_within` → tint) · `standard_item.rs:722` (the tint half) · `tab_widget/header.rs:660` · every `is_hovered` consumer in `crates/teksilo-widgets/src/styles/recipe_*_style.rs` and in -`crates/teksilo-theme-{fluent,macos,material3}/src/styles/*.rs`. +`crates/teksilo-theme-{fluent,kde,macos,material3}/src/styles/*.rs`. Swept with zero hover-reveal hits: `crates/teksilo-preview-ui/src` (no `on_hover` at all), `crates/teksilo-webview/src`, `crates/teksilo-terminal/src`, diff --git a/engineering/docs/kde-breeze-spec.md b/engineering/docs/kde-breeze-spec.md new file mode 100644 index 000000000..b4d7d3117 --- /dev/null +++ b/engineering/docs/kde-breeze-spec.md @@ -0,0 +1,2857 @@ + + + +# KDE colour model and Breeze metrics: clean-room specification + +This document is the authoritative specification from which Teksilo +re-implements the KDE Plasma colour model (crate `teksilo-kde-scheme`) and +transcribes the Breeze design language (crate `teksilo-theme-kde`). It +overrides the implementation plan and the synthesised design wherever they +disagree. + +It is written in prose and mathematics only. It contains no upstream code, +no pseudo-code that mirrors upstream function structure, and no upstream +internal identifiers. Values are facts; behaviour is described as inputs, +outputs, order of operations and rounding. The closing ledger (section o) +records, for every number, where it was observed upstream. The ledger is a +list of references, never an excerpt. + +Who may read what: + +- **Role A** (the author of this file) read the upstream sources listed in + section a. +- **Role B** (the implementer of `math.rs`, `effects.rs`, `palette.rs` and + `accent.rs`) reads only this file, Qt's public documentation, WCAG 2.x, + Björn Ottosson's OKLab article and the existing Teksilo crates. Role B + never reads upstream KDE sources, research notes, or the ledger's target + files. + +Normative words: **must**, **must not**, **should**, **may** have their RFC +2119 meanings. A statement tagged **[divergence]** is a deliberate, +documented difference between Teksilo and KDE. A statement tagged +**[pin]** is behaviour of the Qt 6 runtime that this document describes as +precisely as it can but that the black-box golden vectors in +`tools/kde_golden` must confirm; where a golden disagrees with a **[pin]** +statement, the golden wins and this document is corrected in the same +commit. Every former **[pin]** has now been checked against the goldens +and is tagged [measured-qt] (or [qt] where Qt documents it); none is open. + +Provenance tags used in tables (the same tags the preset uses at each +assignment): + +| Tag | Meaning | +|---|---| +| [M] | Breeze widget-style metrics header (breeze, kstyle) | +| [S] | Breeze widget-style sizing and drawing code (breeze, kstyle) | +| [Hp] | Breeze widget-style painting helper (breeze, kstyle) | +| [Kc] | Breeze style configuration schema, `breeze.kcfg` | +| [K] | Kirigami (frameworks/kirigami) | +| [Qd] | qqc2-desktop-style (frameworks/qqc2-desktop-style) | +| [CS] | KDE colour-scheme library (frameworks/kcolorscheme) | +| [CU] | KDE colour utilities and HCY space (frameworks/kguiaddons) | +| [KC] | KDE configuration library (frameworks/kconfig) | +| [AP] | Plasma accent application (plasma-workspace, kcms/colors) | +| [PI] | Plasma Qt platform integration (plasma/plasma-integration) | +| [PK] | xdg-desktop-portal-kde | +| [PW] | other plasma-workspace files (startup, fonts KCM) | +| [qt] | Qt's public documentation or documented Qt behaviour | +| [measured-qt] | measured in the C0 golden image (`tools/kde_golden/vectors`, Qt 6.11.1, offscreen) | +| [derived] | a rule stated at the assignment, not an upstream value | +| [wcag] | a WCAG lift (Teksilo's, never upstream) | +| [unpublished] | no upstream source; Teksilo's choice | + +--- + +## a. Scope and pinned versions + +### a.1 What is specified + +1. The KConfig text grammar and the cascade KDE uses to read `kdeglobals` + and colour-scheme files (section b). +2. The KDE colour-scheme model: sets, roles, defaults, effects, contrast + (section c). +3. The colour mathematics: the HCY colour space, luma, contrast, blends, + tint, shade roles, HSV lighter and darker, 8-bit alpha blending, the + OKLab tint (section d). +4. State effects for the Disabled and Inactive colour groups (section e). +5. The computed backgrounds and the QPalette-equivalent roles (section f). +6. Plasma's accent application, every key it writes (section g). +7. The ten accent swatches (section h). +8. The light/dark predicate (section i). +9. Desktop settings: fonts, animation factor, font DPI, widget style, + `breezerc` (section j). +10. Breeze geometry and painting rules for every control the preset styles + (section k). +11. Typography (section l) and motion (section m). +12. The answers to the C0 open items (section n) and the ledger (section o). + +Out of scope, documented elsewhere in the plan: Union, Ocean, the Breeze +window decoration (KWin draws it under server-side decorations), Plasma +shell translucency, Kirigami heading levels as rendered styles. + +### a.2 Pinned upstream versions + +| Repository | Tag | Commit | +|---|---|---| +| plasma/breeze | v6.8.0 | cb1de02b5d4fa81d309e33a3f8cd8795ff975336 | +| frameworks/qqc2-desktop-style | v6.31.0 | 2efe38d55ebcfa97599643daada20c157fa1a8a5 | +| frameworks/kirigami | v6.31.0 | eecf183a956fdbfbf331c0ef61fd6e35220d4275 | +| plasma/xdg-desktop-portal-kde | v6.8.0 | d0b8eb0467369a570f57f7fe9242c1bfbf34faf2 | +| frameworks/kconfig | v6.31.0 | a25acdfb1f51c9d8af3426f03380a9de5c88817a | +| plasma/plasma-integration | v6.8.0 | 354bc2ab5bd52715d5a01358ecabfaeb8366152d | +| plasma/plasma-workspace | v6.8.0 | 9d82cf039a17a300c696c87ad32d3113507ed7f9 | +| frameworks/kguiaddons | v6.31.0 | 92ebbc758fa58bb89e6d73fdd24e50a296259bd3 | +| frameworks/kcolorscheme | v6.31.0 | b01fde3096de99584786aa412e0130661c97f7e8 | +| qt/qtbase (the runtime, d.11, d.15) | v6.11.1 | 59c81a3c2247b821b9b84b4eb8d939b77e07e276 | + +The runtime the behaviour was observed against is Qt 6 (Plasma 6.8 ships on +Qt 6.10 or later). Every statement about Qt numeric behaviour refers to Qt 6, +where colour channels are stored as 16-bit integers and the floating-point +channel accessors are single precision. + +Fonts: Noto Sans v2.015 (notofonts latin-greek-cyrillic release +NotoSans-v2.015) and Hack v3.003 (source-foundry). Hashes are in the ledger. + +Fixtures: the three v6.8.0 Breeze colour files (`BreezeLight.colors`, +`BreezeDark.colors`, `BreezeClassic.colors`) are vendored verbatim under +`crates/teksilo-kde-scheme/tests/fixtures/breeze-v6.8.0/` with their LGPL +headers and excluded from the package. The copies in kcolorscheme v6.31.0 +differ from the breeze v6.8.0 files only in translated `Name[...]` lines and +in SPDX header lines; every colour, effect, `[KDE]` and `[WM]` value is +identical. + +### a.3 Representation conventions (normative for every section) + +These conventions are what makes 8-bit equality with KDE possible. + +- **Stored colour.** A colour value is four 16-bit unsigned channels + (red, green, blue, alpha), each an integer `q` in 0..65535. +- **From 8 bits.** A channel given as an 8-bit integer `v` (0..255) is + stored as `q = 257 v`. Every colour read from a configuration file in the + `r,g,b[,a]` form enters the model this way; `#rrggbb` and the other hex + forms are covered in section b.9. +- **Reading a channel as a real number.** The real value of a stored + channel is `F(q) = q / 65535` computed in single precision (IEEE 754 + binary32, round to nearest), then widened to double precision for all + further arithmetic. Call this a *channel read*. +- **Storing a real value.** When an operation produces a real channel value + `v` in [0, 1], it is stored as `Q16(v)`: first round `v` to single + precision, then form the single-precision product `p = v x 65535` + (rounded to single precision), then `q = floor(p + 1/2)`. Alpha is stored + the same way. +- **Quantisation points.** Every operation in section d that returns a + colour stores its result with `Q16`. The next operation reads the stored + channels with `F`. Intermediate real values inside one operation are never + quantised unless this document says so. +- **To 8 bits.** The 8-bit value of a stored channel `q` is + `E8(q) = floor((q + 128) / 257)`, in integer arithmetic. This is exactly + `round(q / 257)` (no tie can occur), for example `E8(7067) = 27`. + [measured-qt]: the Qt 6.11 runtime's 8-bit channel accessors and its + packed 8-bit colour value both agree with this rule on all 65536 values of + `q`, and every `hex` field of the goldens equals `E8` of its `u16` field. + Every colour written to a configuration file, every value compared "8-bit + equal" in the goldens and every 8-bit read (the dark predicate) uses + `E8`. +- **Arithmetic.** All other real arithmetic is double precision (binary64), + evaluated in the order written. Where this document says *single + precision*, the named product or sum is rounded to binary32 at that step. + `F32(x)` names that rounding explicitly, and `R(x) = floor(x + 1/2)` is + the conversion of a non-negative single-precision value to an integer + used inside Q16. +- **Clamp to the unit interval.** `N(x)` maps x to x if 0 < x < 1, to 0 if + x <= 0, and to 1 if x >= 1 **or if x is NaN**. (A NaN input maps to 1, not + to 0.) +- **Interpolation.** `L(a, b, t) = a + (b - a) t` (exactly this form, not + `(1 - t) a + t b`), unless a step says otherwise. + +Teksilo's own `Color` type holds four `f32` in 0..1. The kde-scheme crate +must keep the 16-bit channel integers internally (or an `f32` that is +exactly `q / 65535` and is converted back by `q = round(65535 c)`), and must +convert to Teksilo's `Color` only at the crate boundary. + +--- + +## b. KConfig text grammar and cascade + +### b.1 Bytes and lines + +1. A file is read as bytes. An empty file contributes nothing. +2. The file is split into lines at byte 0x0A only. Each line is then + trimmed of leading and trailing ASCII whitespace (space, tab, CR, LF, + VT, FF). A CR before the LF is therefore removed; a file that uses lone + CR separators is a single line. +3. A line longer than 1.5 MiB without a newline: KDE warns and parses it + as consecutive 1.5 MiB pieces, each treated as a line. Teksilo skips such + a line and records a diagnostic **[divergence]** (an error case only). +4. A UTF-8 byte order mark is **not** recognised. A BOM at the start of the + file is part of the first line, so if the first line is a group header + it is not recognised as one (it does not start with `[`); it is treated + as an entry line, has no `=`, and is rejected as an error. Entries that + follow it belong to the root group until the next valid header. Teksilo + must reproduce this and should emit a diagnostic naming the BOM. The + plan's grammar list mentions the BOM without saying what happens; this + rule is the decision. +5. Bytes are decoded as UTF-8 only when a value, key or group name is used + as text; invalid sequences decode to U+FFFD. + +### b.2 Comments and blank lines + +A trimmed line that is empty, or whose first byte is `#`, is ignored. A `#` +anywhere else is literal text. A `;` is never a comment. + +### b.3 Group headers + +1. A trimmed line whose first byte is `[` is a group header. +2. The header is a sequence of bracketed segments `[a][b]...`. The first + segment starts after the first `[`; each segment ends at the next `]`. A + further segment follows only if the byte after a `]` is `[`. Anything + after the last `]` that does not start a new segment is ignored. +3. If a segment has no closing `]`, the header is invalid: the line is + skipped **and the current group does not change**, so following entries + go to the previous group. +4. Segments are joined into one hierarchical group name. Nested groups such + as `[Colors:Header][Inactive]` name the group `Inactive` inside + `Colors:Header`. A nested group is a separate group: its entries are not + inherited from, and do not fall back to, the parent group. +5. The segment `[$i]` is an option, not a name, when it is exactly `$i` + and it is the last segment of the line (the line ends right after its + `]`). If it is the only segment on the line, the **file** becomes + immutable from this point (b.6). If it follows a name, the **group** is + immutable. A `[$i]` segment in any other position is part of the name. +6. Escape sequences (b.5) are decoded inside each name segment. +7. Entries that appear before any header belong to the root group. After a + bare `[$i]` line, entries before the next named header belong to a group + whose name is empty. + +### b.4 Entry lines + +1. A non-header line is an entry. The key is the text before the first + `=`, trimmed; the value is the text after it, trimmed. +2. A line with an empty key is ignored (not counted as an error). +3. Trailing bracketed suffixes on the key are processed from the right: + - `[$...]` is an option list: each letter `i` (immutable entry), `e` + (environment expansion allowed), `d` (deleted entry) applies; any other + letter is ignored. + - Any other bracket `[xx]` is a locale tag. A second locale tag on one + key is an error and the line is skipped. + - A `[` without a later `]` is an error and the line is skipped. + - `Key[de][$i]` and `Key[$i][de]` are both valid. +4. A line with no `=` is an error and is skipped, unless its options contain + `d`. +5. `[$d]`: the entry is recorded as deleted, its value (if any) is ignored. + A deleted entry hides the same key from every lower layer; a higher + layer that sets the key again makes it present again. +6. Locale-tagged entries: KDE uses them only when the tag equals the + current locale or its language part. Teksilo must ignore every + locale-tagged entry. None of the keys this specification reads is + localised in practice (only `Name` in colour files is). +7. Errors that are counted: missing `]`, second locale, missing `=`, + invalid escape. After the 100th counted error the rest of the file is + not read; entries read so far are kept. + +### b.5 Escapes + +Escape decoding applies to keys, group-name segments and values, and only +when the text contains a backslash. The text is trimmed again first. Then, +left to right: + +| Sequence | Result | +|---|---| +| `\s` | a space | +| `\t` | a tab | +| `\n` | a line feed | +| `\r` | a carriage return | +| `\\` | one backslash | +| `\xHH` | the byte with hexadecimal value HH (two hex digits, either case) | +| `\;` | the two characters backslash and semicolon (kept) | +| `\,` | the two characters backslash and comma (kept) | +| a backslash at the very end | kept as one backslash | + +- `\x` with fewer than two following characters yields the character `x` + and ends decoding. `\x` followed by two characters of which one is not a + hex digit yields `x` and consumes both characters. +- Any other escape is invalid. KDE then keeps a partially decoded value of + the original length. Teksilo keeps the backslash and the following + character verbatim, continues decoding, and records a diagnostic + **[divergence]** (an error case only; no KDE-written file contains it). + +Values never contain unescaped leading or trailing spaces because writers +escape them as `\s`. + +### b.6 Layers, precedence, immutability + +A *cascade* is an ordered list of layers, lowest priority first. Each layer +is one file. Each layer is either a *default layer* (every system file, and +the user's own `system.kdeglobals`) or the *user layer* (the user's own +`kdeglobals`, the only file a KDE program writes). + +The entries of all layers are merged into one map from (group, key) to +value, applying the layers in order. For each entry of the layer being +applied: + +1. If the map already holds an **immutable** entry for that (group, key), + the new entry is ignored. +2. Else if the map holds no entry for that (group, key) and the **group is + marked immutable** by a lower layer, the new entry is ignored. +3. Else, if the layer is the user layer and the group was already marked + immutable when this layer's header for that group was read, every entry + of that group in this layer is ignored, including keys that lower layers + set without immutability. +4. Otherwise the new entry replaces the old one (or is added). It becomes + immutable if it carries `[$i]`, or if its group header in this layer + carried `[$i]`, or if the file became immutable before its group header. + +Consequences that tests must pin: + +- **Duplicate keys.** Within one file the **last** occurrence wins, and a + higher layer wins over a lower one, except that an occurrence that is + immutable freezes the value: later occurrences in the same file and in + higher layers are ignored. Inside a group whose header carries `[$i]`, + and in every group after a bare `[$i]` line, every occurrence is + immutable, so the **first** occurrence wins. +- **Group immutability** is set after the whole file has been read, so a + file can re-open its own immutable group later and add to it. It applies + to that exact group only, not to nested groups. +- **File immutability.** When a layer contains a bare `[$i]` line, no + higher layer is read at all. + +A group **exists** when it, or any group nested inside it, holds at least +one entry that is not deleted. This is the test used for "the cascade has +`[Colors:View]`" and for "the set has an `[Inactive]` subgroup". + +### b.7 The `kdeglobals` cascade + +Let `CH` be `$XDG_CONFIG_HOME` (default `$HOME/.config`) and `CD1..CDn` the +entries of `$XDG_CONFIG_DIRS` in the order listed (default `/etc/xdg`). +Empty and relative entries are ignored [qt]. Only files that exist count. +The layers, lowest priority first: + +1. `/etc/kde5rc`, if readable, unless the environment variable + `KDE_SKIP_KDERC` is set. +2. `CDn/system.kdeglobals`, ..., `CD1/system.kdeglobals` (last listed + directory first). +3. `CDn/kdeglobals`, ..., `CD1/kdeglobals`. +4. `CH/system.kdeglobals` (a default layer). +5. `CH/kdeglobals` (the user layer). + +All system `system.kdeglobals` files rank below all system `kdeglobals` +files. A file-immutable layer in steps 1 to 3 suppresses steps 4 and 5. + +**kdedefaults.** A Plasma session start prepends `CH/kdedefaults` to +`XDG_CONFIG_DIRS` before any program starts (using `/etc/xdg` as the rest +when the variable was empty). A program started in a Plasma session +therefore sees `CH/kdedefaults/kdeglobals` as the highest-priority system +layer, below the user's `CH/kdeglobals`. Teksilo must honour +`XDG_CONFIG_DIRS` exactly. When the session is KDE (b.10) and +`CH/kdedefaults` is not already one of the entries, Teksilo should prepend +it, reproducing the session start **[derived]**; this is what makes a +program launched outside the session environment agree with one launched +inside it. + +### b.8 Locating a colour-scheme file + +Let `DH` be `$XDG_DATA_HOME` (default `$HOME/.local/share`) and `DD1..DDn` +the entries of `$XDG_DATA_DIRS` (default `/usr/local/share` then +`/usr/share`). A relative data path `P` resolves to the first existing file +among `DH/P`, `DD1/P`, ..., `DDn/P`. + +When the `kdeglobals` cascade has no group `[Colors:View]` (b.6, exists), +KDE's platform integration builds the palette from, in order of preference: + +1. `plasma/look-and-feel//contents/colors`, where `` is + `[KDE] LookAndFeelPackage` (default `org.kde.breeze.desktop`); +2. `color-schemes/.colors`, where `` is `[General] ColorScheme` + (default `BreezeLight`); +3. `color-schemes/BreezeLight.colors`. + +That file is read as a full configuration with the `kdeglobals` cascade +below it, so a key the scheme file lacks (for example `[KDE] contrast`) +falls back to `kdeglobals`. **KDE does not apply `[General] AccentColor` in +this fallback** (see n.13; Teksilo applies it there, by decision). + +**A file-immutable `kdeglobals` layer and the scheme file.** This +specification does not say whether a bare `[$i]` line in a `kdeglobals` +layer (b.6) also keeps the scheme file out. Teksilo's rule **[derived]**: +the lock suppresses the higher layers of the `kdeglobals` cascade (b.7 +steps 4 and 5) and nothing else. The scheme file is not one of those +layers, so it is still read on top of the cascade and the accent is still +applied to it (n.13), and the reported origin is the file actually read. +Every entry the lock made immutable, and every group a `[$i]` header +locked, keeps b.6 rules 1 and 2, so the scheme file cannot change them: it +supplies only the keys the locked layers do not set, and replaces only the +entries that were never locked, as it does without the lock. Reason: a +locked `[General] ColorScheme` names the scheme its administrator wants, +and leaving the file out would show the compiled defaults instead. + +**`KDE_COLOR_SCHEME_PATH` is not an environment variable.** It is an +in-process application property that a KDE program sets on itself when it +chooses its own scheme; KDE reads no environment variable of that name. +Teksilo must not read such an environment variable and claim KDE +behaviour. If Teksilo offers a per-application scheme override, it is an +explicit API parameter that replaces the scheme file of step 2. This +corrects the plan (n.14). + +### b.9 Value syntaxes + +All values are trimmed and escape-decoded (b.5) before interpretation. + +**Strings** are used as they are, except when the entry carries `[$e]` +(b.11). + +**Integers and reals** use the C locale. An unparseable value yields the +reader's default. Booleans: a value equal, ignoring ASCII case, to `false`, +`no`, `off` or `0` is false; **every other value is true, including an +empty value** (confirmed: `resolve.json` scheme `SyntheticValueForms` +has `[ColorEffects:Inactive] Enable=` and resolves with its Inactive +effects on). An empty value is present (not absent): it does not +fall back to the reader's default, except for integers and reals, whose +parse fails and so gives the default. + +**Colours** (the same rules for every colour key): + +| Stored text | Result in KDE | Teksilo | +|---|---|---| +| key absent or deleted | the reader's default | the compiled default (c.3) | +| empty, or exactly `invalid` | an *invalid colour* (not the default) | treated as absent **[divergence]** | +| starts with `#` | Qt's hex forms: `#RGB`, `#RRGGBB`, `#AARRGGBB`, `#RRRGGGBBB`, `#RRRRGGGGBBBB` [qt]; anything else is an invalid colour | hex forms parsed as Qt documents [qt]: the 3- and 6-digit forms give 8-bit channels (a single digit d means 17 d, stored as `257 x 17 d`), `#AARRGGBB` adds an 8-bit alpha; a 9-digit form gives 12-bit channels v stored as `q = 16 v + floor(v / 256)`, a 12-digit form gives the 16-bit channels as written [measured-qt]; any other digit count, or a non-hex digit, is treated as absent **[divergence]** | +| contains no comma and no leading `#` | an SVG 1.1 colour keyword (case-insensitive) or `transparent` [qt]; unknown names give an invalid colour (the CSS-only keyword `rebeccapurple` is unknown [measured-qt]) | the SVG 1.1 keyword table [qt]; unknown treated as absent **[divergence]** | +| contains commas | exactly 3 or 4 components, each a base-10 integer (optional sign, optional surrounding ASCII whitespace; both confirmed by `resolve.json`) in 0..255; red, green, blue, then alpha; anything else (wrong count, not an integer, out of range) gives the reader's default | same; a rejected value is treated as absent | + +An invalid colour in KDE behaves like opaque black in arithmetic but is +not a colour a user can intend; the plan's decision to treat it as absent +stands, and it is the only colour divergence. + +When KDE **writes** a colour, it writes `r,g,b` (decimal, no spaces) when +the 8-bit alpha `E8(qa)` is 255, `r,g,b,a` otherwise, and `invalid` for an +invalid colour. Each component is `E8` of the stored channel. + +When KDE writes a real number it writes the shortest decimal text that +reads back to the same double (for example `0.2`, `0.15`). Integers are +plain decimal. When KDE *copies* an entry from a scheme file it copies the +**text** verbatim (so `#3daee9` stays `#3daee9`); when it reads a colour and +writes it back it re-formats it as `r,g,b`. Section g says which keys are +copied and which are re-formatted. + +### b.10 KDE session detection + +A session is KDE when `XDG_CURRENT_DESKTOP`, split at `:`, contains `KDE`, +or when `KDE_FULL_SESSION` equals `true`. (The Plasma session start sets +both, plus `KDE_SESSION_VERSION=6`.) Teksilo compares the desktop names +ignoring ASCII case **[derived]**: the session start writes `KDE`, and +Teksilo's desktop detection has always ignored case. + +### b.11 `[$e]` expansion + +Expansion applies only when the entry carries `[$e]` **and** the value is +read as a plain string. Values read as colours, numbers or booleans are +never expanded. Font keys are read as strings, so they are expanded (j.1). Expansion scans left to right for `$`: + +1. `$$` becomes a single `$`; scanning resumes after it. +2. `${NAME}` (up to the next `}`, or to the end of the value if there is + none) and `$NAME` (the longest run of letters, digits and `_` following + the `$`; letters and digits in the Unicode sense) are replaced by the + value of environment variable `NAME`. An unset or empty variable is + replaced by nothing, except that `QT_DATA_HOME`, `QT_CONFIG_HOME` and + `QT_CACHE_HOME`, when unset, are replaced by the XDG data, config and + cache home directories. +3. A `$` followed by anything else, including `(`, removes just the `$`. + `$(command)` therefore becomes `(command)`. **No command is ever + executed**, and a test must pin this. An empty `${}` is removed + entirely. +4. A trailing lone `$` is kept. + +The injected environment of `KdeConfigLocations::from_env` is the only +source of variable values. + +--- + +## c. The colour-scheme model + +### c.1 Sets, roles, states + +There are seven colour sets, each stored in its own group: + +| Set | Group | +|---|---| +| View | `[Colors:View]` | +| Window | `[Colors:Window]` | +| Button | `[Colors:Button]` | +| Selection | `[Colors:Selection]` | +| Tooltip | `[Colors:Tooltip]` | +| Complementary | `[Colors:Complementary]` | +| Header | `[Colors:Header]` | + +Each set has twelve stored roles (the keys): `BackgroundNormal`, +`BackgroundAlternate`, `ForegroundNormal`, `ForegroundInactive`, +`ForegroundActive`, `ForegroundLink`, `ForegroundVisited`, +`ForegroundNegative`, `ForegroundNeutral`, `ForegroundPositive`, +`DecorationFocus`, `DecorationHover`. + +There are three states (colour groups): Active, Inactive, Disabled. + +After resolution (sections e and f) each (state, set) has 8 foregrounds +(Normal, Inactive, Active, Link, Visited, Negative, Neutral, Positive), 8 +backgrounds (Normal, Alternate and the six computed ones), 2 decorations +(Focus, Hover) and 5 shade roles (Light, Midlight, Mid, Dark, Shadow). + +### c.2 Reading a set for a state + +For a given (state, set): + +1. Choose the **source group** and the **default table**: + - View, Window, Button, Tooltip, Complementary: their own group and their + own table (c.3). + - Header: group `[Colors:Header]`; the default for each key is the value + of the **same key in `[Colors:Window]`** if present there, else the + Header table value. This is a per-key fallback, not a wholesale one: a + Header group that sets only some keys takes the others from Window, + and a scheme without a Header group gets Window's keys for all twelve. + The two decoration keys follow the same rule against the decoration + defaults. + - Selection, Active state: `[Colors:Selection]` with the Selection + table. + - Selection, Inactive state, when **ChangeSelectionColor** (c.5) is + true: group `[Colors:Window]` with the **Window** table, and a *tint + colour* T equal to `[Colors:Selection] BackgroundNormal` (top-level + group, default 61,174,233). + - Selection, Inactive state, when ChangeSelectionColor is false: + `[Colors:Selection]` with the Selection table. + - Selection, Disabled state: `[Colors:Window]` with the Window table and + no tint colour. +2. **Inactive subgroup.** For the Inactive state only: if the source group + has a nested group `[Inactive]` that exists (b.6), that nested group + becomes the source group. Its missing keys take the defaults chosen in + step 1 (for Header: Window's top-level key, else the Header table); they + do **not** fall back to the parent group's keys. For Selection with + ChangeSelectionColor the nested group looked at is + `[Colors:Window][Inactive]`. +3. Read each of the twelve keys from the source group, falling back to the + default. A colour that is treated as absent (b.9) takes the default. +4. **Selection tint.** If a tint colour T was chosen in step 1, replace + BackgroundNormal by `tint(BackgroundNormal, T, 0.4)` and + BackgroundAlternate by `tint(BackgroundAlternate, T, 0.4)` (d.9). The + foregrounds and decorations stay the Window values. +5. Apply the state effects (section e) for every state other than Active. + **This includes the Inactive state when an `[Inactive]` subgroup + supplied the colours**: the subgroup replaces the source values, and the + Inactive effects still run on top of them (they are no-ops for Breeze, + whose Inactive effects are disabled). +6. Compute the six derived backgrounds (section f.1). + +The Tooltip set used for palettes is always the Active state's (f.2). + +### c.3 Compiled defaults for absent keys + +These are the values used when a key is absent. They equal Breeze Light except for +`DecorationHover`. +`DecorationFocus` default for every set is 61,174,233; `DecorationHover` +default is **147,206,233** (note: not the 61,174,233 the Breeze files +write). + +| Key | View | Window | Button | Selection | Tooltip | Complementary | Header | +|---|---|---|---|---|---|---|---| +| BackgroundNormal | 255,255,255 | 239,240,241 | 252,252,252 | 61,174,233 | 247,247,247 | 42,46,50 | 222,224,226 | +| BackgroundAlternate | 247,247,247 | 227,229,231 | 163,212,250 | 163,212,250 | 239,240,241 | 27,30,32 | 239,240,241 | +| ForegroundNormal | 35,38,41 | 35,38,41 | 35,38,41 | 255,255,255 | 35,38,41 | 252,252,252 | 35,38,41 | +| ForegroundInactive | 112,125,138 | 112,125,138 | 112,125,138 | 112,125,138 | 112,125,138 | 161,169,177 | 112,125,138 | +| ForegroundActive | 61,174,233 | 61,174,233 | 61,174,233 | 255,255,255 | 61,174,233 | 61,174,233 | 61,174,233 | +| ForegroundLink | 41,128,185 | 41,128,185 | 41,128,185 | 253,188,75 | 41,128,185 | 29,153,243 | 41,128,185 | +| ForegroundVisited | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | +| ForegroundNegative | 218,68,83 | 218,68,83 | 218,68,83 | 176,55,69 | 218,68,83 | 218,68,83 | 218,68,83 | +| ForegroundNeutral | 246,116,0 | 246,116,0 | 246,116,0 | 198,92,0 | 246,116,0 | 246,116,0 | 246,116,0 | +| ForegroundPositive | 39,174,96 | 39,174,96 | 39,174,96 | 23,104,57 | 39,174,96 | 39,174,96 | 39,174,96 | + +(The Header column applies only where `[Colors:Window]` lacks the key.) + +### c.4 The stock Breeze schemes (v6.8.0), for the Rust constants + +These are the fixture values; the constants must be tested equal to the +fixture files. + +**Breeze Light** (`ColorScheme=BreezeLight`, `Name=Breeze Light`). Every set +has DecorationFocus 61,174,233 and DecorationHover 61,174,233. + +| Key | View | Window | Button | Selection | Tooltip | Complementary | Header | Header [Inactive] | +|---|---|---|---|---|---|---|---|---| +| BackgroundNormal | 255,255,255 | 239,240,241 | 252,252,252 | 61,174,233 | 247,247,247 | 42,46,50 | 222,224,226 | 239,240,241 | +| BackgroundAlternate | 247,247,247 | 227,229,231 | 163,212,250 | 163,212,250 | 239,240,241 | 27,30,32 | 239,240,241 | 227,229,231 | +| ForegroundNormal | 35,38,41 | 35,38,41 | 35,38,41 | 255,255,255 | 35,38,41 | 252,252,252 | 35,38,41 | 35,38,41 | +| ForegroundInactive | 112,125,138 | 112,125,138 | 112,125,138 | 112,125,138 | 112,125,138 | 161,169,177 | 112,125,138 | 112,125,138 | +| ForegroundActive | 61,174,233 | 61,174,233 | 61,174,233 | 255,255,255 | 61,174,233 | 61,174,233 | 61,174,233 | 61,174,233 | +| ForegroundLink | 41,128,185 | 41,128,185 | 41,128,185 | 253,188,75 | 41,128,185 | 29,153,243 | 41,128,185 | 41,128,185 | +| ForegroundVisited | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | +| ForegroundNegative | 218,68,83 | 218,68,83 | 218,68,83 | 176,55,69 | 218,68,83 | 218,68,83 | 218,68,83 | 218,68,83 | +| ForegroundNeutral | 246,116,0 | 246,116,0 | 246,116,0 | 198,92,0 | 246,116,0 | 246,116,0 | 246,116,0 | 246,116,0 | +| ForegroundPositive | 39,174,96 | 39,174,96 | 39,174,96 | 23,104,57 | 39,174,96 | 39,174,96 | 39,174,96 | 39,174,96 | + +`[KDE] contrast=4` (no `frameContrast`). `[WM]`: activeBackground +227,229,231; activeBlend 227,229,231; activeForeground 35,38,41; +inactiveBackground 239,240,241; inactiveBlend 239,240,241; +inactiveForeground 112,125,138. `[General] shadeSortColumn=true`. + +**Breeze Dark** (`ColorScheme=BreezeDark`, `Name=Breeze Dark`). Every set +has DecorationFocus 61,174,233 and DecorationHover 61,174,233. + +| Key | View | Window | Button | Selection | Tooltip | Complementary | Header | Header [Inactive] | +|---|---|---|---|---|---|---|---|---| +| BackgroundNormal | 20,22,24 | 32,35,38 | 41,44,48 | 61,174,233 | 41,44,48 | 32,35,38 | 41,44,48 | 32,35,38 | +| BackgroundAlternate | 29,31,34 | 41,44,48 | 30,87,116 | 30,87,116 | 32,35,38 | 30,87,116 | 32,35,38 | 41,44,48 | +| ForegroundNormal | 252,252,252 | 252,252,252 | 252,252,252 | 252,252,252 | 252,252,252 | 252,252,252 | 252,252,252 | 252,252,252 | +| ForegroundInactive | 161,169,177 | 161,169,177 | 161,169,177 | 161,169,177 | 161,169,177 | 161,169,177 | 161,169,177 | 161,169,177 | +| ForegroundActive | 61,174,233 | 61,174,233 | 61,174,233 | 252,252,252 | 61,174,233 | 61,174,233 | 61,174,233 | 61,174,233 | +| ForegroundLink | 29,153,243 | 29,153,243 | 29,153,243 | 253,188,75 | 29,153,243 | 29,153,243 | 29,153,243 | 29,153,243 | +| ForegroundVisited | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | 155,89,182 | +| ForegroundNegative | 218,68,83 | 218,68,83 | 218,68,83 | 176,55,69 | 218,68,83 | 218,68,83 | 218,68,83 | 218,68,83 | +| ForegroundNeutral | 246,116,0 | 246,116,0 | 246,116,0 | 198,92,0 | 246,116,0 | 246,116,0 | 246,116,0 | 246,116,0 | +| ForegroundPositive | 39,174,96 | 39,174,96 | 39,174,96 | 23,104,57 | 39,174,96 | 39,174,96 | 39,174,96 | 39,174,96 | + +`[KDE] contrast=4`. `[WM]`: activeBackground 39,44,49; activeBlend +252,252,252; activeForeground 252,252,252; inactiveBackground 32,36,40; +inactiveBlend 161,169,177; inactiveForeground 161,169,177. +`[General] shadeSortColumn=true`. + +**Breeze Classic** (`ColorScheme=BreezeClassic`, `Name=Breeze Classic`). No +`[Colors:Header]` group (Header resolves to Window per key). Decorations: +DecorationFocus 61,174,233 and DecorationHover 147,206,233 in View, Window, +Button, Selection, Tooltip; Complementary has DecorationFocus 30,146,255 and +DecorationHover 61,174,230. + +| Key | View | Window | Button | Selection | Tooltip | Complementary | +|---|---|---|---|---|---|---| +| BackgroundNormal | 252,252,252 | 239,240,241 | 239,240,241 | 61,174,233 | 35,38,39 | 49,54,59 | +| BackgroundAlternate | 239,240,241 | 189,195,199 | 189,195,199 | 29,153,243 | 77,77,77 | 59,64,69 | +| ForegroundNormal | 35,38,39 | 35,38,39 | 35,38,39 | 252,252,252 | 252,252,252 | 239,240,241 | +| ForegroundInactive | 127,140,141 | 127,140,141 | 127,140,141 | 239,240,241 | 189,195,199 | 175,176,179 | +| ForegroundActive | 61,174,233 | 61,174,233 | 61,174,233 | 252,252,252 | 61,174,233 | 147,206,233 | +| ForegroundLink | 41,128,185 | 41,128,185 | 41,128,185 | 253,188,75 | 41,128,185 | 61,174,230 | +| ForegroundVisited | 127,140,141 | 127,140,141 | 127,140,141 | 189,195,199 | 127,140,141 | 61,174,230 | +| ForegroundNegative | 218,68,83 | 218,68,83 | 218,68,83 | 176,55,69 | 218,68,83 | 231,76,60 | +| ForegroundNeutral | 246,116,0 | 246,116,0 | 198,92,0 | 246,116,0 | 246,116,0 | 253,188,75 | +| ForegroundPositive | 39,174,96 | 39,174,96 | 39,174,96 | 23,104,57 | 39,174,96 | 46,204,113 | + +`[General]` also has `accentActiveTitlebar=true`, `accentInactiveTitlebar=false`, +`shadeSortColumn=true`. `[KDE] contrast=4`. `[WM]`: activeBackground +71,80,87; activeBlend 252,252,252; activeForeground 252,252,252; +inactiveBackground 239,240,241; inactiveBlend 75,71,67; inactiveForeground +189,195,199. + +**Effects in all three files** (identical): + +`[ColorEffects:Disabled]`: Color 56,56,56; ColorAmount 0; ColorEffect 0; +ContrastAmount 0.65; ContrastEffect 1; IntensityAmount 0.1; +IntensityEffect 2. (No `Enable` key, so enabled by default.) + +`[ColorEffects:Inactive]`: ChangeSelectionColor true; Color 112,111,110; +ColorAmount 0.025; ColorEffect 2; ContrastAmount 0.1; ContrastEffect 2; +**Enable false**; IntensityAmount 0; IntensityEffect 0. + +### c.5 `[ColorEffects:Disabled]` and `[ColorEffects:Inactive]` + +Keys, read from the configuration being resolved (top-level groups): + +| Key | Type | Meaning | +|---|---|---| +| Enable | bool | whether the state's effects run | +| IntensityEffect | int | 0 none, 1 shade, 2 darken, 3 lighten; any other value: none | +| IntensityAmount | real | the intensity amount | +| ColorEffect | int | 0 none, 1 desaturate, 2 fade, 3 tint; any other value: none | +| ColorAmount | real | the colour amount | +| Color | colour | the effect colour, read only when ColorEffect is greater than 0 | +| ContrastEffect | int | 0 none, 1 fade, 2 tint; any other value: none | +| ContrastAmount | real | the contrast amount | +| ChangeSelectionColor | bool | Inactive group only; see c.2 | + +Defaults when a key is absent: + +| Key | Disabled | Inactive | +|---|---|---| +| Enable | **true** | **false** | +| IntensityEffect | 2 (darken) | 0 | +| IntensityAmount | 0.10 | 0.0 | +| ColorEffect | 0 | 1 (desaturate) | +| ColorAmount | 0.0 | -0.9 | +| Color | 56,56,56 | 112,111,110 | +| ContrastEffect | 1 (fade) | 2 (tint) | +| ContrastAmount | 0.65 | 0.25 | + +`ChangeSelectionColor` defaults to the value of `Enable` in +`[ColorEffects:Inactive]` **with a default of true** for that lookup. So a +configuration with no `[ColorEffects:Inactive]` group at all has Inactive +effects off and ChangeSelectionColor on. + +When Enable is false, all three effect kinds are "none" and the amounts are +irrelevant; the effect pass returns its inputs **unchanged and +unquantised** (no colour operation runs). + +### c.6 `[KDE] contrast` and `[KDE] frameContrast` + +- `contrast` is an integer, default 7. The **contrast value** used by the + shade roles is `k = 0.1 x contrast`. It is not clamped when read; the + shade-role computation clamps it (d.10). An unparseable value gives 7. + When KDE has no configuration at all (the system-palette mode used under + a platform high-contrast preference with no scheme) the contrast value is + 0.7. +- `frameContrast` is a real, default 0.2, clamped to [0, 1]. An + unparseable value gives 0.2. It is not used by the colour model itself; + Breeze uses it as the frame intensity bias `fc` (section k). + +### c.7 `[WM]` + +Six colour keys describe title bars: `activeBackground`, +`activeForeground`, `inactiveBackground`, `inactiveForeground`, +`activeBlend`, `inactiveBlend`. The accent application always writes all +six (g.5, g.7). The widget style itself reads only the first four (for its MDI +title bars), defaulting to the Active Highlight, Active HighlightedText, +Disabled Highlight and Disabled HighlightedText of the application palette +when absent. + +### c.8 `[General]` keys + +| Key | Where | Meaning | +|---|---|---| +| ColorScheme | kdeglobals, scheme | the scheme name; default `BreezeLight` | +| Name | scheme | display name (localised variants ignored) | +| AccentColor | kdeglobals | the user's accent; consulted by the accent application (g.1) | +| TintFactor | scheme | presence turns on accent tinting (g.3, g.7); value default 0.15 | +| TitlebarIsAccentColored | scheme | accent-coloured title bar; falls back to `accentActiveTitlebar`, then false | +| accentActiveTitlebar | scheme | legacy name of the previous key | +| accentInactiveTitlebar | scheme | legacy; not read by the accent application | +| ColorSchemeHash | kdeglobals | lower-case hex SHA-1 of the applied scheme file's bytes; written by the accent application | +| shadeSortColumn | scheme | item-view sort-column shading; not used by this specification | + +--- + +## d. Colour mathematics + +All operations here take and return stored colours (a.3) unless they return +a real number. Operation names in this section are this document's own. + +### d.1 Constants + +- Gamma exponent: 2.2 (forward), 1/2.2 (inverse). +- Luma weights (ITU-R BT.709): `wr = 0.2126`, `wg = 0.7152`, + `wb = 0.0722`. + +### d.2 Linearisation + +For a channel read `x` (a.3): `lin(x) = N(x) ^ 2.2` and +`delin(x) = N(x) ^ (1/2.2)`, using the real power function in double +precision. + +### d.3 Luma + +`luma(C) = lin(R) wr + lin(G) wg + lin(B) wb`, evaluated left to right, +where R, G, B are the channel reads of C. Alpha is ignored. Range 0 to 1. + +This is not WCAG relative luminance: it uses a pure 2.2 power instead of +the sRGB transfer curve. + +### d.4 HCY forward conversion + +Input: a stored colour. Output: four reals (h, c, y, a). + +1. `r = lin(R)`, `g = lin(G)`, `b = lin(B)`; `a` is the channel read of + alpha. +2. `y = r wr + g wg + b wb` (as d.3). +3. `p = max(r, g, b)`, `n = min(r, g, b)`, `d = 6 (p - n)`. +4. Hue, tested in this order: + - if `n = p`: `h = 0`; + - else if `r = p`: `h = (g - b) / d`; + - else if `g = p`: `h = (b - r) / d + 1/3`; + - else: `h = (r - g) / d + 2/3`. + + `h` is **not wrapped**: when red is the maximum and blue exceeds green, + `h` lies in (-1/6, 0). It stays negative inside the operations of d.6 + and is wrapped only by the inverse conversion. (Where a hue is reported + to a caller as a number in [0, 1), add 1 when negative.) +5. Chroma: if `r = g = b`: `c = 0`; else + `c = max((y - n) / y, (p - y) / (1 - y))`. + +Equality tests in steps 4 and 5 are exact floating-point comparisons. + +### d.5 HCY inverse conversion + +Input: (h, c, y, a). Output: a stored colour. + +1. `h1 = wrap(h)`, where `wrap(h)` is the remainder of h divided by 1 with + the sign of h (C `fmod`), plus 1 if that remainder is negative; a zero + or NaN remainder gives 0. `c1 = N(c)`, `y1 = N(y)`. +2. `s = 6 h1`. Choose the sector and compute `t` and `m`: + +| Sector | Condition | t | m | +|---|---|---|---| +| 0 | s < 1 | s | wr + wg t | +| 1 | s < 2 | 2 - s | wg + wr t | +| 2 | s < 3 | s - 2 | wg + wb t | +| 3 | s < 4 | 4 - s | wb + wg t | +| 4 | s < 5 | s - 4 | wb + wr t | +| 5 | otherwise | 6 - s | wr + wb t | + +3. Compute the three linear components (top `P`, middle `O`, bottom `B0`): + - if `m >= y1`: + `P = y1 + y1 c1 (1 - m) / m`, + `O = y1 + y1 c1 (t - m) / m`, + `B0 = y1 - y1 c1`; + - else: + `P = y1 + (1 - y1) c1`, + `O = y1 + (1 - y1) c1 (t - m) / (1 - m)`, + `B0 = y1 - (1 - y1) c1 m / (1 - m)`. + + Products and quotients are evaluated left to right as written (for + example `y1 c1 (1 - m) / m` is `((y1 x c1) x (1 - m)) / m`). +4. Assign to (red, green, blue) by sector: + +| Sector | red | green | blue | +|---|---|---|---| +| 0 | P | O | B0 | +| 1 | O | P | B0 | +| 2 | B0 | P | O | +| 3 | B0 | O | P | +| 4 | O | B0 | P | +| 5 | P | B0 | O | + +5. Each channel is `delin` of its component, stored with Q16. Alpha `a` is + stored with Q16. + +Round trip: forward then inverse must reproduce any 8-bit colour exactly +(a proptest over 8-bit inputs). Example: 61,174,233 has +h = 0.58336232, c = 0.88592616, y = 0.37683496 and returns to 61,174,233. + +### d.6 Luma operations + +Each converts the input to HCY (d.4), changes y and c, keeps h and a, and +converts back (d.5). + +- **Luma lighten** with amount `ky` (default 0.5) and chroma factor `kc` + (default 1): `y' = 1 - N((1 - y)(1 - ky))`, `c' = 1 - N((1 - c) kc)`. + Amount 1 gives white, 0.5 halves the distance to white, 0 keeps the + luma. +- **Luma darken** with amount `ky` (default 0.5) and chroma factor `kc` + (default 1): `y' = N(y (1 - ky))`, `c' = N(c kc)`. +- **Luma shade** with luma amount `ky` and chroma amount `kc` (default 0): + `y' = N(y + ky)`, `c' = N(c + kc)`. + +Even with amounts that leave y and c unchanged, the round trip re-quantises +the colour. (The effect pass avoids this by not calling an operation when +the effect is "none", c.5.) + +### d.7 Premultiplied mix + +**Mix**(C1, C2, t): + +1. If `t <= 0`, return C1 unchanged. Else if `t >= 1`, return C2 unchanged. + Else if t is NaN, return C1 unchanged. (Tested in this order.) +2. `a = L(a1, a2, t)` from the alpha channel reads. If `a <= 0`, return + (0, 0, 0, 0). +3. For each colour channel: form the premultiplied values `p1 = x1 a1` and + `p2 = x2 a2` **in single precision** (x the channel reads), then + `x = N'(L(p1, p2, t)) / a`, where `N'` clamps to [0, 1] (no NaN case + arises). +4. Store the three channels and `a` with Q16. + +### d.8 Luma contrast ratio + +`ratio(Y1, Y2) = (Y1 + 0.05) / (Y2 + 0.05)` if `Y1 > Y2`, otherwise +`(Y2 + 0.05) / (Y1 + 0.05)`, for two lumas from d.3. Between colours: +`ratio(luma(C1), luma(C2))`. It has WCAG's shape but uses d.3's luma, so +its values differ from WCAG contrast; Teksilo's WCAG checks must use +Teksilo's own WCAG helpers, never this. + +### d.9 Luma-preserving tint + +**Tint**(base B, colour C, amount t), default t = 0.3: + +1. If `t <= 0` return B unchanged; else if `t >= 1` return C unchanged; + else if t is NaN return B unchanged. +2. `Yb = luma(B)`; `ri = ratio(Yb, luma(C))`; + `rg = 1 + ((ri + 1) x t x t x t)` (left to right). +3. Bisection on `s` in [0, 1] with `l = 0`, `u = 1`, exactly **12** + iterations. In each iteration: + - `s = (l + u) / 2`; + - `M = Mix(B, C, s ^ 0.3)` (a stored colour); + - `Ys = L(Yb, luma(M), s)`; + - if `ratio(Yb, Ys) > rg` then `u = s`, else `l = s`. +4. The result uses the **last midpoint `s` computed in the loop** (the + twelfth midpoint), not the final `l` or `u`: take `M = Mix(B, C, s ^ 0.3)`, + convert M to HCY, replace its y by `L(Yb, y_M, s)`, keep h, c and alpha, + and convert back (d.5). + +The exponent 0.3 and the cube in rg are exact constants. `s ^ 0.3` uses the +real power function. + +### d.10 Shade roles + +**ShadeRole**(colour C, role, contrast value k), with chroma amount 0: + +1. Clamp k: if `k >= 1` or k is NaN, k = 1; else if `k <= -1`, k = -1. +2. `y = luma(C)`, `yi = 1 - y`. +3. If `y < 0.006` (very dark): Light = shade(C, 0.05 + 0.95 k); + Mid = shade(C, 0.01 + 0.20 k); Dark = shade(C, 0.02 + 0.40 k); + Midlight and Shadow = shade(C, 0.03 + 0.60 k). +4. Else if `y > 0.93` (very light): Midlight = shade(C, -0.02 - 0.20 k); + Dark = shade(C, -0.06 - 0.60 k); Shadow = shade(C, -0.10 - 0.90 k); + Light and Mid = shade(C, -0.04 - 0.40 k). +5. Otherwise, with `la = (0.05 + 0.55 y)(0.25 + 0.75 k)` and + `da = (-y)(0.55 + 0.35 k)`: + - Light = shade(C, la); + - Midlight = shade(C, (0.15 + 0.35 yi) la); + - Mid = shade(C, (0.35 + 0.15 y) da); + - Dark = shade(C, da); + - Shadow = darken(shade(C, da), 0.5 + 0.3 y) with chroma factor 1. + +"shade" and "darken" are d.6 (shade with chroma amount 0). Example: Breeze +Light Window background 239,240,241 with k = 0.4 gives Light 255,255,255, +Midlight 246,247,247, Mid 196,200,204, Dark 136,142,147, Shadow 71,74,76 +(computed from this specification; the goldens are authoritative). + +### d.11 HSV lighter and darker (Qt semantics) + +These reproduce Qt's documented `lighter(factor)` and `darker(factor)` +colour functions [qt]. The factor is an integer percentage. + +- factor <= 0: return the colour unchanged. +- lighter with factor < 100 is darker with factor `floor(10000 / factor)`; + darker with factor < 100 is lighter with factor `floor(10000 / factor)`. +- Convert to HSV with 16-bit value and saturation (0..65535) and hue in + hundredths of a degree (0..36000, or "undefined" for an achromatic + colour), as below. +- lighter (factor >= 100): `v' = floor(factor x v / 100)` (integers). If v' + exceeds 65535, the excess is removed from saturation: + `s' = max(0, s - (v' - 65535))` and `v' = 65535`. +- darker (factor >= 100): `v' = floor(v x 100 / factor)` (integers); s + unchanged. +- Convert back to RGB and store. Alpha is kept as stored. + +**RGB to HSV**, on the stored 16-bit integers r, g, b [measured-qt]: + +1. `max` and `min` of the three integers; `v = max`. +2. If `max = min` (exact integer equality): hue undefined, `s = 0`. +3. Otherwise `k = max - min` as a single-precision value; + `s = R(F32(k / v) x 65535)`, where the quotient and the product are each + rounded to single precision and `R(x) = floor(x + 1/2)` turns the + single-precision value into an integer. +4. Hue position `p` (single precision): if `v = r`, `p = (g - b) / k`, plus 6 + when negative; else if `v = g`, `p = 2 + (b - r) / k`; else + `p = 4 + (r - g) / k`. The integer differences are exact; each quotient + and sum is rounded to single precision. The stored hue is + `R(F32(p x 6000))`, which can be 36000. + +**HSV to RGB** [measured-qt]: + +1. If `s = 0` or the hue is undefined, all three channels are `v`. +2. Otherwise, in single precision: `h = 0` when the hue is 36000, else + `h = hue / 6000`; `S = s / 65535`; `V = v / 65535`; `i = trunc(h)`, + `f = h - i`; `p = V (1 - S)`; for odd i, `q = V (1 - S f)`; for even i, + `t = V (1 - S (1 - f))` (each product and difference rounded to single + precision, innermost first). +3. Channels (red, green, blue) by i: 0: (V, t, p); 1: (q, V, p); 2: (p, V, + t); 3: (p, q, V); 4: (t, p, V); 5: (V, p, q). Each is stored as + `R(F32(x x 65535))` (the Q16 of a.3). + +`math.json` member `qt_lighter_darker` pins these rules: 62 colours x 14 +factors (including -10, 0, 1, 50, 99, 100, 102, 110, 120, 150 and 1000), +both directions, all reproduced exactly. + +### d.12 8-bit alpha blend + +**Blend8**(foreground F with alpha, opaque or translucent background G) +operates on 8-bit channel values (E8 of the stored channels) and returns a +colour built from 8-bit integers: + +1. `af` = the alpha channel read of F (single precision value `qa / 65535`). +2. If `af = 0`, return G unchanged. +3. `ai = 1 - af` in double precision (af widened). +4. If G's alpha read is exactly 1: each channel is + `trunc(P + ai x g8)`, where `P` is the **single-precision** product + `af x f8` and the sum is double precision; `trunc` discards the fraction + (all values are non-negative). Alpha is 255. The result is stored from + 8-bit values (`q = 257 v`). +5. Otherwise (translucent background; never the case for Breeze): with + `ab` = G's alpha read, `aib = ab x ai`, each channel is + `trunc(P + aib x g8)` and the **alpha is the integer part of + `af + aib`**, which is 0 unless the sum is exactly 1, and then 1 (out of + 255). The colour channels are not un-premultiplied. KDE writes this + nearly transparent result; Teksilo must reproduce it for byte equality. + +Alpha quantisation that feeds Blend8 (setting an alpha as a fraction +uses Q16): + +| Fraction | Stored alpha q | Effective af | +|---|---|---| +| 0.7 | 45875 | 0.70000761747 (single precision) | +| 0.6 | 39321 | 0.60000002384 (single precision) | +| 1.0 | 65535 | 1.0 | + +Example (why the precision rules matter): white at alpha 0.6 over 255 +gives `trunc(153.0 + 0.39999997616 x 255) = trunc(254.99999392) = 254`, +not 255. + +### d.13 Black-or-white foreground rule + +**OnColour**(background G, active flag): + +1. Start from white. If `ratio(luma(G), luma(white)) < 4.5` (d.8, so + KDE's luma ratio, not WCAG), choose black; otherwise white. + Equivalently, black exactly when `luma(G) > 1.05 / 4.5 - 0.05` + (about 0.183333). +2. Give the chosen colour alpha 1.0 when active, 0.6 when inactive. +3. Return `Blend8(chosen, G)`. + +Active results are pure black or pure white. Inactive results are the +chosen colour at 0.6 over G with the truncation of d.12. + +### d.14 Accent background blend + +**AccentBg**(accent A, background G) = `Blend8(A with alpha 0.7, G)`. +Only the 8-bit values of A are used. Example: 61,174,233 over 255,255,255 +gives 119,198,239; over 20,22,24 gives 48,128,170. + +### d.15 OKLab tint + +**OklabTint**(base B, colour C, factor f) is the accent tint of g.3 and g.7. It +follows Björn Ottosson's OKLab definition ("A perceptual color space for +image processing", 2020, MIT licence), using the matrices **as Ottosson +published them to ten decimal places**: + +- linear sRGB to LMS: l = 0.4122214708 r + 0.5363325363 g + 0.0514459929 b; + m = 0.2119034982 r + 0.6806995451 g + 0.1073969566 b; + s = 0.0883024619 r + 0.2817188376 g + 0.6299787005 b; +- cube roots `l^, m^, s^` (real power 1/3); +- L = 0.2104542553 l^ + 0.7936177850 m^ - 0.0040720468 s^; + a = 1.9779984951 l^ - 2.4285922050 m^ + 0.4505937099 s^; + b = 0.0259040371 l^ + 0.7827717662 m^ - 0.8086757660 s^; +- inverse: l^ = L + 0.3963377774 a + 0.2158037573 b; + m^ = L - 0.1055613458 a - 0.0638541728 b; + s^ = L - 0.0894841775 a - 1.2914855480 b; cube each; + r = 4.0767416621 l - 3.3077115913 m + 0.2309699292 s; + g = -1.2684380046 l + 2.6097574011 m - 0.3413193965 s; + b = -0.0041960863 l - 0.7034186147 m + 1.7076147010 s. + +**sRGB to linear (SL) and linear to sRGB (LS)** are Qt's colour-space +transforms between its named sRGB and linear sRGB spaces, applied to a +stored colour [measured-qt]. Everything below is single precision (each +operation rounded to binary32), and the single-precision power is the +correctly rounded binary32 power (computing it in double precision and +rounding once reproduces every golden). + +- Constants: `A = F32(1 / F32(1.055))`, `B = F32(F32(0.055) / F32(1.055))`, + `K = F32(1 / F32(12.92))`, `D = F32(0.04045)`, `G = F32(2.4)`; for the + inverse curve `D' = F32(K D)`, `K' = F32(1 / K)`, + `A' = F32(F32(1 / A) ^ G)`, `E' = F32(-B / A)`, `G' = F32(1 / G)`. +- Forward curve T(x), x a channel read: with `u = |x|`, `T = K u` when + `u < D`, else `T = (A u + B) ^ G`; the sign of x is restored. +- Inverse curve T'(y): with `u = |y|`, `T' = K' u` when `u < D'`, else + `T' = (A' u) ^ G' + E'`; the sign of y is restored. +- Matrix M (applied in both directions; it is the identity up to + single-precision rounding, because Qt forms it as the inverse of the sRGB + to XYZ matrix times that matrix). With input channels (r, g, b), each + output channel is `(r m1 + g m2) + b m3`, products and sums in single + precision, left to right, with (m1, m2, m3) = + red: (`0x1.fffffep-1` = 0.99999994039535522, `0x1.4p-25` = + 3.7252902984619141e-08, `-0x1p-25` = -2.9802322387695312e-08); + green: (`0x1.928p-26` = 2.3428583517670631e-08, 1, 0); + blue: (0, 0, `0x1.fffffcp-1` = 0.99999988079071045). + The values are binary32 numbers written in hexadecimal floating point. +- SL(X): read X's channels, apply T to each, then M; the linear space's own + curve is the identity. LS(X): read X's channels (identity curve), apply + M, then T' to each. +- **Storing a float colour** (used by SL, LS and step 4): if all three + channels lie in [0, 1], store each with Q16; otherwise the colour is held + in **half precision**: each channel (and the alpha, 1) is rounded to the + nearest binary16 value (ties to even). A channel read of such a colour is + its binary16 value. Converting it to 16-bit channels (step 4, and the 8-bit + write of a.3) clamps each binary16 value to [0, 1] and stores it with Q16. + M can push a channel of an in-gamut colour a few units in the last place + outside [0, 1] (for example the red channel of pure blue becomes + -2.98e-08), and such a colour is then held in half precision. + +Steps: + +1. `Bl = SL(B)`, `Cl = SL(C)` (stored as above). +2. Read the channels of Bl and Cl and convert each to OKLab in double + precision (cube roots by the real power 1/3). +3. Keep B's L. Replace B's a by `Lf(aB, aC, f)` and b by `Lf(bB, bC, f)`, + where `Lf(x, y, f) = x (1 - f) + y f` is evaluated with x, y and f first + rounded to single precision, `(1 - f)` and `x (1 - f)` in double, `y f` + in single, the sum in double, and **the result rounded to single + precision**. +4. Convert back to linear sRGB in double precision, round each channel to + single precision, store the float colour as above (the range test is on + the single-precision values), and convert it to 16-bit channels. +5. The result is `LS` of that colour, stored as above. Alpha is opaque. + +The written value is the E8 projection of step 5 (clamped first when it is +held in half precision). Goldens: `math.json` members `qt_colorspace` (SL +and LS of 573 colours; 6 of the 1146 results are held in half precision) and +`qt_extended` (half-precision storage and its conversion) pin the +conversions exactly; `accent.json` (the `KdeGoldenTintLight` cases, whose +tinted Button background 232,255,255 leaves the gamut) pins the whole +tint. A rule that skips M, the half-precision storage, or the +single-precision curve constants (using `((x + 0.055) / 1.055) ^ 2.4` +instead) still matches those 40 oracle cases but differs from Plasma in +about 1 tint in 1400 over random inputs, so the details above are +normative. + +--- + +## e. State effects (Disabled and Inactive) + +### e.1 When they run + +For every set and for the Disabled and Inactive states (never Active), +after c.2 steps 1 to 4, using the effect parameters of that state (c.5). +The Tooltip set is resolved for the palette in the Active state only (f.2), +but its Inactive and Disabled resolutions follow the same rules when a +caller asks for them. + +### e.2 Order of application within one (state, set) + +Let `BG0` be the set's BackgroundNormal **before** any effect (after the +selection tint of c.2 step 4, if any). + +1. **Foregrounds and decorations first**, each against `BG0`: for each of + the 8 foregrounds and the 2 decorations, apply the foreground chain + (e.3) with background `BG0`. +2. **Then the two stored backgrounds**: BackgroundNormal and + BackgroundAlternate each get the background chain (e.3). +3. Only after both steps are the computed backgrounds derived (f.1), from + the post-effect BackgroundNormal and the post-effect foregrounds. + +### e.3 The chains + +**Background chain** on a colour X: + +1. Intensity: shade gives `shade(X, IntensityAmount)` (chroma amount 0); + darken gives `darken(X, IntensityAmount)` (chroma factor 1); lighten + gives `lighten(X, IntensityAmount)` (chroma factor 1); none leaves X. +2. Colour, on the result: desaturate gives `darken(X, 0, 1 - ColorAmount)` + (luma amount 0, chroma factor `1 - ColorAmount`); fade gives + `Mix(X, Color, ColorAmount)`; tint gives `Tint(X, Color, ColorAmount)`; + none leaves it. + +**Foreground chain** on a colour X against background G: + +1. Contrast: fade gives `Mix(X, G, ContrastAmount)`; tint gives + `Tint(X, G, ContrastAmount)`; none leaves X. +2. Then the background chain (intensity, then colour) on the result. + +Decorations use the foreground chain. + +### e.4 Breeze consequences + +- **Disabled** (all three Breeze files): every foreground and decoration + becomes `darken(Mix(X, BG0, 0.65), 0.1)`; each background becomes + `darken(X, 0.1)`. Example (Breeze Light Window): ForegroundNormal + 35,38,41 becomes 160,161,163; BackgroundNormal 239,240,241 becomes + 227,229,231 (computed from this specification). +- **Inactive**: Enable is false, so foregrounds, backgrounds and + decorations pass through exactly. ChangeSelectionColor is true, so the + Inactive **Selection** set is the Window set (or `[Colors:Window][Inactive]` + if present) with both backgrounds tinted 0.4 toward the Selection + BackgroundNormal, and with the Window set's foregrounds. Example (Breeze + Light): Inactive Selection BackgroundNormal is + `Tint(239,240,241, 61,174,233, 0.4)` = 194,224,245, BackgroundAlternate + `Tint(227,229,231, 61,174,233, 0.4)` = 176,216,243, ForegroundNormal + 35,38,41 (computed from this specification). +- **Disabled Selection** is the Window set with the Disabled effects and no + tint. +- **Header, Inactive**: Breeze has `[Colors:Header][Inactive]`, which + supplies the Inactive Header colours (no effects change them). + +A scheme that enables Inactive effects (Oxygen-style schemes) gets the +full chains; Teksilo must honour them. + +--- + +## f. Computed backgrounds and palette roles + +### f.1 Computed backgrounds + +For each (state, set), from the post-effect values: + +| Background | Value | +|---|---| +| ActiveBackground | Tint(BackgroundNormal, ForegroundActive, 0.3) | +| LinkBackground | Tint(BackgroundNormal, ForegroundLink, 0.3) | +| VisitedBackground | Tint(BackgroundNormal, ForegroundVisited, 0.3) | +| NegativeBackground | Tint(BackgroundNormal, ForegroundNegative, 0.3) | +| NeutralBackground | Tint(BackgroundNormal, ForegroundNeutral, 0.3) | +| PositiveBackground | Tint(BackgroundNormal, ForegroundPositive, 0.3) | + +Examples (Breeze Light View, Active): ActiveBackground 236,246,252; +NegativeBackground 250,236,237; NeutralBackground 254,241,234; +PositiveBackground 221,251,232 (computed from this specification). + +### f.2 Palette roles (the QPalette-equivalent table) + +For each state (Active, Inactive, Disabled): + +| Palette role | Source | +|---|---| +| Window | Window BackgroundNormal | +| WindowText | Window ForegroundNormal | +| Base | View BackgroundNormal | +| Text | View ForegroundNormal | +| AlternateBase | View BackgroundAlternate | +| Link | View ForegroundLink | +| LinkVisited | View ForegroundVisited | +| PlaceholderText | View ForegroundInactive | +| Button | Button BackgroundNormal | +| ButtonText | Button ForegroundNormal | +| Highlight | Selection BackgroundNormal | +| HighlightedText | Selection ForegroundNormal | +| Accent | Selection BackgroundNormal (same as Highlight) | +| ToolTipBase | Tooltip BackgroundNormal **of the Active state**, in every state | +| ToolTipText | Tooltip ForegroundNormal **of the Active state**, in every state | +| Light | ShadeRole(Window BackgroundNormal of this state, Light, k) | +| Midlight | ShadeRole(..., Midlight, k) | +| Mid | ShadeRole(..., Mid, k) | +| Dark | ShadeRole(..., Dark, k) | +| Shadow | ShadeRole(..., Shadow, k) | + +`k` is the contrast value of c.6. The Header and Complementary sets have no +palette role; the preset reads them from the resolved palette directly. +KDE does not set BrightText: it keeps Qt's default, opaque white +255,255,255, in every state (`resolve.json` records it for every +scheme) [measured-qt]. + +Breeze Light with the default k (contrast 4, so k = 0.4) gives the Light to +Shadow values of d.10. + +--- + +## g. Accent application + +This section describes, completely, what Plasma does when an accent colour +is applied to a scheme (the colour settings page, and the command-line tool +with its accent option). Teksilo's `with_accent` must produce, for every key +listed, the same value that KDE writes into `kdeglobals`, compared after +parsing (b.9) and, for copied entries, as text. + +### g.1 Inputs + +- The **scheme file** S (read on its own, with no cascade underneath; its + `[Colors:Header]` presence is S's own). +- The **accent** A. When given explicitly (the command-line accent option, + or Teksilo's `with_accent(a)`), that colour. Otherwise + `[General] AccentColor` read from the output configuration (the + `kdeglobals` cascade). The command-line option accepts SVG colour names + and hex forms [qt]. +- **ACC** (accent present) is false when A is fully transparent black (all four 8-bit + channels 0). Otherwise it is true when A was given explicitly, or when + the output configuration has an `AccentColor` key. (An `AccentColor` + present but unparseable gives an invalid accent with ACC true in + KDE; Teksilo treats an unparseable accent as no accent **[divergence]**.) +- From S's `[General]`: **TB** (accent title bar) = `TitlebarIsAccentColored`, + else `accentActiveTitlebar`, else false; **TINT** (tinting on) = S has a + `TintFactor` key; **factor** = `TintFactor` as a real, default 0.15 (also + used when unparseable). + +### g.2 Bookkeeping keys + +1. `[General] ColorSchemeHash` = lower-case hexadecimal SHA-1 of S's bytes + (written whenever S can be read). Teksilo's `to_kconfig_text` may omit + it; byte comparisons exclude it unless the test supplies the file bytes. +2. The command-line accent option does **not** write `[General] AccentColor` + and does not change `[General] ColorScheme`. (The settings page writes + `AccentColor` itself, outside this procedure.) + +### g.3 The seven colour groups + +The groups are processed in this order: View, Window, Button, Selection, +Tooltip, Complementary, Header. For each group G: + +1. The output group G is deleted, together with its nested groups. +2. If G is Header and S has no `[Colors:Header]` group, nothing is written + for Header (no group, no `[Inactive]`, no header accent); go to the next + group. A scheme without Header colours must stay without them. +3. For each of the twelve keys, in the order BackgroundNormal, + BackgroundAlternate, ForegroundNormal, ForegroundInactive, + ForegroundActive, ForegroundLink, ForegroundVisited, ForegroundNegative, + ForegroundNeutral, ForegroundPositive, DecorationFocus, DecorationHover: + - If ACC and the key is one of **ForegroundActive, + ForegroundLink, DecorationFocus, DecorationHover** (the *accent keys*): + - ForegroundLink gets the **readable accent** (g.4), computed against + S's own `BackgroundNormal` of this group. + - The other three accent keys get A (written `r,g,b`). + - Else if ACC and TINT: the key gets + `OklabTint(S value, A, factor)` (d.15), written `r,g,b`. A key absent + from S is not written by Teksilo **[divergence]** (KDE writes the tint + of an undefined colour). + - Else: the key is **copied as text** from S if S has it; nothing is + written if S lacks it. +4. If G is **Selection** and ACC: let `Bsel = AccentBg(A, S's + [Colors:View] BackgroundNormal)` (d.14). Write BackgroundNormal and + BackgroundAlternate = `Bsel`; ForegroundNormal and ForegroundInactive = + `OnColour(Bsel, active)` (d.13). These overwrite step 3's values. + ForegroundActive, ForegroundLink and the decorations keep step 3's accent + values (so Selection ForegroundActive becomes A, and Selection + ForegroundLink is A made readable against S's Selection background). +5. If G is **Button** and ACC: write BackgroundAlternate = + `AccentBg(A, S's [Colors:Button] BackgroundNormal)`. +6. **Inactive subgroup.** If S's group G has a nested `[Inactive]` group: + for each of the twelve keys, in order: + - if TINT (whether or not ACC): write + `OklabTint(S's [G][Inactive] value, A, factor)` (a key absent from + `[G][Inactive]` is not written, the same **[divergence]** as step 3); + - else if G is Header: **copy as text** the key from S's top-level + `[Colors:Window]` group (not from `[Colors:Header][Inactive]`) into the + output `[Colors:Header][Inactive]`, if Window has it; + - else copy as text from S's `[G][Inactive]` if present. + + Note the accent keys are **not** set to A inside `[Inactive]`. For the + Breeze schemes this means the output `[Colors:Header][Inactive]` equals + S's `[Colors:Window]` text, including Window's own DecorationFocus. +7. **Header accent.** If G is Header and ACC: let `W = S's + [Colors:Window] BackgroundNormal`, `Wa = AccentBg(A, W)` and + `Wt = OklabTint(W, A, factor)` (factor is 0.15 when S has no TintFactor). + If TB: write `[Colors:Header] BackgroundNormal = Wa`, + `[Colors:Header] ForegroundNormal = OnColour(Wa, active)`, + `[Colors:Header][Inactive] BackgroundNormal = Wt`, + `[Colors:Header][Inactive] ForegroundNormal = OnColour(Wt, inactive)`. + Otherwise write nothing in this step. + +### g.4 Readable link accent + +Given the accent A and a background X (S's BackgroundNormal of the current +group; when S lacks it, X is treated as black, luma 0): + +1. If `ratio(luma(X), luma(A)) >= 5.0`, the link is A. +2. Otherwise, if `luma(A) > luma(X)`, the link is `lighten(A)` (luma amount + 0.5, chroma factor 1); else it is `darken(A)` (luma amount 0.5, chroma + factor 1). + +Exactly one step is taken; the result is not re-checked. Example: A = +61,174,233 on 255,255,255 has ratio 2.46, so the link is darken(A) = +45,127,170 (computed from this specification). + +### g.5 `[WM]` + +For each key, in the order activeBackground, activeForeground, +inactiveBackground, inactiveForeground, activeBlend, inactiveBlend, write S's +`[WM]` value if S has it (read as a colour and re-formatted `r,g,b`), +otherwise the default: + +| Key | Default | +|---|---| +| activeBackground | Header BackgroundNormal resolved from S for the Active state (c.2 and e) | +| activeForeground | Header ForegroundNormal, Active | +| inactiveBackground | Header BackgroundNormal, Inactive | +| inactiveForeground | Header ForegroundNormal, Inactive | +| activeBlend | Header BackgroundNormal, Active | +| inactiveBlend | Header BackgroundNormal, Inactive | + +These defaults use S **before** the accent is applied. + +### g.6 `[KDE]` + +- `frameContrast` = S's `[KDE] frameContrast` as a real if present + (written as read, without clamping), otherwise 0.2 (the clamped default of + c.6 applied to S, which has no value). Written as the shortest decimal. +- `contrast` = S's `[KDE] contrast` as an integer, default 7. + +### g.7 Title bar override in `[WM]` + +If ACC and (TINT or TB), with `W` = S's +`[Colors:Window] BackgroundNormal`: + +1. If TINT: `Wt = OklabTint(W, A, factor)`. If not TB: + write `activeBackground = Wt` and `activeForeground = OnColour(Wt, + active)`. In both cases write `inactiveBackground = Wt` and + `inactiveForeground = OnColour(Wt, inactive)`. +2. If TB: `Wa = AccentBg(A, W)`; write `activeBackground = Wa` + and `activeForeground = OnColour(Wa, active)`. + +These overwrite g.5. + +### g.8 Effects + +For `[ColorEffects:Inactive]` then `[ColorEffects:Disabled]`, for each of the +nine keys Enable, ChangeSelectionColor, IntensityEffect, IntensityAmount, +ColorEffect, ColorAmount, Color, ContrastEffect, ContrastAmount: write S's +value as text, **writing an empty value when S lacks the key**. For Breeze, +the output `[ColorEffects:Disabled]` therefore contains `Enable=` and +`ChangeSelectionColor=` with empty values. An empty boolean reads as true +(b.9), so `Enable=` keeps the Disabled effects on, matching the absent-key +default. + +### g.9 Quantisation points (summary) + +- Every written colour is the E8 projection of a stored colour. +- `AccentBg` and `OnColour` work on 8-bit integer channels with the + single/double precision split of d.12. +- `lighten`/`darken` for the link round-trip through HCY with Q16. +- `OklabTint` quantises after each of its conversions (d.15). +- Copied entries are text and are never re-quantised. + +### g.10 Worked examples (Breeze Light, accent 61,174,233) + +Computed from this specification; the accent oracle is authoritative. + +- Selection: BackgroundNormal = BackgroundAlternate = 119,198,239; + ForegroundNormal = ForegroundInactive = 0,0,0; ForegroundActive, + DecorationFocus, DecorationHover = 61,174,233. +- View, Window, Button, Tooltip, Header ForegroundLink = 45,127,170 (their + light backgrounds give a ratio below 5 and the accent is darker, so the + accent is darkened). Selection ForegroundLink is also 45,127,170: it is + checked against S's own Selection background 61,174,233 (ratio 1, equal + lumas, so darkened). Complementary ForegroundLink = 61,174,233 (ratio 5.88 + against 42,46,50). +- Button BackgroundAlternate = AccentBg(A, 252,252,252) = 118,197,238. +- Breeze Dark, same accent: Selection BackgroundNormal 48,128,170 with + ForegroundNormal 0,0,0; View, Window, Button, Tooltip and Header + ForegroundLink = 61,174,233 (ratios 7.84, 6.84, 6.04, 6.04, 6.04); Button + BackgroundAlternate 55,135,177. + +`light_with_accent(61,174,233)` therefore differs from `light()` +(Selection background and text change), which is Plasma's behaviour and is +pinned. + +### g.11 The ten swatches through the selection rule + +AccentBg over the Breeze Light View (255,255,255) and Breeze Dark View +(20,22,24), with OnColour (active), computed from this specification: + +| Swatch | Light selection | Text | Dark selection | Text | +|---|---|---|---|---| +| #e93a9a | 239,117,184 | black | 169,47,115 | white | +| #e93d58 | 239,119,138 | black | 169,49,68 | white | +| #e9643a | 239,146,117 | black | 169,76,47 | white | +| #e8cb2d | 238,218,107 | black | 168,148,38 | black | +| #3dd425 | 119,224,102 | black | 48,155,33 | black | +| #00d3b8 | 76,224,205 | black | 5,154,136 | black | +| #3daee9 | 119,198,239 | black | 48,128,170 | black | +| #b875dc | 205,158,230 | black | 134,88,161 | white | +| #926ee4 | 178,153,236 | black | 108,83,166 | white | +| #686b6f | 149,151,154 | black | 78,81,84 | white | + +--- + +## h. The ten accent swatches + +The colour settings page offers exactly these ten preset swatches, in this +order, with no names, tooltips or translatable labels: + +| Index | Hex | 8-bit | Suggested id **[derived]** | +|---|---|---|---| +| 0 | #e93a9a | 233,58,154 | pink | +| 1 | #e93d58 | 233,61,88 | red | +| 2 | #e9643a | 233,100,58 | orange | +| 3 | #e8cb2d | 232,203,45 | yellow | +| 4 | #3dd425 | 61,212,37 | green | +| 5 | #00d3b8 | 0,211,184 | teal | +| 6 | #3daee9 | 61,174,233 | blue | +| 7 | #b875dc | 184,117,220 | purple | +| 8 | #926ee4 | 146,110,228 | violet | +| 9 | #686b6f | 104,107,111 | gray | + +KDE ships no names; the ids are Teksilo's, chosen by hue, stable, and used +only for persistence (`as_str` / `from_str`). The first swatch is also the +page's fallback custom colour when no custom colour was ever saved. + +--- + +## i. Dark predicate + +**CONFIRMED.** Both xdg-desktop-portal-kde (for +`org.freedesktop.appearance color-scheme`) and the Plasma Qt platform +integration (for the application's colour-scheme hint) use the same rule: + +1. Take the Window palette colour of the **Active** state (f.2, which is + `[Colors:Window] BackgroundNormal` after resolution; effects never apply + to Active) as 8-bit channels (E8). +2. `gray = floor((11 r + 16 g + 5 b) / 32)` (Qt's documented gray value, + integer division) [qt]. +3. Dark when `gray < 192`, light otherwise. + +The portal never answers "no preference": it returns 1 (prefer dark) or 2 +(prefer light). Breeze Light's Window 239,240,241 gives 239 (light); Breeze +Dark's 32,35,38 gives 34 (dark). + +`is_dark()` must implement exactly this. The plan's fallback (luma +comparison) is not needed. + +Other portal facts used by Teksilo's reader: + +- `org.freedesktop.appearance accent-color` is the Active Highlight + (Selection BackgroundNormal) as three doubles in 0..1 (the channel reads, + widened). +- `org.freedesktop.appearance reduced-motion` is 1 when `[KDE] + AnimationDurationFactor` (real, default 1.0, read from `kdeglobals`) + equals 0 exactly, else 0. +- Namespaces served: `org.freedesktop.appearance`, + `org.kde.kdeglobals.` for every top-level `kdeglobals` group + (`General`, `KDE`, `Icons`, `Colors:View`, `Toolbar style`, ...; nested + groups are not exposed), plus `org.kde.VirtualKeyboard` and + `org.kde.TabletMode`. A read is routed to the first namespace whose name + is a prefix of the requested group; anything else fails with "Namespace is + not supported", and an unknown key with "Property doesn't exist". Values + in `org.kde.kdeglobals.*` are the raw strings (expanded when `[$e]`). +- Change signals: the portal emits `SettingChanged` for + `org.kde.kdeglobals.General ColorScheme` on a palette change (any colour + change, not only a scheme switch), `org.kde.kdeglobals.General font` on a + font change, `org.kde.kdeglobals.KDE widgetStyle` on a style change, and + `org.freedesktop.appearance` `color-scheme` and `accent-color` whenever its + own palette changes, and `reduced-motion` when `[KDE] + AnimationDurationFactor` changes. + +--- +## j. Desktop settings + +### j.1 Fonts + +Font keys, read from the `kdeglobals` cascade as **strings** (so `[$e]` +expansion applies, b.11). When the program runs in a sandbox the platform +integration first asks the portal (`org.kde.kdeglobals.`, the key +name) and uses a non-empty answer. + +| Group | Key | Default | +|---|---|---| +| General | font | Noto Sans, 10 pt, normal weight, style name Regular | +| General | fixed | Hack, 10 pt, Regular, monospace hint | +| General | smallestReadableFont | Noto Sans, 8 pt, Regular | +| General | toolBarFont | Noto Sans, 10 pt, Regular | +| General | menuFont | Noto Sans, 10 pt, Regular | +| General | taskbarFont | Noto Sans, 10 pt, Regular | +| WM | activeFont | Noto Sans, 10 pt, Regular (window titles) | + +An absent or empty value gives the default. A value that does not parse +leaves the default in place. + +**Font string layout** [qt]. A comma-separated list (no escaping of commas +inside the family name is done; KDE writes the family as is): + +| Field | Meaning | Qt 5 form | Qt 6 form | +|---|---|---|---| +| 0 | family | yes | yes | +| 1 | point size (real; a value <= 0 means unset) | yes | yes | +| 2 | pixel size (integer; -1 or <= 0 means unset) | yes | yes | +| 3 | style hint (integer) | yes | yes | +| 4 | weight | legacy scale 0..99 | OpenType scale 1..1000 | +| 5 | style: 0 normal, 1 italic, 2 oblique | yes | yes | +| 6 | underline 0 or 1 | yes | yes | +| 7 | strike out 0 or 1 | yes | yes | +| 8 | fixed pitch 0 or 1 | yes | yes | +| 9 | always 0 | yes | yes | +| 10 | capitalisation (integer) | style name, if 11 fields | yes | +| 11 | letter-spacing type (integer) | | yes | +| 12 | letter spacing (real) | | yes | +| 13 | word spacing (real) | | yes | +| 14 | stretch (integer, 0 = any) | | yes | +| 15 | style strategy (integer; 1 is the default) | | yes | +| 16 | style name, if 17 fields | | yes | + +- Accepted field counts: 2 (family and point size), 10 or 11 (Qt 5), 16 or + 17 (Qt 6). Qt 6.11 and later append further fields (font features, + variable axes); KDE cuts the string to its first 17 fields when running on + an older Qt, and Teksilo must do the same (read fields 0 to 16, ignore the + rest). Any other count is invalid and leaves the default. +- The weight field is on the OpenType scale when the string has 16 or more + fields, and on the legacy scale when it has 10 or 11. +- **Legacy weight mapping** [qt]: map a legacy weight w to the OpenType + weight of the **nearest** legacy named weight in this table, ties going to + the lighter one: 0 to 100, 12 to 200, 25 to 300, 50 to 400, 57 to 500, + 63 to 600, 75 to 700, 81 to 800, 87 to 900. (So 50 is 400, 60 is 500, + 70 is 700.) +- A typical Plasma 6 value is + `Noto Sans,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1` (16 fields, OpenType weight + 400, no style name). A Plasma 5 value is + `Noto Sans,10,-1,5,50,0,0,0,0,0` (legacy weight 50). +- Pixel size: Teksilo converts a point size to logical pixels as + `px = pt x 96 / 72` (the logical DPI at scale 1). A positive pixel-size + field is used as is. +- When KDE writes a font whose weight is normal and whose style name is + Regular, Normal, Book or Roman, it drops the style name. + +### j.2 `AnimationDurationFactor` + +`[KDE] AnimationDurationFactor` in `kdeglobals`, a real. Absent means 1.0. +Three upstream consumers, each with its own rule: + +1. **Breeze widget style** (QStyle animations). Only when the key is + **present**: `d = trunc(factor x 100)`, the product formed in single + precision. If `d > 0`, style animations are on with duration `d` ms; + otherwise they are off. When the key is absent, `breezerc [Style] + AnimationsDuration` (100) and `AnimationsEnabled` (true) apply. The + progress-bar busy animation is **not** scaled (its duration is + `ProgressBarBusyStepDuration`, 800 ms, and it is switched by + `ProgressBarAnimated`). +2. **Kirigami and the desktop QtQuick style** (Kirigami units). Let + `f = max(0, factor)` and `base = plasmarc [Units] longDuration` + (default 200). Then `long = max(1, round(base x f))` (round half up), + `short = floor(long / 2)`, `veryShort = floor(long / 4)`, + `veryLong = 2 long`. The tooltip delay (700 ms) and the "human moment" + (2000 ms) are not scaled. +3. **Reduced motion.** The portal reports reduced motion when the factor + equals 0 exactly; the platform integration (on Qt 6.12 or later) when + its absolute value is at most 1e-5 (Qt's documented single-precision + near-zero test [qt], applied by the platform integration [PI]). + +Teksilo's rule for `from_desktop` **[derived]**, following consumer 2, +which drives the controls whose timing the preset reproduces: +`duration_normal = max(1, round(200 f))`, +`duration_fast = floor(duration_normal / 2)`, +`duration_slow = 2 duration_normal`, +`duration_collapse = duration_normal`, +`duration_indeterminate_sweep = 800` (unscaled, consumer 1), tooltip +delays unscaled. A factor of 0 is reported as reduced motion (and gives +normal 1, fast 0, which the preset treats as "no animation"). This differs +from the plan's "scale every non-zero duration" in two places: the busy +sweep is not scaled, and fast is derived from normal by halving. + +### j.3 `forceFontDPI` + +Plasma 6 removed font DPI forcing. The 6.0 configuration update deletes +`forceFontDPIWayland` from `kcmfonts [General]`, the fonts settings page has +no DPI setting any more (scaling is global, set by the display scale), and +no plasma-workspace 6.8 component reads `forceFontDPI` either: the Plasma 5 +X resource loader that applied it no longer exists in the 6.8 tree. +Teksilo must read neither key on either windowing system (see n.8). + +### j.4 `widgetStyle` + +`[KDE] widgetStyle` in `kdeglobals`, a string. Absent or empty means +Breeze. Style names are matched ignoring ASCII case. `native_theme` returns +a Breeze theme only when the value is absent, empty, or equal to `breeze` +ignoring case; any other value (`fusion`, `oxygen`, `kvantum`, ...) keeps +Teksilo's generic geometry with the KDE colours. + +### j.5 Other `kdeglobals [KDE]` and related keys (informative) + +| Key | Type | Default | Use | +|---|---|---|---| +| [KDE] contrast | int | 7 | c.6 | +| [KDE] frameContrast | real | 0.2 | c.6, k | +| [KDE] AnimationDurationFactor | real | 1.0 | j.2 | +| [KDE] widgetStyle | string | breeze | j.4 | +| [KDE] LookAndFeelPackage | string | org.kde.breeze.desktop | b.8 | +| [KDE] SingleClick | bool | false | item activation | +| [KDE] ShowIconsOnPushButtons | bool | true | button icons | +| [KDE] CursorBlinkRate | int ms | 1000 | 0 means no blink, otherwise bounded to 200..2000 | +| [KDE] DoubleClickInterval | int ms | 400 | | +| [KDE] StartDragDist | int px | 10 | | +| [KDE] StartDragTime | int ms | 500 | | +| [KDE] WheelScrollLines | int | 3 | | +| [Icons] Theme | string | breeze | icon theme | +| [Toolbar style] ToolButtonStyle | string | TextBesideIcon | | + +### j.6 `breezerc` + +Read from the `breezerc` cascade (b.6 rules; system layers are +`CDn/breezerc` ... `CD1/breezerc`, the user layer `CH/breezerc`). + +| Group | Key | Type | Default | Notes | +|---|---|---|---|---| +| Common | ShadowStrength | int | 255 | range 25..255; strength = value / 255 | +| Common | ShadowSize | enum | ShadowLarge | ShadowNone, ShadowSmall, ShadowMedium, ShadowLarge, ShadowVeryLarge (stored as their names or indices) | +| Common | ShadowColor | colour | 0,0,0 | | +| Common | OutlineCloseButton | bool | false | decoration only | +| Style | AnimationsEnabled | bool | true | | +| Style | AnimationSteps | int | 10 | animation progress is quantised to multiples of 1 / steps | +| Style | AnimationsDuration | int ms | 100 | overridden by j.2 rule 1 | +| Style | StackedWidgetTransitionsEnabled | bool | false | | +| Style | ProgressBarAnimated | bool | true | busy animation | +| Style | ProgressBarBusyStepDuration | int ms | 800 | | +| Style | ScrollBarAddLineButtons | int | 0 | 0 none, 1 single, any other value double | +| Style | ScrollBarSubLineButtons | int | 0 | same | +| Style | MnemonicsMode | enum | MN_AUTO | MN_NEVER, MN_AUTO, MN_ALWAYS | +| Style | ToolBarDrawItemSeparator | bool | true | | +| Style | ViewDrawFocusIndicator | bool | true | item-view focus rectangle | +| Style | SliderDrawTickMarks | bool | true | | +| Style | ViewDrawTreeBranchLines | bool | true | | +| Style | ViewInvertSortIndicator | bool | true | | +| Style | TabBarDrawCenteredTabs | bool | false | | +| Style | DockWidgetDrawFrame | bool | false | | +| Style | SidePanelDrawFrame | bool | false | | +| Style | MenuItemDrawStrongFocus | bool | true | k.5.13 | +| Style | WindowDragMode | enum | WD_MINIMAL | WD_NONE, WD_MINIMAL, WD_FULL | +| Style | WindowDragWhiteList | string list | empty | | +| Style | WindowDragBlackList | string list | empty | | +| Style | SplitterProxyEnabled | bool | true | | +| Style | SplitterProxyWidth | int px | 12 | | +| Style | WidgetExplorerEnabled | bool | false | | +| Style | DrawWidgetRects | bool | false | | +| Style | MenuOpacity | int percent | 100 | k.5.13 | + +### j.7 Kirigami units (KF 6.31) + +| Unit | Value | +|---|---| +| gridUnit | 18, a **constant** in KF 6.31 (the desktop units do not derive it from the font) | +| smallSpacing | 4 | +| mediumSpacing | 6 | +| largeSpacing | 8 | +| cornerRadius | 5 | +| veryShortDuration / shortDuration / longDuration / veryLongDuration | 50 / 100 / 200 / 400 at factor 1 (j.2) | +| toolTipDelay | 700 | +| humanMoment | 2000 | +| icon sizes small / smallMedium / medium / large | 16 / 22 / 32 / 48 | + +--- + +## k. Breeze geometry and painting + +### k.1 Metric constants + +All in logical pixels unless stated. [M] + +| Constant | Value | +|---|---| +| ArrowSize | 10 | +| SmallArrowSize | 5 | +| Frame width (frame margin) | 2 | +| Frame radius | 5 | +| Layout top-level margin | 10 | +| Layout child margin | 6 | +| Layout default spacing | 6 | +| Line edit frame width | 6 | +| Menu frame width | 0 | +| Menu item margin width | 4 | +| Menu item highlight gap (menu margin) | 4 | +| Menu item extra left margin | 4 | +| Menu item margin height | 4 | +| Menu item item spacing | 2 | +| Menu item accelerator space | 16 | +| Menu item text left margin | 8 | +| Combo box frame width | 6 | +| Combo box item margin width | 3 | +| Spin box frame width | 6 | +| Spin box arrow button width | 20 | +| Group box title margin | 4 | +| Button minimum width | 80 | +| Button margin width | 6 | +| Button item spacing | 4 | +| Tool button margin width | 6 | +| Tool button item spacing | 4 | +| Tool button inline indicator width | 12 | +| Menu button indicator width | 20 | +| Check box size (indicator) | 20 | +| Check box focus margin width | 2 | +| Check box item spacing | 4 | +| Check box radius | 4 (frame radius minus 1) | +| Menu bar item margin width / height | 10 / 6 | +| Scroll bar extent | 21 | +| Scroll bar slider (handle) width | 8 | +| Scroll bar minimum slider length | 20 | +| Scroll bar button heights: none / single / double | 3 / 21 / 42 | +| Tool bar frame width | 0 | +| Tool bar handle extent / handle width | 10 / 6 | +| Tool bar separator width | 8 | +| Tool bar extension width | 20 | +| Tool bar item margin / item spacing | 6 / 0 | +| Tool bar separator vertical margin | 2 | +| Progress bar busy indicator size | 14 | +| Progress bar thickness | 6 | +| Progress bar item spacing | 4 | +| MDI title bar margin width | 4 | +| Slider tick length | 8 | +| Slider tick margin width | 2 | +| Slider groove thickness | 6 | +| Slider control thickness | 20 | +| Tab margin height / width | 4 / 8 | +| Tab minimum width | 80 | +| Tab minimum height / static-tab minimum height (static: k.5.14) | 30 / 34 | +| Tab item spacing | 8 | +| Tab overlap / tab-bar base overlap | 1 / 2 | +| Tab active-effect (indicator) size | 3 | +| Tab widget margin width | 3 | +| Tool box tab min width / item spacing / margin width | 80 / 4 / 8 | +| Tooltip frame width | 3 | +| Header margin width / item spacing / arrow size | 6 / 4 / 10 | +| Item view arrow size | 10 | +| Item view item margin left / right | 2 / 2 | +| Item view item margin top / bottom | 1 / 1 | +| Item view first item top margin | 2 | +| Item view item padding width / height | 2 / 2 | +| Side panel item margin width | 4 | +| Splitter width | 1 | +| Shadow overlap | 2 | +| Blend value (hover and press washes) | 0.3 | +| Focus lighten factor | 110 (percent, d.11) | +| Pen width for symbols, frames and shadows | 1.001 | + +Pen width 1.001 is an antialiasing workaround; Teksilo uses 1. Where a +radius is computed from a pen width, the pen width is truncated to the +integer 1 first. + +Style hints [S]: submenu popup delay 150 ms; menus scrollable; sloppy +submenus on; dialog buttons have icons; form layout labels right-aligned, +fields grow; tab bar alignment leading (centred when +`TabBarDrawCenteredTabs`); focus frame drawn above the widget. + +### k.2 Painting conventions + +- **Stroked rectangle:** a rectangle inset by half the pen width on every + side (0.5 for the 1 px pen), so a 1 px outline lies inside the bounds. +- **Frame radius for a stroked frame:** 5 - 0.5 = **4.5**. +- **Shadowed rectangle:** the bounds inset by 1 on every side (room for a + 1 px shadow). +- **Composite over:** "X at alpha a over Y" means X with its alpha + multiplied by a, source-over composited on opaque Y. +- **With alpha a** means the alpha is **replaced** by a (absolute); **alpha + times a** means multiplied (applied only for 0 <= a < 1). +- **Current group:** painters use the widget's own colour group (Active in + an active window, Inactive in an inactive window, Disabled when disabled) + unless a rule says "Active Highlight", which ignores window activity. + +### k.3 Shared colours + +With `fc` the frame contrast (c.6, default 0.2) and palette roles from f.2 +of the current group: + +| Name | Value | +|---|---| +| frame outline | Mix(Window, WindowText, fc) | +| separator | Mix(Window, WindowText, fc) | +| frame background | Mix(Window, Base, 0.3) | +| focus | View DecorationFocus of the current group | +| hover | View DecorationHover of the current group | +| focus outline | Mix(focus, WindowText, 0.15) | +| hover outline | Mix(hover, WindowText, 0.15) | +| arrow colour for a text role | Mix(that text role, its background role, 0.15); for example Mix(WindowText, Window, 0.15) | +| button outline | Mix(Button, ButtonText, fc) | +| slider outline | Mix(Button, ButtonText, fc) | +| shadow | black with alpha 0.125 | + +Input-frame outline state: focus wins over hover; focused uses focus, +hovered uses hover, otherwise the frame outline. Transitions between states +are animated (k.8). + +### k.4 Computed sizes + +Let `H` be the font line height in pixels (l.2; 18 for Noto Sans 10 pt) and +`I` the small icon size (16). [S] unless tagged. + +| Control | Size rule | H = 18 | +|---|---|---| +| Push button, text | height `H + 2x6 + 2x2 = H + 16`; width `max(textW [+ icon 16 + 4] [+ menu 20 + 4] + 12, 80) + 4` | 34 high, at least 84 wide | +| Push button, icon only | `I + 16` high, no minimum width; 31 wide with a 16 px icon [measured-qt] | 32 | +| Tool button, auto-raise | contents + 12 (contents = icon) | 28 (16 px icon), 34 (22 px) | +| Tool button, framed | contents + 16 | 32 (16 px), 38 (22 px) | +| Line edit | `max(H, 14) + 2` (Qt's inner height [qt]) `+ 2x6` | 32 | +| Combo box | `max(H, 20) + 2x6`; width `textW + 12 + 20 + 2 + 4` | 32 | +| Spin box | inner edit `max(H, 14) + 2`, `+ 2x6`, at least 20; width `+ 20` | 32 | +| Check box / radio row | `max(H + 2x2, 20)`; width `20 + textW + 4` (`+ 4` with an icon) | 22 | +| Check box / radio indicator | 20 x 20, label at x = 24 | | +| Switch (desktop QtQuick style) | 36 x 18 (k.5.9) [Qd] | | +| Slider | thickness 20; tick marks add 3 per side (Qt's built-in 5 minus Breeze's 2) [measured-qt] | 20 (23 one side, 26 both) | +| Scroll bar | extent 21, minimum handle 20 | | +| Progress bar | groove 6; Qt's hint height `H + 8` [qt] [measured-qt] | 26 | +| Menu item | `max(H, 20, iconW) + 1 + 2x4` | 29 | +| Menu separator | 9 x 5 (`1 + 2x4` by `1 + 4`) | 5 high | +| Menu section (separator with text) | `max(H_bold, 20, iconW [, 20 if checkable]) + 4 + 4` | 28 | +| Menu bar item | `H + 2x6`, width `textW + 2x10` | 30 | +| Tab | `max(H + 2x4, 30)` (34 for static tabs); width at least 80 with text | 30 | +| Header section | `max(H, 10 with a sort arrow, 22 with an icon) + 2x6` | 30 | +| Item view row (list, tree) | text only: `H + 1 + 1 + 2x2`; with an icon: `max(H, icon) + 2 + 1 + 1 + 2x2` (Qt adds 2 when the item has a decoration) [measured-qt]; with a check box: `max(H, 20) + 1 + 1 + 2x2` [measured-qt]; first row `+ 1` (top margin 2), minus 1 when the view has a frame | 24 (first 25); 26 with a 16 px icon or a check box | +| Table row | Qt base height `+ 2 x (1 + 1)`, width `+ 2 x (2 + 2)`; the table view adds 1 for its grid line [measured-qt] | 22 (23 per row with the grid) | +| Tooltip | content inset `3 + 1 = 4` on every side (3 from the style, 1 added by Qt) [measured-qt]; height `H + 8` | 26 for one line | +| Splitter | 1 px visual, 12 px hit proxy when enabled (j.6) | | +| Group box title margin | 4 | | +| Dialog margin | 10 (top-level layout margin); child margin 6; spacing 6 | | + +Menu item width: `textW + 2x4` plus the icon column (`iconW + 2`) when any +item has an icon and icons are shown, the check column (`20 + 2`) when any +item is checkable, the right column (`20 + 2`), and the accelerator gap 16 +when the item has a shortcut (the shortcut's own width is added by Qt). + +**Other font sizes** [measured-qt]. The rules above are exact at the +default 10 pt (`H = 18`). At other sizes Qt's text box, which several +controls measure instead of the font height, can exceed `H` by 1 to 3 px +(the combo box at 11 pt), so the `H`-based rules can be short by that +much. `sizes.json` records the +measured heights; Teksilo keeps the deterministic `H` rules and documents +the difference **[divergence]**: + +| pt | H (l.2) | push | line edit | combo | spin | check | menu item | menu section | tab | header | list row | list row, icon | table row | progress | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 8 | 15 | 32 | 29 | 32 | 29 | 20 | 29 | 28 | 30 | 27 | 21 | 24 | 20 | 23 | +| 9 | 17 | 34 | 31 | 32 | 31 | 21 | 29 | 28 | 30 | 29 | 23 | 25 | 22 | 25 | +| 10 | 18 | 34 | 32 | 32 | 32 | 22 | 29 | 28 | 30 | 30 | 24 | 26 | 23 | 26 | +| 11 | 20 | 38 | 34 | 35 | 34 | 25 | 30 | 28 | 30 | 33 | 27 | 29 | 26 | 28 | +| 12 | 22 | 38 | 36 | 36 | 36 | 26 | 31 | 30 | 30 | 34 | 28 | 30 | 27 | 30 | +| 14 | 26 | 42 | 40 | 40 | 40 | 30 | 35 | 34 | 34 | 38 | 32 | 34 | 31 | 34 | + +(Table rows include the 1 px grid line.) + +Teksilo lifts (planned): table row 22 to 24 (D7) and check/radio row 22 to +a 24 node with unchanged paint (D8) [wcag]. + +### k.5 Painting rules per control + +Each rule names the fills and pens; geometry uses k.2. + +**k.5.1 Push button (normal).** Bounds inset by 1 (shadowed rectangle), +then stroked; radius 4.5; 1 px pen. + +In k.5.1 and k.5.2, **Active Highlight** means the Active group's +Highlight for an enabled button and the Disabled group's Highlight for a +disabled one, whatever the window state (an inactive window's muted +Highlight is never used here). + +- Fill: Button. Pen: button outline. +- Hovered, keyboard-focused (focus made visible by keyboard) or pressed, + when enabled: pen becomes Active Highlight. +- Pressed (enabled): fill Mix(Button, Active Highlight, 0.3). +- Checked: fill Mix(Button, ButtonText, 0.125); pen button outline. +- Default button, only in an active window: fill + Mix(Button, Active Highlight, 0.2); pen + Mix(Active Highlight, button outline, 0.5). +- Shadow: only in an active window, only when the button is enabled and + neither flat, pressed nor checked: a 1 px outline of the shadow colour + (black alpha 0.125) around the shadowed rectangle, moved down so that it + shows as a 1 px line below the button (radius 4.5). Equivalent to a + shadow offset (0, 1), blur 0. +- Label: ButtonText (WindowText for flat buttons); icon 16 + 4 spacing; + menu arrow 20 + 4. +- Transitions: the fill animates toward the pressed fill and the pen toward + Highlight over the style animation duration (k.8). + +**k.5.2 Flat button (tool button with auto-raise, flat push button).** + +- Idle (not hovered, pressed, checked or keyboard-focused): nothing is + painted. +- Hovered or keyboard-focused: no fill (transparent), pen Active Highlight. +- Pressed: fill Active Highlight at alpha 0.3. +- Checked: fill ButtonText at alpha 0.125, pen button outline. +- Default (active window): fill Active Highlight at alpha 0.125, pen + Mix(Active Highlight, button outline, 0.5). +- Never a shadow. + +**k.5.3 Line edit.** Fill Base; pen the input-frame outline (k.3); stroked +frame radius 4.5. When the rectangle is shorter than `2x6 + H`, only the +Base fill is painted (no frame). Text inset 6 when there is room. + +**k.5.4 Combo box.** Non-editable: the push-button frame of k.5.1 with +"pressed" also meaning "popup open". Editable: the line-edit frame. Arrow: +a 10 px chevron (k.5.17) centred in a 20 x 20 box at the trailing edge +inside the 2 px frame margin; colour arrow colour of ButtonText +(non-editable) or WindowText (editable), hover colour when its sub-control +is hovered (editable), WindowText when focused and not hovered +(non-editable), Disabled colours when empty or disabled. + +**k.5.5 Spin box.** Line-edit frame. Arrow column 20 wide at the trailing +edge inside the 2 px frame margin; arrow height `min(inner height, 20)` +centred, split into two halves of `floor(h/2)` (10 and 10 at the default +size). Chevrons in arrow colour of Text; hover colour on the hovered half; +Disabled arrow colour at the limit. No button chrome in the arrow column. + +**k.5.6 Check box.** Indicator 20 x 20. + +- Box: the indicator inset by 2 (16 x 16), stroked (15 x 15 at offset 2.5), + radius 4. Pen: separator colour when off; Highlight (current group) when + on or partial. Fill: Button (Button darkened 110 percent, d.11, while + pressed). +- On or partial: the same rounded box filled again with Highlight at alpha + 0.3 over it. [measured-qt] Qt's raster painter composites that wash over + the opaque Button fill in 8-bit sRGB, rounding each channel + (`(t a + b (255 - a) + 127) / 255` with the wash's alpha read as a byte), + so it lands as one opaque fill: Plasma's samples are #c2e4f6 for Breeze + Light and #2f5368 for Breeze Dark (`check_box.on` in the golden pixels). + A compositor that blends in linear light gives another shade of the same + colour (#d9e8f7 for Breeze Light). [measured-qt] The second fill is painted + with the outline pen still set, so the outline is laid twice: the + antialiased edge of a marked box is denser than one stroke gives (the + corner pixels of Plasma's checked box fit two strokes to within 15 levels, + one stroke no closer than 33). +- Check mark (on): a polyline through, in the 16 x 16 box's coordinates + (box at (2,2) inside the indicator, centre (10,10) of the indicator): + (left + 4, centre y), (centre x - 1, bottom - 5), (centre x + 4.5, + top + 5.5), where left = top = 2, bottom = 18 and the centre is (10, 10): + so (6, 10), (9, 13), (14.5, 7.5) in indicator coordinates; pen 2 px, Text + colour (View text), mitre joins, [measured-qt] square caps (Qt's default + pen cap), so the mark runs half the pen width, 1 px, past both end points + (`check_box.on` covers (5, 10), the pixel before the first point, at 0.83). +- Partial: three 2 x 2 squares in Text colour, centred on the box centre and + 4 px to each side. +- Hover (enabled): an extra 1 px ring in the focus colour on the box + outline (same 15 x 15 rounded rectangle, radius 4). +- Toggle animation: the mark is drawn progressively (dash length grows with + the animation progress); partial dots appear at 1/3, 2/3 and 3/3. + +**k.5.7 Radio button.** Indicator 20 x 20. + +- Disc: the indicator inset by 2 and then by 0.5 (15 x 15 circle at 2.5). + Pen: separator colour when off, Highlight when on. Fill Button (darkened + 110 percent while pressed). On: filled again with Highlight at alpha 0.3, + [measured-qt] composited over the face as one opaque fill and with the + outline laid twice, as for the check box (k.5.6; `radio_button.on`). +- Dot (on): a filled circle of radius `floor((18 - 12) / 2) = 3` (6 px + diameter) at the centre, Text colour. +- Hover: a 1 px ring in the focus colour on the 15 x 15 circle. +- Animation: the dot radius grows with the animation progress. + +**k.5.8 Focus frame (keyboard focus on buttons, fields, checks, sliders).** +An annulus filled with Highlight alpha times 0.3: inner edge the widget +rectangle inset by 1 with radius 5, outer edge that inner rectangle grown +by 2 with radius 7 (for check boxes and radio buttons, the inner edge is +the indicator box; for radio buttons and dials both edges are circles). So +the ring runs from 1 px inside to 1 px outside the control, 2 px wide. +This is what the preset's focus-ring tokens transcribe (width 2, offset -1). + +**k.5.9 Switch (desktop QtQuick style, as used by Kirigami apps)** [Qd]. +Uses the **Button** colour set. + +- Indicator: height = Kirigami gridUnit (18), width = 2 x height (36). +- Track: the indicator inset vertically by `floor(height / 6) = 3` top and + bottom (36 x 12), radius `round(12 / 2) = 6`. Fill: ButtonText at 10 + percent over Button background (that is, Button background at alpha 0.9 + composited over ButtonText). Border (1 px): Highlight when checked, + otherwise the handle border colour. +- Checked fill: a rectangle from the track's leading edge to the handle's + trailing edge, same radius, Highlight at alpha 0.5. +- Handle: a circle of the full height (18 x 18), radius `floor(18 / 2) = 9`, + fill Button background, 1 px border: the Button set's DecorationHover when + hovered or keyboard-focused, otherwise Mix(Button background, ButtonText, + fc). +- Handle position: `x = clamp(p x 36 - 9, 0, 18)` where p is the visual + position (0 off, 1 on; mirrored in right-to-left layouts). +- Label: spacing 4 (smallSpacing), padding 1; disabled label opacity 0.6. +- Motion in k.8. + +**k.5.10 Slider.** Thickness 20. Handle box 20 x 20, centred across the +groove, positioned along it by the value over `length - 20`. + +- Groove: 6 thick, centred, full length, drawn on the rectangle inset by + 0.5, radius 3. Background part (whole groove): pen WindowText with alpha + fc, fill WindowText at alpha 0.2 x 0.7 = 0.14 over Window. Fill part + (from the start to the handle centre; mirrored in right-to-left; for + vertical sliders from the top unless inverted): pen Highlight with alpha + fc, fill Highlight at alpha 0.7 over Window. Disabled: only the background + part. +- Handle: the box inset by 1 (18), stroked (17 px circle at 1.5); fill + Button; pen slider outline, hover colour when the handle is hovered, focus + colour when focused; a shadow ellipse of the shadow colour (black 0.125), + 1 px, offset toward the bottom right, drawn unless pressed. +- Ticks (when enabled in breezerc and requested): lines 8 long, 2 px away + from the groove, Highlight for ticks at or below the value, separator + colour above it. + +**k.5.11 Scroll bar.** Extent 21 including a 1 px separator line on the +leading edge (top for horizontal bars, left in left-to-right vertical bars, +right in right-to-left), painted in **Text** (View text) with alpha fc. +**No track or groove is painted.** Buttons none by default (j.6). + +- Handle: 8 wide, centred in the remaining 20 px (so 6 px from the + separator side), stroked (painted 7 wide), radius + `0.5 x min(width, height, 8) = 4`, positioned and sized as Qt's + proportional thumb (length `space x page / (range + page)`, at least 20). +- Handle colour C: WindowText with alpha times 0.5 when idle; hover colour + when the handle is hovered; focus colour when the scroll area has focus + (hover wins). With animations on, C's alpha is multiplied by + `0.7 + 0.3 g`, where g goes from 0 to 1 while the pointer is anywhere over + the scroll bar. +- Painted fill: C at alpha times 0.5 over Window. Pen: C **with alpha fc**. + So the idle, not-hovered thumb is WindowText at effective alpha + `0.5 x 0.7 x 0.5 = 0.175` over Window with a WindowText outline at alpha + fc; with the pointer over the bar, 0.25; a hovered handle is the hover + colour at 0.5 over Window. + +**k.5.12 Progress bar.** Groove 6 thick, centred, radius 3, drawn on the +rectangle inset by 0.5 (inset horizontally by the frame width, normally 0). + +- Groove: pen WindowText with alpha fc; fill WindowText at alpha 0.2 x 0.7 = + 0.14 over Window. +- Contents (determinate): from the start, length `floor(position x + grooveLength)`; pen Highlight with alpha fc; fill Highlight at alpha 0.7 + over Window (HighlightedText instead of Highlight when the bar is in a + selected item). A contents shorter than 6 is drawn 6 long and clipped. + Right-to-left and inverted bars fill from the other end. +- Busy (minimum = maximum = 0): the groove rectangle filled with a repeating + stripe pattern of period 28 px: 14 px of Highlight and 14 px of + Mix(Highlight, Window, 0.7), offset by the animation value, radius 3. The + offset runs linearly from 0 to 28 over 800 ms and loops + (ProgressBarBusyStepDuration). +- Text (when visible, horizontal only): Text colour (HighlightedText when + selected), to the trailing side of the groove with 4 px spacing. + +**k.5.13 Menus (popup panel, items, separators, sections).** + +- Panel: fill frame background, pen frame outline. With compositing + (translucent popup windows) the panel is a rounded rectangle, stroked, + radius 4.5; without, a plain rectangle. Menu frame width 0; items sit 4 px + inside the panel (menu margins). When `MenuOpacity` is below 100 the fill + has alpha `MenuOpacity / 100` and the pen is WindowText with alpha 0.25. +- Window shadow: k.6. +- Item highlight with `MenuItemDrawStrongFocus` true (the default): for the + hovered or keyboard-selected item, the item rectangle (inset 4 from the + panel) is filled with the focus colour at alpha 0.3 and outlined with the + focus outline (1 px, stroked, radius 4.5). While pressed: fill the focus + colour (opaque) and outline the focus colour. Item text: WindowText; + HighlightedText while pressed. +- With `MenuItemDrawStrongFocus` false: no fill; a 1 px line under the text + (2 px below the text's bottom) in the hover colour when selected, the + focus colour when pressed. +- Item layout: contents rectangle = item inset 4 horizontally and 4 + vertically, then 8 more on the leading side. Check column: a 20 x 20 + check box or radio indicator (k.5.6, k.5.7) at the contents' leading edge + minus 8, vertically centred; the text then starts 8 after the indicator. + Icon column: `iconW` wide, then 2 + 1 spacing. Without an icon column the + text moves 4 further (extra left margin). Submenu arrow: 20 x 20 at the + trailing edge, arrow colour of WindowText (HighlightedText when pressed), + text ending 3 before it. Shortcut text: right-aligned, at opacity 0.7. +- Separator: a 1 px line in the separator colour (WindowText with alpha fc + when `MenuOpacity` is below 100), vertically centred in the 5 px item, + inset 4 on each side. +- Section (separator with text): the text in bold, WindowText at alpha 0.7, + leading-aligned with 4 px extra top padding; the line fills the remaining + width starting `4 + 4 - 1 = 7` px after the text. + +**k.5.14 Tabs (regular tab bars).** + +- Selected tab: fill frame background (Window in document mode outside + QtQuick); pen Mix(fill, WindowText, fc); the rectangle stroked, with the + two outer corners rounded (radius 4.5). A **3 px Highlight bar** along the + outer edge (top for tabs on the north, bottom for south, the outer side + for west and east), its outer corners rounded with radius 5. +- Unselected tab: fill `Window darkened 120 percent` (d.11), corners on the + outer edge rounded radius 5 only at the ends of the row; hovered: the hover + colour at alpha 0.2 (animated by mixing). +- Tabs overlap by 1; padding 8 horizontal, 4 vertical; spacing 8 between + icon, text and buttons; minimum width 80, minimum height 30. +- Static tab bars (document mode, not closable, not movable, expanding or + vertical): minimum height 34. Selected: Window fill, then (document mode) + Highlight with alpha fc as the background, or frame background otherwise, + pen Mix(background, WindowText, fc), 3 px Highlight bar. Unselected: Base + darkened 102 percent; hover colour with alpha fc on hover. Between + unselected tabs a 1 px separator in Mix(Base darkened 102, WindowText, fc), + inset 8 at both ends. + +**k.5.15 Header section (table and tree headers).** Fill Button; hovered +Mix(Button, hover, 0.2); pressed or sorted-down Mix(Button, focus, 0.2). +A bottom line (horizontal headers) or trailing line (vertical headers) and +separators between sections, all WindowText with alpha fc, 1 px, not +antialiased. Sort indicator: a 10 px chevron, spacing 4. Margins 6. + +**k.5.16 Item-view selection (lists, trees).** The item rectangle minus its +margins (2 left, 2 right, 1 top, 1 bottom; 2 top on the first row; one less +on each framed side; in row-selection views the inner columns drop their +side margins so the pill spans the whole row). Colour group: Active when the +window is active, Inactive when not, Disabled when disabled. + +- Nothing is painted unless the item is hovered, selected, has a custom + background, or is an alternate row. +- Alternate rows: AlternateBase over the full, un-inset item rectangle, + square. +- Fill: Highlight of that colour group. Hovered and not selected: Highlight + with alpha 0.3. Hovered and selected: Highlight lightened 110 percent + (d.11). +- Outline: the fill colour with alpha 1.0 when selected, 0.8 otherwise; 1 px, + on the stroked rectangle with radius 5 [measured-qt] (the control radius; + this section gave 4.5, the radius less half the pen, as for the push button, + but Plasma's painted rows fit 5 to within 23 levels at every corner pixel + where 4.5 leaves up to 75: `item_view_row.selected` and `.hover` in the + golden pixels), sides chosen by the item's + position in the row (only the row's two ends are rounded and outlined at + the sides). +- Tables: square cells, no rounding. +- Keyboard focus without selection (when `ViewDrawFocusIndicator`): + an outline only, in Highlight lightened 110 percent, radius 4.5, on the + item rectangle minus the margins. + +**k.5.17 Arrows (chevrons).** A polyline in a square of side +`s = min(width, height, 10)`, centred; apex at the centre line; the two +ends at a quarter and three quarters of s; 1 px pen, square caps, mitre +joins; nudged by -0.25, +0.5 or +0.25 px when `s mod 4` is 1, 2 or 3 so the +apex lands on a pixel. [measured-qt] The nudge is along the way the arrow +points: Plasma's spin box puts the ends of its up arrow half a pixel above +the centre of its half and those of its down arrow half a pixel below. + +**k.5.18 Tooltip.** Fill ToolTipBase; pen Mix(ToolTipBase, ToolTipText, fc); +with compositing a rounded rectangle, stroked, radius 4.5. Text ToolTipText. +Padding 4. Window shadow (k.6). Tooltip colours are the Active state's in +every state (f.2). + +**k.5.19 Group box.** Fill frame background, pen frame outline, stroked +radius 4.5; flat group boxes have no frame. Title margin 4. + +**k.5.20 Generic frames and scroll areas.** Fill Base, pen the input-frame +outline, stroked radius 4.5 (frame width 2) when the frame is shown; side +panels use a single line in the Inactive Highlight (Active Highlight when +focused). + +**k.5.21 Splitter.** A 1 px handle; when `SplitterProxyEnabled`, a 12 px +invisible hit proxy. + +### k.6 Shadows + +**Window shadows** (menus, tooltips, combo-box popups). Each size is two +layers. The *size offset* moves both layers; the layer offset is added to +it. Radii are CSS box-shadow **blur radii** (n.4). Colour `ShadowColor` +(default black) with alpha `opacity x ShadowStrength / 255`. + +| ShadowSize | Size offset | Layer 1: offset, blur, opacity | Layer 2: offset, blur, opacity | Effective layers (offset y, blur, opacity) | +|---|---|---|---|---| +| None | | | | no shadow | +| Small | (0, 3) | (0, 0), 12, 0.26 | (0, -2), 6, 0.16 | (3, 12, 0.26) and (1, 6, 0.16) | +| Medium | (0, 4) | (0, 0), 16, 0.24 | (0, -2), 8, 0.14 | (4, 16, 0.24) and (2, 8, 0.14) | +| Large (default) | (0, 5) | (0, 0), 20, 0.22 | (0, -3), 10, 0.12 | (5, 20, 0.22) and (2, 10, 0.12) | +| Very large | (0, 6) | (0, 0), 24, 0.20 | (0, -3), 12, 0.10 | (6, 24, 0.20) and (3, 12, 0.10) | + +The shadow shape is the window's rounded rectangle (radius 5); the window +itself covers the shadow except where the shadow extends beyond it, with a +2 px overlap. An unknown ShadowSize value means Large. + +**Control shadows.** Push button: k.5.1 (offset (0, 1), blur 0, black +0.125). Slider handle: a 1 px ellipse outline in the same colour, rotated +45 degrees so it shows at the bottom right. + +Mapping to Teksilo shadow tokens: `blur` = the radius in the table, offset +as in the last column, colour black with the layer opacity; `spread` 0. +The preset's `shadow_sm`, `shadow_md`, `shadow_lg` are Small, Medium and +Large, first layer in the tokens; the styles paint both layers. +`shadow_xs` is the button shadow. + +### k.7 Layout tokens + +Layout spacing 6 (`control_gap`), child margin 6, top-level (dialog) margin +10, Kirigami largeSpacing 8 (`section_gap`) [K]. Radii: controls and popups +5 (painted 4.5 when stroked), check box 4, progress and slider grooves 3, +scroll handle 4, focus ring outer radius 7. + +### k.8 Control motion (see m for tokens) + +- Style animations (buttons, frames, check boxes, tabs, headers, scroll + bars, combo and spin arrows): duration 100 ms (j.2), linear progress + quantised down to multiples of 1/10 (AnimationSteps), so a transition is + 10 discrete steps. +- Switch handle position: a smoothed animation (Qt's `SmoothedAnimation`, + documented as an ease-in-out quadratic curve [qt]) with duration + Kirigami shortDuration (100 ms) **and Qt's default velocity of 200 px/s**; + Qt uses whichever gives the shorter duration [qt], so the full 18 px + travel takes 90 ms at the default factor. Not animated while the switch + is being pressed or dragged, nor when shortDuration is 0. +- Switch handle colour: a colour animation with an ease-in cubic curve over + shortDuration [Qd]. +- Busy progress: linear, 800 ms per 28 px, looping. + +--- + +## l. Typography + +### l.1 Roles + +| Teksilo style | Font | Size | Weight | Source | +|---|---|---|---|---| +| body | Noto Sans | 10 pt = 13 px face | 400 | j.1 `font` | +| body_bold | Noto Sans | 13 px face | 600 (DemiBold) | Kirigami primary heading weight [K] | +| small, small_bold | Noto Sans | 8 pt = 11 px face | 400 / 600 | j.1 `smallestReadableFont` | +| tiny | as small | | | **[derived]**: KDE has no smaller role | +| mono | Hack | 10 pt = 13 px face | 400 | j.1 `fixed` | + +Letter spacing (tracking) is 0 everywhere. `from_desktop` uses the +desktop's own families and sizes (j.1). + +Kirigami heading factors (documented, not rendered as separate styles): +level 1 x 1.35, level 2 x 1.20, level 3 x 1.15, level 4 x 1.10, level 5 and +higher x 1, applied to the default point size; weight DemiBold for the +primary type, normal otherwise; the secondary type at opacity 0.75 [K]. + +### l.2 Line height + +**Facts.** Noto Sans v2.015 has units per em 1000, hhea ascender 1069, +descender -293, line gap 0 (the OS/2 typographic metrics are identical and +flagged for use; the Windows metrics 1124 and 395 are not used), so its +natural line height is 1.362 em. Hack v3.003: units per em 2048, hhea +ascender 1901, descender -483, line gap 0 (the OS/2 typographic values +1556, -492, 410 are not flagged for use; the Windows metrics equal hhea); +monospaced with an advance of 1233 units (0.60205 em). + +**How Qt measures it** [measured-qt] (`sizes.json`, Noto Sans at 8, 9, 10, +11, 12 and 14 pt, Hack at 9, 10 and 11 pt, all reproduced by the rule): + +1. The face is resolved at the integer pixel size `P = round(pt x 96 / 72)` + (round half up): 10 pt is a 13 px face (resolved point size 9.75), 8 pt + is 11 px. +2. Ascent and descent are `a = floor(64 P asc / upm) / 64` and + `d = floor(64 P desc / upm) / 64`, with `asc` and `desc` the hhea + ascender and the absolute descender (Noto Sans at 13 px: a = 13.890625, + d = 3.796875). +3. The integer line height is `H = round(a) + round(d)`, each rounded half + up separately (not `round(a + d)`, which is 1 px short at 9 and 14 pt). + +Noto Sans: 8 pt 15, 9 pt 17, 10 pt 18, 11 pt 20, 12 pt 22, 14 pt 26. Hack: +9 pt 14, 10 pt 15, 11 pt 18. + +**Text roles (normative).** `H` is computed by steps 1 to 3 with the +bundled face's metrics (Noto Sans for text roles, Hack for mono): 18 for +body and body_bold (10 pt), 15 for small, small_bold and tiny (8 pt), 15 +for mono (Hack 10 pt). The line-height ratio given to Teksilo's text style +is `H / px` with `px` the role's size from l.1, so the line box is the +integer `H`. `from_desktop` applies the same steps to the desktop's fonts +(no font measurement): exact for Noto Sans and Hack, and, for any other +family, `H = round(P x 1.362)` **[derived]**, a documented +/-1 px +approximation. Without either font feature (l.3) the same Noto Sans and +Hack numbers are used for the fallback families. + +The glyph size is the integer face size `P` from step 1 **[measured-qt]**: +13 px for the 10 pt roles and 11 px for the 8 pt roles. Qt resolves a whole +pixel size and draws that face, so Teksilo uses the same size rather than +the unrounded `pt x 96 / 72` (40/3 px), which would make every label 0.33 px +larger than in a Qt application next to it. The line-height ratio given to +Teksilo's text style is therefore `H / P` (18/13 for body, 15/11 for small). +`from_desktop` applies the same rule to the desktop's fonts. + +This corrects the plan's `round(px x 1.35)` (14 instead of 15 at 8 pt) and +the earlier `round(px x 1.362)` (16 instead of 17 at 9 pt and 25 instead +of 26 at 14 pt). + +**Kirigami gridUnit** is the constant 18 (j.7); it coincides with H at the +default font but does not follow the font. + +### l.3 Families without the font features + +With neither `bundled-fonts` nor `system-fonts`, the preset falls back to +Inter and JetBrains Mono with the same sizes **[unpublished]**. + +--- + +## m. Motion + +| Teksilo token | ms at factor 1 | Upstream source | +|---|---|---| +| duration_fast | 100 | breezerc AnimationsDuration; Kirigami shortDuration | +| duration_normal | 200 | Kirigami longDuration | +| duration_slow | 400 | Kirigami veryLongDuration | +| duration_collapse | 200 | Kirigami longDuration | +| duration_indeterminate_sweep | 800 | breezerc ProgressBarBusyStepDuration (not scaled) | +| tooltip_delay, tooltip_delay_heavy | 700 | Kirigami toolTipDelay (not scaled) | +| submenu popup delay (informative) | 150 | Breeze style hint | + +Easing facts: + +- QStyle (Breeze) animations are linear, quantised to 10 steps over + 100 ms (k.8). At 60 Hz that is about 6 frames, so the quantisation is + invisible and `easing_standard` = Linear. +- The switch handle uses Qt's smoothed animation, documented as an ease-in- + out quadratic; the preset approximates it by + `CubicBezier(0.455, 0.03, 0.515, 0.955)` **[derived]** with a test + bounding the maximum deviation from the quadratic ease-in-out at 0.02. +- The switch handle colour uses an ease-in cubic, `t` cubed. The preset + writes it exactly as `CubicBezier(1/3, 0, 2/3, 0)` **[derived]**: with + its control points at one and two thirds along and none of the way up, + a cubic Bezier's horizontal coordinate is its parameter and its vertical + one the parameter cubed. The common approximation + `CubicBezier(0.55, 0.055, 0.675, 0.19)` strays 0.03 from it and is not + used. +- An ease-out cubic appears in the QtQuick **Breeze** style (a different + style package) for its switch border colour; the desktop style that + Kirigami desktop apps use does not use it. Teksilo does not use + ease-out cubic for Breeze controls. +- `from_desktop` scaling: j.2. A factor of 0 is reduced motion. + +--- +## n. C0 settles: answers + +Each item of the plan's C0 "Settles" list, then the plan's fallback table, +then further findings. **CONFIRMED** means settled from the pinned upstream +source (cited in the ledger); **FALLBACK** means not settled, with the +plan's fallback in force and the item tagged `[unverified]` until settled. + +**n.1 `expandSize`. CONFIRMED.** Expanding a size by a margin adds twice the +margin on each axis: `(w + 2 mx, h + 2 my)`; the one-argument form uses the +same margin for both axes. Every size in k.4 uses this. + +**n.2 `scrollBarButtonHeight`. CONFIRMED.** No buttons: 3; single: 21 (the +scroll bar extent); double: 42. breezerc `ScrollBarAddLineButtons` and +`ScrollBarSubLineButtons` default to 0 (no buttons); 1 is single and any +other value double. + +**n.3 qqc2-desktop-style SwitchIndicator. CONFIRMED.** 36 x 18 (height = +Kirigami gridUnit, width twice the height), track inset `floor(h / 6) = 3` +top and bottom, track radius 6, handle 18 x 18 with radius 9, colours and +motion in k.5.9 and k.8. The plan's fallback values (from the QtQuick +Breeze style) match the size but not the colours, borders and checked fill, +which k.5.9 now specifies. + +**n.4 Shadow blur mapping. CONFIRMED.** Breeze's shadow "radius" is a CSS +box-shadow blur radius: the renderer turns it into a Gaussian of standard +deviation `radius / 2` (the CSS Backgrounds definition) and approximates +that Gaussian with three successive box blurs whose total extent is +`max(2, floor(sigma x 3 sqrt(2 pi) / 4 x 1.5 + 0.5))` px (the SVG +filter approximation). The Teksilo token `blur` therefore equals the Breeze +radius, the same convention every sibling preset uses for vendor blur +values, which is also the plan's fallback. Offsets are the combined +offsets of k.6 (size offset plus layer offset). Note: Teksilo's renderer +draws a blur `b` as a smoothstep edge with `sigma = b / 2.5`, which is +somewhat sharper than CSS; that is a renderer-wide property, not part of +this mapping. + +**n.5 Portal dark predicate and namespaces. CONFIRMED.** Section i. + +**n.6 Duplicate-key precedence. CONFIRMED.** Last occurrence wins within a +file and higher layers win, except that an immutable occurrence freezes the +value, so inside an immutable group or file the first occurrence wins +(b.6). + +**n.7 Hack metrics. CONFIRMED.** Units per em 2048, hhea 1901 / -483 / 0 +(natural ratio 1.1640625); Qt's integer line height for Hack 10 pt is 15 +(l.2, [measured-qt]). The synthesised design's "about 1.2" was an +estimate. + +**n.8 `forceFontDPI` on Wayland. CONFIRMED: not read.** Plasma 6.0 deletes +`forceFontDPIWayland`. In addition, **on X11 it is not read either**: the +6.8 fonts settings page has no DPI setting and no 6.8 plasma-workspace +component reads `forceFontDPI` (j.3). The plan's "text scale follows +kcmfonts forceFontDPI on X11" must be dropped (plan correction). + +**n.9 `MenuItemDrawStrongFocus`. CONFIRMED.** breezerc `[Style]`, default +true; behaviour in k.5.13. + +**n.10 Progress hint height. CONFIRMED for Breeze, [measured-qt] for Qt's +part.** Breeze makes a horizontal progress bar at least 6 high, and at +least the font height when its text is visible; it adds nothing else. Qt's +progress-bar size hint supplies `font height + 8` as the contents height +[qt], so the hint is `H + 8 = 26`. The C0 harness confirms 26. + +**n.11 `ThemeExtension` bounds. CONFIRMED (Teksilo code).** There is no +`ThemeExtension` trait. `Theme` carries a `ThemeExtensions` registry keyed +by type; `with_extension` and `extension` require `T: Any + Send + Sync` +(which implies `'static`), and the registry already wraps each value in an +`Arc`. The registry's equality compares only which types are registered, +never their contents, so `assert_same_theme` must compare `KdePalette` by +content through `theme.extension::()`, and `KdePalette` must +implement `PartialEq` itself. An inner `Arc` is optional. + +Plan fallback table: + +| Item | Status | Value in force | +|---|---|---| +| Switch geometry | CONFIRMED | n.3 (qqc2-desktop-style) | +| Dark predicate | CONFIRMED | gray < 192 (i), not the luma fallback | +| Shadow blur mapping | CONFIRMED | blur = radius (n.4), equal to the fallback | +| forceFontDPI on Wayland | CONFIRMED | not read; nor on X11 (n.8) | + +Further items the synthesised design listed for C0: + +- **Busy period. CONFIRMED.** 28 px of stripe offset per 800 ms, linear, + looping; not scaled by the animation factor. +- **Flat-button hover. CONFIRMED.** Outline only, in Active Highlight, no + fill (k.5.2). +- **Groove fill. CONFIRMED.** Slider and progress grooves: WindowText at + 0.14 over Window with a WindowText outline at alpha fc; filled parts + Highlight at 0.7 over Window with a Highlight outline at alpha fc. +- **Wallpaper accent written to `AccentColor`. FALLBACK (not settled, + irrelevant).** The settings-page code that writes `AccentColor` was not + read. Teksilo reads the accent-applied `[Colors:*]` groups, so the + question does not affect any value. +- **Window decoration metrics.** Out of scope (a.1). + +Plan corrections and clarifications found while writing this specification +(each overrides the plan): + +**n.12 Accent precedence, reading side.** `QPalette` Accent and Highlight +are both Selection BackgroundNormal (CONFIRMED); the portal's +`accent-color` is the same colour. `OsThemeColors.accent` = Selection +BackgroundNormal and `focus` = View DecorationFocus stand. + +**n.13 `[General] AccentColor` in the no-colours fallback. DECIDED by the +user (2026-10-17): apply it.** KDE's own behaviour, recorded here as a fact: +when `kdeglobals` has no `[Colors:View]`, the platform integration builds the +palette from the look-and-feel package's colour file, else the named scheme, +else Breeze Light, and does **not** apply `AccentColor` (b.8). Teksilo +deliberately differs: it uses the same trigger (no `[Colors:View]` group) and +the same file order (look-and-feel colours, then `[General] ColorScheme`, +then Breeze Light), and then applies `[General] AccentColor`, when it is +present and not transparent, through the accent application of section g. +Reason: the accent the user chose is honoured even on a profile whose colour +groups were never written. This is a documented deviation from Qt +applications in that edge case only; with `[Colors:View]` present, the +colour groups already carry the accent and nothing is re-applied. A +whole-file lock in a `kdeglobals` layer does not keep the scheme file out, +so the accent is applied under it too, and a locked entry still wins over +what the accented file writes (b.8) **[derived]**. + +**n.14 `KDE_COLOR_SCHEME_PATH`.** It is an in-process application property, +not an environment variable (b.8). `color_scheme_file(name)` must not +honour an environment variable of that name. + +**n.15 `[Inactive]` subgroups do not stop the Inactive effects.** The +subgroup replaces the set's source values (missing keys take the compiled +defaults, or Window's keys for Header), and the Inactive effects then still +apply (c.2 step 5). For Breeze they are disabled, so nothing changes. + +**n.16 Header falls back to Window per key**, not as a whole set (c.2). + +**n.17 Invalid and empty colours.** KDE yields an invalid colour, not the +default; Teksilo treats them as absent (b.9) **[divergence]**, as the plan +decided. + +**n.18 Byte order mark.** KDE does not strip it (b.1); Teksilo reproduces +that and emits a diagnostic. + +**n.19 Line height.** `H` follows Qt's measurement (l.2: integer pixel +size, ascent and descent rounded separately), not `round(px x 1.35)`: the +small role's line box is 15, not 14, and the rule is exact at every +measured size. + +**n.20 Scroll-bar thumb.** The idle thumb fill is WindowText at effective +alpha 0.175 over Window (0.25 while the pointer is over the bar), with a +1 px WindowText outline at alpha fc; the hovered thumb is the hover colour +at 0.5 over Window. Breeze paints **no** track; the only alpha-fc element is +the separator line, in View Text. The synthesised design's +`scrollbar_thumb = window_text at 0.5 x 0.7` and `scrollbar_track_hover = +window_text at alpha fc` do not match Breeze (k.5.11); the preset's tokens +should be re-derived from k.5.11 before the WCAG lift D9. + +**n.21 Progress contents.** The determinate fill is Highlight at 0.7 over +Window with an outline, not solid Highlight (k.5.12). + +**n.22 Animation factor.** Busy sweep not scaled; fast derived from normal +by halving (j.2). + +**n.23 Default button** blend applies only in an active window; its pen is +Mix(Active Highlight, button outline, 0.5) (k.5.1). + +**n.24 Accent oracle procedure.** The command-line accent option applies +the accent to the scheme currently named in `kdeglobals` and does not check +whether that scheme is already in use; only the scheme-name argument has +that check. It also does not write `AccentColor`. The accent run +therefore only needs `[General] ColorScheme` in the scratch `kdeglobals` +to name the scheme under test; `accent_oracle.sh` gets there by applying +the scheme by name first (from a placeholder name, because the scheme-name +path refuses the scheme already in use), which also records the no-accent +`kdeglobals` as each scheme's `base`. + +**n.25 16-bit storage.** All colour arithmetic stores 16-bit channels and +converts to 8 bits with `E8(q) = floor((q + 128) / 257)`, which is +`round(q / 257)` (a.3, [measured-qt] on all 65536 values). The goldens' +"8-bit equal" comparison uses `E8`; their `hex` fields are `E8` of `u16`. + +**n.26 Breeze `contrast`.** All three Breeze schemes set `[KDE] contrast=4`, +so their shade roles use k = 0.4; the default 7 applies only when the key is +absent. + +**n.27 kdedefaults.** It reaches programs through `XDG_CONFIG_DIRS`, which +the Plasma session start prepends; b.7 gives the rule for programs started +outside the session environment. + +**n.28 Confirmed as planned** (no change): push button 34 high and at least +84 wide, radius 4.5 stroked, 1 px pen, shadow only in an active window, +Highlight outline on hover and keyboard focus; tool buttons 28 / 34 auto-raise +and 32 / 38 framed (contents taken as the icon; `sizes.json` confirms all +four); +line edit, combo and spin 32, combo arrow 20, spin column 20 split 10 + 10; +check and radio indicator 20, box 15 at 2.5, radius 4, check pen 2 with +mitre joins, radio dot 6, label at 24; slider 20 / groove 6 radius 3 / +handle 17 painted, ticks 8 with a 2 px gap, mirrored in right-to-left; +scroll bar 21 with a 1 px separator, handle 8 painted 7 radius 4, minimum +20; progress groove 6 radius 3; menu item 29, separator 5, section 28, +margin 4, text margin 8 (+4 without icons), highlight focus at 0.3 with an +outline, radius 4.5, shortcut opacity 0.7; tab 30 (34 static), minimum +width 80, padding 8, 3 px Highlight bar, unselected Window darkened 120, +hover at 0.2 (of the hover colour); header 30; item row 24 (26 with a +16 px icon or a check box, k.4) with margins 2 / 1 and radius 4.5, hover Highlight at 0.3, outline alpha 1.0 selected and 0.8 +otherwise; tooltip padding 4, radius 5 (4.5 stroked); focus ring 2 px of +Highlight at 0.3 from 1 px inside to 1 px outside; `control_gap` 6, +`section_gap` 8, dialog margin 10; durations 100 / 200 / 400 / 200 / 800 / +700; ChangeSelectionColor makes the Inactive Highlight the Window colour +tinted 0.4 toward the selection colour, with the Window foreground as its +text. + +--- + +## o. Ledger + +References only. Paths are relative to the repository root at the pinned +tag (a.2); URLs have the form +`https://invent.kde.org///-/raw//`. Line numbers +are at that tag. "Qt docs" means Qt 6 public documentation of the named +class or function. + +### o.1 Files read beyond the fetch manifest + +Role A fetched these additional files at the same pinned tags (stored +outside every repository): + +| Repository @ tag | Path | sha256 | +|---|---|---| +| plasma/plasma-workspace @ v6.8.0 | kcms/colors/ui/AccentColorUI.qml | c62b614f1fa6da2a385b2c9bc2b0f6998ddf59bed3e7ef1860ddf1bd080367aa | +| frameworks/kconfig @ v6.31.0 | src/gui/kconfiggroupgui.cpp | 484abe215a1ef648b0bd7967b6f4c13b13dbcca9584a9030d8d2953279af92e7 | +| frameworks/kconfig @ v6.31.0 | src/core/kconfig.cpp | 679a6d335ff4bbeee349d7adf32df89031f4be6470ab18e0f4ac61605c00d825 | +| frameworks/kconfig @ v6.31.0 | src/core/kconfigdata.cpp | 42882ba136c16a8aa6fb76cf1f41407bf059ddd1b8ef2dae6b1c5043c876a15b | +| frameworks/kconfig @ v6.31.0 | src/core/kconfigdata_p.h | 87b8ed5500a825080517a84bcc8f31b615296b416a12e1325f6748e71a61585b | +| plasma/breeze @ v6.8.0 | libbreezecommon/breezeboxshadowrenderer.cpp | b52ea0760c82c3d23dccdef1407283b68049fac605ea3264306d978c619c84cd | +| plasma/breeze @ v6.8.0 | libbreezecommon/breezeboxshadowrenderer.h | 31af761f1f7f8411abddc2deaba8f4bfede466c85cb0f7288ea1a3792ce68801 | +| plasma/breeze @ v6.8.0 | kstyle/breeze.h | 8646f4b6a72eca865bc469017f2dbdc54bb348ce185a8ae0613cb7bbcc4cfd0e | +| plasma/breeze @ v6.8.0 | kstyle/animations/breezeanimations.cpp | 934931079849b8be5a4083b699759a8927cfedd3d2fb879aac77ce7e0b617427 | +| plasma/breeze @ v6.8.0 | kstyle/animations/breezeanimationdata.cpp | 97d0b393a5207abc4321ab0f5b000f9a5db9a1423e43406eec3aa179ed09ab29 | +| plasma/breeze @ v6.8.0 | kstyle/animations/breezeanimationdata.h | f7c40f0e967f1c98ed9153e8d19ee61b1e00a6f79ba99b0ee1f1cf78d44074c7 | +| plasma/breeze @ v6.8.0 | kstyle/animations/breezebusyindicatorengine.cpp | 4b25b1d871831befe8ae4ef626b1685ec2ee5325bd28286fb2a14447c89c706a | +| frameworks/qqc2-desktop-style @ v6.31.0 | kirigami-plasmadesktop-integration/plasmadesktopunits.cpp | aeca22cf2e8c4b37bde99fae4de0edbd1331595150b6d5c0bf0c1df5f43a0d1a | +| frameworks/qqc2-desktop-style @ v6.31.0 | kirigami-plasmadesktop-integration/animationspeedprovider.cpp | c3aaf8c9c221785cca717a854481d47046372bd0f9d0f5e0e9fe4531a660662a | +| qt/qtbase @ v6.11.1 | src/gui/painting/qcolor.cpp | 1f9b37229fab0eac532d4c8ef52da4fcddd3e501a8a45a78ef982da0f2fc9216 | +| qt/qtbase @ v6.11.1 | src/gui/painting/qcolorspace.cpp | 2b4a575b0cf6483106a162df7d5677b7b62faba3ad4e502a556b5bc62d141579 | +| qt/qtbase @ v6.11.1 | src/gui/painting/qcolormatrix_p.h | de9757a4012d27503773f1e8e53534e2510b603f95ace3d3bf18f96d25f823a0 | +| qt/qtbase @ v6.11.1 | src/gui/painting/qcolortransferfunction_p.h | 8cbc4d00fd53e3014a7cd4eb2e42c4eec33a0ea5d55339171e16cae8856cc8d8 | +| qt/qtbase @ v6.11.1 | src/gui/painting/qcolortransform.cpp | 75442b871d3e66a46b5da6dd839bb85b17cc9ee7e08627544a85887fa9ef10ae | +| qt/qtbase @ v6.11.1 | src/gui/painting/qcolortrc_p.h | 9ca6f329514171013d136f74e6c70b6ce1c877caa356ee2249d459fa95a6ab5a | +| qt/qtbase @ v6.11.1 | src/gui/painting/qdrawhelper_p.h | 58539076c7ef0e4d4b0df785ab24af13002f6673e126e1faffbf5b8c2857a5b9 | + +The plasma-workspace v6.8.0 tree listing of `kcms/` (GitLab repository API) +was consulted to establish that no X resource loader exists (n.8). + +The qtbase files come from Qt's GitHub mirror, +`https://raw.githubusercontent.com/qt/qtbase/v6.11.1/`. Qt is +LGPL-3.0 or GPL; like the KDE files, these are described in prose here and +never copied. Each behaviour read from them was also confirmed black-box in +the golden image (o.12). + +### o.2 Grammar and cascade (frameworks/kconfig @ v6.31.0) + +| Value or rule | File | Lines | +|---|---|---| +| line trimming, blank and `#` lines | src/core/kconfigini.cpp | 92-98 | +| header parsing, nested segments, invalid header keeps group | src/core/kconfigini.cpp | 100-150 | +| `[$i]` file and group immutability | src/core/kconfigini.cpp | 116-128, 145-149, 278-283 | +| immutable group skips user-layer entries | src/core/kconfigini.cpp | 140-155 | +| key and value trimming, empty key | src/core/kconfigini.cpp | 157-171 | +| key options i, e, d; locale tags; errors | src/core/kconfigini.cpp | 178-229 | +| locale filtering | src/core/kconfigini.cpp | 231-243 | +| 100-error limit, 1.5 MiB line limit | src/core/kconfigini.cpp | 74, 78, 86, 271-274 | +| escape decoding table, `\x` rules, invalid escape | src/core/kconfigini.cpp | 706-801 | +| escape encoding (leading/trailing space as `\s`) | src/core/kconfigini.cpp | 615-704 | +| immutable entry and immutable group block writes; deletion | src/core/kconfigdata.cpp | 71-140 | +| deleted entries read as absent | src/core/kconfigdata.cpp | 221-234 | +| `$` expansion rules, QT_*_HOME fallbacks, `$$` | src/core/kconfig.cpp | 192-250 | +| group exists: non-deleted entry in group or subgroup | src/core/kconfig.cpp | 140-145, 300-303, 373-378, 1094-1101 | +| global files order: kde5rc, system.kdeglobals, kdeglobals, user files | src/core/kconfig.cpp | 110-130, 735-768, 786-850 | +| system files before user files, immutable stops user files | src/core/kconfig.cpp | 711-731 | +| typed reads: absent gives default; bool negatives | src/core/kconfiggroup.cpp | 229-269, 686-701 | +| plain-string reads expand `[$e]` | src/core/kconfiggroup.cpp | 645-662 | +| colour read: empty and `invalid`, `#` forms, names, comma form bounds | src/gui/kconfiggroupgui.cpp | 25-95 | +| colour write: `r,g,b`, `,a` when alpha not 255, `invalid` | src/gui/kconfiggroupgui.cpp | 131-152 | +| font write drops Regular/Normal/Book/Roman style names | src/gui/kconfiggroupgui.cpp | 153-171 | +| list serialisation with `,` | src/core/kconfiggroup.cpp | 133-158 | + +### o.3 Session and locations (plasma/plasma-workspace @ v6.8.0) + +| Value or rule | File | Lines | +|---|---|---| +| KDE_FULL_SESSION, KDE_SESSION_VERSION, XDG_CURRENT_DESKTOP | startkde/startplasma.cpp | 410-415 | +| kdedefaults prepended to XDG_CONFIG_DIRS, `/etc/xdg` default | startkde/startplasma.cpp | 421-428 | +| default ColorScheme BreezeLight; ColorSchemeHash SHA-1 | startkde/startplasma.cpp | 446-466 | +| forceFontDPIWayland deleted | kcms/fonts/kconf_update/plasma6.0-remove-dpi-settings.cpp | 13-29 | +| font defaults (kdeglobals) | kcms/fonts/fontssettings.kcfg | 7-58 | + +### o.4 Colour model (frameworks/kcolorscheme @ v6.31.0) + +| Value or rule | File | Lines | +|---|---|---| +| effect groups, Enable defaults, effect defaults and Color defaults | src/kcolorscheme.cpp | 22-52 | +| background chain (intensity then colour) | src/kcolorscheme.cpp | 54-80 | +| foreground chain (contrast then the background chain) | src/kcolorscheme.cpp | 82-97 | +| effect enumerations | src/kcolorschemehelpers_p.h | 50-78 | +| compiled default tables, decoration defaults 61,174,233 and 147,206,233 | src/kcolorscheme.cpp | 120-217 | +| twelve keys and their names | src/kcolorscheme.cpp | 251-278 | +| set to group mapping, Selection Inactive and Disabled use Window, tint source | src/kcolorscheme.cpp | 289-342 | +| ChangeSelectionColor defaults to Enable, default true | src/kcolorscheme.cpp | 305-307 | +| Header defaults from Window per key | src/kcolorscheme.cpp | 330-334 | +| `[Inactive]` subgroup selection | src/kcolorscheme.cpp | 344-352 | +| selection tint 0.4 on both backgrounds | src/kcolorscheme.cpp | 374-378 | +| effects for every non-Active state; foregrounds against pre-effect background | src/kcolorscheme.cpp | 380-391 | +| computed backgrounds, tint default 0.3 | src/kcolorscheme.cpp | 393-405 | +| contrast value 0.1 x contrast, default 7; 0.7 without config | src/kcolorscheme.cpp | 519-527 | +| frameContrast default 0.2, clamp 0..1 | src/kcolorscheme.cpp | 529-539 | +| shade-role formulas and thresholds 0.006, 0.93 | src/kcolorscheme.cpp | 566-616 | +| shade chroma amount default 0 | src/kcolorscheme.h | 351 | +| palette roles; Tooltip Active in all states; Accent = Selection background | src/kcolorscheme.cpp | 656-695 | +| app-chosen scheme path is an application property | src/kcolorschemehelpers_p.h (and src/kcolorschememanager.cpp 55-66) | 20-36 | + +### o.5 Colour mathematics (frameworks/kguiaddons @ v6.31.0) + +| Value or rule | File | Lines | +|---|---|---| +| clamp with NaN to 1 | src/colors/kguiaddons_colorhelpers_p.h | 13-16 | +| hue wrap | src/colors/kcolorspaces.cpp | 16-20 | +| Rec.709 weights 0.2126, 0.7152, 0.0722 | src/colors/kcolorspaces.cpp | 25-32 | +| gamma 2.2 and 1/2.2 | src/colors/kcolorspaces.cpp | 34-42 | +| forward HCY: luma, hue order, chroma | src/colors/kcolorspaces.cpp | 57-87 | +| inverse HCY: sectors, top/middle/bottom, assignment | src/colors/kcolorspaces.cpp | 89-148 | +| luma | src/colors/kcolorspaces.cpp | 160-163 | +| contrast ratio with +0.05 | src/colors/kcolorutils.cpp | 59-71 | +| lighten, darken, shade formulas | src/colors/kcolorutils.cpp | 73-95 | +| default amounts (lighten 0.5 / 1.0, darken 0.5 / 1.0, tint 0.3, mix 0.5) | src/colors/kcolorutils.h | 121, 140, 156, 172, 189 | +| tint: exponent 0.3, rg with cube, 12 bisection steps, last midpoint | src/colors/kcolorutils.cpp | 97-142 | +| mix: bounds, NaN, premultiplied, transparent result | src/colors/kcolorutils.cpp | 144-166 | +| interpolation form a + (b - a) t | src/colors/kcolorutils.cpp | 19-22 | + +### o.6 Accent application (plasma/plasma-workspace @ v6.8.0) + +| Value or rule | File | Lines | +|---|---|---| +| OKLab matrices (Ottosson), single-precision lerp | kcms/colors/colorsapplicator.cpp | 23-103 | +| sRGB to linear and back via the colour-space transform | kcms/colors/colorsapplicator.cpp | 105-116 | +| accent source, transparent means none, accent-present rule | kcms/colors/colorsapplicator.cpp | 125-137 | +| ColorSchemeHash | kcms/colors/colorsapplicator.cpp | 139-146 | +| TitlebarIsAccentColored / accentActiveTitlebar; TintFactor presence and value | kcms/colors/colorsapplicator.cpp | 150-155 | +| default tint factor 0.15 | kcms/colors/colorsapplicator.h | 86-88 | +| group order, key order, accent keys | kcms/colors/colorsapplicator.cpp | 157-181 | +| delete group, skip Header without Header | kcms/colors/colorsapplicator.cpp | 183-192 | +| link readability: ratio 5.0, one lighten or darken | kcms/colors/colorsapplicator.cpp | 197-218 | +| tint or copy of other keys | kcms/colors/colorsapplicator.cpp | 219-227 | +| Selection backgrounds and foregrounds | kcms/colors/colorsapplicator.cpp | 230-238 | +| Button BackgroundAlternate | kcms/colors/colorsapplicator.cpp | 240-245 | +| `[Inactive]` handling, Header copies Window | kcms/colors/colorsapplicator.cpp | 247-267 | +| Header title-bar accent | kcms/colors/colorsapplicator.cpp | 269-284 | +| `[WM]` keys and defaults from the Header set | kcms/colors/colorsapplicator.cpp | 287-309 | +| `[KDE]` frameContrast and contrast | kcms/colors/colorsapplicator.cpp | 311-318 | +| `[WM]` title-bar override | kcms/colors/colorsapplicator.cpp | 320-338 | +| effects copied, empty when absent | kcms/colors/colorsapplicator.cpp | 340-359 | +| 8-bit blend with truncation, translucent branch | kcms/colors/colorsapplicator.h | 18-43 | +| accent background alpha 0.7 | kcms/colors/colorsapplicator.h | 45-50 | +| black-or-white threshold 4.5, inactive alpha 0.6 | kcms/colors/colorsapplicator.h | 52-72 | +| accent option: current scheme, no AccentColor write, no "already set" check | kcms/colors/plasma-apply-colorscheme.cpp | 131-155 | +| scheme-name path refuses the scheme in use | kcms/colors/plasma-apply-colorscheme.cpp | 64-80 | +| ten swatches and their order | kcms/colors/ui/AccentColorUI.qml | 119-130 | +| fallback custom colour is the first swatch | kcms/colors/ui/AccentColorUI.qml | 148 | + +### o.7 Platform and portal + +| Value or rule | Repository @ tag | File | Lines | +|---|---|---|---| +| dark when gray of Window < 192, values 1 / 2 | plasma/xdg-desktop-portal-kde @ v6.8.0 | src/settings.cpp | 259-274 | +| accent-color is Highlight as doubles | plasma/xdg-desktop-portal-kde @ v6.8.0 | src/settings.cpp | 276-284 | +| reduced-motion is factor == 0 | plasma/xdg-desktop-portal-kde @ v6.8.0 | src/settings.cpp | 286-292 | +| namespaces and errors | plasma/xdg-desktop-portal-kde @ v6.8.0 | src/settings.cpp | 29-49, 87, 151, 228, 378-392, 502-545 | +| change signals | plasma/xdg-desktop-portal-kde @ v6.8.0 | src/settings.cpp | 294-307, 412-468 | +| palette from kdeglobals iff Colors:View; LnF colors, ColorScheme, BreezeLight fallbacks | plasma/plasma-integration @ v6.8.0 | qt6/src/platformtheme/khintssettings.cpp | 427-473 | +| same gray < 192 rule in the platform theme | plasma/plasma-integration @ v6.8.0 | qt6/src/platformtheme/khintssettings.cpp | 475-490 | +| reduced motion fuzzy zero | plasma/plasma-integration @ v6.8.0 | qt6/src/platformtheme/khintssettings.cpp | 492-497 | +| widgetStyle lookup and default breeze | plasma/plasma-integration @ v6.8.0 | qt6/src/platformtheme/khintssettings.cpp | 97-113, 304-322 | +| [KDE] hint keys and defaults | plasma/plasma-integration @ v6.8.0 | qt6/src/platformtheme/khintssettings.cpp | 72-92, 383-403 | +| font keys and defaults; Qt < 6.11 truncation to 17 fields; portal first in sandbox | plasma/plasma-integration @ v6.8.0 | qt6/src/platformtheme/kfontsettingsdata.cpp | 43-97, 144-168 | + +### o.8 Breeze widget style (plasma/breeze @ v6.8.0) + +| Value or rule | File | Lines | +|---|---|---| +| every metric constant of k.1 | kstyle/breezemetrics.h | 14-179 | +| pen width 1.001 | kstyle/breezemetrics.h | 22-28 | +| breezerc keys and defaults | kstyle/breeze.kcfg | whole file | +| size expansion by margins, rectangle inset and centring helpers | kstyle/breezestyle.h | 397-447 | +| scroll-bar button heights | kstyle/breezestyle.h | 366-376 | +| pixel metrics (menu margins 4, frame widths, layout margins, spacing, indicator sizes) | kstyle/breezestyle.cpp | 645-883 | +| style hints (submenu delay 150 and others) | kstyle/breezestyle.cpp | 885-983 | +| check box size | kstyle/breezestyle.cpp | 3304-3334 | +| line edit, combo, spin sizes | kstyle/breezestyle.cpp | 3337-3405 | +| slider tick length and size | kstyle/breezestyle.cpp | 3408-3489 | +| push-button size, minimum width, frame margin | kstyle/breezestyle.cpp | 3491-3565 | +| tool-button size | kstyle/breezestyle.cpp | 3568-3593 | +| menu-bar item size | kstyle/breezestyle.cpp | 3596-3599 | +| menu item, separator and section sizes | kstyle/breezestyle.cpp | 3602-3711 | +| progress-bar size | kstyle/breezestyle.cpp | 3714-3743 | +| tab size, static tab 34 | kstyle/breezestyle.cpp | 3801-3853 | +| header size | kstyle/breezestyle.cpp | 3856-3899 | +| item-view row size | kstyle/breezestyle.cpp | 3902-3916 | +| frames and line edits, neutral and focus outlines | kstyle/breezestyle.cpp | 3919-4085 | +| focus rectangle in views, Highlight lightened 110 | kstyle/breezestyle.cpp | 4087-4167 | +| menu frame for toolbars | kstyle/breezestyle.cpp | 4169-4192 | +| push and tool button frames, states | kstyle/breezestyle.cpp | 4511-4619 | +| menu panel, MenuOpacity, outline alpha 0.25 | kstyle/breezestyle.cpp | 4695-4727 | +| tooltip panel | kstyle/breezestyle.cpp | 4729-4744 | +| item-view selection, alpha 0.3, lighter 110, outline 1.0 / 0.8 | kstyle/breezestyle.cpp | 4746-4897 | +| check box and radio drawing | kstyle/breezestyle.cpp | 4900-4985 | +| menu item drawing, strong focus, separator, section, shortcut 0.7 | kstyle/breezestyle.cpp | 5951-6188 | +| progress bar, busy colours, groove WindowText 0.2 | kstyle/breezestyle.cpp | 6191-6306 | +| scroll-bar handle, alpha 0.7 + 0.3 | kstyle/breezestyle.cpp | 6339-6407 | +| focus frame annulus, radii 5 and 7, Highlight 0.3 | kstyle/breezestyle.cpp | 6614-6781 | +| header section drawing | kstyle/breezestyle.cpp | 6803-6894 | +| tab shapes | kstyle/breezestyle.cpp | 7175-7378 | +| combo box drawing | kstyle/breezestyle.cpp | 7813-7931 | +| spin box drawing and arrows | kstyle/breezestyle.cpp | 7933-7969, 8363-8409 | +| slider drawing | kstyle/breezestyle.cpp | 7971-8137 | +| scroll-bar separator, View Text at alpha fc | kstyle/breezestyle.cpp | 8213-8233 | +| scroll-bar, combo and spin sub-control rectangles | kstyle/breezestyle.cpp | 2970-3212 | +| slider sub-control rectangles | kstyle/breezestyle.cpp | 3253-3301 | +| progress groove and contents rectangles | kstyle/breezestyle.cpp | 2255-2339 | +| AnimationDurationFactor x 100 for QStyle animations | kstyle/breezestyle.cpp | 2070-2087 | +| scroll-bar button mapping 0 / 1 / other | kstyle/breezestyle.cpp | 2148-2213 | +| arrow shade 0.15, highlight alpha 0.3, sunken darkening 110 | kstyle/breezehelper.cpp | 38-42 | +| frame outline, focus and hover outlines, frame background, arrow colours | kstyle/breezehelper.cpp | 142-246 | +| slider outline, scroll handle colour, check-box mark colour, separator | kstyle/breezehelper.cpp | 248-332 | +| alpha multiply rule | kstyle/breezehelper.cpp | 349-356 | +| focus rectangle (stroked, radius 4.5, sides) | kstyle/breezehelper.cpp | 369-421 | +| frame rendering, menu frame (radius 5 with compositing) | kstyle/breezehelper.cpp | 475-611 | +| button frame: fills, pens, default button, shadow | kstyle/breezehelper.cpp | 647-740 | +| separator line | kstyle/breezehelper.cpp | 814-829 | +| check-box background and mark geometry | kstyle/breezehelper.cpp | 831-980 | +| radio background and dot | kstyle/breezehelper.cpp | 983-1078 | +| slider groove and handle | kstyle/breezehelper.cpp | 1081-1200 | +| progress groove and busy stripes | kstyle/breezehelper.cpp | 1204-1271 | +| scroll-bar handle radius, pen alpha fc, fill alpha 0.5 | kstyle/breezehelper.cpp | 1274-1296 | +| static tab and tab shapes, darker 120, hover alpha 0.2, 3 px bar | kstyle/breezehelper.cpp | 1299-1508 | +| arrow geometry | kstyle/breezehelper.cpp | 1511-1578 | +| button and ellipse shadows | kstyle/breezehelper.cpp | 1646-1690 | +| view item position sides | kstyle/breezehelper.cpp | 1692-1737 | +| stroked rectangle 0.5 inset | kstyle/breezehelper.cpp | 1747-1757 | +| item-view margins, first row, framed views, row selection | kstyle/breezehelper.cpp | 1759-1799 | +| frame radius 5 - pen / 2 with integer pen; shadow colour 0.125; frame bias = frameContrast | kstyle/breezehelper.h | 64-108, 340-363 | +| window shadow parameter table, fallback Large, strength / 255 | kstyle/breezeshadowhelper.cpp | 30-70, 175-237 | +| CSS blur semantics, box-blur extent formula | libbreezecommon/breezeboxshadowrenderer.cpp | 27-44, 57-92, 241-284 | +| animation steps quantisation | kstyle/animations/breezeanimationdata.h | 73-80 | +| engines use AnimationsDuration; busy uses ProgressBarBusyStepDuration | kstyle/animations/breezeanimations.cpp | 48-82 | +| busy offset 0 to 28, looping | kstyle/animations/breezebusyindicatorengine.cpp | 85-104 | +| stock schemes | colors/BreezeLight.colors, colors/BreezeDark.colors, colors/BreezeClassic.colors | whole files | + +### o.9 QtQuick style and Kirigami + +| Value or rule | Repository @ tag | File | Lines | +|---|---|---|---| +| switch indicator size, track inset, radii, colours, motion | frameworks/qqc2-desktop-style @ v6.31.0 | org.kde.desktop/private/SwitchIndicator.qml | 12-94 | +| switch padding 1, spacing smallSpacing, label opacity 0.6 | frameworks/qqc2-desktop-style @ v6.31.0 | org.kde.desktop/Switch.qml | 25-26, 64-83 | +| slider handle size from the style | frameworks/qqc2-desktop-style @ v6.31.0 | org.kde.desktop/private/DefaultSliderHandle.qml | 16-48 | +| desktop durations from longDuration x factor, halving | frameworks/qqc2-desktop-style @ v6.31.0 | kirigami-plasmadesktop-integration/plasmadesktopunits.cpp | 18-55 | +| factor = max(0, AnimationDurationFactor), default 1 | frameworks/qqc2-desktop-style @ v6.31.0 | kirigami-plasmadesktop-integration/animationspeedprovider.cpp | 34-48 | +| gridUnit 18, spacings 4 / 6 / 8, durations, tooltip 700, corner 5 | frameworks/kirigami @ v6.31.0 | src/platform/units.cpp | 33-49 | +| icon sizes 16 / 22 / 32 / 48 | frameworks/kirigami @ v6.31.0 | src/platform/units.cpp | 297-345 | +| heading factors 1.35 / 1.20 / 1.15 / 1.10, DemiBold, 0.75 | frameworks/kirigami @ v6.31.0 | src/controls/Heading.qml | 14-37 | + +### o.10 Fonts (font files) + +| Value | Source | Location | +|---|---|---| +| Noto Sans upem 1000, hhea 1069 / -293 / 0, typo-metrics flag, Windows 1124 / 395 | NotoSans-v2.015, `NotoSans[wdth,wght].ttf` (full and unhinted) | head, hhea, OS/2 tables | +| Hack upem 2048, hhea 1901 / -483 / 0, OS/2 typo 1556 / -492 / 410, advance 1233 | Hack v3.003, `Hack-Regular.ttf`, `Hack-Bold.ttf` | head, hhea, OS/2, hmtx tables | + +Font archive hashes: `Hack-v3.003-ttf.zip` +0c2604631b1f055041c68a0e09ae4801acab6c5072ba2db6a822f53c3f8290ac; +`NotoSans-v2.015.zip` +0c34df072a3fa7efbb7cbf34950e1f971a4447cffe365d3a359e2d4089b958f5; +`Hack-Regular.ttf` +15f55cc0c85a2988d2b4b3a8cdb5d77fdfbaf319e1bb5309d725db9818fb7125; +`NotoSans[wdth,wght].ttf` (full) +ae80a5e79e4afbf493e9f4684ec4988e6a861b3598102ee48950b4b7a754cdff. + +### o.11 Fixture hashes (vendored, LGPL-2.0-or-later) + +| File (breeze @ v6.8.0) | sha256 | +|---|---| +| colors/BreezeLight.colors | c46470b75e7df3e1ebff344748b92347f8d5adfa306aef9c91c4d2edae2cd6df | +| colors/BreezeDark.colors | 11fce1d8ab97b529162e50eb200d1a93fab65c7612f9bebd0230a6a348d0cc21 | +| colors/BreezeClassic.colors | 19dcbd1883c03d2d9e0176a39e3ec9df6c18d6e22f0474e6c886aa20aff76508 | + +### o.12 Qt behaviour relied on + +| Behaviour | Reference | Status | +|---|---|---| +| 16-bit channel storage, single-precision float accessors, Q16 on store | Qt 6 docs, the colour class (channel accessors, float API); qtbase src/gui/painting/qcolor.cpp 1315-1338 | [measured-qt]: every `u16` of `math.json` and `resolve.json` reproduced | +| 8-bit accessors round `q / 257` (a.3) | qtbase src/gui/painting/qdrawhelper_p.h 886-887, qcolor.cpp 1526-1530 | [measured-qt]: all 65536 values, and every `hex` field | +| hex, SVG-name and `transparent` colour parsing; 9- and 12-digit forms | Qt 6 docs, the colour class, parsing a colour from a string | [qt]; widths [measured-qt] (`math.json` `qt_color_text`) | +| lighter / darker via HSV value, factor rules, HSV conversions (d.11) | Qt 6 docs, the colour class, lighter and darker; qtbase src/gui/painting/qcolor.cpp 2040-2110, 2181-2225, 2805-2862 | [measured-qt] (`math.json` `qt_lighter_darker`, 1736 records exact) | +| gray value (11 r + 16 g + 5 b) / 32 | Qt docs, qGray | [qt] | +| font string field layout and weight scales | Qt docs, QFont::toString, QFont::fromString, QFont::Weight | [qt] | +| legacy weight nearest-neighbour mapping, ties to the lighter weight | Qt docs, QFont::Weight (Qt 5 and Qt 6 values) | [qt]; all 100 legacy values [measured-qt] (`sizes.json` `legacy_font_weights`) | +| font line height: integer pixel size, ascent and descent rounded separately (l.2) | `sizes.json` font records | [measured-qt] | +| sRGB curve constants, inverse curve, matrix M, float storage (d.15) | Qt 6 docs, the colour-space class (sRGB and linear sRGB) and its transform; qtbase src/gui/painting/qcolortransferfunction_p.h 54-112, qcolorspace.cpp 40-45, 89-106, 353-365, 483-493, qcolormatrix_p.h 32-76, 232-270, 288-312, qcolortransform.cpp 241-272, 1781-1812, qcolortrc_p.h 67-108 | [measured-qt] (`math.json` `qt_colorspace`, 1146 results exact) | +| out-of-range float colours held in half precision, clamped on conversion | Qt 6 docs, the colour class, extended RGB specification; qcolor.cpp 1315-1332 | [measured-qt] (`math.json` `qt_extended`) | +| smoothed animation curve, default velocity 200, shorter of velocity and duration | Qt docs, SmoothedAnimation | [qt] | +| default animation easing linear | Qt docs, QVariantAnimation | [qt] | +| a translucent fill over an opaque one is composited in 8-bit sRGB, each channel rounded; a compositor that blends in linear light gives another shade (k.5.6) | Qt 6 docs, the painter class, composition modes (source over) | [measured-qt] (`pixels.json` `check_box.on`, `radio_button.on`: #c2e4f6 and #2f5368) | +| a fill painted with the pen still set strokes the outline again (k.5.6, k.5.7) | Qt 6 docs, the painter class (drawing a shape fills it with the brush and strokes it with the pen) | [measured-qt] (`pixels.json` `check_box.on`, `radio_button.on` corner pixels) | +| the default pen cap is square (k.5.6 check mark) | Qt 6 docs, the pen class, default cap style | [qt]; [measured-qt] (`check_box.on`, 0.83 before the first point) | +| an item-view selection is stroked and filled at radius 5, not 4.5 (k.5.16) | Qt 6 docs, the painter class (rounded rectangles); Plasma's painted rows | [measured-qt] (`pixels.json` `item_view_row.selected`, `.hover`) | +| tooltip label margin = 1 + frame width | Qt widgets tooltip behaviour | [measured-qt] (`sizes.json` `tooltip`: margin 4, hint height 26 for an 18 px line) | +| line edit inner height max(fm height, 14) + 2 | Qt widgets line edit size hint | [measured-qt] (`sizes.json`: framed 32, frameless 20 at 10 pt) | +| progress bar hint height fm height + 8 | Qt widgets progress bar size hint | [measured-qt] (`sizes.json`: 26 at 10 pt, 23 at 8 pt) | +| XDG_*_DIRS defaults, relative entries ignored | Qt docs, QStandardPaths; XDG Base Directory specification | [qt] | diff --git a/engineering/docs/property-testing.md b/engineering/docs/property-testing.md index f62330bcb..921915e0d 100644 --- a/engineering/docs/property-testing.md +++ b/engineering/docs/property-testing.md @@ -411,6 +411,8 @@ being wrong. |---|---| | `teksilo-core` | [`tests/pointer_invariants.rs`](../../crates/teksilo-core/tests/pointer_invariants.rs), [`tests/prop_kinetic.rs`](../../crates/teksilo-core/tests/prop_kinetic.rs), [`tests/text_run_invariants.rs`](../../crates/teksilo-core/tests/text_run_invariants.rs) | | `teksilo-tokens` | [`tests/prop_color.rs`](../../crates/teksilo-tokens/tests/prop_color.rs) | +| `teksilo-kde-scheme` | [`tests/prop_config.rs`](../../crates/teksilo-kde-scheme/tests/prop_config.rs), [`tests/prop_math.rs`](../../crates/teksilo-kde-scheme/tests/prop_math.rs) | +| `teksilo-theme-kde` | [`tests/contrast.rs`](../../crates/teksilo-theme-kde/tests/contrast.rs) | | `teksilo-data` | [`tests/prop_list_and_selection.rs`](../../crates/teksilo-data/tests/prop_list_and_selection.rs), [`tests/prop_tree_slice.rs`](../../crates/teksilo-data/tests/prop_tree_slice.rs), [`tests/prop_tree_checked.rs`](../../crates/teksilo-data/tests/prop_tree_checked.rs), [`tests/prop_sort_filter.rs`](../../crates/teksilo-data/tests/prop_sort_filter.rs) | | `teksilo-scene` | [`src/index.rs`](../../crates/teksilo-scene/src/index.rs) (`mod proptests`, inline), [`tests/prop_selection_modes.rs`](../../crates/teksilo-scene/tests/prop_selection_modes.rs) | | `teksilo-widgets` | [`src/common/row_offsets.rs`](../../crates/teksilo-widgets/src/common/row_offsets.rs), [`src/common/row_metrics.rs`](../../crates/teksilo-widgets/src/common/row_metrics.rs), [`src/primitives/column_flow.rs`](../../crates/teksilo-widgets/src/primitives/column_flow.rs), [`src/common/column_geometry.rs`](../../crates/teksilo-widgets/src/common/column_geometry.rs), [`src/splitter/distribute.rs`](../../crates/teksilo-widgets/src/splitter/distribute.rs) (all `mod proptests`, inline) | diff --git a/engineering/docs/styling-migration.md b/engineering/docs/styling-migration.md index f2dcb3346..d2ce9bbd6 100644 --- a/engineering/docs/styling-migration.md +++ b/engineering/docs/styling-migration.md @@ -102,10 +102,15 @@ style has to live where its own dependencies already reach. See [charts.md §11](charts.md) for the full reference. -³ Carries a **defaulted label-role hook** — -`StandardItemStyle::selected_label_role` and -`MenuItemStyle::highlighted_label_role`, both `-> Option`, -both `None` by default. +³ Carries **defaulted label hooks**: +`StandardItemStyle::selected_label_role`, +`MenuItemStyle::highlighted_label_role` and +`MenuItemStyle::pressed_label_role`, all `-> Option`, all +`None` by default. `StandardItemStyle` also carries +`selection_emphasis() -> SelectionEmphasis`, defaulted to +`ViewFocusedAndWindowActive`: what mutes a selected row, which a design +language that picks its palette per window (Qt, so KDE's Breeze) sets to +`WindowActive`. A row builds its label *before* any style's `make_body` runs, so a style cannot recolour the text it is about to paint behind. That is @@ -232,6 +237,49 @@ the capsule (those views paint the shared `surface_selected` token behind app-supplied cells this preset cannot retint), and San Francisco is named under the optional `system-fonts` feature rather than bundled. +The **`teksilo-theme-kde`** sibling preset is KDE Plasma's **Breeze** as +Plasma 6.8 ships it, and the first whose colours are not a palette written +in the crate. They are a KDE colour scheme, read and resolved by +`teksilo-kde-scheme` (a no-I/O crate, written from +`engineering/docs/kde-breeze-spec.md` and never from KDE or Qt source, with +a test that fails on upstream identifiers), so the stock Breeze Light and +Dark, any `.colors` file (`from_scheme`), Plasma's accent application +(`light_with_accent`, `PlasmaAccent`) and a whole configured desktop +(`from_desktop`: scheme, fonts, animation speed, `breezerc`) all come out of +one function. `KdePalette` keeps the scheme's colours in every state as a +theme extension; the colour tokens are those colours lifted to WCAG by +stated rules where the scheme falls short, and each lift is tested both to +pass and on its premise that the scheme's own colour fails. Every literal +is tagged at its assignment with where it comes from (`[M]` / `[S]` / +`[Hp]` Breeze's style code, `[qt]` / `[measured-qt]` Qt's behaviour, +`[derived]`, `[wcag]`, `[unpublished]`), and a pixel test compares the +controls with Plasma's own through the production renderer. + +Real `impl FooStyle` blocks cover the buttons, the check box, radio button +and switch, the line edit, combo box, spin box and slider, the menu row, the +popup frame and tooltip, the list and tree rows, tables, tabs, the scroll +bar and the progress bar; the card, panel, dialog, snackbar and the like are +the shipped recipe styles built with Breeze's metrics, and the crate's slot +tests give a reason for every slot it leaves to the widget. Three framework +additions came out of it, all defaulted so no existing style changed: a +`PaintProjection` theme extension (a theme whose palette defines its own +inactive and high-contrast colours paints those, replacing the generic +desaturation chain; see `docs/window-activation.md`), the +`selection_emphasis` and `pressed_label_role` hooks of footnote ³, and the +recipe fields the Breeze metrics needed rendered (row, header and tab +heights, a minimum thumb length, a negative focus-ring offset; see +`density-projection-gaps.md`). + +Known limitations are in the crate's documentation, not deferred: some +widgets keep the framework's chrome, Qt's linear-light-versus-sRGB blending +shows in translucent paints over a backdrop the style does not paint, and +the Noto Sans and Hack faces are named or embedded only under the crate's +own font features, which the umbrella does not forward. The widget shapes +are chosen when a tree is built, so switching to or from this preset at +runtime re-tints and keeps the built shapes; `ThemeMode::Native` with +`native_theme_kde()` builds a window in Breeze's shapes on a Breeze desktop +(`docs/native-theme.md`). + Still ahead on the styling roadmap: image-backed styles, the `ImageTheme` TOML manifest loader, and a GTK4-Adwaita sibling preset crate. diff --git a/engineering/docs/touch-and-pen.md b/engineering/docs/touch-and-pen.md index 5989e5410..b0f52cf0a 100644 --- a/engineering/docs/touch-and-pen.md +++ b/engineering/docs/touch-and-pen.md @@ -1814,8 +1814,8 @@ patch. They are the only `#[ignore]`s the programme added. 8-dp-per-character fallback and `MockTextBackend` — both report one fixed line height whatever the theme's typography asks for. Every text-derived dimension in the census is therefore that constant rather than the theme's line box, which is - how a menu row comes out under the floor in three presets and at its declared - height in the fourth. The allow-list entry on the menu row says so at the point + how a menu row comes out under the floor in some presets and at its declared + height in the others. The allow-list entry on the menu row says so at the point it matters; the general consequence is that a fixture measuring a control sized by its label is measuring the harness's metrics as much as the widget's. Unowned, and the remedy (a backend that reads the theme) would re-measure every @@ -1829,7 +1829,7 @@ patch. They are the only `#[ignore]`s the programme added. target rather than judging it. Same owner as A10's outset-versus-target precedence, and the same finding as the docking gutter already on the list. - **A `SearchField` with an empty query reports a 16 × 16 clear slot reaching - 22 × 24 at Compact, under all four presets.** Measured, not described: the + 22 × 24 at Compact, under every preset.** Measured, not described: the `search_field/empty` fixture produces exactly that row, and the figure above is read off the gate rather than written beside it. The same slot with a query typed reaches 24 × 24 and does not appear at all — `HitTarget::active` diff --git a/examples/widget_catalog/Cargo.toml b/examples/widget_catalog/Cargo.toml index 4a74f5081..398bef493 100644 --- a/examples/widget_catalog/Cargo.toml +++ b/examples/widget_catalog/Cargo.toml @@ -16,6 +16,7 @@ teksilo = { workspace = true, features = [ "theme-material3", "theme-fluent", "theme-macos", + "theme-kde", ] } teksilo-charts = { workspace = true } teksilo-scene = { workspace = true } diff --git a/examples/widget_catalog/src/cli.rs b/examples/widget_catalog/src/cli.rs index 63e8e2270..a7d721a5d 100644 --- a/examples/widget_catalog/src/cli.rs +++ b/examples/widget_catalog/src/cli.rs @@ -24,7 +24,11 @@ pub struct CliOptions { /// Force the startup theme, overriding the persisted selection. One of /// `intui-light` / `intui-dark` / `material3-light` / `material3-dark` /// (aliases `m3-light` / `m3-dark`) / `fluent-light` / `fluent-dark` / - /// `macos-light` / `macos-dark`. `None` → restore the saved theme. + /// `macos-light` / `macos-dark` / `kde-light` / `kde-dark`, or `system`, + /// which starts the app in Native mode and the title-bar switcher on its + /// System entry (follow the desktop; the window is built in Breeze's + /// chrome on a KDE desktop with the Breeze style). + /// `None` → restore the saved theme. pub theme: Option, /// The target density the whole catalog is built at: `compact` (the /// default), `comfortable` or `touch`. @@ -125,7 +129,7 @@ pub fn parse(tab_names: &[&str]) -> CliOptions { eprintln!( "--theme expects one of: intui-light, intui-dark, \ material3-light, material3-dark, fluent-light, fluent-dark, \ - macos-light, macos-dark" + macos-light, macos-dark, kde-light, kde-dark, system" ); continue; }; @@ -171,7 +175,8 @@ fn print_help(tab_names: &[&str]) { --theme Force the startup theme, overriding the saved one.\n \ intui-light | intui-dark | material3-light | material3-dark\n \ | fluent-light | fluent-dark | macos-light | macos-dark\n \ - (aliases m3-light / m3-dark).\n \ + | kde-light | kde-dark, or `system` to follow the desktop.\n \ + (m3-light / m3-dark are aliases for material3-*.)\n \ --help, -h Show this help and exit.\n\ \n\ TABS:\n \ diff --git a/examples/widget_catalog/src/main.rs b/examples/widget_catalog/src/main.rs index 8bc9d7a90..8f7ee4035 100644 --- a/examples/widget_catalog/src/main.rs +++ b/examples/widget_catalog/src/main.rs @@ -99,17 +99,23 @@ fn main() { .with_default_mode(TelemetryMode::Anonymous) .with_data_processor_name("FernTech"), ) + // The factory Native mode asks for a theme: on a Breeze KDE desktop it + // answers Breeze's geometry over the live colours, anywhere else the + // seam keeps IntUI in the desktop's colours. It only matters to a + // window built in Native mode, which `--theme system` starts in (see + // `startup_theme_mode`); picking System in the switcher afterwards + // re-tints the tree and keeps the shapes it was built with. + .native_theme_kde() .theme( - options - .theme - .as_deref() - .and_then(theme_from_name) + startup_theme(options.theme.as_deref()) .unwrap_or_else(teksilo::presets::intui::light) // `--density`. Projected here, once, so every tab is built at // the rung the flag names — see `CliOptions::density` for why // this is not an in-app switch. .with_density(options.density), ) + // After `.theme(..)`, which selects the Manual mode. + .theme_mode(startup_theme_mode(options.theme.as_deref())) .i18n(i18n) .install_native_menu() .initial_window( @@ -164,9 +170,10 @@ fn main() { tree.add(Expand::vertical().respect_intrinsic().child(catalog)); // Invisible: persists + restores the chosen theme. - // `--theme` forces the startup theme and skips restore. + // `--theme` forces the startup theme and skips restore; + // `--theme system` makes the restore follow the OS. let theme_persist = tree.add(ThemePersistenceSlot { - skip_restore: opts.theme.is_some(), + restore: ThemeRestore::for_flag(opts.theme.as_deref()), }); let inner = tree.add( VStack::new() @@ -194,8 +201,9 @@ fn main() { /// Composite-tooltip body for the title-bar `ThemeSwitcher`. Spells out the /// one non-obvious thing about runtime theme switching: colours retint live, /// but widget *chrome* (shapes) is resolved at build time — so a live switch -/// between the IntUI, Material 3 and Fluent families keeps the current shapes. -/// Points the user at the `--theme` startup flag to see a preset's true chrome. +/// between the IntUI, Material 3, Fluent, macOS and KDE families keeps the +/// current shapes. Points the user at the `--theme` startup flag to see a +/// preset's true chrome. /// (Demonstrates the new `ThemeSwitcher::composite_tooltip` setter.) fn theme_switch_caveat() -> impl Widget + 'static { // The tooltip body MUST use the tooltip text roles — `TooltipText` (full @@ -225,8 +233,8 @@ fn theme_switch_caveat() -> impl Widget + 'static { .child( TextWidget::new(lit!( "Widget shapes (Material 3's pill buttons and switch, Fluent's \ - elevation edge and list selection pill, card radii) are chosen \ - when the UI is built. Switching theme family here changes only \ + elevation edge and list selection pill, KDE's Breeze frames and \ + indicators, card radii) are chosen when the UI is built. Switching theme family here changes only \ the colours — the shapes stay until the UI is rebuilt." )) .style(TextStyleRole::Small) @@ -236,8 +244,11 @@ fn theme_switch_caveat() -> impl Widget + 'static { TextWidget::new(lit!( "To see a preset's true chrome, start the catalog with \ --theme macos-light (or macos-dark / fluent-light / \ - fluent-dark / material3-light / material3-dark / \ - intui-light / intui-dark)." + fluent-dark / kde-light / kde-dark / material3-light / \ + material3-dark / intui-light / intui-dark). \ + --theme system starts in Native mode, which on a KDE \ + desktop with the Breeze style builds Breeze's chrome too; \ + picking System in this switcher later only re-tints." )) .style(TextStyleRole::Small) .color(TextRole::TooltipText), @@ -245,6 +256,27 @@ fn theme_switch_caveat() -> impl Widget + 'static { ) } +/// The themes the title-bar `ThemeSwitcher` offers, in order: IntUI, Material 3, +/// Fluent, macOS and KDE, each light then dark. The labels are the design +/// languages' own names, shown as they are in every locale, like the rows +/// before them. +fn switcher_themes() -> Vec<(LocalizedString, Theme)> { + use teksilo::prelude::{fluent, kde, macos, material3}; + use teksilo::presets::intui; + vec![ + (lit!("IntUI Light"), intui::light()), + (lit!("IntUI Dark"), intui::dark()), + (lit!("Material 3 Light"), material3::light()), + (lit!("Material 3 Dark"), material3::dark()), + (lit!("Fluent Light"), fluent::light()), + (lit!("Fluent Dark"), fluent::dark()), + (lit!("macOS Light"), macos::light()), + (lit!("macOS Dark"), macos::dark()), + (lit!("KDE Light"), kde::light()), + (lit!("KDE Dark"), kde::dark()), + ] +} + /// Build the custom title bar. Uses role-driven background/border so /// the chrome retints live across `ctx.set_theme(...)` switches. fn build_title_bar( @@ -296,29 +328,18 @@ fn build_title_bar( // is reachable), so the chosen size persists and restores on restart. let scale_ctrl = TextScaleSlot::default(); - // Theme switcher — IntUI Light/Dark plus the Material 3, Fluent and - // macOS presets, with the OS-follow "System" entry kept on. Selecting an - // entry re-tints the catalog live (colours, focus/scroll preserved). It - // does NOT change widget *chrome* at runtime: shapes (M3 pills and switch, - // Fluent's elevation edge and selection pill, macOS's bezels and selection - // capsule, card radii) are chosen when the UI is built, so a live family - // switch keeps the current shapes — start with `--theme macos-light` to - // see a preset's true chrome. The composite tooltip below spells this out - // for the user. + // Theme switcher: IntUI, Material 3, Fluent, macOS and KDE presets, each + // light then dark (`switcher_themes`), with the OS-follow "System" entry + // kept on. Selecting an entry re-tints the catalog live (colours, + // focus/scroll preserved). It does NOT change widget *chrome* at runtime: + // shapes (M3 pills and switch, Fluent's elevation edge and selection pill, + // macOS's bezels and selection capsule, KDE's Breeze frames and + // indicators, card radii) are chosen when the UI is built, so a live + // family switch keeps the current shapes. Start with `--theme kde-light` + // (or `macos-light`, ...) to see a preset's true chrome. The composite + // tooltip below spells this out for the user. let theme_switcher = teksilo::widgets::ThemeSwitcher::new() - .themes([ - (lit!("IntUI Light"), teksilo::presets::intui::light()), - (lit!("IntUI Dark"), teksilo::presets::intui::dark()), - ( - lit!("Material 3 Light"), - teksilo::prelude::material3::light(), - ), - (lit!("Material 3 Dark"), teksilo::prelude::material3::dark()), - (lit!("Fluent Light"), teksilo::prelude::fluent::light()), - (lit!("Fluent Dark"), teksilo::prelude::fluent::dark()), - (lit!("macOS Light"), teksilo::prelude::macos::light()), - (lit!("macOS Dark"), teksilo::prelude::macos::dark()), - ]) + .themes(switcher_themes()) .composite_tooltip(theme_switch_caveat()); let trailing = HStack::new() @@ -391,16 +412,96 @@ impl Widget for TextScaleSlot { /// binding lives in `build()` where `ctx.settings()` is reachable. #[derive(Debug, Default)] struct ThemePersistenceSlot { - /// When a `--theme` flag forced the startup theme, skip the restore so - /// the forced theme (set on the builder) wins; changes still persist. - skip_restore: bool, + /// What the restore does at startup; see [`ThemeRestore`]. + restore: ThemeRestore, +} + +/// What [`ThemePersistenceSlot`] does with the saved theme at startup. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +enum ThemeRestore { + /// Put back the theme saved by the last run (no `--theme` flag). + #[default] + Saved, + /// A `--theme NAME` flag forced the startup theme, set on the builder: + /// leave it. Changes still persist. + Keep, + /// `--theme system`: follow the OS whatever was saved, as the + /// switcher's System entry does. + FollowSystem, +} + +/// The one thing a restore does after the window is mounted. +#[derive(Debug, Clone, PartialEq)] +enum RestoreAction { + /// `EventContext::follow_system_theme`. + FollowSystem, + /// `EventContext::set_theme`. + Set(Box), +} + +impl ThemeRestore { + /// The restore a `--theme` value asks for (`None`: no flag). The value is + /// already lower case, as `cli::parse` leaves it. + fn for_flag(flag: Option<&str>) -> Self { + match flag { + None => ThemeRestore::Saved, + Some("system") => ThemeRestore::FollowSystem, + Some(_) => ThemeRestore::Keep, + } + } + + /// What to do given the saved `ui.theme` value (empty: nothing saved). + fn action(self, saved: &str) -> Option { + match self { + ThemeRestore::Keep => None, + ThemeRestore::FollowSystem => Some(RestoreAction::FollowSystem), + ThemeRestore::Saved if saved.is_empty() => None, + ThemeRestore::Saved if saved == "system" => Some(RestoreAction::FollowSystem), + // An id this build no longer knows keeps the builder default. + ThemeRestore::Saved => { + theme_from_id(saved).map(|theme| RestoreAction::Set(Box::new(theme))) + } + } + } } const THEME_PREF_KEY: &str = "ui.theme"; -/// Resolve a `--theme NAME` value to a concrete `Theme`. +/// The mode the app starts in for a `--theme` value: `system` starts in +/// [`ThemeMode::Native`], so the first window is built from what the desktop +/// reports (Breeze's chrome on a Breeze KDE desktop, through the factory +/// `native_theme_kde` installs); every other value, and no flag, keeps the +/// builder's fixed theme. +/// +/// The builder's `.theme(..)` selects Manual, so this is applied after it. +fn startup_theme_mode(flag: Option<&str>) -> ThemeMode { + match flag { + Some("system") => ThemeMode::Native, + _ => ThemeMode::Manual, + } +} + +/// The theme `--theme` forces at startup, or `None` to keep the default. +/// +/// `system` is the follow-OS entry, not a preset: it is answered here, before +/// [`theme_from_name`], so it never reaches the unknown-theme warning, and the +/// persistence slot makes it follow the OS ([`ThemeRestore::FollowSystem`]). +fn startup_theme(flag: Option<&str>) -> Option { + match flag? { + "system" => None, + name => { + let theme = theme_from_name(name); + if theme.is_none() { + eprintln!("--theme: unknown theme `{name}`, using the default"); + } + theme + } + } +} + +/// Resolve a `--theme NAME` preset name to a concrete `Theme`. fn theme_from_name(name: &str) -> Option { - use teksilo::prelude::{fluent, macos, material3}; + use teksilo::prelude::{fluent, kde, macos, material3}; use teksilo::presets::intui; match name { "intui-light" => Some(intui::light()), @@ -411,10 +512,9 @@ fn theme_from_name(name: &str) -> Option { "fluent-dark" => Some(fluent::dark()), "macos-light" => Some(macos::light()), "macos-dark" => Some(macos::dark()), - other => { - eprintln!("--theme: unknown theme `{other}` — using the default"); - None - } + "kde-light" => Some(kde::light()), + "kde-dark" => Some(kde::dark()), + _ => None, } } @@ -426,7 +526,7 @@ fn theme_from_name(name: &str) -> Option { /// [`theme_from_name`]. Every preset offered by the switcher must appear /// here or picking it persists fine and silently reverts on next launch. fn theme_from_id(id: &str) -> Option { - use teksilo::prelude::{fluent, macos, material3}; + use teksilo::prelude::{fluent, kde, macos, material3}; use teksilo::presets::intui; match id { "intui.light" => Some(intui::light()), @@ -437,6 +537,8 @@ fn theme_from_id(id: &str) -> Option { "fluent.dark" => Some(fluent::dark()), "macos.light" => Some(macos::light()), "macos.dark" => Some(macos::dark()), + "kde.light" => Some(kde::light()), + "kde.dark" => Some(kde::dark()), // Unknown / custom id: keep the builder default. _ => None, } @@ -461,15 +563,12 @@ impl Widget for ThemePersistenceSlot { // Restore the saved theme once, after mount (an `EventContext` — needed // for set_theme / follow_system_theme — is only reachable post-mount). - // Skipped when `--theme` forced the startup theme. - let saved = pref.get(); - if !self.skip_restore && !saved.is_empty() { - ctx.run_after_mount(move |ectx| { - if saved == "system" { - ectx.follow_system_theme(); - } else if let Some(theme) = theme_from_id(&saved) { - ectx.set_theme(theme); - } + // Skipped when `--theme NAME` forced the startup theme; forced to + // follow the OS by `--theme system`. + if let Some(action) = self.restore.action(&pref.get()) { + ctx.run_after_mount(move |ectx| match action { + RestoreAction::FollowSystem => ectx.follow_system_theme(), + RestoreAction::Set(theme) => ectx.set_theme(*theme), }); } @@ -809,8 +908,9 @@ mod tests { /// Every preset the title-bar switcher offers, as `(--theme name, /// persisted ThemeId)`. The switcher itself is built inside a - /// `BuildContext`, so this list is the testable stand-in — keep it in - /// step with `build_title_bar`'s `.themes([...])`. + /// `BuildContext`, so this list is the testable stand-in: it must hold + /// exactly what `switcher_themes()` offers, which + /// `switcher_offers_every_preset_light_and_dark` checks. const OFFERED: &[(&str, &str)] = &[ ("intui-light", "intui.light"), ("intui-dark", "intui.dark"), @@ -820,8 +920,114 @@ mod tests { ("fluent-dark", "fluent.dark"), ("macos-light", "macos.light"), ("macos-dark", "macos.dark"), + ("kde-light", "kde.light"), + ("kde-dark", "kde.dark"), ]; + /// The switcher offers every preset, light then dark, in the order the + /// user reads them, and each row is one the catalog can restore. + #[test] + fn switcher_offers_every_preset_light_and_dark() { + use teksilo::core::styles::ThemeAppearance; + let rows = switcher_themes(); + let ids: Vec<&str> = rows.iter().map(|(_, theme)| theme.id.as_str()).collect(); + assert_eq!( + ids, + [ + "intui.light", + "intui.dark", + "material3.light", + "material3.dark", + "fluent.light", + "fluent.dark", + "macos.light", + "macos.dark", + "kde.light", + "kde.dark", + ] + ); + let mut unique = ids.clone(); + unique.sort_unstable(); + unique.dedup(); + assert_eq!(unique.len(), ids.len(), "a theme is offered twice: {ids:?}"); + for (label, theme) in &rows { + let id = theme.id.as_str(); + assert!( + OFFERED.iter().any(|(_, offered)| *offered == id), + "`{id}` is offered by the switcher but not in OFFERED" + ); + let restored = theme_from_id(id) + .unwrap_or_else(|| panic!("`{id}` is offered but has no restore arm")); + assert_eq!(restored.id.as_str(), id); + let label = label.resolve_now(); + let expected = if label.ends_with(" Dark") { + ThemeAppearance::Dark + } else { + assert!(label.ends_with(" Light"), "`{label}` names neither"); + ThemeAppearance::Light + }; + assert_eq!(theme.appearance, expected, "`{label}` ({id})"); + } + assert_eq!(OFFERED.len(), rows.len()); + } + + #[test] + fn kde_names_and_ids_resolve() { + for (name, id) in [("kde-light", "kde.light"), ("kde-dark", "kde.dark")] { + assert_eq!(theme_from_name(name).expect(name).id.as_str(), id); + assert_eq!(theme_from_id(id).expect(id).id.as_str(), id); + } + } + + /// `--theme system` is not a preset: it asks for the follow-OS entry, so + /// it forces no startup theme, is not looked up as a name, and makes the + /// restore follow the OS whatever the last run saved. + #[test] + fn theme_system_follows_the_os_instead_of_naming_a_preset() { + assert!(theme_from_name("system").is_none()); + assert!(startup_theme(Some("system")).is_none()); + assert_eq!(startup_theme_mode(Some("system")), ThemeMode::Native); + assert_eq!( + ThemeRestore::for_flag(Some("system")), + ThemeRestore::FollowSystem + ); + for saved in ["", "kde.dark", "intui.light", "system", "custom"] { + assert_eq!( + ThemeRestore::FollowSystem.action(saved), + Some(RestoreAction::FollowSystem), + "saved `{saved}`" + ); + } + } + + #[test] + fn a_named_flag_keeps_its_theme_and_no_flag_restores_the_saved_one() { + assert_eq!( + startup_theme(Some("kde-dark")).map(|t| t.id.as_str().to_string()), + Some("kde.dark".to_string()) + ); + assert!(startup_theme(Some("nope")).is_none()); + assert!(startup_theme(None).is_none()); + + assert_eq!(startup_theme_mode(Some("kde-dark")), ThemeMode::Manual); + assert_eq!(startup_theme_mode(Some("nope")), ThemeMode::Manual); + assert_eq!(startup_theme_mode(None), ThemeMode::Manual); + + assert_eq!(ThemeRestore::for_flag(Some("kde-dark")), ThemeRestore::Keep); + assert_eq!(ThemeRestore::for_flag(None), ThemeRestore::Saved); + assert_eq!(ThemeRestore::Keep.action("fluent.dark"), None); + assert_eq!(ThemeRestore::Saved.action(""), None); + assert_eq!(ThemeRestore::Saved.action("custom"), None); + assert_eq!( + ThemeRestore::Saved.action("system"), + Some(RestoreAction::FollowSystem) + ); + match ThemeRestore::Saved.action("kde.dark") { + Some(RestoreAction::Set(theme)) => assert_eq!(theme.id.as_str(), "kde.dark"), + other => panic!("expected the saved KDE Dark theme, got {other:?}"), + } + } + #[test] fn every_cli_theme_name_resolves() { for (name, _) in OFFERED { diff --git a/tools/extract_widget_api.py b/tools/extract_widget_api.py index 1ecd6f0d5..9f65e26d9 100755 --- a/tools/extract_widget_api.py +++ b/tools/extract_widget_api.py @@ -254,6 +254,10 @@ def marker_end(self) -> str: "theme-fluent": CrateSpec("teksilo-theme-fluent", "teksilo_theme_fluent", "theme-fluent", "Fluent Theme", "Themes", False, False), "theme-macos": CrateSpec("teksilo-theme-macos", "teksilo_theme_macos", "theme-macos", "macOS Theme", "Themes", False, False), "theme-material3": CrateSpec("teksilo-theme-material3", "teksilo_theme_material3", "theme-material3", "Material 3 Theme", "Themes", False, False), + "theme-kde": CrateSpec("teksilo-theme-kde", "teksilo_theme_kde", "theme-kde", "KDE Plasma Theme", "Themes", False, False), + # The KDE colour-scheme model the KDE preset and the platform reader share + # (`teksilo-theme-kde` re-exports it as `scheme`). + "kde-scheme": CrateSpec("teksilo-kde-scheme", "teksilo_kde_scheme", "kde-scheme", "KDE Colour Schemes", "Themes", False, False), # Storybook-equivalent previewer: trait/registry crate + reusable GUI library "preview": CrateSpec("teksilo-preview", "teksilo_preview", "preview", "Widget Previewer Core", "Preview", False, False), @@ -263,7 +267,7 @@ def marker_end(self) -> str: "fmt": CrateSpec("teksilo-fmt", "teksilo_fmt", "fmt", "teksu! Formatter", "Tooling", False, False), "parse": CrateSpec("teksilo-parse", "teksilo_parse", "parse", "teksu! Parser", "Tooling", False, False), - # The umbrella crate's OWN files (its toast/webview install-hook extension + # The umbrella crate's OWN files (its toast/webview/KDE install-hook extension # traits — automation_install.rs only re-exports teksilo-app's, so it does # not add anything new here) — distinct from the crates it merely # re-exports, each of which already has its own entry above. @@ -504,13 +508,15 @@ def _is_scannable(p: Path) -> bool: # teksilo-inspector "TeksiloAppBuilderInspectorExt": "inspector", - # The umbrella crate's OWN files (toast_install.rs / webview_install.rs) — - # these two extension traits are teksilo's own code, not a re-export of - # another crate. `TeksiloAppBuilderAutomationExt`, by contrast, is genuinely + # The umbrella crate's OWN files (toast_install.rs, webview_install.rs and + # kde_native_install.rs): these three extension traits are teksilo's own + # code, not a re-export of another crate. `TeksiloAppBuilderAutomationExt`, + # by contrast, is genuinely # defined in teksilo-app (automation_bridge.rs) and merely re-exported # through automation_install.rs — see the "teksilo-app" group below. "TeksiloAppBuilderToastExt": "teksilo", "TeksiloAppBuilderWebViewExt": "teksilo", + "TeksiloAppBuilderKdeExt": "teksilo", # teksilo-widgets — the toast/notification surface re-exported in prelude "EventContextToastExt": "widgets", diff --git a/tools/kde_golden/.dockerignore b/tools/kde_golden/.dockerignore new file mode 100644 index 000000000..29a54a3b7 --- /dev/null +++ b/tools/kde_golden/.dockerignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech +vectors +*.md diff --git a/tools/kde_golden/Dockerfile b/tools/kde_golden/Dockerfile new file mode 100644 index 000000000..529dcf560 --- /dev/null +++ b/tools/kde_golden/Dockerfile @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech +# +# Reference ("oracle") environment for the KDE Breeze preset: KDE neon User +# Edition on Ubuntu 24.04, upgraded to Plasma 6.8 and Frameworks 6.31 from +# neon's user archive. See README.md for the versions it produced and why the +# upgrade step cannot be pinned more tightly than major.minor. +# +# Nothing here runs in CI. Build and run it through run.sh. + +# invent-registry.kde.org/neon/docker-images/plasma:user, pulled 2026-10-16. +# That tag shipped Plasma 6.7.4 and Frameworks 6.29; the upgrade below brings +# it to 6.8 / 6.31. +FROM invent-registry.kde.org/neon/docker-images/plasma@sha256:f58f9b97f218ee976cf928f6fe00abb929a644aded8c9dfb02f77a9b2a6e1732 + +USER root +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get full-upgrade -y \ + && apt-get install -y --no-install-recommends \ + cmake g++ make extra-cmake-modules \ + qt6-base-dev libkf6colorscheme-dev libkf6guiaddons-dev libkf6config-dev \ + kde-style-breeze kde-style-oxygen plasma-workspace \ + fonts-noto-core fonts-hack \ + python3 dbus \ + && rm -rf /var/lib/apt/lists/* + +# Refuse an image that did not reach the versions the vectors are meant to +# describe. neon keeps only the newest build of each package, so a rebuild +# months from now can land on 6.8.N or 6.32: the check pins major.minor and +# versions.txt records exactly what was installed. +RUN set -e; \ + v() { dpkg-query -W -f='${Version}' "$1"; }; \ + case "$(v plasma-workspace)" in 4:6.8.*) ;; *) echo "plasma-workspace $(v plasma-workspace) is not 6.8.x" >&2; exit 1;; esac; \ + case "$(v kde-style-breeze)" in 4:6.8.*) ;; *) echo "kde-style-breeze $(v kde-style-breeze) is not 6.8.x" >&2; exit 1;; esac; \ + case "$(v kf6-kcolorscheme)" in 6.31.*) ;; *) echo "kf6-kcolorscheme $(v kf6-kcolorscheme) is not 6.31.x" >&2; exit 1;; esac; \ + case "$(v kf6-kguiaddons)" in 6.31.*) ;; *) echo "kf6-kguiaddons $(v kf6-kguiaddons) is not 6.31.x" >&2; exit 1;; esac; \ + mkdir -p /opt/kde_golden; \ + dpkg-query -W -f='${Package}\t${Version}\n' \ + plasma-workspace kde-style-breeze breeze kde-style-oxygen \ + kf6-kcolorscheme kf6-kguiaddons kf6-kconfig libkf6colorscheme-dev \ + qt6-base fonts-noto-core fonts-hack \ + | sort > /opt/kde_golden/versions.txt; \ + sha256sum /usr/share/fonts/truetype/noto/NotoSans-Regular.ttf \ + /usr/share/fonts/truetype/hack/Hack-Regular.ttf \ + >> /opt/kde_golden/fonts.sha256; \ + cat /opt/kde_golden/versions.txt + +COPY driver/ /opt/kde_golden/driver/ +RUN cmake -S /opt/kde_golden/driver -B /opt/kde_golden/build -DCMAKE_BUILD_TYPE=Release \ + && cmake --build /opt/kde_golden/build -j"$(nproc)" + +USER neon +WORKDIR /home/neon diff --git a/tools/kde_golden/README.md b/tools/kde_golden/README.md new file mode 100644 index 000000000..f7230c9d4 --- /dev/null +++ b/tools/kde_golden/README.md @@ -0,0 +1,406 @@ + + + +# kde_golden: reference data from a real KDE Plasma + +This directory produces the reference ("oracle") vectors that the KDE Breeze +preset (`teksilo-kde-scheme`, `teksilo-theme-kde`) is tested against. The +vectors come from a real KDE Plasma 6.8 / Frameworks 6.31 installation running +in Docker, driven by a small C++ program that calls only public Qt 6 and KDE +Frameworks 6 API, plus the `plasma-apply-colorscheme` command-line tool. + +- **Nothing here runs in CI.** The vectors are generated on a maintainer + machine with `run.sh` and committed. +- **Nothing here is packaged.** `tools/` sits outside every crate directory, + so no `cargo package` can include it (the planned package-size guard also + asserts that tools and vectors are absent from the `.crate` files). +- **No KDE source and no `.colors` file is in this directory.** The image has + the installed files; the driver and the scripts read them at run time. The + vectors embed the *parsed key/value pairs* of each input scheme so a test + never needs the file. Those values come from files under their own licences: + `BreezeLight.colors` and `BreezeDark.colors` carry + `LGPL-2.0-or-later` headers; `BreezeClassic.colors` (package + `kde-style-breeze`) and the Oxygen files (package `kde-style-oxygen`) carry + no SPDX header, see their Debian copyright files in the image. + +## Files + +| File | What | +|---|---| +| `Dockerfile` | the image: KDE neon pinned by digest, upgraded to Plasma 6.8 / KF 6.31, build deps, the driver built in | +| `driver/` | the MPL-2.0 C++ driver (`kde_golden`), CMake project | +| `run.sh` | on the host: builds the image, runs itself inside it, writes `vectors/` | +| `accent_oracle.sh` | inside the container: `plasma-apply-colorscheme -a` cases into `vectors/accent.json` | +| `vectors/math.json` | `KColorUtils` and static `KColorScheme::shade` over a lattice, plus the Qt colour behaviour they rest on | +| `vectors/resolve.json` | `KColorScheme` and `createApplicationPalette` over 13 schemes | +| `vectors/sizes.json` | Breeze `QStyle` metrics, `sizeFromContents`, sub-rects, widget `sizeHint` at 6 font sizes | +| `vectors/pixels.json` | Breeze-painted controls sampled along fixed lines, Light and Dark | +| `vectors/accent.json` | `kdeglobals` written by `plasma-apply-colorscheme` for 40 scheme x accent cases | +| `vectors/versions.txt` | `dpkg-query` package/version list from the image | +| `vectors/fonts.sha256` | sha256 of the Noto Sans and Hack faces the image measured with | + +## Provenance + +**Base image.** `invent-registry.kde.org/neon/docker-images/plasma:user`, +pulled 2026-10-16, pinned by digest: + +``` +invent-registry.kde.org/neon/docker-images/plasma@sha256:f58f9b97f218ee976cf928f6fe00abb929a644aded8c9dfb02f77a9b2a6e1732 +``` + +That tag (image created 2026-09-10) ships KDE neon User Edition on Ubuntu +24.04 with Plasma 6.7.4, Frameworks 6.29 and Qt 6.11.1. No published neon image +had Plasma 6.8 yet, so the Dockerfile runs `apt-get full-upgrade` against +neon's user archive (`http://archive.neon.kde.org/user`, suite `noble`), which +on 2026-10-16 carried Plasma 6.8.0 and Frameworks 6.31.0. + +**Versions obtained** (also in `vectors/versions.txt` and in every file's +`meta.packages`): + +| Package | Version | +|---|---| +| plasma-workspace | 4:6.8.0-0zneon+24.04+noble+release+build99 | +| kde-style-breeze | 4:6.8.0-0zneon+24.04+noble+release+build80 | +| breeze | 4:6.8.0-0zneon+24.04+noble+release+build80 | +| kde-style-oxygen | 4:6.8.0-0zneon+24.04+noble+release+build59 | +| kf6-kcolorscheme | 6.31.0-0zneon+24.04+noble+release+build47 | +| kf6-kguiaddons | 6.31.0-0zneon+24.04+noble+release+build59 | +| kf6-kconfig | 6.31.0-0zneon+24.04+noble+release+build36 | +| qt6-base | 6.11.1-0zneon+24.04+noble+release+build162 | +| fonts-noto-core | 20201225-2 (Ubuntu) | +| fonts-hack | 3.003-3 (Ubuntu) | + +The driver reports the same at run time: `qt_runtime` 6.11.1, +`kcolorscheme_build` and `kguiaddons_build` 6.31.0. + +**Reproducibility.** Two consecutive `run.sh` runs produce byte-identical +vectors. A rebuild later may not reach the same packages: neon keeps only the +newest build of each package, so the upgrade step cannot be pinned more tightly +than the Dockerfile's check (Plasma 6.8.x, Frameworks 6.31.x; the build fails +otherwise). Compare `vectors/versions.txt` before and after a regeneration. + +**Run environment** (set by `run.sh --inside`): + +- `QT_QPA_PLATFORM=offscreen`; no platform theme (`QT_QPA_PLATFORMTHEME`, + `QT_STYLE_OVERRIDE`, `KDE_FULL_SESSION`, `DISPLAY` unset); `LC_ALL=C.UTF-8`. +- Every step gets fresh scratch `HOME`, `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, + `XDG_CACHE_HOME`, `XDG_RUNTIME_DIR`, and an **empty** `XDG_CONFIG_DIRS`, so + `/etc/xdg` is never read. `XDG_DATA_DIRS=/usr/local/share:/usr/share` (where + the installed schemes live). No `breezerc` exists, so the Breeze style runs on + its defaults. +- The offscreen screen reports logical DPI 96, physical DPI 100, device pixel + ratio 1. +- Fonts: Noto Sans and Hack from the Ubuntu packages above (sha256 in + `vectors/fonts.sha256`). Teksilo's bundled faces may be newer releases. + +## Regenerating + +```bash +tools/kde_golden/run.sh +``` + +It needs docker without sudo and network access to the two registries. It +never touches the host's KDE configuration: everything runs in the container +as the invoking uid, with scratch XDG directories, and writes only +`tools/kde_golden/vectors/`. `accent_oracle.sh` additionally refuses to run +unless `XDG_CONFIG_HOME` and `XDG_DATA_HOME` are set, absolute, and each either +empty or marked with a `.kde_golden_scratch` file, because +`plasma-apply-colorscheme` rewrites `kdeglobals`. Never run either script +outside the container. + +## Encodings shared by every file + +**Input colour** (a string): `"#rrggbbaa"`, 8 bits per channel. The driver +builds it with `QColor::fromRgb(r, g, b, a)`, so its 16-bit value is exactly +`v * 257`. + +**Output colour** (an object): + +```json +{"hex": "#rrggbbaa", "u16": [r, g, b, a]} +``` + +- `u16`: the QColor's own 16-bit channels (`QColor::rgba64()`), 0..65535. This + is exact: QColor stores RGB colours as 16-bit integers, so any floating + result KColorUtils or KColorScheme computes has already been quantised to 16 + bits by QColor before it reaches the file. +- `hex`: Qt's 8-bit accessors (`red()`, `green()`, `blue()`, `alpha()`), which + round the 16-bit value divided by 257 to the nearest integer + (`floor((q + 128) / 257)`; checked on all 65536 values). +- `spec` and `f` (four exact doubles) appear only when the colour is not stored + as `QColor::Rgb`. Only `math.json`'s `qt_colorspace` and `qt_extended` + members contain such colours (`ExtendedRgb`, held in half precision). +- `{"valid": false}` is an invalid QColor (for example an empty value in a + scheme file). + +**Scalars** (luma, contrast ratios, HCY components, amounts) are JSON numbers +written by `QJsonDocument` with the shortest decimal that round-trips the +double, so parsing them as `f64` gives back the exact value. `1.0` may appear +as `1`. + +**Rectangles and sizes**: `[x, y, width, height]` and `[width, height]`, in +logical pixels (device pixel ratio 1). + +**Line layout.** Each file is one JSON object. Arrays that hold records are +written one record per line, so a regeneration diffs line by line. Object keys +inside a record are sorted. + +**Comparing from Rust.** Colours: compare `u16` exactly, or allow 1 unit per +channel where an implementation quantises at a different step, and require +`hex` to be equal. Scalars: within 1e-6. + +Every file starts with `meta`: + +| Field | Meaning | +|---|---| +| `kind` | `math`, `resolve`, `sizes`, `pixels` or `accent` | +| `generator` | what wrote it | +| `image` | base image reference with digest | +| `packages` | package name to version, from `/opt/kde_golden/versions.txt` | +| `qt_runtime`, `qt_build` | Qt version at run time and at build time (driver files only) | +| `kcolorscheme_build`, `kguiaddons_build` | KF versions the driver was built against (driver files only) | +| `readme` | pointer to this file | + +## `math.json` + +Calls `KColorUtils::{luma, hue, chroma, getHcy, hcyColor, contrastRatio, +lighten, darken, shade, tint, mix, overlayColors}` and the static +`KColorScheme::shade(color, role, contrast, chromaAdjust)`. + +| Member | Content | +|---|---| +| `colors` | the 62 input colours (black, white, greys around the ends and around 0x7f/0x80, primaries, secondaries, half and pastel variants, near-grey and near-black/white values, colours from the Breeze schemes, the Plasma accent swatches, 4 translucent colours) | +| `pair_colors` | the 17-colour subset used for two-colour operations | +| `single` | one record per `colors` entry: `in`, `luma`, `hue`, `chroma`, `hcy` = `[h, c, y, a]` from `getHcy`, `roundtrip` = colour from `hcyColor(h, c, y, a)` | +| `contrast_ratio` | `a` (each of `pair_colors`) x `b` (each of `colors`): `out` = `contrastRatio(a, b)` | +| `lighten` | `in` x `amount` in {-0.5, 0, 0.1, 0.25, 0.5, 0.75, 0.9, 1, 1.5} x `gain` in {0, 0.5, 1, 2}: `out` = `lighten(in, amount, gain)` (gain is `chromaInverseGain`) | +| `darken` | same lattice: `out` = `darken(in, amount, gain)` (gain is `chromaGain`) | +| `shade` | `in` x `luma` in {-1.5, -1, -0.5, -0.1, 0, 0.1, 0.5, 1, 1.5} x `chroma` in {-1, 0, 0.5, 1}: `out` = `shade(in, luma, chroma)` | +| `tint` | `base` x `color` (both `pair_colors`) x `amount` in {-0.5, 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 1, 1.5}: `out` = `tint(base, color, amount)` | +| `mix` | `c1` x `c2` (both `pair_colors`) x `bias` in {-0.5, 0, 0.1, 0.25, 0.5, 0.75, 0.9, 1, 1.5}: `out` = `mix(c1, c2, bias)` | +| `overlay` | `base` x `paint` (both `pair_colors`) x `mode` in {`SourceOver`, `Multiply`, `Screen`}: `out` = `overlayColors(base, paint, mode)` | +| `hcy_color` | `h` in {-0.25, 0, 0.1, 1/6, 0.5, 0.9, 1, 1.25} x `c` in {-0.5, 0, 0.25, 0.5, 1, 1.5} x `y` in {-0.1, 0, 0.1, 0.5, 0.9, 1, 1.1} x `a` in {1, 0.5}: `out` = `hcyColor(h, c, y, a)` | +| `scheme_shade` | `in` x `role` (`Light`, `Midlight`, `Mid`, `Dark`, `Shadow`) x `contrast` in {-1, 0, 0.4, 0.7, 1, 1.5} x `chroma` in {0, 0.5}: `out` = `KColorScheme::shade(in, role, contrast, chroma)` | +| `qt_lighter_darker` | `in` (each of `colors`) x `factor` in {-10, 0, 1, 50, 99, 100, 101, 102, 110, 120, 150, 200, 300, 1000} x `op` in {`lighter`, `darker`}: `out` = `QColor::lighter(factor)` / `darker(factor)` | +| `qt_colorspace` | `in` = each of `colors`, the 256 greys and the reds `#010000` to `#ff0000`: `to_linear` and `from_linear` = Qt's `QColorTransform` from `QColorSpace::SRgb` to `SRgbLinear` and back, applied to the `QColor` | +| `qt_extended` | `in` = three floats, some outside 0..1: `stored` = `QColor::fromRgbF(in)` (an `ExtendedRgb` colour, with its half-precision values in `f`), `to_rgb` = its `toRgb()` | +| `qt_color_text` | `in` = a colour string: `out` = `QColor::fromString(in)` (hex forms of 3, 6, 8, 9 and 12 digits, invalid lengths, names in several cases, `transparent`, unknown names, an empty string) | + +Out-of-range amounts are deliberate: they pin the clamping behaviour. + +## `resolve.json` + +| Member | Content | +|---|---| +| `missing_installed` | installed schemes the image lacked (empty in this run) | +| `schemes` | one record per scheme, one per line | + +Each scheme record: + +| Field | Meaning | +|---|---| +| `name` | scheme name | +| `origin` | `installed` (read from `/usr/share/color-schemes/`) or `synthetic` (written by the driver) | +| `source` | installed only: the file path in the image | +| `purpose`, `source_text` | synthetic only: what the scheme isolates, and its complete file text (MPL-2.0, original values) | +| `sha256` | sha256 of the scheme file as read; for the Breeze files it identifies the upstream file to vendor as a fixture | +| `input` | the parsed scheme: group name to `{key: value}`. Nested groups are spelled as in the file, `"Colors:Header][Inactive"`. Values are KConfig's parsed strings (surrounding whitespace removed; for a duplicated key or group, what KConfig kept). Localised keys (`Name[de]`) are reduced to the untranslated value | +| `contrastF` | `KColorScheme::contrastF(config)` | +| `frameContrast` | `KColorScheme::frameContrast(config)` | +| `set_supported` | set name to `KColorScheme::isColorSetSupported(config, set)` | +| `sets` | set name (`View`, `Window`, `Button`, `Selection`, `Tooltip`, `Complementary`, `Header`) to state (`Active`, `Inactive`, `Disabled`) to the four role groups below | +| `palette` | state to QPalette role name to colour, from `KColorScheme::createApplicationPalette(config)`; roles are every `QPalette::ColorRole` below `NColorRoles` except `NoRole` (Qt 6.11 includes `Accent`) | + +Role groups inside `sets..`, each from +`KColorScheme(state, set, config)`: + +| Group | Keys | Call | +|---|---|---| +| `background` | `Normal`, `Alternate`, `Active`, `Link`, `Visited`, `Negative`, `Neutral`, `Positive` | `background(role).color()` | +| `foreground` | `Normal`, `Inactive`, `Active`, `Link`, `Visited`, `Negative`, `Neutral`, `Positive` | `foreground(role).color()` | +| `decoration` | `Focus`, `Hover` | `decoration(role).color()` | +| `shade` | `Light`, `Midlight`, `Mid`, `Dark`, `Shadow` | `shade(role)` | + +Config objects are opened with `KSharedConfig::openConfig(path, +KConfig::SimpleConfig)`, so nothing cascades into them. + +Schemes, in file order: `BreezeLight`, `BreezeDark`, `BreezeClassic`, +`Oxygen`, `OxygenCold`, `OxygenDark` (installed), then the synthetic +`SyntheticNoHeader` (no Header group, no effect groups), +`SyntheticInactiveEffects` (dark, Header without an Inactive subgroup, Inactive +effects enabled with the selection change, every effect kind with non-zero +amounts, `contrast=2`, `frameContrast=0.35`), `SyntheticInactiveSubgroups` +(Inactive effects without the selection change, explicit Inactive subgroups for +Header, View and a partial one for Window), `SyntheticAccentKeys` (the +`[General]` accent keys and a `[WM]` group, few sets), `SyntheticEmpty` (an +empty file), `SyntheticMalformed` (hex, alpha, spaces, a named colour, too +few components, out-of-range components, an empty value, `contrast=15`, an +out-of-range effect id, a duplicated key and a duplicated group) and +`SyntheticValueForms` (an empty boolean, `[ColorEffects:Inactive] Enable=`; +a signed component; 3-, 9- and 12-digit hex; names in other cases; +`transparent`; a trailing comma; a hex component; the unknown CSS name +`rebeccapurple`). + +Behaviours these record, for orientation (the data is the authority): +a duplicated key keeps the last value; a duplicated group merges, later values +winning; an empty colour value resolves to an invalid colour; an empty +boolean reads as true; `contrastF` is +not clamped (1.5 for `contrast=15`); the `[General]` accent keys do not reach +KColorScheme; Header falls back to Window when the scheme has no Header group. + +## `sizes.json` + +All values come from the Breeze style (`QStyleFactory::create("Breeze")`, +installed as the application style) under the offscreen platform, with the +default `kdeglobals` set to BreezeLight. Every measured widget except a `QMenu` +is created hidden with a plain `QWidget` parent (Breeze answers some metrics +differently for a top-level window) and polished before it is measured. + +| Member | Content | +|---|---| +| `environment` | `style`, `platform`, `logical_dpi`, `physical_dpi`, `device_pixel_ratio`, the Qt default font before the driver overrides it, and a note on parenting | +| `pixel_metrics` | every `QStyle::PixelMetric` name to `pixelMetric(pm, nullptr, nullptr)` | +| `widget_pixel_metrics` | widget class to the pixel metrics whose value changes when the style is given that widget and an option `initFrom` it (only the differing ones; `{}` means none differ). Key example: `QLineEdit.PM_DefaultFrameWidth` is 6 while the widget-less answer is 0 | +| `style_hints` | every `QStyle::StyleHint` name to `styleHint(sh, nullptr, nullptr, nullptr)` | +| `sub_rects` | sub-control and sub-element rectangles at the 10 pt font, keyed by control; each object has the input `rect` plus one rectangle per sub-control, and extra inputs (`range`, `page_step`, `value`) where relevant. Controls: combo box (normal and editable, 150 x 32), spin box (100 x 32), vertical scroll bar at values 0, 50, 100 (`PM_ScrollBarExtent` x 200, range 0..100, page 20), horizontal slider with ticks 0 (none), 2 (below), 3 (both) at 200 wide and value 30, check box and radio (at their size hint), push button, line edit, progress bar (200 wide), item-view item (200 x 24, check + icon + text), tab (80 x 30) | +| `size_from_contents` | records `{id, option, contents, out}`: `out` = `sizeFromContents(type, option, contents, widget)` for content sizes [0,0], [20,16], [60,18], [100,24], [100,32]. `id` is the contents type plus a variant; `option` lists the option fields set beyond `initFrom(widget)` (enum values are Qt's integers: `type` is `QStyleOptionMenuItem::MenuItemType`, `check_type` is `CheckType`, `shape` is `QTabBar::Shape`, `orientation` is `Qt::Orientation`, `ticks` is `QSlider::TickPosition`) | +| `monospace_fonts` | font records for Hack at 9, 10, 11 pt | +| `fonts` | font records for Noto Sans at 10, 8, 9, 11, 12, 14 pt (the order the driver measured them) | +| `widgets` | widget records, one per widget per font | +| `legacy_font_weights` | `legacy` 0..99 in field 4 of a Qt 5 font string (`Noto Sans,10,-1,5,W,0,0,0,0,0`): `weight` = the OpenType weight of `QFont::fromString` | +| `tooltip` | the label `QToolTip::showText` shows for "Tooltip text" at Noto Sans 10 pt: its class, `margin`, `contents_margins`, `size_hint` and the text's `QFontMetrics::size` | + +A **font record**: `requested_family`, `requested_point_size`, +`resolved_family`, `resolved_style`, `resolved_point_size`, +`resolved_pixel_size`, `exact_match` (from `QFontInfo`), `to_string` +(`QFont::toString`), `metrics` (integer `QFontMetrics`: `height`, `ascent`, +`descent`, `leading`, `line_spacing`, `x_height`, `cap_height`, +`average_char_width`, `max_width`), `metrics_f` (the same from +`QFontMetricsF`), `advance` (string to `[QFontMetrics::horizontalAdvance, +QFontMetricsF::horizontalAdvance]`). At 96 DPI Noto Sans 10 pt resolves to a +13 px face (`resolved_point_size` 9.75, `exact_match` false) whose +`QFontMetrics::height` is 18. + +A **widget record**: + +| Field | Meaning | +|---|---| +| `id` | what was measured; a suffix names the variant (`push_button.default`, `tool_button.auto_raise.22`, ...). Several records can share an `id` when only `config` differs (the three `push_button` texts) | +| `class` | the Qt class | +| `font` | `"Noto Sans "`, the application font at measurement | +| `config` | the properties set on the widget (text, icon, flags; enum values as Qt integers: `style` is `Qt::ToolButtonStyle`, `popup_mode` is `QToolButton::ToolButtonPopupMode` or -1 for no menu, `shape` is `QTabBar::Shape`) | +| `size_hint`, `minimum_size_hint` | `QWidget::sizeHint()` and `minimumSizeHint()`; `[-1, -1]` is Qt's invalid size | +| `rows` | menus only: one entry per action, `{kind, text, icon, geometry}`, `kind` in `item`, `shortcut`, `separator`, `section`, `checkable`, `submenu`, `geometry` = `QMenu::actionGeometry` | +| `tab_rects` | tab bars only: `QTabBar::tabRect(i)` after `resize(sizeHint())`, tabs "Tab", "Second tab", "Third", `expanding` off | +| `section_size_hint`, `default_section_size`, `minimum_section_size` | headers: `QHeaderView` values for sections 0 and 1 of a two-column model ("Name", "Size") | +| `size_hint_for_row` | item views: `QAbstractItemView::sizeHintForRow(0)` for a two-row model, with a 16 px icon (`.icon`) or a check box (`.check`) | +| `indentation`, `header_height` | tree views: `QTreeView::indentation()` and its header's size hint height | + +Icons are a solid 64 x 64 pixmap scaled by Qt to the requested icon size. + +## `pixels.json` + +Controls painted by the Breeze style with a hand-built `QStyleOption` and **no +widget** (so no animation engine runs and window activation is explicit), into +a `QImage::Format_ARGB32` image of the given size that is first filled opaque +with the palette's background colour. No text, icon or label is painted: +these vectors pin chrome, not glyph rasterisation. + +The palette is `KColorScheme::createApplicationPalette` of the process's +`kdeglobals`, which `run.sh` sets to the scheme being sampled; the Breeze style +also reads its hover and focus colours from that `kdeglobals`. The font is +Noto Sans 10 pt. + +| Member | Content | +|---|---| +| `schemes` | `BreezeLight` and `BreezeDark`, each `{environment, records}` | +| `schemes..environment` | `scheme`, `kdeglobals_color_scheme` (what the style saw), `style`, `platform`, `image_format` | +| `schemes..records` | one record per painted control state, one per line | + +A record: + +| Field | Meaning | +|---|---| +| `id` | control and state, `push_button.hover` | +| `control` | the QStyle element painted (`CE_PushButtonBevel`, `CC_ComboBox`, `PE_PanelItemViewItem`, ...) | +| `state` | `name`; `flags`, the extra `QStyle::State` bits as an integer (`State_Enabled` is added unless `group` is `Disabled`, `State_Active` unless `group` is `Inactive`); `group`, the palette colour group (`Active`, `Inactive`, `Disabled`); and per-control inputs (`features` = `QStyleOptionButton::ButtonFeatures`, `editable`, `value`, `handle_rect`, `slider_rect`, `active_sub_control`, `type`, `document_mode`, `line_width`) | +| `size` | `[width, height]` of the image, which is also the option rectangle | +| `background` | the fill colour painted first (`Window`, or `Base` for header and item-view rows, `ToolTipBase` for the tooltip) | +| `rows` | y (as a string) to the run-length-encoded row of pixels at that y | +| `cols` | x (as a string) to the run-length-encoded column at that x | + +Run-length encoding: a list of `[start, length, "#rrggbbaa"]`, covering the +whole line in order. All pixels are opaque. + +Sampled lines per control are chosen to cross the outline, the fill and the +indicator: the outer two or three rows and columns, the centre, and for +sliders and scroll bars the line through the handle (its rectangle is in +`state`). Controls: push button (normal, hover, focus, pressed, default, +disabled, inactive window, flat, flat hover; 84 x 34), auto-raise tool button +(normal, hover, pressed, checked, framed; 28 x 28), line edit panel (normal, +hover, focus, disabled; 150 x 32, frame width from a real `QLineEdit`), check +box indicator (off, on, partial, hover, focus, pressed, disabled), radio +indicator (off, on, hover, focus, disabled), combo box (normal, hover, focus, +editable), spin box (normal, hover), slider (normal, handle hover, focus, +disabled; value 30), vertical scroll bar (normal, hover, slider hover, slider +pressed; value 0, page 20), progress bar at 50 %, menu panel, menu item +(normal, selected, separator), tab shape (selected, unselected, hover, and +document mode), header section (normal, hover, sunken), item-view row +(normal, hover, selected, selected and hovered, selected in an inactive +window, focus), tooltip panel, styled frame, group box frame. + +## `accent.json` + +What `plasma-apply-colorscheme` writes to `kdeglobals`. Per case, in its own +scratch config directory and its own `dbus-run-session`: + +1. `kdeglobals` is seeded with only `[General] ColorScheme=KdeGoldenNone`, a + name no installed scheme has: the tool refuses to apply the scheme that is + already current, and an unset `ColorScheme` reads as `BreezeLight`; +2. `plasma-apply-colorscheme SCHEME` applies the scheme (snapshot `base`); +3. `plasma-apply-colorscheme -a ACCENT` applies the accent. With `-a` the tool + ignores a scheme argument and re-applies the *current* scheme with the + accent. + +Cases: the 10 Plasma swatches (`#e93a9a #e93d58 #e9643a #e8cb2d #3dd425 +#00d3b8 #3daee9 #b875dc #926ee4 #686b6f`) x 4 schemes: `BreezeLight`, +`BreezeDark`, `KdeGoldenTintLight` (BreezeLight plus `[General] +TintFactor=0.25`) and `KdeGoldenTitlebarDark` (BreezeDark plus `[General] +TitlebarIsAccentColored=true`). The two derived schemes are built in the +scratch data directory with `kwriteconfig6`, which also sets their `Name` and +`ColorScheme` to the derived name. + +| Member | Content | +|---|---| +| `meta` | `kind`, `generator`, `image`, `packages`, `procedure`, `readme` | +| `schemes` | scheme name to its record, one per line | +| `cases` | one record per case, one per line | + +A scheme record: + +| Field | Meaning | +|---|---| +| `origin` | `installed` or `derived` | +| `source` | the installed file path, or `derived (scratch data dir)` | +| `derived_from`, `changes` | derived only: the installed scheme and the `[General]` keys `kwriteconfig6` set | +| `sha1` | sha1 of the scheme file; the tool writes it to `[General] ColorSchemeHash` | +| `sha256` | sha256 of the scheme file | +| `input` | the scheme file parsed as group to `{key: raw value}` (localised `Name[xx]` keys dropped, `[$x]` flags kept in the key) | +| `base` | `kdeglobals` after step 2 (no accent), same parsing | +| `base_sha256` | sha256 of that file | +| `base_differs_between_cases` | present (true) only if step 2 ever produced a different file for the same scheme; absent in this run | + +A case record: + +| Field | Meaning | +|---|---| +| `scheme`, `accent` | the inputs; `accent` exactly as passed to `-a` | +| `commands` | the two invocations | +| `stdout` | the tool's message for step 3 | +| `kdeglobals` | `kdeglobals` after step 3, parsed as group to `{key: raw value}`; this is the byte-for-byte target for `with_accent` | +| `kdeglobals_sha256` | sha256 of that file | +| `config_files` | every file the tool left in the config directory (only `kdeglobals` in this run) | + +Behaviours these record, for orientation: the tool writes no `AccentColor` +key; `[General] ColorScheme` is removed when it equals the default +(`BreezeLight`); `[General] ColorSchemeHash` is the sha1 of the scheme file. diff --git a/tools/kde_golden/accent_oracle.sh b/tools/kde_golden/accent_oracle.sh new file mode 100755 index 000000000..450a8ccb7 --- /dev/null +++ b/tools/kde_golden/accent_oracle.sh @@ -0,0 +1,219 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech +# +# Accent oracle: what plasma-apply-colorscheme writes to kdeglobals when an +# accent colour is applied. Runs INSIDE the kde_golden container (run.sh +# calls it); never on a desktop, because the tool rewrites the user's +# kdeglobals. +# +# accent_oracle.sh OUT.json +# +# Requirements, checked before anything runs: +# - XDG_CONFIG_HOME and XDG_DATA_HOME are set, are directories, and are +# each either empty or marked as scratch by a .kde_golden_scratch file; +# - QT_QPA_PLATFORM=offscreen; +# - dbus-run-session and plasma-apply-colorscheme are installed. +# +# Procedure per case (one scheme, one accent), each in its own config dir +# under XDG_CONFIG_HOME: +# 1. kdeglobals is seeded with only "[General] ColorScheme=KdeGoldenNone", +# a name no installed scheme has, because the tool refuses to apply the +# scheme that is already current; +# 2. "plasma-apply-colorscheme SCHEME" applies the scheme (snapshot "base"); +# 3. "plasma-apply-colorscheme -a ACCENT" applies the accent to the current +# scheme (snapshot "kdeglobals"). With -a the tool ignores any scheme +# name argument and always uses the current scheme. +# Each command runs in its own dbus-run-session. + +set -euo pipefail + +out=${1:?usage: accent_oracle.sh OUT.json} + +die() { + echo "accent_oracle.sh: $*" >&2 + exit 1 +} + +check_scratch() { + local name=$1 dir=${!1:-} + [[ -n $dir ]] || die "$name is not set; refusing to touch a real configuration" + [[ -d $dir ]] || die "$name=$dir is not a directory" + [[ $dir == /* ]] || die "$name=$dir is not absolute" + if [[ -n $(ls -A "$dir") && ! -e $dir/.kde_golden_scratch ]]; then + die "$name=$dir is neither empty nor marked with .kde_golden_scratch; refusing" + fi + touch "$dir/.kde_golden_scratch" +} + +check_scratch XDG_CONFIG_HOME +check_scratch XDG_DATA_HOME +[[ ${QT_QPA_PLATFORM:-} == offscreen ]] || die "QT_QPA_PLATFORM must be offscreen" +command -v dbus-run-session >/dev/null || die "dbus-run-session is not installed" +command -v plasma-apply-colorscheme >/dev/null || die "plasma-apply-colorscheme is not installed" +command -v kwriteconfig6 >/dev/null || die "kwriteconfig6 is not installed" + +accents=(e93a9a e93d58 e9643a e8cb2d 3dd425 00d3b8 3daee9 b875dc 926ee4 686b6f) +work=$XDG_CONFIG_HOME +schemes_dir=$XDG_DATA_HOME/color-schemes +mkdir -p "$schemes_dir" + +# Two derived schemes, built in the scratch data dir from the installed files +# (never copied into the repository): one with a TintFactor, one with the +# title bar accent-coloured. +derive() { + local name=$1 from=$2 key=$3 value=$4 + cp "/usr/share/color-schemes/$from.colors" "$schemes_dir/$name.colors" + kwriteconfig6 --file "$schemes_dir/$name.colors" --group General --key Name "$name" + kwriteconfig6 --file "$schemes_dir/$name.colors" --group General --key ColorScheme "$name" + kwriteconfig6 --file "$schemes_dir/$name.colors" --group General --key "$key" "$value" +} +derive KdeGoldenTintLight BreezeLight TintFactor 0.25 +derive KdeGoldenTitlebarDark BreezeDark TitlebarIsAccentColored true + +schemes=(BreezeLight BreezeDark KdeGoldenTintLight KdeGoldenTitlebarDark) +manifest=$work/manifest.tsv +: >"$manifest" + +n=0 +for scheme in "${schemes[@]}"; do + for accent in "${accents[@]}"; do + n=$((n + 1)) + case_dir=$work/case-$(printf %02d "$n") + mkdir -p "$case_dir/config" + printf '[General]\nColorScheme=KdeGoldenNone\n' >"$case_dir/config/kdeglobals" + XDG_CONFIG_HOME=$case_dir/config dbus-run-session -- \ + plasma-apply-colorscheme "$scheme" >"$case_dir/apply.out" 2>"$case_dir/apply.err" || true + grep -q "Successfully applied the color scheme" "$case_dir/apply.out" || + die "applying $scheme failed: $(cat "$case_dir/apply.out" "$case_dir/apply.err")" + cp "$case_dir/config/kdeglobals" "$case_dir/base.kdeglobals" + XDG_CONFIG_HOME=$case_dir/config dbus-run-session -- \ + plasma-apply-colorscheme -a "#$accent" >"$case_dir/accent.out" 2>"$case_dir/accent.err" || true + grep -q "Successfully applied the accent color" "$case_dir/accent.out" || + die "accent #$accent on $scheme failed: $(cat "$case_dir/accent.out" "$case_dir/accent.err")" + printf '%s\t%s\t%s\n' "$scheme" "#$accent" "$case_dir" >>"$manifest" + done +done + +python3 -I - "$manifest" "$schemes_dir" "$out" <<'PY' +import hashlib, json, os, sys + +manifest, schemes_dir, out = sys.argv[1:4] + + +def parse(path): + """kdeglobals / .colors -> {group: {key: raw value}}, file order kept. + + Group headers like [Colors:Header][Inactive] become "Colors:Header][Inactive". + Keys keep any [$x] flags verbatim. Values are the raw text after the + first '=' (KConfig strips surrounding whitespace, and so does this). + Localised keys (Name[de]) are dropped: they carry translations only. + """ + groups, cur = {}, None + with open(path, encoding="utf-8") as f: + for line in f: + line = line.rstrip("\n") + s = line.strip() + if not s or s.startswith("#"): + continue + if s.startswith("[") and s.endswith("]"): + cur = s[1:-1] + groups.setdefault(cur, {}) + continue + if "=" not in s or cur is None: + continue + k, v = s.split("=", 1) + k, v = k.strip(), v.strip() + if "[" in k and "[$" not in k: + continue + groups[cur][k] = v + return groups + + +def digest(path, algo): + with open(path, "rb") as f: + return hashlib.new(algo, f.read()).hexdigest() + + +def read(path): + with open(path, encoding="utf-8", errors="replace") as f: + return f.read() + + +rows = [l.rstrip("\n").split("\t") for l in open(manifest, encoding="utf-8") if l.strip()] +schemes, cases = {}, [] +for scheme, accent, case_dir in rows: + if scheme not in schemes: + derived = os.path.join(schemes_dir, scheme + ".colors") + if os.path.exists(derived): + src, origin = derived, "derived" + else: + src, origin = "/usr/share/color-schemes/%s.colors" % scheme, "installed" + rec = { + "origin": origin, + "source": src if origin == "installed" else "derived (scratch data dir)", + "sha1": digest(src, "sha1"), + "sha256": digest(src, "sha256"), + "input": parse(src), + "base": parse(os.path.join(case_dir, "base.kdeglobals")), + "base_sha256": digest(os.path.join(case_dir, "base.kdeglobals"), "sha256"), + } + if scheme == "KdeGoldenTintLight": + rec["derived_from"] = "BreezeLight" + rec["changes"] = {"General": {"Name": scheme, "ColorScheme": scheme, "TintFactor": "0.25"}} + if scheme == "KdeGoldenTitlebarDark": + rec["derived_from"] = "BreezeDark" + rec["changes"] = {"General": {"Name": scheme, "ColorScheme": scheme, "TitlebarIsAccentColored": "true"}} + schemes[scheme] = rec + else: + # The no-accent snapshot must not depend on the case. + if parse(os.path.join(case_dir, "base.kdeglobals")) != schemes[scheme]["base"]: + schemes[scheme]["base_differs_between_cases"] = True + kg = os.path.join(case_dir, "config", "kdeglobals") + files = sorted( + os.path.relpath(os.path.join(dp, fn), os.path.join(case_dir, "config")) + for dp, _, fns in os.walk(os.path.join(case_dir, "config")) + for fn in fns + ) + cases.append({ + "scheme": scheme, + "accent": accent, + "commands": [["plasma-apply-colorscheme", scheme], ["plasma-apply-colorscheme", "-a", accent]], + "stdout": read(os.path.join(case_dir, "accent.out")).strip(), + "kdeglobals": parse(kg), + "kdeglobals_sha256": digest(kg, "sha256"), + "config_files": files, + }) + +versions = {} +try: + for l in open("/opt/kde_golden/versions.txt", encoding="utf-8"): + k, _, v = l.rstrip("\n").partition("\t") + if k: + versions[k] = v +except OSError: + pass + +meta = { + "kind": "accent", + "generator": "tools/kde_golden/accent_oracle.sh", + "image": os.environ.get("KDE_GOLDEN_IMAGE", "unknown"), + "packages": versions, + "procedure": "see accent_oracle.sh header and README.md", + "readme": "tools/kde_golden/README.md documents every field", +} + +with open(out, "w", encoding="utf-8") as f: + f.write("{\n") + f.write('"meta": %s,\n' % json.dumps(meta, sort_keys=True, ensure_ascii=False)) + f.write('"schemes": {\n') + names = list(schemes) + for i, name in enumerate(names): + f.write("%s: %s%s\n" % (json.dumps(name), json.dumps(schemes[name], ensure_ascii=False), "," if i + 1 < len(names) else "")) + f.write("},\n") + f.write('"cases": [\n') + for i, c in enumerate(cases): + f.write("%s%s\n" % (json.dumps(c, ensure_ascii=False), "," if i + 1 < len(cases) else "")) + f.write("]\n}\n") +PY +echo "accent_oracle.sh: wrote $out ($n cases)" diff --git a/tools/kde_golden/driver/CMakeLists.txt b/tools/kde_golden/driver/CMakeLists.txt new file mode 100644 index 000000000..af527d05a --- /dev/null +++ b/tools/kde_golden/driver/CMakeLists.txt @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech +# +# Reference-data driver for tools/kde_golden. Built inside the Docker image +# (see ../Dockerfile); it links the distribution's KF6 and Qt 6 libraries and +# calls only their public API. + +cmake_minimum_required(VERSION 3.22) +project(kde_golden_driver LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC OFF) + +find_package(ECM 6.0 REQUIRED NO_MODULE) +find_package(Qt6 6.6 REQUIRED COMPONENTS Core Gui Widgets) +find_package(KF6ColorScheme REQUIRED) +find_package(KF6GuiAddons REQUIRED) +find_package(KF6Config REQUIRED) + +add_executable(kde_golden + main.cpp + golden_json.cpp + math_vectors.cpp + resolve_vectors.cpp + size_vectors.cpp + pixel_vectors.cpp +) +target_link_libraries(kde_golden PRIVATE + Qt6::Core Qt6::Gui Qt6::Widgets + KF6::ColorScheme KF6::GuiAddons KF6::ConfigCore KF6::ConfigGui +) diff --git a/tools/kde_golden/driver/golden_json.cpp b/tools/kde_golden/driver/golden_json.cpp new file mode 100644 index 000000000..90e250e05 --- /dev/null +++ b/tools/kde_golden/driver/golden_json.cpp @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech + +#include "golden_json.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace golden { + +QString hex8(const QColor &c) +{ + return QString::asprintf("#%02x%02x%02x%02x", c.red(), c.green(), c.blue(), c.alpha()); +} + +static QString specName(QColor::Spec s) +{ + switch (s) { + case QColor::Invalid: + return QStringLiteral("Invalid"); + case QColor::Rgb: + return QStringLiteral("Rgb"); + case QColor::Hsv: + return QStringLiteral("Hsv"); + case QColor::Cmyk: + return QStringLiteral("Cmyk"); + case QColor::Hsl: + return QStringLiteral("Hsl"); + case QColor::ExtendedRgb: + return QStringLiteral("ExtendedRgb"); + } + return QStringLiteral("Unknown"); +} + +QJsonObject color(const QColor &c) +{ + QJsonObject o; + if (!c.isValid()) { + o.insert(QStringLiteral("valid"), false); + return o; + } + const QRgba64 w = c.rgba64(); + o.insert(QStringLiteral("hex"), hex8(c)); + o.insert(QStringLiteral("u16"), QJsonArray{int(w.red()), int(w.green()), int(w.blue()), int(w.alpha())}); + if (c.spec() != QColor::Rgb) { + o.insert(QStringLiteral("spec"), specName(c.spec())); + o.insert(QStringLiteral("f"), QJsonArray{c.redF(), c.greenF(), c.blueF(), c.alphaF()}); + } + return o; +} + +QColor parseHex8(const QString &s) +{ + bool ok = true; + const int r = s.mid(1, 2).toInt(&ok, 16); + const int g = s.mid(3, 2).toInt(&ok, 16); + const int b = s.mid(5, 2).toInt(&ok, 16); + const int a = s.size() >= 9 ? s.mid(7, 2).toInt(&ok, 16) : 255; + return QColor::fromRgb(r, g, b, a); +} + +static QString readText(const QString &path) +{ + QFile f(path); + if (!f.open(QIODevice::ReadOnly)) + return QString(); + return QString::fromUtf8(f.readAll()); +} + +QJsonObject meta(const QString &kind) +{ + const auto env = QProcessEnvironment::systemEnvironment(); + QJsonObject m; + m.insert(QStringLiteral("kind"), kind); + m.insert(QStringLiteral("generator"), QStringLiteral("tools/kde_golden/driver (kde_golden)")); + m.insert(QStringLiteral("image"), env.value(QStringLiteral("KDE_GOLDEN_IMAGE"), QStringLiteral("unknown"))); + m.insert(QStringLiteral("qt_runtime"), QString::fromLatin1(qVersion())); + m.insert(QStringLiteral("qt_build"), QStringLiteral(QT_VERSION_STR)); + m.insert(QStringLiteral("kcolorscheme_build"), QStringLiteral(KCOLORSCHEME_VERSION_STRING)); + m.insert(QStringLiteral("kguiaddons_build"), QStringLiteral(KGUIADDONS_VERSION_STRING)); + QJsonObject pkgs; + const QStringList lines = readText(QStringLiteral("/opt/kde_golden/versions.txt")).split(QLatin1Char('\n'), Qt::SkipEmptyParts); + for (const QString &line : lines) { + const QStringList kv = line.split(QLatin1Char('\t')); + if (kv.size() == 2) + pkgs.insert(kv[0], kv[1]); + } + m.insert(QStringLiteral("packages"), pkgs); + m.insert(QStringLiteral("readme"), QStringLiteral("tools/kde_golden/README.md documents every field")); + return m; +} + +static QByteArray compact(const QJsonValue &v) +{ + if (v.isObject()) + return QJsonDocument(v.toObject()).toJson(QJsonDocument::Compact); + if (v.isArray()) + return QJsonDocument(v.toArray()).toJson(QJsonDocument::Compact); + // Scalars: wrap in an array and strip the brackets. + QByteArray a = QJsonDocument(QJsonArray{v}).toJson(QJsonDocument::Compact); + return a.mid(1, a.size() - 2); +} + +static QByteArray quoted(const QString &s) +{ + return compact(QJsonValue(s)); +} + +bool writeFile(const QString &path, const QList &members) +{ + QFile f(path); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + std::fprintf(stderr, "cannot write %s\n", qPrintable(path)); + return false; + } + f.write("{\n"); + for (int i = 0; i < members.size(); ++i) { + const Member &m = members[i]; + f.write("\""); + f.write(m.key.toUtf8()); + f.write("\": "); + if (m.oneElementPerLine && m.value.isArray()) { + const QJsonArray arr = m.value.toArray(); + f.write("[\n"); + for (int j = 0; j < arr.size(); ++j) { + f.write(compact(arr[j])); + f.write(j + 1 < arr.size() ? ",\n" : "\n"); + } + f.write("]"); + } else if (m.oneElementPerLine && m.value.isObject()) { + const QJsonObject obj = m.value.toObject(); + f.write("{\n"); + const QStringList keys = obj.keys(); + for (int j = 0; j < keys.size(); ++j) { + f.write(quoted(keys[j])); + f.write(": "); + f.write(compact(obj.value(keys[j]))); + f.write(j + 1 < keys.size() ? ",\n" : "\n"); + } + f.write("}"); + } else { + f.write(compact(m.value)); + } + f.write(i + 1 < members.size() ? ",\n" : "\n"); + } + f.write("}\n"); + return f.error() == QFile::NoError; +} + +} // namespace golden diff --git a/tools/kde_golden/driver/golden_json.h b/tools/kde_golden/driver/golden_json.h new file mode 100644 index 000000000..e04899d4f --- /dev/null +++ b/tools/kde_golden/driver/golden_json.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech +// +// JSON helpers shared by every vector writer. A colour is always written the +// same way (see README.md, "Colour encoding"), and every file is written as +// one object whose arrays hold one record per line, so a regenerated file +// diffs line by line. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace golden { + +// "#rrggbbaa" from Qt's own 8-bit accessors. +QString hex8(const QColor &c); + +// {"hex": "#rrggbbaa", "u16": [r, g, b, a]}, plus "spec" and "f" (exact +// doubles) when the colour is not stored in QColor::Rgb, and {"valid": false} +// for an invalid colour. +QJsonObject color(const QColor &c); + +// Parses "#rrggbb" or "#rrggbbaa" into an 8-bit QColor (16-bit = 8-bit * 257). +QColor parseHex8(const QString &s); + +// Metadata common to every file: generator, versions, environment. +QJsonObject meta(const QString &kind); + +// One top-level member: either an array (written one element per line) or any +// other value (written compact on one line). +struct Member { + QString key; + QJsonValue value; + bool oneElementPerLine = false; +}; + +// Writes {"k": v, ...} with arrays one element per line. Returns false and +// prints to stderr on failure. +bool writeFile(const QString &path, const QList &members); + +} // namespace golden diff --git a/tools/kde_golden/driver/main.cpp b/tools/kde_golden/driver/main.cpp new file mode 100644 index 000000000..db65d30a9 --- /dev/null +++ b/tools/kde_golden/driver/main.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech +// +// kde_golden: writes the reference vectors for the KDE Breeze preset from a +// real KDE installation. It calls only public Qt 6 and KDE Frameworks 6 API. +// +// kde_golden math OUT.json +// kde_golden resolve OUT.json +// kde_golden sizes OUT.json +// kde_golden pixels OUT.json SCHEME_LABEL +// +// run.sh drives it inside the container; README.md documents the output. + +#include +#include +#include + +#include + +int writeMath(const QString &path); +int writeResolve(const QString &path); +int writeSizes(const QString &path); +int writePixels(const QString &path, const QString &schemeLabel); + +int main(int argc, char **argv) +{ + if (qEnvironmentVariable("QT_QPA_PLATFORM") != QLatin1String("offscreen")) { + std::fprintf(stderr, "kde_golden: run with QT_QPA_PLATFORM=offscreen\n"); + return 2; + } + QApplication app(argc, argv); + const QStringList args = app.arguments(); + if (args.size() < 3) { + std::fprintf(stderr, "usage: kde_golden math|resolve|sizes|pixels OUT.json [SCHEME_LABEL]\n"); + return 2; + } + const QString mode = args[1]; + const QString out = args[2]; + + if (mode == QLatin1String("sizes") || mode == QLatin1String("pixels")) { + QStyle *breeze = QStyleFactory::create(QStringLiteral("Breeze")); + if (!breeze) { + std::fprintf(stderr, "kde_golden: the Breeze style plugin is not available (have: %s)\n", + qPrintable(QStyleFactory::keys().join(QLatin1String(", ")))); + return 1; + } + QApplication::setStyle(breeze); + } + + if (mode == QLatin1String("math")) + return writeMath(out); + if (mode == QLatin1String("resolve")) + return writeResolve(out); + if (mode == QLatin1String("sizes")) + return writeSizes(out); + if (mode == QLatin1String("pixels")) + return writePixels(out, args.size() > 3 ? args[3] : QString()); + std::fprintf(stderr, "kde_golden: unknown mode %s\n", qPrintable(mode)); + return 2; +} diff --git a/tools/kde_golden/driver/math_vectors.cpp b/tools/kde_golden/driver/math_vectors.cpp new file mode 100644 index 000000000..97247bd0c --- /dev/null +++ b/tools/kde_golden/driver/math_vectors.cpp @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech +// +// math.json: KColorUtils and the static KColorScheme::shade over a fixed +// lattice of colours and amounts, plus the Qt colour behaviour the KDE +// colour model rests on (QColor::lighter / darker, the sRGB colour-space +// transforms, out-of-range float colours, colour text parsing). Every input +// colour is an 8-bit value, so its 16-bit form is exactly value * 257. + +#include "golden_json.h" + +#include +#include + +#include +#include +#include + +namespace { + +// The lattice. Order is part of the format: tests may index by position. +const QStringList kColors = { + // black, white, greys (including the values around 0x7f/0x80 and the ends) + QStringLiteral("#000000ff"), QStringLiteral("#ffffffff"), QStringLiteral("#010101ff"), QStringLiteral("#020202ff"), + QStringLiteral("#040404ff"), QStringLiteral("#080808ff"), QStringLiteral("#101010ff"), QStringLiteral("#202020ff"), + QStringLiteral("#404040ff"), QStringLiteral("#7f7f7fff"), QStringLiteral("#808080ff"), QStringLiteral("#818181ff"), + QStringLiteral("#bfbfbfff"), QStringLiteral("#c0c0c0ff"), QStringLiteral("#fdfdfdff"), QStringLiteral("#fefefeff"), + // saturated primaries and secondaries, half-intensity and pastel variants + QStringLiteral("#ff0000ff"), QStringLiteral("#00ff00ff"), QStringLiteral("#0000ffff"), QStringLiteral("#ffff00ff"), + QStringLiteral("#00ffffff"), QStringLiteral("#ff00ffff"), QStringLiteral("#800000ff"), QStringLiteral("#008000ff"), + QStringLiteral("#000080ff"), QStringLiteral("#ff8080ff"), QStringLiteral("#80ff80ff"), QStringLiteral("#8080ffff"), + // near-threshold: almost grey, almost black, almost white + QStringLiteral("#030201ff"), QStringLiteral("#fffefdff"), QStringLiteral("#7f8081ff"), QStringLiteral("#010000ff"), + QStringLiteral("#000001ff"), QStringLiteral("#fefffeff"), + // colours found in the Breeze schemes + QStringLiteral("#3daee9ff"), QStringLiteral("#232629ff"), QStringLiteral("#eff0f1ff"), QStringLiteral("#fcfcfcff"), + QStringLiteral("#31363bff"), QStringLiteral("#1b1e20ff"), QStringLiteral("#da4453ff"), QStringLiteral("#27ae60ff"), + QStringLiteral("#f67400ff"), QStringLiteral("#2980b9ff"), QStringLiteral("#7f8c8dff"), QStringLiteral("#9b59b6ff"), + QStringLiteral("#1d99f3ff"), QStringLiteral("#dee0e2ff"), QStringLiteral("#292c30ff"), + // the Plasma accent swatches not already listed + QStringLiteral("#e93a9aff"), QStringLiteral("#e93d58ff"), QStringLiteral("#e9643aff"), QStringLiteral("#e8cb2dff"), + QStringLiteral("#3dd425ff"), QStringLiteral("#00d3b8ff"), QStringLiteral("#b875dcff"), QStringLiteral("#926ee4ff"), + QStringLiteral("#686b6fff"), + // translucent + QStringLiteral("#3daee980"), QStringLiteral("#ffffff00"), QStringLiteral("#00000080"), QStringLiteral("#ff000033"), +}; + +// The smaller set used for two-colour operations (tint, mix, overlay). +const QStringList kPairColors = { + QStringLiteral("#000000ff"), QStringLiteral("#ffffffff"), QStringLiteral("#7f7f7fff"), QStringLiteral("#808080ff"), + QStringLiteral("#ff0000ff"), QStringLiteral("#00ff00ff"), QStringLiteral("#0000ffff"), QStringLiteral("#ffff00ff"), + QStringLiteral("#030201ff"), QStringLiteral("#3daee9ff"), QStringLiteral("#232629ff"), QStringLiteral("#eff0f1ff"), + QStringLiteral("#fcfcfcff"), QStringLiteral("#da4453ff"), QStringLiteral("#e8cb2dff"), QStringLiteral("#3daee980"), + QStringLiteral("#ffffff00"), +}; + +const QList kLightenAmounts = {-0.5, 0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0, 1.5}; +const QList kGains = {0.0, 0.5, 1.0, 2.0}; +const QList kShadeLuma = {-1.5, -1.0, -0.5, -0.1, 0.0, 0.1, 0.5, 1.0, 1.5}; +const QList kShadeChroma = {-1.0, 0.0, 0.5, 1.0}; +const QList kTintAmounts = {-0.5, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 1.0, 1.5}; +const QList kMixBias = {-0.5, 0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0, 1.5}; +const QList kHcyHue = {-0.25, 0.0, 0.1, 1.0 / 6.0, 0.5, 0.9, 1.0, 1.25}; +const QList kHcyChroma = {-0.5, 0.0, 0.25, 0.5, 1.0, 1.5}; +const QList kHcyLuma = {-0.1, 0.0, 0.1, 0.5, 0.9, 1.0, 1.1}; +const QList kHcyAlpha = {1.0, 0.5}; +const QList kSchemeContrast = {-1.0, 0.0, 0.4, 0.7, 1.0, 1.5}; +const QList kSchemeChroma = {0.0, 0.5}; +// QColor::lighter / darker factors: the non-positive and below-100 branches, +// the identity, and the factors the Breeze style uses (102, 110, 120). +const QList kQtFactors = {-10, 0, 1, 50, 99, 100, 101, 102, 110, 120, 150, 200, 300, 1000}; + +const QList> kShadeRoles = { + {KColorScheme::LightShade, QStringLiteral("Light")}, + {KColorScheme::MidlightShade, QStringLiteral("Midlight")}, + {KColorScheme::MidShade, QStringLiteral("Mid")}, + {KColorScheme::DarkShade, QStringLiteral("Dark")}, + {KColorScheme::ShadowShade, QStringLiteral("Shadow")}, +}; + +const QList> kOverlayModes = { + {QPainter::CompositionMode_SourceOver, QStringLiteral("SourceOver")}, + {QPainter::CompositionMode_Multiply, QStringLiteral("Multiply")}, + {QPainter::CompositionMode_Screen, QStringLiteral("Screen")}, +}; + +} // namespace + +int writeMath(const QString &path) +{ + using namespace golden; + QJsonArray single; + for (const QString &h : kColors) { + const QColor c = parseHex8(h); + qreal hh = 0, cc = 0, yy = 0, aa = 0; + KColorUtils::getHcy(c, &hh, &cc, &yy, &aa); + QJsonObject o; + o.insert(QStringLiteral("in"), h); + o.insert(QStringLiteral("luma"), KColorUtils::luma(c)); + o.insert(QStringLiteral("hue"), KColorUtils::hue(c)); + o.insert(QStringLiteral("chroma"), KColorUtils::chroma(c)); + o.insert(QStringLiteral("hcy"), QJsonArray{hh, cc, yy, aa}); + o.insert(QStringLiteral("roundtrip"), color(KColorUtils::hcyColor(hh, cc, yy, aa))); + single.append(o); + } + + QJsonArray contrast; + for (const QString &a : kPairColors) + for (const QString &b : kColors) { + QJsonObject o; + o.insert(QStringLiteral("a"), a); + o.insert(QStringLiteral("b"), b); + o.insert(QStringLiteral("out"), KColorUtils::contrastRatio(parseHex8(a), parseHex8(b))); + contrast.append(o); + } + + QJsonArray lighten, darken; + for (const QString &h : kColors) + for (double amt : kLightenAmounts) + for (double g : kGains) { + const QColor c = parseHex8(h); + QJsonObject o; + o.insert(QStringLiteral("in"), h); + o.insert(QStringLiteral("amount"), amt); + o.insert(QStringLiteral("gain"), g); + o.insert(QStringLiteral("out"), color(KColorUtils::lighten(c, amt, g))); + lighten.append(o); + o.insert(QStringLiteral("out"), color(KColorUtils::darken(c, amt, g))); + darken.append(o); + } + + QJsonArray shade; + for (const QString &h : kColors) + for (double l : kShadeLuma) + for (double ch : kShadeChroma) { + QJsonObject o; + o.insert(QStringLiteral("in"), h); + o.insert(QStringLiteral("luma"), l); + o.insert(QStringLiteral("chroma"), ch); + o.insert(QStringLiteral("out"), color(KColorUtils::shade(parseHex8(h), l, ch))); + shade.append(o); + } + + QJsonArray tint, mix; + for (const QString &a : kPairColors) + for (const QString &b : kPairColors) { + for (double amt : kTintAmounts) { + QJsonObject o; + o.insert(QStringLiteral("base"), a); + o.insert(QStringLiteral("color"), b); + o.insert(QStringLiteral("amount"), amt); + o.insert(QStringLiteral("out"), color(KColorUtils::tint(parseHex8(a), parseHex8(b), amt))); + tint.append(o); + } + for (double bias : kMixBias) { + QJsonObject o; + o.insert(QStringLiteral("c1"), a); + o.insert(QStringLiteral("c2"), b); + o.insert(QStringLiteral("bias"), bias); + o.insert(QStringLiteral("out"), color(KColorUtils::mix(parseHex8(a), parseHex8(b), bias))); + mix.append(o); + } + } + + QJsonArray overlay; + for (const QString &a : kPairColors) + for (const QString &b : kPairColors) + for (const auto &mode : kOverlayModes) { + QJsonObject o; + o.insert(QStringLiteral("base"), a); + o.insert(QStringLiteral("paint"), b); + o.insert(QStringLiteral("mode"), mode.second); + o.insert(QStringLiteral("out"), color(KColorUtils::overlayColors(parseHex8(a), parseHex8(b), mode.first))); + overlay.append(o); + } + + QJsonArray hcy; + for (double h : kHcyHue) + for (double c : kHcyChroma) + for (double y : kHcyLuma) + for (double a : kHcyAlpha) { + QJsonObject o; + o.insert(QStringLiteral("h"), h); + o.insert(QStringLiteral("c"), c); + o.insert(QStringLiteral("y"), y); + o.insert(QStringLiteral("a"), a); + o.insert(QStringLiteral("out"), color(KColorUtils::hcyColor(h, c, y, a))); + hcy.append(o); + } + + QJsonArray schemeShade; + for (const QString &h : kColors) + for (const auto &role : kShadeRoles) + for (double k : kSchemeContrast) + for (double ch : kSchemeChroma) { + QJsonObject o; + o.insert(QStringLiteral("in"), h); + o.insert(QStringLiteral("role"), role.second); + o.insert(QStringLiteral("contrast"), k); + o.insert(QStringLiteral("chroma"), ch); + o.insert(QStringLiteral("out"), color(KColorScheme::shade(parseHex8(h), role.first, k, ch))); + schemeShade.append(o); + } + + QJsonArray qtLighterDarker; + for (const QString &h : kColors) + for (int f : kQtFactors) { + const QColor c = parseHex8(h); + QJsonObject o; + o.insert(QStringLiteral("in"), h); + o.insert(QStringLiteral("factor"), f); + o.insert(QStringLiteral("op"), QStringLiteral("lighter")); + o.insert(QStringLiteral("out"), color(c.lighter(f))); + qtLighterDarker.append(o); + o.insert(QStringLiteral("op"), QStringLiteral("darker")); + o.insert(QStringLiteral("out"), color(c.darker(f))); + qtLighterDarker.append(o); + } + + // Qt's own sRGB <-> linear sRGB colour-space transforms (QColorSpace / + // QColorTransform, public API) on QColor values, and the storage of + // out-of-range float colours (QColor::fromRgbF then toRgb). These pin the + // conversions the OKLab accent tint goes through. + const QColorTransform toLinear = QColorSpace(QColorSpace::SRgb).transformationToColorSpace(QColorSpace::SRgbLinear); + const QColorTransform fromLinear = QColorSpace(QColorSpace::SRgbLinear).transformationToColorSpace(QColorSpace::SRgb); + QStringList csInputs = kColors; + for (int v = 0; v < 256; ++v) + csInputs.append(QString::asprintf("#%02x%02x%02xff", v, v, v)); + for (int v = 1; v < 256; ++v) + csInputs.append(QString::asprintf("#%02x0000ff", v)); + QJsonArray qtColorSpace; + for (const QString &h : csInputs) { + const QColor c = parseHex8(h); + QJsonObject o; + o.insert(QStringLiteral("in"), h); + o.insert(QStringLiteral("to_linear"), color(toLinear.map(c))); + o.insert(QStringLiteral("from_linear"), color(fromLinear.map(c))); + qtColorSpace.append(o); + } + const QList> kExtendedInputs = { + {1.05f, 0.123456789f, -0.01f}, {-0.0000001f, 0.5f, 0.75f}, {1.0000001f, 0.333333343f, 0.666666687f}, + {2.0f, -1.0f, 0.0001f}, {0.999f, 1.001f, 0.2f}, {0.1f, 0.2f, 1.5f}, + }; + QJsonArray qtExtended; + for (const auto &v : kExtendedInputs) { + QJsonObject o; + o.insert(QStringLiteral("in"), QJsonArray{double(v[0]), double(v[1]), double(v[2])}); + const QColor e = QColor::fromRgbF(v[0], v[1], v[2]); + o.insert(QStringLiteral("stored"), color(e)); + o.insert(QStringLiteral("to_rgb"), color(e.toRgb())); + qtExtended.append(o); + } + + // QColor's string parsing, which KConfig uses for every colour value that + // starts with '#' or contains no comma. + const QStringList kColorTexts = { + QStringLiteral("#abc"), QStringLiteral("#3daee9"), QStringLiteral("#803daee9"), + QStringLiteral("#123456789"), QStringLiteral("#123456789abc"), QStringLiteral("#12"), + QStringLiteral("#12345"), QStringLiteral("#1234567"), QStringLiteral("#xyzxyz"), + QStringLiteral("red"), QStringLiteral("Red"), QStringLiteral("RED"), + QStringLiteral("transparent"), QStringLiteral("rebeccapurple"), QStringLiteral("grey"), + QStringLiteral("notacolour"), QStringLiteral(" #3daee9"), QStringLiteral(""), + }; + QJsonArray qtColorText; + for (const QString &t : kColorTexts) { + QJsonObject o; + o.insert(QStringLiteral("in"), t); + o.insert(QStringLiteral("out"), color(QColor::fromString(t))); + qtColorText.append(o); + } + + QJsonArray colorsList, pairList; + for (const QString &h : kColors) + colorsList.append(h); + for (const QString &h : kPairColors) + pairList.append(h); + + return writeFile(path, + { + {QStringLiteral("meta"), meta(QStringLiteral("math"))}, + {QStringLiteral("colors"), colorsList}, + {QStringLiteral("pair_colors"), pairList}, + {QStringLiteral("single"), single, true}, + {QStringLiteral("contrast_ratio"), contrast, true}, + {QStringLiteral("lighten"), lighten, true}, + {QStringLiteral("darken"), darken, true}, + {QStringLiteral("shade"), shade, true}, + {QStringLiteral("tint"), tint, true}, + {QStringLiteral("mix"), mix, true}, + {QStringLiteral("overlay"), overlay, true}, + {QStringLiteral("hcy_color"), hcy, true}, + {QStringLiteral("scheme_shade"), schemeShade, true}, + {QStringLiteral("qt_lighter_darker"), qtLighterDarker, true}, + {QStringLiteral("qt_colorspace"), qtColorSpace, true}, + {QStringLiteral("qt_extended"), qtExtended, true}, + {QStringLiteral("qt_color_text"), qtColorText, true}, + }) + ? 0 + : 1; +} diff --git a/tools/kde_golden/driver/pixel_vectors.cpp b/tools/kde_golden/driver/pixel_vectors.cpp new file mode 100644 index 000000000..c44f7b606 --- /dev/null +++ b/tools/kde_golden/driver/pixel_vectors.cpp @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech +// +// pixels-.json: Breeze-painted controls, sampled. Each control is +// painted through QStyle with a hand-built option (no widget, so no animation +// engine and no window-activation guesswork) into an opaque QImage filled +// with the palette's background colour, then sampled along documented lines. +// No text is painted: glyph rasterisation is not what these vectors pin. +// +// The colour scheme is whatever kdeglobals the process sees: run.sh copies +// the scheme into a scratch XDG_CONFIG_HOME first, because the Breeze style +// reads its focus and hover colours from there, not from the palette. + +#include "golden_json.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +QJsonArray rc(const QRect &r) +{ + return QJsonArray{r.x(), r.y(), r.width(), r.height()}; +} + +// Run-length encoding of one line of pixels: [[start, length, "#rrggbbaa"], ...]. +QJsonArray rle(const QImage &img, bool horizontal, int at) +{ + QJsonArray out; + const int n = horizontal ? img.width() : img.height(); + int start = 0; + QRgb prev = 0; + for (int i = 0; i <= n; ++i) { + const bool end = i == n; + const QRgb px = end ? 0 : (horizontal ? img.pixel(i, at) : img.pixel(at, i)); + if (i == 0) { + prev = px; + continue; + } + if (end || px != prev) { + out.append(QJsonArray{start, i - start, golden::hex8(QColor::fromRgba(prev))}); + start = i; + prev = px; + } + } + return out; +} + +struct Shot { + QString id; + QString control; + QJsonObject state; + QSize size; + QPalette::ColorRole background = QPalette::Window; + QPalette::ColorGroup group = QPalette::Active; + std::function draw; + QList rows; // empty: the centre row + QList cols; // empty: the centre column +}; + +QJsonObject runShot(const Shot &s, const QPalette &pal) +{ + QImage img(s.size, QImage::Format_ARGB32); + img.fill(pal.color(s.group, s.background)); + { + QPainter p(&img); + s.draw(p, QRect(QPoint(0, 0), s.size)); + } + QJsonObject o; + o.insert(QStringLiteral("id"), s.id); + o.insert(QStringLiteral("control"), s.control); + o.insert(QStringLiteral("state"), s.state); + o.insert(QStringLiteral("size"), QJsonArray{s.size.width(), s.size.height()}); + o.insert(QStringLiteral("background"), golden::hex8(pal.color(s.group, s.background))); + QJsonObject rows, cols; + const QList ry = s.rows.isEmpty() ? QList{s.size.height() / 2} : s.rows; + const QList cx = s.cols.isEmpty() ? QList{s.size.width() / 2} : s.cols; + for (int y : ry) + rows.insert(QString::number(y), rle(img, true, y)); + for (int x : cx) + cols.insert(QString::number(x), rle(img, false, x)); + o.insert(QStringLiteral("rows"), rows); + o.insert(QStringLiteral("cols"), cols); + return o; +} + +struct Ctx { + QStyle *style; + QPalette pal; + QFont font; + + // Fills the fields QStyleOption::initFrom would, deterministically. + void base(QStyleOption &opt, const QRect &r, QStyle::State extra, QPalette::ColorGroup g) const + { + opt.rect = r; + opt.direction = Qt::LeftToRight; + opt.fontMetrics = QFontMetrics(font); + opt.palette = pal; + opt.palette.setCurrentColorGroup(g); + opt.state = extra; + if (g != QPalette::Disabled) + opt.state |= QStyle::State_Enabled; + if (g != QPalette::Inactive) + opt.state |= QStyle::State_Active; + opt.styleObject = nullptr; + } +}; + +struct Variant { + QString name; + QStyle::State state; + QPalette::ColorGroup group = QPalette::Active; +}; + +QJsonObject stateDesc(const Variant &v) +{ + QJsonObject o; + o.insert(QStringLiteral("name"), v.name); + o.insert(QStringLiteral("flags"), int(v.state)); + o.insert(QStringLiteral("group"), v.group == QPalette::Active ? QStringLiteral("Active") + : v.group == QPalette::Inactive ? QStringLiteral("Inactive") + : QStringLiteral("Disabled")); + return o; +} + +QList shots(const Ctx &c) +{ + QList out; + QStyle *st = c.style; + const QStyle::State hover = QStyle::State_MouseOver; + const QStyle::State focus = QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange; + const QStyle::State sunken = QStyle::State_Sunken; + const QStyle::State on = QStyle::State_On; + + // Push button, at the size Breeze gives an "OK" button. + { + const QSize size(84, 34); + const QList> vs = { + {{QStringLiteral("normal"), QStyle::State_Raised}, QStyleOptionButton::None}, + {{QStringLiteral("hover"), QStyle::State_Raised | hover}, QStyleOptionButton::None}, + {{QStringLiteral("focus"), QStyle::State_Raised | focus}, QStyleOptionButton::None}, + {{QStringLiteral("pressed"), sunken}, QStyleOptionButton::None}, + {{QStringLiteral("default"), QStyle::State_Raised}, QStyleOptionButton::DefaultButton}, + {{QStringLiteral("disabled"), QStyle::State_Raised, QPalette::Disabled}, QStyleOptionButton::None}, + {{QStringLiteral("inactive_window"), QStyle::State_Raised, QPalette::Inactive}, QStyleOptionButton::None}, + {{QStringLiteral("flat"), QStyle::State_None}, QStyleOptionButton::Flat}, + {{QStringLiteral("flat_hover"), hover}, QStyleOptionButton::Flat}, + }; + for (const auto &v : vs) { + Shot s; + s.id = QStringLiteral("push_button.") + v.first.name; + s.control = QStringLiteral("CE_PushButtonBevel"); + s.state = stateDesc(v.first); + s.state.insert(QStringLiteral("features"), int(v.second)); + s.size = size; + s.group = v.first.group; + s.rows = {0, 1, 2, size.height() / 2, size.height() - 2, size.height() - 1}; + s.cols = {0, 1, 2, size.width() / 2}; + const Variant var = v.first; + const auto feat = v.second; + s.draw = [&c, st, var, feat](QPainter &p, const QRect &r) { + QStyleOptionButton opt; + c.base(opt, r, var.state, var.group); + opt.features = feat; + st->drawControl(QStyle::CE_PushButtonBevel, &opt, &p, nullptr); + }; + out.append(s); + } + } + + // Tool button (auto-raise), 28 x 28. + for (const Variant &v : {Variant{QStringLiteral("normal"), QStyle::State_AutoRaise | QStyle::State_Raised}, + Variant{QStringLiteral("hover"), QStyle::State_AutoRaise | QStyle::State_Raised | hover}, + Variant{QStringLiteral("pressed"), QStyle::State_AutoRaise | sunken | hover}, + Variant{QStringLiteral("checked"), QStyle::State_AutoRaise | on}, + Variant{QStringLiteral("framed"), QStyle::State_Raised}}) { + Shot s; + s.id = QStringLiteral("tool_button.") + v.name; + s.control = QStringLiteral("CC_ToolButton"); + s.state = stateDesc(v); + s.size = QSize(28, 28); + s.rows = {0, 1, 14}; + s.cols = {0, 1, 14}; + s.draw = [&c, st, v](QPainter &p, const QRect &r) { + QStyleOptionToolButton opt; + c.base(opt, r, v.state, v.group); + opt.subControls = QStyle::SC_ToolButton; + opt.activeSubControls = (v.state & (QStyle::State_MouseOver | QStyle::State_Sunken)) ? QStyle::SC_ToolButton : QStyle::SC_None; + opt.toolButtonStyle = Qt::ToolButtonIconOnly; + opt.iconSize = QSize(16, 16); + st->drawComplexControl(QStyle::CC_ToolButton, &opt, &p, nullptr); + }; + out.append(s); + } + + // Line edit panel. + for (const Variant &v : {Variant{QStringLiteral("normal"), sunken}, Variant{QStringLiteral("hover"), sunken | hover}, + Variant{QStringLiteral("focus"), sunken | focus}, Variant{QStringLiteral("disabled"), sunken, QPalette::Disabled}}) { + Shot s; + s.id = QStringLiteral("line_edit.") + v.name; + s.control = QStringLiteral("PE_PanelLineEdit"); + s.state = stateDesc(v); + s.size = QSize(150, 32); + s.group = v.group; + s.rows = {0, 1, 2, 16, 30, 31}; + s.cols = {0, 1, 2, 75}; + // QLineEdit::initStyleOption asks the style for the frame width with + // the line edit itself as the widget; with no widget Breeze answers 0 + // and QCommonStyle then paints no frame at all. + QLineEdit probe; + probe.ensurePolished(); + QStyleOptionFrame popt; + c.base(popt, QRect(QPoint(0, 0), s.size), v.state, v.group); + const int lineWidth = st->pixelMetric(QStyle::PM_DefaultFrameWidth, &popt, &probe); + s.state.insert(QStringLiteral("line_width"), lineWidth); + s.draw = [&c, st, v, lineWidth](QPainter &p, const QRect &r) { + QStyleOptionFrame opt; + c.base(opt, r, v.state, v.group); + opt.lineWidth = lineWidth; + opt.midLineWidth = 0; + st->drawPrimitive(QStyle::PE_PanelLineEdit, &opt, &p, nullptr); + }; + out.append(s); + } + + // Check box and radio indicators, at the indicator size the style reports. + const int cbw = st->pixelMetric(QStyle::PM_IndicatorWidth); + const int cbh = st->pixelMetric(QStyle::PM_IndicatorHeight); + for (const Variant &v : {Variant{QStringLiteral("off"), QStyle::State_Off}, Variant{QStringLiteral("on"), on}, + Variant{QStringLiteral("partial"), QStyle::State_NoChange}, Variant{QStringLiteral("hover_off"), QStyle::State_Off | hover}, + Variant{QStringLiteral("focus_on"), on | focus}, Variant{QStringLiteral("pressed_off"), QStyle::State_Off | sunken}, + Variant{QStringLiteral("disabled_on"), on, QPalette::Disabled}}) { + Shot s; + s.id = QStringLiteral("check_box.") + v.name; + s.control = QStringLiteral("PE_IndicatorCheckBox"); + s.state = stateDesc(v); + s.size = QSize(cbw, cbh); + s.group = v.group; + s.rows = {cbh / 2, cbh / 2 - 2, cbh / 2 + 2, 2, 3}; + s.cols = {cbw / 2, cbw / 2 - 2, 2, 3}; + s.draw = [&c, st, v](QPainter &p, const QRect &r) { + QStyleOptionButton opt; + c.base(opt, r, v.state, v.group); + st->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &p, nullptr); + }; + out.append(s); + } + const int rbw = st->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth); + const int rbh = st->pixelMetric(QStyle::PM_ExclusiveIndicatorHeight); + for (const Variant &v : {Variant{QStringLiteral("off"), QStyle::State_Off}, Variant{QStringLiteral("on"), on}, + Variant{QStringLiteral("hover_off"), QStyle::State_Off | hover}, Variant{QStringLiteral("focus_on"), on | focus}, + Variant{QStringLiteral("disabled_on"), on, QPalette::Disabled}}) { + Shot s; + s.id = QStringLiteral("radio_button.") + v.name; + s.control = QStringLiteral("PE_IndicatorRadioButton"); + s.state = stateDesc(v); + s.size = QSize(rbw, rbh); + s.group = v.group; + s.rows = {rbh / 2, 3}; + s.cols = {rbw / 2}; + s.draw = [&c, st, v](QPainter &p, const QRect &r) { + QStyleOptionButton opt; + c.base(opt, r, v.state, v.group); + st->drawPrimitive(QStyle::PE_IndicatorRadioButton, &opt, &p, nullptr); + }; + out.append(s); + } + + // Combo box frame and arrow (no label). + for (const auto &[v, editable] : QList>{{{QStringLiteral("normal"), QStyle::State_None}, false}, + {{QStringLiteral("hover"), hover}, false}, + {{QStringLiteral("focus"), focus}, false}, + {{QStringLiteral("editable"), QStyle::State_None}, true}}) { + Shot s; + s.id = QStringLiteral("combo_box.") + v.name; + s.control = QStringLiteral("CC_ComboBox"); + s.state = stateDesc(v); + s.state.insert(QStringLiteral("editable"), editable); + s.size = QSize(150, 32); + s.rows = {0, 1, 16, 31}; + s.cols = {0, 1, 40, 140}; + const Variant var = v; + const bool ed = editable; + s.draw = [&c, st, var, ed](QPainter &p, const QRect &r) { + QStyleOptionComboBox opt; + c.base(opt, r, var.state, var.group); + opt.editable = ed; + opt.frame = true; + opt.subControls = QStyle::SC_All; + st->drawComplexControl(QStyle::CC_ComboBox, &opt, &p, nullptr); + }; + out.append(s); + } + + // Spin box. + for (const Variant &v : {Variant{QStringLiteral("normal"), QStyle::State_None}, Variant{QStringLiteral("hover"), hover}}) { + Shot s; + s.id = QStringLiteral("spin_box.") + v.name; + s.control = QStringLiteral("CC_SpinBox"); + s.state = stateDesc(v); + s.size = QSize(100, 32); + s.rows = {0, 8, 16, 24, 31}; + s.cols = {0, 40, 90}; + s.draw = [&c, st, v](QPainter &p, const QRect &r) { + QStyleOptionSpinBox opt; + c.base(opt, r, v.state, v.group); + opt.frame = true; + opt.buttonSymbols = QAbstractSpinBox::UpDownArrows; + opt.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled; + opt.subControls = QStyle::SC_All; + st->drawComplexControl(QStyle::CC_SpinBox, &opt, &p, nullptr); + }; + out.append(s); + } + + // Slider, value 30 of 0..100. + for (const Variant &v : {Variant{QStringLiteral("normal"), QStyle::State_Horizontal}, Variant{QStringLiteral("handle_hover"), QStyle::State_Horizontal | hover}, + Variant{QStringLiteral("focus"), QStyle::State_Horizontal | focus}, Variant{QStringLiteral("disabled"), QStyle::State_Horizontal, QPalette::Disabled}}) { + QStyleOptionSlider probe; + c.base(probe, QRect(), v.state, v.group); + probe.orientation = Qt::Horizontal; + probe.minimum = 0; + probe.maximum = 100; + probe.sliderPosition = 30; + probe.sliderValue = 30; + const int thick = st->sizeFromContents(QStyle::CT_Slider, &probe, QSize(200, st->pixelMetric(QStyle::PM_SliderThickness, &probe, nullptr)), nullptr).height(); + probe.rect = QRect(0, 0, 200, thick); + probe.subControls = QStyle::SC_All; + const QRect handle = st->subControlRect(QStyle::CC_Slider, &probe, QStyle::SC_SliderHandle, nullptr); + Shot s; + s.id = QStringLiteral("slider.") + v.name; + s.control = QStringLiteral("CC_Slider"); + s.state = stateDesc(v); + s.state.insert(QStringLiteral("value"), 30); + s.state.insert(QStringLiteral("handle_rect"), rc(handle)); + s.size = QSize(200, thick); + s.group = v.group; + s.rows = {thick / 2}; + s.cols = {handle.center().x(), 10, 190}; + s.draw = [&c, st, v](QPainter &p, const QRect &r) { + QStyleOptionSlider opt; + c.base(opt, r, v.state, v.group); + opt.orientation = Qt::Horizontal; + opt.minimum = 0; + opt.maximum = 100; + opt.sliderPosition = 30; + opt.sliderValue = 30; + opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; + opt.activeSubControls = (v.state & QStyle::State_MouseOver) ? QStyle::SC_SliderHandle : QStyle::SC_None; + st->drawComplexControl(QStyle::CC_Slider, &opt, &p, nullptr); + }; + out.append(s); + } + + // Vertical scroll bar, value 0 of 0..100, page 20. + for (const Variant &v : {Variant{QStringLiteral("normal"), QStyle::State_None}, Variant{QStringLiteral("hover"), hover}, + Variant{QStringLiteral("slider_hover"), hover}, Variant{QStringLiteral("slider_pressed"), hover | sunken}}) { + QStyleOptionSlider probe; + c.base(probe, QRect(), v.state, v.group); + const int ext = st->pixelMetric(QStyle::PM_ScrollBarExtent, &probe, nullptr); + probe.rect = QRect(0, 0, ext, 200); + probe.orientation = Qt::Vertical; + probe.minimum = 0; + probe.maximum = 100; + probe.pageStep = 20; + probe.singleStep = 1; + probe.subControls = QStyle::SC_All; + const QRect slider = st->subControlRect(QStyle::CC_ScrollBar, &probe, QStyle::SC_ScrollBarSlider, nullptr); + const bool sliderActive = v.name.startsWith(QStringLiteral("slider")); + Shot s; + s.id = QStringLiteral("scroll_bar.") + v.name; + s.control = QStringLiteral("CC_ScrollBar"); + s.state = stateDesc(v); + s.state.insert(QStringLiteral("slider_rect"), rc(slider)); + s.state.insert(QStringLiteral("active_sub_control"), sliderActive ? QStringLiteral("SC_ScrollBarSlider") : QStringLiteral("none")); + s.size = QSize(ext, 200); + s.rows = {slider.center().y(), 150}; + s.cols = {0, ext / 2}; + s.draw = [&c, st, v, sliderActive](QPainter &p, const QRect &r) { + QStyleOptionSlider opt; + c.base(opt, r, v.state, v.group); + opt.orientation = Qt::Vertical; + opt.minimum = 0; + opt.maximum = 100; + opt.pageStep = 20; + opt.singleStep = 1; + opt.subControls = QStyle::SC_All; + opt.activeSubControls = sliderActive ? QStyle::SC_ScrollBarSlider : QStyle::SC_None; + st->drawComplexControl(QStyle::CC_ScrollBar, &opt, &p, nullptr); + }; + out.append(s); + } + + // Progress bar at 50 %, no text. + { + QStyleOptionProgressBar probe; + c.base(probe, QRect(), QStyle::State_Horizontal, QPalette::Active); + probe.minimum = 0; + probe.maximum = 100; + probe.progress = 50; + probe.textVisible = false; + const QSize hint = st->sizeFromContents(QStyle::CT_ProgressBar, &probe, QSize(200, probe.fontMetrics.height()), nullptr); + Shot s; + s.id = QStringLiteral("progress_bar.half"); + s.control = QStringLiteral("CE_ProgressBar"); + s.state = stateDesc({QStringLiteral("half"), QStyle::State_Horizontal}); + s.size = QSize(200, hint.height()); + s.rows = {hint.height() / 2}; + s.cols = {50, 150}; + s.draw = [&c, st](QPainter &p, const QRect &r) { + QStyleOptionProgressBar opt; + c.base(opt, r, QStyle::State_Horizontal, QPalette::Active); + opt.minimum = 0; + opt.maximum = 100; + opt.progress = 50; + opt.textVisible = false; + st->drawControl(QStyle::CE_ProgressBar, &opt, &p, nullptr); + }; + out.append(s); + } + + // Menu panel and menu item rows (empty text: the highlight is the subject). + { + Shot s; + s.id = QStringLiteral("menu.panel"); + s.control = QStringLiteral("PE_PanelMenu+PE_FrameMenu"); + s.state = stateDesc({QStringLiteral("normal"), QStyle::State_None}); + s.size = QSize(200, 100); + s.rows = {0, 1, 50}; + s.cols = {0, 1, 100}; + s.draw = [&c, st](QPainter &p, const QRect &r) { + QStyleOption opt; + c.base(opt, r, QStyle::State_None, QPalette::Active); + st->drawPrimitive(QStyle::PE_PanelMenu, &opt, &p, nullptr); + QStyleOptionFrame fr; + c.base(fr, r, QStyle::State_None, QPalette::Active); + fr.lineWidth = st->pixelMetric(QStyle::PM_MenuPanelWidth, &fr, nullptr); + st->drawPrimitive(QStyle::PE_FrameMenu, &fr, &p, nullptr); + }; + out.append(s); + } + for (const auto &[v, type] : QList>{ + {{QStringLiteral("normal"), QStyle::State_None}, QStyleOptionMenuItem::Normal}, + {{QStringLiteral("selected"), QStyle::State_Selected}, QStyleOptionMenuItem::Normal}, + {{QStringLiteral("separator"), QStyle::State_None}, QStyleOptionMenuItem::Separator}}) { + QStyleOptionMenuItem probe; + c.base(probe, QRect(), v.state, v.group); + probe.menuItemType = type; + probe.maxIconWidth = 16; + const QSize hint = st->sizeFromContents(QStyle::CT_MenuItem, &probe, QSize(0, probe.fontMetrics.height()), nullptr); + Shot s; + s.id = QStringLiteral("menu_item.") + v.name; + s.control = QStringLiteral("CE_MenuItem"); + s.state = stateDesc(v); + s.state.insert(QStringLiteral("type"), int(type)); + s.size = QSize(200, hint.height()); + s.background = QPalette::Window; + s.rows = {hint.height() / 2, 0, 1, 2, 3, 4, 5}; + s.cols = {100, 4, 5, 6}; + const Variant var = v; + const auto ty = type; + s.draw = [&c, st, var, ty](QPainter &p, const QRect &r) { + QStyleOptionMenuItem opt; + c.base(opt, r, var.state, var.group); + opt.menuItemType = ty; + opt.maxIconWidth = 16; + opt.menuRect = r; + st->drawControl(QStyle::CE_MenuItem, &opt, &p, nullptr); + }; + out.append(s); + } + + // Tab shapes. + for (const auto &[v, doc] : QList>{{{QStringLiteral("selected"), QStyle::State_Selected}, false}, + {{QStringLiteral("unselected"), QStyle::State_None}, false}, + {{QStringLiteral("hover"), hover}, false}, + {{QStringLiteral("selected.document_mode"), QStyle::State_Selected}, true}, + {{QStringLiteral("unselected.document_mode"), QStyle::State_None}, true}}) { + Shot s; + s.id = QStringLiteral("tab.") + v.name; + s.control = QStringLiteral("CE_TabBarTabShape"); + s.state = stateDesc(v); + s.state.insert(QStringLiteral("document_mode"), doc); + s.size = QSize(80, 30); + s.rows = {0, 1, 2, 3, 15, 27, 28, 29}; + s.cols = {0, 1, 40, 79}; + const Variant var = v; + const bool d = doc; + s.draw = [&c, st, var, d](QPainter &p, const QRect &r) { + QStyleOptionTab opt; + c.base(opt, r, var.state, var.group); + opt.shape = QTabBar::RoundedNorth; + opt.position = QStyleOptionTab::Middle; + opt.selectedPosition = QStyleOptionTab::NotAdjacent; + opt.documentMode = d; + st->drawControl(QStyle::CE_TabBarTabShape, &opt, &p, nullptr); + }; + out.append(s); + } + + // Header section. + for (const Variant &v : {Variant{QStringLiteral("normal"), QStyle::State_Raised}, Variant{QStringLiteral("hover"), QStyle::State_Raised | hover}, + Variant{QStringLiteral("sunken"), sunken}}) { + Shot s; + s.id = QStringLiteral("header.") + v.name; + s.control = QStringLiteral("CE_HeaderSection"); + s.state = stateDesc(v); + s.size = QSize(100, 30); + s.background = QPalette::Base; + s.rows = {0, 15, 28, 29}; + s.cols = {0, 50, 98, 99}; + s.draw = [&c, st, v](QPainter &p, const QRect &r) { + QStyleOptionHeader opt; + c.base(opt, r, v.state | QStyle::State_Horizontal, v.group); + opt.orientation = Qt::Horizontal; + opt.section = 0; + opt.position = QStyleOptionHeader::Middle; + st->drawControl(QStyle::CE_HeaderSection, &opt, &p, nullptr); + }; + out.append(s); + } + + // Item-view row backgrounds. + for (const Variant &v : {Variant{QStringLiteral("normal"), QStyle::State_None}, Variant{QStringLiteral("hover"), hover}, + Variant{QStringLiteral("selected"), QStyle::State_Selected}, Variant{QStringLiteral("selected_hover"), QStyle::State_Selected | hover}, + Variant{QStringLiteral("selected_inactive_window"), QStyle::State_Selected, QPalette::Inactive}, + Variant{QStringLiteral("focus"), focus}}) { + Shot s; + s.id = QStringLiteral("item_view_row.") + v.name; + s.control = QStringLiteral("PE_PanelItemViewItem"); + s.state = stateDesc(v); + s.size = QSize(200, 24); + s.background = QPalette::Base; + s.group = v.group; + s.rows = {0, 1, 2, 12, 22, 23}; + s.cols = {0, 1, 2, 3, 100}; + s.draw = [&c, st, v](QPainter &p, const QRect &r) { + QStyleOptionViewItem opt; + c.base(opt, r, v.state, v.group); + opt.features = QStyleOptionViewItem::HasDisplay; + opt.viewItemPosition = QStyleOptionViewItem::OnlyOne; + opt.showDecorationSelected = true; + st->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &p, nullptr); + }; + out.append(s); + } + + // Tooltip panel, generic frame, group box frame. + { + Shot s; + s.id = QStringLiteral("tooltip.panel"); + s.control = QStringLiteral("PE_PanelTipLabel"); + s.state = stateDesc({QStringLiteral("normal"), QStyle::State_None}); + s.size = QSize(100, 30); + s.background = QPalette::ToolTipBase; + s.rows = {0, 1, 15}; + s.cols = {0, 1, 50}; + s.draw = [&c, st](QPainter &p, const QRect &r) { + QStyleOptionFrame opt; + c.base(opt, r, QStyle::State_None, QPalette::Active); + st->drawPrimitive(QStyle::PE_PanelTipLabel, &opt, &p, nullptr); + }; + out.append(s); + } + { + Shot s; + s.id = QStringLiteral("frame.styled_panel"); + s.control = QStringLiteral("PE_Frame"); + s.state = stateDesc({QStringLiteral("sunken"), sunken}); + s.size = QSize(100, 50); + s.rows = {0, 1, 25}; + s.cols = {0, 1, 50}; + s.draw = [&c, st](QPainter &p, const QRect &r) { + QStyleOptionFrame opt; + c.base(opt, r, QStyle::State_Sunken, QPalette::Active); + opt.lineWidth = st->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, nullptr); + opt.frameShape = QFrame::StyledPanel; + st->drawPrimitive(QStyle::PE_Frame, &opt, &p, nullptr); + }; + out.append(s); + } + { + Shot s; + s.id = QStringLiteral("group_box.frame"); + s.control = QStringLiteral("PE_FrameGroupBox"); + s.state = stateDesc({QStringLiteral("normal"), QStyle::State_None}); + s.size = QSize(100, 60); + s.rows = {0, 1, 2, 30}; + s.cols = {0, 1, 2, 50}; + s.draw = [&c, st](QPainter &p, const QRect &r) { + QStyleOptionFrame opt; + c.base(opt, r, QStyle::State_None, QPalette::Active); + opt.lineWidth = 1; + st->drawPrimitive(QStyle::PE_FrameGroupBox, &opt, &p, nullptr); + }; + out.append(s); + } + return out; +} + +} // namespace + +int writePixels(const QString &path, const QString &schemeLabel) +{ + KSharedConfigPtr config = KSharedConfig::openConfig(); + const QPalette pal = KColorScheme::createApplicationPalette(config); + QApplication::setPalette(pal); + QFont font(QStringLiteral("Noto Sans")); + font.setPointSizeF(10); + QApplication::setFont(font); + + Ctx c{QApplication::style(), pal, font}; + QJsonArray records; + const QList all = shots(c); + for (const Shot &s : all) + records.append(runShot(s, pal)); + + QJsonObject env; + env.insert(QStringLiteral("scheme"), schemeLabel); + env.insert(QStringLiteral("kdeglobals_color_scheme"), config->group(QStringLiteral("General")).readEntry("ColorScheme", QString())); + env.insert(QStringLiteral("style"), c.style ? c.style->name() : QStringLiteral("none")); + env.insert(QStringLiteral("platform"), QGuiApplication::platformName()); + env.insert(QStringLiteral("image_format"), QStringLiteral("ARGB32, filled opaque with the palette background, then painted")); + + return golden::writeFile(path, + { + {QStringLiteral("meta"), golden::meta(QStringLiteral("pixels"))}, + {QStringLiteral("environment"), env}, + {QStringLiteral("records"), records, true}, + }) + ? 0 + : 1; +} diff --git a/tools/kde_golden/driver/resolve_vectors.cpp b/tools/kde_golden/driver/resolve_vectors.cpp new file mode 100644 index 000000000..02c953746 --- /dev/null +++ b/tools/kde_golden/driver/resolve_vectors.cpp @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech +// +// resolve.json: what KColorScheme and createApplicationPalette resolve for a +// set of input schemes. The installed schemes are read from the image at run +// time and never copied into the repository; the synthetic schemes are +// written below, in this file, and their full text is embedded in the output. + +#include "golden_json.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +struct Synthetic { + QString name; + QString purpose; + QString text; +}; + +// Hand-written inputs (MPL-2.0, original values). Each one isolates a rule. +const QList kSynthetic = { + {QStringLiteral("SyntheticNoHeader"), + QStringLiteral("no Colors:Header group and no ColorEffects groups: Header falls back, effects take their defaults"), + QStringLiteral("[General]\n" + "ColorScheme=SyntheticNoHeader\n" + "Name=Synthetic No Header\n" + "\n" + "[KDE]\n" + "contrast=7\n" + "\n" + "[Colors:View]\n" + "BackgroundNormal=250,248,240\n" + "BackgroundAlternate=240,236,226\n" + "ForegroundNormal=40,30,20\n" + "ForegroundInactive=120,110,100\n" + "ForegroundActive=200,90,10\n" + "ForegroundLink=10,90,200\n" + "ForegroundVisited=120,40,160\n" + "ForegroundNegative=190,20,40\n" + "ForegroundNeutral=200,140,0\n" + "ForegroundPositive=20,140,60\n" + "DecorationFocus=200,90,10\n" + "DecorationHover=230,130,50\n" + "\n" + "[Colors:Window]\n" + "BackgroundNormal=230,226,216\n" + "BackgroundAlternate=220,214,202\n" + "ForegroundNormal=30,25,20\n" + "ForegroundInactive=110,100,90\n" + "ForegroundActive=200,90,10\n" + "ForegroundLink=10,90,200\n" + "ForegroundVisited=120,40,160\n" + "ForegroundNegative=190,20,40\n" + "ForegroundNeutral=200,140,0\n" + "ForegroundPositive=20,140,60\n" + "DecorationFocus=200,90,10\n" + "DecorationHover=230,130,50\n" + "\n" + "[Colors:Button]\n" + "BackgroundNormal=236,232,224\n" + "BackgroundAlternate=210,204,190\n" + "ForegroundNormal=30,25,20\n" + "ForegroundInactive=110,100,90\n" + "ForegroundActive=200,90,10\n" + "ForegroundLink=10,90,200\n" + "ForegroundVisited=120,40,160\n" + "ForegroundNegative=190,20,40\n" + "ForegroundNeutral=200,140,0\n" + "ForegroundPositive=20,140,60\n" + "DecorationFocus=200,90,10\n" + "DecorationHover=230,130,50\n" + "\n" + "[Colors:Selection]\n" + "BackgroundNormal=200,90,10\n" + "BackgroundAlternate=170,80,20\n" + "ForegroundNormal=255,255,255\n" + "ForegroundInactive=240,220,200\n" + "ForegroundActive=255,240,200\n" + "ForegroundLink=255,230,160\n" + "ForegroundVisited=240,200,240\n" + "ForegroundNegative=255,180,180\n" + "ForegroundNeutral=255,230,120\n" + "ForegroundPositive=180,255,190\n" + "DecorationFocus=200,90,10\n" + "DecorationHover=230,130,50\n" + "\n" + "[Colors:Tooltip]\n" + "BackgroundNormal=255,250,220\n" + "BackgroundAlternate=245,238,200\n" + "ForegroundNormal=20,20,20\n" + "ForegroundInactive=100,100,90\n" + "ForegroundActive=200,90,10\n" + "ForegroundLink=10,90,200\n" + "ForegroundVisited=120,40,160\n" + "ForegroundNegative=190,20,40\n" + "ForegroundNeutral=200,140,0\n" + "ForegroundPositive=20,140,60\n" + "DecorationFocus=200,90,10\n" + "DecorationHover=230,130,50\n" + "\n" + "[Colors:Complementary]\n" + "BackgroundNormal=40,36,30\n" + "BackgroundAlternate=30,27,22\n" + "ForegroundNormal=240,235,225\n" + "ForegroundInactive=160,150,140\n" + "ForegroundActive=255,160,60\n" + "ForegroundLink=100,170,255\n" + "ForegroundVisited=200,140,230\n" + "ForegroundNegative=255,90,100\n" + "ForegroundNeutral=255,200,60\n" + "ForegroundPositive=90,220,130\n" + "DecorationFocus=255,160,60\n" + "DecorationHover=255,190,110\n")}, + + {QStringLiteral("SyntheticInactiveEffects"), + QStringLiteral("dark scheme, Header without an Inactive subgroup, Inactive effects enabled with the selection change; every effect kind with " + "non-zero amounts in both states"), + QStringLiteral("[General]\n" + "ColorScheme=SyntheticInactiveEffects\n" + "Name=Synthetic Inactive Effects\n" + "\n" + "[KDE]\n" + "contrast=2\n" + "frameContrast=0.35\n" + "\n" + "[ColorEffects:Inactive]\n" + "Enable=true\n" + "ChangeSelectionColor=true\n" + "IntensityEffect=1\n" + "IntensityAmount=0.25\n" + "ColorEffect=1\n" + "ColorAmount=0.3\n" + "Color=112,111,110\n" + "ContrastEffect=1\n" + "ContrastAmount=0.4\n" + "\n" + "[ColorEffects:Disabled]\n" + "IntensityEffect=3\n" + "IntensityAmount=0.2\n" + "ColorEffect=3\n" + "ColorAmount=0.5\n" + "Color=56,56,56\n" + "ContrastEffect=2\n" + "ContrastAmount=0.6\n" + "\n" + "[Colors:View]\n" + "BackgroundNormal=24,26,30\n" + "BackgroundAlternate=32,35,40\n" + "ForegroundNormal=220,224,230\n" + "ForegroundInactive=140,146,154\n" + "ForegroundActive=90,200,250\n" + "ForegroundLink=80,160,255\n" + "ForegroundVisited=170,120,230\n" + "ForegroundNegative=240,80,90\n" + "ForegroundNeutral=240,170,40\n" + "ForegroundPositive=60,200,110\n" + "DecorationFocus=90,200,250\n" + "DecorationHover=90,200,250\n" + "\n" + "[Colors:Window]\n" + "BackgroundNormal=36,39,44\n" + "BackgroundAlternate=30,33,38\n" + "ForegroundNormal=220,224,230\n" + "ForegroundInactive=140,146,154\n" + "ForegroundActive=90,200,250\n" + "ForegroundLink=80,160,255\n" + "ForegroundVisited=170,120,230\n" + "ForegroundNegative=240,80,90\n" + "ForegroundNeutral=240,170,40\n" + "ForegroundPositive=60,200,110\n" + "DecorationFocus=90,200,250\n" + "DecorationHover=90,200,250\n" + "\n" + "[Colors:Button]\n" + "BackgroundNormal=44,48,54\n" + "BackgroundAlternate=38,42,48\n" + "ForegroundNormal=220,224,230\n" + "ForegroundInactive=140,146,154\n" + "ForegroundActive=90,200,250\n" + "ForegroundLink=80,160,255\n" + "ForegroundVisited=170,120,230\n" + "ForegroundNegative=240,80,90\n" + "ForegroundNeutral=240,170,40\n" + "ForegroundPositive=60,200,110\n" + "DecorationFocus=90,200,250\n" + "DecorationHover=90,200,250\n" + "\n" + "[Colors:Selection]\n" + "BackgroundNormal=90,200,250\n" + "BackgroundAlternate=60,150,200\n" + "ForegroundNormal=10,10,10\n" + "ForegroundInactive=40,60,70\n" + "ForegroundActive=0,0,0\n" + "ForegroundLink=0,40,120\n" + "ForegroundVisited=70,20,110\n" + "ForegroundNegative=120,0,10\n" + "ForegroundNeutral=110,70,0\n" + "ForegroundPositive=0,90,30\n" + "DecorationFocus=90,200,250\n" + "DecorationHover=90,200,250\n" + "\n" + "[Colors:Tooltip]\n" + "BackgroundNormal=48,52,58\n" + "BackgroundAlternate=40,44,50\n" + "ForegroundNormal=230,232,236\n" + "ForegroundInactive=150,156,164\n" + "ForegroundActive=90,200,250\n" + "ForegroundLink=80,160,255\n" + "ForegroundVisited=170,120,230\n" + "ForegroundNegative=240,80,90\n" + "ForegroundNeutral=240,170,40\n" + "ForegroundPositive=60,200,110\n" + "DecorationFocus=90,200,250\n" + "DecorationHover=90,200,250\n" + "\n" + "[Colors:Complementary]\n" + "BackgroundNormal=16,18,20\n" + "BackgroundAlternate=12,13,15\n" + "ForegroundNormal=235,238,242\n" + "ForegroundInactive=150,156,164\n" + "ForegroundActive=90,200,250\n" + "ForegroundLink=80,160,255\n" + "ForegroundVisited=170,120,230\n" + "ForegroundNegative=240,80,90\n" + "ForegroundNeutral=240,170,40\n" + "ForegroundPositive=60,200,110\n" + "DecorationFocus=90,200,250\n" + "DecorationHover=90,200,250\n" + "\n" + "[Colors:Header]\n" + "BackgroundNormal=52,56,62\n" + "BackgroundAlternate=36,39,44\n" + "ForegroundNormal=225,228,234\n" + "ForegroundInactive=140,146,154\n" + "ForegroundActive=90,200,250\n" + "ForegroundLink=80,160,255\n" + "ForegroundVisited=170,120,230\n" + "ForegroundNegative=240,80,90\n" + "ForegroundNeutral=240,170,40\n" + "ForegroundPositive=60,200,110\n" + "DecorationFocus=90,200,250\n" + "DecorationHover=90,200,250\n")}, + + {QStringLiteral("SyntheticInactiveSubgroups"), + QStringLiteral("Inactive effects enabled without the selection change, plus explicit Inactive subgroups for Header, View and (partial) " + "Window; Disabled uses shade, desaturate and fade"), + QStringLiteral("[General]\n" + "ColorScheme=SyntheticInactiveSubgroups\n" + "Name=Synthetic Inactive Subgroups\n" + "\n" + "[KDE]\n" + "contrast=5\n" + "\n" + "[ColorEffects:Inactive]\n" + "Enable=true\n" + "ChangeSelectionColor=false\n" + "IntensityEffect=2\n" + "IntensityAmount=0.15\n" + "ColorEffect=2\n" + "ColorAmount=0.25\n" + "Color=200,0,0\n" + "ContrastEffect=2\n" + "ContrastAmount=0.3\n" + "\n" + "[ColorEffects:Disabled]\n" + "IntensityEffect=1\n" + "IntensityAmount=-0.5\n" + "ColorEffect=1\n" + "ColorAmount=0.8\n" + "ContrastEffect=1\n" + "ContrastAmount=0.5\n" + "\n" + "[Colors:View]\n" + "BackgroundNormal=255,255,255\n" + "BackgroundAlternate=245,247,250\n" + "ForegroundNormal=20,22,26\n" + "ForegroundInactive=110,116,124\n" + "ForegroundActive=0,120,215\n" + "ForegroundLink=0,100,200\n" + "ForegroundVisited=110,60,180\n" + "ForegroundNegative=200,30,40\n" + "ForegroundNeutral=190,120,0\n" + "ForegroundPositive=20,130,50\n" + "DecorationFocus=0,120,215\n" + "DecorationHover=60,150,230\n" + "\n" + "[Colors:View][Inactive]\n" + "BackgroundNormal=250,250,250\n" + "ForegroundNormal=60,62,66\n" + "\n" + "[Colors:Window]\n" + "BackgroundNormal=240,241,243\n" + "BackgroundAlternate=228,230,233\n" + "ForegroundNormal=20,22,26\n" + "ForegroundInactive=110,116,124\n" + "ForegroundActive=0,120,215\n" + "ForegroundLink=0,100,200\n" + "ForegroundVisited=110,60,180\n" + "ForegroundNegative=200,30,40\n" + "ForegroundNeutral=190,120,0\n" + "ForegroundPositive=20,130,50\n" + "DecorationFocus=0,120,215\n" + "DecorationHover=60,150,230\n" + "\n" + "[Colors:Window][Inactive]\n" + "BackgroundNormal=232,233,235\n" + "\n" + "[Colors:Button]\n" + "BackgroundNormal=248,249,250\n" + "BackgroundAlternate=220,222,226\n" + "ForegroundNormal=20,22,26\n" + "ForegroundInactive=110,116,124\n" + "ForegroundActive=0,120,215\n" + "ForegroundLink=0,100,200\n" + "ForegroundVisited=110,60,180\n" + "ForegroundNegative=200,30,40\n" + "ForegroundNeutral=190,120,0\n" + "ForegroundPositive=20,130,50\n" + "DecorationFocus=0,120,215\n" + "DecorationHover=60,150,230\n" + "\n" + "[Colors:Selection]\n" + "BackgroundNormal=0,120,215\n" + "BackgroundAlternate=0,100,180\n" + "ForegroundNormal=255,255,255\n" + "ForegroundInactive=200,220,240\n" + "ForegroundActive=255,255,255\n" + "ForegroundLink=220,240,255\n" + "ForegroundVisited=230,210,255\n" + "ForegroundNegative=255,200,200\n" + "ForegroundNeutral=255,230,170\n" + "ForegroundPositive=200,255,210\n" + "DecorationFocus=0,120,215\n" + "DecorationHover=60,150,230\n" + "\n" + "[Colors:Tooltip]\n" + "BackgroundNormal=250,250,250\n" + "BackgroundAlternate=240,240,240\n" + "ForegroundNormal=20,22,26\n" + "ForegroundInactive=110,116,124\n" + "ForegroundActive=0,120,215\n" + "ForegroundLink=0,100,200\n" + "ForegroundVisited=110,60,180\n" + "ForegroundNegative=200,30,40\n" + "ForegroundNeutral=190,120,0\n" + "ForegroundPositive=20,130,50\n" + "DecorationFocus=0,120,215\n" + "DecorationHover=60,150,230\n" + "\n" + "[Colors:Complementary]\n" + "BackgroundNormal=30,32,36\n" + "BackgroundAlternate=24,26,30\n" + "ForegroundNormal=240,242,245\n" + "ForegroundInactive=150,156,164\n" + "ForegroundActive=80,170,255\n" + "ForegroundLink=80,170,255\n" + "ForegroundVisited=180,140,240\n" + "ForegroundNegative=255,90,100\n" + "ForegroundNeutral=255,190,60\n" + "ForegroundPositive=80,210,120\n" + "DecorationFocus=80,170,255\n" + "DecorationHover=110,190,255\n" + "\n" + "[Colors:Header]\n" + "BackgroundNormal=226,228,232\n" + "BackgroundAlternate=240,241,243\n" + "ForegroundNormal=20,22,26\n" + "ForegroundInactive=110,116,124\n" + "ForegroundActive=0,120,215\n" + "ForegroundLink=0,100,200\n" + "ForegroundVisited=110,60,180\n" + "ForegroundNegative=200,30,40\n" + "ForegroundNeutral=190,120,0\n" + "ForegroundPositive=20,130,50\n" + "DecorationFocus=0,120,215\n" + "DecorationHover=60,150,230\n" + "\n" + "[Colors:Header][Inactive]\n" + "BackgroundNormal=240,241,243\n" + "BackgroundAlternate=226,228,232\n" + "ForegroundNormal=90,94,100\n" + "ForegroundInactive=130,136,144\n" + "ForegroundActive=0,120,215\n" + "ForegroundLink=0,100,200\n" + "ForegroundVisited=110,60,180\n" + "ForegroundNegative=200,30,40\n" + "ForegroundNeutral=190,120,0\n" + "ForegroundPositive=20,130,50\n" + "DecorationFocus=0,120,215\n" + "DecorationHover=60,150,230\n")}, + + {QStringLiteral("SyntheticAccentKeys"), + QStringLiteral("a light scheme carrying the [General] accent keys (AccentColor, TintFactor, TitlebarIsAccentColored) and a [WM] group; " + "shows which of them KColorScheme itself reads"), + QStringLiteral("[General]\n" + "ColorScheme=SyntheticAccentKeys\n" + "Name=Synthetic Accent Keys\n" + "AccentColor=233,58,154\n" + "TintFactor=0.3\n" + "TitlebarIsAccentColored=true\n" + "\n" + "[KDE]\n" + "contrast=4\n" + "\n" + "[WM]\n" + "activeBackground=200,40,120\n" + "activeForeground=255,255,255\n" + "inactiveBackground=240,230,236\n" + "inactiveForeground=120,100,110\n" + "\n" + "[Colors:View]\n" + "BackgroundNormal=252,252,252\n" + "ForegroundNormal=35,38,41\n" + "DecorationFocus=61,174,233\n" + "\n" + "[Colors:Window]\n" + "BackgroundNormal=239,240,241\n" + "ForegroundNormal=35,38,41\n" + "\n" + "[Colors:Selection]\n" + "BackgroundNormal=61,174,233\n" + "ForegroundNormal=255,255,255\n")}, + + {QStringLiteral("SyntheticEmpty"), + QStringLiteral("an empty file: every value is KColorScheme's built-in default"), + QString()}, + + {QStringLiteral("SyntheticMalformed"), + QStringLiteral("value syntax edge cases (hex, alpha, spaces, named colour, too few components, empty value), an out-of-range " + "contrast and effect id, a duplicated key and a duplicated group"), + QStringLiteral("[General]\n" + "ColorScheme=SyntheticMalformed\n" + "Name=Synthetic Malformed\n" + "\n" + "[KDE]\n" + "contrast=15\n" + "\n" + "[ColorEffects:Disabled]\n" + "IntensityEffect=7\n" + "IntensityAmount=0.4\n" + "ColorEffect=0\n" + "ContrastEffect=1\n" + "ContrastAmount=2.5\n" + "\n" + "[Colors:View]\n" + "BackgroundNormal=#ff8800\n" + "BackgroundAlternate=10,20,30,128\n" + "ForegroundNormal= 12 , 34 , 56 \n" + "ForegroundInactive=300,20\n" + "ForegroundActive=red\n" + "ForegroundLink=\n" + "ForegroundVisited=256,-1,1000\n" + "ForegroundPositive=0,100,0\n" + "ForegroundPositive=0,200,0\n" + "\n" + "[Colors:Window]\n" + "BackgroundNormal=100,100,100\n" + "ForegroundNormal=1,2,3\n" + "\n" + "[Colors:Window]\n" + "BackgroundNormal=200,200,200\n" + "\n" + "[Colors:Selection]\n" + "BackgroundNormal=0,0,0\n")}, + + {QStringLiteral("SyntheticValueForms"), + QStringLiteral("more value forms: an empty boolean (Inactive effects Enable=), a signed component, the 3-, 9- and 12-digit hex " + "forms, colour names in other cases, transparent, a trailing comma, a hex component and an unknown CSS name"), + QStringLiteral("[General]\n" + "ColorScheme=SyntheticValueForms\n" + "Name=Synthetic Value Forms\n" + "\n" + "[ColorEffects:Inactive]\n" + "Enable=\n" + "ChangeSelectionColor=off\n" + "\n" + "[Colors:View]\n" + "BackgroundNormal=+250,240,230\n" + "BackgroundAlternate=#abc\n" + "ForegroundNormal=#123456789\n" + "ForegroundInactive=#123456789abc\n" + "ForegroundActive=Red\n" + "ForegroundLink=grey\n" + "ForegroundVisited=rebeccapurple\n" + "ForegroundNegative=10,20,30,\n" + "ForegroundNeutral=0x10,20,30\n" + "ForegroundPositive=transparent\n" + "DecorationFocus=#803daee9\n" + "\n" + "[Colors:Window]\n" + "BackgroundNormal=200,190,180\n")}, +}; + +const QList> kSets = { + {KColorScheme::View, QStringLiteral("View")}, + {KColorScheme::Window, QStringLiteral("Window")}, + {KColorScheme::Button, QStringLiteral("Button")}, + {KColorScheme::Selection, QStringLiteral("Selection")}, + {KColorScheme::Tooltip, QStringLiteral("Tooltip")}, + {KColorScheme::Complementary, QStringLiteral("Complementary")}, + {KColorScheme::Header, QStringLiteral("Header")}, +}; + +const QList> kStates = { + {QPalette::Active, QStringLiteral("Active")}, + {QPalette::Inactive, QStringLiteral("Inactive")}, + {QPalette::Disabled, QStringLiteral("Disabled")}, +}; + +const QList> kBg = { + {KColorScheme::NormalBackground, QStringLiteral("Normal")}, + {KColorScheme::AlternateBackground, QStringLiteral("Alternate")}, + {KColorScheme::ActiveBackground, QStringLiteral("Active")}, + {KColorScheme::LinkBackground, QStringLiteral("Link")}, + {KColorScheme::VisitedBackground, QStringLiteral("Visited")}, + {KColorScheme::NegativeBackground, QStringLiteral("Negative")}, + {KColorScheme::NeutralBackground, QStringLiteral("Neutral")}, + {KColorScheme::PositiveBackground, QStringLiteral("Positive")}, +}; + +const QList> kFg = { + {KColorScheme::NormalText, QStringLiteral("Normal")}, + {KColorScheme::InactiveText, QStringLiteral("Inactive")}, + {KColorScheme::ActiveText, QStringLiteral("Active")}, + {KColorScheme::LinkText, QStringLiteral("Link")}, + {KColorScheme::VisitedText, QStringLiteral("Visited")}, + {KColorScheme::NegativeText, QStringLiteral("Negative")}, + {KColorScheme::NeutralText, QStringLiteral("Neutral")}, + {KColorScheme::PositiveText, QStringLiteral("Positive")}, +}; + +const QList> kDeco = { + {KColorScheme::FocusColor, QStringLiteral("Focus")}, + {KColorScheme::HoverColor, QStringLiteral("Hover")}, +}; + +const QList> kShade = { + {KColorScheme::LightShade, QStringLiteral("Light")}, + {KColorScheme::MidlightShade, QStringLiteral("Midlight")}, + {KColorScheme::MidShade, QStringLiteral("Mid")}, + {KColorScheme::DarkShade, QStringLiteral("Dark")}, + {KColorScheme::ShadowShade, QStringLiteral("Shadow")}, +}; + +void collectGroup(const KConfigGroup &g, const QString &path, QJsonObject &out) +{ + const QMap map = g.entryMap(); + QJsonObject entries; + for (auto it = map.cbegin(); it != map.cend(); ++it) + entries.insert(it.key(), it.value()); + if (!entries.isEmpty()) + out.insert(path, entries); + const QStringList subs = g.groupList(); + for (const QString &sub : subs) + collectGroup(g.group(sub), path + QStringLiteral("][") + sub, out); +} + +QJsonObject parsedInput(const KSharedConfigPtr &config) +{ + QJsonObject out; + const QStringList tops = config->groupList(); + for (const QString &top : tops) + collectGroup(config->group(top), top, out); + return out; +} + +QJsonObject resolveScheme(const KSharedConfigPtr &config) +{ + QJsonObject sets; + for (const auto &set : kSets) { + QJsonObject states; + for (const auto &state : kStates) { + const KColorScheme cs(state.first, set.first, config); + QJsonObject bg, fg, deco, shade; + for (const auto &r : kBg) + bg.insert(r.second, golden::color(cs.background(r.first).color())); + for (const auto &r : kFg) + fg.insert(r.second, golden::color(cs.foreground(r.first).color())); + for (const auto &r : kDeco) + deco.insert(r.second, golden::color(cs.decoration(r.first).color())); + for (const auto &r : kShade) + shade.insert(r.second, golden::color(cs.shade(r.first))); + QJsonObject st; + st.insert(QStringLiteral("background"), bg); + st.insert(QStringLiteral("foreground"), fg); + st.insert(QStringLiteral("decoration"), deco); + st.insert(QStringLiteral("shade"), shade); + states.insert(state.second, st); + } + sets.insert(set.second, states); + } + return sets; +} + +QJsonObject paletteOf(const KSharedConfigPtr &config) +{ + const QPalette pal = KColorScheme::createApplicationPalette(config); + const QMetaEnum roles = QMetaEnum::fromType(); + QJsonObject out; + for (const auto &state : kStates) { + QJsonObject g; + for (int i = 0; i < roles.keyCount(); ++i) { + const int v = roles.value(i); + if (v < 0 || v >= QPalette::NColorRoles || v == QPalette::NoRole) + continue; + g.insert(QString::fromLatin1(roles.key(i)), golden::color(pal.color(state.first, QPalette::ColorRole(v)))); + } + out.insert(state.second, g); + } + return out; +} + +QJsonObject schemeRecord(const QString &name, const QString &file, const KSharedConfigPtr &config) +{ + QJsonObject o; + o.insert(QStringLiteral("name"), name); + o.insert(QStringLiteral("input"), parsedInput(config)); + o.insert(QStringLiteral("contrastF"), KColorScheme::contrastF(config)); + o.insert(QStringLiteral("frameContrast"), KColorScheme::frameContrast(config)); + QJsonObject supported; + for (const auto &set : kSets) + supported.insert(set.second, KColorScheme::isColorSetSupported(config, set.first)); + o.insert(QStringLiteral("set_supported"), supported); + o.insert(QStringLiteral("sets"), resolveScheme(config)); + o.insert(QStringLiteral("palette"), paletteOf(config)); + QFile f(file); + if (f.open(QIODevice::ReadOnly)) + o.insert(QStringLiteral("sha256"), QString::fromLatin1(QCryptographicHash::hash(f.readAll(), QCryptographicHash::Sha256).toHex())); + return o; +} + +} // namespace + +int writeResolve(const QString &path) +{ + QJsonArray schemes; + + // Installed schemes, in a fixed order; a missing one is reported, not fatal. + const QStringList installed = {QStringLiteral("BreezeLight"), + QStringLiteral("BreezeDark"), + QStringLiteral("BreezeClassic"), + QStringLiteral("Oxygen"), + QStringLiteral("OxygenCold"), + QStringLiteral("OxygenDark")}; + QJsonArray missing; + for (const QString &name : installed) { + const QString file = QStringLiteral("/usr/share/color-schemes/%1.colors").arg(name); + if (!QFile::exists(file)) { + missing.append(name); + continue; + } + KSharedConfigPtr config = KSharedConfig::openConfig(file, KConfig::SimpleConfig); + QJsonObject rec = schemeRecord(name, file, config); + rec.insert(QStringLiteral("origin"), QStringLiteral("installed")); + rec.insert(QStringLiteral("source"), file); + schemes.append(rec); + } + + QTemporaryDir dir; + if (!dir.isValid()) { + std::fprintf(stderr, "no temporary directory\n"); + return 1; + } + for (const Synthetic &s : kSynthetic) { + const QString file = dir.filePath(s.name + QStringLiteral(".colors")); + QFile f(file); + if (!f.open(QIODevice::WriteOnly)) { + std::fprintf(stderr, "cannot write %s\n", qPrintable(file)); + return 1; + } + f.write(s.text.toUtf8()); + f.close(); + KSharedConfigPtr config = KSharedConfig::openConfig(file, KConfig::SimpleConfig); + QJsonObject rec = schemeRecord(s.name, file, config); + rec.insert(QStringLiteral("origin"), QStringLiteral("synthetic")); + rec.insert(QStringLiteral("purpose"), s.purpose); + rec.insert(QStringLiteral("source_text"), s.text); + schemes.append(rec); + } + + return golden::writeFile(path, + { + {QStringLiteral("meta"), golden::meta(QStringLiteral("resolve"))}, + {QStringLiteral("missing_installed"), missing}, + {QStringLiteral("schemes"), schemes, true}, + }) + ? 0 + : 1; +} diff --git a/tools/kde_golden/driver/size_vectors.cpp b/tools/kde_golden/driver/size_vectors.cpp new file mode 100644 index 000000000..db8dec59e --- /dev/null +++ b/tools/kde_golden/driver/size_vectors.cpp @@ -0,0 +1,1082 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 FernTech +// +// sizes.json: what the Breeze QStyle reports, through QStyle and through the +// widgets' own sizeHint, under QT_QPA_PLATFORM=offscreen. Every widget is +// created hidden, polished with the Breeze style, measured, then destroyed. + +#include "golden_json.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +// Every measured widget except a QMenu is given this parent, so the style +// sees an ordinary child widget rather than a top-level window (Breeze, for +// one, answers the layout margins differently for a window). +QWidget *gHost = nullptr; + +void adopt(QWidget *w) +{ + if (gHost && !w->parentWidget() && !w->inherits("QMenu")) + w->setParent(gHost); +} + +QJsonArray sz(const QSize &s) +{ + return QJsonArray{s.width(), s.height()}; +} + +QJsonArray rc(const QRect &r) +{ + return QJsonArray{r.x(), r.y(), r.width(), r.height()}; +} + +QIcon testIcon() +{ + QPixmap pm(64, 64); + pm.fill(QColor(61, 174, 233)); + return QIcon(pm); +} + +QJsonObject fontRecord(const QFont &font) +{ + const QFontInfo info(font); + const QFontMetrics fm(font); + const QFontMetricsF fmf(font); + QJsonObject o; + o.insert(QStringLiteral("requested_family"), font.family()); + o.insert(QStringLiteral("requested_point_size"), font.pointSizeF()); + o.insert(QStringLiteral("resolved_family"), info.family()); + o.insert(QStringLiteral("resolved_style"), info.styleName()); + o.insert(QStringLiteral("resolved_point_size"), info.pointSizeF()); + o.insert(QStringLiteral("resolved_pixel_size"), info.pixelSize()); + o.insert(QStringLiteral("exact_match"), info.exactMatch()); + o.insert(QStringLiteral("to_string"), font.toString()); + QJsonObject i; + i.insert(QStringLiteral("height"), fm.height()); + i.insert(QStringLiteral("ascent"), fm.ascent()); + i.insert(QStringLiteral("descent"), fm.descent()); + i.insert(QStringLiteral("leading"), fm.leading()); + i.insert(QStringLiteral("line_spacing"), fm.lineSpacing()); + i.insert(QStringLiteral("x_height"), fm.xHeight()); + i.insert(QStringLiteral("cap_height"), fm.capHeight()); + i.insert(QStringLiteral("average_char_width"), fm.averageCharWidth()); + i.insert(QStringLiteral("max_width"), fm.maxWidth()); + o.insert(QStringLiteral("metrics"), i); + QJsonObject f; + f.insert(QStringLiteral("height"), fmf.height()); + f.insert(QStringLiteral("ascent"), fmf.ascent()); + f.insert(QStringLiteral("descent"), fmf.descent()); + f.insert(QStringLiteral("leading"), fmf.leading()); + f.insert(QStringLiteral("line_spacing"), fmf.lineSpacing()); + f.insert(QStringLiteral("x_height"), fmf.xHeight()); + f.insert(QStringLiteral("cap_height"), fmf.capHeight()); + f.insert(QStringLiteral("average_char_width"), fmf.averageCharWidth()); + f.insert(QStringLiteral("max_width"), fmf.maxWidth()); + o.insert(QStringLiteral("metrics_f"), f); + QJsonObject adv; + for (const QString &s : {QStringLiteral("M"), QStringLiteral("x"), QStringLiteral("0"), QStringLiteral("OK"), + QStringLiteral("Cancel"), QStringLiteral("The quick brown fox")}) { + adv.insert(s, QJsonArray{fm.horizontalAdvance(s), fmf.horizontalAdvance(s)}); + } + o.insert(QStringLiteral("advance"), adv); + return o; +} + +struct Recorder { + QJsonArray out; + + void add(const QString &id, QWidget *w, const QJsonObject &config = {}, const QJsonObject &extra = {}) + { + adopt(w); + w->ensurePolished(); + QJsonObject o; + o.insert(QStringLiteral("id"), id); + o.insert(QStringLiteral("class"), QString::fromLatin1(w->metaObject()->className())); + if (!config.isEmpty()) + o.insert(QStringLiteral("config"), config); + o.insert(QStringLiteral("size_hint"), sz(w->sizeHint())); + o.insert(QStringLiteral("minimum_size_hint"), sz(w->minimumSizeHint())); + for (auto it = extra.begin(); it != extra.end(); ++it) + o.insert(it.key(), it.value()); + out.append(o); + } +}; + +QJsonObject cfg(std::initializer_list> kv) +{ + QJsonObject o; + for (const auto &p : kv) + o.insert(p.first, p.second); + return o; +} + +QStandardItemModel *rowsModel(QObject *parent, bool icon, bool check) +{ + auto *m = new QStandardItemModel(parent); + m->setHorizontalHeaderLabels({QStringLiteral("Name"), QStringLiteral("Size")}); + for (const QString &t : {QStringLiteral("Row one"), QStringLiteral("Row two")}) { + auto *a = new QStandardItem(t); + if (icon) + a->setIcon(testIcon()); + if (check) { + a->setCheckable(true); + a->setCheckState(Qt::Checked); + } + m->appendRow({a, new QStandardItem(QStringLiteral("12 KiB"))}); + } + return m; +} + +QJsonArray widgetsFor() +{ + Recorder r; + const QIcon icon = testIcon(); + + { + QLabel w(QStringLiteral("Label")); + r.add(QStringLiteral("label"), &w, cfg({{QStringLiteral("text"), QStringLiteral("Label")}})); + } + for (const QString &t : {QStringLiteral("OK"), QStringLiteral("Cancel"), QStringLiteral("Apply changes")}) { + QPushButton w(t); + r.add(QStringLiteral("push_button"), &w, cfg({{QStringLiteral("text"), t}})); + } + { + QPushButton w(icon, QStringLiteral("Save")); + r.add(QStringLiteral("push_button.icon"), &w, cfg({{QStringLiteral("text"), QStringLiteral("Save")}, {QStringLiteral("icon"), true}})); + } + { + QPushButton w(QStringLiteral("OK")); + w.setDefault(true); + r.add(QStringLiteral("push_button.default"), &w, cfg({{QStringLiteral("text"), QStringLiteral("OK")}, {QStringLiteral("default"), true}})); + } + { + QPushButton w(QStringLiteral("OK")); + w.setFlat(true); + r.add(QStringLiteral("push_button.flat"), &w, cfg({{QStringLiteral("text"), QStringLiteral("OK")}, {QStringLiteral("flat"), true}})); + } + { + QPushButton w; + w.setIcon(icon); + r.add(QStringLiteral("push_button.icon_only"), &w, cfg({{QStringLiteral("icon"), true}})); + } + + struct Tb { + QString id; + bool autoRaise; + int iconSize; + Qt::ToolButtonStyle style; + QToolButton::ToolButtonPopupMode popup; + bool menu; + }; + const QList tbs = { + {QStringLiteral("tool_button.auto_raise.16"), true, 16, Qt::ToolButtonIconOnly, QToolButton::DelayedPopup, false}, + {QStringLiteral("tool_button.auto_raise.22"), true, 22, Qt::ToolButtonIconOnly, QToolButton::DelayedPopup, false}, + {QStringLiteral("tool_button.framed.16"), false, 16, Qt::ToolButtonIconOnly, QToolButton::DelayedPopup, false}, + {QStringLiteral("tool_button.framed.22"), false, 22, Qt::ToolButtonIconOnly, QToolButton::DelayedPopup, false}, + {QStringLiteral("tool_button.text_beside_icon.16"), true, 16, Qt::ToolButtonTextBesideIcon, QToolButton::DelayedPopup, false}, + {QStringLiteral("tool_button.text_under_icon.22"), true, 22, Qt::ToolButtonTextUnderIcon, QToolButton::DelayedPopup, false}, + {QStringLiteral("tool_button.menu_button_popup.16"), true, 16, Qt::ToolButtonIconOnly, QToolButton::MenuButtonPopup, true}, + {QStringLiteral("tool_button.instant_popup.16"), true, 16, Qt::ToolButtonIconOnly, QToolButton::InstantPopup, true}, + }; + for (const Tb &t : tbs) { + QToolButton w; + QMenu menu; + w.setIcon(icon); + w.setText(QStringLiteral("Tool")); + w.setAutoRaise(t.autoRaise); + w.setIconSize(QSize(t.iconSize, t.iconSize)); + w.setToolButtonStyle(t.style); + if (t.menu) { + menu.addAction(QStringLiteral("Entry")); + w.setMenu(&menu); + w.setPopupMode(t.popup); + } + r.add(t.id, &w, + cfg({{QStringLiteral("auto_raise"), t.autoRaise}, + {QStringLiteral("icon_size"), t.iconSize}, + {QStringLiteral("text"), QStringLiteral("Tool")}, + {QStringLiteral("style"), int(t.style)}, + {QStringLiteral("popup_mode"), t.menu ? int(t.popup) : -1}})); + } + + { + QLineEdit w; + r.add(QStringLiteral("line_edit"), &w); + } + { + QLineEdit w(QStringLiteral("Text")); + w.setClearButtonEnabled(true); + r.add(QStringLiteral("line_edit.clear_button"), &w, cfg({{QStringLiteral("clear_button"), true}})); + } + { + QLineEdit w; + w.setFrame(false); + r.add(QStringLiteral("line_edit.frameless"), &w, cfg({{QStringLiteral("frame"), false}})); + } + { + QComboBox w; + w.addItems({QStringLiteral("Item"), QStringLiteral("Longer item text")}); + r.add(QStringLiteral("combo_box"), &w, cfg({{QStringLiteral("items"), QJsonArray{QStringLiteral("Item"), QStringLiteral("Longer item text")}}})); + } + { + QComboBox w; + w.setEditable(true); + w.addItems({QStringLiteral("Item"), QStringLiteral("Longer item text")}); + r.add(QStringLiteral("combo_box.editable"), &w, cfg({{QStringLiteral("editable"), true}})); + } + { + QComboBox w; + w.addItem(icon, QStringLiteral("Item")); + r.add(QStringLiteral("combo_box.icon"), &w, cfg({{QStringLiteral("icon"), true}})); + } + { + QSpinBox w; + w.setRange(0, 99); + r.add(QStringLiteral("spin_box.0_99"), &w, cfg({{QStringLiteral("range"), QJsonArray{0, 99}}})); + } + { + QSpinBox w; + w.setRange(0, 9999); + r.add(QStringLiteral("spin_box.0_9999"), &w, cfg({{QStringLiteral("range"), QJsonArray{0, 9999}}})); + } + { + QDoubleSpinBox w; + w.setRange(0, 100); + w.setDecimals(2); + r.add(QStringLiteral("double_spin_box"), &w, cfg({{QStringLiteral("range"), QJsonArray{0, 100}}, {QStringLiteral("decimals"), 2}})); + } + { + QSpinBox w; + w.setRange(0, 99); + w.setButtonSymbols(QAbstractSpinBox::NoButtons); + r.add(QStringLiteral("spin_box.no_buttons"), &w, cfg({{QStringLiteral("range"), QJsonArray{0, 99}}, {QStringLiteral("buttons"), false}})); + } + { + QCheckBox w(QStringLiteral("Check")); + r.add(QStringLiteral("check_box"), &w, cfg({{QStringLiteral("text"), QStringLiteral("Check")}})); + } + { + QCheckBox w; + r.add(QStringLiteral("check_box.no_text"), &w); + } + { + QRadioButton w(QStringLiteral("Radio")); + r.add(QStringLiteral("radio_button"), &w, cfg({{QStringLiteral("text"), QStringLiteral("Radio")}})); + } + { + QRadioButton w; + r.add(QStringLiteral("radio_button.no_text"), &w); + } + + struct Sl { + QString id; + Qt::Orientation o; + QSlider::TickPosition t; + }; + for (const Sl &s : {Sl{QStringLiteral("slider.horizontal"), Qt::Horizontal, QSlider::NoTicks}, + Sl{QStringLiteral("slider.horizontal.ticks_below"), Qt::Horizontal, QSlider::TicksBelow}, + Sl{QStringLiteral("slider.horizontal.ticks_both"), Qt::Horizontal, QSlider::TicksBothSides}, + Sl{QStringLiteral("slider.vertical"), Qt::Vertical, QSlider::NoTicks}}) { + QSlider w(s.o); + w.setTickPosition(s.t); + r.add(s.id, &w, cfg({{QStringLiteral("orientation"), int(s.o)}, {QStringLiteral("ticks"), int(s.t)}})); + } + for (Qt::Orientation o : {Qt::Horizontal, Qt::Vertical}) { + QScrollBar w(o); + r.add(o == Qt::Horizontal ? QStringLiteral("scroll_bar.horizontal") : QStringLiteral("scroll_bar.vertical"), &w); + } + { + QProgressBar w; + w.setValue(50); + r.add(QStringLiteral("progress_bar"), &w, cfg({{QStringLiteral("text_visible"), true}})); + } + { + QProgressBar w; + w.setValue(50); + w.setTextVisible(false); + r.add(QStringLiteral("progress_bar.no_text"), &w, cfg({{QStringLiteral("text_visible"), false}})); + } + { + QProgressBar w; + w.setOrientation(Qt::Vertical); + w.setValue(50); + r.add(QStringLiteral("progress_bar.vertical"), &w, cfg({{QStringLiteral("orientation"), int(Qt::Vertical)}})); + } + + // Menus: action rows, read back through actionGeometry. + for (bool icons : {true, false}) { + QMenu menu; + QMenu sub(QStringLiteral("Submenu")); + sub.addAction(QStringLiteral("Inner")); + QAction *a = menu.addAction(QStringLiteral("Open")); + if (icons) + a->setIcon(icon); + QAction *b = menu.addAction(QStringLiteral("Save")); + b->setShortcut(QKeySequence(QStringLiteral("Ctrl+S"))); + menu.addSeparator(); + menu.addSection(QStringLiteral("Section")); + QAction *c = menu.addAction(QStringLiteral("Checkable")); + c->setCheckable(true); + c->setChecked(true); + menu.addMenu(&sub); + adopt(&menu); + menu.ensurePolished(); + QJsonArray rows; + const QList actions = menu.actions(); + for (QAction *act : actions) { + QJsonObject row; + QString kind = QStringLiteral("item"); + if (act->isSeparator()) + kind = act->text().isEmpty() ? QStringLiteral("separator") : QStringLiteral("section"); + else if (act->menu()) + kind = QStringLiteral("submenu"); + else if (act->isCheckable()) + kind = QStringLiteral("checkable"); + else if (!act->shortcut().isEmpty()) + kind = QStringLiteral("shortcut"); + row.insert(QStringLiteral("kind"), kind); + row.insert(QStringLiteral("text"), act->text()); + row.insert(QStringLiteral("icon"), !act->icon().isNull()); + row.insert(QStringLiteral("geometry"), rc(menu.actionGeometry(act))); + rows.append(row); + } + r.add(icons ? QStringLiteral("menu.with_icon") : QStringLiteral("menu.no_icons"), &menu, cfg({{QStringLiteral("icons"), icons}}), + cfg({{QStringLiteral("rows"), rows}})); + } + + // Tab bars. + struct Tab { + QString id; + bool document; + bool closable; + QTabBar::Shape shape; + }; + for (const Tab &t : {Tab{QStringLiteral("tab_bar"), false, false, QTabBar::RoundedNorth}, + Tab{QStringLiteral("tab_bar.document_mode"), true, false, QTabBar::RoundedNorth}, + Tab{QStringLiteral("tab_bar.closable"), false, true, QTabBar::RoundedNorth}, + Tab{QStringLiteral("tab_bar.west"), false, false, QTabBar::RoundedWest}, + Tab{QStringLiteral("tab_bar.south"), false, false, QTabBar::RoundedSouth}}) { + QTabBar w; + w.setDocumentMode(t.document); + w.setTabsClosable(t.closable); + w.setShape(t.shape); + w.setExpanding(false); + for (const QString &s : {QStringLiteral("Tab"), QStringLiteral("Second tab"), QStringLiteral("Third")}) + w.addTab(s); + adopt(&w); + w.ensurePolished(); + w.resize(w.sizeHint()); + QJsonArray tabs; + for (int i = 0; i < w.count(); ++i) + tabs.append(rc(w.tabRect(i))); + r.add(t.id, &w, + cfg({{QStringLiteral("document_mode"), t.document}, {QStringLiteral("closable"), t.closable}, {QStringLiteral("shape"), int(t.shape)}, + {QStringLiteral("tabs"), QJsonArray{QStringLiteral("Tab"), QStringLiteral("Second tab"), QStringLiteral("Third")}}}), + cfg({{QStringLiteral("tab_rects"), tabs}})); + } + + // Header sections and item-view rows. + { + QTableView view; + view.setModel(rowsModel(&view, false, false)); + view.horizontalHeader()->setSortIndicatorShown(false); + adopt(&view); + view.ensurePolished(); + QHeaderView *h = view.horizontalHeader(); + QHeaderView *v = view.verticalHeader(); + r.add(QStringLiteral("header.horizontal"), h, {}, + cfg({{QStringLiteral("section_size_hint"), QJsonArray{h->sectionSizeHint(0), h->sectionSizeHint(1)}}, + {QStringLiteral("default_section_size"), h->defaultSectionSize()}, + {QStringLiteral("minimum_section_size"), h->minimumSectionSize()}})); + r.add(QStringLiteral("header.vertical"), v, {}, + cfg({{QStringLiteral("default_section_size"), v->defaultSectionSize()}, + {QStringLiteral("minimum_section_size"), v->minimumSectionSize()}, + {QStringLiteral("section_size_hint"), QJsonArray{v->sectionSizeHint(0), v->sectionSizeHint(1)}}})); + r.add(QStringLiteral("table_view"), &view, {}, cfg({{QStringLiteral("size_hint_for_row"), static_cast(view).sizeHintForRow(0)}})); + } + { + QTableView view; + view.setModel(rowsModel(&view, false, false)); + view.setSortingEnabled(true); + adopt(&view); + view.ensurePolished(); + QHeaderView *h = view.horizontalHeader(); + r.add(QStringLiteral("header.horizontal.sort_indicator"), h, cfg({{QStringLiteral("sort_indicator"), true}}), + cfg({{QStringLiteral("section_size_hint"), QJsonArray{h->sectionSizeHint(0), h->sectionSizeHint(1)}}})); + } + for (int variant = 0; variant < 3; ++variant) { + const bool icon = variant == 1; + const bool check = variant == 2; + QListView view; + view.setModel(rowsModel(&view, icon, check)); + adopt(&view); + view.ensurePolished(); + const QString id = variant == 0 ? QStringLiteral("list_view") : (icon ? QStringLiteral("list_view.icon") : QStringLiteral("list_view.check")); + r.add(id, &view, cfg({{QStringLiteral("icon"), icon}, {QStringLiteral("check"), check}}), + cfg({{QStringLiteral("size_hint_for_row"), static_cast(view).sizeHintForRow(0)}})); + } + for (int variant = 0; variant < 2; ++variant) { + QTreeView view; + view.setModel(rowsModel(&view, variant == 1, false)); + adopt(&view); + view.ensurePolished(); + r.add(variant == 0 ? QStringLiteral("tree_view") : QStringLiteral("tree_view.icon"), &view, cfg({{QStringLiteral("icon"), variant == 1}}), + cfg({{QStringLiteral("size_hint_for_row"), static_cast(view).sizeHintForRow(0)}, + {QStringLiteral("indentation"), view.indentation()}, + {QStringLiteral("header_height"), view.header()->sizeHint().height()}})); + } + return r.out; +} + +QJsonObject enumValues(const QMetaEnum &e, const std::function &query) +{ + QJsonObject o; + for (int i = 0; i < e.keyCount(); ++i) { + const QByteArray key = e.key(i); + if (key.endsWith("CustomBase") || key.contains("_Last")) + continue; + o.insert(QString::fromLatin1(key), query(e.value(i))); + } + return o; +} + +// sizeFromContents over a small lattice of content sizes. Each option is +// initialised from a real widget (initFrom: font, palette, direction, state) +// and then given the fields listed in "option". +QJsonArray contentsLattice(QStyle *style) +{ + const QList sizes = {QSize(0, 0), QSize(20, 16), QSize(60, 18), QSize(100, 24), QSize(100, 32)}; + QJsonArray out; + auto add = [&](const QString &id, QStyle::ContentsType ct, const QStyleOption &opt, QWidget *w, const QJsonObject &desc) { + for (const QSize &s : sizes) { + QJsonObject o; + o.insert(QStringLiteral("id"), id); + o.insert(QStringLiteral("option"), desc); + o.insert(QStringLiteral("contents"), sz(s)); + o.insert(QStringLiteral("out"), sz(style->sizeFromContents(ct, &opt, s, w))); + out.append(o); + } + }; + + { + QPushButton w(QStringLiteral("OK")); + adopt(&w); + w.ensurePolished(); + QStyleOptionButton opt; + opt.initFrom(&w); + opt.text = QStringLiteral("OK"); + add(QStringLiteral("CT_PushButton"), QStyle::CT_PushButton, opt, &w, cfg({{QStringLiteral("text"), QStringLiteral("OK")}})); + opt.features |= QStyleOptionButton::Flat; + add(QStringLiteral("CT_PushButton.flat"), QStyle::CT_PushButton, opt, &w, cfg({{QStringLiteral("text"), QStringLiteral("OK")}, {QStringLiteral("flat"), true}})); + } + for (bool autoRaise : {true, false}) { + QToolButton w; + w.setAutoRaise(autoRaise); + adopt(&w); + w.ensurePolished(); + QStyleOptionToolButton opt; + opt.initFrom(&w); + opt.subControls = QStyle::SC_ToolButton; + opt.toolButtonStyle = Qt::ToolButtonIconOnly; + opt.iconSize = QSize(16, 16); + opt.icon = testIcon(); + if (autoRaise) + opt.state |= QStyle::State_AutoRaise; + add(autoRaise ? QStringLiteral("CT_ToolButton.auto_raise") : QStringLiteral("CT_ToolButton.framed"), QStyle::CT_ToolButton, opt, &w, + cfg({{QStringLiteral("auto_raise"), autoRaise}, {QStringLiteral("icon_size"), 16}})); + } + { + QLineEdit w; + adopt(&w); + w.ensurePolished(); + QStyleOptionFrame opt; + opt.initFrom(&w); + opt.lineWidth = style->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, &w); + opt.midLineWidth = 0; + opt.state |= QStyle::State_Sunken; + add(QStringLiteral("CT_LineEdit"), QStyle::CT_LineEdit, opt, &w, cfg({{QStringLiteral("line_width"), opt.lineWidth}})); + } + for (bool editable : {false, true}) { + QComboBox w; + w.setEditable(editable); + adopt(&w); + w.ensurePolished(); + QStyleOptionComboBox opt; + opt.initFrom(&w); + opt.editable = editable; + opt.frame = true; + add(editable ? QStringLiteral("CT_ComboBox.editable") : QStringLiteral("CT_ComboBox"), QStyle::CT_ComboBox, opt, &w, + cfg({{QStringLiteral("editable"), editable}, {QStringLiteral("frame"), true}})); + } + { + QSpinBox w; + adopt(&w); + w.ensurePolished(); + QStyleOptionSpinBox opt; + opt.initFrom(&w); + opt.frame = true; + opt.buttonSymbols = QAbstractSpinBox::UpDownArrows; + opt.subControls = QStyle::SC_SpinBoxFrame | QStyle::SC_SpinBoxEditField | QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; + add(QStringLiteral("CT_SpinBox"), QStyle::CT_SpinBox, opt, &w, cfg({{QStringLiteral("frame"), true}})); + } + { + QCheckBox w(QStringLiteral("X")); + adopt(&w); + w.ensurePolished(); + QStyleOptionButton opt; + opt.initFrom(&w); + opt.text = QStringLiteral("X"); + add(QStringLiteral("CT_CheckBox"), QStyle::CT_CheckBox, opt, &w, cfg({{QStringLiteral("text"), QStringLiteral("X")}})); + QRadioButton rw(QStringLiteral("X")); + adopt(&rw); + rw.ensurePolished(); + QStyleOptionButton ropt; + ropt.initFrom(&rw); + ropt.text = QStringLiteral("X"); + add(QStringLiteral("CT_RadioButton"), QStyle::CT_RadioButton, ropt, &rw, cfg({{QStringLiteral("text"), QStringLiteral("X")}})); + } + { + QSlider w(Qt::Horizontal); + adopt(&w); + w.ensurePolished(); + QStyleOptionSlider opt; + opt.initFrom(&w); + opt.orientation = Qt::Horizontal; + opt.tickPosition = QSlider::NoTicks; + opt.minimum = 0; + opt.maximum = 100; + add(QStringLiteral("CT_Slider"), QStyle::CT_Slider, opt, &w, cfg({{QStringLiteral("orientation"), int(Qt::Horizontal)}})); + opt.tickPosition = QSlider::TicksBothSides; + add(QStringLiteral("CT_Slider.ticks_both"), QStyle::CT_Slider, opt, &w, cfg({{QStringLiteral("orientation"), int(Qt::Horizontal)}, {QStringLiteral("ticks"), int(QSlider::TicksBothSides)}})); + } + { + QProgressBar w; + adopt(&w); + w.ensurePolished(); + QStyleOptionProgressBar opt; + opt.initFrom(&w); + opt.minimum = 0; + opt.maximum = 100; + opt.progress = 50; + opt.textVisible = true; + opt.text = QStringLiteral("50%"); + opt.state |= QStyle::State_Horizontal; + add(QStringLiteral("CT_ProgressBar"), QStyle::CT_ProgressBar, opt, &w, cfg({{QStringLiteral("text_visible"), true}})); + } + { + QMenu w; + adopt(&w); + w.ensurePolished(); + struct Mi { + QString id; + QStyleOptionMenuItem::MenuItemType type; + QString text; + int maxIconWidth; + QStyleOptionMenuItem::CheckType check; + }; + for (const Mi &m : {Mi{QStringLiteral("CT_MenuItem.normal"), QStyleOptionMenuItem::Normal, QStringLiteral("Item"), 0, QStyleOptionMenuItem::NotCheckable}, + Mi{QStringLiteral("CT_MenuItem.normal.icon_column"), QStyleOptionMenuItem::Normal, QStringLiteral("Item"), 16, QStyleOptionMenuItem::NotCheckable}, + Mi{QStringLiteral("CT_MenuItem.shortcut"), QStyleOptionMenuItem::Normal, QStringLiteral("Save\tCtrl+S"), 16, QStyleOptionMenuItem::NotCheckable}, + Mi{QStringLiteral("CT_MenuItem.checkable"), QStyleOptionMenuItem::Normal, QStringLiteral("Item"), 16, QStyleOptionMenuItem::NonExclusive}, + Mi{QStringLiteral("CT_MenuItem.submenu"), QStyleOptionMenuItem::SubMenu, QStringLiteral("Item"), 16, QStyleOptionMenuItem::NotCheckable}, + Mi{QStringLiteral("CT_MenuItem.separator"), QStyleOptionMenuItem::Separator, QString(), 16, QStyleOptionMenuItem::NotCheckable}, + Mi{QStringLiteral("CT_MenuItem.section"), QStyleOptionMenuItem::Separator, QStringLiteral("Section"), 16, QStyleOptionMenuItem::NotCheckable}}) { + QStyleOptionMenuItem opt; + opt.initFrom(&w); + opt.menuItemType = m.type; + opt.text = m.text; + opt.maxIconWidth = m.maxIconWidth; + opt.checkType = m.check; + opt.menuHasCheckableItems = m.check != QStyleOptionMenuItem::NotCheckable; + opt.reservedShortcutWidth = 0; + add(m.id, QStyle::CT_MenuItem, opt, &w, + cfg({{QStringLiteral("type"), int(m.type)}, {QStringLiteral("text"), m.text}, {QStringLiteral("max_icon_width"), m.maxIconWidth}, + {QStringLiteral("check_type"), int(m.check)}})); + } + } + { + QTabBar w; + adopt(&w); + w.ensurePolished(); + QStyleOptionTab opt; + opt.initFrom(&w); + opt.text = QStringLiteral("Tab"); + opt.shape = QTabBar::RoundedNorth; + opt.position = QStyleOptionTab::Middle; + add(QStringLiteral("CT_TabBarTab"), QStyle::CT_TabBarTab, opt, &w, cfg({{QStringLiteral("text"), QStringLiteral("Tab")}, {QStringLiteral("shape"), 0}})); + opt.documentMode = true; + add(QStringLiteral("CT_TabBarTab.document_mode"), QStyle::CT_TabBarTab, opt, &w, + cfg({{QStringLiteral("text"), QStringLiteral("Tab")}, {QStringLiteral("shape"), 0}, {QStringLiteral("document_mode"), true}})); + } + { + QTableView view; + adopt(&view); + view.ensurePolished(); + QStyleOptionHeader opt; + opt.initFrom(view.horizontalHeader()); + opt.text = QStringLiteral("Name"); + opt.orientation = Qt::Horizontal; + opt.section = 0; + add(QStringLiteral("CT_HeaderSection"), QStyle::CT_HeaderSection, opt, view.horizontalHeader(), cfg({{QStringLiteral("text"), QStringLiteral("Name")}})); + } + { + QListView view; + adopt(&view); + view.ensurePolished(); + QStyleOptionViewItem opt; + opt.initFrom(&view); + opt.features = QStyleOptionViewItem::HasDisplay; + opt.text = QStringLiteral("Row"); + opt.decorationSize = QSize(16, 16); + opt.displayAlignment = Qt::AlignLeft | Qt::AlignVCenter; + add(QStringLiteral("CT_ItemViewItem"), QStyle::CT_ItemViewItem, opt, &view, cfg({{QStringLiteral("text"), QStringLiteral("Row")}})); + } + return out; +} + +QJsonObject subRects(QStyle *style) +{ + QJsonObject out; + + auto ccRects = [&](QStyle::ComplexControl cc, const QStyleOptionComplex &opt, QWidget *w, + const QList> &scs) { + QJsonObject o; + for (const auto &sc : scs) + o.insert(sc.second, rc(style->subControlRect(cc, &opt, sc.first, w))); + return o; + }; + + { + QComboBox w; + adopt(&w); + w.ensurePolished(); + QStyleOptionComboBox opt; + opt.initFrom(&w); + opt.rect = QRect(0, 0, 150, 32); + opt.frame = true; + opt.subControls = QStyle::SC_All; + QJsonObject o = ccRects(QStyle::CC_ComboBox, opt, &w, + {{QStyle::SC_ComboBoxFrame, QStringLiteral("frame")}, + {QStyle::SC_ComboBoxEditField, QStringLiteral("edit_field")}, + {QStyle::SC_ComboBoxArrow, QStringLiteral("arrow")}, + {QStyle::SC_ComboBoxListBoxPopup, QStringLiteral("popup")}}); + o.insert(QStringLiteral("rect"), rc(opt.rect)); + out.insert(QStringLiteral("combo_box"), o); + opt.editable = true; + QJsonObject e = ccRects(QStyle::CC_ComboBox, opt, &w, + {{QStyle::SC_ComboBoxEditField, QStringLiteral("edit_field")}, {QStyle::SC_ComboBoxArrow, QStringLiteral("arrow")}}); + e.insert(QStringLiteral("rect"), rc(opt.rect)); + out.insert(QStringLiteral("combo_box.editable"), e); + } + { + QSpinBox w; + adopt(&w); + w.ensurePolished(); + QStyleOptionSpinBox opt; + opt.initFrom(&w); + opt.rect = QRect(0, 0, 100, 32); + opt.frame = true; + opt.buttonSymbols = QAbstractSpinBox::UpDownArrows; + opt.subControls = QStyle::SC_All; + QJsonObject o = ccRects(QStyle::CC_SpinBox, opt, &w, + {{QStyle::SC_SpinBoxFrame, QStringLiteral("frame")}, + {QStyle::SC_SpinBoxEditField, QStringLiteral("edit_field")}, + {QStyle::SC_SpinBoxUp, QStringLiteral("up")}, + {QStyle::SC_SpinBoxDown, QStringLiteral("down")}}); + o.insert(QStringLiteral("rect"), rc(opt.rect)); + out.insert(QStringLiteral("spin_box"), o); + } + for (int value : {0, 50, 100}) { + QScrollBar w(Qt::Vertical); + adopt(&w); + w.ensurePolished(); + QStyleOptionSlider opt; + opt.initFrom(&w); + opt.orientation = Qt::Vertical; + opt.rect = QRect(0, 0, style->pixelMetric(QStyle::PM_ScrollBarExtent, &opt, &w), 200); + opt.minimum = 0; + opt.maximum = 100; + opt.pageStep = 20; + opt.singleStep = 1; + opt.sliderPosition = value; + opt.sliderValue = value; + opt.subControls = QStyle::SC_All; + QJsonObject o = ccRects(QStyle::CC_ScrollBar, opt, &w, + {{QStyle::SC_ScrollBarGroove, QStringLiteral("groove")}, + {QStyle::SC_ScrollBarSlider, QStringLiteral("slider")}, + {QStyle::SC_ScrollBarAddLine, QStringLiteral("add_line")}, + {QStyle::SC_ScrollBarSubLine, QStringLiteral("sub_line")}, + {QStyle::SC_ScrollBarAddPage, QStringLiteral("add_page")}, + {QStyle::SC_ScrollBarSubPage, QStringLiteral("sub_page")}}); + o.insert(QStringLiteral("rect"), rc(opt.rect)); + o.insert(QStringLiteral("range"), QJsonArray{0, 100}); + o.insert(QStringLiteral("page_step"), 20); + out.insert(QStringLiteral("scroll_bar.vertical.value_%1").arg(value), o); + } + for (QSlider::TickPosition tp : {QSlider::NoTicks, QSlider::TicksBelow, QSlider::TicksBothSides}) { + QSlider w(Qt::Horizontal); + adopt(&w); + w.ensurePolished(); + QStyleOptionSlider opt; + opt.initFrom(&w); + opt.orientation = Qt::Horizontal; + opt.tickPosition = tp; + opt.minimum = 0; + opt.maximum = 100; + opt.sliderPosition = 30; + opt.sliderValue = 30; + opt.tickInterval = 10; + opt.subControls = QStyle::SC_All; + const QSize hint = style->sizeFromContents(QStyle::CT_Slider, &opt, QSize(200, style->pixelMetric(QStyle::PM_SliderThickness, &opt, &w)), &w); + opt.rect = QRect(QPoint(0, 0), QSize(200, hint.height())); + QJsonObject o = ccRects(QStyle::CC_Slider, opt, &w, + {{QStyle::SC_SliderGroove, QStringLiteral("groove")}, {QStyle::SC_SliderHandle, QStringLiteral("handle")}}); + o.insert(QStringLiteral("rect"), rc(opt.rect)); + o.insert(QStringLiteral("value"), 30); + out.insert(QStringLiteral("slider.horizontal.ticks_%1").arg(int(tp)), o); + } + + auto seRects = [&](const QStyleOption &opt, QWidget *w, const QList> &ses) { + QJsonObject o; + for (const auto &se : ses) + o.insert(se.second, rc(style->subElementRect(se.first, &opt, w))); + o.insert(QStringLiteral("rect"), rc(opt.rect)); + return o; + }; + { + QCheckBox w(QStringLiteral("Check")); + adopt(&w); + w.ensurePolished(); + QStyleOptionButton opt; + opt.initFrom(&w); + opt.text = QStringLiteral("Check"); + opt.rect = QRect(QPoint(0, 0), w.sizeHint()); + out.insert(QStringLiteral("check_box"), + seRects(opt, &w, + {{QStyle::SE_CheckBoxIndicator, QStringLiteral("indicator")}, + {QStyle::SE_CheckBoxContents, QStringLiteral("contents")}, + {QStyle::SE_CheckBoxFocusRect, QStringLiteral("focus")}})); + QRadioButton rw(QStringLiteral("Radio")); + adopt(&rw); + rw.ensurePolished(); + QStyleOptionButton ropt; + ropt.initFrom(&rw); + ropt.text = QStringLiteral("Radio"); + ropt.rect = QRect(QPoint(0, 0), rw.sizeHint()); + out.insert(QStringLiteral("radio_button"), + seRects(ropt, &rw, + {{QStyle::SE_RadioButtonIndicator, QStringLiteral("indicator")}, + {QStyle::SE_RadioButtonContents, QStringLiteral("contents")}, + {QStyle::SE_RadioButtonFocusRect, QStringLiteral("focus")}})); + } + { + QPushButton w(QStringLiteral("OK")); + adopt(&w); + w.ensurePolished(); + QStyleOptionButton opt; + opt.initFrom(&w); + opt.text = QStringLiteral("OK"); + opt.rect = QRect(QPoint(0, 0), w.sizeHint()); + out.insert(QStringLiteral("push_button"), + seRects(opt, &w, {{QStyle::SE_PushButtonContents, QStringLiteral("contents")}, {QStyle::SE_PushButtonFocusRect, QStringLiteral("focus")}})); + } + { + QLineEdit w; + adopt(&w); + w.ensurePolished(); + QStyleOptionFrame opt; + opt.initFrom(&w); + opt.rect = QRect(QPoint(0, 0), w.sizeHint()); + opt.lineWidth = style->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, &w); + out.insert(QStringLiteral("line_edit"), seRects(opt, &w, {{QStyle::SE_LineEditContents, QStringLiteral("contents")}})); + } + { + QProgressBar w; + adopt(&w); + w.ensurePolished(); + QStyleOptionProgressBar opt; + opt.initFrom(&w); + opt.minimum = 0; + opt.maximum = 100; + opt.progress = 50; + opt.textVisible = true; + opt.text = QStringLiteral("50%"); + opt.state |= QStyle::State_Horizontal; + opt.rect = QRect(QPoint(0, 0), QSize(200, w.sizeHint().height())); + out.insert(QStringLiteral("progress_bar"), + seRects(opt, &w, + {{QStyle::SE_ProgressBarGroove, QStringLiteral("groove")}, + {QStyle::SE_ProgressBarContents, QStringLiteral("contents")}, + {QStyle::SE_ProgressBarLabel, QStringLiteral("label")}})); + } + { + QListView view; + adopt(&view); + view.ensurePolished(); + QStyleOptionViewItem opt; + opt.initFrom(&view); + opt.features = QStyleOptionViewItem::HasDisplay | QStyleOptionViewItem::HasDecoration | QStyleOptionViewItem::HasCheckIndicator; + opt.text = QStringLiteral("Row"); + opt.icon = testIcon(); + opt.decorationSize = QSize(16, 16); + opt.displayAlignment = Qt::AlignLeft | Qt::AlignVCenter; + opt.decorationAlignment = Qt::AlignCenter; + opt.decorationPosition = QStyleOptionViewItem::Left; + opt.rect = QRect(0, 0, 200, 24); + out.insert(QStringLiteral("item_view_item"), + seRects(opt, &view, + {{QStyle::SE_ItemViewItemCheckIndicator, QStringLiteral("check")}, + {QStyle::SE_ItemViewItemDecoration, QStringLiteral("decoration")}, + {QStyle::SE_ItemViewItemText, QStringLiteral("text")}, + {QStyle::SE_ItemViewItemFocusRect, QStringLiteral("focus")}})); + } + { + QTabBar w; + adopt(&w); + w.ensurePolished(); + QStyleOptionTab opt; + opt.initFrom(&w); + opt.text = QStringLiteral("Tab"); + opt.shape = QTabBar::RoundedNorth; + opt.rect = QRect(0, 0, 80, 30); + out.insert(QStringLiteral("tab"), seRects(opt, &w, {{QStyle::SE_TabBarTabText, QStringLiteral("text")}})); + } + return out; +} + + +// Pixel metrics that change when the style is told which widget asks. Only +// the values that differ from the widget-less answer are recorded. +QJsonObject widgetPixelMetrics(QStyle *style, const QMetaEnum &pmEnum) +{ + QJsonObject out; + auto probe = [&](const QString &id, QWidget *w) { + adopt(w); + w->ensurePolished(); + QStyleOption opt; + opt.initFrom(w); + QJsonObject o; + for (int i = 0; i < pmEnum.keyCount(); ++i) { + const QByteArray key = pmEnum.key(i); + if (key.endsWith("CustomBase")) + continue; + const auto pm = QStyle::PixelMetric(pmEnum.value(i)); + const int bare = style->pixelMetric(pm, nullptr, nullptr); + const int with = style->pixelMetric(pm, &opt, w); + if (with != bare) + o.insert(QString::fromLatin1(key), with); + } + out.insert(id, o); + }; + QLineEdit le; + probe(QStringLiteral("QLineEdit"), &le); + QComboBox cb; + probe(QStringLiteral("QComboBox"), &cb); + QComboBox cbe; + cbe.setEditable(true); + probe(QStringLiteral("QComboBox.editable"), &cbe); + QSpinBox sb; + probe(QStringLiteral("QSpinBox"), &sb); + QPushButton pb(QStringLiteral("OK")); + probe(QStringLiteral("QPushButton"), &pb); + QToolButton tb; + probe(QStringLiteral("QToolButton"), &tb); + QCheckBox chk; + probe(QStringLiteral("QCheckBox"), &chk); + QRadioButton rb; + probe(QStringLiteral("QRadioButton"), &rb); + QSlider sl(Qt::Horizontal); + probe(QStringLiteral("QSlider"), &sl); + QScrollBar sc(Qt::Vertical); + probe(QStringLiteral("QScrollBar"), &sc); + QProgressBar pr; + probe(QStringLiteral("QProgressBar"), &pr); + QMenu menu; + probe(QStringLiteral("QMenu"), &menu); + QMenuBar mb; + probe(QStringLiteral("QMenuBar"), &mb); + QTabBar tab; + probe(QStringLiteral("QTabBar"), &tab); + QTabWidget tw; + probe(QStringLiteral("QTabWidget"), &tw); + QTableView table; + probe(QStringLiteral("QTableView"), &table); + probe(QStringLiteral("QHeaderView.horizontal"), table.horizontalHeader()); + QListView lv; + probe(QStringLiteral("QListView"), &lv); + QTreeView tv; + probe(QStringLiteral("QTreeView"), &tv); + QFrame fr; + fr.setFrameShape(QFrame::StyledPanel); + probe(QStringLiteral("QFrame.styled_panel"), &fr); + QGroupBox gb(QStringLiteral("Group")); + probe(QStringLiteral("QGroupBox"), &gb); + QToolBar tbar; + probe(QStringLiteral("QToolBar"), &tbar); + QStatusBar stb; + probe(QStringLiteral("QStatusBar"), &stb); + return out; +} + +} // namespace + +int writeSizes(const QString &path) +{ + QStyle *style = QApplication::style(); + QWidget host; + gHost = &host; + QJsonObject env; + env.insert(QStringLiteral("widgets_parented"), QStringLiteral("every measured widget except QMenu has a plain QWidget parent")); + env.insert(QStringLiteral("style"), style ? style->name() : QStringLiteral("none")); + env.insert(QStringLiteral("platform"), QGuiApplication::platformName()); + if (QScreen *s = QGuiApplication::primaryScreen()) { + env.insert(QStringLiteral("logical_dpi"), s->logicalDotsPerInch()); + env.insert(QStringLiteral("physical_dpi"), s->physicalDotsPerInch()); + env.insert(QStringLiteral("device_pixel_ratio"), s->devicePixelRatio()); + } + env.insert(QStringLiteral("default_font_before_override"), QApplication::font().toString()); + + const QMetaEnum pmEnum = QMetaEnum::fromType(); + const QMetaEnum shEnum = QMetaEnum::fromType(); + + QJsonArray fonts, widgets; + struct Req { + QString family; + double pt; + }; + const QList reqs = {{QStringLiteral("Noto Sans"), 10}, {QStringLiteral("Noto Sans"), 8}, {QStringLiteral("Noto Sans"), 9}, + {QStringLiteral("Noto Sans"), 11}, {QStringLiteral("Noto Sans"), 12}, {QStringLiteral("Noto Sans"), 14}}; + QJsonObject pixelMetrics, widgetMetrics, styleHints, lattice, sub; + for (const Req &rq : reqs) { + QFont f(rq.family); + f.setPointSizeF(rq.pt); + QApplication::setFont(f); + // No event loop runs here, so an existing widget does not pick up the + // new application font by itself: give the host the font explicitly. + host.setFont(f); + fonts.append(fontRecord(f)); + const QJsonArray ws = widgetsFor(); + for (const QJsonValue &v : ws) { + QJsonObject o = v.toObject(); + o.insert(QStringLiteral("font"), QStringLiteral("%1 %2").arg(rq.family).arg(rq.pt)); + widgets.append(o); + } + if (rq.pt == 10) { + pixelMetrics = enumValues(pmEnum, [&](int v) { return style->pixelMetric(QStyle::PixelMetric(v), nullptr, nullptr); }); + widgetMetrics = widgetPixelMetrics(style, pmEnum); + styleHints = enumValues(shEnum, [&](int v) { return style->styleHint(QStyle::StyleHint(v), nullptr, nullptr, nullptr); }); + sub = subRects(style); + lattice.insert(QStringLiteral("records"), contentsLattice(style)); + } + } + // Restore the default font for anything after us. + QFont def(QStringLiteral("Noto Sans")); + def.setPointSizeF(10); + QApplication::setFont(def); + + QJsonArray monoFonts; + for (double pt : {9.0, 10.0, 11.0}) { + QFont f(QStringLiteral("Hack")); + f.setPointSizeF(pt); + monoFonts.append(fontRecord(f)); + } + + // QFont::fromString on a Qt 5 style string (10 fields): the legacy weight + // 0..99 in field 4 and the OpenType weight Qt maps it to. + QJsonArray legacyWeights; + for (int w = 0; w <= 99; ++w) { + QFont f; + f.fromString(QStringLiteral("Noto Sans,10,-1,5,%1,0,0,0,0,0").arg(w)); + QJsonObject o; + o.insert(QStringLiteral("legacy"), w); + o.insert(QStringLiteral("weight"), int(f.weight())); + legacyWeights.append(o); + } + + // The tooltip label Qt shows (QToolTip::showText), found among the + // top-level widgets by its class name: its margin and size hint. + QJsonObject tooltip; + { + QFont tipFont(QStringLiteral("Noto Sans")); + tipFont.setPointSizeF(10.0); + QApplication::setFont(tipFont); + QToolTip::showText(QPoint(100, 100), QStringLiteral("Tooltip text")); + QApplication::processEvents(); + for (QWidget *w : QApplication::topLevelWidgets()) { + if (QString::fromLatin1(w->metaObject()->className()) != QLatin1String("QTipLabel")) + continue; + const auto *label = qobject_cast(w); + const QMargins m = w->contentsMargins(); + tooltip.insert(QStringLiteral("class"), QStringLiteral("QTipLabel")); + tooltip.insert(QStringLiteral("text"), QStringLiteral("Tooltip text")); + tooltip.insert(QStringLiteral("font"), QStringLiteral("Noto Sans 10")); + tooltip.insert(QStringLiteral("margin"), label ? label->margin() : -1); + tooltip.insert(QStringLiteral("contents_margins"), QJsonArray{m.left(), m.top(), m.right(), m.bottom()}); + tooltip.insert(QStringLiteral("size_hint"), QJsonArray{w->sizeHint().width(), w->sizeHint().height()}); + const QFontMetrics fm(w->font()); + const QSize text = fm.size(0, QStringLiteral("Tooltip text")); + tooltip.insert(QStringLiteral("text_size"), QJsonArray{text.width(), text.height()}); + } + QToolTip::hideText(); + QApplication::processEvents(); + } + + return golden::writeFile(path, + { + {QStringLiteral("meta"), golden::meta(QStringLiteral("sizes"))}, + {QStringLiteral("environment"), env}, + {QStringLiteral("pixel_metrics"), pixelMetrics, true}, + {QStringLiteral("widget_pixel_metrics"), widgetMetrics, true}, + {QStringLiteral("style_hints"), styleHints, true}, + {QStringLiteral("sub_rects"), sub, true}, + {QStringLiteral("size_from_contents"), lattice.value(QStringLiteral("records")), true}, + {QStringLiteral("monospace_fonts"), monoFonts, true}, + {QStringLiteral("fonts"), fonts, true}, + {QStringLiteral("widgets"), widgets, true}, + {QStringLiteral("legacy_font_weights"), legacyWeights, true}, + {QStringLiteral("tooltip"), tooltip}, + }) + ? 0 + : 1; +} diff --git a/tools/kde_golden/run.sh b/tools/kde_golden/run.sh new file mode 100755 index 000000000..fcc1b248d --- /dev/null +++ b/tools/kde_golden/run.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech +# +# Builds the kde_golden image and regenerates every vector under vectors/. +# +# tools/kde_golden/run.sh # on the host: build, then run inside +# tools/kde_golden/run.sh --inside # in the container (run.sh does this) +# +# Needs docker (no sudo on the maintainer's machine). Not part of CI and not +# packaged; see README.md. + +set -euo pipefail + +here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +image_tag=teksilo-kde-golden +base_digest=sha256:f58f9b97f218ee976cf928f6fe00abb929a644aded8c9dfb02f77a9b2a6e1732 + +if [[ ${1:-} != --inside ]]; then + docker build -t "$image_tag" "$here" + mkdir -p "$here/vectors" + docker run --rm \ + --user "$(id -u):$(id -g)" \ + -e KDE_GOLDEN_IMAGE="invent-registry.kde.org/neon/docker-images/plasma@$base_digest" \ + -v "$here:/kde_golden:ro" \ + -v "$here/vectors:/out" \ + "$image_tag" \ + bash /kde_golden/run.sh --inside + exit 0 +fi + +# ---- inside the container ---------------------------------------------------- + +out=/out +driver=/opt/kde_golden/build/kde_golden +[[ -x $driver ]] || { echo "run.sh: $driver missing; is this the kde_golden image?" >&2; exit 1; } + +# A deterministic environment: no session, no platform theme, C locale, and +# every XDG directory a fresh scratch one (only XDG_DATA_DIRS keeps the system +# value, which is where the installed colour schemes live). +unset DISPLAY WAYLAND_DISPLAY KDE_FULL_SESSION KDE_SESSION_VERSION XDG_CURRENT_DESKTOP \ + QT_QPA_PLATFORMTHEME QT_STYLE_OVERRIDE QT_SCALE_FACTOR QT_SCREEN_SCALE_FACTORS \ + QT_FONT_DPI DBUS_SESSION_BUS_ADDRESS SESSION_MANAGER +export QT_QPA_PLATFORM=offscreen +export LC_ALL=C.UTF-8 LANG=C.UTF-8 LANGUAGE= +export XDG_DATA_DIRS=/usr/local/share:/usr/share + +scratch=$(mktemp -d /tmp/kde_golden.XXXXXX) +trap 'rm -rf "$scratch"' EXIT + +fresh_env() { + # $1: name of a fresh scratch root; exports HOME and the XDG dirs into it. + local root=$scratch/$1 + mkdir -p "$root"/{home,config,data,cache,etc-xdg,run} + chmod 700 "$root/run" + export HOME=$root/home XDG_CONFIG_HOME=$root/config XDG_DATA_HOME=$root/data \ + XDG_CACHE_HOME=$root/cache XDG_CONFIG_DIRS=$root/etc-xdg XDG_RUNTIME_DIR=$root/run +} + +fresh_env math +"$driver" math "$out/math.json" + +fresh_env resolve +"$driver" resolve "$out/resolve.json" + +# The Breeze style reads kdeglobals itself, so give it the default scheme. +fresh_env sizes +cp /usr/share/color-schemes/BreezeLight.colors "$XDG_CONFIG_HOME/kdeglobals" +"$driver" sizes "$out/sizes.json" + +for scheme in BreezeLight BreezeDark; do + fresh_env "pixels-$scheme" + cp "/usr/share/color-schemes/$scheme.colors" "$XDG_CONFIG_HOME/kdeglobals" + "$driver" pixels "$scratch/pixels-$scheme.json" "$scheme" +done + +python3 -I - "$out/pixels.json" "$scratch/pixels-BreezeLight.json" "$scratch/pixels-BreezeDark.json" <<'PY' +import json, sys + +out, *parts = sys.argv[1:] +docs = [json.load(open(p, encoding="utf-8")) for p in parts] +with open(out, "w", encoding="utf-8") as f: + f.write("{\n") + f.write('"meta": %s,\n' % json.dumps(docs[0]["meta"], sort_keys=True)) + f.write('"schemes": {\n') + for i, d in enumerate(docs): + name = d["environment"]["scheme"] + f.write('%s: {\n"environment": %s,\n"records": [\n' % (json.dumps(name), json.dumps(d["environment"], sort_keys=True))) + recs = d["records"] + for j, r in enumerate(recs): + f.write(json.dumps(r, sort_keys=True) + ("," if j + 1 < len(recs) else "") + "\n") + f.write("]\n}" + ("," if i + 1 < len(docs) else "") + "\n") + f.write("}\n}\n") +PY + +fresh_env accent +bash /kde_golden/accent_oracle.sh "$out/accent.json" + +cp /opt/kde_golden/versions.txt "$out/versions.txt" +cat /opt/kde_golden/fonts.sha256 >"$out/fonts.sha256" +ls -l "$out" diff --git a/tools/kde_golden/vectors/accent.json b/tools/kde_golden/vectors/accent.json new file mode 100644 index 000000000..eff5e6f88 --- /dev/null +++ b/tools/kde_golden/vectors/accent.json @@ -0,0 +1,51 @@ +{ +"meta": {"generator": "tools/kde_golden/accent_oracle.sh", "image": "invent-registry.kde.org/neon/docker-images/plasma@sha256:f58f9b97f218ee976cf928f6fe00abb929a644aded8c9dfb02f77a9b2a6e1732", "kind": "accent", "packages": {"breeze": "4:6.8.0-0zneon+24.04+noble+release+build80", "fonts-hack": "3.003-3", "fonts-noto-core": "20201225-2", "kde-style-breeze": "4:6.8.0-0zneon+24.04+noble+release+build80", "kde-style-oxygen": "4:6.8.0-0zneon+24.04+noble+release+build59", "kf6-kcolorscheme": "6.31.0-0zneon+24.04+noble+release+build47", "kf6-kconfig": "6.31.0-0zneon+24.04+noble+release+build36", "kf6-kguiaddons": "6.31.0-0zneon+24.04+noble+release+build59", "libkf6colorscheme-dev": "6.31.0-0zneon+24.04+noble+release+build47", "plasma-workspace": "4:6.8.0-0zneon+24.04+noble+release+build99", "qt6-base": "6.11.1-0zneon+24.04+noble+release+build162"}, "procedure": "see accent_oracle.sh header and README.md", "readme": "tools/kde_golden/README.md documents every field"}, +"schemes": { +"BreezeLight": {"origin": "installed", "source": "/usr/share/color-schemes/BreezeLight.colors", "sha1": "9980f09638f2f885144ced7c6086ae56addf7c8c", "sha256": "c46470b75e7df3e1ebff344748b92347f8d5adfa306aef9c91c4d2edae2cd6df", "input": {"ColorEffects:Disabled": {"Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "163,212,250", "BackgroundNormal": "252,252,252", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "163,212,250", "BackgroundNormal": "61,174,233", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "255,255,255", "ForegroundInactive": "112,125,138", "ForegroundLink": "253,188,75", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeLight", "Name": "Breeze Light", "shadeSortColumn": "true"}, "KDE": {"contrast": "4"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "base": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "163,212,250", "BackgroundNormal": "252,252,252", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "163,212,250", "BackgroundNormal": "61,174,233", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "255,255,255", "ForegroundInactive": "112,125,138", "ForegroundLink": "253,188,75", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "base_sha256": "30b0712e60f2efe538b7f3c2ef7a4f042fb236470014c75b33a1e2eddf841d50"}, +"BreezeDark": {"origin": "installed", "source": "/usr/share/color-schemes/BreezeDark.colors", "sha1": "27363f5706202c1c8261fbda7381ddaab8ad48f3", "sha256": "11fce1d8ab97b529162e50eb200d1a93fab65c7612f9bebd0230a6a348d0cc21", "input": {"ColorEffects:Disabled": {"Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "61,174,233", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "252,252,252", "ForegroundInactive": "161,169,177", "ForegroundLink": "253,188,75", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "Name": "Breeze Dark", "shadeSortColumn": "true"}, "KDE": {"contrast": "4"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "base": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "61,174,233", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "252,252,252", "ForegroundInactive": "161,169,177", "ForegroundLink": "253,188,75", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "base_sha256": "c9df398081add7ddee7e0166970649c3e87df4caffc5ebc9018d889532aafb1a"}, +"KdeGoldenTintLight": {"origin": "derived", "source": "derived (scratch data dir)", "sha1": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11", "sha256": "5dd3eddc1361f8f5948ec00702a3cccdff5571a402bc7591755e11fd8fd60168", "input": {"ColorEffects:Disabled": {"Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "163,212,250", "BackgroundNormal": "252,252,252", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "163,212,250", "BackgroundNormal": "61,174,233", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "255,255,255", "ForegroundInactive": "112,125,138", "ForegroundLink": "253,188,75", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTintLight", "Name": "KdeGoldenTintLight", "TintFactor": "0.25", "shadeSortColumn": "true"}, "KDE": {"contrast": "4"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "base": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "163,212,250", "BackgroundNormal": "252,252,252", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,230", "BackgroundNormal": "239,240,241", "DecorationFocus": "97,172,216", "DecorationHover": "97,172,216", "ForegroundActive": "97,172,216", "ForegroundInactive": "115,125,134", "ForegroundLink": "69,127,169", "ForegroundNegative": "199,90,95", "ForegroundNeutral": "227,130,72", "ForegroundNormal": "36,38,40", "ForegroundPositive": "82,168,110", "ForegroundVisited": "147,99,167"}, "Colors:Selection": {"BackgroundAlternate": "163,212,250", "BackgroundNormal": "61,174,233", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "255,255,255", "ForegroundInactive": "112,125,138", "ForegroundLink": "253,188,75", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "base_sha256": "2d87b1cdc6d711366c11aa8ab41f93beb9007a506e33610b9da999027f893bfb", "derived_from": "BreezeLight", "changes": {"General": {"Name": "KdeGoldenTintLight", "ColorScheme": "KdeGoldenTintLight", "TintFactor": "0.25"}}}, +"KdeGoldenTitlebarDark": {"origin": "derived", "source": "derived (scratch data dir)", "sha1": "645f7c749422dc8327f5375893100ceff3876d7a", "sha256": "f351e97b7c2c1caa4675ee40d500b43970cad1803ae8325123c0578a8d4bc487", "input": {"ColorEffects:Disabled": {"Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "61,174,233", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "252,252,252", "ForegroundInactive": "161,169,177", "ForegroundLink": "253,188,75", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "Name": "KdeGoldenTitlebarDark", "TitlebarIsAccentColored": "true", "shadeSortColumn": "true"}, "KDE": {"contrast": "4"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "base": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "61,174,233", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "252,252,252", "ForegroundInactive": "161,169,177", "ForegroundLink": "253,188,75", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "base_sha256": "7eecc7a48f299ff6d6825053f904129872361de4386e01e401a436865181ebb1", "derived_from": "BreezeDark", "changes": {"General": {"Name": "KdeGoldenTitlebarDark", "ColorScheme": "KdeGoldenTitlebarDark", "TitlebarIsAccentColored": "true"}}} +}, +"cases": [ +{"scheme": "BreezeLight", "accent": "#e93a9a", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#e93a9a"]], "stdout": "Successfully applied the accent color #e93a9a", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "238,116,183", "BackgroundNormal": "252,252,252", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "239,117,184", "BackgroundNormal": "239,117,184", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "0,0,0", "ForegroundLink": "170,42,112", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "dbe81b0dcf9d9c989a5bcd6de98261bb9d0f506d6b36afa9fab677b7045a6d80", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#e93d58", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#e93d58"]], "stdout": "Successfully applied the accent color #e93d58", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "238,118,137", "BackgroundNormal": "252,252,252", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,45,64", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,45,64", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "239,119,138", "BackgroundNormal": "239,119,138", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "0,0,0", "ForegroundLink": "170,45,64", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,45,64", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,45,64", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,45,64", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "802a999f5bcd5234e85654ca213b3b9697716178327c7268699a1f855b753a9c", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#e9643a", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#e9643a"]], "stdout": "Successfully applied the accent color #e9643a", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "238,145,116", "BackgroundNormal": "252,252,252", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,73,42", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "249,195,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,73,42", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "239,146,117", "BackgroundNormal": "239,146,117", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "0,0,0", "ForegroundLink": "170,73,42", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,73,42", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,73,42", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "112,125,138", "ForegroundLink": "170,73,42", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "b787d03851babfaf53fbc5edbcd0bdb27dd3a8f6422626431f3316816b0d535d", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#e8cb2d", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#e8cb2d"]], "stdout": "Successfully applied the accent color #e8cb2d", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "237,217,107", "BackgroundNormal": "252,252,252", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "112,125,138", "ForegroundLink": "169,148,33", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "112,125,138", "ForegroundLink": "169,148,33", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "238,218,107", "BackgroundNormal": "238,218,107", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "0,0,0", "ForegroundLink": "254,231,139", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "112,125,138", "ForegroundLink": "169,148,33", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "112,125,138", "ForegroundLink": "169,148,33", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "112,125,138", "ForegroundLink": "169,148,33", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "2ed3fe5aad9293b2aa4e8640a495445713177c49147291c42bf15d005eb94195", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#3dd425", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#3dd425"]], "stdout": "Successfully applied the accent color #3dd425", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "118,223,101", "BackgroundNormal": "252,252,252", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,155,27", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,155,27", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "119,224,102", "BackgroundNormal": "119,224,102", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "0,0,0", "ForegroundLink": "100,254,84", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,155,27", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,155,27", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,155,27", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "6329b725d6a0dace9d1d4cd9dbb6d1e8c1a8c93ec569ae48e619844412834f92", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#00d3b8", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#00d3b8"]], "stdout": "Successfully applied the accent color #00d3b8", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "75,223,204", "BackgroundNormal": "252,252,252", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "112,125,138", "ForegroundLink": "0,154,134", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "112,125,138", "ForegroundLink": "0,154,134", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "76,224,205", "BackgroundNormal": "76,224,205", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "0,0,0", "ForegroundLink": "0,253,220", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "112,125,138", "ForegroundLink": "0,154,134", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "112,125,138", "ForegroundLink": "0,154,134", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "112,125,138", "ForegroundLink": "0,154,134", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "bd02e7e886455c9934c2bf027167f17469797ca61d07a707392efffd472f0cd2", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#3daee9", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#3daee9"]], "stdout": "Successfully applied the accent color #3daee9", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "118,197,238", "BackgroundNormal": "252,252,252", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,127,170", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,127,170", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "119,198,239", "BackgroundNormal": "119,198,239", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "0,0,0", "ForegroundLink": "45,127,170", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,127,170", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,127,170", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "45,127,170", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "c30f133fa47e8a2bef151d40fcf77f77b7e1f8a3069aa66ce27bdac3f83876b6", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#b875dc", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#b875dc"]], "stdout": "Successfully applied the accent color #b875dc", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "204,157,229", "BackgroundNormal": "252,252,252", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "112,125,138", "ForegroundLink": "150,68,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "223,201,238", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "112,125,138", "ForegroundLink": "150,68,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "205,158,230", "BackgroundNormal": "205,158,230", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "0,0,0", "ForegroundLink": "150,68,187", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "112,125,138", "ForegroundLink": "150,68,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "112,125,138", "ForegroundLink": "150,68,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "112,125,138", "ForegroundLink": "150,68,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "21c08c6640191713f52281670a39fe523151ab67177caa728137f3c328faf134", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#926ee4", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#926ee4"]], "stdout": "Successfully applied the accent color #926ee4", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "177,152,235", "BackgroundNormal": "252,252,252", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "112,125,138", "ForegroundLink": "118,54,221", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "112,125,138", "ForegroundLink": "118,54,221", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "178,153,236", "BackgroundNormal": "178,153,236", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "0,0,0", "ForegroundLink": "118,54,221", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "112,125,138", "ForegroundLink": "118,54,221", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "112,125,138", "ForegroundLink": "118,54,221", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "112,125,138", "ForegroundLink": "118,54,221", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "3bffc972fd42a7ca08f2c2477680e55daeb2fd28aa924df6be1327d99972195e", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeLight", "accent": "#686b6f", "commands": [["plasma-apply-colorscheme", "BreezeLight"], ["plasma-apply-colorscheme", "-a", "#686b6f"]], "stdout": "Successfully applied the accent color #686b6f", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "148,150,153", "BackgroundNormal": "252,252,252", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "112,125,138", "ForegroundLink": "104,107,111", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "222,224,226", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "112,125,138", "ForegroundLink": "76,78,81", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "112,125,138", "ForegroundLink": "41,128,185", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "149,151,154", "BackgroundNormal": "149,151,154", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "0,0,0", "ForegroundLink": "76,78,81", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "239,240,241", "BackgroundNormal": "247,247,247", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "112,125,138", "ForegroundLink": "76,78,81", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "247,247,247", "BackgroundNormal": "255,255,255", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "112,125,138", "ForegroundLink": "104,107,111", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "227,229,231", "BackgroundNormal": "239,240,241", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "112,125,138", "ForegroundLink": "76,78,81", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "35,38,41", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorSchemeHash": "9980f09638f2f885144ced7c6086ae56addf7c8c"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "227,229,231", "activeBlend": "227,229,231", "activeForeground": "35,38,41", "inactiveBackground": "239,240,241", "inactiveBlend": "239,240,241", "inactiveForeground": "112,125,138"}}, "kdeglobals_sha256": "929277933b0ef05860e96668404461672015f186a0eb8efd5e84f7bdc47dc9db", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#e93a9a", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#e93a9a"]], "stdout": "Successfully applied the accent color #e93a9a", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "175,53,122", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "169,47,115", "BackgroundNormal": "169,47,115", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "255,255,255", "ForegroundLink": "170,42,112", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "233,58,154", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "09fff4ef87c7dacb370e828713f9346a6feefc3cd9efe307cb169673a661adbf", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#e93d58", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#e93d58"]], "stdout": "Successfully applied the accent color #e93d58", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "175,55,76", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "169,49,68", "BackgroundNormal": "169,49,68", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "255,255,255", "ForegroundLink": "170,45,64", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "2c7e409b9c58e153374f12a7cff0639ef82d118fbf3e854886a82ce4a10e3937", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#e9643a", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#e9643a"]], "stdout": "Successfully applied the accent color #e9643a", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "175,83,55", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "249,195,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "233,100,58", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "249,195,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "169,76,47", "BackgroundNormal": "169,76,47", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "255,255,255", "ForegroundLink": "170,73,42", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "249,195,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "233,100,58", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "233,100,58", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "cb75183df6a4c796077a2a1557e58ed7e981193f3046a14716a20694bed00395", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#e8cb2d", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#e8cb2d"]], "stdout": "Successfully applied the accent color #e8cb2d", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "174,155,45", "BackgroundNormal": "41,44,48", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "168,148,38", "BackgroundNormal": "168,148,38", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "0,0,0", "ForegroundLink": "254,231,139", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "a5a8d1159194f8cac31b4addaf203ebc6c1bcaca4e8cd91bced03fa8f4aefb5c", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#3dd425", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#3dd425"]], "stdout": "Successfully applied the accent color #3dd425", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "55,161,40", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "48,155,33", "BackgroundNormal": "48,155,33", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "0,0,0", "ForegroundLink": "100,254,84", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "dfa60b9da62ad94167a58484c00f58162ef34f0dcac9125b3c972f0c48b0fa8f", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#00d3b8", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#00d3b8"]], "stdout": "Successfully applied the accent color #00d3b8", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "12,160,143", "BackgroundNormal": "41,44,48", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "5,154,136", "BackgroundNormal": "5,154,136", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "0,0,0", "ForegroundLink": "0,253,220", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "8d6c4fa41bdfdc5eb53fd47cce6be09e4214ddf344a05994befea989a4e96fd7", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#3daee9", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#3daee9"]], "stdout": "Successfully applied the accent color #3daee9", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "55,135,177", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "48,128,170", "BackgroundNormal": "48,128,170", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "0,0,0", "ForegroundLink": "45,127,170", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "fa039baeba3437c967562a118f15622dc29480ff256c33a5aad9f642a6b50402", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#b875dc", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#b875dc"]], "stdout": "Successfully applied the accent color #b875dc", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "141,95,168", "BackgroundNormal": "41,44,48", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "223,201,238", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "184,117,220", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "223,201,238", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "134,88,161", "BackgroundNormal": "134,88,161", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "255,255,255", "ForegroundLink": "150,68,187", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "223,201,238", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "184,117,220", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "184,117,220", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "e1cb95ca941e9ffb176297880e44e33b737dc847d3b4766950969eaa50956c6f", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#926ee4", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#926ee4"]], "stdout": "Successfully applied the accent color #926ee4", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "114,90,174", "BackgroundNormal": "41,44,48", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "108,83,166", "BackgroundNormal": "108,83,166", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "255,255,255", "ForegroundLink": "118,54,221", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "146,110,228", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "2f00171f1d3393c188f34d0e9c726fdde573a7bdfa7e3c1f16cd309cce327e58", "config_files": ["kdeglobals"]}, +{"scheme": "BreezeDark", "accent": "#686b6f", "commands": [["plasma-apply-colorscheme", "BreezeDark"], ["plasma-apply-colorscheme", "-a", "#686b6f"]], "stdout": "Successfully applied the accent color #686b6f", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "85,88,92", "BackgroundNormal": "41,44,48", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "78,81,84", "BackgroundNormal": "78,81,84", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "255,255,255", "ForegroundLink": "76,78,81", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "BreezeDark", "ColorSchemeHash": "27363f5706202c1c8261fbda7381ddaab8ad48f3"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "39,44,49", "activeBlend": "252,252,252", "activeForeground": "252,252,252", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "4f9f81de17d9dec7cabd2a585353607c8e166b422e785f727d54a9dddf700916", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#e93a9a", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#e93a9a"]], "stdout": "Successfully applied the accent color #e93a9a", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "238,116,183", "BackgroundNormal": "255,237,255", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "143,112,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,64,100", "ForegroundNeutral": "248,110,79", "ForegroundNormal": "57,26,43", "ForegroundPositive": "127,157,115", "ForegroundVisited": "170,80,170"}, "Colors:Complementary": {"BackgroundAlternate": "47,18,34", "BackgroundNormal": "65,34,52", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "193,155,178", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,64,100", "ForegroundNeutral": "248,110,79", "ForegroundNormal": "255,237,255", "ForegroundPositive": "127,157,115", "ForegroundVisited": "170,80,170"}, "Colors:Header": {"BackgroundAlternate": "255,225,245", "BackgroundNormal": "254,210,229", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "143,112,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,64,100", "ForegroundNeutral": "248,110,79", "ForegroundNormal": "57,26,43", "ForegroundPositive": "127,157,115", "ForegroundVisited": "170,80,170"}, "Colors:Header][Inactive": {"BackgroundAlternate": "255,214,234", "BackgroundNormal": "255,225,245", "DecorationFocus": "141,160,219", "DecorationHover": "141,160,219", "ForegroundActive": "141,160,219", "ForegroundInactive": "143,112,138", "ForegroundLink": "110,115,172", "ForegroundNegative": "218,64,100", "ForegroundNeutral": "248,110,79", "ForegroundNormal": "57,26,43", "ForegroundPositive": "127,157,115", "ForegroundVisited": "170,80,170"}, "Colors:Selection": {"BackgroundAlternate": "239,117,184", "BackgroundNormal": "239,117,184", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "0,0,0", "ForegroundLink": "170,42,112", "ForegroundNegative": "179,46,82", "ForegroundNeutral": "203,84,63", "ForegroundNormal": "0,0,0", "ForegroundPositive": "85,90,70", "ForegroundVisited": "170,80,170"}, "Colors:Tooltip": {"BackgroundAlternate": "255,225,245", "BackgroundNormal": "255,232,251", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "143,112,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,64,100", "ForegroundNeutral": "248,110,79", "ForegroundNormal": "57,26,43", "ForegroundPositive": "127,157,115", "ForegroundVisited": "170,80,170"}, "Colors:View": {"BackgroundAlternate": "255,232,251", "BackgroundNormal": "255,240,255", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "143,112,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,64,100", "ForegroundNeutral": "248,110,79", "ForegroundNormal": "57,26,43", "ForegroundPositive": "127,157,115", "ForegroundVisited": "170,80,170"}, "Colors:Window": {"BackgroundAlternate": "255,214,234", "BackgroundNormal": "255,225,245", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "143,112,138", "ForegroundLink": "170,42,112", "ForegroundNegative": "218,64,100", "ForegroundNeutral": "248,110,79", "ForegroundNormal": "57,26,43", "ForegroundPositive": "127,157,115", "ForegroundVisited": "170,80,170"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "255,225,245", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "255,225,245", "inactiveBlend": "239,240,241", "inactiveForeground": "101,89,97"}}, "kdeglobals_sha256": "9810769725b0e9e86950464ae9259127aa0ecb2a14de14f4d27f67bf2020a246", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#e93d58", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#e93d58"]], "stdout": "Successfully applied the accent color #e93d58", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "238,118,137", "BackgroundNormal": "255,239,240", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "145,113,124", "ForegroundLink": "170,45,64", "ForegroundNegative": "220,65,83", "ForegroundNeutral": "250,110,52", "ForegroundNormal": "58,27,31", "ForegroundPositive": "130,158,97", "ForegroundVisited": "171,84,157"}, "Colors:Complementary": {"BackgroundAlternate": "49,19,23", "BackgroundNormal": "66,35,40", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "194,157,163", "ForegroundLink": "246,189,194", "ForegroundNegative": "220,65,83", "ForegroundNeutral": "250,110,52", "ForegroundNormal": "255,239,240", "ForegroundPositive": "130,158,97", "ForegroundVisited": "171,84,157"}, "Colors:Header": {"BackgroundAlternate": "255,227,228", "BackgroundNormal": "255,211,213", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "145,113,124", "ForegroundLink": "170,45,64", "ForegroundNegative": "220,65,83", "ForegroundNeutral": "250,110,52", "ForegroundNormal": "58,27,31", "ForegroundPositive": "130,158,97", "ForegroundVisited": "171,84,157"}, "Colors:Header][Inactive": {"BackgroundAlternate": "255,216,218", "BackgroundNormal": "255,227,228", "DecorationFocus": "142,162,205", "DecorationHover": "142,162,205", "ForegroundActive": "142,162,205", "ForegroundInactive": "145,113,124", "ForegroundLink": "111,118,159", "ForegroundNegative": "220,65,83", "ForegroundNeutral": "250,110,52", "ForegroundNormal": "58,27,31", "ForegroundPositive": "130,158,97", "ForegroundVisited": "171,84,157"}, "Colors:Selection": {"BackgroundAlternate": "239,119,138", "BackgroundNormal": "239,119,138", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "0,0,0", "ForegroundLink": "170,45,64", "ForegroundNegative": "181,47,66", "ForegroundNeutral": "204,84,36", "ForegroundNormal": "0,0,0", "ForegroundPositive": "87,91,53", "ForegroundVisited": "171,84,157"}, "Colors:Tooltip": {"BackgroundAlternate": "255,227,228", "BackgroundNormal": "255,234,235", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "145,113,124", "ForegroundLink": "170,45,64", "ForegroundNegative": "220,65,83", "ForegroundNeutral": "250,110,52", "ForegroundNormal": "58,27,31", "ForegroundPositive": "130,158,97", "ForegroundVisited": "171,84,157"}, "Colors:View": {"BackgroundAlternate": "255,234,235", "BackgroundNormal": "255,242,242", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "145,113,124", "ForegroundLink": "170,45,64", "ForegroundNegative": "220,65,83", "ForegroundNeutral": "250,110,52", "ForegroundNormal": "58,27,31", "ForegroundPositive": "130,158,97", "ForegroundVisited": "171,84,157"}, "Colors:Window": {"BackgroundAlternate": "255,216,218", "BackgroundNormal": "255,227,228", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "145,113,124", "ForegroundLink": "170,45,64", "ForegroundNegative": "220,65,83", "ForegroundNeutral": "250,110,52", "ForegroundNormal": "58,27,31", "ForegroundPositive": "130,158,97", "ForegroundVisited": "171,84,157"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "255,227,228", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "255,227,228", "inactiveBlend": "239,240,241", "inactiveForeground": "101,90,91"}}, "kdeglobals_sha256": "7ffc34ed871fd5408553e0fd633c91af6a652d67d27bad8252f789c4dac0ea98", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#e9643a", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#e9643a"]], "stdout": "Successfully applied the accent color #e9643a", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "238,145,116", "BackgroundNormal": "255,243,231", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "140,117,117", "ForegroundLink": "170,73,42", "ForegroundNegative": "217,72,74", "ForegroundNeutral": "246,115,32", "ForegroundNormal": "54,30,25", "ForegroundPositive": "123,161,87", "ForegroundVisited": "167,90,151"}, "Colors:Complementary": {"BackgroundAlternate": "45,22,16", "BackgroundNormal": "62,38,33", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "189,160,156", "ForegroundLink": "249,195,187", "ForegroundNegative": "217,72,74", "ForegroundNeutral": "246,115,32", "ForegroundNormal": "255,243,231", "ForegroundPositive": "123,161,87", "ForegroundVisited": "167,90,151"}, "Colors:Header": {"BackgroundAlternate": "255,231,220", "BackgroundNormal": "250,215,205", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "140,117,117", "ForegroundLink": "170,73,42", "ForegroundNegative": "217,72,74", "ForegroundNeutral": "246,115,32", "ForegroundNormal": "54,30,25", "ForegroundPositive": "123,161,87", "ForegroundVisited": "167,90,151"}, "Colors:Header][Inactive": {"BackgroundAlternate": "255,220,210", "BackgroundNormal": "255,231,220", "DecorationFocus": "135,166,198", "DecorationHover": "135,166,198", "ForegroundActive": "135,166,198", "ForegroundInactive": "140,117,117", "ForegroundLink": "104,122,153", "ForegroundNegative": "217,72,74", "ForegroundNeutral": "246,115,32", "ForegroundNormal": "54,30,25", "ForegroundPositive": "123,161,87", "ForegroundVisited": "167,90,151"}, "Colors:Selection": {"BackgroundAlternate": "239,146,117", "BackgroundNormal": "239,146,117", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "0,0,0", "ForegroundLink": "170,73,42", "ForegroundNegative": "177,55,58", "ForegroundNeutral": "201,89,13", "ForegroundNormal": "0,0,0", "ForegroundPositive": "82,93,44", "ForegroundVisited": "167,90,151"}, "Colors:Tooltip": {"BackgroundAlternate": "255,231,220", "BackgroundNormal": "255,238,226", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "140,117,117", "ForegroundLink": "170,73,42", "ForegroundNegative": "217,72,74", "ForegroundNeutral": "246,115,32", "ForegroundNormal": "54,30,25", "ForegroundPositive": "123,161,87", "ForegroundVisited": "167,90,151"}, "Colors:View": {"BackgroundAlternate": "255,238,226", "BackgroundNormal": "255,246,234", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "140,117,117", "ForegroundLink": "170,73,42", "ForegroundNegative": "217,72,74", "ForegroundNeutral": "246,115,32", "ForegroundNormal": "54,30,25", "ForegroundPositive": "123,161,87", "ForegroundVisited": "167,90,151"}, "Colors:Window": {"BackgroundAlternate": "255,220,210", "BackgroundNormal": "255,231,220", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "140,117,117", "ForegroundLink": "170,73,42", "ForegroundNegative": "217,72,74", "ForegroundNeutral": "246,115,32", "ForegroundNormal": "54,30,25", "ForegroundPositive": "123,161,87", "ForegroundVisited": "167,90,151"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "255,231,220", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "255,231,220", "inactiveBlend": "239,240,241", "inactiveForeground": "101,92,87"}}, "kdeglobals_sha256": "3521b5ebd755f576b4e9a721a3db48c81e1c7416a5260e9a46e5ed4e538a420e", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#e8cb2d", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#e8cb2d"]], "stdout": "Successfully applied the accent color #e8cb2d", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "237,217,107", "BackgroundNormal": "255,253,222", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "121,126,109", "ForegroundLink": "169,148,33", "ForegroundNegative": "204,89,62", "ForegroundNeutral": "232,128,0", "ForegroundNormal": "41,38,16", "ForegroundPositive": "95,168,75", "ForegroundVisited": "151,102,144"}, "Colors:Complementary": {"BackgroundAlternate": "33,30,7", "BackgroundNormal": "48,46,25", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "170,170,147", "ForegroundLink": "232,203,45", "ForegroundNegative": "204,89,62", "ForegroundNeutral": "232,128,0", "ForegroundNormal": "255,253,222", "ForegroundPositive": "95,168,75", "ForegroundVisited": "151,102,144"}, "Colors:Header": {"BackgroundAlternate": "246,241,211", "BackgroundNormal": "230,225,196", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "121,126,109", "ForegroundLink": "169,148,33", "ForegroundNegative": "204,89,62", "ForegroundNeutral": "232,128,0", "ForegroundNormal": "41,38,16", "ForegroundPositive": "95,168,75", "ForegroundVisited": "151,102,144"}, "Colors:Header][Inactive": {"BackgroundAlternate": "235,230,201", "BackgroundNormal": "246,241,211", "DecorationFocus": "106,175,191", "DecorationHover": "106,175,191", "ForegroundActive": "106,175,191", "ForegroundInactive": "121,126,109", "ForegroundLink": "78,130,146", "ForegroundNegative": "204,89,62", "ForegroundNeutral": "232,128,0", "ForegroundNormal": "41,38,16", "ForegroundPositive": "95,168,75", "ForegroundVisited": "151,102,144"}, "Colors:Selection": {"BackgroundAlternate": "238,218,107", "BackgroundNormal": "238,218,107", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "0,0,0", "ForegroundLink": "254,231,139", "ForegroundNegative": "165,71,46", "ForegroundNeutral": "188,102,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "59,100,31", "ForegroundVisited": "151,102,144"}, "Colors:Tooltip": {"BackgroundAlternate": "246,241,211", "BackgroundNormal": "254,248,217", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "121,126,109", "ForegroundLink": "169,148,33", "ForegroundNegative": "204,89,62", "ForegroundNeutral": "232,128,0", "ForegroundNormal": "41,38,16", "ForegroundPositive": "95,168,75", "ForegroundVisited": "151,102,144"}, "Colors:View": {"BackgroundAlternate": "254,248,217", "BackgroundNormal": "255,255,225", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "121,126,109", "ForegroundLink": "169,148,33", "ForegroundNegative": "204,89,62", "ForegroundNeutral": "232,128,0", "ForegroundNormal": "41,38,16", "ForegroundPositive": "95,168,75", "ForegroundVisited": "151,102,144"}, "Colors:Window": {"BackgroundAlternate": "235,230,201", "BackgroundNormal": "246,241,211", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "121,126,109", "ForegroundLink": "169,148,33", "ForegroundNegative": "204,89,62", "ForegroundNeutral": "232,128,0", "ForegroundNormal": "41,38,16", "ForegroundPositive": "95,168,75", "ForegroundVisited": "151,102,144"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "246,241,211", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "246,241,211", "inactiveBlend": "239,240,241", "inactiveForeground": "98,96,84"}}, "kdeglobals_sha256": "592b1153f5fe7bba60d72884ceea29ec2ea5769c999cc4af654d62996ff88131", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#3dd425", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#3dd425"]], "stdout": "Successfully applied the accent color #3dd425", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "118,223,101", "BackgroundNormal": "231,255,226", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "93,134,112", "ForegroundLink": "45,155,27", "ForegroundNegative": "187,104,66", "ForegroundNeutral": "214,142,0", "ForegroundNormal": "15,45,19", "ForegroundPositive": "31,176,79", "ForegroundVisited": "131,113,147"}, "Colors:Complementary": {"BackgroundAlternate": "7,37,10", "BackgroundNormal": "23,53,28", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "142,179,150", "ForegroundLink": "61,212,37", "ForegroundNegative": "187,104,66", "ForegroundNeutral": "214,142,0", "ForegroundNormal": "231,255,226", "ForegroundPositive": "31,176,79", "ForegroundVisited": "131,113,147"}, "Colors:Header": {"BackgroundAlternate": "218,251,214", "BackgroundNormal": "201,235,200", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "93,134,112", "ForegroundLink": "45,155,27", "ForegroundNegative": "187,104,66", "ForegroundNeutral": "214,142,0", "ForegroundNormal": "15,45,19", "ForegroundPositive": "31,176,79", "ForegroundVisited": "131,113,147"}, "Colors:Header][Inactive": {"BackgroundAlternate": "206,240,204", "BackgroundNormal": "218,251,214", "DecorationFocus": "48,182,194", "DecorationHover": "48,182,194", "ForegroundActive": "48,182,194", "ForegroundInactive": "93,134,112", "ForegroundLink": "5,137,149", "ForegroundNegative": "187,104,66", "ForegroundNeutral": "214,142,0", "ForegroundNormal": "15,45,19", "ForegroundPositive": "31,176,79", "ForegroundVisited": "131,113,147"}, "Colors:Selection": {"BackgroundAlternate": "119,224,102", "BackgroundNormal": "119,224,102", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "0,0,0", "ForegroundLink": "100,254,84", "ForegroundNegative": "149,86,50", "ForegroundNeutral": "170,114,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "0,107,35", "ForegroundVisited": "131,113,147"}, "Colors:Tooltip": {"BackgroundAlternate": "218,251,214", "BackgroundNormal": "226,255,221", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "93,134,112", "ForegroundLink": "45,155,27", "ForegroundNegative": "187,104,66", "ForegroundNeutral": "214,142,0", "ForegroundNormal": "15,45,19", "ForegroundPositive": "31,176,79", "ForegroundVisited": "131,113,147"}, "Colors:View": {"BackgroundAlternate": "226,255,221", "BackgroundNormal": "234,255,228", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "93,134,112", "ForegroundLink": "45,155,27", "ForegroundNegative": "187,104,66", "ForegroundNeutral": "214,142,0", "ForegroundNormal": "15,45,19", "ForegroundPositive": "31,176,79", "ForegroundVisited": "131,113,147"}, "Colors:Window": {"BackgroundAlternate": "206,240,204", "BackgroundNormal": "218,251,214", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "93,134,112", "ForegroundLink": "45,155,27", "ForegroundNegative": "187,104,66", "ForegroundNeutral": "214,142,0", "ForegroundNormal": "15,45,19", "ForegroundPositive": "31,176,79", "ForegroundVisited": "131,113,147"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "218,251,214", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "218,251,214", "inactiveBlend": "239,240,241", "inactiveForeground": "87,100,85"}}, "kdeglobals_sha256": "d148593d2766a99ae72d872936c3d1a2994cb73d48692d9693ee4069b9a5a065", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#00d3b8", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#00d3b8"]], "stdout": "Successfully applied the accent color #00d3b8", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "75,223,204", "BackgroundNormal": "228,255,253", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "91,132,135", "ForegroundLink": "0,154,134", "ForegroundNegative": "185,103,96", "ForegroundNeutral": "211,141,72", "ForegroundNormal": "11,44,41", "ForegroundPositive": "9,174,111", "ForegroundVisited": "130,109,168"}, "Colors:Complementary": {"BackgroundAlternate": "3,35,32", "BackgroundNormal": "19,52,49", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "139,177,175", "ForegroundLink": "0,211,184", "ForegroundNegative": "185,103,96", "ForegroundNeutral": "211,141,72", "ForegroundNormal": "228,255,253", "ForegroundPositive": "9,174,111", "ForegroundVisited": "130,109,168"}, "Colors:Header": {"BackgroundAlternate": "215,248,241", "BackgroundNormal": "198,232,226", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "91,132,135", "ForegroundLink": "0,154,134", "ForegroundNegative": "185,103,96", "ForegroundNeutral": "211,141,72", "ForegroundNormal": "11,44,41", "ForegroundPositive": "9,174,111", "ForegroundVisited": "130,109,168"}, "Colors:Header][Inactive": {"BackgroundAlternate": "203,237,231", "BackgroundNormal": "215,248,241", "DecorationFocus": "44,179,216", "DecorationHover": "44,179,216", "ForegroundActive": "44,179,216", "ForegroundInactive": "91,132,135", "ForegroundLink": "0,134,170", "ForegroundNegative": "185,103,96", "ForegroundNeutral": "211,141,72", "ForegroundNormal": "11,44,41", "ForegroundPositive": "9,174,111", "ForegroundVisited": "130,109,168"}, "Colors:Selection": {"BackgroundAlternate": "76,224,205", "BackgroundNormal": "76,224,205", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "0,0,0", "ForegroundLink": "0,253,220", "ForegroundNegative": "147,84,79", "ForegroundNeutral": "167,114,56", "ForegroundNormal": "0,0,0", "ForegroundPositive": "0,106,66", "ForegroundVisited": "130,109,168"}, "Colors:Tooltip": {"BackgroundAlternate": "215,248,241", "BackgroundNormal": "223,255,248", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "91,132,135", "ForegroundLink": "0,154,134", "ForegroundNegative": "185,103,96", "ForegroundNeutral": "211,141,72", "ForegroundNormal": "11,44,41", "ForegroundPositive": "9,174,111", "ForegroundVisited": "130,109,168"}, "Colors:View": {"BackgroundAlternate": "223,255,248", "BackgroundNormal": "231,255,255", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "91,132,135", "ForegroundLink": "0,154,134", "ForegroundNegative": "185,103,96", "ForegroundNeutral": "211,141,72", "ForegroundNormal": "11,44,41", "ForegroundPositive": "9,174,111", "ForegroundVisited": "130,109,168"}, "Colors:Window": {"BackgroundAlternate": "203,237,231", "BackgroundNormal": "215,248,241", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "91,132,135", "ForegroundLink": "0,154,134", "ForegroundNegative": "185,103,96", "ForegroundNeutral": "211,141,72", "ForegroundNormal": "11,44,41", "ForegroundPositive": "9,174,111", "ForegroundVisited": "130,109,168"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "215,248,241", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "215,248,241", "inactiveBlend": "239,240,241", "inactiveForeground": "85,99,96"}}, "kdeglobals_sha256": "f3a2ae86adebbe97f1df33223543f7a3b979deceb72eb3d1cb8401754148f02e", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#3daee9", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#3daee9"]], "stdout": "Successfully applied the accent color #3daee9", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "118,197,238", "BackgroundNormal": "232,255,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "96,128,151", "ForegroundLink": "45,127,170", "ForegroundNegative": "187,98,115", "ForegroundNeutral": "213,138,99", "ForegroundNormal": "18,40,54", "ForegroundPositive": "33,172,131", "ForegroundVisited": "134,102,183"}, "Colors:Complementary": {"BackgroundAlternate": "10,32,45", "BackgroundNormal": "25,48,63", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "143,172,193", "ForegroundLink": "61,174,233", "ForegroundNegative": "187,98,115", "ForegroundNeutral": "213,138,99", "ForegroundNormal": "232,255,255", "ForegroundPositive": "33,172,131", "ForegroundVisited": "134,102,183"}, "Colors:Header": {"BackgroundAlternate": "219,244,255", "BackgroundNormal": "203,228,245", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "96,128,151", "ForegroundLink": "45,127,170", "ForegroundNegative": "187,98,115", "ForegroundNeutral": "213,138,99", "ForegroundNormal": "18,40,54", "ForegroundPositive": "33,172,131", "ForegroundVisited": "134,102,183"}, "Colors:Header][Inactive": {"BackgroundAlternate": "208,233,250", "BackgroundNormal": "219,244,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "96,128,151", "ForegroundLink": "32,129,186", "ForegroundNegative": "187,98,115", "ForegroundNeutral": "213,138,99", "ForegroundNormal": "18,40,54", "ForegroundPositive": "33,172,131", "ForegroundVisited": "134,102,183"}, "Colors:Selection": {"BackgroundAlternate": "119,198,239", "BackgroundNormal": "119,198,239", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "0,0,0", "ForegroundLink": "45,127,170", "ForegroundNegative": "149,80,97", "ForegroundNeutral": "169,111,81", "ForegroundNormal": "0,0,0", "ForegroundPositive": "0,103,83", "ForegroundVisited": "134,102,183"}, "Colors:Tooltip": {"BackgroundAlternate": "219,244,255", "BackgroundNormal": "227,251,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "96,128,151", "ForegroundLink": "45,127,170", "ForegroundNegative": "187,98,115", "ForegroundNeutral": "213,138,99", "ForegroundNormal": "18,40,54", "ForegroundPositive": "33,172,131", "ForegroundVisited": "134,102,183"}, "Colors:View": {"BackgroundAlternate": "227,251,255", "BackgroundNormal": "235,255,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "96,128,151", "ForegroundLink": "45,127,170", "ForegroundNegative": "187,98,115", "ForegroundNeutral": "213,138,99", "ForegroundNormal": "18,40,54", "ForegroundPositive": "33,172,131", "ForegroundVisited": "134,102,183"}, "Colors:Window": {"BackgroundAlternate": "208,233,250", "BackgroundNormal": "219,244,255", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "96,128,151", "ForegroundLink": "45,127,170", "ForegroundNegative": "187,98,115", "ForegroundNeutral": "213,138,99", "ForegroundNormal": "18,40,54", "ForegroundPositive": "33,172,131", "ForegroundVisited": "134,102,183"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "219,244,255", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "219,244,255", "inactiveBlend": "239,240,241", "inactiveForeground": "87,97,101"}}, "kdeglobals_sha256": "ff0c384501365132aabbe35bd3d4751c20864c33457e0d18be070c730e8cf0ab", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#b875dc", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#b875dc"]], "stdout": "Successfully applied the accent color #b875dc", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "204,157,229", "BackgroundNormal": "255,245,255", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "124,118,152", "ForegroundLink": "150,68,187", "ForegroundNegative": "204,80,115", "ForegroundNeutral": "232,123,100", "ForegroundNormal": "43,32,54", "ForegroundPositive": "98,163,131", "ForegroundVisited": "155,89,184"}, "Colors:Complementary": {"BackgroundAlternate": "35,24,45", "BackgroundNormal": "50,40,63", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "172,162,193", "ForegroundLink": "223,201,238", "ForegroundNegative": "204,80,115", "ForegroundNeutral": "232,123,100", "ForegroundNormal": "255,245,255", "ForegroundPositive": "98,163,131", "ForegroundVisited": "155,89,184"}, "Colors:Header": {"BackgroundAlternate": "249,233,255", "BackgroundNormal": "232,217,245", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "124,118,152", "ForegroundLink": "150,68,187", "ForegroundNegative": "204,80,115", "ForegroundNeutral": "232,123,100", "ForegroundNormal": "43,32,54", "ForegroundPositive": "98,163,131", "ForegroundVisited": "155,89,184"}, "Colors:Header][Inactive": {"BackgroundAlternate": "237,222,250", "BackgroundNormal": "249,233,255", "DecorationFocus": "114,165,233", "DecorationHover": "114,165,233", "ForegroundActive": "114,165,233", "ForegroundInactive": "124,118,152", "ForegroundLink": "86,120,186", "ForegroundNegative": "204,80,115", "ForegroundNeutral": "232,123,100", "ForegroundNormal": "43,32,54", "ForegroundPositive": "98,163,131", "ForegroundVisited": "155,89,184"}, "Colors:Selection": {"BackgroundAlternate": "205,158,230", "BackgroundNormal": "205,158,230", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "0,0,0", "ForegroundLink": "150,68,187", "ForegroundNegative": "166,63,97", "ForegroundNeutral": "188,97,82", "ForegroundNormal": "0,0,0", "ForegroundPositive": "61,96,84", "ForegroundVisited": "155,89,184"}, "Colors:Tooltip": {"BackgroundAlternate": "249,233,255", "BackgroundNormal": "255,240,255", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "124,118,152", "ForegroundLink": "150,68,187", "ForegroundNegative": "204,80,115", "ForegroundNeutral": "232,123,100", "ForegroundNormal": "43,32,54", "ForegroundPositive": "98,163,131", "ForegroundVisited": "155,89,184"}, "Colors:View": {"BackgroundAlternate": "255,240,255", "BackgroundNormal": "255,248,255", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "124,118,152", "ForegroundLink": "150,68,187", "ForegroundNegative": "204,80,115", "ForegroundNeutral": "232,123,100", "ForegroundNormal": "43,32,54", "ForegroundPositive": "98,163,131", "ForegroundVisited": "155,89,184"}, "Colors:Window": {"BackgroundAlternate": "237,222,250", "BackgroundNormal": "249,233,255", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "124,118,152", "ForegroundLink": "150,68,187", "ForegroundNegative": "204,80,115", "ForegroundNeutral": "232,123,100", "ForegroundNormal": "43,32,54", "ForegroundPositive": "98,163,131", "ForegroundVisited": "155,89,184"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "249,233,255", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "249,233,255", "inactiveBlend": "239,240,241", "inactiveForeground": "99,93,101"}}, "kdeglobals_sha256": "25a4403656dd2749d0e16528070c5103063d80c615a14f047995af5afbbfd540", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#926ee4", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#926ee4"]], "stdout": "Successfully applied the accent color #926ee4", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "177,152,235", "BackgroundNormal": "254,247,255", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "117,120,157", "ForegroundLink": "118,54,221", "ForegroundNegative": "199,85,122", "ForegroundNeutral": "227,127,108", "ForegroundNormal": "37,33,59", "ForegroundPositive": "85,165,138", "ForegroundVisited": "149,91,189"}, "Colors:Complementary": {"BackgroundAlternate": "29,25,49", "BackgroundNormal": "45,41,68", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "165,164,200", "ForegroundLink": "209,199,242", "ForegroundNegative": "199,85,122", "ForegroundNeutral": "227,127,108", "ForegroundNormal": "254,247,255", "ForegroundPositive": "85,165,138", "ForegroundVisited": "149,91,189"}, "Colors:Header": {"BackgroundAlternate": "241,235,255", "BackgroundNormal": "224,219,252", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "117,120,157", "ForegroundLink": "118,54,221", "ForegroundNegative": "199,85,122", "ForegroundNeutral": "227,127,108", "ForegroundNormal": "37,33,59", "ForegroundPositive": "85,165,138", "ForegroundVisited": "149,91,189"}, "Colors:Header][Inactive": {"BackgroundAlternate": "229,224,255", "BackgroundNormal": "241,235,255", "DecorationFocus": "102,166,239", "DecorationHover": "102,166,239", "ForegroundActive": "102,166,239", "ForegroundInactive": "117,120,157", "ForegroundLink": "75,121,191", "ForegroundNegative": "199,85,122", "ForegroundNeutral": "227,127,108", "ForegroundNormal": "37,33,59", "ForegroundPositive": "85,165,138", "ForegroundVisited": "149,91,189"}, "Colors:Selection": {"BackgroundAlternate": "178,153,236", "BackgroundNormal": "178,153,236", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "0,0,0", "ForegroundLink": "118,54,221", "ForegroundNegative": "161,67,103", "ForegroundNeutral": "182,100,90", "ForegroundNormal": "0,0,0", "ForegroundPositive": "50,97,90", "ForegroundVisited": "149,91,189"}, "Colors:Tooltip": {"BackgroundAlternate": "241,235,255", "BackgroundNormal": "249,242,255", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "117,120,157", "ForegroundLink": "118,54,221", "ForegroundNegative": "199,85,122", "ForegroundNeutral": "227,127,108", "ForegroundNormal": "37,33,59", "ForegroundPositive": "85,165,138", "ForegroundVisited": "149,91,189"}, "Colors:View": {"BackgroundAlternate": "249,242,255", "BackgroundNormal": "255,250,255", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "117,120,157", "ForegroundLink": "118,54,221", "ForegroundNegative": "199,85,122", "ForegroundNeutral": "227,127,108", "ForegroundNormal": "37,33,59", "ForegroundPositive": "85,165,138", "ForegroundVisited": "149,91,189"}, "Colors:Window": {"BackgroundAlternate": "229,224,255", "BackgroundNormal": "241,235,255", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "117,120,157", "ForegroundLink": "118,54,221", "ForegroundNegative": "199,85,122", "ForegroundNeutral": "227,127,108", "ForegroundNormal": "37,33,59", "ForegroundPositive": "85,165,138", "ForegroundVisited": "149,91,189"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "241,235,255", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "241,235,255", "inactiveBlend": "239,240,241", "inactiveForeground": "96,93,101"}}, "kdeglobals_sha256": "d262f44a4ee70416c26cbf61c904f91df1f3ea8a51f3f29805f08727f3692623", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTintLight", "accent": "#686b6f", "commands": [["plasma-apply-colorscheme", "KdeGoldenTintLight"], ["plasma-apply-colorscheme", "-a", "#686b6f"]], "stdout": "Successfully applied the accent color #686b6f", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "148,150,153", "BackgroundNormal": "251,252,253", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "114,125,135", "ForegroundLink": "104,107,111", "ForegroundNegative": "198,90,97", "ForegroundNeutral": "226,131,74", "ForegroundNormal": "35,38,41", "ForegroundPositive": "81,168,112", "ForegroundVisited": "147,99,168"}, "Colors:Complementary": {"BackgroundAlternate": "27,30,32", "BackgroundNormal": "42,46,50", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "162,169,176", "ForegroundLink": "196,198,202", "ForegroundNegative": "198,90,97", "ForegroundNeutral": "226,131,74", "ForegroundNormal": "251,252,253", "ForegroundPositive": "81,168,112", "ForegroundVisited": "147,99,168"}, "Colors:Header": {"BackgroundAlternate": "238,240,242", "BackgroundNormal": "222,224,227", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "114,125,135", "ForegroundLink": "76,78,81", "ForegroundNegative": "198,90,97", "ForegroundNeutral": "226,131,74", "ForegroundNormal": "35,38,41", "ForegroundPositive": "81,168,112", "ForegroundVisited": "147,99,168"}, "Colors:Header][Inactive": {"BackgroundAlternate": "227,229,232", "BackgroundNormal": "238,240,242", "DecorationFocus": "96,172,217", "DecorationHover": "96,172,217", "ForegroundActive": "96,172,217", "ForegroundInactive": "114,125,135", "ForegroundLink": "68,127,170", "ForegroundNegative": "198,90,97", "ForegroundNeutral": "226,131,74", "ForegroundNormal": "35,38,41", "ForegroundPositive": "81,168,112", "ForegroundVisited": "147,99,168"}, "Colors:Selection": {"BackgroundAlternate": "149,151,154", "BackgroundNormal": "149,151,154", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "0,0,0", "ForegroundLink": "76,78,81", "ForegroundNegative": "160,73,80", "ForegroundNeutral": "182,104,58", "ForegroundNormal": "0,0,0", "ForegroundPositive": "47,100,67", "ForegroundVisited": "147,99,168"}, "Colors:Tooltip": {"BackgroundAlternate": "238,240,242", "BackgroundNormal": "246,247,248", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "114,125,135", "ForegroundLink": "76,78,81", "ForegroundNegative": "198,90,97", "ForegroundNeutral": "226,131,74", "ForegroundNormal": "35,38,41", "ForegroundPositive": "81,168,112", "ForegroundVisited": "147,99,168"}, "Colors:View": {"BackgroundAlternate": "246,247,248", "BackgroundNormal": "254,255,255", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "114,125,135", "ForegroundLink": "104,107,111", "ForegroundNegative": "198,90,97", "ForegroundNeutral": "226,131,74", "ForegroundNormal": "35,38,41", "ForegroundPositive": "81,168,112", "ForegroundVisited": "147,99,168"}, "Colors:Window": {"BackgroundAlternate": "227,229,232", "BackgroundNormal": "238,240,242", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "114,125,135", "ForegroundLink": "76,78,81", "ForegroundNegative": "198,90,97", "ForegroundNeutral": "226,131,74", "ForegroundNormal": "35,38,41", "ForegroundPositive": "81,168,112", "ForegroundVisited": "147,99,168"}, "General": {"ColorScheme": "KdeGoldenTintLight", "ColorSchemeHash": "72fe2b7a58ea4e7eb18af21f2a73b98d2caf3f11"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "238,240,242", "activeBlend": "227,229,231", "activeForeground": "0,0,0", "inactiveBackground": "238,240,242", "inactiveBlend": "239,240,241", "inactiveForeground": "95,95,96"}}, "kdeglobals_sha256": "b5d75a94a8abdc969a63df299a4bcda8b2213ebdc9651106ac732e8f99c3e475", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#e93a9a", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#e93a9a"]], "stdout": "Successfully applied the accent color #e93a9a", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "175,53,122", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "172,51,119", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "46,28,39", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "171,164,168", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "169,47,115", "BackgroundNormal": "169,47,115", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "255,255,255", "ForegroundLink": "170,42,112", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "233,58,154", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,58,154", "DecorationHover": "233,58,154", "ForegroundActive": "233,58,154", "ForegroundInactive": "161,169,177", "ForegroundLink": "247,188,212", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "172,51,119", "activeBlend": "252,252,252", "activeForeground": "255,255,255", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "2f2f9f03a32c6de80f4b29b02b7580db68b9b72adb1fcc49bff83c1b16cc4be9", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#e93d58", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#e93d58"]], "stdout": "Successfully applied the accent color #e93d58", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "175,55,76", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "172,53,73", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "46,29,32", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "171,164,165", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "169,49,68", "BackgroundNormal": "169,49,68", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "255,255,255", "ForegroundLink": "170,45,64", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,61,88", "DecorationHover": "233,61,88", "ForegroundActive": "233,61,88", "ForegroundInactive": "161,169,177", "ForegroundLink": "246,189,194", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "172,53,73", "activeBlend": "252,252,252", "activeForeground": "255,255,255", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "28ec2ae591aa400d4f7fa7bb34f714ccf5dfc0c556428885eee46839c38b574d", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#e9643a", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#e9643a"]], "stdout": "Successfully applied the accent color #e9643a", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "175,83,55", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "249,195,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "233,100,58", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "172,80,52", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "249,195,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "44,31,29", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "170,165,164", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "169,76,47", "BackgroundNormal": "169,76,47", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "255,255,255", "ForegroundLink": "170,73,42", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "249,195,187", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "233,100,58", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "233,100,58", "DecorationHover": "233,100,58", "ForegroundActive": "233,100,58", "ForegroundInactive": "161,169,177", "ForegroundLink": "233,100,58", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "172,80,52", "activeBlend": "252,252,252", "activeForeground": "255,255,255", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "8e7d6032a2ec65f3cf36df6d63906e6eea71512e6d763721bd8ceb06371383c4", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#e8cb2d", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#e8cb2d"]], "stdout": "Successfully applied the accent color #e8cb2d", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "174,155,45", "BackgroundNormal": "41,44,48", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "172,152,42", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "36,35,24", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "167,166,162", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "168,148,38", "BackgroundNormal": "168,148,38", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "0,0,0", "ForegroundLink": "254,231,139", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "232,203,45", "DecorationHover": "232,203,45", "ForegroundActive": "232,203,45", "ForegroundInactive": "161,169,177", "ForegroundLink": "232,203,45", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "172,152,42", "activeBlend": "252,252,252", "activeForeground": "0,0,0", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "e15f46ccb4e0ff6dbaa2c3c3169cc179910be5b4a7b9f4e768fa3ec631383e49", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#3dd425", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#3dd425"]], "stdout": "Successfully applied the accent color #3dd425", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "55,161,40", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "52,158,37", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "22,39,26", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "161,168,163", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "48,155,33", "BackgroundNormal": "48,155,33", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "0,0,0", "ForegroundLink": "100,254,84", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,212,37", "DecorationHover": "61,212,37", "ForegroundActive": "61,212,37", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,212,37", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "52,158,37", "activeBlend": "252,252,252", "activeForeground": "0,0,0", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "fb8eebf04c7eed2d7420dd0314d083658d6d5e332d63bc4ce35465de008d1592", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#00d3b8", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#00d3b8"]], "stdout": "Successfully applied the accent color #00d3b8", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "12,160,143", "BackgroundNormal": "41,44,48", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "9,158,140", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "20,38,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "160,168,168", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "5,154,136", "BackgroundNormal": "5,154,136", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "0,0,0", "ForegroundLink": "0,253,220", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "0,211,184", "DecorationHover": "0,211,184", "ForegroundActive": "0,211,184", "ForegroundInactive": "161,169,177", "ForegroundLink": "0,211,184", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "9,158,140", "activeBlend": "252,252,252", "activeForeground": "0,0,0", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "c44db8f91b4c667fc6478c04735003675f047968242e4fa2d122da9d6561a80d", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#3daee9", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#3daee9"]], "stdout": "Successfully applied the accent color #3daee9", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "55,135,177", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "52,132,174", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "23,36,46", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "162,167,171", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "48,128,170", "BackgroundNormal": "48,128,170", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "0,0,0", "ForegroundLink": "45,127,170", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "0,0,0", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "61,174,233", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "52,132,174", "activeBlend": "252,252,252", "activeForeground": "0,0,0", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "9440bc25a83f2b557bb1646e97fc98fa7ddd3698b72a20b1da81f6496c1ce1b4", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#b875dc", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#b875dc"]], "stdout": "Successfully applied the accent color #b875dc", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "141,95,168", "BackgroundNormal": "41,44,48", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "223,201,238", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "184,117,220", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "138,92,165", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "223,201,238", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "37,32,46", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "167,165,171", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "134,88,161", "BackgroundNormal": "134,88,161", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "255,255,255", "ForegroundLink": "150,68,187", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "223,201,238", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "184,117,220", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "184,117,220", "DecorationHover": "184,117,220", "ForegroundActive": "184,117,220", "ForegroundInactive": "161,169,177", "ForegroundLink": "184,117,220", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "138,92,165", "activeBlend": "252,252,252", "activeForeground": "255,255,255", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "1df717817fbd41d45c028e8f366a27590a776a81761dba15867ad4741118916d", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#926ee4", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#926ee4"]], "stdout": "Successfully applied the accent color #926ee4", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "114,90,174", "BackgroundNormal": "41,44,48", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "111,87,171", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "33,32,49", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "166,165,172", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "108,83,166", "BackgroundNormal": "108,83,166", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "255,255,255", "ForegroundLink": "118,54,221", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "146,110,228", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "146,110,228", "DecorationHover": "146,110,228", "ForegroundActive": "146,110,228", "ForegroundInactive": "161,169,177", "ForegroundLink": "209,199,242", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "111,87,171", "activeBlend": "252,252,252", "activeForeground": "255,255,255", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "6ed2d86a4200158b12633ea5949f4ed3f39d639c232bc1f5b44148efb5648749", "config_files": ["kdeglobals"]}, +{"scheme": "KdeGoldenTitlebarDark", "accent": "#686b6f", "commands": [["plasma-apply-colorscheme", "KdeGoldenTitlebarDark"], ["plasma-apply-colorscheme", "-a", "#686b6f"]], "stdout": "Successfully applied the accent color #686b6f", "kdeglobals": {"ColorEffects:Disabled": {"ChangeSelectionColor": "", "Color": "56,56,56", "ColorAmount": "0", "ColorEffect": "0", "ContrastAmount": "0.65", "ContrastEffect": "1", "Enable": "", "IntensityAmount": "0.1", "IntensityEffect": "2"}, "ColorEffects:Inactive": {"ChangeSelectionColor": "true", "Color": "112,111,110", "ColorAmount": "0.025", "ColorEffect": "2", "ContrastAmount": "0.1", "ContrastEffect": "2", "Enable": "false", "IntensityAmount": "0", "IntensityEffect": "0"}, "Colors:Button": {"BackgroundAlternate": "85,88,92", "BackgroundNormal": "41,44,48", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Complementary": {"BackgroundAlternate": "30,87,116", "BackgroundNormal": "32,35,38", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "82,85,89", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Header][Inactive": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "61,174,233", "DecorationHover": "61,174,233", "ForegroundActive": "61,174,233", "ForegroundInactive": "161,169,177", "ForegroundLink": "29,153,243", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "165,166,168", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Selection": {"BackgroundAlternate": "78,81,84", "BackgroundNormal": "78,81,84", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "255,255,255", "ForegroundLink": "76,78,81", "ForegroundNegative": "176,55,69", "ForegroundNeutral": "198,92,0", "ForegroundNormal": "255,255,255", "ForegroundPositive": "23,104,57", "ForegroundVisited": "155,89,182"}, "Colors:Tooltip": {"BackgroundAlternate": "32,35,38", "BackgroundNormal": "41,44,48", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:View": {"BackgroundAlternate": "29,31,34", "BackgroundNormal": "20,22,24", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "Colors:Window": {"BackgroundAlternate": "41,44,48", "BackgroundNormal": "32,35,38", "DecorationFocus": "104,107,111", "DecorationHover": "104,107,111", "ForegroundActive": "104,107,111", "ForegroundInactive": "161,169,177", "ForegroundLink": "196,198,202", "ForegroundNegative": "218,68,83", "ForegroundNeutral": "246,116,0", "ForegroundNormal": "252,252,252", "ForegroundPositive": "39,174,96", "ForegroundVisited": "155,89,182"}, "General": {"ColorScheme": "KdeGoldenTitlebarDark", "ColorSchemeHash": "645f7c749422dc8327f5375893100ceff3876d7a"}, "KDE": {"contrast": "4", "frameContrast": "0.2"}, "WM": {"activeBackground": "82,85,89", "activeBlend": "252,252,252", "activeForeground": "255,255,255", "inactiveBackground": "32,36,40", "inactiveBlend": "161,169,177", "inactiveForeground": "161,169,177"}}, "kdeglobals_sha256": "26d818582c96f170ae9d01be96c50bfcbf8ba4dd8817398178543537382764aa", "config_files": ["kdeglobals"]} +] +} diff --git a/tools/kde_golden/vectors/fonts.sha256 b/tools/kde_golden/vectors/fonts.sha256 new file mode 100644 index 000000000..62aa2dc3f --- /dev/null +++ b/tools/kde_golden/vectors/fonts.sha256 @@ -0,0 +1,2 @@ +89c3c497f618fdaa0b2d1e98fef93582f28c71debd2c4a8cdf41f190ced2909d /usr/share/fonts/truetype/noto/NotoSans-Regular.ttf +9f67c8a8cd28498525639437d6fac588c452e8572945c790db7641cc7907a69b /usr/share/fonts/truetype/hack/Hack-Regular.ttf diff --git a/tools/kde_golden/vectors/math.json b/tools/kde_golden/vectors/math.json new file mode 100644 index 000000000..5ed582dfa --- /dev/null +++ b/tools/kde_golden/vectors/math.json @@ -0,0 +1,20928 @@ +{ +"meta": {"generator":"tools/kde_golden/driver (kde_golden)","image":"invent-registry.kde.org/neon/docker-images/plasma@sha256:f58f9b97f218ee976cf928f6fe00abb929a644aded8c9dfb02f77a9b2a6e1732","kcolorscheme_build":"6.31.0","kguiaddons_build":"6.31.0","kind":"math","packages":{"breeze":"4:6.8.0-0zneon+24.04+noble+release+build80","fonts-hack":"3.003-3","fonts-noto-core":"20201225-2","kde-style-breeze":"4:6.8.0-0zneon+24.04+noble+release+build80","kde-style-oxygen":"4:6.8.0-0zneon+24.04+noble+release+build59","kf6-kcolorscheme":"6.31.0-0zneon+24.04+noble+release+build47","kf6-kconfig":"6.31.0-0zneon+24.04+noble+release+build36","kf6-kguiaddons":"6.31.0-0zneon+24.04+noble+release+build59","libkf6colorscheme-dev":"6.31.0-0zneon+24.04+noble+release+build47","plasma-workspace":"4:6.8.0-0zneon+24.04+noble+release+build99","qt6-base":"6.11.1-0zneon+24.04+noble+release+build162"},"qt_build":"6.11.1","qt_runtime":"6.11.1","readme":"tools/kde_golden/README.md documents every field"}, +"colors": ["#000000ff","#ffffffff","#010101ff","#020202ff","#040404ff","#080808ff","#101010ff","#202020ff","#404040ff","#7f7f7fff","#808080ff","#818181ff","#bfbfbfff","#c0c0c0ff","#fdfdfdff","#fefefeff","#ff0000ff","#00ff00ff","#0000ffff","#ffff00ff","#00ffffff","#ff00ffff","#800000ff","#008000ff","#000080ff","#ff8080ff","#80ff80ff","#8080ffff","#030201ff","#fffefdff","#7f8081ff","#010000ff","#000001ff","#fefffeff","#3daee9ff","#232629ff","#eff0f1ff","#fcfcfcff","#31363bff","#1b1e20ff","#da4453ff","#27ae60ff","#f67400ff","#2980b9ff","#7f8c8dff","#9b59b6ff","#1d99f3ff","#dee0e2ff","#292c30ff","#e93a9aff","#e93d58ff","#e9643aff","#e8cb2dff","#3dd425ff","#00d3b8ff","#b875dcff","#926ee4ff","#686b6fff","#3daee980","#ffffff00","#00000080","#ff000033"], +"pair_colors": ["#000000ff","#ffffffff","#7f7f7fff","#808080ff","#ff0000ff","#00ff00ff","#0000ffff","#ffff00ff","#030201ff","#3daee9ff","#232629ff","#eff0f1ff","#fcfcfcff","#da4453ff","#e8cb2dff","#3daee980","#ffffff00"], +"single": [ +{"chroma":0,"hcy":[0,0,0,1],"hue":0,"in":"#000000ff","luma":0,"roundtrip":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"hcy":[0,0,1,1],"hue":0,"in":"#ffffffff","luma":1,"roundtrip":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"hcy":[0,0,5.077052561215497e-06,1],"hue":0,"in":"#010101ff","luma":5.077052561215497e-06,"roundtrip":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"chroma":0,"hcy":[0,0,2.3328007701206905e-05,1],"hue":0,"in":"#020202ff","luma":2.3328007701206905e-05,"roundtrip":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"chroma":0,"hcy":[0,0,0.00010718737628693813,1],"hue":0,"in":"#040404ff","luma":0.00010718737628693813,"roundtrip":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"chroma":0,"hcy":[0,0,0.0004925038512690163,1],"hue":0,"in":"#080808ff","luma":0.0004925038512690163,"roundtrip":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"chroma":0,"hcy":[0,0,0.0022629534551296933,1],"hue":0,"in":"#101010ff","luma":0.0022629534551296933,"roundtrip":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"chroma":0,"hcy":[0,0,0.010397803645369345,1],"hue":0,"in":"#202020ff","luma":0.010397803645369345,"roundtrip":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"chroma":0,"hcy":[0,0,0.04777575977207176,1],"hue":0,"in":"#404040ff","luma":0.04777575977207176,"roundtrip":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"chroma":0,"hcy":[0,0,0.21576439972078568,1],"hue":0,"in":"#7f7f7fff","luma":0.21576439972078568,"roundtrip":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"chroma":0,"hcy":[0,0,0.21951974663564944,1],"hue":0,"in":"#808080ff","luma":0.21951974663564944,"roundtrip":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"chroma":0,"hcy":[0,0,0.22331043694287703,1],"hue":0,"in":"#818181ff","luma":0.22331043694287703,"roundtrip":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"chroma":0,"hcy":[0,0,0.5295232456839567,1],"hue":0,"in":"#bfbfbfff","luma":0.5295232456839567,"roundtrip":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"chroma":0,"hcy":[0,0,0.5356416323624177,1],"hue":0,"in":"#c0c0c0ff","luma":0.5356416323624177,"roundtrip":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"chroma":0,"hcy":[0,0,0.9828262560725913,1],"hue":0,"in":"#fdfdfdff","luma":0.9828262560725913,"roundtrip":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"chroma":0,"hcy":[0,0,0.991392844104757,1],"hue":0,"in":"#fefefeff","luma":0.991392844104757,"roundtrip":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"chroma":1,"hcy":[0,1,0.2126,1],"hue":0,"in":"#ff0000ff","luma":0.2126,"roundtrip":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"chroma":1,"hcy":[0.3333333333333333,1,0.7152,1],"hue":0.3333333333333333,"in":"#00ff00ff","luma":0.7152,"roundtrip":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"chroma":1,"hcy":[0.6666666666666666,1,0.0722,1],"hue":0.6666666666666666,"in":"#0000ffff","luma":0.0722,"roundtrip":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"chroma":1,"hcy":[0.16666666666666666,1,0.9278,1],"hue":0.16666666666666666,"in":"#ffff00ff","luma":0.9278,"roundtrip":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"chroma":1,"hcy":[0.5,1,0.7874,1],"hue":0.5,"in":"#00ffffff","luma":0.7874,"roundtrip":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"chroma":1,"hcy":[0.8333333333333334,1,0.2848,1],"hue":0.8333333333333334,"in":"#ff00ffff","luma":0.2848,"roundtrip":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"chroma":1,"hcy":[0,1,0.046669898134739075,1],"hue":0,"in":"#800000ff","luma":0.046669898134739075,"roundtrip":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"chroma":1,"hcy":[0.3333333333333333,1,0.1570005227938165,1],"hue":0.3333333333333333,"in":"#008000ff","luma":0.1570005227938165,"roundtrip":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"chroma":1,"hcy":[0.6666666666666666,1,0.01584932570709389,1],"hue":0.6666666666666666,"in":"#000080ff","luma":0.01584932570709389,"roundtrip":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"chroma":1,"hcy":[0,1,0.3854498485009104,1],"hue":0,"in":"#ff8080ff","luma":0.3854498485009104,"roundtrip":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"chroma":1,"hcy":[0.3333333333333333,1,0.7777192238418329,1],"hue":0.3333333333333333,"in":"#80ff80ff","luma":0.7777192238418329,"roundtrip":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"chroma":1,"hcy":[0.6666666666666666,1,0.27587042092855557,1],"hue":0.6666666666666666,"in":"#8080ffff","luma":0.27587042092855557,"roundtrip":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"chroma":0.8258439748312948,"hcy":[0.05867185925224872,0.8258439748312948,2.915232221393058e-05,1],"hue":0.05867185925224872,"in":"#030201ff","luma":2.915232221393058e-05,"roundtrip":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"chroma":1,"hcy":[0.08313648311414243,1,0.9926042177921633,1],"hue":0.08313648311414243,"in":"#fffefdff","luma":0.9926042177921633,"roundtrip":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"chroma":0.014752150948411593,"hcy":[0.5837236418635325,0.014752150948411593,0.21899504772173126,1],"hue":0.5837236418635325,"in":"#7f8081ff","luma":0.21899504772173126,"roundtrip":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"chroma":1,"hcy":[0,1,1.0793813745144147e-06,1],"hue":0,"in":"#010000ff","luma":1.0793813745144147e-06,"roundtrip":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"chroma":1,"hcy":[0.6666666666666666,1,3.665631949197589e-07,1],"hue":0.6666666666666666,"in":"#000001ff","luma":3.665631949197589e-07,"roundtrip":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"chroma":1,"hcy":[0.3333333333333333,1,0.9975486820010347,1],"hue":0.3333333333333333,"in":"#fefffeff","luma":0.9975486820010347,"roundtrip":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"chroma":0.8859261608923455,"hcy":[0.5833623180737052,0.8859261608923455,0.37683495841856884,1],"hue":0.5833623180737052,"in":"#3daee9ff","luma":0.37683495841856884,"roundtrip":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"chroma":0.14668682237720893,"hcy":[0.5872793887563216,0.14668682237720893,0.014840649046253766,1],"hue":0.5872793887563216,"in":"#232629ff","luma":0.014840649046253766,"roundtrip":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"chroma":0.0727300176330628,"hcy":[0.5835416664605718,0.0727300176330628,0.8740169984782439,1],"hue":0.5835416664605718,"in":"#eff0f1ff","luma":0.8740169984782439,"roundtrip":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"chroma":0,"hcy":[0,0,0.9743002039098161,1],"hue":0,"in":"#fcfcfcff","luma":0.9743002039098161,"roundtrip":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"chroma":0.17142281261865266,"hcy":[0.5879608447318521,0.17142281261865266,0.032041292327068156,1],"hue":0.5879608447318521,"in":"#31363bff","luma":0.032041292327068156,"roundtrip":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"chroma":0.17984945088614265,"hcy":[0.5707375264074906,0.17984945088614265,0.008724053384721276,1],"hue":0.5707375264074906,"in":"#1b1e20ff","luma":0.008724053384721276,"roundtrip":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"chroma":0.721097446056771,"hcy":[0.992338743786733,0.721097446056771,0.19573963297852393,1],"hue":0.992338743786733,"in":"#da4453ff","luma":0.19573963297852393,"roundtrip":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"chroma":0.9498397358104452,"hcy":[0.373671532865446,0.9498397358104452,0.3203273046425214,1],"hue":0.373671532865446,"in":"#27ae60ff","luma":0.3203273046425214,"roundtrip":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]}}, +{"chroma":1,"hcy":[0.03188593040423337,1,0.3228699804981117,1],"hue":0.03188593040423337,"in":"#f67400ff","luma":0.3228699804981117,"roundtrip":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"chroma":0.908698550047228,"hcy":[0.596036761235255,0.908698550047228,0.196452900302308,1],"hue":0.596036761235255,"in":"#2980b9ff","luma":0.196452900302308,"roundtrip":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"chroma":0.15944869388279823,"hcy":[0.5125996687873353,0.15944869388279823,0.25669390809405357,1],"hue":0.5125996687873353,"in":"#7f8c8dff","luma":0.25669390809405357,"roundtrip":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"chroma":0.43948326485231587,"hcy":[0.7707620633205988,0.43948326485231587,0.17606825443530055,1],"hue":0.7707620633205988,"in":"#9b59b6ff","luma":0.17606825443530055,"roundtrip":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"chroma":0.9720133101225753,"hcy":[0.6074336286605359,0.9720133101225753,0.29918214303553803,1],"hue":0.6074336286605359,"in":"#1d99f3ff","luma":0.29918214303553803,"roundtrip":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]}}, +{"chroma":0.06755635527814857,"hcy":[0.5837797599763813,0.06755635527814857,0.7498440023087394,1],"hue":0.5837797599763813,"in":"#dee0e2ff","luma":0.7498440023087394,"roundtrip":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"chroma":0.13053775381326926,"hcy":[0.5990862582111002,0.13053775381326926,0.02062934270429031,1],"hue":0.5990862582111002,"in":"#292c30ff","luma":0.02062934270429031,"roundtrip":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"chroma":0.82949986682183,"hcy":[0.9378844784763815,0.82949986682183,0.22564643266369816,1],"hue":0.9378844784763815,"in":"#e93a9aff","luma":0.22564643266369816,"roundtrip":{"hex":"#e93a9aff","u16":[59881,14906,39578,65535]}}, +{"chroma":0.7972492638033875,"hcy":[0.9885712750532858,0.7972492638033875,0.212019010254512,1],"hue":0.9885712750532858,"in":"#e93d58ff","luma":0.212019010254512,"roundtrip":{"hex":"#e93d58ff","u16":[59881,15677,22616,65535]}}, +{"chroma":0.8566115398359366,"hcy":[0.0189929760683667,0.8566115398359366,0.26831131861182883,1],"hue":0.0189929760683667,"in":"#e9643aff","luma":0.26831131861182883,"roundtrip":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"chroma":0.9637535336934658,"hcy":[0.12305962155795816,0.9637535336934658,0.6073142671594791,1],"hue":0.12305962155795816,"in":"#e8cb2dff","luma":0.6073142671594791,"roundtrip":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"chroma":0.9705895301610493,"hcy":[0.3260007657713445,0.9705895301610493,0.4865791267509881,1],"hue":0.3260007657713445,"in":"#3dd425ff","luma":0.4865791267509881,"roundtrip":{"hex":"#3dd425ff","u16":[15677,54484,9509,65535]}}, +{"chroma":1,"hcy":[0.45665136060931066,1,0.5066936611948379,1],"hue":0.45665136060931066,"in":"#00d3b8ff","luma":0.5066936611948379,"roundtrip":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"chroma":0.6122838512136782,"hcy":[0.7611689580060336,0.6122838512136782,0.2847150365241454,1],"hue":0.7611689580060336,"in":"#b875dcff","luma":0.2847150365241454,"roundtrip":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"chroma":0.7160920552505384,"hcy":[0.7029468871329987,0.7160920552505384,0.2312673381537172,1],"hue":0.7029468871329987,"in":"#926ee4ff","luma":0.2312673381537172,"roundtrip":{"hex":"#926ee4ff","u16":[37522,28270,58596,65535]}}, +{"chroma":0.05419426225979192,"hcy":[0.5968321943965229,0.05419426225979192,0.14698838493637675,1],"hue":0.5968321943965229,"in":"#686b6fff","luma":0.14698838493637675,"roundtrip":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"chroma":0.8859261608923455,"hcy":[0.5833623180737052,0.8859261608923455,0.37683495841856884,0.501960813999176],"hue":0.5833623180737052,"in":"#3daee980","luma":0.37683495841856884,"roundtrip":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"chroma":0,"hcy":[0,0,1,0],"hue":0,"in":"#ffffff00","luma":1,"roundtrip":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0,"hcy":[0,0,0,0.501960813999176],"hue":0,"in":"#00000080","luma":0,"roundtrip":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"hcy":[0,1,0.2126,0.20000000298023224],"hue":0,"in":"#ff000033","luma":0.2126,"roundtrip":{"hex":"#ff000033","u16":[65535,0,0,13107]}} +], +"contrast_ratio": [ +{"a":"#000000ff","b":"#000000ff","out":1}, +{"a":"#000000ff","b":"#ffffffff","out":21}, +{"a":"#000000ff","b":"#010101ff","out":1.0001015410512244}, +{"a":"#000000ff","b":"#020202ff","out":1.0004665601540241}, +{"a":"#000000ff","b":"#040404ff","out":1.0021437475257389}, +{"a":"#000000ff","b":"#080808ff","out":1.0098500770253804}, +{"a":"#000000ff","b":"#101010ff","out":1.0452590691025938}, +{"a":"#000000ff","b":"#202020ff","out":1.2079560729073868}, +{"a":"#000000ff","b":"#404040ff","out":1.9555151954414352}, +{"a":"#000000ff","b":"#7f7f7fff","out":5.315287994415714}, +{"a":"#000000ff","b":"#808080ff","out":5.390394932712988}, +{"a":"#000000ff","b":"#818181ff","out":5.46620873885754}, +{"a":"#000000ff","b":"#bfbfbfff","out":11.590464913679135}, +{"a":"#000000ff","b":"#c0c0c0ff","out":11.712832647248353}, +{"a":"#000000ff","b":"#fdfdfdff","out":20.656525121451825}, +{"a":"#000000ff","b":"#fefefeff","out":20.82785688209514}, +{"a":"#000000ff","b":"#ff0000ff","out":5.252}, +{"a":"#000000ff","b":"#00ff00ff","out":15.303999999999998}, +{"a":"#000000ff","b":"#0000ffff","out":2.444}, +{"a":"#000000ff","b":"#ffff00ff","out":19.555999999999997}, +{"a":"#000000ff","b":"#00ffffff","out":16.748}, +{"a":"#000000ff","b":"#ff00ffff","out":6.696}, +{"a":"#000000ff","b":"#800000ff","out":1.9333979626947817}, +{"a":"#000000ff","b":"#008000ff","out":4.140010455876329}, +{"a":"#000000ff","b":"#000080ff","out":1.316986514141878}, +{"a":"#000000ff","b":"#ff8080ff","out":8.708996970018207}, +{"a":"#000000ff","b":"#80ff80ff","out":16.554384476836656}, +{"a":"#000000ff","b":"#8080ffff","out":6.517408418571111}, +{"a":"#000000ff","b":"#030201ff","out":1.0005830464442786}, +{"a":"#000000ff","b":"#fffefdff","out":20.852084355843264}, +{"a":"#000000ff","b":"#7f8081ff","out":5.379900954434625}, +{"a":"#000000ff","b":"#010000ff","out":1.0000215876274903}, +{"a":"#000000ff","b":"#000001ff","out":1.0000073312638984}, +{"a":"#000000ff","b":"#fefffeff","out":20.950973640020692}, +{"a":"#000000ff","b":"#3daee9ff","out":8.536699168371376}, +{"a":"#000000ff","b":"#232629ff","out":1.2968129809250752}, +{"a":"#000000ff","b":"#eff0f1ff","out":18.480339969564877}, +{"a":"#000000ff","b":"#fcfcfcff","out":20.48600407819632}, +{"a":"#000000ff","b":"#31363bff","out":1.6408258465413632}, +{"a":"#000000ff","b":"#1b1e20ff","out":1.1744810676944255}, +{"a":"#000000ff","b":"#da4453ff","out":4.914792659570478}, +{"a":"#000000ff","b":"#27ae60ff","out":7.406546092850427}, +{"a":"#000000ff","b":"#f67400ff","out":7.457399609962234}, +{"a":"#000000ff","b":"#2980b9ff","out":4.9290580060461595}, +{"a":"#000000ff","b":"#7f8c8dff","out":6.1338781618810705}, +{"a":"#000000ff","b":"#9b59b6ff","out":4.521365088706011}, +{"a":"#000000ff","b":"#1d99f3ff","out":6.98364286071076}, +{"a":"#000000ff","b":"#dee0e2ff","out":15.996880046174788}, +{"a":"#000000ff","b":"#292c30ff","out":1.412586854085806}, +{"a":"#000000ff","b":"#e93a9aff","out":5.5129286532739625}, +{"a":"#000000ff","b":"#e93d58ff","out":5.24038020509024}, +{"a":"#000000ff","b":"#e9643aff","out":6.366226372236576}, +{"a":"#000000ff","b":"#e8cb2dff","out":13.146285343189582}, +{"a":"#000000ff","b":"#3dd425ff","out":10.731582535019761}, +{"a":"#000000ff","b":"#00d3b8ff","out":11.133873223896758}, +{"a":"#000000ff","b":"#b875dcff","out":6.694300730482908}, +{"a":"#000000ff","b":"#926ee4ff","out":5.625346763074344}, +{"a":"#000000ff","b":"#686b6fff","out":3.939767698727535}, +{"a":"#000000ff","b":"#3daee980","out":8.536699168371376}, +{"a":"#000000ff","b":"#ffffff00","out":21}, +{"a":"#000000ff","b":"#00000080","out":1}, +{"a":"#000000ff","b":"#ff000033","out":5.252}, +{"a":"#ffffffff","b":"#000000ff","out":21}, +{"a":"#ffffffff","b":"#ffffffff","out":1}, +{"a":"#ffffffff","b":"#010101ff","out":20.99786785442459}, +{"a":"#ffffffff","b":"#020202ff","out":20.99020680587965}, +{"a":"#ffffffff","b":"#040404ff","out":20.95507760423426}, +{"a":"#ffffffff","b":"#080808ff","out":20.795166013016217}, +{"a":"#ffffffff","b":"#101010ff","out":20.090713030626492}, +{"a":"#ffffffff","b":"#202020ff","out":17.38473812996845}, +{"a":"#ffffffff","b":"#404040ff","out":10.738857999648268}, +{"a":"#ffffffff","b":"#7f7f7fff","out":3.950867765220394}, +{"a":"#ffffffff","b":"#808080ff","out":3.895818444128488}, +{"a":"#ffffffff","b":"#818181ff","out":3.8417852305415403}, +{"a":"#ffffffff","b":"#bfbfbfff","out":1.8118341374913856}, +{"a":"#ffffffff","b":"#c0c0c0ff","out":1.7929053229436724}, +{"a":"#ffffffff","b":"#fdfdfdff","out":1.0166279118355426}, +{"a":"#ffffffff","b":"#fefefeff","out":1.0082650422882848}, +{"a":"#ffffffff","b":"#ff0000ff","out":3.9984767707539985}, +{"a":"#ffffffff","b":"#00ff00ff","out":1.3721902770517513}, +{"a":"#ffffffff","b":"#0000ffff","out":8.592471358428805}, +{"a":"#ffffffff","b":"#ffff00ff","out":1.0738392309265699}, +{"a":"#ffffffff","b":"#00ffffff","out":1.2538810604251254}, +{"a":"#ffffffff","b":"#ff00ffff","out":3.1362007168458783}, +{"a":"#ffffffff","b":"#800000ff","out":10.86170586976831}, +{"a":"#ffffffff","b":"#008000ff","out":5.072450957265725}, +{"a":"#ffffffff","b":"#000080ff","out":15.945493575295401}, +{"a":"#ffffffff","b":"#ff8080ff","out":2.411299495486688}, +{"a":"#ffffffff","b":"#80ff80ff","out":1.2685461080950347}, +{"a":"#ffffffff","b":"#8080ffff","out":3.222139637614437}, +{"a":"#ffffffff","b":"#030201ff","out":20.987763159316597}, +{"a":"#ffffffff","b":"#fffefdff","out":1.0070935663616423}, +{"a":"#ffffffff","b":"#7f8081ff","out":3.903417586654603}, +{"a":"#ffffffff","b":"#010000ff","out":20.999546669609032}, +{"a":"#ffffffff","b":"#000001ff","out":20.99984604458682}, +{"a":"#ffffffff","b":"#fefffeff","out":1.0023400516282288}, +{"a":"#ffffffff","b":"#3daee9ff","out":2.459967205803079}, +{"a":"#ffffffff","b":"#232629ff","out":16.193545491054348}, +{"a":"#ffffffff","b":"#eff0f1ff","out":1.136342731496538}, +{"a":"#ffffffff","b":"#fcfcfcff","out":1.0250901015074352}, +{"a":"#ffffffff","b":"#31363bff","out":12.798433206220594}, +{"a":"#ffffffff","b":"#1b1e20ff","out":17.880237134195973}, +{"a":"#ffffffff","b":"#da4453ff","out":4.272815041160916}, +{"a":"#ffffffff","b":"#27ae60ff","out":2.8353296849487504}, +{"a":"#ffffffff","b":"#f67400ff","out":2.8159949980347574}, +{"a":"#ffffffff","b":"#2980b9ff","out":4.260448948712035}, +{"a":"#ffffffff","b":"#7f8c8dff","out":3.423608921759207}, +{"a":"#ffffffff","b":"#9b59b6ff","out":4.6446149753436705}, +{"a":"#ffffffff","b":"#1d99f3ff","out":3.007026621900125}, +{"a":"#ffffffff","b":"#dee0e2ff","out":1.3127559836282932}, +{"a":"#ffffffff","b":"#292c30ff","out":14.866342511442046}, +{"a":"#ffffffff","b":"#e93a9aff","out":3.809227603105063}, +{"a":"#ffffffff","b":"#e93d58ff","out":4.00734282211082}, +{"a":"#ffffffff","b":"#e9643aff","out":3.298657441963111}, +{"a":"#ffffffff","b":"#e8cb2dff","out":1.5974094165603232}, +{"a":"#ffffffff","b":"#3dd425ff","out":1.9568409348268905}, +{"a":"#ffffffff","b":"#00d3b8ff","out":1.8861360802032001}, +{"a":"#ffffffff","b":"#b875dcff","out":3.136996804516895}, +{"a":"#ffffffff","b":"#926ee4ff","out":3.733103199583586}, +{"a":"#ffffffff","b":"#686b6fff","out":5.330263509389798}, +{"a":"#ffffffff","b":"#3daee980","out":2.459967205803079}, +{"a":"#ffffffff","b":"#ffffff00","out":1}, +{"a":"#ffffffff","b":"#00000080","out":21}, +{"a":"#ffffffff","b":"#ff000033","out":3.9984767707539985}, +{"a":"#7f7f7fff","b":"#000000ff","out":5.315287994415714}, +{"a":"#7f7f7fff","b":"#ffffffff","out":3.950867765220394}, +{"a":"#7f7f7fff","b":"#010101ff","out":5.314748329283365}, +{"a":"#7f7f7fff","b":"#020202ff","out":5.3128092493140535}, +{"a":"#7f7f7fff","b":"#040404ff","out":5.30391773389695}, +{"a":"#7f7f7fff","b":"#080808ff","out":5.263442678612704}, +{"a":"#7f7f7fff","b":"#101010ff","out":5.0851393224257295}, +{"a":"#7f7f7fff","b":"#202020ff","out":4.40023285087059}, +{"a":"#7f7f7fff","b":"#404040ff","out":2.718101095203123}, +{"a":"#7f7f7fff","b":"#7f7f7fff","out":1}, +{"a":"#7f7f7fff","b":"#808080ff","out":1.0141303610220525}, +{"a":"#7f7f7fff","b":"#818181ff","out":1.0283937097294418}, +{"a":"#7f7f7fff","b":"#bfbfbfff","out":2.180590200541565}, +{"a":"#7f7f7fff","b":"#c0c0c0ff","out":2.203612045020694}, +{"a":"#7f7f7fff","b":"#fdfdfdff","out":3.8862475830385383}, +{"a":"#7f7f7fff","b":"#fefefeff","out":3.9184813511473062}, +{"a":"#7f7f7fff","b":"#ff0000ff","out":1.0120502655018495}, +{"a":"#7f7f7fff","b":"#00ff00ff","out":2.879241918044424}, +{"a":"#7f7f7fff","b":"#0000ffff","out":2.1748314216103575}, +{"a":"#7f7f7fff","b":"#ffff00ff","out":3.6791985722214293}, +{"a":"#7f7f7fff","b":"#00ffffff","out":3.1509111110433885}, +{"a":"#7f7f7fff","b":"#ff00ffff","out":1.2597624074245597}, +{"a":"#7f7f7fff","b":"#800000ff","out":2.7491949908787707}, +{"a":"#7f7f7fff","b":"#008000ff","out":1.2838827464484288}, +{"a":"#7f7f7fff","b":"#000080ff","out":4.035947169800026}, +{"a":"#7f7f7fff","b":"#ff8080ff","out":1.6384807331546198}, +{"a":"#7f7f7fff","b":"#80ff80ff","out":3.1144849525047062}, +{"a":"#7f7f7fff","b":"#8080ffff","out":1.2261628016051727}, +{"a":"#7f7f7fff","b":"#030201ff","out":5.312190740493139}, +{"a":"#7f7f7fff","b":"#fffefdff","out":3.9230394247217912}, +{"a":"#7f7f7fff","b":"#7f8081ff","out":1.0121560600454378}, +{"a":"#7f7f7fff","b":"#010000ff","out":5.3151732524354935}, +{"a":"#7f7f7fff","b":"#000001ff","out":5.3152490269224115}, +{"a":"#7f7f7fff","b":"#fefffeff","out":3.941644114492378}, +{"a":"#7f7f7fff","b":"#3daee9ff","out":1.6060652174143912}, +{"a":"#7f7f7fff","b":"#232629ff","out":4.098731330267899}, +{"a":"#7f7f7fff","b":"#eff0f1ff","out":3.476827594098472}, +{"a":"#7f7f7fff","b":"#fcfcfcff","out":3.8541663405104467}, +{"a":"#7f7f7fff","b":"#31363bff","out":3.2393980175407493}, +{"a":"#7f7f7fff","b":"#1b1e20ff","out":4.5256480846046605}, +{"a":"#7f7f7fff","b":"#da4453ff","out":1.0814877376495953}, +{"a":"#7f7f7fff","b":"#27ae60ff","out":1.3934421052315147}, +{"a":"#7f7f7fff","b":"#f67400ff","out":1.4030095110174727}, +{"a":"#7f7f7fff","b":"#2980b9ff","out":1.0783577689481014}, +{"a":"#7f7f7fff","b":"#7f8c8dff","out":1.1540067383602497}, +{"a":"#7f7f7fff","b":"#9b59b6ff","out":1.1755936293870308}, +{"a":"#7f7f7fff","b":"#1d99f3ff","out":1.3138785458187465}, +{"a":"#7f7f7fff","b":"#dee0e2ff","out":3.009597986596633}, +{"a":"#7f7f7fff","b":"#292c30ff","out":3.7628043748542788}, +{"a":"#7f7f7fff","b":"#e93a9aff","out":1.037183433722856}, +{"a":"#7f7f7fff","b":"#e93d58ff","out":1.0142943424701727}, +{"a":"#7f7f7fff","b":"#e9643aff","out":1.1977199314364504}, +{"a":"#7f7f7fff","b":"#e8cb2dff","out":2.473296904514145}, +{"a":"#7f7f7fff","b":"#3dd425ff","out":2.0190030241624632}, +{"a":"#7f7f7fff","b":"#00d3b8ff","out":2.0946886105878177}, +{"a":"#7f7f7fff","b":"#b875dcff","out":1.2594427127026788}, +{"a":"#7f7f7fff","b":"#926ee4ff","out":1.058333390210348}, +{"a":"#7f7f7fff","b":"#686b6fff","out":1.3491374113586554}, +{"a":"#7f7f7fff","b":"#3daee980","out":1.6060652174143912}, +{"a":"#7f7f7fff","b":"#ffffff00","out":3.950867765220394}, +{"a":"#7f7f7fff","b":"#00000080","out":5.315287994415714}, +{"a":"#7f7f7fff","b":"#ff000033","out":1.0120502655018495}, +{"a":"#808080ff","b":"#000000ff","out":5.390394932712988}, +{"a":"#808080ff","b":"#ffffffff","out":3.895818444128488}, +{"a":"#808080ff","b":"#010101ff","out":5.3898476419174886}, +{"a":"#808080ff","b":"#020202ff","out":5.38788116204816}, +{"a":"#808080ff","b":"#040404ff","out":5.37886400630818}, +{"a":"#808080ff","b":"#080808ff","out":5.33781702388038}, +{"a":"#808080ff","b":"#101010ff","out":5.15699417689904}, +{"a":"#808080ff","b":"#202020ff","out":4.462409729634486}, +{"a":"#808080ff","b":"#404040ff","out":2.756508844972779}, +{"a":"#808080ff","b":"#7f7f7fff","out":1.0141303610220525}, +{"a":"#808080ff","b":"#808080ff","out":1}, +{"a":"#808080ff","b":"#818181ff","out":1.0140646106808346}, +{"a":"#808080ff","b":"#bfbfbfff","out":2.1502069993683466}, +{"a":"#808080ff","b":"#c0c0c0ff","out":2.1729080695304974}, +{"a":"#808080ff","b":"#fdfdfdff","out":3.832098645702642}, +{"a":"#808080ff","b":"#fefefeff","out":3.8638832853778435}, +{"a":"#808080ff","b":"#ff0000ff","out":1.0263509011258547}, +{"a":"#808080ff","b":"#00ff00ff","out":2.839124069949637}, +{"a":"#808080ff","b":"#0000ffff","out":2.2055625747598153}, +{"a":"#808080ff","b":"#ffff00ff","out":3.627934547303653}, +{"a":"#808080ff","b":"#00ffffff","out":3.1070079667744723}, +{"a":"#808080ff","b":"#ff00ffff","out":1.2422095381849692}, +{"a":"#808080ff","b":"#800000ff","out":2.7880421086199054}, +{"a":"#808080ff","b":"#008000ff","out":1.3020244731657293}, +{"a":"#808080ff","b":"#000080ff","out":4.092976560375231}, +{"a":"#808080ff","b":"#ff8080ff","out":1.6156510012217167}, +{"a":"#808080ff","b":"#80ff80ff","out":3.0710893512406945}, +{"a":"#808080ff","b":"#8080ffff","out":1.2090780916660768}, +{"a":"#808080ff","b":"#030201ff","out":5.387253913474311}, +{"a":"#808080ff","b":"#fffefdff","out":3.8683778491436804}, +{"a":"#808080ff","b":"#7f8081ff","out":1.0019505894936063}, +{"a":"#808080ff","b":"#010000ff","out":5.3902785693871635}, +{"a":"#808080ff","b":"#000001ff","out":5.3903554145949375}, +{"a":"#808080ff","b":"#fefffeff","out":3.8867233109163037}, +{"a":"#808080ff","b":"#3daee9ff","out":1.5836871462913111}, +{"a":"#808080ff","b":"#232629ff","out":4.156647883696982}, +{"a":"#808080ff","b":"#eff0f1ff","out":3.428383300342655}, +{"a":"#808080ff","b":"#fcfcfcff","out":3.8004644063966024}, +{"a":"#808080ff","b":"#31363bff","out":3.2851718810227206}, +{"a":"#808080ff","b":"#1b1e20ff","out":4.589597125898884}, +{"a":"#808080ff","b":"#da4453ff","out":1.0967695498235066}, +{"a":"#808080ff","b":"#27ae60ff","out":1.374026613134023}, +{"a":"#808080ff","b":"#f67400ff","out":1.3834607117013078}, +{"a":"#808080ff","b":"#2980b9ff","out":1.0935953535342728}, +{"a":"#808080ff","b":"#7f8c8dff","out":1.1379274131949153}, +{"a":"#808080ff","b":"#9b59b6ff","out":1.1922051917854943}, +{"a":"#808080ff","b":"#1d99f3ff","out":1.295571650664915}, +{"a":"#808080ff","b":"#dee0e2ff","out":2.9676638253523198}, +{"a":"#808080ff","b":"#292c30ff","out":3.815974159126328}, +{"a":"#808080ff","b":"#e93a9aff","out":1.022731863266149}, +{"a":"#808080ff","b":"#e93d58ff","out":1.0286266877119015}, +{"a":"#808080ff","b":"#e9643aff","out":1.1810315295455451}, +{"a":"#808080ff","b":"#e8cb2dff","out":2.4388352815130467}, +{"a":"#808080ff","b":"#3dd425ff","out":1.9908712940293876}, +{"a":"#808080ff","b":"#00d3b8ff","out":2.0655023171545386}, +{"a":"#808080ff","b":"#b875dcff","out":1.2418942979218155}, +{"a":"#808080ff","b":"#926ee4ff","out":1.0435871273430246}, +{"a":"#808080ff","b":"#686b6fff","out":1.3682012100495102}, +{"a":"#808080ff","b":"#3daee980","out":1.5836871462913111}, +{"a":"#808080ff","b":"#ffffff00","out":3.895818444128488}, +{"a":"#808080ff","b":"#00000080","out":5.390394932712988}, +{"a":"#808080ff","b":"#ff000033","out":1.0263509011258547}, +{"a":"#ff0000ff","b":"#000000ff","out":5.252}, +{"a":"#ff0000ff","b":"#ffffffff","out":3.9984767707539985}, +{"a":"#ff0000ff","b":"#010101ff","out":5.251466760544664}, +{"a":"#ff0000ff","b":"#020202ff","out":5.249550768784759}, +{"a":"#ff0000ff","b":"#040404ff","out":5.240765122735158}, +{"a":"#ff0000ff","b":"#080808ff","out":5.200771995255294}, +{"a":"#ff0000ff","b":"#101010ff","out":5.024591658897634}, +{"a":"#ff0000ff","b":"#202020ff","out":4.347840221837824}, +{"a":"#ff0000ff","b":"#404040ff","out":2.6857372482929858}, +{"a":"#ff0000ff","b":"#7f7f7fff","out":1.0120502655018495}, +{"a":"#ff0000ff","b":"#808080ff","out":1.0263509011258547}, +{"a":"#ff0000ff","b":"#818181ff","out":1.0407861269721137}, +{"a":"#ff0000ff","b":"#bfbfbfff","out":2.2068668914088225}, +{"a":"#ff0000ff","b":"#c0c0c0ff","out":2.230166155226267}, +{"a":"#ff0000ff","b":"#fdfdfdff","out":3.933077898220074}, +{"a":"#ff0000ff","b":"#fefefeff","out":3.9657000917926775}, +{"a":"#ff0000ff","b":"#ff0000ff","out":1}, +{"a":"#ff0000ff","b":"#00ff00ff","out":2.9139375476009137}, +{"a":"#ff0000ff","b":"#0000ffff","out":2.148936170212766}, +{"a":"#ff0000ff","b":"#ffff00ff","out":3.7235338918507237}, +{"a":"#ff0000ff","b":"#00ffffff","out":3.188880426504189}, +{"a":"#ff0000ff","b":"#ff00ffff","out":1.2749428789032748}, +{"a":"#ff0000ff","b":"#800000ff","out":2.7164609156201505}, +{"a":"#ff0000ff","b":"#008000ff","out":1.26859582988379}, +{"a":"#ff0000ff","b":"#000080ff","out":3.9878920122595924}, +{"a":"#ff0000ff","b":"#ff8080ff","out":1.6582248610087982}, +{"a":"#ff0000ff","b":"#80ff80ff","out":3.152015323083903}, +{"a":"#ff0000ff","b":"#8080ffff","out":1.2409383889130068}, +{"a":"#ff0000ff","b":"#030201ff","out":5.24893962441575}, +{"a":"#ff0000ff","b":"#fffefdff","out":3.970313091363912}, +{"a":"#ff0000ff","b":"#7f8081ff","out":1.0243528092982912}, +{"a":"#ff0000ff","b":"#010000ff","out":5.251886624227934}, +{"a":"#ff0000ff","b":"#000001ff","out":5.2519614964842845}, +{"a":"#ff0000ff","b":"#fefffeff","out":3.9891419725858137}, +{"a":"#ff0000ff","b":"#3daee9ff","out":1.6254187296975202}, +{"a":"#ff0000ff","b":"#232629ff","out":4.049928615191306}, +{"a":"#ff0000ff","b":"#eff0f1ff","out":3.5187242897115154}, +{"a":"#ff0000ff","b":"#fcfcfcff","out":3.9006100682018894}, +{"a":"#ff0000ff","b":"#31363bff","out":3.2008271999557407}, +{"a":"#ff0000ff","b":"#1b1e20ff","out":4.4717621632760585}, +{"a":"#ff0000ff","b":"#da4453ff","out":1.0686106950560539}, +{"a":"#ff0000ff","b":"#27ae60ff","out":1.4102334525610107}, +{"a":"#ff0000ff","b":"#f67400ff","out":1.4199161481268534}, +{"a":"#ff0000ff","b":"#2980b9ff","out":1.0655179942207431}, +{"a":"#ff0000ff","b":"#7f8c8dff","out":1.1679128259484142}, +{"a":"#ff0000ff","b":"#9b59b6ff","out":1.1615960881192837}, +{"a":"#ff0000ff","b":"#1d99f3ff","out":1.3297111311330465}, +{"a":"#ff0000ff","b":"#dee0e2ff","out":3.045864441388954}, +{"a":"#ff0000ff","b":"#292c30ff","out":3.718001470004458}, +{"a":"#ff0000ff","b":"#e93a9aff","out":1.0496817694733365}, +{"a":"#ff0000ff","b":"#e93d58ff","out":1.0022173572250488}, +{"a":"#ff0000ff","b":"#e9643aff","out":1.2121527746071166}, +{"a":"#ff0000ff","b":"#e8cb2dff","out":2.503100788878443}, +{"a":"#ff0000ff","b":"#3dd425ff","out":2.043332546652658}, +{"a":"#ff0000ff","b":"#00d3b8ff","out":2.119930164489101}, +{"a":"#ff0000ff","b":"#b875dcff","out":1.2746193317751158}, +{"a":"#ff0000ff","b":"#926ee4ff","out":1.0710865885518552}, +{"a":"#ff0000ff","b":"#686b6fff","out":1.3330735214912008}, +{"a":"#ff0000ff","b":"#3daee980","out":1.6254187296975202}, +{"a":"#ff0000ff","b":"#ffffff00","out":3.9984767707539985}, +{"a":"#ff0000ff","b":"#00000080","out":5.252}, +{"a":"#ff0000ff","b":"#ff000033","out":1}, +{"a":"#00ff00ff","b":"#000000ff","out":15.303999999999998}, +{"a":"#00ff00ff","b":"#ffffffff","out":1.3721902770517513}, +{"a":"#00ff00ff","b":"#010101ff","out":15.302446173529235}, +{"a":"#00ff00ff","b":"#020202ff","out":15.296863093199152}, +{"a":"#00ff00ff","b":"#040404ff","out":15.271262269295288}, +{"a":"#00ff00ff","b":"#080808ff","out":15.154724793485723}, +{"a":"#00ff00ff","b":"#101010ff","out":14.641346296224182}, +{"a":"#00ff00ff","b":"#202020ff","out":12.669334873382722}, +{"a":"#00ff00ff","b":"#404040ff","out":7.826070610791289}, +{"a":"#00ff00ff","b":"#7f7f7fff","out":2.879241918044424}, +{"a":"#00ff00ff","b":"#808080ff","out":2.839124069949637}, +{"a":"#00ff00ff","b":"#818181ff","out":2.7997467222956063}, +{"a":"#00ff00ff","b":"#bfbfbfff","out":1.3203956971508648}, +{"a":"#00ff00ff","b":"#c0c0c0ff","out":1.3066010982061886}, +{"a":"#00ff00ff","b":"#fdfdfdff","out":1.3497468061586402}, +{"a":"#00ff00ff","b":"#fefefeff","out":1.3609420335922073}, +{"a":"#00ff00ff","b":"#ff0000ff","out":2.9139375476009137}, +{"a":"#00ff00ff","b":"#00ff00ff","out":1}, +{"a":"#00ff00ff","b":"#0000ffff","out":6.261865793780687}, +{"a":"#00ff00ff","b":"#ffff00ff","out":1.277835859905907}, +{"a":"#00ff00ff","b":"#00ffffff","out":1.0943544171458444}, +{"a":"#00ff00ff","b":"#ff00ffff","out":2.2855436081242533}, +{"a":"#00ff00ff","b":"#800000ff","out":7.915597458615914}, +{"a":"#00ff00ff","b":"#008000ff","out":3.6966090214283174}, +{"a":"#00ff00ff","b":"#000080ff","out":11.620468270300991}, +{"a":"#00ff00ff","b":"#ff8080ff","out":1.7572632132822987}, +{"a":"#00ff00ff","b":"#80ff80ff","out":1.0817031153186525}, +{"a":"#00ff00ff","b":"#8080ffff","out":2.3481726197167303}, +{"a":"#00ff00ff","b":"#030201ff","out":15.295082256675295}, +{"a":"#00ff00ff","b":"#fffefdff","out":1.3625251147310027}, +{"a":"#00ff00ff","b":"#7f8081ff","out":2.8446620355315257}, +{"a":"#00ff00ff","b":"#010000ff","out":15.30366963008079}, +{"a":"#00ff00ff","b":"#000001ff","out":15.303887803159842}, +{"a":"#00ff00ff","b":"#fefffeff","out":1.368986777314473}, +{"a":"#00ff00ff","b":"#3daee9ff","out":1.792730386552872}, +{"a":"#00ff00ff","b":"#232629ff","out":11.80123905690932}, +{"a":"#00ff00ff","b":"#eff0f1ff","out":1.2075496582308467}, +{"a":"#00ff00ff","b":"#fcfcfcff","out":1.3386045529401676}, +{"a":"#00ff00ff","b":"#31363bff","out":9.327010561333331}, +{"a":"#00ff00ff","b":"#1b1e20ff","out":13.030435671511196}, +{"a":"#00ff00ff","b":"#da4453ff","out":3.1138648280917454}, +{"a":"#00ff00ff","b":"#27ae60ff","out":2.0662802618312224}, +{"a":"#00ff00ff","b":"#f67400ff","out":2.052189878567806}, +{"a":"#00ff00ff","b":"#2980b9ff","out":3.1048528910042372}, +{"a":"#00ff00ff","b":"#7f8c8dff","out":2.4949957589810907}, +{"a":"#00ff00ff","b":"#9b59b6ff","out":3.3848184563171206}, +{"a":"#00ff00ff","b":"#1d99f3ff","out":2.1914064486456906}, +{"a":"#00ff00ff","b":"#dee0e2ff","out":1.0452744410725816}, +{"a":"#00ff00ff","b":"#292c30ff","out":10.834024085481383}, +{"a":"#00ff00ff","b":"#e93a9aff","out":2.7760199637104703}, +{"a":"#00ff00ff","b":"#e93d58ff","out":2.9203987880754276}, +{"a":"#00ff00ff","b":"#e9643aff","out":2.4039358805620687}, +{"a":"#00ff00ff","b":"#e8cb2dff","out":1.1641311290971041}, +{"a":"#00ff00ff","b":"#3dd425ff","out":1.4260711269805109}, +{"a":"#00ff00ff","b":"#00d3b8ff","out":1.374544122449037}, +{"a":"#00ff00ff","b":"#b875dcff","out":2.286123766491741}, +{"a":"#00ff00ff","b":"#926ee4ff","out":2.720543398401295}, +{"a":"#00ff00ff","b":"#686b6fff","out":3.884492987985784}, +{"a":"#00ff00ff","b":"#3daee980","out":1.792730386552872}, +{"a":"#00ff00ff","b":"#ffffff00","out":1.3721902770517513}, +{"a":"#00ff00ff","b":"#00000080","out":15.303999999999998}, +{"a":"#00ff00ff","b":"#ff000033","out":2.9139375476009137}, +{"a":"#0000ffff","b":"#000000ff","out":2.444}, +{"a":"#0000ffff","b":"#ffffffff","out":8.592471358428805}, +{"a":"#0000ffff","b":"#010101ff","out":2.443751858867319}, +{"a":"#0000ffff","b":"#020202ff","out":2.442860258741422}, +{"a":"#0000ffff","b":"#040404ff","out":2.438771888797549}, +{"a":"#0000ffff","b":"#080808ff","out":2.42016122551484}, +{"a":"#0000ffff","b":"#101010ff","out":2.338176316516721}, +{"a":"#0000ffff","b":"#202020ff","out":2.0232523804591853}, +{"a":"#0000ffff","b":"#404040ff","out":1.2497985214828746}, +{"a":"#0000ffff","b":"#7f7f7fff","out":2.1748314216103575}, +{"a":"#0000ffff","b":"#808080ff","out":2.2055625747598153}, +{"a":"#0000ffff","b":"#818181ff","out":2.2365829537060313}, +{"a":"#0000ffff","b":"#bfbfbfff","out":4.742416085793427}, +{"a":"#0000ffff","b":"#c0c0c0ff","out":4.792484716550063}, +{"a":"#0000ffff","b":"#fdfdfdff","out":8.45193335574952}, +{"a":"#0000ffff","b":"#fefefeff","out":8.52203636746937}, +{"a":"#0000ffff","b":"#ff0000ff","out":2.148936170212766}, +{"a":"#0000ffff","b":"#00ff00ff","out":6.261865793780687}, +{"a":"#0000ffff","b":"#0000ffff","out":1}, +{"a":"#0000ffff","b":"#ffff00ff","out":8.00163666121113}, +{"a":"#0000ffff","b":"#00ffffff","out":6.852700490998363}, +{"a":"#0000ffff","b":"#ff00ffff","out":2.7397708674304417}, +{"a":"#0000ffff","b":"#800000ff","out":1.2640956736054165}, +{"a":"#0000ffff","b":"#008000ff","out":1.6939486317006258}, +{"a":"#0000ffff","b":"#000080ff","out":1.8557517284772362}, +{"a":"#0000ffff","b":"#ff8080ff","out":3.563419382167843}, +{"a":"#0000ffff","b":"#80ff80ff","out":6.773479736839876}, +{"a":"#0000ffff","b":"#8080ffff","out":2.6666973889407166}, +{"a":"#0000ffff","b":"#030201ff","out":2.4425758648271314}, +{"a":"#0000ffff","b":"#fffefdff","out":8.531949409101172}, +{"a":"#0000ffff","b":"#7f8081ff","out":2.2012688029601577}, +{"a":"#0000ffff","b":"#010000ff","out":2.4439472409773555}, +{"a":"#0000ffff","b":"#000001ff","out":2.44398208252239}, +{"a":"#0000ffff","b":"#fefffeff","out":8.572411473003557}, +{"a":"#0000ffff","b":"#3daee9ff","out":3.4929210999882883}, +{"a":"#0000ffff","b":"#232629ff","out":1.884620246673182}, +{"a":"#0000ffff","b":"#eff0f1ff","out":7.5615138991672985}, +{"a":"#0000ffff","b":"#fcfcfcff","out":8.382162061455123}, +{"a":"#0000ffff","b":"#31363bff","out":1.4894938455239586}, +{"a":"#0000ffff","b":"#1b1e20ff","out":2.080919026474998}, +{"a":"#0000ffff","b":"#da4453ff","out":2.0109626266654983}, +{"a":"#0000ffff","b":"#27ae60ff","out":3.0305016746523843}, +{"a":"#0000ffff","b":"#f67400ff","out":3.0513091693789827}, +{"a":"#0000ffff","b":"#2980b9ff","out":2.0167995114755155}, +{"a":"#0000ffff","b":"#7f8c8dff","out":2.5097701153359537}, +{"a":"#0000ffff","b":"#9b59b6ff","out":1.8499857155098243}, +{"a":"#0000ffff","b":"#1d99f3ff","out":2.857464345626334}, +{"a":"#0000ffff","b":"#dee0e2ff","out":6.5453682676656255}, +{"a":"#0000ffff","b":"#292c30ff","out":1.7301590999030647}, +{"a":"#0000ffff","b":"#e93a9aff","out":2.255699121634191}, +{"a":"#0000ffff","b":"#e93d58ff","out":2.1441817533102454}, +{"a":"#0000ffff","b":"#e9643aff","out":2.604838941176995}, +{"a":"#0000ffff","b":"#e8cb2dff","out":5.379003822908994}, +{"a":"#0000ffff","b":"#3dd425ff","out":4.390991217274862}, +{"a":"#0000ffff","b":"#00d3b8ff","out":4.555594608795728}, +{"a":"#0000ffff","b":"#b875dcff","out":2.739075585303972}, +{"a":"#0000ffff","b":"#926ee4ff","out":2.3016967115688804}, +{"a":"#0000ffff","b":"#686b6fff","out":1.612016243341872}, +{"a":"#0000ffff","b":"#3daee980","out":3.4929210999882883}, +{"a":"#0000ffff","b":"#ffffff00","out":8.592471358428805}, +{"a":"#0000ffff","b":"#00000080","out":2.444}, +{"a":"#0000ffff","b":"#ff000033","out":2.148936170212766}, +{"a":"#ffff00ff","b":"#000000ff","out":19.555999999999997}, +{"a":"#ffff00ff","b":"#ffffffff","out":1.0738392309265699}, +{"a":"#ffff00ff","b":"#010101ff","out":19.554014464815584}, +{"a":"#ffff00ff","b":"#020202ff","out":19.54688020456107}, +{"a":"#ffff00ff","b":"#040404ff","out":19.514166553733578}, +{"a":"#ffff00ff","b":"#080808ff","out":19.365250788121198}, +{"a":"#ffff00ff","b":"#101010ff","out":18.709237334615793}, +{"a":"#ffff00ff","b":"#202020ff","out":16.189330422364907}, +{"a":"#ffff00ff","b":"#404040ff","out":10.000433668624833}, +{"a":"#ffff00ff","b":"#7f7f7fff","out":3.6791985722214293}, +{"a":"#ffff00ff","b":"#808080ff","out":3.627934547303653}, +{"a":"#ffff00ff","b":"#818181ff","out":3.5776167604033504}, +{"a":"#ffff00ff","b":"#bfbfbfff","out":1.6872489710848348}, +{"a":"#ffff00ff","b":"#c0c0c0ff","out":1.6696217378803075}, +{"a":"#ffff00ff","b":"#fdfdfdff","out":1.0562755738112}, +{"a":"#ffff00ff","b":"#fefefeff","out":1.0650366579103672}, +{"a":"#ffff00ff","b":"#ff0000ff","out":3.7235338918507237}, +{"a":"#ffff00ff","b":"#00ff00ff","out":1.277835859905907}, +{"a":"#ffff00ff","b":"#0000ffff","out":8.00163666121113}, +{"a":"#ffff00ff","b":"#ffff00ff","out":1}, +{"a":"#ffff00ff","b":"#00ffffff","out":1.1676618103654168}, +{"a":"#ffff00ff","b":"#ff00ffff","out":2.9205495818399045}, +{"a":"#ffff00ff","b":"#800000ff","out":10.114834285199478}, +{"a":"#ffff00ff","b":"#008000ff","out":4.723659567632787}, +{"a":"#ffff00ff","b":"#000080ff","out":14.849051064689373}, +{"a":"#ffff00ff","b":"#ff8080ff","out":2.2454939492256036}, +{"a":"#ffff00ff","b":"#80ff80ff","out":1.181318461424119}, +{"a":"#ffff00ff","b":"#8080ffff","out":3.0005791787232345}, +{"a":"#ffff00ff","b":"#030201ff","out":19.544604587790253}, +{"a":"#ffff00ff","b":"#fffefdff","out":1.066275534661652}, +{"a":"#ffff00ff","b":"#7f8081ff","out":3.635011158315115}, +{"a":"#ffff00ff","b":"#010000ff","out":19.5555778414702}, +{"a":"#ffff00ff","b":"#000001ff","out":19.55585663085428}, +{"a":"#ffff00ff","b":"#fefffeff","out":1.0713322581315552}, +{"a":"#ffff00ff","b":"#3daee9ff","out":2.290815175080238}, +{"a":"#ffff00ff","b":"#232629ff","out":15.080046458240895}, +{"a":"#ffff00ff","b":"#eff0f1ff","out":1.0582056408164902}, +{"a":"#ffff00ff","b":"#fcfcfcff","out":1.0475559459089958}, +{"a":"#ffff00ff","b":"#31363bff","out":11.918388560992854}, +{"a":"#ffff00ff","b":"#1b1e20ff","out":16.650757971254112}, +{"a":"#ffff00ff","b":"#da4453ff","out":3.9790081402353747}, +{"a":"#ffff00ff","b":"#27ae60ff","out":2.6403670151837026}, +{"a":"#ffff00ff","b":"#f67400ff","out":2.622361818169891}, +{"a":"#ffff00ff","b":"#2980b9ff","out":3.967492363857741}, +{"a":"#ffff00ff","b":"#7f8c8dff","out":3.1881950511391928}, +{"a":"#ffff00ff","b":"#9b59b6ff","out":4.325242402753372}, +{"a":"#ffff00ff","b":"#1d99f3ff","out":2.800257743708516}, +{"a":"#ffff00ff","b":"#dee0e2ff","out":1.2224883817064238}, +{"a":"#ffff00ff","b":"#292c30ff","out":13.84410448351241}, +{"a":"#ffff00ff","b":"#e93a9aff","out":3.5472978574439336}, +{"a":"#ffff00ff","b":"#e93d58ff","out":3.731790296628533}, +{"a":"#ffff00ff","b":"#e9643aff","out":3.071835473096695}, +{"a":"#ffff00ff","b":"#e8cb2dff","out":1.4875685023930323}, +{"a":"#ffff00ff","b":"#3dd425ff","out":1.822284824832127}, +{"a":"#ffff00ff","b":"#00d3b8ff","out":1.7564417706882753}, +{"a":"#ffff00ff","b":"#b875dcff","out":2.9212909290063047}, +{"a":"#ffff00ff","b":"#926ee4ff","out":3.476407912907457}, +{"a":"#ffff00ff","b":"#686b6fff","out":4.96374443760128}, +{"a":"#ffff00ff","b":"#3daee980","out":2.290815175080238}, +{"a":"#ffff00ff","b":"#ffffff00","out":1.0738392309265699}, +{"a":"#ffff00ff","b":"#00000080","out":19.555999999999997}, +{"a":"#ffff00ff","b":"#ff000033","out":3.7235338918507237}, +{"a":"#030201ff","b":"#000000ff","out":1.0005830464442786}, +{"a":"#030201ff","b":"#ffffffff","out":20.987763159316597}, +{"a":"#030201ff","b":"#010101ff","out":1.0004814565054547}, +{"a":"#030201ff","b":"#020202ff","out":1.0001164319677378}, +{"a":"#030201ff","b":"#040404ff","out":1.0015597916504846}, +{"a":"#030201ff","b":"#080808ff","out":1.0092616306203004}, +{"a":"#030201ff","b":"#101010ff","out":1.0446499896406183}, +{"a":"#030201ff","b":"#202020ff","out":1.2072521888113528}, +{"a":"#030201ff","b":"#404040ff","out":1.9543757036366451}, +{"a":"#030201ff","b":"#7f7f7fff","out":5.312190740493139}, +{"a":"#030201ff","b":"#808080ff","out":5.387253913474311}, +{"a":"#030201ff","b":"#818181ff","out":5.46302354240613}, +{"a":"#030201ff","b":"#bfbfbfff","out":11.58371107212698}, +{"a":"#030201ff","b":"#c0c0c0ff","out":11.706007501198082}, +{"a":"#030201ff","b":"#fdfdfdff","out":20.644488425881164}, +{"a":"#030201ff","b":"#fefefeff","out":20.815720350359765}, +{"a":"#030201ff","b":"#ff0000ff","out":5.24893962441575}, +{"a":"#030201ff","b":"#00ff00ff","out":15.295082256675295}, +{"a":"#030201ff","b":"#0000ffff","out":2.4425758648271314}, +{"a":"#030201ff","b":"#ffff00ff","out":19.544604587790253}, +{"a":"#030201ff","b":"#00ffffff","out":16.738240828201636}, +{"a":"#030201ff","b":"#ff00ffff","out":6.6920981959420915}, +{"a":"#030201ff","b":"#800000ff","out":1.9322713587496811}, +{"a":"#030201ff","b":"#008000ff","out":4.137598044048892}, +{"a":"#030201ff","b":"#000080ff","out":1.3162190972773187}, +{"a":"#030201ff","b":"#ff8080ff","out":8.703922179140381}, +{"a":"#030201ff","b":"#80ff80ff","out":16.544738126100714}, +{"a":"#030201ff","b":"#8080ffff","out":6.513610681024124}, +{"a":"#030201ff","b":"#030201ff","out":1}, +{"a":"#030201ff","b":"#fffefdff","out":20.83993370659663}, +{"a":"#030201ff","b":"#7f8081ff","out":5.376766050107392}, +{"a":"#030201ff","b":"#010000ff","out":1.0005614466964863}, +{"a":"#030201ff","b":"#000001ff","out":1.0005757109596913}, +{"a":"#030201ff","b":"#fefffeff","out":20.938765367325686}, +{"a":"#030201ff","b":"#3daee9ff","out":8.531724776576828}, +{"a":"#030201ff","b":"#232629ff","out":1.2960573193134681}, +{"a":"#030201ff","b":"#eff0f1ff","out":18.46957135166094}, +{"a":"#030201ff","b":"#fcfcfcff","out":20.474066746379922}, +{"a":"#030201ff","b":"#31363bff","out":1.6398697263283473}, +{"a":"#030201ff","b":"#1b1e20ff","out":1.173796689708185}, +{"a":"#030201ff","b":"#da4453ff","out":4.91192877696252}, +{"a":"#030201ff","b":"#27ae60ff","out":7.402230248824119}, +{"a":"#030201ff","b":"#f67400ff","out":7.453054133250825}, +{"a":"#030201ff","b":"#2980b9ff","out":4.926185810925244}, +{"a":"#030201ff","b":"#7f8c8dff","out":6.130303909984007}, +{"a":"#030201ff","b":"#9b59b6ff","out":4.518730458979249}, +{"a":"#030201ff","b":"#1d99f3ff","out":6.979573445230936}, +{"a":"#030201ff","b":"#dee0e2ff","out":15.987558557005428}, +{"a":"#030201ff","b":"#292c30ff","out":1.4117637302627148}, +{"a":"#030201ff","b":"#e93a9aff","out":5.509716232815435}, +{"a":"#030201ff","b":"#e93d58ff","out":5.237326600438329}, +{"a":"#030201ff","b":"#e9643aff","out":6.362516729480789}, +{"a":"#030201ff","b":"#e8cb2dff","out":13.138624914650384}, +{"a":"#030201ff","b":"#3dd425ff","out":10.725329169983484}, +{"a":"#030201ff","b":"#00d3b8ff","out":11.127385441381044}, +{"a":"#030201ff","b":"#b875dcff","out":6.6903999166007315}, +{"a":"#030201ff","b":"#926ee4ff","out":5.622068835830123}, +{"a":"#030201ff","b":"#686b6fff","out":3.937471969696157}, +{"a":"#030201ff","b":"#3daee980","out":8.531724776576828}, +{"a":"#030201ff","b":"#ffffff00","out":20.987763159316597}, +{"a":"#030201ff","b":"#00000080","out":1.0005830464442786}, +{"a":"#030201ff","b":"#ff000033","out":5.24893962441575}, +{"a":"#3daee9ff","b":"#000000ff","out":8.536699168371376}, +{"a":"#3daee9ff","b":"#ffffffff","out":2.459967205803079}, +{"a":"#3daee9ff","b":"#010101ff","out":8.53583243097326}, +{"a":"#3daee9ff","b":"#020202ff","out":8.532718142080762}, +{"a":"#3daee9ff","b":"#040404ff","out":8.51843778843925}, +{"a":"#3daee9ff","b":"#080808ff","out":8.453432209974299}, +{"a":"#3daee9ff","b":"#101010ff","out":8.16706539145415}, +{"a":"#3daee9ff","b":"#202020ff","out":7.067060930307421}, +{"a":"#3daee9ff","b":"#404040ff","out":4.365447626421698}, +{"a":"#3daee9ff","b":"#7f7f7fff","out":1.6060652174143912}, +{"a":"#3daee9ff","b":"#808080ff","out":1.5836871462913111}, +{"a":"#3daee9ff","b":"#818181ff","out":1.5617221325059716}, +{"a":"#3daee9ff","b":"#bfbfbfff","out":1.3577220756029467}, +{"a":"#3daee9ff","b":"#c0c0c0ff","out":1.3720563904424101}, +{"a":"#3daee9ff","b":"#fdfdfdff","out":2.4197321135532834}, +{"a":"#3daee9ff","b":"#fefefeff","out":2.43980213795781}, +{"a":"#3daee9ff","b":"#ff0000ff","out":1.6254187296975202}, +{"a":"#3daee9ff","b":"#00ff00ff","out":1.792730386552872}, +{"a":"#3daee9ff","b":"#0000ffff","out":3.4929210999882883}, +{"a":"#3daee9ff","b":"#ffff00ff","out":2.290815175080238}, +{"a":"#3daee9ff","b":"#00ffffff","out":1.9618824172757126}, +{"a":"#3daee9ff","b":"#ff00ffff","out":1.2748953357782822}, +{"a":"#3daee9ff","b":"#800000ff","out":4.415386450740268}, +{"a":"#3daee9ff","b":"#008000ff","out":2.0619994223092815}, +{"a":"#3daee9ff","b":"#000080ff","out":6.481994368737875}, +{"a":"#3daee9ff","b":"#ff8080ff","out":1.0201831876991985}, +{"a":"#3daee9ff","b":"#80ff80ff","out":1.939202044060654}, +{"a":"#3daee9ff","b":"#8080ffff","out":1.3098303221333147}, +{"a":"#3daee9ff","b":"#030201ff","out":8.531724776576828}, +{"a":"#3daee9ff","b":"#fffefdff","out":2.442640175619707}, +{"a":"#3daee9ff","b":"#7f8081ff","out":1.5867762698000263}, +{"a":"#3daee9ff","b":"#010000ff","out":8.536514885267968}, +{"a":"#3daee9ff","b":"#000001ff","out":8.536636584035774}, +{"a":"#3daee9ff","b":"#fefffeff","out":2.4542241944807457}, +{"a":"#3daee9ff","b":"#3daee9ff","out":1}, +{"a":"#3daee9ff","b":"#232629ff","out":6.582829825069891}, +{"a":"#3daee9ff","b":"#eff0f1ff","out":2.1648109655819745}, +{"a":"#3daee9ff","b":"#fcfcfcff","out":2.399757057633861}, +{"a":"#3daee9ff","b":"#31363bff","out":5.202684481333331}, +{"a":"#3daee9ff","b":"#1b1e20ff","out":7.268485974941607}, +{"a":"#3daee9ff","b":"#da4453ff","out":1.7369398384991952}, +{"a":"#3daee9ff","b":"#27ae60ff","out":1.1525884077886035}, +{"a":"#3daee9ff","b":"#f67400ff","out":1.1447286741838698}, +{"a":"#3daee9ff","b":"#2980b9ff","out":1.7319129046361301}, +{"a":"#3daee9ff","b":"#7f8c8dff","out":1.3917294969147926}, +{"a":"#3daee9ff","b":"#9b59b6ff","out":1.888080037972455}, +{"a":"#3daee9ff","b":"#1d99f3ff","out":1.2223848410688278}, +{"a":"#3daee9ff","b":"#dee0e2ff","out":1.8738952527978865}, +{"a":"#3daee9ff","b":"#292c30ff","out":6.04330922638816}, +{"a":"#3daee9ff","b":"#e93a9aff","out":1.5484871481697278}, +{"a":"#3daee9ff","b":"#e93d58ff","out":1.6290228636615447}, +{"a":"#3daee9ff","b":"#e9643aff","out":1.3409355353118353}, +{"a":"#3daee9ff","b":"#e8cb2dff","out":1.5399728963036212}, +{"a":"#3daee9ff","b":"#3dd425ff","out":1.2571114810722706}, +{"a":"#3daee9ff","b":"#00d3b8ff","out":1.3042363335407154}, +{"a":"#3daee9ff","b":"#b875dcff","out":1.2752189529668116}, +{"a":"#3daee9ff","b":"#926ee4ff","out":1.5175418561585583}, +{"a":"#3daee9ff","b":"#686b6fff","out":2.1668026698956275}, +{"a":"#3daee9ff","b":"#3daee980","out":1}, +{"a":"#3daee9ff","b":"#ffffff00","out":2.459967205803079}, +{"a":"#3daee9ff","b":"#00000080","out":8.536699168371376}, +{"a":"#3daee9ff","b":"#ff000033","out":1.6254187296975202}, +{"a":"#232629ff","b":"#000000ff","out":1.2968129809250752}, +{"a":"#232629ff","b":"#ffffffff","out":16.193545491054348}, +{"a":"#232629ff","b":"#010101ff","out":1.2966813145412937}, +{"a":"#232629ff","b":"#020202ff","out":1.2962082218174569}, +{"a":"#232629ff","b":"#040404ff","out":1.2940388882601577}, +{"a":"#232629ff","b":"#080808ff","out":1.2841638679129226}, +{"a":"#232629ff","b":"#101010ff","out":1.2406617835312854}, +{"a":"#232629ff","b":"#202020ff","out":1.073559717948867}, +{"a":"#232629ff","b":"#404040ff","out":1.507939251229948}, +{"a":"#232629ff","b":"#7f7f7fff","out":4.098731330267899}, +{"a":"#232629ff","b":"#808080ff","out":4.156647883696982}, +{"a":"#232629ff","b":"#818181ff","out":4.215109517918496}, +{"a":"#232629ff","b":"#bfbfbfff","out":8.937653373434875}, +{"a":"#232629ff","b":"#c0c0c0ff","out":9.032013728682035}, +{"a":"#232629ff","b":"#fdfdfdff","out":15.928684725777957}, +{"a":"#232629ff","b":"#fefefeff","out":16.060802281017953}, +{"a":"#232629ff","b":"#ff0000ff","out":4.049928615191306}, +{"a":"#232629ff","b":"#00ff00ff","out":11.80123905690932}, +{"a":"#232629ff","b":"#0000ffff","out":1.884620246673182}, +{"a":"#232629ff","b":"#ffff00ff","out":15.080046458240895}, +{"a":"#232629ff","b":"#00ffffff","out":12.914738089722771}, +{"a":"#232629ff","b":"#ff00ffff","out":5.163427648004757}, +{"a":"#232629ff","b":"#800000ff","out":1.4908841838671305}, +{"a":"#232629ff","b":"#008000ff","out":3.192449888127333}, +{"a":"#232629ff","b":"#000080ff","out":1.0155562394219804}, +{"a":"#232629ff","b":"#ff8080ff","out":6.715692315021158}, +{"a":"#232629ff","b":"#80ff80ff","out":12.765437052478969}, +{"a":"#232629ff","b":"#8080ffff","out":5.025711890948184}, +{"a":"#232629ff","b":"#030201ff","out":1.2960573193134681}, +{"a":"#232629ff","b":"#fffefdff","out":16.079484599983363}, +{"a":"#232629ff","b":"#7f8081ff","out":4.148555754428752}, +{"a":"#232629ff","b":"#010000ff","out":1.2967849864138534}, +{"a":"#232629ff","b":"#000001ff","out":1.296803473716585}, +{"a":"#232629ff","b":"#fefffeff","out":16.15574022483598}, +{"a":"#232629ff","b":"#3daee9ff","out":6.582829825069891}, +{"a":"#232629ff","b":"#232629ff","out":1}, +{"a":"#232629ff","b":"#eff0f1ff","out":14.25058218987137}, +{"a":"#232629ff","b":"#fcfcfcff","out":15.797192331914143}, +{"a":"#232629ff","b":"#31363bff","out":1.2652756185173966}, +{"a":"#232629ff","b":"#1b1e20ff","out":1.1041582675068524}, +{"a":"#232629ff","b":"#da4453ff","out":3.789900881516882}, +{"a":"#232629ff","b":"#27ae60ff","out":5.7113448136268685}, +{"a":"#232629ff","b":"#f67400ff","out":5.750559039471161}, +{"a":"#232629ff","b":"#2980b9ff","out":3.800901192807339}, +{"a":"#232629ff","b":"#7f8c8dff","out":4.729963573857426}, +{"a":"#232629ff","b":"#9b59b6ff","out":3.4865205355059885}, +{"a":"#232629ff","b":"#1d99f3ff","out":5.385235159914124}, +{"a":"#232629ff","b":"#dee0e2ff","out":12.33553355917481}, +{"a":"#232629ff","b":"#292c30ff","out":1.0892756896049451}, +{"a":"#232629ff","b":"#e93a9aff","out":4.25113623503471}, +{"a":"#232629ff","b":"#e93d58ff","out":4.040968344835691}, +{"a":"#232629ff","b":"#e9643aff","out":4.909132207864899}, +{"a":"#232629ff","b":"#e8cb2dff","out":10.137379511586738}, +{"a":"#232629ff","b":"#3dd425ff","out":8.275350951040325}, +{"a":"#232629ff","b":"#00d3b8ff","out":8.585565835371623}, +{"a":"#232629ff","b":"#b875dcff","out":5.162117305232063}, +{"a":"#232629ff","b":"#926ee4ff","out":4.337824224323795}, +{"a":"#232629ff","b":"#686b6fff","out":3.0380384501681355}, +{"a":"#232629ff","b":"#3daee980","out":6.582829825069891}, +{"a":"#232629ff","b":"#ffffff00","out":16.193545491054348}, +{"a":"#232629ff","b":"#00000080","out":1.2968129809250752}, +{"a":"#232629ff","b":"#ff000033","out":4.049928615191306}, +{"a":"#eff0f1ff","b":"#000000ff","out":18.480339969564877}, +{"a":"#eff0f1ff","b":"#ffffffff","out":1.136342731496538}, +{"a":"#eff0f1ff","b":"#010101ff","out":18.478463646941155}, +{"a":"#eff0f1ff","b":"#020202ff","out":18.471721800196686}, +{"a":"#eff0f1ff","b":"#040404ff","out":18.440807534041152}, +{"a":"#eff0f1ff","b":"#080808ff","out":18.300082744956224}, +{"a":"#eff0f1ff","b":"#101010ff","out":17.680152716044983}, +{"a":"#eff0f1ff","b":"#202020ff","out":15.298850996365454}, +{"a":"#eff0f1ff","b":"#404040ff","out":9.450368891351495}, +{"a":"#eff0f1ff","b":"#7f7f7fff","out":3.476827594098472}, +{"a":"#eff0f1ff","b":"#808080ff","out":3.428383300342655}, +{"a":"#eff0f1ff","b":"#818181ff","out":3.380833197640993}, +{"a":"#eff0f1ff","b":"#bfbfbfff","out":1.5944433728240075}, +{"a":"#eff0f1ff","b":"#c0c0c0ff","out":1.5777857095829322}, +{"a":"#eff0f1ff","b":"#fdfdfdff","out":1.1177567704636868}, +{"a":"#eff0f1ff","b":"#fefefeff","out":1.1270277990770932}, +{"a":"#eff0f1ff","b":"#ff0000ff","out":3.5187242897115154}, +{"a":"#eff0f1ff","b":"#00ff00ff","out":1.2075496582308467}, +{"a":"#eff0f1ff","b":"#0000ffff","out":7.5615138991672985}, +{"a":"#eff0f1ff","b":"#ffff00ff","out":1.0582056408164902}, +{"a":"#eff0f1ff","b":"#00ffffff","out":1.1034356322883256}, +{"a":"#eff0f1ff","b":"#ff00ffff","out":2.7599074028621384}, +{"a":"#eff0f1ff","b":"#800000ff","out":9.558477005844606}, +{"a":"#eff0f1ff","b":"#008000ff","out":4.463838960438829}, +{"a":"#eff0f1ff","b":"#000080ff","out":14.03229248828436}, +{"a":"#eff0f1ff","b":"#ff8080ff","out":2.1219825926206792}, +{"a":"#eff0f1ff","b":"#80ff80ff","out":1.116341111650697}, +{"a":"#eff0f1ff","b":"#8080ffff","out":2.83553504440597}, +{"a":"#eff0f1ff","b":"#030201ff","out":18.46957135166094}, +{"a":"#eff0f1ff","b":"#fffefdff","out":1.1283387854435791}, +{"a":"#eff0f1ff","b":"#7f8081ff","out":3.4350706687883585}, +{"a":"#eff0f1ff","b":"#010000ff","out":18.479941031481847}, +{"a":"#eff0f1ff","b":"#000001ff","out":18.480204486308892}, +{"a":"#eff0f1ff","b":"#fefffeff","out":1.13368983874348}, +{"a":"#eff0f1ff","b":"#3daee9ff","out":2.1648109655819745}, +{"a":"#eff0f1ff","b":"#232629ff","out":14.25058218987137}, +{"a":"#eff0f1ff","b":"#eff0f1ff","out":1}, +{"a":"#eff0f1ff","b":"#fcfcfcff","out":1.1085296110317535}, +{"a":"#eff0f1ff","b":"#31363bff","out":11.262828415653562}, +{"a":"#eff0f1ff","b":"#1b1e20ff","out":15.73489814173238}, +{"a":"#eff0f1ff","b":"#da4453ff","out":3.760146408939242}, +{"a":"#eff0f1ff","b":"#27ae60ff","out":2.495136023983437}, +{"a":"#eff0f1ff","b":"#f67400ff","out":2.4781211864893566}, +{"a":"#eff0f1ff","b":"#2980b9ff","out":3.749264047389223}, +{"a":"#eff0f1ff","b":"#7f8c8dff","out":3.012831276045028}, +{"a":"#eff0f1ff","b":"#9b59b6ff","out":4.087336370099201}, +{"a":"#eff0f1ff","b":"#1d99f3ff","out":2.6462321081069775}, +{"a":"#eff0f1ff","b":"#dee0e2ff","out":1.1552465178348288}, +{"a":"#eff0f1ff","b":"#292c30ff","out":13.082622081687807}, +{"a":"#eff0f1ff","b":"#e93a9aff","out":3.3521819584205863}, +{"a":"#eff0f1ff","b":"#e93d58ff","out":3.526526558438262}, +{"a":"#eff0f1ff","b":"#e9643aff","out":2.902871950981596}, +{"a":"#eff0f1ff","b":"#e8cb2dff","out":1.4057461470770978}, +{"a":"#eff0f1ff","b":"#3dd425ff","out":1.7220517019981945}, +{"a":"#eff0f1ff","b":"#00d3b8ff","out":1.6598302852865536}, +{"a":"#eff0f1ff","b":"#b875dcff","out":2.760607972900518}, +{"a":"#eff0f1ff","b":"#926ee4ff","out":3.2851912509416703}, +{"a":"#eff0f1ff","b":"#686b6fff","out":4.690718180042354}, +{"a":"#eff0f1ff","b":"#3daee980","out":2.1648109655819745}, +{"a":"#eff0f1ff","b":"#ffffff00","out":1.136342731496538}, +{"a":"#eff0f1ff","b":"#00000080","out":18.480339969564877}, +{"a":"#eff0f1ff","b":"#ff000033","out":3.5187242897115154}, +{"a":"#fcfcfcff","b":"#000000ff","out":20.48600407819632}, +{"a":"#fcfcfcff","b":"#ffffffff","out":1.0250901015074352}, +{"a":"#fcfcfcff","b":"#010101ff","out":20.483924119008076}, +{"a":"#fcfcfcff","b":"#020202ff","out":20.476450582258796}, +{"a":"#fcfcfcff","b":"#040404ff","out":20.442181202822066}, +{"a":"#fcfcfcff","b":"#080808ff","out":20.286183607115227}, +{"a":"#fcfcfcff","b":"#101010ff","out":19.598972813299348}, +{"a":"#fcfcfcff","b":"#202020ff","out":16.95922934423375}, +{"a":"#fcfcfcff","b":"#404040ff","out":10.476013751236458}, +{"a":"#fcfcfcff","b":"#7f7f7fff","out":3.8541663405104467}, +{"a":"#fcfcfcff","b":"#808080ff","out":3.8004644063966024}, +{"a":"#fcfcfcff","b":"#818181ff","out":3.747753709544209}, +{"a":"#fcfcfcff","b":"#bfbfbfff","out":1.767487691888754}, +{"a":"#fcfcfcff","b":"#c0c0c0ff","out":1.749022178935427}, +{"a":"#fcfcfcff","b":"#fdfdfdff","out":1.0083237825495208}, +{"a":"#fcfcfcff","b":"#fefefeff","out":1.0166871393071066}, +{"a":"#fcfcfcff","b":"#ff0000ff","out":3.9006100682018894}, +{"a":"#fcfcfcff","b":"#00ff00ff","out":1.3386045529401676}, +{"a":"#fcfcfcff","b":"#0000ffff","out":8.382162061455123}, +{"a":"#fcfcfcff","b":"#ffff00ff","out":1.0475559459089958}, +{"a":"#fcfcfcff","b":"#00ffffff","out":1.2231910722591546}, +{"a":"#fcfcfcff","b":"#ff00ffff","out":3.0594390797784237}, +{"a":"#fcfcfcff","b":"#800000ff","out":10.595854797344881}, +{"a":"#fcfcfcff","b":"#008000ff","out":4.948297666523643}, +{"a":"#fcfcfcff","b":"#000080ff","out":15.55521173392166}, +{"a":"#fcfcfcff","b":"#ff8080ff","out":2.3522805380139538}, +{"a":"#fcfcfcff","b":"#80ff80ff","out":1.2374971782769026}, +{"a":"#fcfcfcff","b":"#8080ffff","out":3.143274559842256}, +{"a":"#fcfcfcff","b":"#030201ff","out":20.474066746379922}, +{"a":"#fcfcfcff","b":"#fffefdff","out":1.0178697747129988}, +{"a":"#fcfcfcff","b":"#7f8081ff","out":3.8078775523385446}, +{"a":"#fcfcfcff","b":"#010000ff","out":20.485561843518315}, +{"a":"#fcfcfcff","b":"#000001ff","out":20.485853890995262}, +{"a":"#fcfcfcff","b":"#fefffeff","out":1.0226969378727815}, +{"a":"#fcfcfcff","b":"#3daee9ff","out":2.399757057633861}, +{"a":"#fcfcfcff","b":"#232629ff","out":15.797192331914143}, +{"a":"#fcfcfcff","b":"#eff0f1ff","out":1.1085296110317535}, +{"a":"#fcfcfcff","b":"#fcfcfcff","out":1}, +{"a":"#fcfcfcff","b":"#31363bff","out":12.485178802721824}, +{"a":"#fcfcfcff","b":"#1b1e20ff","out":17.442600516678855}, +{"a":"#fcfcfcff","b":"#da4453ff","out":4.168233636123863}, +{"a":"#fcfcfcff","b":"#27ae60ff","out":2.765932166137676}, +{"a":"#fcfcfcff","b":"#f67400ff","out":2.7470707149485944}, +{"a":"#fcfcfcff","b":"#2980b9ff","out":4.156170216107713}, +{"a":"#fcfcfcff","b":"#7f8c8dff","out":3.3398126825384966}, +{"a":"#fcfcfcff","b":"#9b59b6ff","out":4.530933396502006}, +{"a":"#fcfcfcff","b":"#1d99f3ff","out":2.933426649499565}, +{"a":"#fcfcfcff","b":"#dee0e2ff","out":1.2806249730612305}, +{"a":"#fcfcfcff","b":"#292c30ff","out":14.502473967488815}, +{"a":"#fcfcfcff","b":"#e93a9aff","out":3.7159929624756343}, +{"a":"#fcfcfcff","b":"#e93d58ff","out":3.909259114118715}, +{"a":"#fcfcfcff","b":"#e9643aff","out":3.217919514696616}, +{"a":"#fcfcfcff","b":"#e8cb2dff","out":1.5583112296287613}, +{"a":"#fcfcfcff","b":"#3dd425ff","out":1.9089453033926276}, +{"a":"#fcfcfcff","b":"#00d3b8ff","out":1.8399710205274278}, +{"a":"#fcfcfcff","b":"#b875dcff","out":3.060215682410569}, +{"a":"#fcfcfcff","b":"#926ee4ff","out":3.6417317795712894}, +{"a":"#fcfcfcff","b":"#686b6fff","out":5.199799999581925}, +{"a":"#fcfcfcff","b":"#3daee980","out":2.399757057633861}, +{"a":"#fcfcfcff","b":"#ffffff00","out":1.0250901015074352}, +{"a":"#fcfcfcff","b":"#00000080","out":20.48600407819632}, +{"a":"#fcfcfcff","b":"#ff000033","out":3.9006100682018894}, +{"a":"#da4453ff","b":"#000000ff","out":4.914792659570478}, +{"a":"#da4453ff","b":"#ffffffff","out":4.272815041160916}, +{"a":"#da4453ff","b":"#010101ff","out":4.914293657026518}, +{"a":"#da4453ff","b":"#020202ff","out":4.912500682495409}, +{"a":"#da4453ff","b":"#040404ff","out":4.904279123334297}, +{"a":"#da4453ff","b":"#080808ff","out":4.866853775015314}, +{"a":"#da4453ff","b":"#101010ff","out":4.701985187069526}, +{"a":"#da4453ff","b":"#202020ff","out":4.068684921415426}, +{"a":"#da4453ff","b":"#404040ff","out":2.5132981175638576}, +{"a":"#da4453ff","b":"#7f7f7fff","out":1.0814877376495953}, +{"a":"#da4453ff","b":"#808080ff","out":1.0967695498235066}, +{"a":"#da4453ff","b":"#818181ff","out":1.1121951865483686}, +{"a":"#da4453ff","b":"#bfbfbfff","out":2.3582815627245743}, +{"a":"#da4453ff","b":"#c0c0c0ff","out":2.3831794052268283}, +{"a":"#da4453ff","b":"#fdfdfdff","out":4.202929106526557}, +{"a":"#da4453ff","b":"#fefefeff","out":4.237789531474429}, +{"a":"#da4453ff","b":"#ff0000ff","out":1.0686106950560539}, +{"a":"#da4453ff","b":"#00ff00ff","out":3.1138648280917454}, +{"a":"#da4453ff","b":"#0000ffff","out":2.0109626266654983}, +{"a":"#da4453ff","b":"#ffff00ff","out":3.9790081402353747}, +{"a":"#da4453ff","b":"#00ffffff","out":3.4076717290172867}, +{"a":"#da4453ff","b":"#ff00ffff","out":1.3624175959815947}, +{"a":"#da4453ff","b":"#800000ff","out":2.542049156150041}, +{"a":"#da4453ff","b":"#008000ff","out":1.187144987180992}, +{"a":"#da4453ff","b":"#000080ff","out":3.7318473703376216}, +{"a":"#da4453ff","b":"#ff8080ff","out":1.77199682128184}, +{"a":"#da4453ff","b":"#80ff80ff","out":3.3682772852280216}, +{"a":"#da4453ff","b":"#8080ffff","out":1.3260800342980676}, +{"a":"#da4453ff","b":"#030201ff","out":4.91192877696252}, +{"a":"#da4453ff","b":"#fffefdff","out":4.242719032152539}, +{"a":"#da4453ff","b":"#7f8081ff","out":1.0946343675268684}, +{"a":"#da4453ff","b":"#010000ff","out":4.914686563147721}, +{"a":"#da4453ff","b":"#000001ff","out":4.91475662819264}, +{"a":"#da4453ff","b":"#fefffeff","out":4.262839776002204}, +{"a":"#da4453ff","b":"#3daee9ff","out":1.7369398384991952}, +{"a":"#da4453ff","b":"#232629ff","out":3.789900881516882}, +{"a":"#da4453ff","b":"#eff0f1ff","out":3.760146408939242}, +{"a":"#da4453ff","b":"#fcfcfcff","out":4.168233636123863}, +{"a":"#da4453ff","b":"#31363bff","out":2.995316455996954}, +{"a":"#da4453ff","b":"#1b1e20ff","out":4.184650391358374}, +{"a":"#da4453ff","b":"#da4453ff","out":1}, +{"a":"#da4453ff","b":"#27ae60ff","out":1.50699054993252}, +{"a":"#da4453ff","b":"#f67400ff","out":1.5173375819711514}, +{"a":"#da4453ff","b":"#2980b9ff","out":1.002902532713746}, +{"a":"#da4453ff","b":"#7f8c8dff","out":1.2480441367016148}, +{"a":"#da4453ff","b":"#9b59b6ff","out":1.087015218445247}, +{"a":"#da4453ff","b":"#1d99f3ff","out":1.4209435360638563}, +{"a":"#da4453ff","b":"#dee0e2ff","out":3.25484331775917}, +{"a":"#da4453ff","b":"#292c30ff","out":3.4792852880902814}, +{"a":"#da4453ff","b":"#e93a9aff","out":1.1217011652645705}, +{"a":"#da4453ff","b":"#e93d58ff","out":1.0662464457957859}, +{"a":"#da4453ff","b":"#e9643aff","out":1.295319418987035}, +{"a":"#da4453ff","b":"#e8cb2dff","out":2.6748402737987496}, +{"a":"#da4453ff","b":"#3dd425ff","out":2.1835270129091535}, +{"a":"#da4453ff","b":"#00d3b8ff","out":2.265380046544993}, +{"a":"#da4453ff","b":"#b875dcff","out":1.3620718500600895}, +{"a":"#da4453ff","b":"#926ee4ff","out":1.1445745838576156}, +{"a":"#da4453ff","b":"#686b6fff","out":1.2474828556916835}, +{"a":"#da4453ff","b":"#3daee980","out":1.7369398384991952}, +{"a":"#da4453ff","b":"#ffffff00","out":4.272815041160916}, +{"a":"#da4453ff","b":"#00000080","out":4.914792659570478}, +{"a":"#da4453ff","b":"#ff000033","out":1.0686106950560539}, +{"a":"#e8cb2dff","b":"#000000ff","out":13.146285343189582}, +{"a":"#e8cb2dff","b":"#ffffffff","out":1.5974094165603232}, +{"a":"#e8cb2dff","b":"#010101ff","out":13.144950591088271}, +{"a":"#e8cb2dff","b":"#020202ff","out":13.140154670602566}, +{"a":"#e8cb2dff","b":"#040404ff","out":13.118163313045004}, +{"a":"#e8cb2dff","b":"#080808ff","out":13.018056484100441}, +{"a":"#e8cb2dff","b":"#101010ff","out":12.577059345178714}, +{"a":"#e8cb2dff","b":"#202020ff","out":10.883082289199683}, +{"a":"#e8cb2dff","b":"#404040ff","out":6.722671024922391}, +{"a":"#e8cb2dff","b":"#7f7f7fff","out":2.473296904514145}, +{"a":"#e8cb2dff","b":"#808080ff","out":2.4388352815130467}, +{"a":"#e8cb2dff","b":"#818181ff","out":2.405009755616689}, +{"a":"#e8cb2dff","b":"#bfbfbfff","out":1.1342327888568353}, +{"a":"#e8cb2dff","b":"#c0c0c0ff","out":1.1223830937495707}, +{"a":"#e8cb2dff","b":"#fdfdfdff","out":1.5712822734486678}, +{"a":"#e8cb2dff","b":"#fefefeff","out":1.5843149862014054}, +{"a":"#e8cb2dff","b":"#ff0000ff","out":2.503100788878443}, +{"a":"#e8cb2dff","b":"#00ff00ff","out":1.1641311290971041}, +{"a":"#e8cb2dff","b":"#0000ffff","out":5.379003822908994}, +{"a":"#e8cb2dff","b":"#ffff00ff","out":1.4875685023930323}, +{"a":"#e8cb2dff","b":"#00ffffff","out":1.2739720432643948}, +{"a":"#e8cb2dff","b":"#ff00ffff","out":1.9633042627224588}, +{"a":"#e8cb2dff","b":"#800000ff","out":6.799575460846256}, +{"a":"#e8cb2dff","b":"#008000ff","out":3.175423222550018}, +{"a":"#e8cb2dff","b":"#000080ff","out":9.982095641849028}, +{"a":"#e8cb2dff","b":"#ff8080ff","out":1.5095062483598611}, +{"a":"#e8cb2dff","b":"#80ff80ff","out":1.259244268983758}, +{"a":"#e8cb2dff","b":"#8080ffff","out":2.017103194841946}, +{"a":"#e8cb2dff","b":"#030201ff","out":13.138624914650384}, +{"a":"#e8cb2dff","b":"#fffefdff","out":1.5861579002349635}, +{"a":"#e8cb2dff","b":"#7f8081ff","out":2.443592447989803}, +{"a":"#e8cb2dff","b":"#010000ff","out":13.146001552205087}, +{"a":"#e8cb2dff","b":"#000001ff","out":13.14618896500902}, +{"a":"#e8cb2dff","b":"#fefffeff","out":1.593680122794103}, +{"a":"#e8cb2dff","b":"#3daee9ff","out":1.5399728963036212}, +{"a":"#e8cb2dff","b":"#232629ff","out":10.137379511586738}, +{"a":"#e8cb2dff","b":"#eff0f1ff","out":1.4057461470770978}, +{"a":"#e8cb2dff","b":"#fcfcfcff","out":1.5583112296287613}, +{"a":"#e8cb2dff","b":"#31363bff","out":8.011993089272792}, +{"a":"#e8cb2dff","b":"#1b1e20ff","out":11.193271398573076}, +{"a":"#e8cb2dff","b":"#da4453ff","out":2.6748402737987496}, +{"a":"#e8cb2dff","b":"#27ae60ff","out":1.7749549085881948}, +{"a":"#e8cb2dff","b":"#f67400ff","out":1.7628511318647384}, +{"a":"#e8cb2dff","b":"#2980b9ff","out":2.667098931898119}, +{"a":"#e8cb2dff","b":"#7f8c8dff","out":2.143225704235055}, +{"a":"#e8cb2dff","b":"#9b59b6ff","out":2.9075920845294925}, +{"a":"#e8cb2dff","b":"#1d99f3ff","out":1.8824395240984044}, +{"a":"#e8cb2dff","b":"#dee0e2ff","out":1.2168365153021687}, +{"a":"#e8cb2dff","b":"#292c30ff","out":9.30653241261937}, +{"a":"#e8cb2dff","b":"#e93a9aff","out":2.3846282384558704}, +{"a":"#e8cb2dff","b":"#e93d58ff","out":2.508651057497688}, +{"a":"#e8cb2dff","b":"#e9643aff","out":2.0650043800706137}, +{"a":"#e8cb2dff","b":"#e8cb2dff","out":1}, +{"a":"#e8cb2dff","b":"#3dd425ff","out":1.2250090143080072}, +{"a":"#e8cb2dff","b":"#00d3b8ff","out":1.1807468145922086}, +{"a":"#e8cb2dff","b":"#b875dcff","out":1.9638026244215723}, +{"a":"#e8cb2dff","b":"#926ee4ff","out":2.336973327490468}, +{"a":"#e8cb2dff","b":"#686b6fff","out":3.336817383277589}, +{"a":"#e8cb2dff","b":"#3daee980","out":1.5399728963036212}, +{"a":"#e8cb2dff","b":"#ffffff00","out":1.5974094165603232}, +{"a":"#e8cb2dff","b":"#00000080","out":13.146285343189582}, +{"a":"#e8cb2dff","b":"#ff000033","out":2.503100788878443}, +{"a":"#3daee980","b":"#000000ff","out":8.536699168371376}, +{"a":"#3daee980","b":"#ffffffff","out":2.459967205803079}, +{"a":"#3daee980","b":"#010101ff","out":8.53583243097326}, +{"a":"#3daee980","b":"#020202ff","out":8.532718142080762}, +{"a":"#3daee980","b":"#040404ff","out":8.51843778843925}, +{"a":"#3daee980","b":"#080808ff","out":8.453432209974299}, +{"a":"#3daee980","b":"#101010ff","out":8.16706539145415}, +{"a":"#3daee980","b":"#202020ff","out":7.067060930307421}, +{"a":"#3daee980","b":"#404040ff","out":4.365447626421698}, +{"a":"#3daee980","b":"#7f7f7fff","out":1.6060652174143912}, +{"a":"#3daee980","b":"#808080ff","out":1.5836871462913111}, +{"a":"#3daee980","b":"#818181ff","out":1.5617221325059716}, +{"a":"#3daee980","b":"#bfbfbfff","out":1.3577220756029467}, +{"a":"#3daee980","b":"#c0c0c0ff","out":1.3720563904424101}, +{"a":"#3daee980","b":"#fdfdfdff","out":2.4197321135532834}, +{"a":"#3daee980","b":"#fefefeff","out":2.43980213795781}, +{"a":"#3daee980","b":"#ff0000ff","out":1.6254187296975202}, +{"a":"#3daee980","b":"#00ff00ff","out":1.792730386552872}, +{"a":"#3daee980","b":"#0000ffff","out":3.4929210999882883}, +{"a":"#3daee980","b":"#ffff00ff","out":2.290815175080238}, +{"a":"#3daee980","b":"#00ffffff","out":1.9618824172757126}, +{"a":"#3daee980","b":"#ff00ffff","out":1.2748953357782822}, +{"a":"#3daee980","b":"#800000ff","out":4.415386450740268}, +{"a":"#3daee980","b":"#008000ff","out":2.0619994223092815}, +{"a":"#3daee980","b":"#000080ff","out":6.481994368737875}, +{"a":"#3daee980","b":"#ff8080ff","out":1.0201831876991985}, +{"a":"#3daee980","b":"#80ff80ff","out":1.939202044060654}, +{"a":"#3daee980","b":"#8080ffff","out":1.3098303221333147}, +{"a":"#3daee980","b":"#030201ff","out":8.531724776576828}, +{"a":"#3daee980","b":"#fffefdff","out":2.442640175619707}, +{"a":"#3daee980","b":"#7f8081ff","out":1.5867762698000263}, +{"a":"#3daee980","b":"#010000ff","out":8.536514885267968}, +{"a":"#3daee980","b":"#000001ff","out":8.536636584035774}, +{"a":"#3daee980","b":"#fefffeff","out":2.4542241944807457}, +{"a":"#3daee980","b":"#3daee9ff","out":1}, +{"a":"#3daee980","b":"#232629ff","out":6.582829825069891}, +{"a":"#3daee980","b":"#eff0f1ff","out":2.1648109655819745}, +{"a":"#3daee980","b":"#fcfcfcff","out":2.399757057633861}, +{"a":"#3daee980","b":"#31363bff","out":5.202684481333331}, +{"a":"#3daee980","b":"#1b1e20ff","out":7.268485974941607}, +{"a":"#3daee980","b":"#da4453ff","out":1.7369398384991952}, +{"a":"#3daee980","b":"#27ae60ff","out":1.1525884077886035}, +{"a":"#3daee980","b":"#f67400ff","out":1.1447286741838698}, +{"a":"#3daee980","b":"#2980b9ff","out":1.7319129046361301}, +{"a":"#3daee980","b":"#7f8c8dff","out":1.3917294969147926}, +{"a":"#3daee980","b":"#9b59b6ff","out":1.888080037972455}, +{"a":"#3daee980","b":"#1d99f3ff","out":1.2223848410688278}, +{"a":"#3daee980","b":"#dee0e2ff","out":1.8738952527978865}, +{"a":"#3daee980","b":"#292c30ff","out":6.04330922638816}, +{"a":"#3daee980","b":"#e93a9aff","out":1.5484871481697278}, +{"a":"#3daee980","b":"#e93d58ff","out":1.6290228636615447}, +{"a":"#3daee980","b":"#e9643aff","out":1.3409355353118353}, +{"a":"#3daee980","b":"#e8cb2dff","out":1.5399728963036212}, +{"a":"#3daee980","b":"#3dd425ff","out":1.2571114810722706}, +{"a":"#3daee980","b":"#00d3b8ff","out":1.3042363335407154}, +{"a":"#3daee980","b":"#b875dcff","out":1.2752189529668116}, +{"a":"#3daee980","b":"#926ee4ff","out":1.5175418561585583}, +{"a":"#3daee980","b":"#686b6fff","out":2.1668026698956275}, +{"a":"#3daee980","b":"#3daee980","out":1}, +{"a":"#3daee980","b":"#ffffff00","out":2.459967205803079}, +{"a":"#3daee980","b":"#00000080","out":8.536699168371376}, +{"a":"#3daee980","b":"#ff000033","out":1.6254187296975202}, +{"a":"#ffffff00","b":"#000000ff","out":21}, +{"a":"#ffffff00","b":"#ffffffff","out":1}, +{"a":"#ffffff00","b":"#010101ff","out":20.99786785442459}, +{"a":"#ffffff00","b":"#020202ff","out":20.99020680587965}, +{"a":"#ffffff00","b":"#040404ff","out":20.95507760423426}, +{"a":"#ffffff00","b":"#080808ff","out":20.795166013016217}, +{"a":"#ffffff00","b":"#101010ff","out":20.090713030626492}, +{"a":"#ffffff00","b":"#202020ff","out":17.38473812996845}, +{"a":"#ffffff00","b":"#404040ff","out":10.738857999648268}, +{"a":"#ffffff00","b":"#7f7f7fff","out":3.950867765220394}, +{"a":"#ffffff00","b":"#808080ff","out":3.895818444128488}, +{"a":"#ffffff00","b":"#818181ff","out":3.8417852305415403}, +{"a":"#ffffff00","b":"#bfbfbfff","out":1.8118341374913856}, +{"a":"#ffffff00","b":"#c0c0c0ff","out":1.7929053229436724}, +{"a":"#ffffff00","b":"#fdfdfdff","out":1.0166279118355426}, +{"a":"#ffffff00","b":"#fefefeff","out":1.0082650422882848}, +{"a":"#ffffff00","b":"#ff0000ff","out":3.9984767707539985}, +{"a":"#ffffff00","b":"#00ff00ff","out":1.3721902770517513}, +{"a":"#ffffff00","b":"#0000ffff","out":8.592471358428805}, +{"a":"#ffffff00","b":"#ffff00ff","out":1.0738392309265699}, +{"a":"#ffffff00","b":"#00ffffff","out":1.2538810604251254}, +{"a":"#ffffff00","b":"#ff00ffff","out":3.1362007168458783}, +{"a":"#ffffff00","b":"#800000ff","out":10.86170586976831}, +{"a":"#ffffff00","b":"#008000ff","out":5.072450957265725}, +{"a":"#ffffff00","b":"#000080ff","out":15.945493575295401}, +{"a":"#ffffff00","b":"#ff8080ff","out":2.411299495486688}, +{"a":"#ffffff00","b":"#80ff80ff","out":1.2685461080950347}, +{"a":"#ffffff00","b":"#8080ffff","out":3.222139637614437}, +{"a":"#ffffff00","b":"#030201ff","out":20.987763159316597}, +{"a":"#ffffff00","b":"#fffefdff","out":1.0070935663616423}, +{"a":"#ffffff00","b":"#7f8081ff","out":3.903417586654603}, +{"a":"#ffffff00","b":"#010000ff","out":20.999546669609032}, +{"a":"#ffffff00","b":"#000001ff","out":20.99984604458682}, +{"a":"#ffffff00","b":"#fefffeff","out":1.0023400516282288}, +{"a":"#ffffff00","b":"#3daee9ff","out":2.459967205803079}, +{"a":"#ffffff00","b":"#232629ff","out":16.193545491054348}, +{"a":"#ffffff00","b":"#eff0f1ff","out":1.136342731496538}, +{"a":"#ffffff00","b":"#fcfcfcff","out":1.0250901015074352}, +{"a":"#ffffff00","b":"#31363bff","out":12.798433206220594}, +{"a":"#ffffff00","b":"#1b1e20ff","out":17.880237134195973}, +{"a":"#ffffff00","b":"#da4453ff","out":4.272815041160916}, +{"a":"#ffffff00","b":"#27ae60ff","out":2.8353296849487504}, +{"a":"#ffffff00","b":"#f67400ff","out":2.8159949980347574}, +{"a":"#ffffff00","b":"#2980b9ff","out":4.260448948712035}, +{"a":"#ffffff00","b":"#7f8c8dff","out":3.423608921759207}, +{"a":"#ffffff00","b":"#9b59b6ff","out":4.6446149753436705}, +{"a":"#ffffff00","b":"#1d99f3ff","out":3.007026621900125}, +{"a":"#ffffff00","b":"#dee0e2ff","out":1.3127559836282932}, +{"a":"#ffffff00","b":"#292c30ff","out":14.866342511442046}, +{"a":"#ffffff00","b":"#e93a9aff","out":3.809227603105063}, +{"a":"#ffffff00","b":"#e93d58ff","out":4.00734282211082}, +{"a":"#ffffff00","b":"#e9643aff","out":3.298657441963111}, +{"a":"#ffffff00","b":"#e8cb2dff","out":1.5974094165603232}, +{"a":"#ffffff00","b":"#3dd425ff","out":1.9568409348268905}, +{"a":"#ffffff00","b":"#00d3b8ff","out":1.8861360802032001}, +{"a":"#ffffff00","b":"#b875dcff","out":3.136996804516895}, +{"a":"#ffffff00","b":"#926ee4ff","out":3.733103199583586}, +{"a":"#ffffff00","b":"#686b6fff","out":5.330263509389798}, +{"a":"#ffffff00","b":"#3daee980","out":2.459967205803079}, +{"a":"#ffffff00","b":"#ffffff00","out":1}, +{"a":"#ffffff00","b":"#00000080","out":21}, +{"a":"#ffffff00","b":"#ff000033","out":3.9984767707539985} +], +"lighten": [ +{"amount":-0.5,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#000000ff","out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#000000ff","out":{"hex":"#904141ff","u16":[37051,16792,16792,65535]}}, +{"amount":0.1,"gain":1,"in":"#000000ff","out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"amount":0.1,"gain":2,"in":"#000000ff","out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"amount":0.25,"gain":0,"in":"#000000ff","out":{"hex":"#ff4040ff","u16":[65535,16404,16404,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#000000ff","out":{"hex":"#ce6b6bff","u16":[52929,27562,27562,65535]}}, +{"amount":0.25,"gain":1,"in":"#000000ff","out":{"hex":"#888888ff","u16":[34899,34899,34899,65535]}}, +{"amount":0.25,"gain":2,"in":"#000000ff","out":{"hex":"#888888ff","u16":[34899,34899,34899,65535]}}, +{"amount":0.5,"gain":0,"in":"#000000ff","out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#000000ff","out":{"hex":"#e0aeaeff","u16":[57502,44773,44773,65535]}}, +{"amount":0.5,"gain":1,"in":"#000000ff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#000000ff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#000000ff","out":{"hex":"#ffd6d6ff","u16":[65535,55089,55089,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#000000ff","out":{"hex":"#f0dbdbff","u16":[61676,56311,56311,65535]}}, +{"amount":0.75,"gain":1,"in":"#000000ff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.75,"gain":2,"in":"#000000ff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.9,"gain":0,"in":"#000000ff","out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#000000ff","out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]}}, +{"amount":0.9,"gain":1,"in":"#000000ff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":0.9,"gain":2,"in":"#000000ff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":1,"gain":0,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.25,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.25,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.5,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.5,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.75,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.75,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.75,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.9,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.9,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.9,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#010101ff","out":{"hex":"#020000ff","u16":[520,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#010101ff","out":{"hex":"#020101ff","u16":[414,188,188,65535]}}, +{"amount":0,"gain":1,"in":"#010101ff","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"amount":0,"gain":2,"in":"#010101ff","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"amount":0.1,"gain":0,"in":"#010101ff","out":{"hex":"#b50000ff","u16":[46515,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#010101ff","out":{"hex":"#904141ff","u16":[37052,16792,16792,65535]}}, +{"amount":0.1,"gain":1,"in":"#010101ff","out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"amount":0.1,"gain":2,"in":"#010101ff","out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"amount":0.25,"gain":0,"in":"#010101ff","out":{"hex":"#ff4040ff","u16":[65535,16405,16405,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#010101ff","out":{"hex":"#ce6b6bff","u16":[52929,27563,27563,65535]}}, +{"amount":0.25,"gain":1,"in":"#010101ff","out":{"hex":"#888888ff","u16":[34899,34899,34899,65535]}}, +{"amount":0.25,"gain":2,"in":"#010101ff","out":{"hex":"#888888ff","u16":[34899,34899,34899,65535]}}, +{"amount":0.5,"gain":0,"in":"#010101ff","out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#010101ff","out":{"hex":"#e0aeaeff","u16":[57502,44773,44773,65535]}}, +{"amount":0.5,"gain":1,"in":"#010101ff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#010101ff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#010101ff","out":{"hex":"#ffd6d6ff","u16":[65535,55089,55089,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#010101ff","out":{"hex":"#f0dbdbff","u16":[61676,56311,56311,65535]}}, +{"amount":0.75,"gain":1,"in":"#010101ff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.75,"gain":2,"in":"#010101ff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.9,"gain":0,"in":"#010101ff","out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#010101ff","out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]}}, +{"amount":0.9,"gain":1,"in":"#010101ff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":0.9,"gain":2,"in":"#010101ff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":1,"gain":0,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#020202ff","out":{"hex":"#040000ff","u16":[1039,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#020202ff","out":{"hex":"#030101ff","u16":[828,375,375,65535]}}, +{"amount":0,"gain":1,"in":"#020202ff","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"amount":0,"gain":2,"in":"#020202ff","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"amount":0.1,"gain":0,"in":"#020202ff","out":{"hex":"#b50000ff","u16":[46518,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#020202ff","out":{"hex":"#904141ff","u16":[37055,16793,16793,65535]}}, +{"amount":0.1,"gain":1,"in":"#020202ff","out":{"hex":"#5a5a5aff","u16":[23013,23013,23013,65535]}}, +{"amount":0.1,"gain":2,"in":"#020202ff","out":{"hex":"#5a5a5aff","u16":[23013,23013,23013,65535]}}, +{"amount":0.25,"gain":0,"in":"#020202ff","out":{"hex":"#ff4040ff","u16":[65535,16408,16408,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#020202ff","out":{"hex":"#ce6b6bff","u16":[52929,27564,27564,65535]}}, +{"amount":0.25,"gain":1,"in":"#020202ff","out":{"hex":"#888888ff","u16":[34900,34900,34900,65535]}}, +{"amount":0.25,"gain":2,"in":"#020202ff","out":{"hex":"#888888ff","u16":[34900,34900,34900,65535]}}, +{"amount":0.5,"gain":0,"in":"#020202ff","out":{"hex":"#ffa1a1ff","u16":[65535,41450,41450,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#020202ff","out":{"hex":"#e0aeaeff","u16":[57502,44773,44773,65535]}}, +{"amount":0.5,"gain":1,"in":"#020202ff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#020202ff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#020202ff","out":{"hex":"#ffd6d6ff","u16":[65535,55089,55089,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#020202ff","out":{"hex":"#f0dbdbff","u16":[61676,56311,56311,65535]}}, +{"amount":0.75,"gain":1,"in":"#020202ff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.75,"gain":2,"in":"#020202ff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.9,"gain":0,"in":"#020202ff","out":{"hex":"#fff0f0ff","u16":[65535,61612,61612,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#020202ff","out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]}}, +{"amount":0.9,"gain":1,"in":"#020202ff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":0.9,"gain":2,"in":"#020202ff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":1,"gain":0,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#040404ff","out":{"hex":"#080000ff","u16":[2078,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#040404ff","out":{"hex":"#060303ff","u16":[1655,750,750,65535]}}, +{"amount":0,"gain":1,"in":"#040404ff","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"amount":0,"gain":2,"in":"#040404ff","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"amount":0.1,"gain":0,"in":"#040404ff","out":{"hex":"#b50000ff","u16":[46534,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#040404ff","out":{"hex":"#904141ff","u16":[37067,16799,16799,65535]}}, +{"amount":0.1,"gain":1,"in":"#040404ff","out":{"hex":"#5a5a5aff","u16":[23021,23021,23021,65535]}}, +{"amount":0.1,"gain":2,"in":"#040404ff","out":{"hex":"#5a5a5aff","u16":[23021,23021,23021,65535]}}, +{"amount":0.25,"gain":0,"in":"#040404ff","out":{"hex":"#ff4040ff","u16":[65535,16421,16421,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#040404ff","out":{"hex":"#ce6b6bff","u16":[52930,27570,27570,65535]}}, +{"amount":0.25,"gain":1,"in":"#040404ff","out":{"hex":"#888888ff","u16":[34904,34904,34904,65535]}}, +{"amount":0.25,"gain":2,"in":"#040404ff","out":{"hex":"#888888ff","u16":[34904,34904,34904,65535]}}, +{"amount":0.5,"gain":0,"in":"#040404ff","out":{"hex":"#ffa1a1ff","u16":[65535,41453,41453,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#040404ff","out":{"hex":"#e0aeaeff","u16":[57503,44775,44775,65535]}}, +{"amount":0.5,"gain":1,"in":"#040404ff","out":{"hex":"#bababaff","u16":[47826,47826,47826,65535]}}, +{"amount":0.5,"gain":2,"in":"#040404ff","out":{"hex":"#bababaff","u16":[47826,47826,47826,65535]}}, +{"amount":0.75,"gain":0,"in":"#040404ff","out":{"hex":"#ffd6d6ff","u16":[65535,55090,55090,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#040404ff","out":{"hex":"#f0dbdbff","u16":[61676,56312,56312,65535]}}, +{"amount":0.75,"gain":1,"in":"#040404ff","out":{"hex":"#e0e0e0ff","u16":[57503,57503,57503,65535]}}, +{"amount":0.75,"gain":2,"in":"#040404ff","out":{"hex":"#e0e0e0ff","u16":[57503,57503,57503,65535]}}, +{"amount":0.9,"gain":0,"in":"#040404ff","out":{"hex":"#fff0f0ff","u16":[65535,61612,61612,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#040404ff","out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]}}, +{"amount":0.9,"gain":1,"in":"#040404ff","out":{"hex":"#f3f3f3ff","u16":[62471,62471,62471,65535]}}, +{"amount":0.9,"gain":2,"in":"#040404ff","out":{"hex":"#f3f3f3ff","u16":[62471,62471,62471,65535]}}, +{"amount":1,"gain":0,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#080808ff","out":{"hex":"#100000ff","u16":[4156,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#080808ff","out":{"hex":"#0d0606ff","u16":[3311,1500,1500,65535]}}, +{"amount":0,"gain":1,"in":"#080808ff","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"amount":0,"gain":2,"in":"#080808ff","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"amount":0.1,"gain":0,"in":"#080808ff","out":{"hex":"#b50000ff","u16":[46607,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#080808ff","out":{"hex":"#904141ff","u16":[37126,16826,16826,65535]}}, +{"amount":0.1,"gain":1,"in":"#080808ff","out":{"hex":"#5a5a5aff","u16":[23057,23057,23057,65535]}}, +{"amount":0.1,"gain":2,"in":"#080808ff","out":{"hex":"#5a5a5aff","u16":[23057,23057,23057,65535]}}, +{"amount":0.25,"gain":0,"in":"#080808ff","out":{"hex":"#ff4040ff","u16":[65535,16478,16478,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#080808ff","out":{"hex":"#ce6b6bff","u16":[52936,27598,27598,65535]}}, +{"amount":0.25,"gain":1,"in":"#080808ff","out":{"hex":"#888888ff","u16":[34922,34922,34922,65535]}}, +{"amount":0.25,"gain":2,"in":"#080808ff","out":{"hex":"#888888ff","u16":[34922,34922,34922,65535]}}, +{"amount":0.5,"gain":0,"in":"#080808ff","out":{"hex":"#ffa1a1ff","u16":[65535,41465,41465,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#080808ff","out":{"hex":"#e0aeaeff","u16":[57506,44786,44786,65535]}}, +{"amount":0.5,"gain":1,"in":"#080808ff","out":{"hex":"#bababaff","u16":[47834,47834,47834,65535]}}, +{"amount":0.5,"gain":2,"in":"#080808ff","out":{"hex":"#bababaff","u16":[47834,47834,47834,65535]}}, +{"amount":0.75,"gain":0,"in":"#080808ff","out":{"hex":"#ffd6d6ff","u16":[65535,55095,55095,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#080808ff","out":{"hex":"#f0dbdbff","u16":[61678,56316,56316,65535]}}, +{"amount":0.75,"gain":1,"in":"#080808ff","out":{"hex":"#e0e0e0ff","u16":[57506,57506,57506,65535]}}, +{"amount":0.75,"gain":2,"in":"#080808ff","out":{"hex":"#e0e0e0ff","u16":[57506,57506,57506,65535]}}, +{"amount":0.9,"gain":0,"in":"#080808ff","out":{"hex":"#fff0f0ff","u16":[65535,61613,61613,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#080808ff","out":{"hex":"#f9f1f1ff","u16":[64025,62045,62045,65535]}}, +{"amount":0.9,"gain":1,"in":"#080808ff","out":{"hex":"#f3f3f3ff","u16":[62472,62472,62472,65535]}}, +{"amount":0.9,"gain":2,"in":"#080808ff","out":{"hex":"#f3f3f3ff","u16":[62472,62472,62472,65535]}}, +{"amount":1,"gain":0,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#101010ff","out":{"hex":"#200000ff","u16":[8312,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#101010ff","out":{"hex":"#1a0c0cff","u16":[6621,3001,3001,65535]}}, +{"amount":0,"gain":1,"in":"#101010ff","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"amount":0,"gain":2,"in":"#101010ff","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"amount":0.1,"gain":0,"in":"#101010ff","out":{"hex":"#b70000ff","u16":[46942,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#101010ff","out":{"hex":"#914242ff","u16":[37392,16946,16946,65535]}}, +{"amount":0.1,"gain":1,"in":"#101010ff","out":{"hex":"#5a5a5aff","u16":[23222,23222,23222,65535]}}, +{"amount":0.1,"gain":2,"in":"#101010ff","out":{"hex":"#5a5a5aff","u16":[23222,23222,23222,65535]}}, +{"amount":0.25,"gain":0,"in":"#101010ff","out":{"hex":"#ff4141ff","u16":[65535,16739,16739,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#101010ff","out":{"hex":"#ce6c6cff","u16":[52961,27724,27724,65535]}}, +{"amount":0.25,"gain":1,"in":"#101010ff","out":{"hex":"#888888ff","u16":[35006,35006,35006,65535]}}, +{"amount":0.25,"gain":2,"in":"#101010ff","out":{"hex":"#888888ff","u16":[35006,35006,35006,65535]}}, +{"amount":0.5,"gain":0,"in":"#101010ff","out":{"hex":"#ffa2a2ff","u16":[65535,41523,41523,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#101010ff","out":{"hex":"#e0aeaeff","u16":[57522,44833,44833,65535]}}, +{"amount":0.5,"gain":1,"in":"#101010ff","out":{"hex":"#bababaff","u16":[47873,47873,47873,65535]}}, +{"amount":0.5,"gain":2,"in":"#101010ff","out":{"hex":"#bababaff","u16":[47873,47873,47873,65535]}}, +{"amount":0.75,"gain":0,"in":"#101010ff","out":{"hex":"#ffd6d6ff","u16":[65535,55115,55115,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#101010ff","out":{"hex":"#f0dbdbff","u16":[61685,56334,56334,65535]}}, +{"amount":0.75,"gain":1,"in":"#101010ff","out":{"hex":"#e0e0e0ff","u16":[57522,57522,57522,65535]}}, +{"amount":0.75,"gain":2,"in":"#101010ff","out":{"hex":"#e0e0e0ff","u16":[57522,57522,57522,65535]}}, +{"amount":0.9,"gain":0,"in":"#101010ff","out":{"hex":"#fff0f0ff","u16":[65535,61621,61621,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#101010ff","out":{"hex":"#f9f1f1ff","u16":[64028,62051,62051,65535]}}, +{"amount":0.9,"gain":1,"in":"#101010ff","out":{"hex":"#f3f3f3ff","u16":[62478,62478,62478,65535]}}, +{"amount":0.9,"gain":2,"in":"#101010ff","out":{"hex":"#f3f3f3ff","u16":[62478,62478,62478,65535]}}, +{"amount":1,"gain":0,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#202020ff","out":{"hex":"#410000ff","u16":[16624,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#202020ff","out":{"hex":"#341717ff","u16":[13242,6001,6001,65535]}}, +{"amount":0,"gain":1,"in":"#202020ff","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"amount":0,"gain":2,"in":"#202020ff","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"amount":0.1,"gain":0,"in":"#202020ff","out":{"hex":"#bc0000ff","u16":[48444,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#202020ff","out":{"hex":"#964444ff","u16":[38589,17489,17489,65535]}}, +{"amount":0.1,"gain":1,"in":"#202020ff","out":{"hex":"#5d5d5dff","u16":[23966,23966,23966,65535]}}, +{"amount":0.1,"gain":2,"in":"#202020ff","out":{"hex":"#5d5d5dff","u16":[23966,23966,23966,65535]}}, +{"amount":0.25,"gain":0,"in":"#202020ff","out":{"hex":"#ff4646ff","u16":[65535,17879,17879,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#202020ff","out":{"hex":"#cf6e6eff","u16":[53079,28296,28296,65535]}}, +{"amount":0.25,"gain":1,"in":"#202020ff","out":{"hex":"#8a8a8aff","u16":[35389,35389,35389,65535]}}, +{"amount":0.25,"gain":2,"in":"#202020ff","out":{"hex":"#8a8a8aff","u16":[35389,35389,35389,65535]}}, +{"amount":0.5,"gain":0,"in":"#202020ff","out":{"hex":"#ffa3a3ff","u16":[65535,41788,41788,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#202020ff","out":{"hex":"#e0afafff","u16":[57593,45049,45049,65535]}}, +{"amount":0.5,"gain":1,"in":"#202020ff","out":{"hex":"#bbbbbbff","u16":[48049,48049,48049,65535]}}, +{"amount":0.5,"gain":2,"in":"#202020ff","out":{"hex":"#bbbbbbff","u16":[48049,48049,48049,65535]}}, +{"amount":0.75,"gain":0,"in":"#202020ff","out":{"hex":"#ffd7d7ff","u16":[65535,55210,55210,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#202020ff","out":{"hex":"#f0dcdcff","u16":[61717,56416,56416,65535]}}, +{"amount":0.75,"gain":1,"in":"#202020ff","out":{"hex":"#e0e0e0ff","u16":[57593,57593,57593,65535]}}, +{"amount":0.75,"gain":2,"in":"#202020ff","out":{"hex":"#e0e0e0ff","u16":[57593,57593,57593,65535]}}, +{"amount":0.9,"gain":0,"in":"#202020ff","out":{"hex":"#fff0f0ff","u16":[65535,61654,61654,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#202020ff","out":{"hex":"#f9f2f2ff","u16":[64041,62080,62080,65535]}}, +{"amount":0.9,"gain":1,"in":"#202020ff","out":{"hex":"#f3f3f3ff","u16":[62503,62503,62503,65535]}}, +{"amount":0.9,"gain":2,"in":"#202020ff","out":{"hex":"#f3f3f3ff","u16":[62503,62503,62503,65535]}}, +{"amount":1,"gain":0,"in":"#202020ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#202020ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#202020ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#202020ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#202020ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#202020ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#202020ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#202020ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#404040ff","out":{"hex":"#810000ff","u16":[33248,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#404040ff","out":{"hex":"#672f2fff","u16":[26484,12003,12003,65535]}}, +{"amount":0,"gain":1,"in":"#404040ff","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"amount":0,"gain":2,"in":"#404040ff","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"amount":0.1,"gain":0,"in":"#404040ff","out":{"hex":"#d50000ff","u16":[54725,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#404040ff","out":{"hex":"#aa4d4dff","u16":[43592,19756,19756,65535]}}, +{"amount":0.1,"gain":1,"in":"#404040ff","out":{"hex":"#696969ff","u16":[27073,27073,27073,65535]}}, +{"amount":0.1,"gain":2,"in":"#404040ff","out":{"hex":"#696969ff","u16":[27073,27073,27073,65535]}}, +{"amount":0.25,"gain":0,"in":"#404040ff","out":{"hex":"#ff5757ff","u16":[65535,22264,22264,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#404040ff","out":{"hex":"#d17878ff","u16":[53613,30763,30763,65535]}}, +{"amount":0.25,"gain":1,"in":"#404040ff","out":{"hex":"#909090ff","u16":[37089,37089,37089,65535]}}, +{"amount":0.25,"gain":2,"in":"#404040ff","out":{"hex":"#909090ff","u16":[37089,37089,37089,65535]}}, +{"amount":0.5,"gain":0,"in":"#404040ff","out":{"hex":"#ffa7a7ff","u16":[65535,42981,42981,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#404040ff","out":{"hex":"#e1b3b3ff","u16":[57916,46027,46027,65535]}}, +{"amount":0.5,"gain":1,"in":"#404040ff","out":{"hex":"#bebebeff","u16":[48849,48849,48849,65535]}}, +{"amount":0.5,"gain":2,"in":"#404040ff","out":{"hex":"#bebebeff","u16":[48849,48849,48849,65535]}}, +{"amount":0.75,"gain":0,"in":"#404040ff","out":{"hex":"#ffd9d9ff","u16":[65535,55642,55642,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#404040ff","out":{"hex":"#f1ddddff","u16":[61867,56793,56793,65535]}}, +{"amount":0.75,"gain":1,"in":"#404040ff","out":{"hex":"#e1e1e1ff","u16":[57916,57916,57916,65535]}}, +{"amount":0.75,"gain":2,"in":"#404040ff","out":{"hex":"#e1e1e1ff","u16":[57916,57916,57916,65535]}}, +{"amount":0.9,"gain":0,"in":"#404040ff","out":{"hex":"#fff0f0ff","u16":[65535,61806,61806,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#404040ff","out":{"hex":"#f9f2f2ff","u16":[64098,62215,62215,65535]}}, +{"amount":0.9,"gain":1,"in":"#404040ff","out":{"hex":"#f4f4f4ff","u16":[62621,62621,62621,65535]}}, +{"amount":0.9,"gain":2,"in":"#404040ff","out":{"hex":"#f4f4f4ff","u16":[62621,62621,62621,65535]}}, +{"amount":1,"gain":0,"in":"#404040ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#404040ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#404040ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#404040ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#404040ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#404040ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#404040ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#404040ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#7f7f7fff","out":{"hex":"#ff1515ff","u16":[65535,5339,5339,65535]}}, +{"amount":0,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#cb5d5dff","u16":[52265,24019,24019,65535]}}, +{"amount":0,"gain":1,"in":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"gain":2,"in":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"gain":0,"in":"#7f7f7fff","out":{"hex":"#ff5b5bff","u16":[65535,23385,23385,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#d17a7aff","u16":[53771,31454,31454,65535]}}, +{"amount":0.1,"gain":1,"in":"#7f7f7fff","out":{"hex":"#929292ff","u16":[37578,37578,37578,65535]}}, +{"amount":0.1,"gain":2,"in":"#7f7f7fff","out":{"hex":"#929292ff","u16":[37578,37578,37578,65535]}}, +{"amount":0.25,"gain":0,"in":"#7f7f7fff","out":{"hex":"#ff8989ff","u16":[65535,35089,35089,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#da9b9bff","u16":[55940,39724,39724,65535]}}, +{"amount":0.25,"gain":1,"in":"#7f7f7fff","out":{"hex":"#aaaaaaff","u16":[43787,43787,43787,65535]}}, +{"amount":0.25,"gain":2,"in":"#7f7f7fff","out":{"hex":"#aaaaaaff","u16":[43787,43787,43787,65535]}}, +{"amount":0.5,"gain":0,"in":"#7f7f7fff","out":{"hex":"#ffbabaff","u16":[65535,47911,47911,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#e7c3c3ff","u16":[59346,50145,50145,65535]}}, +{"amount":0.5,"gain":1,"in":"#7f7f7fff","out":{"hex":"#cbcbcbff","u16":[52265,52265,52265,65535]}}, +{"amount":0.5,"gain":2,"in":"#7f7f7fff","out":{"hex":"#cbcbcbff","u16":[52265,52265,52265,65535]}}, +{"amount":0.75,"gain":0,"in":"#7f7f7fff","out":{"hex":"#ffe0e0ff","u16":[65535,57537,57537,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#f3e3e3ff","u16":[62533,58450,58450,65535]}}, +{"amount":0.75,"gain":1,"in":"#7f7f7fff","out":{"hex":"#e7e7e7ff","u16":[59346,59346,59346,65535]}}, +{"amount":0.75,"gain":2,"in":"#7f7f7fff","out":{"hex":"#e7e7e7ff","u16":[59346,59346,59346,65535]}}, +{"amount":0.9,"gain":0,"in":"#7f7f7fff","out":{"hex":"#fff3f3ff","u16":[65535,62483,62483,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#faf4f4ff","u16":[64354,62816,62816,65535]}}, +{"amount":0.9,"gain":1,"in":"#7f7f7fff","out":{"hex":"#f6f6f6ff","u16":[63147,63147,63147,65535]}}, +{"amount":0.9,"gain":2,"in":"#7f7f7fff","out":{"hex":"#f6f6f6ff","u16":[63147,63147,63147,65535]}}, +{"amount":1,"gain":0,"in":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#808080ff","out":{"hex":"#ff1e1eff","u16":[65535,7619,7619,65535]}}, +{"amount":0,"gain":0.5,"in":"#808080ff","out":{"hex":"#cc5f5fff","u16":[52338,24438,24438,65535]}}, +{"amount":0,"gain":1,"in":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"gain":2,"in":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"gain":0,"in":"#808080ff","out":{"hex":"#ff5d5dff","u16":[65535,23821,23821,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#808080ff","out":{"hex":"#d17b7bff","u16":[53835,31728,31728,65535]}}, +{"amount":0.1,"gain":1,"in":"#808080ff","out":{"hex":"#939393ff","u16":[37774,37774,37774,65535]}}, +{"amount":0.1,"gain":2,"in":"#808080ff","out":{"hex":"#939393ff","u16":[37774,37774,37774,65535]}}, +{"amount":0.25,"gain":0,"in":"#808080ff","out":{"hex":"#ff8989ff","u16":[65535,35314,35314,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#808080ff","out":{"hex":"#da9b9bff","u16":[55991,39898,39898,65535]}}, +{"amount":0.25,"gain":1,"in":"#808080ff","out":{"hex":"#abababff","u16":[43923,43923,43923,65535]}}, +{"amount":0.25,"gain":2,"in":"#808080ff","out":{"hex":"#abababff","u16":[43923,43923,43923,65535]}}, +{"amount":0.5,"gain":0,"in":"#808080ff","out":{"hex":"#ffbbbbff","u16":[65535,48014,48014,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#808080ff","out":{"hex":"#e7c3c3ff","u16":[59378,50232,50232,65535]}}, +{"amount":0.5,"gain":1,"in":"#808080ff","out":{"hex":"#ccccccff","u16":[52338,52338,52338,65535]}}, +{"amount":0.5,"gain":2,"in":"#808080ff","out":{"hex":"#ccccccff","u16":[52338,52338,52338,65535]}}, +{"amount":0.75,"gain":0,"in":"#808080ff","out":{"hex":"#ffe0e0ff","u16":[65535,57579,57579,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#808080ff","out":{"hex":"#f3e4e4ff","u16":[62547,58486,58486,65535]}}, +{"amount":0.75,"gain":1,"in":"#808080ff","out":{"hex":"#e7e7e7ff","u16":[59378,59378,59378,65535]}}, +{"amount":0.75,"gain":2,"in":"#808080ff","out":{"hex":"#e7e7e7ff","u16":[59378,59378,59378,65535]}}, +{"amount":0.9,"gain":0,"in":"#808080ff","out":{"hex":"#fff3f3ff","u16":[65535,62498,62498,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#808080ff","out":{"hex":"#faf4f4ff","u16":[64360,62829,62829,65535]}}, +{"amount":0.9,"gain":1,"in":"#808080ff","out":{"hex":"#f6f6f6ff","u16":[63158,63158,63158,65535]}}, +{"amount":0.9,"gain":2,"in":"#808080ff","out":{"hex":"#f6f6f6ff","u16":[63158,63158,63158,65535]}}, +{"amount":1,"gain":0,"in":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#818181ff","out":{"hex":"#ff2424ff","u16":[65535,9292,9292,65535]}}, +{"amount":0,"gain":0.5,"in":"#818181ff","out":{"hex":"#cc6161ff","u16":[52412,24852,24852,65535]}}, +{"amount":0,"gain":1,"in":"#818181ff","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"amount":0,"gain":2,"in":"#818181ff","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"amount":0.1,"gain":0,"in":"#818181ff","out":{"hex":"#ff5e5eff","u16":[65535,24251,24251,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#818181ff","out":{"hex":"#d27d7dff","u16":[53899,32002,32002,65535]}}, +{"amount":0.1,"gain":1,"in":"#818181ff","out":{"hex":"#949494ff","u16":[37970,37970,37970,65535]}}, +{"amount":0.1,"gain":2,"in":"#818181ff","out":{"hex":"#949494ff","u16":[37970,37970,37970,65535]}}, +{"amount":0.25,"gain":0,"in":"#818181ff","out":{"hex":"#ff8a8aff","u16":[65535,35539,35539,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#818181ff","out":{"hex":"#da9c9cff","u16":[56042,40072,40072,65535]}}, +{"amount":0.25,"gain":1,"in":"#818181ff","out":{"hex":"#abababff","u16":[44059,44059,44059,65535]}}, +{"amount":0.25,"gain":2,"in":"#818181ff","out":{"hex":"#abababff","u16":[44059,44059,44059,65535]}}, +{"amount":0.5,"gain":0,"in":"#818181ff","out":{"hex":"#ffbbbbff","u16":[65535,48118,48118,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#818181ff","out":{"hex":"#e7c4c4ff","u16":[59410,50320,50320,65535]}}, +{"amount":0.5,"gain":1,"in":"#818181ff","out":{"hex":"#ccccccff","u16":[52412,52412,52412,65535]}}, +{"amount":0.5,"gain":2,"in":"#818181ff","out":{"hex":"#ccccccff","u16":[52412,52412,52412,65535]}}, +{"amount":0.75,"gain":0,"in":"#818181ff","out":{"hex":"#ffe0e0ff","u16":[65535,57620,57620,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#818181ff","out":{"hex":"#f3e4e4ff","u16":[62562,58523,58523,65535]}}, +{"amount":0.75,"gain":1,"in":"#818181ff","out":{"hex":"#e7e7e7ff","u16":[59410,59410,59410,65535]}}, +{"amount":0.75,"gain":2,"in":"#818181ff","out":{"hex":"#e7e7e7ff","u16":[59410,59410,59410,65535]}}, +{"amount":0.9,"gain":0,"in":"#818181ff","out":{"hex":"#fff3f3ff","u16":[65535,62513,62513,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#818181ff","out":{"hex":"#faf5f5ff","u16":[64366,62843,62843,65535]}}, +{"amount":0.9,"gain":1,"in":"#818181ff","out":{"hex":"#f6f6f6ff","u16":[63170,63170,63170,65535]}}, +{"amount":0.9,"gain":2,"in":"#818181ff","out":{"hex":"#f6f6f6ff","u16":[63170,63170,63170,65535]}}, +{"amount":1,"gain":0,"in":"#818181ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#818181ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#818181ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#818181ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#818181ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#818181ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#818181ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#818181ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#bfbfbfff","out":{"hex":"#ff5b5bff","u16":[65535,23398,23398,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#d17a7aff","u16":[53773,31462,31462,65535]}}, +{"amount":-0.5,"gain":1,"in":"#bfbfbfff","out":{"hex":"#929292ff","u16":[37584,37584,37584,65535]}}, +{"amount":-0.5,"gain":2,"in":"#bfbfbfff","out":{"hex":"#929292ff","u16":[37584,37584,37584,65535]}}, +{"amount":0,"gain":0,"in":"#bfbfbfff","out":{"hex":"#ffa9a9ff","u16":[65535,43333,43333,65535]}}, +{"amount":0,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#e2b4b4ff","u16":[58014,46317,46317,65535]}}, +{"amount":0,"gain":1,"in":"#bfbfbfff","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"amount":0,"gain":2,"in":"#bfbfbfff","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"amount":0.1,"gain":0,"in":"#bfbfbfff","out":{"hex":"#ffb4b4ff","u16":[65535,46147,46147,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#e5bdbdff","u16":[58818,48659,48659,65535]}}, +{"amount":0.1,"gain":1,"in":"#bfbfbfff","out":{"hex":"#c7c7c7ff","u16":[51023,51023,51023,65535]}}, +{"amount":0.1,"gain":2,"in":"#bfbfbfff","out":{"hex":"#c7c7c7ff","u16":[51023,51023,51023,65535]}}, +{"amount":0.25,"gain":0,"in":"#bfbfbfff","out":{"hex":"#ffc3c3ff","u16":[65535,50018,50018,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#e9cacaff","u16":[60001,51936,51936,65535]}}, +{"amount":0.25,"gain":1,"in":"#bfbfbfff","out":{"hex":"#d1d1d1ff","u16":[53773,53773,53773,65535]}}, +{"amount":0.25,"gain":2,"in":"#bfbfbfff","out":{"hex":"#d1d1d1ff","u16":[53773,53773,53773,65535]}}, +{"amount":0.5,"gain":0,"in":"#bfbfbfff","out":{"hex":"#ffd9d9ff","u16":[65535,55772,55772,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#f1ddddff","u16":[61912,56906,56906,65535]}}, +{"amount":0.5,"gain":1,"in":"#bfbfbfff","out":{"hex":"#e2e2e2ff","u16":[58014,58014,58014,65535]}}, +{"amount":0.5,"gain":2,"in":"#bfbfbfff","out":{"hex":"#e2e2e2ff","u16":[58014,58014,58014,65535]}}, +{"amount":0.75,"gain":0,"in":"#bfbfbfff","out":{"hex":"#ffededff","u16":[65535,60889,60889,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#f8efefff","u16":[63754,61403,61403,65535]}}, +{"amount":0.75,"gain":1,"in":"#bfbfbfff","out":{"hex":"#f1f1f1ff","u16":[61912,61912,61912,65535]}}, +{"amount":0.75,"gain":2,"in":"#bfbfbfff","out":{"hex":"#f1f1f1ff","u16":[61912,61912,61912,65535]}}, +{"amount":0.9,"gain":0,"in":"#bfbfbfff","out":{"hex":"#fff8f8ff","u16":[65535,63725,63725,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#fcf9f9ff","u16":[64830,63920,63920,65535]}}, +{"amount":0.9,"gain":1,"in":"#bfbfbfff","out":{"hex":"#f9f9f9ff","u16":[64115,64115,64115,65535]}}, +{"amount":0.9,"gain":2,"in":"#bfbfbfff","out":{"hex":"#f9f9f9ff","u16":[64115,64115,64115,65535]}}, +{"amount":1,"gain":0,"in":"#bfbfbfff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#bfbfbfff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#bfbfbfff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#bfbfbfff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#bfbfbfff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#bfbfbfff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#ff6060ff","u16":[65535,24558,24558,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#d27d7dff","u16":[53946,32200,32200,65535]}}, +{"amount":-0.5,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#949494ff","u16":[38112,38112,38112,65535]}}, +{"amount":-0.5,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#949494ff","u16":[38112,38112,38112,65535]}}, +{"amount":0,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#ffaaaaff","u16":[65535,43711,43711,65535]}}, +{"amount":0,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#e2b5b5ff","u16":[58119,46630,46630,65535]}}, +{"amount":0,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"amount":0,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"amount":0.1,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#ffb5b5ff","u16":[65535,46463,46463,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#e5bebeff","u16":[58911,48924,48924,65535]}}, +{"amount":0.1,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#c7c7c7ff","u16":[51244,51244,51244,65535]}}, +{"amount":0.1,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#c7c7c7ff","u16":[51244,51244,51244,65535]}}, +{"amount":0.25,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#ffc4c4ff","u16":[65535,50257,50257,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#eacbcbff","u16":[60077,52141,52141,65535]}}, +{"amount":0.25,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#d2d2d2ff","u16":[53946,53946,53946,65535]}}, +{"amount":0.25,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#d2d2d2ff","u16":[53946,53946,53946,65535]}}, +{"amount":0.5,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#ffdadaff","u16":[65535,55912,55912,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#f1dedeff","u16":[61960,57028,57028,65535]}}, +{"amount":0.5,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#e2e2e2ff","u16":[58119,58119,58119,65535]}}, +{"amount":0.5,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#e2e2e2ff","u16":[58119,58119,58119,65535]}}, +{"amount":0.75,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#ffededff","u16":[65535,60952,60952,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#f8efefff","u16":[63778,61458,61458,65535]}}, +{"amount":0.75,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#f1f1f1ff","u16":[61960,61960,61960,65535]}}, +{"amount":0.75,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#f1f1f1ff","u16":[61960,61960,61960,65535]}}, +{"amount":0.9,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#fff8f8ff","u16":[65535,63749,63749,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#fcf9f9ff","u16":[64839,63942,63942,65535]}}, +{"amount":0.9,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#fafafaff","u16":[64134,64134,64134,65535]}}, +{"amount":0.9,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#fafafaff","u16":[64134,64134,64134,65535]}}, +{"amount":1,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fdfdfdff","out":{"hex":"#fffbfbff","u16":[65535,64552,64552,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#fefcfcff","u16":[65150,64657,64657,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fdfdfdff","out":{"hex":"#fcfcfcff","u16":[64762,64762,64762,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fdfdfdff","out":{"hex":"#fcfcfcff","u16":[64762,64762,64762,65535]}}, +{"amount":0,"gain":0,"in":"#fdfdfdff","out":{"hex":"#fffcfcff","u16":[65535,64881,64881,65535]}}, +{"amount":0,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#fefdfdff","u16":[65279,64951,64951,65535]}}, +{"amount":0,"gain":1,"in":"#fdfdfdff","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"amount":0,"gain":2,"in":"#fdfdfdff","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"amount":0.1,"gain":0,"in":"#fdfdfdff","out":{"hex":"#fffdfdff","u16":[65535,64947,64947,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#fefdfdff","u16":[65304,65010,65010,65535]}}, +{"amount":0.1,"gain":1,"in":"#fdfdfdff","out":{"hex":"#fdfdfdff","u16":[65073,65073,65073,65535]}}, +{"amount":0.1,"gain":2,"in":"#fdfdfdff","out":{"hex":"#fdfdfdff","u16":[65073,65073,65073,65535]}}, +{"amount":0.25,"gain":0,"in":"#fdfdfdff","out":{"hex":"#fffdfdff","u16":[65535,65046,65046,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#fefdfdff","u16":[65343,65098,65098,65535]}}, +{"amount":0.25,"gain":1,"in":"#fdfdfdff","out":{"hex":"#fefefeff","u16":[65150,65150,65150,65535]}}, +{"amount":0.25,"gain":2,"in":"#fdfdfdff","out":{"hex":"#fefefeff","u16":[65150,65150,65150,65535]}}, +{"amount":0.5,"gain":0,"in":"#fdfdfdff","out":{"hex":"#fffefeff","u16":[65535,65209,65209,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#fffefeff","u16":[65407,65244,65244,65535]}}, +{"amount":0.5,"gain":1,"in":"#fdfdfdff","out":{"hex":"#fefefeff","u16":[65279,65279,65279,65535]}}, +{"amount":0.5,"gain":2,"in":"#fdfdfdff","out":{"hex":"#fefefeff","u16":[65279,65279,65279,65535]}}, +{"amount":0.75,"gain":0,"in":"#fdfdfdff","out":{"hex":"#fffefeff","u16":[65535,65372,65372,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#fffefeff","u16":[65471,65390,65390,65535]}}, +{"amount":0.75,"gain":1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65407,65407,65407,65535]}}, +{"amount":0.75,"gain":2,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65407,65407,65407,65535]}}, +{"amount":0.9,"gain":0,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65470,65470,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65509,65477,65477,65535]}}, +{"amount":0.9,"gain":1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65484,65484,65484,65535]}}, +{"amount":0.9,"gain":2,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65484,65484,65484,65535]}}, +{"amount":1,"gain":0,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fefefeff","out":{"hex":"#fffdfdff","u16":[65535,65044,65044,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fefefeff","out":{"hex":"#fefdfdff","u16":[65342,65097,65097,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fefefeff","out":{"hex":"#fdfdfdff","u16":[65149,65149,65149,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fefefeff","out":{"hex":"#fdfdfdff","u16":[65149,65149,65149,65535]}}, +{"amount":0,"gain":0,"in":"#fefefeff","out":{"hex":"#fffefeff","u16":[65535,65208,65208,65535]}}, +{"amount":0,"gain":0.5,"in":"#fefefeff","out":{"hex":"#fffefeff","u16":[65407,65243,65243,65535]}}, +{"amount":0,"gain":1,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"amount":0,"gain":2,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"amount":0.1,"gain":0,"in":"#fefefeff","out":{"hex":"#fffefeff","u16":[65535,65241,65241,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fefefeff","out":{"hex":"#fffefeff","u16":[65420,65272,65272,65535]}}, +{"amount":0.1,"gain":1,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65304,65304,65304,65535]}}, +{"amount":0.1,"gain":2,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65304,65304,65304,65535]}}, +{"amount":0.25,"gain":0,"in":"#fefefeff","out":{"hex":"#fffefeff","u16":[65535,65290,65290,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fefefeff","out":{"hex":"#fffefeff","u16":[65439,65316,65316,65535]}}, +{"amount":0.25,"gain":1,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65342,65342,65342,65535]}}, +{"amount":0.25,"gain":2,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65342,65342,65342,65535]}}, +{"amount":0.5,"gain":0,"in":"#fefefeff","out":{"hex":"#fffefeff","u16":[65535,65372,65372,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fefefeff","out":{"hex":"#fffefeff","u16":[65471,65389,65389,65535]}}, +{"amount":0.5,"gain":1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65407,65407,65407,65535]}}, +{"amount":0.5,"gain":2,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65407,65407,65407,65535]}}, +{"amount":0.75,"gain":0,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65454,65454,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65503,65462,65462,65535]}}, +{"amount":0.75,"gain":1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65471,65471,65471,65535]}}, +{"amount":0.75,"gain":2,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65471,65471,65471,65535]}}, +{"amount":0.9,"gain":0,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65502,65502,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65522,65506,65506,65535]}}, +{"amount":0.9,"gain":1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65509,65509,65509,65535]}}, +{"amount":0.9,"gain":2,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65509,65509,65509,65535]}}, +{"amount":1,"gain":0,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"gain":1,"in":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"gain":2,"in":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#ff0000ff","out":{"hex":"#ff5a5aff","u16":[65535,23011,23011,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#ff5a5aff","u16":[65535,23011,23011,65535]}}, +{"amount":0.1,"gain":1,"in":"#ff0000ff","out":{"hex":"#ff5a5aff","u16":[65535,23011,23011,65535]}}, +{"amount":0.1,"gain":2,"in":"#ff0000ff","out":{"hex":"#ff5a5aff","u16":[65535,23011,23011,65535]}}, +{"amount":0.25,"gain":0,"in":"#ff0000ff","out":{"hex":"#ff8888ff","u16":[65535,34899,34899,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#ff8888ff","u16":[65535,34899,34899,65535]}}, +{"amount":0.25,"gain":1,"in":"#ff0000ff","out":{"hex":"#ff8888ff","u16":[65535,34899,34899,65535]}}, +{"amount":0.25,"gain":2,"in":"#ff0000ff","out":{"hex":"#ff8888ff","u16":[65535,34899,34899,65535]}}, +{"amount":0.5,"gain":0,"in":"#ff0000ff","out":{"hex":"#ffbabaff","u16":[65535,47824,47824,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#ffbabaff","u16":[65535,47824,47824,65535]}}, +{"amount":0.5,"gain":1,"in":"#ff0000ff","out":{"hex":"#ffbabaff","u16":[65535,47824,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#ff0000ff","out":{"hex":"#ffbabaff","u16":[65535,47824,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#ff0000ff","out":{"hex":"#ffe0e0ff","u16":[65535,57502,57502,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#ffe0e0ff","u16":[65535,57502,57502,65535]}}, +{"amount":0.75,"gain":1,"in":"#ff0000ff","out":{"hex":"#ffe0e0ff","u16":[65535,57502,57502,65535]}}, +{"amount":0.75,"gain":2,"in":"#ff0000ff","out":{"hex":"#ffe0e0ff","u16":[65535,57502,57502,65535]}}, +{"amount":0.9,"gain":0,"in":"#ff0000ff","out":{"hex":"#fff3f3ff","u16":[65535,62470,62470,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#fff3f3ff","u16":[65535,62470,62470,65535]}}, +{"amount":0.9,"gain":1,"in":"#ff0000ff","out":{"hex":"#fff3f3ff","u16":[65535,62470,62470,65535]}}, +{"amount":0.9,"gain":2,"in":"#ff0000ff","out":{"hex":"#fff3f3ff","u16":[65535,62470,62470,65535]}}, +{"amount":1,"gain":0,"in":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#00ff00ff","out":{"hex":"#00e700ff","u16":[0,59244,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#00e700ff","u16":[0,59244,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#00ff00ff","out":{"hex":"#00e700ff","u16":[0,59244,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#00ff00ff","out":{"hex":"#00e700ff","u16":[0,59244,0,65535]}}, +{"amount":0,"gain":0,"in":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"gain":1,"in":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"gain":2,"in":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#00ff00ff","out":{"hex":"#5aff5aff","u16":[23011,65535,23011,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#5aff5aff","u16":[23011,65535,23011,65535]}}, +{"amount":0.1,"gain":1,"in":"#00ff00ff","out":{"hex":"#5aff5aff","u16":[23011,65535,23011,65535]}}, +{"amount":0.1,"gain":2,"in":"#00ff00ff","out":{"hex":"#5aff5aff","u16":[23011,65535,23011,65535]}}, +{"amount":0.25,"gain":0,"in":"#00ff00ff","out":{"hex":"#88ff88ff","u16":[34899,65535,34899,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#88ff88ff","u16":[34899,65535,34899,65535]}}, +{"amount":0.25,"gain":1,"in":"#00ff00ff","out":{"hex":"#88ff88ff","u16":[34899,65535,34899,65535]}}, +{"amount":0.25,"gain":2,"in":"#00ff00ff","out":{"hex":"#88ff88ff","u16":[34899,65535,34899,65535]}}, +{"amount":0.5,"gain":0,"in":"#00ff00ff","out":{"hex":"#baffbaff","u16":[47824,65535,47824,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#baffbaff","u16":[47824,65535,47824,65535]}}, +{"amount":0.5,"gain":1,"in":"#00ff00ff","out":{"hex":"#baffbaff","u16":[47824,65535,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#00ff00ff","out":{"hex":"#baffbaff","u16":[47824,65535,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#00ff00ff","out":{"hex":"#e0ffe0ff","u16":[57502,65535,57502,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#e0ffe0ff","u16":[57502,65535,57502,65535]}}, +{"amount":0.75,"gain":1,"in":"#00ff00ff","out":{"hex":"#e0ffe0ff","u16":[57502,65535,57502,65535]}}, +{"amount":0.75,"gain":2,"in":"#00ff00ff","out":{"hex":"#e0ffe0ff","u16":[57502,65535,57502,65535]}}, +{"amount":0.9,"gain":0,"in":"#00ff00ff","out":{"hex":"#f3fff3ff","u16":[62470,65535,62470,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#f3fff3ff","u16":[62470,65535,62470,65535]}}, +{"amount":0.9,"gain":1,"in":"#00ff00ff","out":{"hex":"#f3fff3ff","u16":[62470,65535,62470,65535]}}, +{"amount":0.9,"gain":2,"in":"#00ff00ff","out":{"hex":"#f3fff3ff","u16":[62470,65535,62470,65535]}}, +{"amount":1,"gain":0,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"gain":1,"in":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"gain":2,"in":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#0000ffff","out":{"hex":"#5a5affff","u16":[23011,23011,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#0000ffff","out":{"hex":"#5a5affff","u16":[23011,23011,65535,65535]}}, +{"amount":0.1,"gain":1,"in":"#0000ffff","out":{"hex":"#5a5affff","u16":[23011,23011,65535,65535]}}, +{"amount":0.1,"gain":2,"in":"#0000ffff","out":{"hex":"#5a5affff","u16":[23011,23011,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#0000ffff","out":{"hex":"#8888ffff","u16":[34899,34899,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#0000ffff","out":{"hex":"#8888ffff","u16":[34899,34899,65535,65535]}}, +{"amount":0.25,"gain":1,"in":"#0000ffff","out":{"hex":"#8888ffff","u16":[34899,34899,65535,65535]}}, +{"amount":0.25,"gain":2,"in":"#0000ffff","out":{"hex":"#8888ffff","u16":[34899,34899,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#0000ffff","out":{"hex":"#babaffff","u16":[47824,47824,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#0000ffff","out":{"hex":"#babaffff","u16":[47824,47824,65535,65535]}}, +{"amount":0.5,"gain":1,"in":"#0000ffff","out":{"hex":"#babaffff","u16":[47824,47824,65535,65535]}}, +{"amount":0.5,"gain":2,"in":"#0000ffff","out":{"hex":"#babaffff","u16":[47824,47824,65535,65535]}}, +{"amount":0.75,"gain":0,"in":"#0000ffff","out":{"hex":"#e0e0ffff","u16":[57502,57502,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#0000ffff","out":{"hex":"#e0e0ffff","u16":[57502,57502,65535,65535]}}, +{"amount":0.75,"gain":1,"in":"#0000ffff","out":{"hex":"#e0e0ffff","u16":[57502,57502,65535,65535]}}, +{"amount":0.75,"gain":2,"in":"#0000ffff","out":{"hex":"#e0e0ffff","u16":[57502,57502,65535,65535]}}, +{"amount":0.9,"gain":0,"in":"#0000ffff","out":{"hex":"#f3f3ffff","u16":[62470,62470,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#0000ffff","out":{"hex":"#f3f3ffff","u16":[62470,62470,65535,65535]}}, +{"amount":0.9,"gain":1,"in":"#0000ffff","out":{"hex":"#f3f3ffff","u16":[62470,62470,65535,65535]}}, +{"amount":0.9,"gain":2,"in":"#0000ffff","out":{"hex":"#f3f3ffff","u16":[62470,62470,65535,65535]}}, +{"amount":1,"gain":0,"in":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ffff00ff","out":{"hex":"#fafa00ff","u16":[64363,64363,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#fafa00ff","u16":[64363,64363,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ffff00ff","out":{"hex":"#fafa00ff","u16":[64363,64363,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ffff00ff","out":{"hex":"#fafa00ff","u16":[64363,64363,0,65535]}}, +{"amount":0,"gain":0,"in":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#ffff00ff","out":{"hex":"#ffff5aff","u16":[65535,65535,23011,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#ffff5aff","u16":[65535,65535,23011,65535]}}, +{"amount":0.1,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffff5aff","u16":[65535,65535,23011,65535]}}, +{"amount":0.1,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffff5aff","u16":[65535,65535,23011,65535]}}, +{"amount":0.25,"gain":0,"in":"#ffff00ff","out":{"hex":"#ffff88ff","u16":[65535,65535,34899,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#ffff88ff","u16":[65535,65535,34899,65535]}}, +{"amount":0.25,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffff88ff","u16":[65535,65535,34899,65535]}}, +{"amount":0.25,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffff88ff","u16":[65535,65535,34899,65535]}}, +{"amount":0.5,"gain":0,"in":"#ffff00ff","out":{"hex":"#ffffbaff","u16":[65535,65535,47824,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#ffffbaff","u16":[65535,65535,47824,65535]}}, +{"amount":0.5,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffffbaff","u16":[65535,65535,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffffbaff","u16":[65535,65535,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#ffff00ff","out":{"hex":"#ffffe0ff","u16":[65535,65535,57502,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#ffffe0ff","u16":[65535,65535,57502,65535]}}, +{"amount":0.75,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffffe0ff","u16":[65535,65535,57502,65535]}}, +{"amount":0.75,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffffe0ff","u16":[65535,65535,57502,65535]}}, +{"amount":0.9,"gain":0,"in":"#ffff00ff","out":{"hex":"#fffff3ff","u16":[65535,65535,62470,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#fffff3ff","u16":[65535,65535,62470,65535]}}, +{"amount":0.9,"gain":1,"in":"#ffff00ff","out":{"hex":"#fffff3ff","u16":[65535,65535,62470,65535]}}, +{"amount":0.9,"gain":2,"in":"#ffff00ff","out":{"hex":"#fffff3ff","u16":[65535,65535,62470,65535]}}, +{"amount":1,"gain":0,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#00ffffff","out":{"hex":"#00efefff","u16":[0,61354,61354,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#00ffffff","out":{"hex":"#00efefff","u16":[0,61354,61354,65535]}}, +{"amount":-0.5,"gain":1,"in":"#00ffffff","out":{"hex":"#00efefff","u16":[0,61354,61354,65535]}}, +{"amount":-0.5,"gain":2,"in":"#00ffffff","out":{"hex":"#00efefff","u16":[0,61354,61354,65535]}}, +{"amount":0,"gain":0,"in":"#00ffffff","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#00ffffff","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"amount":0,"gain":1,"in":"#00ffffff","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"amount":0,"gain":2,"in":"#00ffffff","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#00ffffff","out":{"hex":"#5affffff","u16":[23011,65535,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#00ffffff","out":{"hex":"#5affffff","u16":[23011,65535,65535,65535]}}, +{"amount":0.1,"gain":1,"in":"#00ffffff","out":{"hex":"#5affffff","u16":[23011,65535,65535,65535]}}, +{"amount":0.1,"gain":2,"in":"#00ffffff","out":{"hex":"#5affffff","u16":[23011,65535,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#00ffffff","out":{"hex":"#88ffffff","u16":[34899,65535,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#00ffffff","out":{"hex":"#88ffffff","u16":[34899,65535,65535,65535]}}, +{"amount":0.25,"gain":1,"in":"#00ffffff","out":{"hex":"#88ffffff","u16":[34899,65535,65535,65535]}}, +{"amount":0.25,"gain":2,"in":"#00ffffff","out":{"hex":"#88ffffff","u16":[34899,65535,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#00ffffff","out":{"hex":"#baffffff","u16":[47824,65535,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#00ffffff","out":{"hex":"#baffffff","u16":[47824,65535,65535,65535]}}, +{"amount":0.5,"gain":1,"in":"#00ffffff","out":{"hex":"#baffffff","u16":[47824,65535,65535,65535]}}, +{"amount":0.5,"gain":2,"in":"#00ffffff","out":{"hex":"#baffffff","u16":[47824,65535,65535,65535]}}, +{"amount":0.75,"gain":0,"in":"#00ffffff","out":{"hex":"#e0ffffff","u16":[57502,65535,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#00ffffff","out":{"hex":"#e0ffffff","u16":[57502,65535,65535,65535]}}, +{"amount":0.75,"gain":1,"in":"#00ffffff","out":{"hex":"#e0ffffff","u16":[57502,65535,65535,65535]}}, +{"amount":0.75,"gain":2,"in":"#00ffffff","out":{"hex":"#e0ffffff","u16":[57502,65535,65535,65535]}}, +{"amount":0.9,"gain":0,"in":"#00ffffff","out":{"hex":"#f3ffffff","u16":[62470,65535,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#00ffffff","out":{"hex":"#f3ffffff","u16":[62470,65535,65535,65535]}}, +{"amount":0.9,"gain":1,"in":"#00ffffff","out":{"hex":"#f3ffffff","u16":[62470,65535,65535,65535]}}, +{"amount":0.9,"gain":2,"in":"#00ffffff","out":{"hex":"#f3ffffff","u16":[62470,65535,65535,65535]}}, +{"amount":1,"gain":0,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#ff00ffff","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"amount":0,"gain":1,"in":"#ff00ffff","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"amount":0,"gain":2,"in":"#ff00ffff","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#ff00ffff","out":{"hex":"#ff5affff","u16":[65535,23011,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#ff5affff","u16":[65535,23011,65535,65535]}}, +{"amount":0.1,"gain":1,"in":"#ff00ffff","out":{"hex":"#ff5affff","u16":[65535,23011,65535,65535]}}, +{"amount":0.1,"gain":2,"in":"#ff00ffff","out":{"hex":"#ff5affff","u16":[65535,23011,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#ff00ffff","out":{"hex":"#ff88ffff","u16":[65535,34899,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#ff88ffff","u16":[65535,34899,65535,65535]}}, +{"amount":0.25,"gain":1,"in":"#ff00ffff","out":{"hex":"#ff88ffff","u16":[65535,34899,65535,65535]}}, +{"amount":0.25,"gain":2,"in":"#ff00ffff","out":{"hex":"#ff88ffff","u16":[65535,34899,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#ff00ffff","out":{"hex":"#ffbaffff","u16":[65535,47824,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#ffbaffff","u16":[65535,47824,65535,65535]}}, +{"amount":0.5,"gain":1,"in":"#ff00ffff","out":{"hex":"#ffbaffff","u16":[65535,47824,65535,65535]}}, +{"amount":0.5,"gain":2,"in":"#ff00ffff","out":{"hex":"#ffbaffff","u16":[65535,47824,65535,65535]}}, +{"amount":0.75,"gain":0,"in":"#ff00ffff","out":{"hex":"#ffe0ffff","u16":[65535,57502,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#ffe0ffff","u16":[65535,57502,65535,65535]}}, +{"amount":0.75,"gain":1,"in":"#ff00ffff","out":{"hex":"#ffe0ffff","u16":[65535,57502,65535,65535]}}, +{"amount":0.75,"gain":2,"in":"#ff00ffff","out":{"hex":"#ffe0ffff","u16":[65535,57502,65535,65535]}}, +{"amount":0.9,"gain":0,"in":"#ff00ffff","out":{"hex":"#fff3ffff","u16":[65535,62470,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#fff3ffff","u16":[65535,62470,65535,65535]}}, +{"amount":0.9,"gain":1,"in":"#ff00ffff","out":{"hex":"#fff3ffff","u16":[65535,62470,65535,65535]}}, +{"amount":0.9,"gain":2,"in":"#ff00ffff","out":{"hex":"#fff3ffff","u16":[65535,62470,65535,65535]}}, +{"amount":1,"gain":0,"in":"#ff00ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#ff00ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#ff00ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#ff00ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#ff00ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#ff00ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#800000ff","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#800000ff","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"amount":0,"gain":1,"in":"#800000ff","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"amount":0,"gain":2,"in":"#800000ff","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#800000ff","out":{"hex":"#d40000ff","u16":[54552,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#800000ff","out":{"hex":"#d40000ff","u16":[54552,0,0,65535]}}, +{"amount":0.1,"gain":1,"in":"#800000ff","out":{"hex":"#d40000ff","u16":[54552,0,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#800000ff","out":{"hex":"#d40000ff","u16":[54552,0,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#800000ff","out":{"hex":"#ff5656ff","u16":[65535,22149,22149,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#800000ff","out":{"hex":"#ff5656ff","u16":[65535,22149,22149,65535]}}, +{"amount":0.25,"gain":1,"in":"#800000ff","out":{"hex":"#ff5656ff","u16":[65535,22149,22149,65535]}}, +{"amount":0.25,"gain":2,"in":"#800000ff","out":{"hex":"#ff5656ff","u16":[65535,22149,22149,65535]}}, +{"amount":0.5,"gain":0,"in":"#800000ff","out":{"hex":"#ffa7a7ff","u16":[65535,42946,42946,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#800000ff","out":{"hex":"#ffa7a7ff","u16":[65535,42946,42946,65535]}}, +{"amount":0.5,"gain":1,"in":"#800000ff","out":{"hex":"#ffa7a7ff","u16":[65535,42946,42946,65535]}}, +{"amount":0.5,"gain":2,"in":"#800000ff","out":{"hex":"#ffa7a7ff","u16":[65535,42946,42946,65535]}}, +{"amount":0.75,"gain":0,"in":"#800000ff","out":{"hex":"#ffd8d8ff","u16":[65535,55630,55630,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#800000ff","out":{"hex":"#ffd8d8ff","u16":[65535,55630,55630,65535]}}, +{"amount":0.75,"gain":1,"in":"#800000ff","out":{"hex":"#ffd8d8ff","u16":[65535,55630,55630,65535]}}, +{"amount":0.75,"gain":2,"in":"#800000ff","out":{"hex":"#ffd8d8ff","u16":[65535,55630,55630,65535]}}, +{"amount":0.9,"gain":0,"in":"#800000ff","out":{"hex":"#fff0f0ff","u16":[65535,61801,61801,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#800000ff","out":{"hex":"#fff0f0ff","u16":[65535,61801,61801,65535]}}, +{"amount":0.9,"gain":1,"in":"#800000ff","out":{"hex":"#fff0f0ff","u16":[65535,61801,61801,65535]}}, +{"amount":0.9,"gain":2,"in":"#800000ff","out":{"hex":"#fff0f0ff","u16":[65535,61801,61801,65535]}}, +{"amount":1,"gain":0,"in":"#800000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#800000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#800000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#800000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#800000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#800000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#800000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#800000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#008000ff","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#008000ff","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"amount":0,"gain":1,"in":"#008000ff","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"amount":0,"gain":2,"in":"#008000ff","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#008000ff","out":{"hex":"#009c00ff","u16":[0,39993,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#008000ff","out":{"hex":"#009c00ff","u16":[0,39993,0,65535]}}, +{"amount":0.1,"gain":1,"in":"#008000ff","out":{"hex":"#009c00ff","u16":[0,39993,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#008000ff","out":{"hex":"#009c00ff","u16":[0,39993,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#008000ff","out":{"hex":"#00bc00ff","u16":[0,48436,0,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#008000ff","out":{"hex":"#00bc00ff","u16":[0,48436,0,65535]}}, +{"amount":0.25,"gain":1,"in":"#008000ff","out":{"hex":"#00bc00ff","u16":[0,48436,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#008000ff","out":{"hex":"#00bc00ff","u16":[0,48436,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#008000ff","out":{"hex":"#00e800ff","u16":[0,59511,0,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#008000ff","out":{"hex":"#00e800ff","u16":[0,59511,0,65535]}}, +{"amount":0.5,"gain":1,"in":"#008000ff","out":{"hex":"#00e800ff","u16":[0,59511,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#008000ff","out":{"hex":"#00e800ff","u16":[0,59511,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#008000ff","out":{"hex":"#8aff8aff","u16":[35527,65535,35527,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#008000ff","out":{"hex":"#8aff8aff","u16":[35527,65535,35527,65535]}}, +{"amount":0.75,"gain":1,"in":"#008000ff","out":{"hex":"#8aff8aff","u16":[35527,65535,35527,65535]}}, +{"amount":0.75,"gain":2,"in":"#008000ff","out":{"hex":"#8aff8aff","u16":[35527,65535,35527,65535]}}, +{"amount":0.9,"gain":0,"in":"#008000ff","out":{"hex":"#d9ffd9ff","u16":[55871,65535,55871,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#008000ff","out":{"hex":"#d9ffd9ff","u16":[55871,65535,55871,65535]}}, +{"amount":0.9,"gain":1,"in":"#008000ff","out":{"hex":"#d9ffd9ff","u16":[55871,65535,55871,65535]}}, +{"amount":0.9,"gain":2,"in":"#008000ff","out":{"hex":"#d9ffd9ff","u16":[55871,65535,55871,65535]}}, +{"amount":1,"gain":0,"in":"#008000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#008000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#008000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#008000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#008000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#008000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#008000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#008000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#000080ff","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"amount":0,"gain":0.5,"in":"#000080ff","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"amount":0,"gain":1,"in":"#000080ff","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"amount":0,"gain":2,"in":"#000080ff","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"amount":0.1,"gain":0,"in":"#000080ff","out":{"hex":"#3e3effff","u16":[16061,16061,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#000080ff","out":{"hex":"#3e3effff","u16":[16061,16061,65535,65535]}}, +{"amount":0.1,"gain":1,"in":"#000080ff","out":{"hex":"#3e3effff","u16":[16061,16061,65535,65535]}}, +{"amount":0.1,"gain":2,"in":"#000080ff","out":{"hex":"#3e3effff","u16":[16061,16061,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#000080ff","out":{"hex":"#7c7cffff","u16":[31849,31849,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#000080ff","out":{"hex":"#7c7cffff","u16":[31849,31849,65535,65535]}}, +{"amount":0.25,"gain":1,"in":"#000080ff","out":{"hex":"#7c7cffff","u16":[31849,31849,65535,65535]}}, +{"amount":0.25,"gain":2,"in":"#000080ff","out":{"hex":"#7c7cffff","u16":[31849,31849,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#000080ff","out":{"hex":"#b5b5ffff","u16":[46481,46481,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#000080ff","out":{"hex":"#b5b5ffff","u16":[46481,46481,65535,65535]}}, +{"amount":0.5,"gain":1,"in":"#000080ff","out":{"hex":"#b5b5ffff","u16":[46481,46481,65535,65535]}}, +{"amount":0.5,"gain":2,"in":"#000080ff","out":{"hex":"#b5b5ffff","u16":[46481,46481,65535,65535]}}, +{"amount":0.75,"gain":0,"in":"#000080ff","out":{"hex":"#dedeffff","u16":[56970,56970,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#000080ff","out":{"hex":"#dedeffff","u16":[56970,56970,65535,65535]}}, +{"amount":0.75,"gain":1,"in":"#000080ff","out":{"hex":"#dedeffff","u16":[56970,56970,65535,65535]}}, +{"amount":0.75,"gain":2,"in":"#000080ff","out":{"hex":"#dedeffff","u16":[56970,56970,65535,65535]}}, +{"amount":0.9,"gain":0,"in":"#000080ff","out":{"hex":"#f2f2ffff","u16":[62278,62278,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#000080ff","out":{"hex":"#f2f2ffff","u16":[62278,62278,65535,65535]}}, +{"amount":0.9,"gain":1,"in":"#000080ff","out":{"hex":"#f2f2ffff","u16":[62278,62278,65535,65535]}}, +{"amount":0.9,"gain":2,"in":"#000080ff","out":{"hex":"#f2f2ffff","u16":[62278,62278,65535,65535]}}, +{"amount":1,"gain":0,"in":"#000080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#000080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#000080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#000080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#000080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#000080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#000080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#000080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ff8080ff","out":{"hex":"#a20000ff","u16":[41589,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#a20000ff","u16":[41589,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ff8080ff","out":{"hex":"#a20000ff","u16":[41589,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ff8080ff","out":{"hex":"#a20000ff","u16":[41589,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#ff8080ff","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"amount":0,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"amount":0,"gain":1,"in":"#ff8080ff","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"amount":0,"gain":2,"in":"#ff8080ff","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"amount":0.1,"gain":0,"in":"#ff8080ff","out":{"hex":"#ff9393ff","u16":[65535,37774,37774,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#ff9393ff","u16":[65535,37774,37774,65535]}}, +{"amount":0.1,"gain":1,"in":"#ff8080ff","out":{"hex":"#ff9393ff","u16":[65535,37774,37774,65535]}}, +{"amount":0.1,"gain":2,"in":"#ff8080ff","out":{"hex":"#ff9393ff","u16":[65535,37774,37774,65535]}}, +{"amount":0.25,"gain":0,"in":"#ff8080ff","out":{"hex":"#ffababff","u16":[65535,43923,43923,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#ffababff","u16":[65535,43923,43923,65535]}}, +{"amount":0.25,"gain":1,"in":"#ff8080ff","out":{"hex":"#ffababff","u16":[65535,43923,43923,65535]}}, +{"amount":0.25,"gain":2,"in":"#ff8080ff","out":{"hex":"#ffababff","u16":[65535,43923,43923,65535]}}, +{"amount":0.5,"gain":0,"in":"#ff8080ff","out":{"hex":"#ffccccff","u16":[65535,52338,52338,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#ffccccff","u16":[65535,52338,52338,65535]}}, +{"amount":0.5,"gain":1,"in":"#ff8080ff","out":{"hex":"#ffccccff","u16":[65535,52338,52338,65535]}}, +{"amount":0.5,"gain":2,"in":"#ff8080ff","out":{"hex":"#ffccccff","u16":[65535,52338,52338,65535]}}, +{"amount":0.75,"gain":0,"in":"#ff8080ff","out":{"hex":"#ffe7e7ff","u16":[65535,59378,59378,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#ffe7e7ff","u16":[65535,59378,59378,65535]}}, +{"amount":0.75,"gain":1,"in":"#ff8080ff","out":{"hex":"#ffe7e7ff","u16":[65535,59378,59378,65535]}}, +{"amount":0.75,"gain":2,"in":"#ff8080ff","out":{"hex":"#ffe7e7ff","u16":[65535,59378,59378,65535]}}, +{"amount":0.9,"gain":0,"in":"#ff8080ff","out":{"hex":"#fff6f6ff","u16":[65535,63158,63158,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#fff6f6ff","u16":[65535,63158,63158,65535]}}, +{"amount":0.9,"gain":1,"in":"#ff8080ff","out":{"hex":"#fff6f6ff","u16":[65535,63158,63158,65535]}}, +{"amount":0.9,"gain":2,"in":"#ff8080ff","out":{"hex":"#fff6f6ff","u16":[65535,63158,63158,65535]}}, +{"amount":1,"gain":0,"in":"#ff8080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#ff8080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#ff8080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#ff8080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#ff8080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#ff8080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#80ff80ff","out":{"hex":"#00f700ff","u16":[0,63471,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#00f700ff","u16":[0,63471,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#80ff80ff","out":{"hex":"#00f700ff","u16":[0,63471,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#80ff80ff","out":{"hex":"#00f700ff","u16":[0,63471,0,65535]}}, +{"amount":0,"gain":0,"in":"#80ff80ff","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"amount":0,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"amount":0,"gain":1,"in":"#80ff80ff","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"amount":0,"gain":2,"in":"#80ff80ff","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"amount":0.1,"gain":0,"in":"#80ff80ff","out":{"hex":"#93ff93ff","u16":[37774,65535,37774,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#93ff93ff","u16":[37774,65535,37774,65535]}}, +{"amount":0.1,"gain":1,"in":"#80ff80ff","out":{"hex":"#93ff93ff","u16":[37774,65535,37774,65535]}}, +{"amount":0.1,"gain":2,"in":"#80ff80ff","out":{"hex":"#93ff93ff","u16":[37774,65535,37774,65535]}}, +{"amount":0.25,"gain":0,"in":"#80ff80ff","out":{"hex":"#abffabff","u16":[43923,65535,43923,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#abffabff","u16":[43923,65535,43923,65535]}}, +{"amount":0.25,"gain":1,"in":"#80ff80ff","out":{"hex":"#abffabff","u16":[43923,65535,43923,65535]}}, +{"amount":0.25,"gain":2,"in":"#80ff80ff","out":{"hex":"#abffabff","u16":[43923,65535,43923,65535]}}, +{"amount":0.5,"gain":0,"in":"#80ff80ff","out":{"hex":"#ccffccff","u16":[52338,65535,52338,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#ccffccff","u16":[52338,65535,52338,65535]}}, +{"amount":0.5,"gain":1,"in":"#80ff80ff","out":{"hex":"#ccffccff","u16":[52338,65535,52338,65535]}}, +{"amount":0.5,"gain":2,"in":"#80ff80ff","out":{"hex":"#ccffccff","u16":[52338,65535,52338,65535]}}, +{"amount":0.75,"gain":0,"in":"#80ff80ff","out":{"hex":"#e7ffe7ff","u16":[59378,65535,59378,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#e7ffe7ff","u16":[59378,65535,59378,65535]}}, +{"amount":0.75,"gain":1,"in":"#80ff80ff","out":{"hex":"#e7ffe7ff","u16":[59378,65535,59378,65535]}}, +{"amount":0.75,"gain":2,"in":"#80ff80ff","out":{"hex":"#e7ffe7ff","u16":[59378,65535,59378,65535]}}, +{"amount":0.9,"gain":0,"in":"#80ff80ff","out":{"hex":"#f6fff6ff","u16":[63158,65535,63158,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#f6fff6ff","u16":[63158,65535,63158,65535]}}, +{"amount":0.9,"gain":1,"in":"#80ff80ff","out":{"hex":"#f6fff6ff","u16":[63158,65535,63158,65535]}}, +{"amount":0.9,"gain":2,"in":"#80ff80ff","out":{"hex":"#f6fff6ff","u16":[63158,65535,63158,65535]}}, +{"amount":1,"gain":0,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#8080ffff","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#8080ffff","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"amount":0,"gain":1,"in":"#8080ffff","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"amount":0,"gain":2,"in":"#8080ffff","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#8080ffff","out":{"hex":"#9393ffff","u16":[37774,37774,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#8080ffff","out":{"hex":"#9393ffff","u16":[37774,37774,65535,65535]}}, +{"amount":0.1,"gain":1,"in":"#8080ffff","out":{"hex":"#9393ffff","u16":[37774,37774,65535,65535]}}, +{"amount":0.1,"gain":2,"in":"#8080ffff","out":{"hex":"#9393ffff","u16":[37774,37774,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#8080ffff","out":{"hex":"#ababffff","u16":[43923,43923,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#8080ffff","out":{"hex":"#ababffff","u16":[43923,43923,65535,65535]}}, +{"amount":0.25,"gain":1,"in":"#8080ffff","out":{"hex":"#ababffff","u16":[43923,43923,65535,65535]}}, +{"amount":0.25,"gain":2,"in":"#8080ffff","out":{"hex":"#ababffff","u16":[43923,43923,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#8080ffff","out":{"hex":"#ccccffff","u16":[52338,52338,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#8080ffff","out":{"hex":"#ccccffff","u16":[52338,52338,65535,65535]}}, +{"amount":0.5,"gain":1,"in":"#8080ffff","out":{"hex":"#ccccffff","u16":[52338,52338,65535,65535]}}, +{"amount":0.5,"gain":2,"in":"#8080ffff","out":{"hex":"#ccccffff","u16":[52338,52338,65535,65535]}}, +{"amount":0.75,"gain":0,"in":"#8080ffff","out":{"hex":"#e7e7ffff","u16":[59378,59378,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#8080ffff","out":{"hex":"#e7e7ffff","u16":[59378,59378,65535,65535]}}, +{"amount":0.75,"gain":1,"in":"#8080ffff","out":{"hex":"#e7e7ffff","u16":[59378,59378,65535,65535]}}, +{"amount":0.75,"gain":2,"in":"#8080ffff","out":{"hex":"#e7e7ffff","u16":[59378,59378,65535,65535]}}, +{"amount":0.9,"gain":0,"in":"#8080ffff","out":{"hex":"#f6f6ffff","u16":[63158,63158,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#8080ffff","out":{"hex":"#f6f6ffff","u16":[63158,63158,65535,65535]}}, +{"amount":0.9,"gain":1,"in":"#8080ffff","out":{"hex":"#f6f6ffff","u16":[63158,63158,65535,65535]}}, +{"amount":0.9,"gain":2,"in":"#8080ffff","out":{"hex":"#f6f6ffff","u16":[63158,63158,65535,65535]}}, +{"amount":1,"gain":0,"in":"#8080ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#8080ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#8080ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#8080ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#8080ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#8080ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#8080ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#8080ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#030201ff","out":{"hex":"#030200ff","u16":[806,502,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#030201ff","out":{"hex":"#030201ff","u16":[789,508,188,65535]}}, +{"amount":0,"gain":1,"in":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"gain":2,"in":"#030201ff","out":{"hex":"#030201ff","u16":[734,526,352,65535]}}, +{"amount":0.1,"gain":0,"in":"#030201ff","out":{"hex":"#7f4f00ff","u16":[32614,20291,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#030201ff","out":{"hex":"#7c501eff","u16":[31914,20545,7588,65535]}}, +{"amount":0.1,"gain":1,"in":"#030201ff","out":{"hex":"#795128ff","u16":[31194,20796,10398,65535]}}, +{"amount":0.1,"gain":2,"in":"#030201ff","out":{"hex":"#745337ff","u16":[29692,21287,14249,65535]}}, +{"amount":0.25,"gain":0,"in":"#030201ff","out":{"hex":"#c07800ff","u16":[49460,30772,0,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#030201ff","out":{"hex":"#bc792dff","u16":[48397,31157,11507,65535]}}, +{"amount":0.25,"gain":1,"in":"#030201ff","out":{"hex":"#b87b3dff","u16":[47306,31538,15769,65535]}}, +{"amount":0.25,"gain":2,"in":"#030201ff","out":{"hex":"#af7e54ff","u16":[45029,32282,21609,65535]}}, +{"amount":0.5,"gain":0,"in":"#030201ff","out":{"hex":"#ffa74aff","u16":[65535,42972,19121,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#030201ff","out":{"hex":"#faa95bff","u16":[64222,43420,23454,65535]}}, +{"amount":0.5,"gain":1,"in":"#030201ff","out":{"hex":"#f5ab69ff","u16":[62877,43863,26990,65535]}}, +{"amount":0.5,"gain":2,"in":"#030201ff","out":{"hex":"#eaae7fff","u16":[60076,44734,32760,65535]}}, +{"amount":0.75,"gain":0,"in":"#030201ff","out":{"hex":"#ffd8c0ff","u16":[65535,55639,49245,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#030201ff","out":{"hex":"#fcd9c3ff","u16":[64883,55804,50029,65535]}}, +{"amount":0.75,"gain":1,"in":"#030201ff","out":{"hex":"#fadac6ff","u16":[64222,55969,50799,65535]}}, +{"amount":0.75,"gain":2,"in":"#030201ff","out":{"hex":"#f5dbcbff","u16":[62877,56296,52299,65535]}}, +{"amount":0.9,"gain":0,"in":"#030201ff","out":{"hex":"#fff0e8ff","u16":[65535,61805,59660,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#030201ff","out":{"hex":"#fef1e9ff","u16":[65275,61863,59911,65535]}}, +{"amount":0.9,"gain":1,"in":"#030201ff","out":{"hex":"#fdf1eaff","u16":[65014,61921,60160,65535]}}, +{"amount":0.9,"gain":2,"in":"#030201ff","out":{"hex":"#fbf1ecff","u16":[64487,62037,60656,65535]}}, +{"amount":1,"gain":0,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fffefdff","out":{"hex":"#fffdfcff","u16":[65535,65149,64762,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fffefdff","out":{"hex":"#fffdfcff","u16":[65535,65149,64762,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fffefdff","out":{"hex":"#fffdfcff","u16":[65535,65149,64762,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fffefdff","out":{"hex":"#fffdfcff","u16":[65535,65149,64762,65535]}}, +{"amount":0,"gain":0,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"amount":0,"gain":0.5,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"amount":0,"gain":1,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"amount":0,"gain":2,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"amount":0.1,"gain":0,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65304,65073,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65304,65073,65535]}}, +{"amount":0.1,"gain":1,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65304,65073,65535]}}, +{"amount":0.1,"gain":2,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65304,65073,65535]}}, +{"amount":0.25,"gain":0,"in":"#fffefdff","out":{"hex":"#fffefeff","u16":[65535,65342,65150,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fffefdff","out":{"hex":"#fffefeff","u16":[65535,65342,65150,65535]}}, +{"amount":0.25,"gain":1,"in":"#fffefdff","out":{"hex":"#fffefeff","u16":[65535,65342,65150,65535]}}, +{"amount":0.25,"gain":2,"in":"#fffefdff","out":{"hex":"#fffefeff","u16":[65535,65342,65150,65535]}}, +{"amount":0.5,"gain":0,"in":"#fffefdff","out":{"hex":"#fffffeff","u16":[65535,65407,65279,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fffefdff","out":{"hex":"#fffffeff","u16":[65535,65407,65279,65535]}}, +{"amount":0.5,"gain":1,"in":"#fffefdff","out":{"hex":"#fffffeff","u16":[65535,65407,65279,65535]}}, +{"amount":0.5,"gain":2,"in":"#fffefdff","out":{"hex":"#fffffeff","u16":[65535,65407,65279,65535]}}, +{"amount":0.75,"gain":0,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65471,65407,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65471,65407,65535]}}, +{"amount":0.75,"gain":1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65471,65407,65535]}}, +{"amount":0.75,"gain":2,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65471,65407,65535]}}, +{"amount":0.9,"gain":0,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65509,65484,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65509,65484,65535]}}, +{"amount":0.9,"gain":1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65509,65484,65535]}}, +{"amount":0.9,"gain":2,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65509,65484,65535]}}, +{"amount":1,"gain":0,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#7f8081ff","out":{"hex":"#0089bcff","u16":[0,35187,48321,65535]}}, +{"amount":0,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#5d85a2ff","u16":[23818,34065,41574,65535]}}, +{"amount":0,"gain":1,"in":"#7f8081ff","out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"amount":0,"gain":2,"in":"#7f8081ff","out":{"hex":"#808080ff","u16":[32860,32860,32860,65535]}}, +{"amount":0.1,"gain":0,"in":"#7f8081ff","out":{"hex":"#009dd8ff","u16":[0,40419,55507,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#6a98baff","u16":[27360,39130,47756,65535]}}, +{"amount":0.1,"gain":1,"in":"#7f8081ff","out":{"hex":"#929394ff","u16":[37493,37788,38083,65535]}}, +{"amount":0.1,"gain":2,"in":"#7f8081ff","out":{"hex":"#939393ff","u16":[37747,37747,37747,65535]}}, +{"amount":0.25,"gain":0,"in":"#7f8081ff","out":{"hex":"#00b7fbff","u16":[0,47012,64561,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#7cb1d8ff","u16":[31822,45513,55545,65535]}}, +{"amount":0.25,"gain":1,"in":"#7f8081ff","out":{"hex":"#aaabacff","u16":[43608,43951,44295,65535]}}, +{"amount":0.25,"gain":2,"in":"#7f8081ff","out":{"hex":"#abababff","u16":[43904,43904,43904,65535]}}, +{"amount":0.5,"gain":0,"in":"#7f8081ff","out":{"hex":"#97d3ffff","u16":[38885,54143,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#b3cfe7ff","u16":[46098,53258,59470,65535]}}, +{"amount":0.5,"gain":1,"in":"#7f8081ff","out":{"hex":"#cbccccff","u16":[52159,52355,52552,65535]}}, +{"amount":0.5,"gain":2,"in":"#7f8081ff","out":{"hex":"#ccccccff","u16":[52328,52328,52328,65535]}}, +{"amount":0.75,"gain":0,"in":"#7f8081ff","out":{"hex":"#d3eaffff","u16":[54203,60163,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#dde9f4ff","u16":[56821,59776,62591,65535]}}, +{"amount":0.75,"gain":1,"in":"#7f8081ff","out":{"hex":"#e7e7e7ff","u16":[59301,59385,59470,65535]}}, +{"amount":0.75,"gain":2,"in":"#7f8081ff","out":{"hex":"#e7e7e7ff","u16":[59373,59373,59373,65535]}}, +{"amount":0.9,"gain":0,"in":"#7f8081ff","out":{"hex":"#eff7ffff","u16":[61304,63452,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#f2f6faff","u16":[62225,63307,64377,65535]}}, +{"amount":0.9,"gain":1,"in":"#7f8081ff","out":{"hex":"#f6f6f6ff","u16":[63130,63161,63193,65535]}}, +{"amount":0.9,"gain":2,"in":"#7f8081ff","out":{"hex":"#f6f6f6ff","u16":[63157,63157,63157,65535]}}, +{"amount":1,"gain":0,"in":"#7f8081ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#7f8081ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#7f8081ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#7f8081ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#7f8081ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#7f8081ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#010000ff","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#010000ff","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"amount":0,"gain":1,"in":"#010000ff","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"amount":0,"gain":2,"in":"#010000ff","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#010000ff","out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#010000ff","out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"amount":0.1,"gain":1,"in":"#010000ff","out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#010000ff","out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#010000ff","out":{"hex":"#ff4040ff","u16":[65535,16405,16405,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#010000ff","out":{"hex":"#ff4040ff","u16":[65535,16405,16405,65535]}}, +{"amount":0.25,"gain":1,"in":"#010000ff","out":{"hex":"#ff4040ff","u16":[65535,16405,16405,65535]}}, +{"amount":0.25,"gain":2,"in":"#010000ff","out":{"hex":"#ff4040ff","u16":[65535,16405,16405,65535]}}, +{"amount":0.5,"gain":0,"in":"#010000ff","out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#010000ff","out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"amount":0.5,"gain":1,"in":"#010000ff","out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"amount":0.5,"gain":2,"in":"#010000ff","out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"amount":0.75,"gain":0,"in":"#010000ff","out":{"hex":"#ffd6d6ff","u16":[65535,55089,55089,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#010000ff","out":{"hex":"#ffd6d6ff","u16":[65535,55089,55089,65535]}}, +{"amount":0.75,"gain":1,"in":"#010000ff","out":{"hex":"#ffd6d6ff","u16":[65535,55089,55089,65535]}}, +{"amount":0.75,"gain":2,"in":"#010000ff","out":{"hex":"#ffd6d6ff","u16":[65535,55089,55089,65535]}}, +{"amount":0.9,"gain":0,"in":"#010000ff","out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#010000ff","out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]}}, +{"amount":0.9,"gain":1,"in":"#010000ff","out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]}}, +{"amount":0.9,"gain":2,"in":"#010000ff","out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]}}, +{"amount":1,"gain":0,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"amount":0,"gain":0.5,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"amount":0,"gain":1,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"amount":0,"gain":2,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"amount":0.1,"gain":0,"in":"#000001ff","out":{"hex":"#3434ffff","u16":[13305,13305,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#000001ff","out":{"hex":"#3434ffff","u16":[13305,13305,65535,65535]}}, +{"amount":0.1,"gain":1,"in":"#000001ff","out":{"hex":"#3434ffff","u16":[13305,13305,65535,65535]}}, +{"amount":0.1,"gain":2,"in":"#000001ff","out":{"hex":"#3434ffff","u16":[13305,13305,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#000001ff","out":{"hex":"#7878ffff","u16":[30926,30926,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#000001ff","out":{"hex":"#7878ffff","u16":[30926,30926,65535,65535]}}, +{"amount":0.25,"gain":1,"in":"#000001ff","out":{"hex":"#7878ffff","u16":[30926,30926,65535,65535]}}, +{"amount":0.25,"gain":2,"in":"#000001ff","out":{"hex":"#7878ffff","u16":[30926,30926,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#000001ff","out":{"hex":"#b3b3ffff","u16":[46094,46094,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#000001ff","out":{"hex":"#b3b3ffff","u16":[46094,46094,65535,65535]}}, +{"amount":0.5,"gain":1,"in":"#000001ff","out":{"hex":"#b3b3ffff","u16":[46094,46094,65535,65535]}}, +{"amount":0.5,"gain":2,"in":"#000001ff","out":{"hex":"#b3b3ffff","u16":[46094,46094,65535,65535]}}, +{"amount":0.75,"gain":0,"in":"#000001ff","out":{"hex":"#ddddffff","u16":[56819,56819,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#000001ff","out":{"hex":"#ddddffff","u16":[56819,56819,65535,65535]}}, +{"amount":0.75,"gain":1,"in":"#000001ff","out":{"hex":"#ddddffff","u16":[56819,56819,65535,65535]}}, +{"amount":0.75,"gain":2,"in":"#000001ff","out":{"hex":"#ddddffff","u16":[56819,56819,65535,65535]}}, +{"amount":0.9,"gain":0,"in":"#000001ff","out":{"hex":"#f2f2ffff","u16":[62224,62224,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#000001ff","out":{"hex":"#f2f2ffff","u16":[62224,62224,65535,65535]}}, +{"amount":0.9,"gain":1,"in":"#000001ff","out":{"hex":"#f2f2ffff","u16":[62224,62224,65535,65535]}}, +{"amount":0.9,"gain":2,"in":"#000001ff","out":{"hex":"#f2f2ffff","u16":[62224,62224,65535,65535]}}, +{"amount":1,"gain":0,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fefffeff","out":{"hex":"#fdfffdff","u16":[65149,65535,65149,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fefffeff","out":{"hex":"#fdfffdff","u16":[65149,65535,65149,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fefffeff","out":{"hex":"#fdfffdff","u16":[65149,65535,65149,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fefffeff","out":{"hex":"#fdfffdff","u16":[65149,65535,65149,65535]}}, +{"amount":0,"gain":0,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"amount":0,"gain":0.5,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"amount":0,"gain":1,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"amount":0,"gain":2,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"amount":0.1,"gain":0,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65304,65535,65304,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65304,65535,65304,65535]}}, +{"amount":0.1,"gain":1,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65304,65535,65304,65535]}}, +{"amount":0.1,"gain":2,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65304,65535,65304,65535]}}, +{"amount":0.25,"gain":0,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65342,65535,65342,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65342,65535,65342,65535]}}, +{"amount":0.25,"gain":1,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65342,65535,65342,65535]}}, +{"amount":0.25,"gain":2,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65342,65535,65342,65535]}}, +{"amount":0.5,"gain":0,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65407,65535,65407,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65407,65535,65407,65535]}}, +{"amount":0.5,"gain":1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65407,65535,65407,65535]}}, +{"amount":0.5,"gain":2,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65407,65535,65407,65535]}}, +{"amount":0.75,"gain":0,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65471,65535,65471,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65471,65535,65471,65535]}}, +{"amount":0.75,"gain":1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65471,65535,65471,65535]}}, +{"amount":0.75,"gain":2,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65471,65535,65471,65535]}}, +{"amount":0.9,"gain":0,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65509,65535,65509,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65509,65535,65509,65535]}}, +{"amount":0.9,"gain":1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65509,65535,65509,65535]}}, +{"amount":0.9,"gain":2,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65509,65535,65509,65535]}}, +{"amount":1,"gain":0,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#3daee9ff","out":{"hex":"#004f6cff","u16":[0,20301,27823,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#144f6bff","u16":[5155,20227,27408,65535]}}, +{"amount":-0.5,"gain":1,"in":"#3daee9ff","out":{"hex":"#1b4e69ff","u16":[7065,20152,26985,65535]}}, +{"amount":-0.5,"gain":2,"in":"#3daee9ff","out":{"hex":"#264e66ff","u16":[9681,20002,26115,65535]}}, +{"amount":0,"gain":0,"in":"#3daee9ff","out":{"hex":"#00aff0ff","u16":[0,45047,61740,65535]}}, +{"amount":0,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#2dafedff","u16":[11440,44883,60819,65535]}}, +{"amount":0,"gain":1,"in":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"gain":2,"in":"#3daee9ff","out":{"hex":"#54ade1ff","u16":[21483,44386,57950,65535]}}, +{"amount":0.1,"gain":0,"in":"#3daee9ff","out":{"hex":"#28bbffff","u16":[10177,48176,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#3cbbfbff","u16":[15295,48006,64574,65535]}}, +{"amount":0.1,"gain":1,"in":"#3daee9ff","out":{"hex":"#4abaf7ff","u16":[18894,47835,63595,65535]}}, +{"amount":0.1,"gain":2,"in":"#3daee9ff","out":{"hex":"#5fb9f0ff","u16":[24327,47492,61581,65535]}}, +{"amount":0.25,"gain":0,"in":"#3daee9ff","out":{"hex":"#75c9ffff","u16":[30097,51565,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#7bc8fcff","u16":[31575,51434,64735,65535]}}, +{"amount":0.25,"gain":1,"in":"#3daee9ff","out":{"hex":"#80c8f9ff","u16":[32975,51304,63923,65535]}}, +{"amount":0.25,"gain":2,"in":"#3daee9ff","out":{"hex":"#8ac7f2ff","u16":[35579,51041,62261,65535]}}, +{"amount":0.5,"gain":0,"in":"#3daee9ff","out":{"hex":"#b2ddffff","u16":[45756,56685,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#b4dcfdff","u16":[46365,56607,65003,65535]}}, +{"amount":0.5,"gain":1,"in":"#3daee9ff","out":{"hex":"#b7dcfbff","u16":[46964,56529,64466,65535]}}, +{"amount":0.5,"gain":2,"in":"#3daee9ff","out":{"hex":"#bbdbf7ff","u16":[48137,56373,63375,65535]}}, +{"amount":0.75,"gain":0,"in":"#3daee9ff","out":{"hex":"#ddefffff","u16":[56688,61302,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#ddeefeff","u16":[56925,61266,65270,65535]}}, +{"amount":0.75,"gain":1,"in":"#3daee9ff","out":{"hex":"#deeefdff","u16":[57160,61231,65003,65535]}}, +{"amount":0.75,"gain":2,"in":"#3daee9ff","out":{"hex":"#e0eefbff","u16":[57628,61160,64466,65535]}}, +{"amount":0.9,"gain":0,"in":"#3daee9ff","out":{"hex":"#f2f9ffff","u16":[62177,63882,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#f2f9ffff","u16":[62262,63869,65429,65535]}}, +{"amount":0.9,"gain":1,"in":"#3daee9ff","out":{"hex":"#f3f8feff","u16":[62347,63855,65323,65535]}}, +{"amount":0.9,"gain":2,"in":"#3daee9ff","out":{"hex":"#f3f8fdff","u16":[62516,63828,65110,65535]}}, +{"amount":1,"gain":0,"in":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#232629ff","out":{"hex":"#002838ff","u16":[0,10317,14453,65535]}}, +{"amount":0,"gain":0.5,"in":"#232629ff","out":{"hex":"#1a2731ff","u16":[6564,10046,12677,65535]}}, +{"amount":0,"gain":1,"in":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"gain":2,"in":"#232629ff","out":{"hex":"#262626ff","u16":[9668,9668,9668,65535]}}, +{"amount":0.1,"gain":0,"in":"#232629ff","out":{"hex":"#00658eff","u16":[0,25996,36417,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#232629ff","out":{"hex":"#40627cff","u16":[16540,25313,31944,65535]}}, +{"amount":0.1,"gain":1,"in":"#232629ff","out":{"hex":"#586067ff","u16":[22665,24608,26551,65535]}}, +{"amount":0.1,"gain":2,"in":"#232629ff","out":{"hex":"#5f5f5fff","u16":[24360,24360,24360,65535]}}, +{"amount":0.25,"gain":0,"in":"#232629ff","out":{"hex":"#0094cfff","u16":[0,37987,53216,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#232629ff","out":{"hex":"#5e90b6ff","u16":[24169,36990,46679,65535]}}, +{"amount":0.25,"gain":1,"in":"#232629ff","out":{"hex":"#818c97ff","u16":[33120,35959,38798,65535]}}, +{"amount":0.25,"gain":2,"in":"#232629ff","out":{"hex":"#8b8b8bff","u16":[35597,35597,35597,65535]}}, +{"amount":0.5,"gain":0,"in":"#232629ff","out":{"hex":"#6fc4ffff","u16":[28575,50378,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#232629ff","out":{"hex":"#95c0e5ff","u16":[38417,49440,58871,65535]}}, +{"amount":0.5,"gain":1,"in":"#232629ff","out":{"hex":"#b3bdc7ff","u16":[45890,48480,51148,65535]}}, +{"amount":0.5,"gain":2,"in":"#232629ff","out":{"hex":"#bbbbbbff","u16":[48145,48145,48145,65535]}}, +{"amount":0.75,"gain":0,"in":"#232629ff","out":{"hex":"#c7e4ffff","u16":[51182,58548,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#232629ff","out":{"hex":"#d2e2f2ff","u16":[54047,58159,62310,65535]}}, +{"amount":0.75,"gain":1,"in":"#232629ff","out":{"hex":"#dde1e5ff","u16":[56740,57767,58871,65535]}}, +{"amount":0.75,"gain":2,"in":"#232629ff","out":{"hex":"#e0e0e0ff","u16":[57631,57631,57631,65535]}}, +{"amount":0.9,"gain":0,"in":"#232629ff","out":{"hex":"#ebf5ffff","u16":[60286,62852,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#232629ff","out":{"hex":"#eef4faff","u16":[61250,62709,64268,65535]}}, +{"amount":0.9,"gain":1,"in":"#232629ff","out":{"hex":"#f2f3f5ff","u16":[62196,62566,62971,65535]}}, +{"amount":0.9,"gain":2,"in":"#232629ff","out":{"hex":"#f3f3f3ff","u16":[62517,62517,62517,65535]}}, +{"amount":1,"gain":0,"in":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#eff0f1ff","out":{"hex":"#d4ebffff","u16":[54595,60349,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#dee9f5ff","u16":[56973,59996,62860,65535]}}, +{"amount":-0.5,"gain":1,"in":"#eff0f1ff","out":{"hex":"#e6e8eaff","u16":[59238,59640,60040,65535]}}, +{"amount":-0.5,"gain":2,"in":"#eff0f1ff","out":{"hex":"#e8e8e8ff","u16":[59583,59583,59583,65535]}}, +{"amount":0,"gain":0,"in":"#eff0f1ff","out":{"hex":"#e4f2ffff","u16":[58516,62136,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#e9f1f8ff","u16":[59991,61908,63766,65535]}}, +{"amount":0,"gain":1,"in":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"gain":2,"in":"#eff0f1ff","out":{"hex":"#f0f0f0ff","u16":[61644,61644,61644,65535]}}, +{"amount":0.1,"gain":0,"in":"#eff0f1ff","out":{"hex":"#e7f3ffff","u16":[59263,62486,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#ecf2f9ff","u16":[60573,62282,63946,65535]}}, +{"amount":0.1,"gain":1,"in":"#eff0f1ff","out":{"hex":"#f1f2f2ff","u16":[61849,62078,62308,65535]}}, +{"amount":0.1,"gain":2,"in":"#eff0f1ff","out":{"hex":"#f1f1f1ff","u16":[62046,62046,62046,65535]}}, +{"amount":0.25,"gain":0,"in":"#eff0f1ff","out":{"hex":"#ebf5ffff","u16":[60363,63006,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#eff5faff","u16":[61433,62839,64214,65535]}}, +{"amount":0.25,"gain":1,"in":"#eff0f1ff","out":{"hex":"#f3f4f5ff","u16":[62482,62670,62860,65535]}}, +{"amount":0.25,"gain":2,"in":"#eff0f1ff","out":{"hex":"#f4f4f4ff","u16":[62644,62644,62644,65535]}}, +{"amount":0.5,"gain":0,"in":"#eff0f1ff","out":{"hex":"#f2f8ffff","u16":[62144,63862,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#f4f8fcff","u16":[62836,63753,64658,65535]}}, +{"amount":0.5,"gain":1,"in":"#eff0f1ff","out":{"hex":"#f7f8f8ff","u16":[63519,63643,63766,65535]}}, +{"amount":0.5,"gain":2,"in":"#eff0f1ff","out":{"hex":"#f8f8f8ff","u16":[63625,63625,63625,65535]}}, +{"amount":0.75,"gain":0,"in":"#eff0f1ff","out":{"hex":"#f9fcffff","u16":[63867,64705,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#fafcfdff","u16":[64203,64651,65098,65535]}}, +{"amount":0.75,"gain":1,"in":"#eff0f1ff","out":{"hex":"#fbfbfcff","u16":[64536,64597,64658,65535]}}, +{"amount":0.75,"gain":2,"in":"#eff0f1ff","out":{"hex":"#fbfbfbff","u16":[64589,64589,64589,65535]}}, +{"amount":0.9,"gain":0,"in":"#eff0f1ff","out":{"hex":"#fcfeffff","u16":[64874,65205,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#fdfefeff","u16":[65006,65183,65361,65535]}}, +{"amount":0.9,"gain":1,"in":"#eff0f1ff","out":{"hex":"#fdfefeff","u16":[65138,65162,65186,65535]}}, +{"amount":0.9,"gain":2,"in":"#eff0f1ff","out":{"hex":"#fefefeff","u16":[65158,65158,65158,65535]}}, +{"amount":1,"gain":0,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fcfcfcff","out":{"hex":"#fff9f9ff","u16":[65535,64057,64057,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#fdfafaff","u16":[64958,64216,64216,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fcfcfcff","out":{"hex":"#fafafaff","u16":[64374,64374,64374,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fcfcfcff","out":{"hex":"#fafafaff","u16":[64374,64374,64374,65535]}}, +{"amount":0,"gain":0,"in":"#fcfcfcff","out":{"hex":"#fffbfbff","u16":[65535,64554,64554,65535]}}, +{"amount":0,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#fefcfcff","u16":[65151,64659,64659,65535]}}, +{"amount":0,"gain":1,"in":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"gain":2,"in":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"gain":0,"in":"#fcfcfcff","out":{"hex":"#fffcfcff","u16":[65535,64653,64653,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#fefcfcff","u16":[65189,64747,64747,65535]}}, +{"amount":0.1,"gain":1,"in":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64842,64842,64842,65535]}}, +{"amount":0.1,"gain":2,"in":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64842,64842,64842,65535]}}, +{"amount":0.25,"gain":0,"in":"#fcfcfcff","out":{"hex":"#fffcfcff","u16":[65535,64801,64801,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#fefcfcff","u16":[65247,64879,64879,65535]}}, +{"amount":0.25,"gain":1,"in":"#fcfcfcff","out":{"hex":"#fdfdfdff","u16":[64958,64958,64958,65535]}}, +{"amount":0.25,"gain":2,"in":"#fcfcfcff","out":{"hex":"#fdfdfdff","u16":[64958,64958,64958,65535]}}, +{"amount":0.5,"gain":0,"in":"#fcfcfcff","out":{"hex":"#fffdfdff","u16":[65535,65047,65047,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#fefdfdff","u16":[65343,65099,65099,65535]}}, +{"amount":0.5,"gain":1,"in":"#fcfcfcff","out":{"hex":"#fefefeff","u16":[65151,65151,65151,65535]}}, +{"amount":0.5,"gain":2,"in":"#fcfcfcff","out":{"hex":"#fefefeff","u16":[65151,65151,65151,65535]}}, +{"amount":0.75,"gain":0,"in":"#fcfcfcff","out":{"hex":"#fffefeff","u16":[65535,65291,65291,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#fffefeff","u16":[65439,65317,65317,65535]}}, +{"amount":0.75,"gain":1,"in":"#fcfcfcff","out":{"hex":"#fefefeff","u16":[65343,65343,65343,65535]}}, +{"amount":0.75,"gain":2,"in":"#fcfcfcff","out":{"hex":"#fefefeff","u16":[65343,65343,65343,65535]}}, +{"amount":0.9,"gain":0,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65438,65438,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65497,65448,65448,65535]}}, +{"amount":0.9,"gain":1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65458,65458,65458,65535]}}, +{"amount":0.9,"gain":2,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65458,65458,65458,65535]}}, +{"amount":1,"gain":0,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#31363bff","out":{"hex":"#003950ff","u16":[0,14628,20572,65535]}}, +{"amount":0,"gain":0.5,"in":"#31363bff","out":{"hex":"#243746ff","u16":[9190,14259,18111,65535]}}, +{"amount":0,"gain":1,"in":"#31363bff","out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"amount":0,"gain":2,"in":"#31363bff","out":{"hex":"#353535ff","u16":[13717,13717,13717,65535]}}, +{"amount":0.1,"gain":0,"in":"#31363bff","out":{"hex":"#006b97ff","u16":[0,27534,38724,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#31363bff","out":{"hex":"#436885ff","u16":[17298,26840,34091,65535]}}, +{"amount":0.1,"gain":1,"in":"#31363bff","out":{"hex":"#5c666fff","u16":[23704,26123,28542,65535]}}, +{"amount":0.1,"gain":2,"in":"#31363bff","out":{"hex":"#646464ff","u16":[25819,25819,25819,65535]}}, +{"amount":0.25,"gain":0,"in":"#31363bff","out":{"hex":"#0097d4ff","u16":[0,38802,54571,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#31363bff","out":{"hex":"#5f93bbff","u16":[24377,37823,48043,65535]}}, +{"amount":0.25,"gain":1,"in":"#31363bff","out":{"hex":"#828f9dff","u16":[33405,36813,40222,65535]}}, +{"amount":0.25,"gain":2,"in":"#31363bff","out":{"hex":"#8e8e8eff","u16":[36385,36385,36385,65535]}}, +{"amount":0.5,"gain":0,"in":"#31363bff","out":{"hex":"#75c5ffff","u16":[30041,50641,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#31363bff","out":{"hex":"#98c2e6ff","u16":[38992,49773,59197,65535]}}, +{"amount":0.5,"gain":1,"in":"#31363bff","out":{"hex":"#b3becaff","u16":[45971,48887,51916,65535]}}, +{"amount":0.5,"gain":2,"in":"#31363bff","out":{"hex":"#bdbdbdff","u16":[48514,48514,48514,65535]}}, +{"amount":0.75,"gain":0,"in":"#31363bff","out":{"hex":"#c9e4ffff","u16":[51556,58658,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#31363bff","out":{"hex":"#d3e3f3ff","u16":[54239,58296,62462,65535]}}, +{"amount":0.75,"gain":1,"in":"#31363bff","out":{"hex":"#dde1e6ff","u16":[56771,57932,59197,65535]}}, +{"amount":0.75,"gain":2,"in":"#31363bff","out":{"hex":"#e1e1e1ff","u16":[57780,57780,57780,65535]}}, +{"amount":0.9,"gain":0,"in":"#31363bff","out":{"hex":"#ebf5ffff","u16":[60409,62892,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#31363bff","out":{"hex":"#eff4faff","u16":[61316,62760,64327,65535]}}, +{"amount":0.9,"gain":1,"in":"#31363bff","out":{"hex":"#f2f4f5ff","u16":[62207,62627,63091,65535]}}, +{"amount":0.9,"gain":2,"in":"#31363bff","out":{"hex":"#f3f3f3ff","u16":[62571,62571,62571,65535]}}, +{"amount":1,"gain":0,"in":"#31363bff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#31363bff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#31363bff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#31363bff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#31363bff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#31363bff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#31363bff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#31363bff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#1b1e20ff","out":{"hex":"#002029ff","u16":[0,8217,10562,65535]}}, +{"amount":0,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#141f25ff","u16":[5064,7968,9480,65535]}}, +{"amount":0,"gain":1,"in":"#1b1e20ff","out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"amount":0,"gain":2,"in":"#1b1e20ff","out":{"hex":"#1e1e1eff","u16":[7593,7593,7593,65535]}}, +{"amount":0.1,"gain":0,"in":"#1b1e20ff","out":{"hex":"#006481ff","u16":[0,25770,33125,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#3e6174ff","u16":[15881,24991,29731,65535]}}, +{"amount":0.1,"gain":1,"in":"#1b1e20ff","out":{"hex":"#555e64ff","u16":[21763,24181,25793,65535]}}, +{"amount":0.1,"gain":2,"in":"#1b1e20ff","out":{"hex":"#5d5d5dff","u16":[23815,23815,23815,65535]}}, +{"amount":0.25,"gain":0,"in":"#1b1e20ff","out":{"hex":"#0095bfff","u16":[0,38210,49116,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#5c90acff","u16":[23547,37054,44083,65535]}}, +{"amount":0.25,"gain":1,"in":"#1b1e20ff","out":{"hex":"#7e8c95ff","u16":[32268,35853,38243,65535]}}, +{"amount":0.25,"gain":2,"in":"#1b1e20ff","out":{"hex":"#898989ff","u16":[35311,35311,35311,65535]}}, +{"amount":0.5,"gain":0,"in":"#1b1e20ff","out":{"hex":"#3bc9ffff","u16":[15127,51657,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#83c3e6ff","u16":[33619,50201,59104,65535]}}, +{"amount":0.5,"gain":1,"in":"#1b1e20ff","out":{"hex":"#acbdc9ff","u16":[44218,48692,51699,65535]}}, +{"amount":0.5,"gain":2,"in":"#1b1e20ff","out":{"hex":"#bbbbbbff","u16":[48013,48013,48013,65535]}}, +{"amount":0.75,"gain":0,"in":"#1b1e20ff","out":{"hex":"#bde6ffff","u16":[48678,59087,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#cce4f3ff","u16":[52547,58474,62419,65535]}}, +{"amount":0.75,"gain":1,"in":"#1b1e20ff","out":{"hex":"#dae1e6ff","u16":[56102,57853,59104,65535]}}, +{"amount":0.75,"gain":2,"in":"#1b1e20ff","out":{"hex":"#e0e0e0ff","u16":[57578,57578,57578,65535]}}, +{"amount":0.9,"gain":0,"in":"#1b1e20ff","out":{"hex":"#e7f5ffff","u16":[59481,63051,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#ecf4faff","u16":[60740,62825,64310,65535]}}, +{"amount":0.9,"gain":1,"in":"#1b1e20ff","out":{"hex":"#f1f4f5ff","u16":[61968,62598,63057,65535]}}, +{"amount":0.9,"gain":2,"in":"#1b1e20ff","out":{"hex":"#f3f3f3ff","u16":[62498,62498,62498,65535]}}, +{"amount":1,"gain":0,"in":"#1b1e20ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#1b1e20ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#1b1e20ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#1b1e20ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#1b1e20ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#1b1e20ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#da4453ff","out":{"hex":"#f4003cff","u16":[62676,0,15457,65535]}}, +{"amount":0,"gain":0.5,"in":"#da4453ff","out":{"hex":"#e73249ff","u16":[59463,12753,18673,65535]}}, +{"amount":0,"gain":1,"in":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"gain":2,"in":"#da4453ff","out":{"hex":"#bc5d64ff","u16":[48258,23948,25707,65535]}}, +{"amount":0.1,"gain":0,"in":"#da4453ff","out":{"hex":"#ff4f61ff","u16":[65535,20413,24930,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#da4453ff","out":{"hex":"#f35b69ff","u16":[62441,23489,26913,65535]}}, +{"amount":0.1,"gain":1,"in":"#da4453ff","out":{"hex":"#e66670ff","u16":[59151,26144,28735,65535]}}, +{"amount":0.1,"gain":2,"in":"#da4453ff","out":{"hex":"#ca777dff","u16":[51807,30660,32015,65535]}}, +{"amount":0.25,"gain":0,"in":"#da4453ff","out":{"hex":"#ff838cff","u16":[65535,33647,35901,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#da4453ff","out":{"hex":"#f58990ff","u16":[62969,35143,36999,65535]}}, +{"amount":0.25,"gain":1,"in":"#da4453ff","out":{"hex":"#eb8e94ff","u16":[60271,36566,38059,65535]}}, +{"amount":0.25,"gain":2,"in":"#da4453ff","out":{"hex":"#d4999cff","u16":[54388,39228,40079,65535]}}, +{"amount":0.5,"gain":0,"in":"#da4453ff","out":{"hex":"#ffb8bcff","u16":[65535,47260,48286,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#da4453ff","out":{"hex":"#f8bbbeff","u16":[63838,47935,48805,65535]}}, +{"amount":0.5,"gain":1,"in":"#da4453ff","out":{"hex":"#f2bdc0ff","u16":[62085,48599,49318,65535]}}, +{"amount":0.5,"gain":2,"in":"#da4453ff","out":{"hex":"#e3c2c4ff","u16":[58389,49896,50324,65535]}}, +{"amount":0.75,"gain":0,"in":"#da4453ff","out":{"hex":"#ffdfe0ff","u16":[65535,57277,57688,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#da4453ff","out":{"hex":"#fce0e1ff","u16":[64693,57547,57899,65535]}}, +{"amount":0.75,"gain":1,"in":"#da4453ff","out":{"hex":"#f8e1e2ff","u16":[63838,57815,58108,65535]}}, +{"amount":0.75,"gain":2,"in":"#da4453ff","out":{"hex":"#f2e3e4ff","u16":[62085,58347,58525,65535]}}, +{"amount":0.9,"gain":0,"in":"#da4453ff","out":{"hex":"#fff3f3ff","u16":[65535,62389,62538,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#da4453ff","out":{"hex":"#fef3f4ff","u16":[65200,62487,62614,65535]}}, +{"amount":0.9,"gain":1,"in":"#da4453ff","out":{"hex":"#fcf4f4ff","u16":[64863,62584,62691,65535]}}, +{"amount":0.9,"gain":2,"in":"#da4453ff","out":{"hex":"#faf4f5ff","u16":[64182,62778,62843,65535]}}, +{"amount":1,"gain":0,"in":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#27ae60ff","out":{"hex":"#00af5cff","u16":[0,44993,23610,65535]}}, +{"amount":0,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#1caf5eff","u16":[7314,44856,24148,65535]}}, +{"amount":0,"gain":1,"in":"#27ae60ff","out":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]}}, +{"amount":0,"gain":2,"in":"#27ae60ff","out":{"hex":"#35ad64ff","u16":[13735,44441,25682,65535]}}, +{"amount":0.1,"gain":0,"in":"#27ae60ff","out":{"hex":"#00bf64ff","u16":[0,49106,25768,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#1fbe67ff","u16":[7983,48956,26355,65535]}}, +{"amount":0.1,"gain":1,"in":"#27ae60ff","out":{"hex":"#2bbe69ff","u16":[10939,48805,26927,65535]}}, +{"amount":0.1,"gain":2,"in":"#27ae60ff","out":{"hex":"#3abd6dff","u16":[14990,48503,28030,65535]}}, +{"amount":0.25,"gain":0,"in":"#27ae60ff","out":{"hex":"#00d46fff","u16":[0,54595,28648,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#23d472ff","u16":[8875,54429,29301,65535]}}, +{"amount":0.25,"gain":1,"in":"#27ae60ff","out":{"hex":"#2fd374ff","u16":[12162,54261,29937,65535]}}, +{"amount":0.25,"gain":2,"in":"#27ae60ff","out":{"hex":"#41d279ff","u16":[16666,53925,31163,65535]}}, +{"amount":0.5,"gain":0,"in":"#27ae60ff","out":{"hex":"#00f380ff","u16":[0,62503,32797,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#28f283ff","u16":[10161,62312,33545,65535]}}, +{"amount":0.5,"gain":1,"in":"#27ae60ff","out":{"hex":"#36f285ff","u16":[13924,62121,34273,65535]}}, +{"amount":0.5,"gain":2,"in":"#27ae60ff","out":{"hex":"#4af08bff","u16":[19080,61735,35677,65535]}}, +{"amount":0.75,"gain":0,"in":"#27ae60ff","out":{"hex":"#a1ffbdff","u16":[41417,65535,48608,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#a3ffbeff","u16":[42015,65408,48940,65535]}}, +{"amount":0.75,"gain":1,"in":"#27ae60ff","out":{"hex":"#a6fec0ff","u16":[42603,65281,49269,65535]}}, +{"amount":0.75,"gain":2,"in":"#27ae60ff","out":{"hex":"#aafdc2ff","u16":[43752,65025,49920,65535]}}, +{"amount":0.9,"gain":0,"in":"#27ae60ff","out":{"hex":"#dfffe7ff","u16":[57354,65535,59458,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#e0ffe8ff","u16":[57517,65484,59563,65535]}}, +{"amount":0.9,"gain":1,"in":"#27ae60ff","out":{"hex":"#e0ffe8ff","u16":[57679,65433,59667,65535]}}, +{"amount":0.9,"gain":2,"in":"#27ae60ff","out":{"hex":"#e2fee9ff","u16":[58003,65332,59876,65535]}}, +{"amount":1,"gain":0,"in":"#27ae60ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#27ae60ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#27ae60ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#27ae60ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#27ae60ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#27ae60ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#f67400ff","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#f67400ff","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"amount":0,"gain":1,"in":"#f67400ff","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"amount":0,"gain":2,"in":"#f67400ff","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#f67400ff","out":{"hex":"#ff8649ff","u16":[65535,34417,18686,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#f67400ff","out":{"hex":"#ff8649ff","u16":[65535,34417,18686,65535]}}, +{"amount":0.1,"gain":1,"in":"#f67400ff","out":{"hex":"#ff8649ff","u16":[65535,34417,18686,65535]}}, +{"amount":0.1,"gain":2,"in":"#f67400ff","out":{"hex":"#ff8649ff","u16":[65535,34417,18686,65535]}}, +{"amount":0.25,"gain":0,"in":"#f67400ff","out":{"hex":"#ffa280ff","u16":[65535,41641,32887,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#f67400ff","out":{"hex":"#ffa280ff","u16":[65535,41641,32887,65535]}}, +{"amount":0.25,"gain":1,"in":"#f67400ff","out":{"hex":"#ffa280ff","u16":[65535,41641,32887,65535]}}, +{"amount":0.25,"gain":2,"in":"#f67400ff","out":{"hex":"#ffa280ff","u16":[65535,41641,32887,65535]}}, +{"amount":0.5,"gain":0,"in":"#f67400ff","out":{"hex":"#ffc7b7ff","u16":[65535,51127,46926,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#f67400ff","out":{"hex":"#ffc7b7ff","u16":[65535,51127,46926,65535]}}, +{"amount":0.5,"gain":1,"in":"#f67400ff","out":{"hex":"#ffc7b7ff","u16":[65535,51127,46926,65535]}}, +{"amount":0.5,"gain":2,"in":"#f67400ff","out":{"hex":"#ffc7b7ff","u16":[65535,51127,46926,65535]}}, +{"amount":0.75,"gain":0,"in":"#f67400ff","out":{"hex":"#ffe5deff","u16":[65535,58862,57145,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#f67400ff","out":{"hex":"#ffe5deff","u16":[65535,58862,57145,65535]}}, +{"amount":0.75,"gain":1,"in":"#f67400ff","out":{"hex":"#ffe5deff","u16":[65535,58862,57145,65535]}}, +{"amount":0.75,"gain":2,"in":"#f67400ff","out":{"hex":"#ffe5deff","u16":[65535,58862,57145,65535]}}, +{"amount":0.9,"gain":0,"in":"#f67400ff","out":{"hex":"#fff5f3ff","u16":[65535,62968,62341,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#f67400ff","out":{"hex":"#fff5f3ff","u16":[65535,62968,62341,65535]}}, +{"amount":0.9,"gain":1,"in":"#f67400ff","out":{"hex":"#fff5f3ff","u16":[65535,62968,62341,65535]}}, +{"amount":0.9,"gain":2,"in":"#f67400ff","out":{"hex":"#fff5f3ff","u16":[65535,62968,62341,65535]}}, +{"amount":1,"gain":0,"in":"#f67400ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#f67400ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#f67400ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#f67400ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#f67400ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#f67400ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#f67400ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#f67400ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#2980b9ff","out":{"hex":"#0081beff","u16":[0,33053,48831,65535]}}, +{"amount":0,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#1e80bcff","u16":[7689,32975,48193,65535]}}, +{"amount":0,"gain":1,"in":"#2980b9ff","out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"amount":0,"gain":2,"in":"#2980b9ff","out":{"hex":"#387fb4ff","u16":[14439,32738,46216,65535]}}, +{"amount":0.1,"gain":0,"in":"#2980b9ff","out":{"hex":"#0096deff","u16":[0,38628,57068,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#2396dbff","u16":[8986,38537,56322,65535]}}, +{"amount":0.1,"gain":1,"in":"#2980b9ff","out":{"hex":"#3096d8ff","u16":[12314,38444,55564,65535]}}, +{"amount":0.1,"gain":2,"in":"#2980b9ff","out":{"hex":"#4295d2ff","u16":[16875,38259,54011,65535]}}, +{"amount":0.25,"gain":0,"in":"#2980b9ff","out":{"hex":"#38b0ffff","u16":[14334,45315,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#42b0fcff","u16":[17068,45216,64709,65535]}}, +{"amount":0.25,"gain":1,"in":"#2980b9ff","out":{"hex":"#4bb0f9ff","u16":[19356,45117,63871,65535]}}, +{"amount":0.25,"gain":2,"in":"#2980b9ff","out":{"hex":"#5aaff2ff","u16":[23157,44917,62152,65535]}}, +{"amount":0.5,"gain":0,"in":"#2980b9ff","out":{"hex":"#a0cfffff","u16":[41027,53098,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#a2cefdff","u16":[41598,53044,64986,65535]}}, +{"amount":0.5,"gain":1,"in":"#2980b9ff","out":{"hex":"#a4cefbff","u16":[42159,52989,64431,65535]}}, +{"amount":0.5,"gain":2,"in":"#2980b9ff","out":{"hex":"#a8cef6ff","u16":[43257,52879,63304,65535]}}, +{"amount":0.75,"gain":0,"in":"#2980b9ff","out":{"hex":"#d6e8ffff","u16":[54940,59706,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#d7e8feff","u16":[55142,59682,65261,65535]}}, +{"amount":0.75,"gain":1,"in":"#2980b9ff","out":{"hex":"#d7e8fdff","u16":[55343,59659,64986,65535]}}, +{"amount":0.75,"gain":2,"in":"#2980b9ff","out":{"hex":"#d9e8fbff","u16":[55744,59611,64431,65535]}}, +{"amount":0.9,"gain":0,"in":"#2980b9ff","out":{"hex":"#f0f6ffff","u16":[61559,63281,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#f0f6ffff","u16":[61630,63272,65426,65535]}}, +{"amount":0.9,"gain":1,"in":"#2980b9ff","out":{"hex":"#f0f6feff","u16":[61701,63263,65316,65535]}}, +{"amount":0.9,"gain":2,"in":"#2980b9ff","out":{"hex":"#f1f6fdff","u16":[61842,63245,65096,65535]}}, +{"amount":1,"gain":0,"in":"#2980b9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#2980b9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#2980b9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#2980b9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#2980b9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#2980b9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#7f8c8dff","out":{"hex":"#00999eff","u16":[0,39241,40668,65535]}}, +{"amount":0,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#5d9396ff","u16":[23818,37653,38529,65535]}}, +{"amount":0,"gain":1,"in":"#7f8c8dff","out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"amount":0,"gain":2,"in":"#7f8c8dff","out":{"hex":"#898989ff","u16":[35320,35320,35320,65535]}}, +{"amount":0.1,"gain":0,"in":"#7f8c8dff","out":{"hex":"#00abb2ff","u16":[0,44049,45651,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#68a4a8ff","u16":[26737,42267,43250,65535]}}, +{"amount":0.1,"gain":1,"in":"#7f8c8dff","out":{"hex":"#8f9d9eff","u16":[36639,40389,40678,65535]}}, +{"amount":0.1,"gain":2,"in":"#7f8c8dff","out":{"hex":"#9a9a9aff","u16":[39649,39649,39649,65535]}}, +{"amount":0.25,"gain":0,"in":"#7f8c8dff","out":{"hex":"#00c4cbff","u16":[0,50262,52091,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#77bcc0ff","u16":[30508,48228,49351,65535]}}, +{"amount":0.25,"gain":1,"in":"#7f8c8dff","out":{"hex":"#a3b3b5ff","u16":[41807,46086,46415,65535]}}, +{"amount":0.25,"gain":2,"in":"#7f8c8dff","out":{"hex":"#b0b0b0ff","u16":[45241,45241,45241,65535]}}, +{"amount":0.5,"gain":0,"in":"#7f8c8dff","out":{"hex":"#00e5eeff","u16":[0,58946,61090,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#8bdce1ff","u16":[35779,56561,57877,65535]}}, +{"amount":0.5,"gain":1,"in":"#7f8c8dff","out":{"hex":"#bfd2d4ff","u16":[49029,54048,54434,65535]}}, +{"amount":0.5,"gain":2,"in":"#7f8c8dff","out":{"hex":"#cececeff","u16":[53057,53057,53057,65535]}}, +{"amount":0.75,"gain":0,"in":"#7f8c8dff","out":{"hex":"#94f9ffff","u16":[38095,63943,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#bdf2f6ff","u16":[48595,62197,63157,65535]}}, +{"amount":0.75,"gain":1,"in":"#7f8c8dff","out":{"hex":"#ddebecff","u16":[56895,60391,60666,65535]}}, +{"amount":0.75,"gain":2,"in":"#7f8c8dff","out":{"hex":"#e8e8e8ff","u16":[59688,59688,59688,65535]}}, +{"amount":0.9,"gain":0,"in":"#7f8c8dff","out":{"hex":"#dcfdffff","u16":[56490,64904,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#e7fafbff","u16":[59454,64225,64596,65535]}}, +{"amount":0.9,"gain":1,"in":"#7f8c8dff","out":{"hex":"#f2f7f8ff","u16":[62252,63537,63641,65535]}}, +{"amount":0.9,"gain":2,"in":"#7f8c8dff","out":{"hex":"#f6f6f6ff","u16":[63274,63274,63274,65535]}}, +{"amount":1,"gain":0,"in":"#7f8c8dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#7f8c8dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#7f8c8dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#7f8c8dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#7f8c8dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#7f8c8dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#9b59b6ff","out":{"hex":"#c000eeff","u16":[49379,0,61158,65535]}}, +{"amount":0,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#af41d4ff","u16":[44912,16691,54537,65535]}}, +{"amount":0,"gain":1,"in":"#9b59b6ff","out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"amount":0,"gain":2,"in":"#9b59b6ff","out":{"hex":"#747474ff","u16":[29758,29758,29758,65535]}}, +{"amount":0.1,"gain":0,"in":"#9b59b6ff","out":{"hex":"#d24bffff","u16":[53874,19216,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#c162e5ff","u16":[49532,25079,58950,65535]}}, +{"amount":0.1,"gain":1,"in":"#9b59b6ff","out":{"hex":"#ae73c8ff","u16":[44676,29632,51335,65535]}}, +{"amount":0.1,"gain":2,"in":"#9b59b6ff","out":{"hex":"#8a8a8aff","u16":[35431,35431,35431,65535]}}, +{"amount":0.25,"gain":0,"in":"#9b59b6ff","out":{"hex":"#da81ffff","u16":[56022,33113,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#cd8ceaff","u16":[52611,35981,60108,65535]}}, +{"amount":0.25,"gain":1,"in":"#9b59b6ff","out":{"hex":"#be96d2ff","u16":[48912,38598,54017,65535]}}, +{"amount":0.25,"gain":2,"in":"#9b59b6ff","out":{"hex":"#a5a5a5ff","u16":[42318,42318,42318,65535]}}, +{"amount":0.5,"gain":0,"in":"#9b59b6ff","out":{"hex":"#e7b7ffff","u16":[59397,47025,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#dfbcf1ff","u16":[57311,48324,61980,65535]}}, +{"amount":0.5,"gain":1,"in":"#9b59b6ff","out":{"hex":"#d7c1e2ff","u16":[55129,49582,58163,65535]}}, +{"amount":0.5,"gain":2,"in":"#9b59b6ff","out":{"hex":"#c8c8c8ff","u16":[51482,51482,51482,65535]}}, +{"amount":0.75,"gain":0,"in":"#9b59b6ff","out":{"hex":"#f3dfffff","u16":[62556,57184,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#f0e1f8ff","u16":[61588,57703,63787,65535]}}, +{"amount":0.75,"gain":1,"in":"#9b59b6ff","out":{"hex":"#ece3f1ff","u16":[60600,58217,61980,65535]}}, +{"amount":0.75,"gain":2,"in":"#9b59b6ff","out":{"hex":"#e6e6e6ff","u16":[59012,59012,59012,65535]}}, +{"amount":0.9,"gain":0,"in":"#9b59b6ff","out":{"hex":"#faf3ffff","u16":[64363,62356,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#f9f3fcff","u16":[63991,62543,64843,65535]}}, +{"amount":0.9,"gain":1,"in":"#9b59b6ff","out":{"hex":"#f8f4faff","u16":[63616,62731,64142,65535]}}, +{"amount":0.9,"gain":2,"in":"#9b59b6ff","out":{"hex":"#f5f5f5ff","u16":[63023,63023,63023,65535]}}, +{"amount":1,"gain":0,"in":"#9b59b6ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#9b59b6ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#9b59b6ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#9b59b6ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#9b59b6ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#9b59b6ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#1d99f3ff","out":{"hex":"#0099f5ff","u16":[0,39362,62994,65535]}}, +{"amount":0,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#1599f4ff","u16":[5439,39341,62723,65535]}}, +{"amount":0,"gain":1,"in":"#1d99f3ff","out":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]}}, +{"amount":0,"gain":2,"in":"#1d99f3ff","out":{"hex":"#2899f1ff","u16":[10213,39280,61903,65535]}}, +{"amount":0.1,"gain":0,"in":"#1d99f3ff","out":{"hex":"#49a7ffff","u16":[18740,43035,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#4ba7feff","u16":[19302,43016,65271,65535]}}, +{"amount":0.1,"gain":1,"in":"#1d99f3ff","out":{"hex":"#4da7fdff","u16":[19846,42998,65007,65535]}}, +{"amount":0.1,"gain":2,"in":"#1d99f3ff","out":{"hex":"#51a7fbff","u16":[20881,42960,64473,65535]}}, +{"amount":0.25,"gain":0,"in":"#1d99f3ff","out":{"hex":"#80baffff","u16":[32909,47694,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#81bafeff","u16":[33151,47680,65315,65535]}}, +{"amount":0.25,"gain":1,"in":"#1d99f3ff","out":{"hex":"#82b9fdff","u16":[33390,47666,65095,65535]}}, +{"amount":0.25,"gain":2,"in":"#1d99f3ff","out":{"hex":"#84b9fcff","u16":[33863,47639,64651,65535]}}, +{"amount":0.5,"gain":0,"in":"#1d99f3ff","out":{"hex":"#b7d4ffff","u16":[46936,54431,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#b7d4feff","u16":[47041,54423,65389,65535]}}, +{"amount":0.5,"gain":1,"in":"#1d99f3ff","out":{"hex":"#b7d4feff","u16":[47147,54415,65242,65535]}}, +{"amount":0.5,"gain":2,"in":"#1d99f3ff","out":{"hex":"#b8d4fdff","u16":[47356,54399,64948,65535]}}, +{"amount":0.75,"gain":0,"in":"#1d99f3ff","out":{"hex":"#deebffff","u16":[57149,60290,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#dfebffff","u16":[57191,60287,65462,65535]}}, +{"amount":0.75,"gain":1,"in":"#1d99f3ff","out":{"hex":"#dfebfeff","u16":[57232,60283,65389,65535]}}, +{"amount":0.75,"gain":2,"in":"#1d99f3ff","out":{"hex":"#dfebfeff","u16":[57315,60276,65242,65535]}}, +{"amount":0.9,"gain":0,"in":"#1d99f3ff","out":{"hex":"#f3f7ffff","u16":[62343,63499,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#f3f7ffff","u16":[62358,63498,65506,65535]}}, +{"amount":0.9,"gain":1,"in":"#1d99f3ff","out":{"hex":"#f3f7ffff","u16":[62373,63497,65477,65535]}}, +{"amount":0.9,"gain":2,"in":"#1d99f3ff","out":{"hex":"#f3f7ffff","u16":[62403,63494,65418,65535]}}, +{"amount":1,"gain":0,"in":"#1d99f3ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#1d99f3ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#1d99f3ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#1d99f3ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#1d99f3ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#1d99f3ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#dee0e2ff","out":{"hex":"#9dd5ffff","u16":[40355,54640,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#b6d2eaff","u16":[46720,53846,60050,65535]}}, +{"amount":-0.5,"gain":1,"in":"#dee0e2ff","out":{"hex":"#cbced2ff","u16":[52184,53038,53885,65535]}}, +{"amount":-0.5,"gain":2,"in":"#dee0e2ff","out":{"hex":"#cececeff","u16":[52920,52920,52920,65535]}}, +{"amount":0,"gain":0,"in":"#dee0e2ff","out":{"hex":"#c4e4ffff","u16":[50464,58543,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#d2e2f1ff","u16":[53880,58058,61943,65535]}}, +{"amount":0,"gain":1,"in":"#dee0e2ff","out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"amount":0,"gain":2,"in":"#dee0e2ff","out":{"hex":"#e0e0e0ff","u16":[57497,57497,57497,65535]}}, +{"amount":0.1,"gain":0,"in":"#dee0e2ff","out":{"hex":"#cbe7ffff","u16":[52210,59287,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#d7e5f2ff","u16":[55180,58857,62314,65535]}}, +{"amount":0.1,"gain":1,"in":"#dee0e2ff","out":{"hex":"#e2e3e5ff","u16":[57970,58424,58879,65535]}}, +{"amount":0.1,"gain":2,"in":"#dee0e2ff","out":{"hex":"#e3e3e3ff","u16":[58361,58361,58361,65535]}}, +{"amount":0.25,"gain":0,"in":"#dee0e2ff","out":{"hex":"#d5ebffff","u16":[54705,60383,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#deeaf5ff","u16":[57064,60033,62864,65535]}}, +{"amount":0.25,"gain":1,"in":"#dee0e2ff","out":{"hex":"#e7e8eaff","u16":[59311,59680,60050,65535]}}, +{"amount":0.25,"gain":2,"in":"#dee0e2ff","out":{"hex":"#e8e8e8ff","u16":[59629,59629,59629,65535]}}, +{"amount":0.5,"gain":0,"in":"#dee0e2ff","out":{"hex":"#e4f2ffff","u16":[58583,62157,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#eaf1f8ff","u16":[60048,61932,63769,65535]}}, +{"amount":0.5,"gain":1,"in":"#dee0e2ff","out":{"hex":"#eff0f1ff","u16":[61470,61706,61943,65535]}}, +{"amount":0.5,"gain":2,"in":"#dee0e2ff","out":{"hex":"#f0f0f0ff","u16":[61673,61673,61673,65535]}}, +{"amount":0.75,"gain":0,"in":"#dee0e2ff","out":{"hex":"#f2f9ffff","u16":[62176,63873,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#f5f8fcff","u16":[62863,63764,64659,65535]}}, +{"amount":0.75,"gain":1,"in":"#dee0e2ff","out":{"hex":"#f7f8f8ff","u16":[63542,63655,63769,65535]}}, +{"amount":0.75,"gain":2,"in":"#dee0e2ff","out":{"hex":"#f8f8f8ff","u16":[63639,63639,63639,65535]}}, +{"amount":0.9,"gain":0,"in":"#dee0e2ff","out":{"hex":"#fafcffff","u16":[64217,64876,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#fbfcfeff","u16":[64482,64834,65186,65535]}}, +{"amount":0.9,"gain":1,"in":"#dee0e2ff","out":{"hex":"#fcfcfcff","u16":[64746,64791,64836,65535]}}, +{"amount":0.9,"gain":2,"in":"#dee0e2ff","out":{"hex":"#fcfcfcff","u16":[64785,64785,64785,65535]}}, +{"amount":1,"gain":0,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#292c30ff","out":{"hex":"#002e45ff","u16":[0,11820,17816,65535]}}, +{"amount":0,"gain":0.5,"in":"#292c30ff","out":{"hex":"#1e2d3cff","u16":[7689,11567,15371,65535]}}, +{"amount":0,"gain":1,"in":"#292c30ff","out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"amount":0,"gain":2,"in":"#292c30ff","out":{"hex":"#2c2c2cff","u16":[11229,11229,11229,65535]}}, +{"amount":0.1,"gain":0,"in":"#292c30ff","out":{"hex":"#006699ff","u16":[0,26172,39448,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#292c30ff","out":{"hex":"#426484ff","u16":[17026,25612,34035,65535]}}, +{"amount":0.1,"gain":1,"in":"#292c30ff","out":{"hex":"#5b616aff","u16":[23331,25038,27314,65535]}}, +{"amount":0.1,"gain":2,"in":"#292c30ff","out":{"hex":"#616161ff","u16":[24863,24863,24863,65535]}}, +{"amount":0.25,"gain":0,"in":"#292c30ff","out":{"hex":"#0093ddff","u16":[0,37753,56904,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#292c30ff","out":{"hex":"#6090bfff","u16":[24559,36946,49095,65535]}}, +{"amount":0.25,"gain":1,"in":"#292c30ff","out":{"hex":"#838d99ff","u16":[33655,36118,39401,65535]}}, +{"amount":0.25,"gain":2,"in":"#292c30ff","out":{"hex":"#8c8c8cff","u16":[35864,35864,35864,65535]}}, +{"amount":0.5,"gain":0,"in":"#292c30ff","out":{"hex":"#83c1ffff","u16":[33748,49674,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#292c30ff","out":{"hex":"#9fbfe5ff","u16":[40830,49069,58779,65535]}}, +{"amount":0.5,"gain":1,"in":"#292c30ff","out":{"hex":"#b6bdc6ff","u16":[46677,48456,50929,65535]}}, +{"amount":0.5,"gain":2,"in":"#292c30ff","out":{"hex":"#bcbcbcff","u16":[48269,48269,48269,65535]}}, +{"amount":0.75,"gain":0,"in":"#292c30ff","out":{"hex":"#cde3ffff","u16":[52585,58255,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#292c30ff","out":{"hex":"#d6e2f2ff","u16":[54871,58006,62267,65535]}}, +{"amount":0.75,"gain":1,"in":"#292c30ff","out":{"hex":"#dee1e5ff","u16":[57047,57757,58779,65535]}}, +{"amount":0.75,"gain":2,"in":"#292c30ff","out":{"hex":"#e0e0e0ff","u16":[57681,57681,57681,65535]}}, +{"amount":0.9,"gain":0,"in":"#292c30ff","out":{"hex":"#ecf4ffff","u16":[60752,62744,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#292c30ff","out":{"hex":"#eff4faff","u16":[61535,62654,64252,65535]}}, +{"amount":0.9,"gain":1,"in":"#292c30ff","out":{"hex":"#f2f3f5ff","u16":[62306,62563,62937,65535]}}, +{"amount":0.9,"gain":2,"in":"#292c30ff","out":{"hex":"#f3f3f3ff","u16":[62535,62535,62535,65535]}}, +{"amount":1,"gain":0,"in":"#292c30ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#292c30ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#292c30ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#292c30ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#292c30ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#292c30ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#292c30ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#292c30ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#e93a9aff","out":{"hex":"#f8009eff","u16":[63783,0,40725,65535]}}, +{"amount":0,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#f12a9cff","u16":[61869,10878,40157,65535]}}, +{"amount":0,"gain":1,"in":"#e93a9aff","out":{"hex":"#e93a9aff","u16":[59881,14906,39578,65535]}}, +{"amount":0,"gain":2,"in":"#e93a9aff","out":{"hex":"#d94f95ff","u16":[55647,20426,38389,65535]}}, +{"amount":0.1,"gain":0,"in":"#e93a9aff","out":{"hex":"#ff53adff","u16":[65535,21211,44424,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#f85aabff","u16":[63736,23250,43927,65535]}}, +{"amount":0.1,"gain":1,"in":"#e93a9aff","out":{"hex":"#f162a9ff","u16":[61873,25095,43422,65535]}}, +{"amount":0.1,"gain":2,"in":"#e93a9aff","out":{"hex":"#e16ea5ff","u16":[57931,28365,42392,65535]}}, +{"amount":0.25,"gain":0,"in":"#e93a9aff","out":{"hex":"#ff84beff","u16":[65535,34018,48724,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#f988bcff","u16":[64040,35020,48354,65535]}}, +{"amount":0.25,"gain":1,"in":"#e93a9aff","out":{"hex":"#f38cbbff","u16":[62501,35990,47980,65535]}}, +{"amount":0.25,"gain":2,"in":"#e93a9aff","out":{"hex":"#e793b8ff","u16":[59280,37839,47222,65535]}}, +{"amount":0.5,"gain":0,"in":"#e93a9aff","out":{"hex":"#ffb9d6ff","u16":[65535,47425,55020,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#fbbad5ff","u16":[64543,47879,54807,65535]}}, +{"amount":0.5,"gain":1,"in":"#e93a9aff","out":{"hex":"#f7bcd4ff","u16":[63532,48328,54594,65535]}}, +{"amount":0.5,"gain":2,"in":"#e93a9aff","out":{"hex":"#efbfd3ff","u16":[61450,49211,54163,65535]}}, +{"amount":0.75,"gain":0,"in":"#e93a9aff","out":{"hex":"#ffdfecff","u16":[65535,57343,60552,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#fde0ebff","u16":[65041,57524,60457,65535]}}, +{"amount":0.75,"gain":1,"in":"#e93a9aff","out":{"hex":"#fbe1ebff","u16":[64543,57705,60362,65535]}}, +{"amount":0.75,"gain":2,"in":"#e93a9aff","out":{"hex":"#f7e2eaff","u16":[63532,58064,60172,65535]}}, +{"amount":0.9,"gain":0,"in":"#e93a9aff","out":{"hex":"#fff3f7ff","u16":[65535,62413,63598,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#fef3f7ff","u16":[65338,62479,63562,65535]}}, +{"amount":0.9,"gain":1,"in":"#e93a9aff","out":{"hex":"#fdf3f7ff","u16":[65140,62544,63527,65535]}}, +{"amount":0.9,"gain":2,"in":"#e93a9aff","out":{"hex":"#fcf4f7ff","u16":[64743,62675,63455,65535]}}, +{"amount":1,"gain":0,"in":"#e93a9aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#e93a9aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#e93a9aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#e93a9aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#e93a9aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#e93a9aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#e93d58ff","out":{"hex":"#fc004bff","u16":[64772,0,19159,65535]}}, +{"amount":0,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#f32d52ff","u16":[62384,11440,20973,65535]}}, +{"amount":0,"gain":1,"in":"#e93d58ff","out":{"hex":"#e93d58ff","u16":[59881,15677,22616,65535]}}, +{"amount":0,"gain":2,"in":"#e93d58ff","out":{"hex":"#d45463ff","u16":[54456,21483,25532,65535]}}, +{"amount":0.1,"gain":0,"in":"#e93d58ff","out":{"hex":"#ff576eff","u16":[65535,22333,28148,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#f65f72ff","u16":[63350,24386,29246,65535]}}, +{"amount":0.1,"gain":1,"in":"#e93d58ff","out":{"hex":"#ee6676ff","u16":[61070,26250,30297,65535]}}, +{"amount":0.1,"gain":2,"in":"#e93d58ff","out":{"hex":"#db737eff","u16":[56174,29567,32278,65535]}}, +{"amount":0.25,"gain":0,"in":"#e93d58ff","out":{"hex":"#ff8693ff","u16":[65535,34560,37712,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#f88b95ff","u16":[63720,35611,38365,65535]}}, +{"amount":0.25,"gain":1,"in":"#e93d58ff","out":{"hex":"#f18f98ff","u16":[61841,36625,39004,65535]}}, +{"amount":0.25,"gain":2,"in":"#e93d58ff","out":{"hex":"#e1969dff","u16":[57862,38558,40247,65535]}}, +{"amount":0.5,"gain":0,"in":"#e93d58ff","out":{"hex":"#ffb9bfff","u16":[65535,47670,49149,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#fabbc0ff","u16":[64332,48151,49468,65535]}}, +{"amount":0.5,"gain":1,"in":"#e93d58ff","out":{"hex":"#f6bdc2ff","u16":[63101,48627,49784,65535]}}, +{"amount":0.5,"gain":2,"in":"#e93d58ff","out":{"hex":"#ecc1c4ff","u16":[60549,49563,50409,65535]}}, +{"amount":0.75,"gain":0,"in":"#e93d58ff","out":{"hex":"#ffe0e2ff","u16":[65535,57440,58039,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#fde0e2ff","u16":[64937,57634,58170,65535]}}, +{"amount":0.75,"gain":1,"in":"#e93d58ff","out":{"hex":"#fae1e3ff","u16":[64332,57826,58300,65535]}}, +{"amount":0.75,"gain":2,"in":"#e93d58ff","out":{"hex":"#f6e2e4ff","u16":[63101,58209,58560,65535]}}, +{"amount":0.9,"gain":0,"in":"#e93d58ff","out":{"hex":"#fff3f4ff","u16":[65535,62448,62666,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#fef3f4ff","u16":[65297,62518,62713,65535]}}, +{"amount":0.9,"gain":1,"in":"#e93d58ff","out":{"hex":"#fdf4f4ff","u16":[65057,62588,62761,65535]}}, +{"amount":0.9,"gain":2,"in":"#e93d58ff","out":{"hex":"#fbf4f5ff","u16":[64575,62728,62857,65535]}}, +{"amount":1,"gain":0,"in":"#e93d58ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#e93d58ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#e93d58ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#e93d58ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#e93d58ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#e93d58ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#e9643aff","out":{"hex":"#f55b00ff","u16":[62857,23421,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#e9643aff","out":{"hex":"#ef602aff","u16":[61391,24592,10878,65535]}}, +{"amount":0,"gain":1,"in":"#e9643aff","out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"amount":0,"gain":2,"in":"#e9643aff","out":{"hex":"#dd6c4fff","u16":[56716,27759,20426,65535]}}, +{"amount":0.1,"gain":0,"in":"#e9643aff","out":{"hex":"#ff734bff","u16":[65535,29554,19196,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#e9643aff","out":{"hex":"#f97754ff","u16":[64110,30470,21575,65535]}}, +{"amount":0.1,"gain":1,"in":"#e9643aff","out":{"hex":"#f47a5cff","u16":[62646,31354,23674,65535]}}, +{"amount":0.1,"gain":2,"in":"#e9643aff","out":{"hex":"#e8816aff","u16":[59588,33039,27312,65535]}}, +{"amount":0.25,"gain":0,"in":"#e9643aff","out":{"hex":"#ff9681ff","u16":[65535,38550,33105,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#e9643aff","out":{"hex":"#fa9885ff","u16":[64350,39111,34191,65535]}}, +{"amount":0.25,"gain":1,"in":"#e9643aff","out":{"hex":"#f69a89ff","u16":[63139,39662,35238,65535]}}, +{"amount":0.25,"gain":2,"in":"#e9643aff","out":{"hex":"#ec9f91ff","u16":[60628,40737,37227,65535]}}, +{"amount":0.5,"gain":0,"in":"#e9643aff","out":{"hex":"#ffc1b7ff","u16":[65535,49559,47021,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#e9643aff","out":{"hex":"#fcc2b9ff","u16":[64748,49837,47503,65535]}}, +{"amount":0.5,"gain":1,"in":"#e9643aff","out":{"hex":"#f9c3bbff","u16":[63949,50113,47979,65535]}}, +{"amount":0.5,"gain":2,"in":"#e9643aff","out":{"hex":"#f2c5beff","u16":[62315,50660,48915,65535]}}, +{"amount":0.75,"gain":0,"in":"#e9643aff","out":{"hex":"#ffe2dfff","u16":[65535,58208,57183,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#e9643aff","out":{"hex":"#fde3dfff","u16":[65143,58322,57374,65535]}}, +{"amount":0.75,"gain":1,"in":"#e9643aff","out":{"hex":"#fce3e0ff","u16":[64748,58437,57564,65535]}}, +{"amount":0.75,"gain":2,"in":"#e9643aff","out":{"hex":"#f9e4e1ff","u16":[63949,58665,57943,65535]}}, +{"amount":0.9,"gain":0,"in":"#e9643aff","out":{"hex":"#fff4f3ff","u16":[65535,62727,62355,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#e9643aff","out":{"hex":"#fef4f3ff","u16":[65379,62769,62424,65535]}}, +{"amount":0.9,"gain":1,"in":"#e9643aff","out":{"hex":"#fef4f3ff","u16":[65222,62811,62493,65535]}}, +{"amount":0.9,"gain":2,"in":"#e9643aff","out":{"hex":"#fdf5f4ff","u16":[64906,62895,62631,65535]}}, +{"amount":1,"gain":0,"in":"#e9643aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#e9643aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#e9643aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#e9643aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#e9643aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#e9643aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#e9643aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#e9643aff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#e8cb2dff","out":{"hex":"#c3aa00ff","u16":[50141,43683,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#c3aa1bff","u16":[50034,43684,7067,65535]}}, +{"amount":-0.5,"gain":1,"in":"#e8cb2dff","out":{"hex":"#c2aa26ff","u16":[49926,43686,9684,65535]}}, +{"amount":-0.5,"gain":2,"in":"#e8cb2dff","out":{"hex":"#c1aa34ff","u16":[49710,43688,13270,65535]}}, +{"amount":0,"gain":0,"in":"#e8cb2dff","out":{"hex":"#e9cb00ff","u16":[59881,52168,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#e8cb21ff","u16":[59752,52170,8439,65535]}}, +{"amount":0,"gain":1,"in":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"gain":2,"in":"#e8cb2dff","out":{"hex":"#e7cb3eff","u16":[59366,52174,15848,65535]}}, +{"amount":0.1,"gain":0,"in":"#e8cb2dff","out":{"hex":"#f0d100ff","u16":[61610,53675,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#efd122ff","u16":[61479,53677,8683,65535]}}, +{"amount":0.1,"gain":1,"in":"#e8cb2dff","out":{"hex":"#efd12eff","u16":[61346,53678,11899,65535]}}, +{"amount":0.1,"gain":2,"in":"#e8cb2dff","out":{"hex":"#eed13fff","u16":[61081,53681,16306,65535]}}, +{"amount":0.25,"gain":0,"in":"#e8cb2dff","out":{"hex":"#f9d900ff","u16":[64101,55845,0,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#f9d923ff","u16":[63964,55847,9034,65535]}}, +{"amount":0.25,"gain":1,"in":"#e8cb2dff","out":{"hex":"#f8d930ff","u16":[63826,55848,12380,65535]}}, +{"amount":0.25,"gain":2,"in":"#e8cb2dff","out":{"hex":"#f7d942ff","u16":[63550,55851,16965,65535]}}, +{"amount":0.5,"gain":0,"in":"#e8cb2dff","out":{"hex":"#ffe786ff","u16":[65535,59278,34443,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#ffe789ff","u16":[65429,59279,35091,65535]}}, +{"amount":0.5,"gain":1,"in":"#e8cb2dff","out":{"hex":"#fee78bff","u16":[65323,59280,35725,65535]}}, +{"amount":0.5,"gain":2,"in":"#e8cb2dff","out":{"hex":"#fde790ff","u16":[65109,59282,36953,65535]}}, +{"amount":0.75,"gain":0,"in":"#e8cb2dff","out":{"hex":"#fff3cdff","u16":[65535,62500,52791,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#fff3ceff","u16":[65482,62501,52987,65535]}}, +{"amount":0.75,"gain":1,"in":"#e8cb2dff","out":{"hex":"#fff3cfff","u16":[65429,62501,53182,65535]}}, +{"amount":0.75,"gain":2,"in":"#e8cb2dff","out":{"hex":"#fef3d0ff","u16":[65323,62502,53569,65535]}}, +{"amount":0.9,"gain":0,"in":"#e8cb2dff","out":{"hex":"#fffaedff","u16":[65535,64342,60822,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#fffaedff","u16":[65514,64342,60888,65535]}}, +{"amount":0.9,"gain":1,"in":"#e8cb2dff","out":{"hex":"#fffaedff","u16":[65493,64342,60954,65535]}}, +{"amount":0.9,"gain":2,"in":"#e8cb2dff","out":{"hex":"#fffaeeff","u16":[65450,64343,61086,65535]}}, +{"amount":1,"gain":0,"in":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#3dd425ff","out":{"hex":"#259700ff","u16":[9402,38890,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#289713ff","u16":[10320,38819,4935,65535]}}, +{"amount":-0.5,"gain":1,"in":"#3dd425ff","out":{"hex":"#2b971aff","u16":[11149,38747,6762,65535]}}, +{"amount":-0.5,"gain":2,"in":"#3dd425ff","out":{"hex":"#319624ff","u16":[12617,38603,9267,65535]}}, +{"amount":0,"gain":0,"in":"#3dd425ff","out":{"hex":"#33d500ff","u16":[13220,54686,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#38d41bff","u16":[14511,54585,6939,65535]}}, +{"amount":0,"gain":1,"in":"#3dd425ff","out":{"hex":"#3dd425ff","u16":[15677,54484,9509,65535]}}, +{"amount":0,"gain":2,"in":"#3dd425ff","out":{"hex":"#45d333ff","u16":[17741,54281,13031,65535]}}, +{"amount":0.1,"gain":0,"in":"#3dd425ff","out":{"hex":"#36df00ff","u16":[13837,57237,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#3bde1cff","u16":[15188,57132,7263,65535]}}, +{"amount":0.1,"gain":1,"in":"#3dd425ff","out":{"hex":"#40de27ff","u16":[16408,57026,9953,65535]}}, +{"amount":0.1,"gain":2,"in":"#3dd425ff","out":{"hex":"#48dd35ff","u16":[18568,56814,13639,65535]}}, +{"amount":0.25,"gain":0,"in":"#3dd425ff","out":{"hex":"#39ed00ff","u16":[14705,60826,0,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#3fec1eff","u16":[16141,60714,7718,65535]}}, +{"amount":0.25,"gain":1,"in":"#3dd425ff","out":{"hex":"#44ec29ff","u16":[17437,60602,10577,65535]}}, +{"amount":0.25,"gain":2,"in":"#3dd425ff","out":{"hex":"#4deb38ff","u16":[19733,60376,14494,65535]}}, +{"amount":0.5,"gain":0,"in":"#3dd425ff","out":{"hex":"#5dff4bff","u16":[23932,65535,19313,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#61ff50ff","u16":[24842,65422,20547,65535]}}, +{"amount":0.5,"gain":1,"in":"#3dd425ff","out":{"hex":"#64fe54ff","u16":[25714,65310,21698,65535]}}, +{"amount":0.5,"gain":2,"in":"#3dd425ff","out":{"hex":"#6afd5dff","u16":[27358,65083,23804,65535]}}, +{"amount":0.75,"gain":0,"in":"#3dd425ff","out":{"hex":"#c3ffc0ff","u16":[50127,65535,49276,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#c4ffc1ff","u16":[50318,65479,49483,65535]}}, +{"amount":0.75,"gain":1,"in":"#3dd425ff","out":{"hex":"#c5ffc1ff","u16":[50508,65422,49690,65535]}}, +{"amount":0.75,"gain":2,"in":"#3dd425ff","out":{"hex":"#c6fec3ff","u16":[50886,65310,50100,65535]}}, +{"amount":0.9,"gain":0,"in":"#3dd425ff","out":{"hex":"#e9ffe8ff","u16":[59942,65535,59669,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#e9ffe8ff","u16":[60004,65513,59736,65535]}}, +{"amount":0.9,"gain":1,"in":"#3dd425ff","out":{"hex":"#eaffe9ff","u16":[60066,65490,59802,65535]}}, +{"amount":0.9,"gain":2,"in":"#3dd425ff","out":{"hex":"#eaffe9ff","u16":[60189,65445,59934,65535]}}, +{"amount":1,"gain":0,"in":"#3dd425ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#3dd425ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#3dd425ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#3dd425ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#3dd425ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#3dd425ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#00d3b8ff","out":{"hex":"#009c88ff","u16":[0,40043,34919,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#009c88ff","u16":[0,40043,34919,65535]}}, +{"amount":-0.5,"gain":1,"in":"#00d3b8ff","out":{"hex":"#009c88ff","u16":[0,40043,34919,65535]}}, +{"amount":-0.5,"gain":2,"in":"#00d3b8ff","out":{"hex":"#009c88ff","u16":[0,40043,34919,65535]}}, +{"amount":0,"gain":0,"in":"#00d3b8ff","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"amount":0,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"amount":0,"gain":1,"in":"#00d3b8ff","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"amount":0,"gain":2,"in":"#00d3b8ff","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"amount":0.1,"gain":0,"in":"#00d3b8ff","out":{"hex":"#00dcc0ff","u16":[0,56566,49328,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#00dcc0ff","u16":[0,56566,49328,65535]}}, +{"amount":0.1,"gain":1,"in":"#00d3b8ff","out":{"hex":"#00dcc0ff","u16":[0,56566,49328,65535]}}, +{"amount":0.1,"gain":2,"in":"#00d3b8ff","out":{"hex":"#00dcc0ff","u16":[0,56566,49328,65535]}}, +{"amount":0.25,"gain":0,"in":"#00d3b8ff","out":{"hex":"#00e9cbff","u16":[0,59871,52210,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#00e9cbff","u16":[0,59871,52210,65535]}}, +{"amount":0.25,"gain":1,"in":"#00d3b8ff","out":{"hex":"#00e9cbff","u16":[0,59871,52210,65535]}}, +{"amount":0.25,"gain":2,"in":"#00d3b8ff","out":{"hex":"#00e9cbff","u16":[0,59871,52210,65535]}}, +{"amount":0.5,"gain":0,"in":"#00d3b8ff","out":{"hex":"#00fddcff","u16":[0,64940,56630,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#00fddcff","u16":[0,64940,56630,65535]}}, +{"amount":0.5,"gain":1,"in":"#00d3b8ff","out":{"hex":"#00fddcff","u16":[0,64940,56630,65535]}}, +{"amount":0.5,"gain":2,"in":"#00d3b8ff","out":{"hex":"#00fddcff","u16":[0,64940,56630,65535]}}, +{"amount":0.75,"gain":0,"in":"#00d3b8ff","out":{"hex":"#b4ffeeff","u16":[46362,65535,61237,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#b4ffeeff","u16":[46362,65535,61237,65535]}}, +{"amount":0.75,"gain":1,"in":"#00d3b8ff","out":{"hex":"#b4ffeeff","u16":[46362,65535,61237,65535]}}, +{"amount":0.75,"gain":2,"in":"#00d3b8ff","out":{"hex":"#b4ffeeff","u16":[46362,65535,61237,65535]}}, +{"amount":0.9,"gain":0,"in":"#00d3b8ff","out":{"hex":"#e5fff8ff","u16":[58768,65535,63857,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#e5fff8ff","u16":[58768,65535,63857,65535]}}, +{"amount":0.9,"gain":1,"in":"#00d3b8ff","out":{"hex":"#e5fff8ff","u16":[58768,65535,63857,65535]}}, +{"amount":0.9,"gain":2,"in":"#00d3b8ff","out":{"hex":"#e5fff8ff","u16":[58768,65535,63857,65535]}}, +{"amount":1,"gain":0,"in":"#00d3b8ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#00d3b8ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#00d3b8ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#00d3b8ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#00d3b8ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#00d3b8ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#b875dcff","out":{"hex":"#cd5fffff","u16":[52594,24416,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#b875dcff","out":{"hex":"#c36beeff","u16":[50026,27418,61236,65535]}}, +{"amount":0,"gain":1,"in":"#b875dcff","out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"amount":0,"gain":2,"in":"#b875dcff","out":{"hex":"#a087b1ff","u16":[41148,34667,45372,65535]}}, +{"amount":0.1,"gain":0,"in":"#b875dcff","out":{"hex":"#d27bffff","u16":[54058,31714,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#b875dcff","out":{"hex":"#ca83f0ff","u16":[51831,33755,61682,65535]}}, +{"amount":0.1,"gain":1,"in":"#b875dcff","out":{"hex":"#c18be0ff","u16":[49484,35658,57516,65535]}}, +{"amount":0.1,"gain":2,"in":"#b875dcff","out":{"hex":"#ad98baff","u16":[44336,39138,47859,65535]}}, +{"amount":0.25,"gain":0,"in":"#b875dcff","out":{"hex":"#db9bffff","u16":[56168,39888,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#b875dcff","out":{"hex":"#d4a0f3ff","u16":[54407,41204,62344,65535]}}, +{"amount":0.25,"gain":1,"in":"#b875dcff","out":{"hex":"#cda5e5ff","u16":[52574,42472,58944,65535]}}, +{"amount":0.25,"gain":2,"in":"#b875dcff","out":{"hex":"#bdafc8ff","u16":[48658,44879,51322,65535]}}, +{"amount":0.5,"gain":0,"in":"#b875dcff","out":{"hex":"#e7c3ffff","u16":[59488,50227,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#b875dcff","out":{"hex":"#e3c6f7ff","u16":[58401,50900,63429,65535]}}, +{"amount":0.5,"gain":1,"in":"#b875dcff","out":{"hex":"#dfc9eeff","u16":[57288,51563,61236,65535]}}, +{"amount":0.5,"gain":2,"in":"#b875dcff","out":{"hex":"#d6cedcff","u16":[54982,52859,56540,65535]}}, +{"amount":0.75,"gain":0,"in":"#b875dcff","out":{"hex":"#f4e4ffff","u16":[62599,58485,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#b875dcff","out":{"hex":"#f2e5fbff","u16":[62091,58766,64492,65535]}}, +{"amount":0.75,"gain":1,"in":"#b875dcff","out":{"hex":"#f0e6f7ff","u16":[61578,59047,63429,65535]}}, +{"amount":0.75,"gain":2,"in":"#b875dcff","out":{"hex":"#ece8eeff","u16":[60535,59602,61236,65535]}}, +{"amount":0.9,"gain":0,"in":"#b875dcff","out":{"hex":"#fbf4ffff","u16":[64380,62829,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#b875dcff","out":{"hex":"#faf5fdff","u16":[64184,62932,65120,65535]}}, +{"amount":0.9,"gain":1,"in":"#b875dcff","out":{"hex":"#f9f5fcff","u16":[63987,63036,64703,65535]}}, +{"amount":0.9,"gain":2,"in":"#b875dcff","out":{"hex":"#f7f6f8ff","u16":[63592,63242,63857,65535]}}, +{"amount":1,"gain":0,"in":"#b875dcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#b875dcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#b875dcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#b875dcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#b875dcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#b875dcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#b875dcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#b875dcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#926ee4ff","out":{"hex":"#9764ffff","u16":[38919,25738,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#9569f2ff","u16":[38228,27040,62182,65535]}}, +{"amount":0,"gain":1,"in":"#926ee4ff","out":{"hex":"#926ee4ff","u16":[37522,28270,58596,65535]}}, +{"amount":0,"gain":2,"in":"#926ee4ff","out":{"hex":"#8c77c4ff","u16":[36059,30555,50494,65535]}}, +{"amount":0.1,"gain":0,"in":"#926ee4ff","out":{"hex":"#a57fffff","u16":[42517,32597,65535,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#a382f3ff","u16":[41959,33491,62527,65535]}}, +{"amount":0.1,"gain":1,"in":"#926ee4ff","out":{"hex":"#a186e7ff","u16":[41392,34358,59334,65535]}}, +{"amount":0.1,"gain":2,"in":"#926ee4ff","out":{"hex":"#9d8ccbff","u16":[40230,36016,52236,65535]}}, +{"amount":0.25,"gain":0,"in":"#926ee4ff","out":{"hex":"#b89dffff","u16":[47313,40452,65535,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#b7a0f5ff","u16":[46905,41032,63040,65535]}}, +{"amount":0.25,"gain":1,"in":"#926ee4ff","out":{"hex":"#b5a2ebff","u16":[46493,41602,60421,65535]}}, +{"amount":0.25,"gain":2,"in":"#926ee4ff","out":{"hex":"#b2a6d5ff","u16":[45656,42714,54725,65535]}}, +{"amount":0.5,"gain":0,"in":"#926ee4ff","out":{"hex":"#d3c5ffff","u16":[54214,50514,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#d2c6f9ff","u16":[53984,50811,63885,65535]}}, +{"amount":0.5,"gain":1,"in":"#926ee4ff","out":{"hex":"#d1c7f2ff","u16":[53753,51107,62182,65535]}}, +{"amount":0.5,"gain":2,"in":"#926ee4ff","out":{"hex":"#cfc9e4ff","u16":[53286,51691,58596,65535]}}, +{"amount":0.75,"gain":0,"in":"#926ee4ff","out":{"hex":"#eae4ffff","u16":[60195,58604,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#eae5fcff","u16":[60093,58729,64716,65535]}}, +{"amount":0.75,"gain":1,"in":"#926ee4ff","out":{"hex":"#e9e5f9ff","u16":[59992,58853,63885,65535]}}, +{"amount":0.75,"gain":2,"in":"#926ee4ff","out":{"hex":"#e9e6f2ff","u16":[59788,59101,62182,65535]}}, +{"amount":0.9,"gain":0,"in":"#926ee4ff","out":{"hex":"#f7f5ffff","u16":[63464,62873,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#f7f5feff","u16":[63426,62918,65209,65535]}}, +{"amount":0.9,"gain":1,"in":"#926ee4ff","out":{"hex":"#f7f5fcff","u16":[63387,62964,64881,65535]}}, +{"amount":0.9,"gain":2,"in":"#926ee4ff","out":{"hex":"#f6f5faff","u16":[63311,63056,64219,65535]}}, +{"amount":1,"gain":0,"in":"#926ee4ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#926ee4ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#926ee4ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#926ee4ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#926ee4ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#926ee4ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#686b6fff","out":{"hex":"#0071a7ff","u16":[0,28942,42977,65535]}}, +{"amount":0,"gain":0.5,"in":"#686b6fff","out":{"hex":"#4c6e8eff","u16":[19504,28231,36615,65535]}}, +{"amount":0,"gain":1,"in":"#686b6fff","out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"amount":0,"gain":2,"in":"#686b6fff","out":{"hex":"#6b6b6bff","u16":[27414,27414,27414,65535]}}, +{"amount":0.1,"gain":0,"in":"#686b6fff","out":{"hex":"#008bceff","u16":[0,35634,52915,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#686b6fff","out":{"hex":"#5d87afff","u16":[24015,34759,45082,65535]}}, +{"amount":0.1,"gain":1,"in":"#686b6fff","out":{"hex":"#808489ff","u16":[32908,33858,35123,65535]}}, +{"amount":0.1,"gain":2,"in":"#686b6fff","out":{"hex":"#838383ff","u16":[33752,33752,33752,65535]}}, +{"amount":0.25,"gain":0,"in":"#686b6fff","out":{"hex":"#00a9fbff","u16":[0,43499,64595,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#686b6fff","out":{"hex":"#72a5d6ff","u16":[29315,42432,55033,65535]}}, +{"amount":0.25,"gain":1,"in":"#686b6fff","out":{"hex":"#9ca1a7ff","u16":[40172,41331,42876,65535]}}, +{"amount":0.25,"gain":2,"in":"#686b6fff","out":{"hex":"#a0a0a0ff","u16":[41203,41203,41203,65535]}}, +{"amount":0.5,"gain":0,"in":"#686b6fff","out":{"hex":"#98cbffff","u16":[39098,52172,65535,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#686b6fff","out":{"hex":"#b0c9e6ff","u16":[45142,51575,59157,65535]}}, +{"amount":0.5,"gain":1,"in":"#686b6fff","out":{"hex":"#c4c6caff","u16":[50344,50969,51822,65535]}}, +{"amount":0.5,"gain":2,"in":"#686b6fff","out":{"hex":"#c6c6c6ff","u16":[50900,50900,50900,65535]}}, +{"amount":0.75,"gain":0,"in":"#686b6fff","out":{"hex":"#d3e7ffff","u16":[54274,59306,65535,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#686b6fff","out":{"hex":"#dce6f3ff","u16":[56452,59051,62444,65535]}}, +{"amount":0.75,"gain":1,"in":"#686b6fff","out":{"hex":"#e4e5e6ff","u16":[58533,58795,59157,65535]}}, +{"amount":0.75,"gain":2,"in":"#686b6fff","out":{"hex":"#e5e5e5ff","u16":[58766,58766,58766,65535]}}, +{"amount":0.9,"gain":0,"in":"#686b6fff","out":{"hex":"#eff6ffff","u16":[61328,63132,65535,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#686b6fff","out":{"hex":"#f2f5faff","u16":[62093,63038,64320,65535]}}, +{"amount":0.9,"gain":1,"in":"#686b6fff","out":{"hex":"#f5f5f5ff","u16":[62846,62943,63077,65535]}}, +{"amount":0.9,"gain":2,"in":"#686b6fff","out":{"hex":"#f5f5f5ff","u16":[62932,62932,62932,65535]}}, +{"amount":1,"gain":0,"in":"#686b6fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":0.5,"in":"#686b6fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":1,"in":"#686b6fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"gain":2,"in":"#686b6fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0,"in":"#686b6fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#686b6fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":1,"in":"#686b6fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"gain":2,"in":"#686b6fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0,"in":"#3daee980","out":{"hex":"#004f6c80","u16":[0,20301,27823,32896]}}, +{"amount":-0.5,"gain":0.5,"in":"#3daee980","out":{"hex":"#144f6b80","u16":[5155,20227,27408,32896]}}, +{"amount":-0.5,"gain":1,"in":"#3daee980","out":{"hex":"#1b4e6980","u16":[7065,20152,26985,32896]}}, +{"amount":-0.5,"gain":2,"in":"#3daee980","out":{"hex":"#264e6680","u16":[9681,20002,26115,32896]}}, +{"amount":0,"gain":0,"in":"#3daee980","out":{"hex":"#00aff080","u16":[0,45047,61740,32896]}}, +{"amount":0,"gain":0.5,"in":"#3daee980","out":{"hex":"#2dafed80","u16":[11440,44883,60819,32896]}}, +{"amount":0,"gain":1,"in":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"gain":2,"in":"#3daee980","out":{"hex":"#54ade180","u16":[21483,44386,57950,32896]}}, +{"amount":0.1,"gain":0,"in":"#3daee980","out":{"hex":"#28bbff80","u16":[10177,48176,65535,32896]}}, +{"amount":0.1,"gain":0.5,"in":"#3daee980","out":{"hex":"#3cbbfb80","u16":[15295,48006,64574,32896]}}, +{"amount":0.1,"gain":1,"in":"#3daee980","out":{"hex":"#4abaf780","u16":[18894,47835,63595,32896]}}, +{"amount":0.1,"gain":2,"in":"#3daee980","out":{"hex":"#5fb9f080","u16":[24327,47492,61581,32896]}}, +{"amount":0.25,"gain":0,"in":"#3daee980","out":{"hex":"#75c9ff80","u16":[30097,51565,65535,32896]}}, +{"amount":0.25,"gain":0.5,"in":"#3daee980","out":{"hex":"#7bc8fc80","u16":[31575,51434,64735,32896]}}, +{"amount":0.25,"gain":1,"in":"#3daee980","out":{"hex":"#80c8f980","u16":[32975,51304,63923,32896]}}, +{"amount":0.25,"gain":2,"in":"#3daee980","out":{"hex":"#8ac7f280","u16":[35579,51041,62261,32896]}}, +{"amount":0.5,"gain":0,"in":"#3daee980","out":{"hex":"#b2ddff80","u16":[45756,56685,65535,32896]}}, +{"amount":0.5,"gain":0.5,"in":"#3daee980","out":{"hex":"#b4dcfd80","u16":[46365,56607,65003,32896]}}, +{"amount":0.5,"gain":1,"in":"#3daee980","out":{"hex":"#b7dcfb80","u16":[46964,56529,64466,32896]}}, +{"amount":0.5,"gain":2,"in":"#3daee980","out":{"hex":"#bbdbf780","u16":[48137,56373,63375,32896]}}, +{"amount":0.75,"gain":0,"in":"#3daee980","out":{"hex":"#ddefff80","u16":[56688,61302,65535,32896]}}, +{"amount":0.75,"gain":0.5,"in":"#3daee980","out":{"hex":"#ddeefe80","u16":[56925,61266,65270,32896]}}, +{"amount":0.75,"gain":1,"in":"#3daee980","out":{"hex":"#deeefd80","u16":[57160,61231,65003,32896]}}, +{"amount":0.75,"gain":2,"in":"#3daee980","out":{"hex":"#e0eefb80","u16":[57628,61160,64466,32896]}}, +{"amount":0.9,"gain":0,"in":"#3daee980","out":{"hex":"#f2f9ff80","u16":[62177,63882,65535,32896]}}, +{"amount":0.9,"gain":0.5,"in":"#3daee980","out":{"hex":"#f2f9ff80","u16":[62262,63869,65429,32896]}}, +{"amount":0.9,"gain":1,"in":"#3daee980","out":{"hex":"#f3f8fe80","u16":[62347,63855,65323,32896]}}, +{"amount":0.9,"gain":2,"in":"#3daee980","out":{"hex":"#f3f8fd80","u16":[62516,63828,65110,32896]}}, +{"amount":1,"gain":0,"in":"#3daee980","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1,"gain":0.5,"in":"#3daee980","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1,"gain":1,"in":"#3daee980","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1,"gain":2,"in":"#3daee980","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1.5,"gain":0,"in":"#3daee980","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1.5,"gain":0.5,"in":"#3daee980","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1.5,"gain":1,"in":"#3daee980","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1.5,"gain":2,"in":"#3daee980","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":-0.5,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.25,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.25,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.25,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.25,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.5,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.5,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.5,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.5,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.75,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.75,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.75,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.75,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.9,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.9,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.9,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.9,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":-0.5,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":-0.5,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":-0.5,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.1,"gain":0,"in":"#00000080","out":{"hex":"#b5000080","u16":[46514,0,0,32896]}}, +{"amount":0.1,"gain":0.5,"in":"#00000080","out":{"hex":"#90414180","u16":[37051,16792,16792,32896]}}, +{"amount":0.1,"gain":1,"in":"#00000080","out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32896]}}, +{"amount":0.1,"gain":2,"in":"#00000080","out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32896]}}, +{"amount":0.25,"gain":0,"in":"#00000080","out":{"hex":"#ff404080","u16":[65535,16404,16404,32896]}}, +{"amount":0.25,"gain":0.5,"in":"#00000080","out":{"hex":"#ce6b6b80","u16":[52929,27562,27562,32896]}}, +{"amount":0.25,"gain":1,"in":"#00000080","out":{"hex":"#88888880","u16":[34899,34899,34899,32896]}}, +{"amount":0.25,"gain":2,"in":"#00000080","out":{"hex":"#88888880","u16":[34899,34899,34899,32896]}}, +{"amount":0.5,"gain":0,"in":"#00000080","out":{"hex":"#ffa1a180","u16":[65535,41449,41449,32896]}}, +{"amount":0.5,"gain":0.5,"in":"#00000080","out":{"hex":"#e0aeae80","u16":[57502,44773,44773,32896]}}, +{"amount":0.5,"gain":1,"in":"#00000080","out":{"hex":"#bababa80","u16":[47824,47824,47824,32896]}}, +{"amount":0.5,"gain":2,"in":"#00000080","out":{"hex":"#bababa80","u16":[47824,47824,47824,32896]}}, +{"amount":0.75,"gain":0,"in":"#00000080","out":{"hex":"#ffd6d680","u16":[65535,55089,55089,32896]}}, +{"amount":0.75,"gain":0.5,"in":"#00000080","out":{"hex":"#f0dbdb80","u16":[61676,56311,56311,32896]}}, +{"amount":0.75,"gain":1,"in":"#00000080","out":{"hex":"#e0e0e080","u16":[57502,57502,57502,32896]}}, +{"amount":0.75,"gain":2,"in":"#00000080","out":{"hex":"#e0e0e080","u16":[57502,57502,57502,32896]}}, +{"amount":0.9,"gain":0,"in":"#00000080","out":{"hex":"#fff0f080","u16":[65535,61611,61611,32896]}}, +{"amount":0.9,"gain":0.5,"in":"#00000080","out":{"hex":"#f9f1f180","u16":[64025,62043,62043,32896]}}, +{"amount":0.9,"gain":1,"in":"#00000080","out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32896]}}, +{"amount":0.9,"gain":2,"in":"#00000080","out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32896]}}, +{"amount":1,"gain":0,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1,"gain":0.5,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1,"gain":1,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1,"gain":2,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1.5,"gain":0,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1.5,"gain":0.5,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1.5,"gain":1,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":1.5,"gain":2,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"amount":-0.5,"gain":0,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":-0.5,"gain":0.5,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":-0.5,"gain":1,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":-0.5,"gain":2,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":0,"gain":0,"in":"#ff000033","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"amount":0,"gain":0.5,"in":"#ff000033","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"amount":0,"gain":1,"in":"#ff000033","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"amount":0,"gain":2,"in":"#ff000033","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"amount":0.1,"gain":0,"in":"#ff000033","out":{"hex":"#ff5a5a33","u16":[65535,23011,23011,13107]}}, +{"amount":0.1,"gain":0.5,"in":"#ff000033","out":{"hex":"#ff5a5a33","u16":[65535,23011,23011,13107]}}, +{"amount":0.1,"gain":1,"in":"#ff000033","out":{"hex":"#ff5a5a33","u16":[65535,23011,23011,13107]}}, +{"amount":0.1,"gain":2,"in":"#ff000033","out":{"hex":"#ff5a5a33","u16":[65535,23011,23011,13107]}}, +{"amount":0.25,"gain":0,"in":"#ff000033","out":{"hex":"#ff888833","u16":[65535,34899,34899,13107]}}, +{"amount":0.25,"gain":0.5,"in":"#ff000033","out":{"hex":"#ff888833","u16":[65535,34899,34899,13107]}}, +{"amount":0.25,"gain":1,"in":"#ff000033","out":{"hex":"#ff888833","u16":[65535,34899,34899,13107]}}, +{"amount":0.25,"gain":2,"in":"#ff000033","out":{"hex":"#ff888833","u16":[65535,34899,34899,13107]}}, +{"amount":0.5,"gain":0,"in":"#ff000033","out":{"hex":"#ffbaba33","u16":[65535,47824,47824,13107]}}, +{"amount":0.5,"gain":0.5,"in":"#ff000033","out":{"hex":"#ffbaba33","u16":[65535,47824,47824,13107]}}, +{"amount":0.5,"gain":1,"in":"#ff000033","out":{"hex":"#ffbaba33","u16":[65535,47824,47824,13107]}}, +{"amount":0.5,"gain":2,"in":"#ff000033","out":{"hex":"#ffbaba33","u16":[65535,47824,47824,13107]}}, +{"amount":0.75,"gain":0,"in":"#ff000033","out":{"hex":"#ffe0e033","u16":[65535,57502,57502,13107]}}, +{"amount":0.75,"gain":0.5,"in":"#ff000033","out":{"hex":"#ffe0e033","u16":[65535,57502,57502,13107]}}, +{"amount":0.75,"gain":1,"in":"#ff000033","out":{"hex":"#ffe0e033","u16":[65535,57502,57502,13107]}}, +{"amount":0.75,"gain":2,"in":"#ff000033","out":{"hex":"#ffe0e033","u16":[65535,57502,57502,13107]}}, +{"amount":0.9,"gain":0,"in":"#ff000033","out":{"hex":"#fff3f333","u16":[65535,62470,62470,13107]}}, +{"amount":0.9,"gain":0.5,"in":"#ff000033","out":{"hex":"#fff3f333","u16":[65535,62470,62470,13107]}}, +{"amount":0.9,"gain":1,"in":"#ff000033","out":{"hex":"#fff3f333","u16":[65535,62470,62470,13107]}}, +{"amount":0.9,"gain":2,"in":"#ff000033","out":{"hex":"#fff3f333","u16":[65535,62470,62470,13107]}}, +{"amount":1,"gain":0,"in":"#ff000033","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"amount":1,"gain":0.5,"in":"#ff000033","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"amount":1,"gain":1,"in":"#ff000033","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"amount":1,"gain":2,"in":"#ff000033","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"amount":1.5,"gain":0,"in":"#ff000033","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"amount":1.5,"gain":0.5,"in":"#ff000033","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"amount":1.5,"gain":1,"in":"#ff000033","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"amount":1.5,"gain":2,"in":"#ff000033","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}} +], +"darken": [ +{"amount":-0.5,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.25,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.5,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.75,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.9,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0.5,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":2,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#ffffffff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ffffffff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":0.1,"gain":1,"in":"#ffffffff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":0.1,"gain":2,"in":"#ffffffff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"amount":0.25,"gain":0,"in":"#ffffffff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ffffffff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.25,"gain":1,"in":"#ffffffff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.25,"gain":2,"in":"#ffffffff","out":{"hex":"#e0e0e0ff","u16":[57502,57502,57502,65535]}}, +{"amount":0.5,"gain":0,"in":"#ffffffff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ffffffff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.5,"gain":1,"in":"#ffffffff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#ffffffff","out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#ffffffff","out":{"hex":"#888888ff","u16":[34899,34899,34899,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ffffffff","out":{"hex":"#888888ff","u16":[34899,34899,34899,65535]}}, +{"amount":0.75,"gain":1,"in":"#ffffffff","out":{"hex":"#888888ff","u16":[34899,34899,34899,65535]}}, +{"amount":0.75,"gain":2,"in":"#ffffffff","out":{"hex":"#888888ff","u16":[34899,34899,34899,65535]}}, +{"amount":0.9,"gain":0,"in":"#ffffffff","out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ffffffff","out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"amount":0.9,"gain":1,"in":"#ffffffff","out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"amount":0.9,"gain":2,"in":"#ffffffff","out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"amount":1,"gain":0,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#010101ff","out":{"hex":"#010101ff","u16":[309,309,309,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#010101ff","out":{"hex":"#010101ff","u16":[309,309,309,65535]}}, +{"amount":-0.5,"gain":1,"in":"#010101ff","out":{"hex":"#010101ff","u16":[309,309,309,65535]}}, +{"amount":-0.5,"gain":2,"in":"#010101ff","out":{"hex":"#010101ff","u16":[309,309,309,65535]}}, +{"amount":0,"gain":0,"in":"#010101ff","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"amount":0,"gain":0.5,"in":"#010101ff","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"amount":0,"gain":1,"in":"#010101ff","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"amount":0,"gain":2,"in":"#010101ff","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"amount":0.1,"gain":0,"in":"#010101ff","out":{"hex":"#010101ff","u16":[245,245,245,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#010101ff","out":{"hex":"#010101ff","u16":[245,245,245,65535]}}, +{"amount":0.1,"gain":1,"in":"#010101ff","out":{"hex":"#010101ff","u16":[245,245,245,65535]}}, +{"amount":0.1,"gain":2,"in":"#010101ff","out":{"hex":"#010101ff","u16":[245,245,245,65535]}}, +{"amount":0.25,"gain":0,"in":"#010101ff","out":{"hex":"#010101ff","u16":[225,225,225,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#010101ff","out":{"hex":"#010101ff","u16":[225,225,225,65535]}}, +{"amount":0.25,"gain":1,"in":"#010101ff","out":{"hex":"#010101ff","u16":[225,225,225,65535]}}, +{"amount":0.25,"gain":2,"in":"#010101ff","out":{"hex":"#010101ff","u16":[225,225,225,65535]}}, +{"amount":0.5,"gain":0,"in":"#010101ff","out":{"hex":"#010101ff","u16":[188,188,188,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#010101ff","out":{"hex":"#010101ff","u16":[188,188,188,65535]}}, +{"amount":0.5,"gain":1,"in":"#010101ff","out":{"hex":"#010101ff","u16":[188,188,188,65535]}}, +{"amount":0.5,"gain":2,"in":"#010101ff","out":{"hex":"#010101ff","u16":[188,188,188,65535]}}, +{"amount":0.75,"gain":0,"in":"#010101ff","out":{"hex":"#010101ff","u16":[137,137,137,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#010101ff","out":{"hex":"#010101ff","u16":[137,137,137,65535]}}, +{"amount":0.75,"gain":1,"in":"#010101ff","out":{"hex":"#010101ff","u16":[137,137,137,65535]}}, +{"amount":0.75,"gain":2,"in":"#010101ff","out":{"hex":"#010101ff","u16":[137,137,137,65535]}}, +{"amount":0.9,"gain":0,"in":"#010101ff","out":{"hex":"#000000ff","u16":[90,90,90,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#010101ff","out":{"hex":"#000000ff","u16":[90,90,90,65535]}}, +{"amount":0.9,"gain":1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[90,90,90,65535]}}, +{"amount":0.9,"gain":2,"in":"#010101ff","out":{"hex":"#000000ff","u16":[90,90,90,65535]}}, +{"amount":1,"gain":0,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#020202ff","out":{"hex":"#020202ff","u16":[618,618,618,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#020202ff","out":{"hex":"#020202ff","u16":[618,618,618,65535]}}, +{"amount":-0.5,"gain":1,"in":"#020202ff","out":{"hex":"#020202ff","u16":[618,618,618,65535]}}, +{"amount":-0.5,"gain":2,"in":"#020202ff","out":{"hex":"#020202ff","u16":[618,618,618,65535]}}, +{"amount":0,"gain":0,"in":"#020202ff","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"amount":0,"gain":0.5,"in":"#020202ff","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"amount":0,"gain":1,"in":"#020202ff","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"amount":0,"gain":2,"in":"#020202ff","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"amount":0.1,"gain":0,"in":"#020202ff","out":{"hex":"#020202ff","u16":[490,490,490,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#020202ff","out":{"hex":"#020202ff","u16":[490,490,490,65535]}}, +{"amount":0.1,"gain":1,"in":"#020202ff","out":{"hex":"#020202ff","u16":[490,490,490,65535]}}, +{"amount":0.1,"gain":2,"in":"#020202ff","out":{"hex":"#020202ff","u16":[490,490,490,65535]}}, +{"amount":0.25,"gain":0,"in":"#020202ff","out":{"hex":"#020202ff","u16":[451,451,451,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#020202ff","out":{"hex":"#020202ff","u16":[451,451,451,65535]}}, +{"amount":0.25,"gain":1,"in":"#020202ff","out":{"hex":"#020202ff","u16":[451,451,451,65535]}}, +{"amount":0.25,"gain":2,"in":"#020202ff","out":{"hex":"#020202ff","u16":[451,451,451,65535]}}, +{"amount":0.5,"gain":0,"in":"#020202ff","out":{"hex":"#010101ff","u16":[375,375,375,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#020202ff","out":{"hex":"#010101ff","u16":[375,375,375,65535]}}, +{"amount":0.5,"gain":1,"in":"#020202ff","out":{"hex":"#010101ff","u16":[375,375,375,65535]}}, +{"amount":0.5,"gain":2,"in":"#020202ff","out":{"hex":"#010101ff","u16":[375,375,375,65535]}}, +{"amount":0.75,"gain":0,"in":"#020202ff","out":{"hex":"#010101ff","u16":[274,274,274,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#020202ff","out":{"hex":"#010101ff","u16":[274,274,274,65535]}}, +{"amount":0.75,"gain":1,"in":"#020202ff","out":{"hex":"#010101ff","u16":[274,274,274,65535]}}, +{"amount":0.75,"gain":2,"in":"#020202ff","out":{"hex":"#010101ff","u16":[274,274,274,65535]}}, +{"amount":0.9,"gain":0,"in":"#020202ff","out":{"hex":"#010101ff","u16":[180,180,180,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#020202ff","out":{"hex":"#010101ff","u16":[180,180,180,65535]}}, +{"amount":0.9,"gain":1,"in":"#020202ff","out":{"hex":"#010101ff","u16":[180,180,180,65535]}}, +{"amount":0.9,"gain":2,"in":"#020202ff","out":{"hex":"#010101ff","u16":[180,180,180,65535]}}, +{"amount":1,"gain":0,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#040404ff","out":{"hex":"#050505ff","u16":[1236,1236,1236,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#040404ff","out":{"hex":"#050505ff","u16":[1236,1236,1236,65535]}}, +{"amount":-0.5,"gain":1,"in":"#040404ff","out":{"hex":"#050505ff","u16":[1236,1236,1236,65535]}}, +{"amount":-0.5,"gain":2,"in":"#040404ff","out":{"hex":"#050505ff","u16":[1236,1236,1236,65535]}}, +{"amount":0,"gain":0,"in":"#040404ff","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"amount":0,"gain":0.5,"in":"#040404ff","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"amount":0,"gain":1,"in":"#040404ff","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"amount":0,"gain":2,"in":"#040404ff","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"amount":0.1,"gain":0,"in":"#040404ff","out":{"hex":"#040404ff","u16":[980,980,980,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#040404ff","out":{"hex":"#040404ff","u16":[980,980,980,65535]}}, +{"amount":0.1,"gain":1,"in":"#040404ff","out":{"hex":"#040404ff","u16":[980,980,980,65535]}}, +{"amount":0.1,"gain":2,"in":"#040404ff","out":{"hex":"#040404ff","u16":[980,980,980,65535]}}, +{"amount":0.25,"gain":0,"in":"#040404ff","out":{"hex":"#040404ff","u16":[902,902,902,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#040404ff","out":{"hex":"#040404ff","u16":[902,902,902,65535]}}, +{"amount":0.25,"gain":1,"in":"#040404ff","out":{"hex":"#040404ff","u16":[902,902,902,65535]}}, +{"amount":0.25,"gain":2,"in":"#040404ff","out":{"hex":"#040404ff","u16":[902,902,902,65535]}}, +{"amount":0.5,"gain":0,"in":"#040404ff","out":{"hex":"#030303ff","u16":[750,750,750,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#040404ff","out":{"hex":"#030303ff","u16":[750,750,750,65535]}}, +{"amount":0.5,"gain":1,"in":"#040404ff","out":{"hex":"#030303ff","u16":[750,750,750,65535]}}, +{"amount":0.5,"gain":2,"in":"#040404ff","out":{"hex":"#030303ff","u16":[750,750,750,65535]}}, +{"amount":0.75,"gain":0,"in":"#040404ff","out":{"hex":"#020202ff","u16":[547,547,547,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#040404ff","out":{"hex":"#020202ff","u16":[547,547,547,65535]}}, +{"amount":0.75,"gain":1,"in":"#040404ff","out":{"hex":"#020202ff","u16":[547,547,547,65535]}}, +{"amount":0.75,"gain":2,"in":"#040404ff","out":{"hex":"#020202ff","u16":[547,547,547,65535]}}, +{"amount":0.9,"gain":0,"in":"#040404ff","out":{"hex":"#010101ff","u16":[361,361,361,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#040404ff","out":{"hex":"#010101ff","u16":[361,361,361,65535]}}, +{"amount":0.9,"gain":1,"in":"#040404ff","out":{"hex":"#010101ff","u16":[361,361,361,65535]}}, +{"amount":0.9,"gain":2,"in":"#040404ff","out":{"hex":"#010101ff","u16":[361,361,361,65535]}}, +{"amount":1,"gain":0,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#080808ff","out":{"hex":"#0a0a0aff","u16":[2472,2472,2472,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#080808ff","out":{"hex":"#0a0a0aff","u16":[2472,2472,2472,65535]}}, +{"amount":-0.5,"gain":1,"in":"#080808ff","out":{"hex":"#0a0a0aff","u16":[2472,2472,2472,65535]}}, +{"amount":-0.5,"gain":2,"in":"#080808ff","out":{"hex":"#0a0a0aff","u16":[2472,2472,2472,65535]}}, +{"amount":0,"gain":0,"in":"#080808ff","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"amount":0,"gain":0.5,"in":"#080808ff","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"amount":0,"gain":1,"in":"#080808ff","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"amount":0,"gain":2,"in":"#080808ff","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"amount":0.1,"gain":0,"in":"#080808ff","out":{"hex":"#080808ff","u16":[1960,1960,1960,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#080808ff","out":{"hex":"#080808ff","u16":[1960,1960,1960,65535]}}, +{"amount":0.1,"gain":1,"in":"#080808ff","out":{"hex":"#080808ff","u16":[1960,1960,1960,65535]}}, +{"amount":0.1,"gain":2,"in":"#080808ff","out":{"hex":"#080808ff","u16":[1960,1960,1960,65535]}}, +{"amount":0.25,"gain":0,"in":"#080808ff","out":{"hex":"#070707ff","u16":[1804,1804,1804,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#080808ff","out":{"hex":"#070707ff","u16":[1804,1804,1804,65535]}}, +{"amount":0.25,"gain":1,"in":"#080808ff","out":{"hex":"#070707ff","u16":[1804,1804,1804,65535]}}, +{"amount":0.25,"gain":2,"in":"#080808ff","out":{"hex":"#070707ff","u16":[1804,1804,1804,65535]}}, +{"amount":0.5,"gain":0,"in":"#080808ff","out":{"hex":"#060606ff","u16":[1500,1500,1500,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#080808ff","out":{"hex":"#060606ff","u16":[1500,1500,1500,65535]}}, +{"amount":0.5,"gain":1,"in":"#080808ff","out":{"hex":"#060606ff","u16":[1500,1500,1500,65535]}}, +{"amount":0.5,"gain":2,"in":"#080808ff","out":{"hex":"#060606ff","u16":[1500,1500,1500,65535]}}, +{"amount":0.75,"gain":0,"in":"#080808ff","out":{"hex":"#040404ff","u16":[1095,1095,1095,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#080808ff","out":{"hex":"#040404ff","u16":[1095,1095,1095,65535]}}, +{"amount":0.75,"gain":1,"in":"#080808ff","out":{"hex":"#040404ff","u16":[1095,1095,1095,65535]}}, +{"amount":0.75,"gain":2,"in":"#080808ff","out":{"hex":"#040404ff","u16":[1095,1095,1095,65535]}}, +{"amount":0.9,"gain":0,"in":"#080808ff","out":{"hex":"#030303ff","u16":[722,722,722,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#080808ff","out":{"hex":"#030303ff","u16":[722,722,722,65535]}}, +{"amount":0.9,"gain":1,"in":"#080808ff","out":{"hex":"#030303ff","u16":[722,722,722,65535]}}, +{"amount":0.9,"gain":2,"in":"#080808ff","out":{"hex":"#030303ff","u16":[722,722,722,65535]}}, +{"amount":1,"gain":0,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#101010ff","out":{"hex":"#131313ff","u16":[4944,4944,4944,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#101010ff","out":{"hex":"#131313ff","u16":[4944,4944,4944,65535]}}, +{"amount":-0.5,"gain":1,"in":"#101010ff","out":{"hex":"#131313ff","u16":[4944,4944,4944,65535]}}, +{"amount":-0.5,"gain":2,"in":"#101010ff","out":{"hex":"#131313ff","u16":[4944,4944,4944,65535]}}, +{"amount":0,"gain":0,"in":"#101010ff","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"amount":0,"gain":0.5,"in":"#101010ff","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"amount":0,"gain":1,"in":"#101010ff","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"amount":0,"gain":2,"in":"#101010ff","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"amount":0.1,"gain":0,"in":"#101010ff","out":{"hex":"#0f0f0fff","u16":[3920,3920,3920,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#101010ff","out":{"hex":"#0f0f0fff","u16":[3920,3920,3920,65535]}}, +{"amount":0.1,"gain":1,"in":"#101010ff","out":{"hex":"#0f0f0fff","u16":[3920,3920,3920,65535]}}, +{"amount":0.1,"gain":2,"in":"#101010ff","out":{"hex":"#0f0f0fff","u16":[3920,3920,3920,65535]}}, +{"amount":0.25,"gain":0,"in":"#101010ff","out":{"hex":"#0e0e0eff","u16":[3608,3608,3608,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#101010ff","out":{"hex":"#0e0e0eff","u16":[3608,3608,3608,65535]}}, +{"amount":0.25,"gain":1,"in":"#101010ff","out":{"hex":"#0e0e0eff","u16":[3608,3608,3608,65535]}}, +{"amount":0.25,"gain":2,"in":"#101010ff","out":{"hex":"#0e0e0eff","u16":[3608,3608,3608,65535]}}, +{"amount":0.5,"gain":0,"in":"#101010ff","out":{"hex":"#0c0c0cff","u16":[3001,3001,3001,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#101010ff","out":{"hex":"#0c0c0cff","u16":[3001,3001,3001,65535]}}, +{"amount":0.5,"gain":1,"in":"#101010ff","out":{"hex":"#0c0c0cff","u16":[3001,3001,3001,65535]}}, +{"amount":0.5,"gain":2,"in":"#101010ff","out":{"hex":"#0c0c0cff","u16":[3001,3001,3001,65535]}}, +{"amount":0.75,"gain":0,"in":"#101010ff","out":{"hex":"#090909ff","u16":[2190,2190,2190,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#101010ff","out":{"hex":"#090909ff","u16":[2190,2190,2190,65535]}}, +{"amount":0.75,"gain":1,"in":"#101010ff","out":{"hex":"#090909ff","u16":[2190,2190,2190,65535]}}, +{"amount":0.75,"gain":2,"in":"#101010ff","out":{"hex":"#090909ff","u16":[2190,2190,2190,65535]}}, +{"amount":0.9,"gain":0,"in":"#101010ff","out":{"hex":"#060606ff","u16":[1444,1444,1444,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#101010ff","out":{"hex":"#060606ff","u16":[1444,1444,1444,65535]}}, +{"amount":0.9,"gain":1,"in":"#101010ff","out":{"hex":"#060606ff","u16":[1444,1444,1444,65535]}}, +{"amount":0.9,"gain":2,"in":"#101010ff","out":{"hex":"#060606ff","u16":[1444,1444,1444,65535]}}, +{"amount":1,"gain":0,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#202020ff","out":{"hex":"#262626ff","u16":[9888,9888,9888,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#202020ff","out":{"hex":"#262626ff","u16":[9888,9888,9888,65535]}}, +{"amount":-0.5,"gain":1,"in":"#202020ff","out":{"hex":"#262626ff","u16":[9888,9888,9888,65535]}}, +{"amount":-0.5,"gain":2,"in":"#202020ff","out":{"hex":"#262626ff","u16":[9888,9888,9888,65535]}}, +{"amount":0,"gain":0,"in":"#202020ff","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"amount":0,"gain":0.5,"in":"#202020ff","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"amount":0,"gain":1,"in":"#202020ff","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"amount":0,"gain":2,"in":"#202020ff","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"amount":0.1,"gain":0,"in":"#202020ff","out":{"hex":"#1f1f1fff","u16":[7839,7839,7839,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#202020ff","out":{"hex":"#1f1f1fff","u16":[7839,7839,7839,65535]}}, +{"amount":0.1,"gain":1,"in":"#202020ff","out":{"hex":"#1f1f1fff","u16":[7839,7839,7839,65535]}}, +{"amount":0.1,"gain":2,"in":"#202020ff","out":{"hex":"#1f1f1fff","u16":[7839,7839,7839,65535]}}, +{"amount":0.25,"gain":0,"in":"#202020ff","out":{"hex":"#1c1c1cff","u16":[7216,7216,7216,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#202020ff","out":{"hex":"#1c1c1cff","u16":[7216,7216,7216,65535]}}, +{"amount":0.25,"gain":1,"in":"#202020ff","out":{"hex":"#1c1c1cff","u16":[7216,7216,7216,65535]}}, +{"amount":0.25,"gain":2,"in":"#202020ff","out":{"hex":"#1c1c1cff","u16":[7216,7216,7216,65535]}}, +{"amount":0.5,"gain":0,"in":"#202020ff","out":{"hex":"#171717ff","u16":[6001,6001,6001,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#202020ff","out":{"hex":"#171717ff","u16":[6001,6001,6001,65535]}}, +{"amount":0.5,"gain":1,"in":"#202020ff","out":{"hex":"#171717ff","u16":[6001,6001,6001,65535]}}, +{"amount":0.5,"gain":2,"in":"#202020ff","out":{"hex":"#171717ff","u16":[6001,6001,6001,65535]}}, +{"amount":0.75,"gain":0,"in":"#202020ff","out":{"hex":"#111111ff","u16":[4379,4379,4379,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#202020ff","out":{"hex":"#111111ff","u16":[4379,4379,4379,65535]}}, +{"amount":0.75,"gain":1,"in":"#202020ff","out":{"hex":"#111111ff","u16":[4379,4379,4379,65535]}}, +{"amount":0.75,"gain":2,"in":"#202020ff","out":{"hex":"#111111ff","u16":[4379,4379,4379,65535]}}, +{"amount":0.9,"gain":0,"in":"#202020ff","out":{"hex":"#0b0b0bff","u16":[2888,2888,2888,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#202020ff","out":{"hex":"#0b0b0bff","u16":[2888,2888,2888,65535]}}, +{"amount":0.9,"gain":1,"in":"#202020ff","out":{"hex":"#0b0b0bff","u16":[2888,2888,2888,65535]}}, +{"amount":0.9,"gain":2,"in":"#202020ff","out":{"hex":"#0b0b0bff","u16":[2888,2888,2888,65535]}}, +{"amount":1,"gain":0,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#404040ff","out":{"hex":"#4d4d4dff","u16":[19777,19777,19777,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#404040ff","out":{"hex":"#4d4d4dff","u16":[19777,19777,19777,65535]}}, +{"amount":-0.5,"gain":1,"in":"#404040ff","out":{"hex":"#4d4d4dff","u16":[19777,19777,19777,65535]}}, +{"amount":-0.5,"gain":2,"in":"#404040ff","out":{"hex":"#4d4d4dff","u16":[19777,19777,19777,65535]}}, +{"amount":0,"gain":0,"in":"#404040ff","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"amount":0,"gain":0.5,"in":"#404040ff","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"amount":0,"gain":1,"in":"#404040ff","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"amount":0,"gain":2,"in":"#404040ff","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"amount":0.1,"gain":0,"in":"#404040ff","out":{"hex":"#3d3d3dff","u16":[15679,15679,15679,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#404040ff","out":{"hex":"#3d3d3dff","u16":[15679,15679,15679,65535]}}, +{"amount":0.1,"gain":1,"in":"#404040ff","out":{"hex":"#3d3d3dff","u16":[15679,15679,15679,65535]}}, +{"amount":0.1,"gain":2,"in":"#404040ff","out":{"hex":"#3d3d3dff","u16":[15679,15679,15679,65535]}}, +{"amount":0.25,"gain":0,"in":"#404040ff","out":{"hex":"#383838ff","u16":[14432,14432,14432,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#404040ff","out":{"hex":"#383838ff","u16":[14432,14432,14432,65535]}}, +{"amount":0.25,"gain":1,"in":"#404040ff","out":{"hex":"#383838ff","u16":[14432,14432,14432,65535]}}, +{"amount":0.25,"gain":2,"in":"#404040ff","out":{"hex":"#383838ff","u16":[14432,14432,14432,65535]}}, +{"amount":0.5,"gain":0,"in":"#404040ff","out":{"hex":"#2f2f2fff","u16":[12003,12003,12003,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#404040ff","out":{"hex":"#2f2f2fff","u16":[12003,12003,12003,65535]}}, +{"amount":0.5,"gain":1,"in":"#404040ff","out":{"hex":"#2f2f2fff","u16":[12003,12003,12003,65535]}}, +{"amount":0.5,"gain":2,"in":"#404040ff","out":{"hex":"#2f2f2fff","u16":[12003,12003,12003,65535]}}, +{"amount":0.75,"gain":0,"in":"#404040ff","out":{"hex":"#222222ff","u16":[8759,8759,8759,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#404040ff","out":{"hex":"#222222ff","u16":[8759,8759,8759,65535]}}, +{"amount":0.75,"gain":1,"in":"#404040ff","out":{"hex":"#222222ff","u16":[8759,8759,8759,65535]}}, +{"amount":0.75,"gain":2,"in":"#404040ff","out":{"hex":"#222222ff","u16":[8759,8759,8759,65535]}}, +{"amount":0.9,"gain":0,"in":"#404040ff","out":{"hex":"#161616ff","u16":[5775,5775,5775,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#404040ff","out":{"hex":"#161616ff","u16":[5775,5775,5775,65535]}}, +{"amount":0.9,"gain":1,"in":"#404040ff","out":{"hex":"#161616ff","u16":[5775,5775,5775,65535]}}, +{"amount":0.9,"gain":2,"in":"#404040ff","out":{"hex":"#161616ff","u16":[5775,5775,5775,65535]}}, +{"amount":1,"gain":0,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#404040ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#7f7f7fff","out":{"hex":"#999999ff","u16":[39244,39244,39244,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#999999ff","u16":[39244,39244,39244,65535]}}, +{"amount":-0.5,"gain":1,"in":"#7f7f7fff","out":{"hex":"#999999ff","u16":[39244,39244,39244,65535]}}, +{"amount":-0.5,"gain":2,"in":"#7f7f7fff","out":{"hex":"#999999ff","u16":[39244,39244,39244,65535]}}, +{"amount":0,"gain":0,"in":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"gain":1,"in":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"gain":2,"in":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"gain":0,"in":"#7f7f7fff","out":{"hex":"#797979ff","u16":[31113,31113,31113,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#797979ff","u16":[31113,31113,31113,65535]}}, +{"amount":0.1,"gain":1,"in":"#7f7f7fff","out":{"hex":"#797979ff","u16":[31113,31113,31113,65535]}}, +{"amount":0.1,"gain":2,"in":"#7f7f7fff","out":{"hex":"#797979ff","u16":[31113,31113,31113,65535]}}, +{"amount":0.25,"gain":0,"in":"#7f7f7fff","out":{"hex":"#6f6f6fff","u16":[28638,28638,28638,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#6f6f6fff","u16":[28638,28638,28638,65535]}}, +{"amount":0.25,"gain":1,"in":"#7f7f7fff","out":{"hex":"#6f6f6fff","u16":[28638,28638,28638,65535]}}, +{"amount":0.25,"gain":2,"in":"#7f7f7fff","out":{"hex":"#6f6f6fff","u16":[28638,28638,28638,65535]}}, +{"amount":0.5,"gain":0,"in":"#7f7f7fff","out":{"hex":"#5d5d5dff","u16":[23818,23818,23818,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#5d5d5dff","u16":[23818,23818,23818,65535]}}, +{"amount":0.5,"gain":1,"in":"#7f7f7fff","out":{"hex":"#5d5d5dff","u16":[23818,23818,23818,65535]}}, +{"amount":0.5,"gain":2,"in":"#7f7f7fff","out":{"hex":"#5d5d5dff","u16":[23818,23818,23818,65535]}}, +{"amount":0.75,"gain":0,"in":"#7f7f7fff","out":{"hex":"#444444ff","u16":[17381,17381,17381,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#444444ff","u16":[17381,17381,17381,65535]}}, +{"amount":0.75,"gain":1,"in":"#7f7f7fff","out":{"hex":"#444444ff","u16":[17381,17381,17381,65535]}}, +{"amount":0.75,"gain":2,"in":"#7f7f7fff","out":{"hex":"#444444ff","u16":[17381,17381,17381,65535]}}, +{"amount":0.9,"gain":0,"in":"#7f7f7fff","out":{"hex":"#2d2d2dff","u16":[11460,11460,11460,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#2d2d2dff","u16":[11460,11460,11460,65535]}}, +{"amount":0.9,"gain":1,"in":"#7f7f7fff","out":{"hex":"#2d2d2dff","u16":[11460,11460,11460,65535]}}, +{"amount":0.9,"gain":2,"in":"#7f7f7fff","out":{"hex":"#2d2d2dff","u16":[11460,11460,11460,65535]}}, +{"amount":1,"gain":0,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#808080ff","out":{"hex":"#9a9a9aff","u16":[39553,39553,39553,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#808080ff","out":{"hex":"#9a9a9aff","u16":[39553,39553,39553,65535]}}, +{"amount":-0.5,"gain":1,"in":"#808080ff","out":{"hex":"#9a9a9aff","u16":[39553,39553,39553,65535]}}, +{"amount":-0.5,"gain":2,"in":"#808080ff","out":{"hex":"#9a9a9aff","u16":[39553,39553,39553,65535]}}, +{"amount":0,"gain":0,"in":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"gain":0.5,"in":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"gain":1,"in":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"gain":2,"in":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"gain":0,"in":"#808080ff","out":{"hex":"#7a7a7aff","u16":[31358,31358,31358,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#808080ff","out":{"hex":"#7a7a7aff","u16":[31358,31358,31358,65535]}}, +{"amount":0.1,"gain":1,"in":"#808080ff","out":{"hex":"#7a7a7aff","u16":[31358,31358,31358,65535]}}, +{"amount":0.1,"gain":2,"in":"#808080ff","out":{"hex":"#7a7a7aff","u16":[31358,31358,31358,65535]}}, +{"amount":0.25,"gain":0,"in":"#808080ff","out":{"hex":"#707070ff","u16":[28864,28864,28864,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#808080ff","out":{"hex":"#707070ff","u16":[28864,28864,28864,65535]}}, +{"amount":0.25,"gain":1,"in":"#808080ff","out":{"hex":"#707070ff","u16":[28864,28864,28864,65535]}}, +{"amount":0.25,"gain":2,"in":"#808080ff","out":{"hex":"#707070ff","u16":[28864,28864,28864,65535]}}, +{"amount":0.5,"gain":0,"in":"#808080ff","out":{"hex":"#5d5d5dff","u16":[24006,24006,24006,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#808080ff","out":{"hex":"#5d5d5dff","u16":[24006,24006,24006,65535]}}, +{"amount":0.5,"gain":1,"in":"#808080ff","out":{"hex":"#5d5d5dff","u16":[24006,24006,24006,65535]}}, +{"amount":0.5,"gain":2,"in":"#808080ff","out":{"hex":"#5d5d5dff","u16":[24006,24006,24006,65535]}}, +{"amount":0.75,"gain":0,"in":"#808080ff","out":{"hex":"#444444ff","u16":[17518,17518,17518,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#808080ff","out":{"hex":"#444444ff","u16":[17518,17518,17518,65535]}}, +{"amount":0.75,"gain":1,"in":"#808080ff","out":{"hex":"#444444ff","u16":[17518,17518,17518,65535]}}, +{"amount":0.75,"gain":2,"in":"#808080ff","out":{"hex":"#444444ff","u16":[17518,17518,17518,65535]}}, +{"amount":0.9,"gain":0,"in":"#808080ff","out":{"hex":"#2d2d2dff","u16":[11550,11550,11550,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#808080ff","out":{"hex":"#2d2d2dff","u16":[11550,11550,11550,65535]}}, +{"amount":0.9,"gain":1,"in":"#808080ff","out":{"hex":"#2d2d2dff","u16":[11550,11550,11550,65535]}}, +{"amount":0.9,"gain":2,"in":"#808080ff","out":{"hex":"#2d2d2dff","u16":[11550,11550,11550,65535]}}, +{"amount":1,"gain":0,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#818181ff","out":{"hex":"#9b9b9bff","u16":[39862,39862,39862,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#818181ff","out":{"hex":"#9b9b9bff","u16":[39862,39862,39862,65535]}}, +{"amount":-0.5,"gain":1,"in":"#818181ff","out":{"hex":"#9b9b9bff","u16":[39862,39862,39862,65535]}}, +{"amount":-0.5,"gain":2,"in":"#818181ff","out":{"hex":"#9b9b9bff","u16":[39862,39862,39862,65535]}}, +{"amount":0,"gain":0,"in":"#818181ff","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"amount":0,"gain":0.5,"in":"#818181ff","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"amount":0,"gain":1,"in":"#818181ff","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"amount":0,"gain":2,"in":"#818181ff","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"amount":0.1,"gain":0,"in":"#818181ff","out":{"hex":"#7b7b7bff","u16":[31603,31603,31603,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#818181ff","out":{"hex":"#7b7b7bff","u16":[31603,31603,31603,65535]}}, +{"amount":0.1,"gain":1,"in":"#818181ff","out":{"hex":"#7b7b7bff","u16":[31603,31603,31603,65535]}}, +{"amount":0.1,"gain":2,"in":"#818181ff","out":{"hex":"#7b7b7bff","u16":[31603,31603,31603,65535]}}, +{"amount":0.25,"gain":0,"in":"#818181ff","out":{"hex":"#717171ff","u16":[29089,29089,29089,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#818181ff","out":{"hex":"#717171ff","u16":[29089,29089,29089,65535]}}, +{"amount":0.25,"gain":1,"in":"#818181ff","out":{"hex":"#717171ff","u16":[29089,29089,29089,65535]}}, +{"amount":0.25,"gain":2,"in":"#818181ff","out":{"hex":"#717171ff","u16":[29089,29089,29089,65535]}}, +{"amount":0.5,"gain":0,"in":"#818181ff","out":{"hex":"#5e5e5eff","u16":[24193,24193,24193,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#818181ff","out":{"hex":"#5e5e5eff","u16":[24193,24193,24193,65535]}}, +{"amount":0.5,"gain":1,"in":"#818181ff","out":{"hex":"#5e5e5eff","u16":[24193,24193,24193,65535]}}, +{"amount":0.5,"gain":2,"in":"#818181ff","out":{"hex":"#5e5e5eff","u16":[24193,24193,24193,65535]}}, +{"amount":0.75,"gain":0,"in":"#818181ff","out":{"hex":"#454545ff","u16":[17655,17655,17655,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#818181ff","out":{"hex":"#454545ff","u16":[17655,17655,17655,65535]}}, +{"amount":0.75,"gain":1,"in":"#818181ff","out":{"hex":"#454545ff","u16":[17655,17655,17655,65535]}}, +{"amount":0.75,"gain":2,"in":"#818181ff","out":{"hex":"#454545ff","u16":[17655,17655,17655,65535]}}, +{"amount":0.9,"gain":0,"in":"#818181ff","out":{"hex":"#2d2d2dff","u16":[11641,11641,11641,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#818181ff","out":{"hex":"#2d2d2dff","u16":[11641,11641,11641,65535]}}, +{"amount":0.9,"gain":1,"in":"#818181ff","out":{"hex":"#2d2d2dff","u16":[11641,11641,11641,65535]}}, +{"amount":0.9,"gain":2,"in":"#818181ff","out":{"hex":"#2d2d2dff","u16":[11641,11641,11641,65535]}}, +{"amount":1,"gain":0,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#818181ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#bfbfbfff","out":{"hex":"#e6e6e6ff","u16":[59021,59021,59021,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#e6e6e6ff","u16":[59021,59021,59021,65535]}}, +{"amount":-0.5,"gain":1,"in":"#bfbfbfff","out":{"hex":"#e6e6e6ff","u16":[59021,59021,59021,65535]}}, +{"amount":-0.5,"gain":2,"in":"#bfbfbfff","out":{"hex":"#e6e6e6ff","u16":[59021,59021,59021,65535]}}, +{"amount":0,"gain":0,"in":"#bfbfbfff","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"amount":0,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"amount":0,"gain":1,"in":"#bfbfbfff","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"amount":0,"gain":2,"in":"#bfbfbfff","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"amount":0.1,"gain":0,"in":"#bfbfbfff","out":{"hex":"#b6b6b6ff","u16":[46792,46792,46792,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#b6b6b6ff","u16":[46792,46792,46792,65535]}}, +{"amount":0.1,"gain":1,"in":"#bfbfbfff","out":{"hex":"#b6b6b6ff","u16":[46792,46792,46792,65535]}}, +{"amount":0.1,"gain":2,"in":"#bfbfbfff","out":{"hex":"#b6b6b6ff","u16":[46792,46792,46792,65535]}}, +{"amount":0.25,"gain":0,"in":"#bfbfbfff","out":{"hex":"#a8a8a8ff","u16":[43070,43070,43070,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#a8a8a8ff","u16":[43070,43070,43070,65535]}}, +{"amount":0.25,"gain":1,"in":"#bfbfbfff","out":{"hex":"#a8a8a8ff","u16":[43070,43070,43070,65535]}}, +{"amount":0.25,"gain":2,"in":"#bfbfbfff","out":{"hex":"#a8a8a8ff","u16":[43070,43070,43070,65535]}}, +{"amount":0.5,"gain":0,"in":"#bfbfbfff","out":{"hex":"#8b8b8bff","u16":[35821,35821,35821,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#8b8b8bff","u16":[35821,35821,35821,65535]}}, +{"amount":0.5,"gain":1,"in":"#bfbfbfff","out":{"hex":"#8b8b8bff","u16":[35821,35821,35821,65535]}}, +{"amount":0.5,"gain":2,"in":"#bfbfbfff","out":{"hex":"#8b8b8bff","u16":[35821,35821,35821,65535]}}, +{"amount":0.75,"gain":0,"in":"#bfbfbfff","out":{"hex":"#666666ff","u16":[26140,26140,26140,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#666666ff","u16":[26140,26140,26140,65535]}}, +{"amount":0.75,"gain":1,"in":"#bfbfbfff","out":{"hex":"#666666ff","u16":[26140,26140,26140,65535]}}, +{"amount":0.75,"gain":2,"in":"#bfbfbfff","out":{"hex":"#666666ff","u16":[26140,26140,26140,65535]}}, +{"amount":0.9,"gain":0,"in":"#bfbfbfff","out":{"hex":"#434343ff","u16":[17235,17235,17235,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#434343ff","u16":[17235,17235,17235,65535]}}, +{"amount":0.9,"gain":1,"in":"#bfbfbfff","out":{"hex":"#434343ff","u16":[17235,17235,17235,65535]}}, +{"amount":0.9,"gain":2,"in":"#bfbfbfff","out":{"hex":"#434343ff","u16":[17235,17235,17235,65535]}}, +{"amount":1,"gain":0,"in":"#bfbfbfff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#bfbfbfff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#bfbfbfff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#bfbfbfff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#bfbfbfff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#bfbfbfff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#bfbfbfff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#e7e7e7ff","u16":[59330,59330,59330,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#e7e7e7ff","u16":[59330,59330,59330,65535]}}, +{"amount":-0.5,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#e7e7e7ff","u16":[59330,59330,59330,65535]}}, +{"amount":-0.5,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#e7e7e7ff","u16":[59330,59330,59330,65535]}}, +{"amount":0,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"amount":0,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"amount":0,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"amount":0,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"amount":0.1,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#b7b7b7ff","u16":[47037,47037,47037,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#b7b7b7ff","u16":[47037,47037,47037,65535]}}, +{"amount":0.1,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#b7b7b7ff","u16":[47037,47037,47037,65535]}}, +{"amount":0.1,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#b7b7b7ff","u16":[47037,47037,47037,65535]}}, +{"amount":0.25,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#a8a8a8ff","u16":[43296,43296,43296,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#a8a8a8ff","u16":[43296,43296,43296,65535]}}, +{"amount":0.25,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#a8a8a8ff","u16":[43296,43296,43296,65535]}}, +{"amount":0.25,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#a8a8a8ff","u16":[43296,43296,43296,65535]}}, +{"amount":0.5,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#8c8c8cff","u16":[36008,36008,36008,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#8c8c8cff","u16":[36008,36008,36008,65535]}}, +{"amount":0.5,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#8c8c8cff","u16":[36008,36008,36008,65535]}}, +{"amount":0.5,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#8c8c8cff","u16":[36008,36008,36008,65535]}}, +{"amount":0.75,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#666666ff","u16":[26277,26277,26277,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#666666ff","u16":[26277,26277,26277,65535]}}, +{"amount":0.75,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#666666ff","u16":[26277,26277,26277,65535]}}, +{"amount":0.75,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#666666ff","u16":[26277,26277,26277,65535]}}, +{"amount":0.9,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#434343ff","u16":[17326,17326,17326,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#434343ff","u16":[17326,17326,17326,65535]}}, +{"amount":0.9,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#434343ff","u16":[17326,17326,17326,65535]}}, +{"amount":0.9,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#434343ff","u16":[17326,17326,17326,65535]}}, +{"amount":1,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#c0c0c0ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#c0c0c0ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#c0c0c0ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#c0c0c0ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#fdfdfdff","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"amount":0,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"amount":0,"gain":1,"in":"#fdfdfdff","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"amount":0,"gain":2,"in":"#fdfdfdff","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"amount":0.1,"gain":0,"in":"#fdfdfdff","out":{"hex":"#f1f1f1ff","u16":[61980,61980,61980,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#f1f1f1ff","u16":[61980,61980,61980,65535]}}, +{"amount":0.1,"gain":1,"in":"#fdfdfdff","out":{"hex":"#f1f1f1ff","u16":[61980,61980,61980,65535]}}, +{"amount":0.1,"gain":2,"in":"#fdfdfdff","out":{"hex":"#f1f1f1ff","u16":[61980,61980,61980,65535]}}, +{"amount":0.25,"gain":0,"in":"#fdfdfdff","out":{"hex":"#dededeff","u16":[57051,57051,57051,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#dededeff","u16":[57051,57051,57051,65535]}}, +{"amount":0.25,"gain":1,"in":"#fdfdfdff","out":{"hex":"#dededeff","u16":[57051,57051,57051,65535]}}, +{"amount":0.25,"gain":2,"in":"#fdfdfdff","out":{"hex":"#dededeff","u16":[57051,57051,57051,65535]}}, +{"amount":0.5,"gain":0,"in":"#fdfdfdff","out":{"hex":"#b9b9b9ff","u16":[47448,47448,47448,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#b9b9b9ff","u16":[47448,47448,47448,65535]}}, +{"amount":0.5,"gain":1,"in":"#fdfdfdff","out":{"hex":"#b9b9b9ff","u16":[47448,47448,47448,65535]}}, +{"amount":0.5,"gain":2,"in":"#fdfdfdff","out":{"hex":"#b9b9b9ff","u16":[47448,47448,47448,65535]}}, +{"amount":0.75,"gain":0,"in":"#fdfdfdff","out":{"hex":"#878787ff","u16":[34625,34625,34625,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#878787ff","u16":[34625,34625,34625,65535]}}, +{"amount":0.75,"gain":1,"in":"#fdfdfdff","out":{"hex":"#878787ff","u16":[34625,34625,34625,65535]}}, +{"amount":0.75,"gain":2,"in":"#fdfdfdff","out":{"hex":"#878787ff","u16":[34625,34625,34625,65535]}}, +{"amount":0.9,"gain":0,"in":"#fdfdfdff","out":{"hex":"#595959ff","u16":[22830,22830,22830,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#595959ff","u16":[22830,22830,22830,65535]}}, +{"amount":0.9,"gain":1,"in":"#fdfdfdff","out":{"hex":"#595959ff","u16":[22830,22830,22830,65535]}}, +{"amount":0.9,"gain":2,"in":"#fdfdfdff","out":{"hex":"#595959ff","u16":[22830,22830,22830,65535]}}, +{"amount":1,"gain":0,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"amount":0,"gain":0.5,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"amount":0,"gain":1,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"amount":0,"gain":2,"in":"#fefefeff","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"amount":0.1,"gain":0,"in":"#fefefeff","out":{"hex":"#f2f2f2ff","u16":[62225,62225,62225,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fefefeff","out":{"hex":"#f2f2f2ff","u16":[62225,62225,62225,65535]}}, +{"amount":0.1,"gain":1,"in":"#fefefeff","out":{"hex":"#f2f2f2ff","u16":[62225,62225,62225,65535]}}, +{"amount":0.1,"gain":2,"in":"#fefefeff","out":{"hex":"#f2f2f2ff","u16":[62225,62225,62225,65535]}}, +{"amount":0.25,"gain":0,"in":"#fefefeff","out":{"hex":"#dfdfdfff","u16":[57277,57277,57277,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fefefeff","out":{"hex":"#dfdfdfff","u16":[57277,57277,57277,65535]}}, +{"amount":0.25,"gain":1,"in":"#fefefeff","out":{"hex":"#dfdfdfff","u16":[57277,57277,57277,65535]}}, +{"amount":0.25,"gain":2,"in":"#fefefeff","out":{"hex":"#dfdfdfff","u16":[57277,57277,57277,65535]}}, +{"amount":0.5,"gain":0,"in":"#fefefeff","out":{"hex":"#b9b9b9ff","u16":[47636,47636,47636,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fefefeff","out":{"hex":"#b9b9b9ff","u16":[47636,47636,47636,65535]}}, +{"amount":0.5,"gain":1,"in":"#fefefeff","out":{"hex":"#b9b9b9ff","u16":[47636,47636,47636,65535]}}, +{"amount":0.5,"gain":2,"in":"#fefefeff","out":{"hex":"#b9b9b9ff","u16":[47636,47636,47636,65535]}}, +{"amount":0.75,"gain":0,"in":"#fefefeff","out":{"hex":"#878787ff","u16":[34762,34762,34762,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fefefeff","out":{"hex":"#878787ff","u16":[34762,34762,34762,65535]}}, +{"amount":0.75,"gain":1,"in":"#fefefeff","out":{"hex":"#878787ff","u16":[34762,34762,34762,65535]}}, +{"amount":0.75,"gain":2,"in":"#fefefeff","out":{"hex":"#878787ff","u16":[34762,34762,34762,65535]}}, +{"amount":0.9,"gain":0,"in":"#fefefeff","out":{"hex":"#595959ff","u16":[22920,22920,22920,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fefefeff","out":{"hex":"#595959ff","u16":[22920,22920,22920,65535]}}, +{"amount":0.9,"gain":1,"in":"#fefefeff","out":{"hex":"#595959ff","u16":[22920,22920,22920,65535]}}, +{"amount":0.9,"gain":2,"in":"#fefefeff","out":{"hex":"#595959ff","u16":[22920,22920,22920,65535]}}, +{"amount":1,"gain":0,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ff0000ff","out":{"hex":"#989898ff","u16":[38982,38982,38982,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#d38282ff","u16":[54235,33398,33398,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ff0000ff","out":{"hex":"#ff6767ff","u16":[65535,26374,26374,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ff0000ff","out":{"hex":"#ff6767ff","u16":[65535,26374,26374,65535]}}, +{"amount":0,"gain":0,"in":"#ff0000ff","out":{"hex":"#7e7e7eff","u16":[32421,32421,32421,65535]}}, +{"amount":0,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#cb5c5cff","u16":[52203,23659,23659,65535]}}, +{"amount":0,"gain":1,"in":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"gain":2,"in":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#ff0000ff","out":{"hex":"#787878ff","u16":[30904,30904,30904,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#c25858ff","u16":[49762,22552,22552,65535]}}, +{"amount":0.1,"gain":1,"in":"#ff0000ff","out":{"hex":"#f30000ff","u16":[62470,0,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#ff0000ff","out":{"hex":"#f30000ff","u16":[62470,0,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#ff0000ff","out":{"hex":"#6f6f6fff","u16":[28447,28447,28447,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#b25151ff","u16":[45804,20759,20759,65535]}}, +{"amount":0.25,"gain":1,"in":"#ff0000ff","out":{"hex":"#e00000ff","u16":[57502,0,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#ff0000ff","out":{"hex":"#e00000ff","u16":[57502,0,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#ff0000ff","out":{"hex":"#5c5c5cff","u16":[23659,23659,23659,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#944343ff","u16":[38095,17265,17265,65535]}}, +{"amount":0.5,"gain":1,"in":"#ff0000ff","out":{"hex":"#ba0000ff","u16":[47824,0,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#ff0000ff","out":{"hex":"#ba0000ff","u16":[47824,0,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#ff0000ff","out":{"hex":"#434343ff","u16":[17265,17265,17265,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#6c3131ff","u16":[27799,12599,12599,65535]}}, +{"amount":0.75,"gain":1,"in":"#ff0000ff","out":{"hex":"#880000ff","u16":[34899,0,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#ff0000ff","out":{"hex":"#880000ff","u16":[34899,0,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#ff0000ff","out":{"hex":"#2c2c2cff","u16":[11383,11383,11383,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#472020ff","u16":[18329,8307,8307,65535]}}, +{"amount":0.9,"gain":1,"in":"#ff0000ff","out":{"hex":"#5a0000ff","u16":[23011,0,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#ff0000ff","out":{"hex":"#5a0000ff","u16":[23011,0,0,65535]}}, +{"amount":1,"gain":0,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#00ff00ff","out":{"hex":"#dbdbdbff","u16":[56274,56274,56274,65535]}}, +{"amount":0,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#a0eea0ff","u16":[41065,61115,41065,65535]}}, +{"amount":0,"gain":1,"in":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"gain":2,"in":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#00ff00ff","out":{"hex":"#d1d1d1ff","u16":[53642,53642,53642,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#98e398ff","u16":[39145,58257,39145,65535]}}, +{"amount":0.1,"gain":1,"in":"#00ff00ff","out":{"hex":"#00f300ff","u16":[0,62470,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#00ff00ff","out":{"hex":"#00f300ff","u16":[0,62470,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#00ff00ff","out":{"hex":"#c0c0c0ff","u16":[49376,49376,49376,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#8cd18cff","u16":[36031,53624,36031,65535]}}, +{"amount":0.25,"gain":1,"in":"#00ff00ff","out":{"hex":"#00e000ff","u16":[0,57502,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#00ff00ff","out":{"hex":"#00e000ff","u16":[0,57502,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#00ff00ff","out":{"hex":"#a0a0a0ff","u16":[41065,41065,41065,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#75ae75ff","u16":[29967,44598,29967,65535]}}, +{"amount":0.5,"gain":1,"in":"#00ff00ff","out":{"hex":"#00ba00ff","u16":[0,47824,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#00ff00ff","out":{"hex":"#00ba00ff","u16":[0,47824,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#00ff00ff","out":{"hex":"#757575ff","u16":[29967,29967,29967,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#557f55ff","u16":[21868,32545,21868,65535]}}, +{"amount":0.75,"gain":1,"in":"#00ff00ff","out":{"hex":"#008800ff","u16":[0,34899,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#00ff00ff","out":{"hex":"#008800ff","u16":[0,34899,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#00ff00ff","out":{"hex":"#4d4d4dff","u16":[19759,19759,19759,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#385338ff","u16":[14419,21459,14419,65535]}}, +{"amount":0.9,"gain":1,"in":"#00ff00ff","out":{"hex":"#005a00ff","u16":[0,23011,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#00ff00ff","out":{"hex":"#005a00ff","u16":[0,23011,0,65535]}}, +{"amount":1,"gain":0,"in":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#0000ffff","out":{"hex":"#5d5d5dff","u16":[23860,23860,23860,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#0000ffff","out":{"hex":"#4e4ec3ff","u16":[20018,20018,50112,65535]}}, +{"amount":-0.5,"gain":1,"in":"#0000ffff","out":{"hex":"#3a3affff","u16":[14983,14983,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#0000ffff","out":{"hex":"#3a3affff","u16":[14983,14983,65535,65535]}}, +{"amount":0,"gain":0,"in":"#0000ffff","out":{"hex":"#4d4d4dff","u16":[19844,19844,19844,65535]}}, +{"amount":0,"gain":0.5,"in":"#0000ffff","out":{"hex":"#3838c0ff","u16":[14481,14481,49363,65535]}}, +{"amount":0,"gain":1,"in":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"gain":2,"in":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#0000ffff","out":{"hex":"#4a4a4aff","u16":[18916,18916,18916,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#0000ffff","out":{"hex":"#3636b7ff","u16":[13804,13804,47055,65535]}}, +{"amount":0.1,"gain":1,"in":"#0000ffff","out":{"hex":"#0000f3ff","u16":[0,0,62470,65535]}}, +{"amount":0.1,"gain":2,"in":"#0000ffff","out":{"hex":"#0000f3ff","u16":[0,0,62470,65535]}}, +{"amount":0.25,"gain":0,"in":"#0000ffff","out":{"hex":"#444444ff","u16":[17412,17412,17412,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#0000ffff","out":{"hex":"#3131a9ff","u16":[12706,12706,43312,65535]}}, +{"amount":0.25,"gain":1,"in":"#0000ffff","out":{"hex":"#0000e0ff","u16":[0,0,57502,65535]}}, +{"amount":0.25,"gain":2,"in":"#0000ffff","out":{"hex":"#0000e0ff","u16":[0,0,57502,65535]}}, +{"amount":0.5,"gain":0,"in":"#0000ffff","out":{"hex":"#383838ff","u16":[14481,14481,14481,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#0000ffff","out":{"hex":"#29298cff","u16":[10567,10567,36022,65535]}}, +{"amount":0.5,"gain":1,"in":"#0000ffff","out":{"hex":"#0000baff","u16":[0,0,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#0000ffff","out":{"hex":"#0000baff","u16":[0,0,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#0000ffff","out":{"hex":"#292929ff","u16":[10567,10567,10567,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#0000ffff","out":{"hex":"#1e1e66ff","u16":[7711,7711,26287,65535]}}, +{"amount":0.75,"gain":1,"in":"#0000ffff","out":{"hex":"#000088ff","u16":[0,0,34899,65535]}}, +{"amount":0.75,"gain":2,"in":"#0000ffff","out":{"hex":"#000088ff","u16":[0,0,34899,65535]}}, +{"amount":0.9,"gain":0,"in":"#0000ffff","out":{"hex":"#1b1b1bff","u16":[6968,6968,6968,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#0000ffff","out":{"hex":"#141443ff","u16":[5085,5085,17332,65535]}}, +{"amount":0.9,"gain":1,"in":"#0000ffff","out":{"hex":"#00005aff","u16":[0,0,23011,65535]}}, +{"amount":0.9,"gain":2,"in":"#0000ffff","out":{"hex":"#00005aff","u16":[0,0,23011,65535]}}, +{"amount":1,"gain":0,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#ffff00ff","out":{"hex":"#f6f6f6ff","u16":[63340,63340,63340,65535]}}, +{"amount":0,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#fbfbb4ff","u16":[64449,64449,46222,65535]}}, +{"amount":0,"gain":1,"in":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"gain":2,"in":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#ffff00ff","out":{"hex":"#ebebebff","u16":[60378,60378,60378,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#efefabff","u16":[61435,61435,44060,65535]}}, +{"amount":0.1,"gain":1,"in":"#ffff00ff","out":{"hex":"#f3f300ff","u16":[62470,62470,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#ffff00ff","out":{"hex":"#f3f300ff","u16":[62470,62470,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#ffff00ff","out":{"hex":"#d8d8d8ff","u16":[55576,55576,55576,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#dcdc9eff","u16":[56549,56549,40556,65535]}}, +{"amount":0.25,"gain":1,"in":"#ffff00ff","out":{"hex":"#e0e000ff","u16":[57502,57502,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#ffff00ff","out":{"hex":"#e0e000ff","u16":[57502,57502,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#ffff00ff","out":{"hex":"#b4b4b4ff","u16":[46222,46222,46222,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#b7b783ff","u16":[47031,47031,33730,65535]}}, +{"amount":0.5,"gain":1,"in":"#ffff00ff","out":{"hex":"#baba00ff","u16":[47824,47824,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#ffff00ff","out":{"hex":"#baba00ff","u16":[47824,47824,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#ffff00ff","out":{"hex":"#838383ff","u16":[33730,33730,33730,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#868660ff","u16":[34320,34320,24614,65535]}}, +{"amount":0.75,"gain":1,"in":"#ffff00ff","out":{"hex":"#888800ff","u16":[34899,34899,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#ffff00ff","out":{"hex":"#888800ff","u16":[34899,34899,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#ffff00ff","out":{"hex":"#575757ff","u16":[22240,22240,22240,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#58583fff","u16":[22629,22629,16229,65535]}}, +{"amount":0.9,"gain":1,"in":"#ffff00ff","out":{"hex":"#5a5a00ff","u16":[23011,23011,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#ffff00ff","out":{"hex":"#5a5a00ff","u16":[23011,23011,0,65535]}}, +{"amount":1,"gain":0,"in":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#00ffffff","out":{"hex":"#e5e5e5ff","u16":[58788,58788,58788,65535]}}, +{"amount":0,"gain":0.5,"in":"#00ffffff","out":{"hex":"#a7f2f2ff","u16":[42900,62271,62271,65535]}}, +{"amount":0,"gain":1,"in":"#00ffffff","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"amount":0,"gain":2,"in":"#00ffffff","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#00ffffff","out":{"hex":"#dadadaff","u16":[56039,56039,56039,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#00ffffff","out":{"hex":"#9fe7e7ff","u16":[40894,59359,59359,65535]}}, +{"amount":0.1,"gain":1,"in":"#00ffffff","out":{"hex":"#00f3f3ff","u16":[0,62470,62470,65535]}}, +{"amount":0.1,"gain":2,"in":"#00ffffff","out":{"hex":"#00f3f3ff","u16":[0,62470,62470,65535]}}, +{"amount":0.25,"gain":0,"in":"#00ffffff","out":{"hex":"#c9c9c9ff","u16":[51582,51582,51582,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#00ffffff","out":{"hex":"#92d5d5ff","u16":[37642,54638,54638,65535]}}, +{"amount":0.25,"gain":1,"in":"#00ffffff","out":{"hex":"#00e0e0ff","u16":[0,57502,57502,65535]}}, +{"amount":0.25,"gain":2,"in":"#00ffffff","out":{"hex":"#00e0e0ff","u16":[0,57502,57502,65535]}}, +{"amount":0.5,"gain":0,"in":"#00ffffff","out":{"hex":"#a7a7a7ff","u16":[42900,42900,42900,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#00ffffff","out":{"hex":"#7ab1b1ff","u16":[31306,45442,45442,65535]}}, +{"amount":0.5,"gain":1,"in":"#00ffffff","out":{"hex":"#00babaff","u16":[0,47824,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#00ffffff","out":{"hex":"#00babaff","u16":[0,47824,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#00ffffff","out":{"hex":"#7a7a7aff","u16":[31306,31306,31306,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#00ffffff","out":{"hex":"#598181ff","u16":[22845,33161,33161,65535]}}, +{"amount":0.75,"gain":1,"in":"#00ffffff","out":{"hex":"#008888ff","u16":[0,34899,34899,65535]}}, +{"amount":0.75,"gain":2,"in":"#00ffffff","out":{"hex":"#008888ff","u16":[0,34899,34899,65535]}}, +{"amount":0.9,"gain":0,"in":"#00ffffff","out":{"hex":"#505050ff","u16":[20642,20642,20642,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#00ffffff","out":{"hex":"#3b5555ff","u16":[15063,21865,21865,65535]}}, +{"amount":0.9,"gain":1,"in":"#00ffffff","out":{"hex":"#005a5aff","u16":[0,23011,23011,65535]}}, +{"amount":0.9,"gain":2,"in":"#00ffffff","out":{"hex":"#005a5aff","u16":[0,23011,23011,65535]}}, +{"amount":1,"gain":0,"in":"#00ffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#00ffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#00ffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#00ffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#00ffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#00ffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#00ffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#00ffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ff00ffff","out":{"hex":"#adadadff","u16":[44522,44522,44522,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#db96dbff","u16":[56216,38661,56216,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ff00ffff","out":{"hex":"#ff7affff","u16":[65535,31468,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ff00ffff","out":{"hex":"#ff7affff","u16":[65535,31468,65535,65535]}}, +{"amount":0,"gain":0,"in":"#ff00ffff","out":{"hex":"#909090ff","u16":[37029,37029,37029,65535]}}, +{"amount":0,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#d169d1ff","u16":[53594,27021,53594,65535]}}, +{"amount":0,"gain":1,"in":"#ff00ffff","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"amount":0,"gain":2,"in":"#ff00ffff","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#ff00ffff","out":{"hex":"#898989ff","u16":[35297,35297,35297,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#c764c7ff","u16":[51087,25758,51087,65535]}}, +{"amount":0.1,"gain":1,"in":"#ff00ffff","out":{"hex":"#f300f3ff","u16":[62470,0,62470,65535]}}, +{"amount":0.1,"gain":2,"in":"#ff00ffff","out":{"hex":"#f300f3ff","u16":[62470,0,62470,65535]}}, +{"amount":0.25,"gain":0,"in":"#ff00ffff","out":{"hex":"#7e7e7eff","u16":[32490,32490,32490,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#b75cb7ff","u16":[47024,23709,47024,65535]}}, +{"amount":0.25,"gain":1,"in":"#ff00ffff","out":{"hex":"#e000e0ff","u16":[57502,0,57502,65535]}}, +{"amount":0.25,"gain":2,"in":"#ff00ffff","out":{"hex":"#e000e0ff","u16":[57502,0,57502,65535]}}, +{"amount":0.5,"gain":0,"in":"#ff00ffff","out":{"hex":"#696969ff","u16":[27021,27021,27021,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#984d98ff","u16":[39109,19718,39109,65535]}}, +{"amount":0.5,"gain":1,"in":"#ff00ffff","out":{"hex":"#ba00baff","u16":[47824,0,47824,65535]}}, +{"amount":0.5,"gain":2,"in":"#ff00ffff","out":{"hex":"#ba00baff","u16":[47824,0,47824,65535]}}, +{"amount":0.75,"gain":0,"in":"#ff00ffff","out":{"hex":"#4d4d4dff","u16":[19718,19718,19718,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#6f386fff","u16":[28540,14389,28540,65535]}}, +{"amount":0.75,"gain":1,"in":"#ff00ffff","out":{"hex":"#880088ff","u16":[34899,0,34899,65535]}}, +{"amount":0.75,"gain":2,"in":"#ff00ffff","out":{"hex":"#880088ff","u16":[34899,0,34899,65535]}}, +{"amount":0.9,"gain":0,"in":"#ff00ffff","out":{"hex":"#333333ff","u16":[13001,13001,13001,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#492549ff","u16":[18818,9488,18818,65535]}}, +{"amount":0.9,"gain":1,"in":"#ff00ffff","out":{"hex":"#5a005aff","u16":[23011,0,23011,65535]}}, +{"amount":0.9,"gain":2,"in":"#ff00ffff","out":{"hex":"#5a005aff","u16":[23011,0,23011,65535]}}, +{"amount":1,"gain":0,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#ff00ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#800000ff","out":{"hex":"#4c4c4cff","u16":[19567,19567,19567,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#800000ff","out":{"hex":"#7b3838ff","u16":[31507,14279,14279,65535]}}, +{"amount":-0.5,"gain":1,"in":"#800000ff","out":{"hex":"#9a0000ff","u16":[39553,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#800000ff","out":{"hex":"#9a0000ff","u16":[39553,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#800000ff","out":{"hex":"#3f3f3fff","u16":[16274,16274,16274,65535]}}, +{"amount":0,"gain":0.5,"in":"#800000ff","out":{"hex":"#662e2eff","u16":[26204,11876,11876,65535]}}, +{"amount":0,"gain":1,"in":"#800000ff","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"amount":0,"gain":2,"in":"#800000ff","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#800000ff","out":{"hex":"#3c3c3cff","u16":[15513,15513,15513,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#800000ff","out":{"hex":"#612c2cff","u16":[24978,11320,11320,65535]}}, +{"amount":0.1,"gain":1,"in":"#800000ff","out":{"hex":"#7a0000ff","u16":[31358,0,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#800000ff","out":{"hex":"#7a0000ff","u16":[31358,0,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#800000ff","out":{"hex":"#383838ff","u16":[14279,14279,14279,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#800000ff","out":{"hex":"#592929ff","u16":[22992,10420,10420,65535]}}, +{"amount":0.25,"gain":1,"in":"#800000ff","out":{"hex":"#700000ff","u16":[28864,0,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#800000ff","out":{"hex":"#700000ff","u16":[28864,0,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#800000ff","out":{"hex":"#2e2e2eff","u16":[11876,11876,11876,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#800000ff","out":{"hex":"#4a2222ff","u16":[19122,8666,8666,65535]}}, +{"amount":0.5,"gain":1,"in":"#800000ff","out":{"hex":"#5d0000ff","u16":[24006,0,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#800000ff","out":{"hex":"#5d0000ff","u16":[24006,0,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#800000ff","out":{"hex":"#222222ff","u16":[8666,8666,8666,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#800000ff","out":{"hex":"#361919ff","u16":[13954,6324,6324,65535]}}, +{"amount":0.75,"gain":1,"in":"#800000ff","out":{"hex":"#440000ff","u16":[17518,0,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#800000ff","out":{"hex":"#440000ff","u16":[17518,0,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#800000ff","out":{"hex":"#161616ff","u16":[5714,5714,5714,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#800000ff","out":{"hex":"#241010ff","u16":[9201,4170,4170,65535]}}, +{"amount":0.9,"gain":1,"in":"#800000ff","out":{"hex":"#2d0000ff","u16":[11550,0,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#800000ff","out":{"hex":"#2d0000ff","u16":[11550,0,0,65535]}}, +{"amount":1,"gain":0,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#800000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#008000ff","out":{"hex":"#848484ff","u16":[33964,33964,33964,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#008000ff","out":{"hex":"#609060ff","u16":[24785,36886,24785,65535]}}, +{"amount":-0.5,"gain":1,"in":"#008000ff","out":{"hex":"#009a00ff","u16":[0,39553,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#008000ff","out":{"hex":"#009a00ff","u16":[0,39553,0,65535]}}, +{"amount":0,"gain":0,"in":"#008000ff","out":{"hex":"#6e6e6eff","u16":[28247,28247,28247,65535]}}, +{"amount":0,"gain":0.5,"in":"#008000ff","out":{"hex":"#507750ff","u16":[20613,30677,20613,65535]}}, +{"amount":0,"gain":1,"in":"#008000ff","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"amount":0,"gain":2,"in":"#008000ff","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#008000ff","out":{"hex":"#696969ff","u16":[26926,26926,26926,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#008000ff","out":{"hex":"#4c724cff","u16":[19649,29243,19649,65535]}}, +{"amount":0.1,"gain":1,"in":"#008000ff","out":{"hex":"#007a00ff","u16":[0,31358,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#008000ff","out":{"hex":"#007a00ff","u16":[0,31358,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#008000ff","out":{"hex":"#606060ff","u16":[24785,24785,24785,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#008000ff","out":{"hex":"#466946ff","u16":[18086,26917,18086,65535]}}, +{"amount":0.25,"gain":1,"in":"#008000ff","out":{"hex":"#007000ff","u16":[0,28864,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#008000ff","out":{"hex":"#007000ff","u16":[0,28864,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#008000ff","out":{"hex":"#505050ff","u16":[20613,20613,20613,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#008000ff","out":{"hex":"#3b573bff","u16":[15042,22387,15042,65535]}}, +{"amount":0.5,"gain":1,"in":"#008000ff","out":{"hex":"#005d00ff","u16":[0,24006,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#008000ff","out":{"hex":"#005d00ff","u16":[0,24006,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#008000ff","out":{"hex":"#3b3b3bff","u16":[15042,15042,15042,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#008000ff","out":{"hex":"#2b402bff","u16":[10977,16336,10977,65535]}}, +{"amount":0.75,"gain":1,"in":"#008000ff","out":{"hex":"#004400ff","u16":[0,17518,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#008000ff","out":{"hex":"#004400ff","u16":[0,17518,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#008000ff","out":{"hex":"#272727ff","u16":[9918,9918,9918,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#008000ff","out":{"hex":"#1c2a1cff","u16":[7238,10771,7238,65535]}}, +{"amount":0.9,"gain":1,"in":"#008000ff","out":{"hex":"#002d00ff","u16":[0,11550,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#008000ff","out":{"hex":"#002d00ff","u16":[0,11550,0,65535]}}, +{"amount":1,"gain":0,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#008000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#000080ff","out":{"hex":"#2f2f2fff","u16":[11977,11977,11977,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#000080ff","out":{"hex":"#222274ff","u16":[8740,8740,29793,65535]}}, +{"amount":-0.5,"gain":1,"in":"#000080ff","out":{"hex":"#00009aff","u16":[0,0,39553,65535]}}, +{"amount":-0.5,"gain":2,"in":"#000080ff","out":{"hex":"#00009aff","u16":[0,0,39553,65535]}}, +{"amount":0,"gain":0,"in":"#000080ff","out":{"hex":"#272727ff","u16":[9961,9961,9961,65535]}}, +{"amount":0,"gain":0.5,"in":"#000080ff","out":{"hex":"#1c1c60ff","u16":[7269,7269,24778,65535]}}, +{"amount":0,"gain":1,"in":"#000080ff","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"amount":0,"gain":2,"in":"#000080ff","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"amount":0.1,"gain":0,"in":"#000080ff","out":{"hex":"#252525ff","u16":[9495,9495,9495,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#000080ff","out":{"hex":"#1b1b5cff","u16":[6929,6929,23620,65535]}}, +{"amount":0.1,"gain":1,"in":"#000080ff","out":{"hex":"#00007aff","u16":[0,0,31358,65535]}}, +{"amount":0.1,"gain":2,"in":"#000080ff","out":{"hex":"#00007aff","u16":[0,0,31358,65535]}}, +{"amount":0.25,"gain":0,"in":"#000080ff","out":{"hex":"#222222ff","u16":[8740,8740,8740,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#000080ff","out":{"hex":"#191955ff","u16":[6378,6378,21741,65535]}}, +{"amount":0.25,"gain":1,"in":"#000080ff","out":{"hex":"#000070ff","u16":[0,0,28864,65535]}}, +{"amount":0.25,"gain":2,"in":"#000080ff","out":{"hex":"#000070ff","u16":[0,0,28864,65535]}}, +{"amount":0.5,"gain":0,"in":"#000080ff","out":{"hex":"#1c1c1cff","u16":[7269,7269,7269,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#000080ff","out":{"hex":"#151546ff","u16":[5304,5304,18082,65535]}}, +{"amount":0.5,"gain":1,"in":"#000080ff","out":{"hex":"#00005dff","u16":[0,0,24006,65535]}}, +{"amount":0.5,"gain":2,"in":"#000080ff","out":{"hex":"#00005dff","u16":[0,0,24006,65535]}}, +{"amount":0.75,"gain":0,"in":"#000080ff","out":{"hex":"#151515ff","u16":[5304,5304,5304,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#000080ff","out":{"hex":"#0f0f33ff","u16":[3871,3871,13195,65535]}}, +{"amount":0.75,"gain":1,"in":"#000080ff","out":{"hex":"#000044ff","u16":[0,0,17518,65535]}}, +{"amount":0.75,"gain":2,"in":"#000080ff","out":{"hex":"#000044ff","u16":[0,0,17518,65535]}}, +{"amount":0.9,"gain":0,"in":"#000080ff","out":{"hex":"#0e0e0eff","u16":[3497,3497,3497,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#000080ff","out":{"hex":"#0a0a22ff","u16":[2552,2552,8700,65535]}}, +{"amount":0.9,"gain":1,"in":"#000080ff","out":{"hex":"#00002dff","u16":[0,0,11550,65535]}}, +{"amount":0.9,"gain":2,"in":"#000080ff","out":{"hex":"#00002dff","u16":[0,0,11550,65535]}}, +{"amount":1,"gain":0,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#ff8080ff","out":{"hex":"#c7c7c7ff","u16":[51088,51088,51088,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#e5bebeff","u16":[58845,48736,48736,65535]}}, +{"amount":-0.5,"gain":1,"in":"#ff8080ff","out":{"hex":"#ffb4b4ff","u16":[65535,46239,46239,65535]}}, +{"amount":-0.5,"gain":2,"in":"#ff8080ff","out":{"hex":"#ffb4b4ff","u16":[65535,46239,46239,65535]}}, +{"amount":0,"gain":0,"in":"#ff8080ff","out":{"hex":"#a5a5a5ff","u16":[42489,42489,42489,65535]}}, +{"amount":0,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#d89494ff","u16":[55463,38057,38057,65535]}}, +{"amount":0,"gain":1,"in":"#ff8080ff","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"amount":0,"gain":2,"in":"#ff8080ff","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"amount":0.1,"gain":0,"in":"#ff8080ff","out":{"hex":"#9e9e9eff","u16":[40502,40502,40502,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#d58a8aff","u16":[54756,35448,35448,65535]}}, +{"amount":0.1,"gain":1,"in":"#ff8080ff","out":{"hex":"#ff7272ff","u16":[65535,29332,29332,65535]}}, +{"amount":0.1,"gain":2,"in":"#ff8080ff","out":{"hex":"#ff7272ff","u16":[65535,29332,29332,65535]}}, +{"amount":0.25,"gain":0,"in":"#ff8080ff","out":{"hex":"#919191ff","u16":[37281,37281,37281,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#d17979ff","u16":[53675,31034,31034,65535]}}, +{"amount":0.25,"gain":1,"in":"#ff8080ff","out":{"hex":"#ff5858ff","u16":[65535,22709,22709,65535]}}, +{"amount":0.25,"gain":2,"in":"#ff8080ff","out":{"hex":"#ff5858ff","u16":[65535,22709,22709,65535]}}, +{"amount":0.5,"gain":0,"in":"#ff8080ff","out":{"hex":"#797979ff","u16":[31006,31006,31006,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#c25858ff","u16":[49925,22626,22626,65535]}}, +{"amount":0.5,"gain":1,"in":"#ff8080ff","out":{"hex":"#f40000ff","u16":[62676,0,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#ff8080ff","out":{"hex":"#f40000ff","u16":[62676,0,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#ff8080ff","out":{"hex":"#585858ff","u16":[22626,22626,22626,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#8e4040ff","u16":[36432,16511,16511,65535]}}, +{"amount":0.75,"gain":1,"in":"#ff8080ff","out":{"hex":"#b20000ff","u16":[45737,0,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#ff8080ff","out":{"hex":"#b20000ff","u16":[45737,0,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#ff8080ff","out":{"hex":"#3a3a3aff","u16":[14919,14919,14919,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#5d2a2aff","u16":[24022,10887,10887,65535]}}, +{"amount":0.9,"gain":1,"in":"#ff8080ff","out":{"hex":"#750000ff","u16":[30157,0,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#ff8080ff","out":{"hex":"#750000ff","u16":[30157,0,0,65535]}}, +{"amount":1,"gain":0,"in":"#ff8080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#ff8080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#ff8080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#ff8080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#ff8080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#ff8080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#ff8080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#80ff80ff","out":{"hex":"#e3e3e3ff","u16":[58458,58458,58458,65535]}}, +{"amount":0,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#baf2baff","u16":[47763,62118,47763,65535]}}, +{"amount":0,"gain":1,"in":"#80ff80ff","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"amount":0,"gain":2,"in":"#80ff80ff","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"amount":0.1,"gain":0,"in":"#80ff80ff","out":{"hex":"#d9d9d9ff","u16":[55725,55725,55725,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#9eeb9eff","u16":[40665,60519,40665,65535]}}, +{"amount":0.1,"gain":1,"in":"#80ff80ff","out":{"hex":"#00fd00ff","u16":[0,64896,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#80ff80ff","out":{"hex":"#00fd00ff","u16":[0,64896,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#80ff80ff","out":{"hex":"#c8c8c8ff","u16":[51293,51293,51293,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#92d992ff","u16":[37430,55706,37430,65535]}}, +{"amount":0.25,"gain":1,"in":"#80ff80ff","out":{"hex":"#00e800ff","u16":[0,59735,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#80ff80ff","out":{"hex":"#00e800ff","u16":[0,59735,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#80ff80ff","out":{"hex":"#a6a6a6ff","u16":[42659,42659,42659,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#79b479ff","u16":[31130,46330,31130,65535]}}, +{"amount":0.5,"gain":1,"in":"#80ff80ff","out":{"hex":"#00c100ff","u16":[0,49680,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#80ff80ff","out":{"hex":"#00c100ff","u16":[0,49680,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#80ff80ff","out":{"hex":"#797979ff","u16":[31130,31130,31130,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#588458ff","u16":[22717,33809,22717,65535]}}, +{"amount":0.75,"gain":1,"in":"#80ff80ff","out":{"hex":"#008d00ff","u16":[0,36254,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#80ff80ff","out":{"hex":"#008d00ff","u16":[0,36254,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#80ff80ff","out":{"hex":"#505050ff","u16":[20526,20526,20526,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#3a573aff","u16":[14979,22292,14979,65535]}}, +{"amount":0.9,"gain":1,"in":"#80ff80ff","out":{"hex":"#005d00ff","u16":[0,23904,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#80ff80ff","out":{"hex":"#005d00ff","u16":[0,23904,0,65535]}}, +{"amount":1,"gain":0,"in":"#80ff80ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#80ff80ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#80ff80ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#80ff80ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#80ff80ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#80ff80ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#80ff80ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#8080ffff","out":{"hex":"#abababff","u16":[43882,43882,43882,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#8080ffff","out":{"hex":"#a6a6daff","u16":[42766,42766,55976,65535]}}, +{"amount":-0.5,"gain":1,"in":"#8080ffff","out":{"hex":"#a2a2ffff","u16":[41613,41613,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#8080ffff","out":{"hex":"#a2a2ffff","u16":[41613,41613,65535,65535]}}, +{"amount":0,"gain":0,"in":"#8080ffff","out":{"hex":"#8e8e8eff","u16":[36496,36496,36496,65535]}}, +{"amount":0,"gain":0.5,"in":"#8080ffff","out":{"hex":"#8787d0ff","u16":[34752,34752,53424,65535]}}, +{"amount":0,"gain":1,"in":"#8080ffff","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"amount":0,"gain":2,"in":"#8080ffff","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#8080ffff","out":{"hex":"#878787ff","u16":[34790,34790,34790,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#8080ffff","out":{"hex":"#8080ceff","u16":[32863,32863,52896,65535]}}, +{"amount":0.1,"gain":1,"in":"#8080ffff","out":{"hex":"#7878ffff","u16":[30790,30790,65535,65535]}}, +{"amount":0.1,"gain":2,"in":"#8080ffff","out":{"hex":"#7878ffff","u16":[30790,30790,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#8080ffff","out":{"hex":"#7d7d7dff","u16":[32023,32023,32023,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#8080ffff","out":{"hex":"#7474cbff","u16":[29756,29756,52091,65535]}}, +{"amount":0.25,"gain":1,"in":"#8080ffff","out":{"hex":"#6a6affff","u16":[27260,27260,65535,65535]}}, +{"amount":0.25,"gain":2,"in":"#8080ffff","out":{"hex":"#6a6affff","u16":[27260,27260,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#8080ffff","out":{"hex":"#686868ff","u16":[26633,26633,26633,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#8080ffff","out":{"hex":"#5b5bc5ff","u16":[23465,23465,50717,65535]}}, +{"amount":0.5,"gain":1,"in":"#8080ffff","out":{"hex":"#4d4dffff","u16":[19674,19674,65535,65535]}}, +{"amount":0.5,"gain":2,"in":"#8080ffff","out":{"hex":"#4d4dffff","u16":[19674,19674,65535,65535]}}, +{"amount":0.75,"gain":0,"in":"#8080ffff","out":{"hex":"#4c4c4cff","u16":[19435,19435,19435,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#8080ffff","out":{"hex":"#3737bcff","u16":[14183,14183,48346,65535]}}, +{"amount":0.75,"gain":1,"in":"#8080ffff","out":{"hex":"#0000faff","u16":[0,0,64185,65535]}}, +{"amount":0.75,"gain":2,"in":"#8080ffff","out":{"hex":"#0000faff","u16":[0,0,64185,65535]}}, +{"amount":0.9,"gain":0,"in":"#8080ffff","out":{"hex":"#323232ff","u16":[12815,12815,12815,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#8080ffff","out":{"hex":"#24247cff","u16":[9351,9351,31877,65535]}}, +{"amount":0.9,"gain":1,"in":"#8080ffff","out":{"hex":"#0000a5ff","u16":[0,0,42320,65535]}}, +{"amount":0.9,"gain":2,"in":"#8080ffff","out":{"hex":"#0000a5ff","u16":[0,0,42320,65535]}}, +{"amount":1,"gain":0,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#8080ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#030201ff","out":{"hex":"#030303ff","u16":[684,684,684,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#030201ff","out":{"hex":"#030302ff","u16":[816,652,537,65535]}}, +{"amount":-0.5,"gain":1,"in":"#030201ff","out":{"hex":"#040201ff","u16":[927,618,309,65535]}}, +{"amount":-0.5,"gain":2,"in":"#030201ff","out":{"hex":"#040200ff","u16":[969,603,0,65535]}}, +{"amount":0,"gain":0,"in":"#030201ff","out":{"hex":"#020202ff","u16":[569,569,569,65535]}}, +{"amount":0,"gain":0.5,"in":"#030201ff","out":{"hex":"#030202ff","u16":[679,542,447,65535]}}, +{"amount":0,"gain":1,"in":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"gain":2,"in":"#030201ff","out":{"hex":"#030200ff","u16":[806,502,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#030201ff","out":{"hex":"#020202ff","u16":[542,542,542,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#030201ff","out":{"hex":"#030202ff","u16":[647,517,426,65535]}}, +{"amount":0.1,"gain":1,"in":"#030201ff","out":{"hex":"#030201ff","u16":[735,490,245,65535]}}, +{"amount":0.1,"gain":2,"in":"#030201ff","out":{"hex":"#030200ff","u16":[768,478,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#030201ff","out":{"hex":"#020202ff","u16":[499,499,499,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#030201ff","out":{"hex":"#020202ff","u16":[596,476,392,65535]}}, +{"amount":0.25,"gain":1,"in":"#030201ff","out":{"hex":"#030201ff","u16":[676,451,225,65535]}}, +{"amount":0.25,"gain":2,"in":"#030201ff","out":{"hex":"#030200ff","u16":[707,440,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#030201ff","out":{"hex":"#020202ff","u16":[415,415,415,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#030201ff","out":{"hex":"#020201ff","u16":[495,396,326,65535]}}, +{"amount":0.5,"gain":1,"in":"#030201ff","out":{"hex":"#020101ff","u16":[563,375,188,65535]}}, +{"amount":0.5,"gain":2,"in":"#030201ff","out":{"hex":"#020100ff","u16":[588,366,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#030201ff","out":{"hex":"#010101ff","u16":[303,303,303,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#030201ff","out":{"hex":"#010101ff","u16":[362,289,238,65535]}}, +{"amount":0.75,"gain":1,"in":"#030201ff","out":{"hex":"#020101ff","u16":[411,274,137,65535]}}, +{"amount":0.75,"gain":2,"in":"#030201ff","out":{"hex":"#020100ff","u16":[429,267,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#030201ff","out":{"hex":"#010101ff","u16":[200,200,200,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#030201ff","out":{"hex":"#010101ff","u16":[238,190,157,65535]}}, +{"amount":0.9,"gain":1,"in":"#030201ff","out":{"hex":"#010100ff","u16":[271,180,90,65535]}}, +{"amount":0.9,"gain":2,"in":"#030201ff","out":{"hex":"#010100ff","u16":[283,176,0,65535]}}, +{"amount":1,"gain":0,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#fffefdff","out":{"hex":"#fefefeff","u16":[65314,65314,65314,65535]}}, +{"amount":0,"gain":0.5,"in":"#fffefdff","out":{"hex":"#fffefeff","u16":[65425,65296,65168,65535]}}, +{"amount":0,"gain":1,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"amount":0,"gain":2,"in":"#fffefdff","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"amount":0.1,"gain":0,"in":"#fffefdff","out":{"hex":"#f2f2f2ff","u16":[62260,62260,62260,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fffefdff","out":{"hex":"#f9f1e9ff","u16":[63923,61983,59976,65535]}}, +{"amount":0.1,"gain":1,"in":"#fffefdff","out":{"hex":"#fff0e0ff","u16":[65535,61704,57583,65535]}}, +{"amount":0.1,"gain":2,"in":"#fffefdff","out":{"hex":"#fff0e0ff","u16":[65535,61704,57583,65535]}}, +{"amount":0.25,"gain":0,"in":"#fffefdff","out":{"hex":"#dfdfdfff","u16":[57308,57308,57308,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fffefdff","out":{"hex":"#f0dcc6ff","u16":[61587,56570,50981,65535]}}, +{"amount":0.25,"gain":1,"in":"#fffefdff","out":{"hex":"#ffd9a9ff","u16":[65535,55821,43533,65535]}}, +{"amount":0.25,"gain":2,"in":"#fffefdff","out":{"hex":"#ffd9a9ff","u16":[65535,55821,43533,65535]}}, +{"amount":0.5,"gain":0,"in":"#fffefdff","out":{"hex":"#b9b9b9ff","u16":[47662,47662,47662,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fffefdff","out":{"hex":"#d7b487ff","u16":[55144,46297,34781,65535]}}, +{"amount":0.5,"gain":1,"in":"#fffefdff","out":{"hex":"#f0af00ff","u16":[61569,44881,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#fffefdff","out":{"hex":"#f0af00ff","u16":[61569,44881,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#fffefdff","out":{"hex":"#878787ff","u16":[34781,34781,34781,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fffefdff","out":{"hex":"#9d8363ff","u16":[40241,33785,25381,65535]}}, +{"amount":0.75,"gain":1,"in":"#fffefdff","out":{"hex":"#af7f00ff","u16":[44930,32752,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#fffefdff","out":{"hex":"#af7f00ff","u16":[44930,32752,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#fffefdff","out":{"hex":"#595959ff","u16":[22933,22933,22933,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fffefdff","out":{"hex":"#675741ff","u16":[26533,22276,16735,65535]}}, +{"amount":0.9,"gain":1,"in":"#fffefdff","out":{"hex":"#735400ff","u16":[29625,21595,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#fffefdff","out":{"hex":"#735400ff","u16":[29625,21595,0,65535]}}, +{"amount":1,"gain":0,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#7f8081ff","out":{"hex":"#9a9a9aff","u16":[39510,39510,39510,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#999a9aff","u16":[39378,39532,39687,65535]}}, +{"amount":-0.5,"gain":1,"in":"#7f8081ff","out":{"hex":"#999a9bff","u16":[39244,39553,39862,65535]}}, +{"amount":-0.5,"gain":2,"in":"#7f8081ff","out":{"hex":"#989a9cff","u16":[38976,39596,40211,65535]}}, +{"amount":0,"gain":0,"in":"#7f8081ff","out":{"hex":"#808080ff","u16":[32860,32860,32860,65535]}}, +{"amount":0,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#7f8080ff","u16":[32750,32878,33007,65535]}}, +{"amount":0,"gain":1,"in":"#7f8081ff","out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"amount":0,"gain":2,"in":"#7f8081ff","out":{"hex":"#7e8082ff","u16":[32416,32932,33443,65535]}}, +{"amount":0.1,"gain":0,"in":"#7f8081ff","out":{"hex":"#7a7a7aff","u16":[31324,31324,31324,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#797a7aff","u16":[31218,31341,31464,65535]}}, +{"amount":0.1,"gain":1,"in":"#7f8081ff","out":{"hex":"#797a7bff","u16":[31113,31358,31603,65535]}}, +{"amount":0.1,"gain":2,"in":"#7f8081ff","out":{"hex":"#787a7cff","u16":[30900,31392,31879,65535]}}, +{"amount":0.25,"gain":0,"in":"#7f8081ff","out":{"hex":"#707070ff","u16":[28832,28832,28832,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#707071ff","u16":[28736,28848,28961,65535]}}, +{"amount":0.25,"gain":1,"in":"#7f8081ff","out":{"hex":"#6f7071ff","u16":[28638,28864,29089,65535]}}, +{"amount":0.25,"gain":2,"in":"#7f8081ff","out":{"hex":"#6f7072ff","u16":[28443,28895,29343,65535]}}, +{"amount":0.5,"gain":0,"in":"#7f8081ff","out":{"hex":"#5d5d5dff","u16":[23979,23979,23979,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#5d5d5eff","u16":[23899,23992,24087,65535]}}, +{"amount":0.5,"gain":1,"in":"#7f8081ff","out":{"hex":"#5d5d5eff","u16":[23818,24006,24193,65535]}}, +{"amount":0.5,"gain":2,"in":"#7f8081ff","out":{"hex":"#5c5e5fff","u16":[23655,24032,24404,65535]}}, +{"amount":0.75,"gain":0,"in":"#7f8081ff","out":{"hex":"#444444ff","u16":[17499,17499,17499,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#444444ff","u16":[17440,17508,17577,65535]}}, +{"amount":0.75,"gain":1,"in":"#7f8081ff","out":{"hex":"#444445ff","u16":[17381,17518,17655,65535]}}, +{"amount":0.75,"gain":2,"in":"#7f8081ff","out":{"hex":"#434445ff","u16":[17262,17537,17809,65535]}}, +{"amount":0.9,"gain":0,"in":"#7f8081ff","out":{"hex":"#2d2d2dff","u16":[11538,11538,11538,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#2d2d2dff","u16":[11499,11544,11589,65535]}}, +{"amount":0.9,"gain":1,"in":"#7f8081ff","out":{"hex":"#2d2d2dff","u16":[11460,11550,11641,65535]}}, +{"amount":0.9,"gain":2,"in":"#7f8081ff","out":{"hex":"#2c2d2eff","u16":[11382,11563,11742,65535]}}, +{"amount":1,"gain":0,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#7f8081ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#010000ff","out":{"hex":"#010101ff","u16":[153,153,153,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#010000ff","out":{"hex":"#010000ff","u16":[246,112,112,65535]}}, +{"amount":-0.5,"gain":1,"in":"#010000ff","out":{"hex":"#010000ff","u16":[309,0,0,65535]}}, +{"amount":-0.5,"gain":2,"in":"#010000ff","out":{"hex":"#010000ff","u16":[309,0,0,65535]}}, +{"amount":0,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[127,127,127,65535]}}, +{"amount":0,"gain":0.5,"in":"#010000ff","out":{"hex":"#010000ff","u16":[205,93,93,65535]}}, +{"amount":0,"gain":1,"in":"#010000ff","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"amount":0,"gain":2,"in":"#010000ff","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[121,121,121,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#010000ff","out":{"hex":"#010000ff","u16":[195,88,88,65535]}}, +{"amount":0.1,"gain":1,"in":"#010000ff","out":{"hex":"#010000ff","u16":[245,0,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#010000ff","out":{"hex":"#010000ff","u16":[245,0,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[112,112,112,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#010000ff","out":{"hex":"#010000ff","u16":[180,81,81,65535]}}, +{"amount":0.25,"gain":1,"in":"#010000ff","out":{"hex":"#010000ff","u16":[225,0,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#010000ff","out":{"hex":"#010000ff","u16":[225,0,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[93,93,93,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#010000ff","out":{"hex":"#010000ff","u16":[149,68,68,65535]}}, +{"amount":0.5,"gain":1,"in":"#010000ff","out":{"hex":"#010000ff","u16":[188,0,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#010000ff","out":{"hex":"#010000ff","u16":[188,0,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[68,68,68,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#010000ff","out":{"hex":"#000000ff","u16":[109,49,49,65535]}}, +{"amount":0.75,"gain":1,"in":"#010000ff","out":{"hex":"#010000ff","u16":[137,0,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#010000ff","out":{"hex":"#010000ff","u16":[137,0,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[45,45,45,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#010000ff","out":{"hex":"#000000ff","u16":[72,33,33,65535]}}, +{"amount":0.9,"gain":1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[90,0,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#010000ff","out":{"hex":"#000000ff","u16":[90,0,0,65535]}}, +{"amount":1,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[94,94,94,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#000001ff","out":{"hex":"#000001ff","u16":[68,68,233,65535]}}, +{"amount":-0.5,"gain":1,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,309,65535]}}, +{"amount":-0.5,"gain":2,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,309,65535]}}, +{"amount":0,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[78,78,78,65535]}}, +{"amount":0,"gain":0.5,"in":"#000001ff","out":{"hex":"#000001ff","u16":[57,57,194,65535]}}, +{"amount":0,"gain":1,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"amount":0,"gain":2,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"amount":0.1,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[74,74,74,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#000001ff","out":{"hex":"#000001ff","u16":[54,54,185,65535]}}, +{"amount":0.1,"gain":1,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,245,65535]}}, +{"amount":0.1,"gain":2,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,245,65535]}}, +{"amount":0.25,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[68,68,68,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#000001ff","out":{"hex":"#000001ff","u16":[50,50,170,65535]}}, +{"amount":0.25,"gain":1,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,225,65535]}}, +{"amount":0.25,"gain":2,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,225,65535]}}, +{"amount":0.5,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[57,57,57,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#000001ff","out":{"hex":"#000001ff","u16":[41,41,141,65535]}}, +{"amount":0.5,"gain":1,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,188,65535]}}, +{"amount":0.5,"gain":2,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,188,65535]}}, +{"amount":0.75,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[41,41,41,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#000001ff","out":{"hex":"#000000ff","u16":[30,30,103,65535]}}, +{"amount":0.75,"gain":1,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,137,65535]}}, +{"amount":0.75,"gain":2,"in":"#000001ff","out":{"hex":"#000001ff","u16":[0,0,137,65535]}}, +{"amount":0.9,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[27,27,27,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#000001ff","out":{"hex":"#000000ff","u16":[20,20,68,65535]}}, +{"amount":0.9,"gain":1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,90,65535]}}, +{"amount":0.9,"gain":2,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,90,65535]}}, +{"amount":1,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65462,65462,65462,65535]}}, +{"amount":0,"gain":0.5,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65370,65498,65370,65535]}}, +{"amount":0,"gain":1,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"amount":0,"gain":2,"in":"#fefffeff","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"amount":0.1,"gain":0,"in":"#fefffeff","out":{"hex":"#f3f3f3ff","u16":[62401,62401,62401,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fefffeff","out":{"hex":"#e2f9e2ff","u16":[58176,63991,58176,65535]}}, +{"amount":0.1,"gain":1,"in":"#fefffeff","out":{"hex":"#d0ffd0ff","u16":[53545,65535,53545,65535]}}, +{"amount":0.1,"gain":2,"in":"#fefffeff","out":{"hex":"#d0ffd0ff","u16":[53545,65535,53545,65535]}}, +{"amount":0.25,"gain":0,"in":"#fefffeff","out":{"hex":"#dfdfdfff","u16":[57438,57438,57438,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fefffeff","out":{"hex":"#aef0aeff","u16":[44747,61646,44747,65535]}}, +{"amount":0.25,"gain":1,"in":"#fefffeff","out":{"hex":"#60ff60ff","u16":[24591,65535,24591,65535]}}, +{"amount":0.25,"gain":2,"in":"#fefffeff","out":{"hex":"#60ff60ff","u16":[24591,65535,24591,65535]}}, +{"amount":0.5,"gain":0,"in":"#fefffeff","out":{"hex":"#bababaff","u16":[47770,47770,47770,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fefffeff","out":{"hex":"#88ca88ff","u16":[34860,51880,34860,65535]}}, +{"amount":0.5,"gain":1,"in":"#fefffeff","out":{"hex":"#00d800ff","u16":[0,55632,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#fefffeff","out":{"hex":"#00d800ff","u16":[0,55632,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#fefffeff","out":{"hex":"#888888ff","u16":[34860,34860,34860,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fefffeff","out":{"hex":"#639363ff","u16":[25439,37859,25439,65535]}}, +{"amount":0.75,"gain":1,"in":"#fefffeff","out":{"hex":"#009e00ff","u16":[0,40597,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#fefffeff","out":{"hex":"#009e00ff","u16":[0,40597,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#fefffeff","out":{"hex":"#595959ff","u16":[22985,22985,22985,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fefffeff","out":{"hex":"#416141ff","u16":[16773,24962,16773,65535]}}, +{"amount":0.9,"gain":1,"in":"#fefffeff","out":{"hex":"#006800ff","u16":[0,26768,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#fefffeff","out":{"hex":"#006800ff","u16":[0,26768,0,65535]}}, +{"amount":1,"gain":0,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#3daee9ff","out":{"hex":"#c5c5c5ff","u16":[50566,50566,50566,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#acc8e1ff","u16":[44188,51518,57774,65535]}}, +{"amount":-0.5,"gain":1,"in":"#3daee9ff","out":{"hex":"#8eccf9ff","u16":[36449,52450,64037,65535]}}, +{"amount":-0.5,"gain":2,"in":"#3daee9ff","out":{"hex":"#85cdffff","u16":[34109,52687,65535,65535]}}, +{"amount":0,"gain":0,"in":"#3daee9ff","out":{"hex":"#a4a4a4ff","u16":[42055,42055,42055,65535]}}, +{"amount":0,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#7da9caff","u16":[32234,43411,51893,65535]}}, +{"amount":0,"gain":1,"in":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"gain":2,"in":"#3daee9ff","out":{"hex":"#00aff0ff","u16":[0,45047,61740,65535]}}, +{"amount":0.1,"gain":0,"in":"#3daee9ff","out":{"hex":"#9c9c9cff","u16":[40088,40088,40088,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#78a1c0ff","u16":[30726,41381,49466,65535]}}, +{"amount":0.1,"gain":1,"in":"#3daee9ff","out":{"hex":"#3aa6deff","u16":[14944,42627,57081,65535]}}, +{"amount":0.1,"gain":2,"in":"#3daee9ff","out":{"hex":"#00a7e5ff","u16":[0,42941,58853,65535]}}, +{"amount":0.25,"gain":0,"in":"#3daee9ff","out":{"hex":"#909090ff","u16":[36900,36900,36900,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#6e94b1ff","u16":[28282,38090,45532,65535]}}, +{"amount":0.25,"gain":1,"in":"#3daee9ff","out":{"hex":"#3699ccff","u16":[13755,39237,52541,65535]}}, +{"amount":0.25,"gain":2,"in":"#3daee9ff","out":{"hex":"#009ad3ff","u16":[0,39526,54172,65535]}}, +{"amount":0.5,"gain":0,"in":"#3daee9ff","out":{"hex":"#777777ff","u16":[30689,30689,30689,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#5c7b93ff","u16":[23522,31679,37868,65535]}}, +{"amount":0.5,"gain":1,"in":"#3daee9ff","out":{"hex":"#2d7faaff","u16":[11440,32633,43698,65535]}}, +{"amount":0.5,"gain":2,"in":"#3daee9ff","out":{"hex":"#0080afff","u16":[0,32873,45054,65535]}}, +{"amount":0.75,"gain":0,"in":"#3daee9ff","out":{"hex":"#575757ff","u16":[22395,22395,22395,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#435a6cff","u16":[17165,23117,27634,65535]}}, +{"amount":0.75,"gain":1,"in":"#3daee9ff","out":{"hex":"#205d7cff","u16":[8348,23813,31888,65535]}}, +{"amount":0.75,"gain":2,"in":"#3daee9ff","out":{"hex":"#005d80ff","u16":[0,23989,32878,65535]}}, +{"amount":0.9,"gain":0,"in":"#3daee9ff","out":{"hex":"#393939ff","u16":[14766,14766,14766,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#2c3b47ff","u16":[11318,15242,18220,65535]}}, +{"amount":0.9,"gain":1,"in":"#3daee9ff","out":{"hex":"#153d52ff","u16":[5504,15701,21025,65535]}}, +{"amount":0.9,"gain":2,"in":"#3daee9ff","out":{"hex":"#003e54ff","u16":[0,15817,21678,65535]}}, +{"amount":1,"gain":0,"in":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#232629ff","out":{"hex":"#2d2d2dff","u16":[11624,11624,11624,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#232629ff","out":{"hex":"#2c2d2fff","u16":[11228,11683,12160,65535]}}, +{"amount":-0.5,"gain":1,"in":"#232629ff","out":{"hex":"#2a2e31ff","u16":[10815,11742,12669,65535]}}, +{"amount":-0.5,"gain":2,"in":"#232629ff","out":{"hex":"#272e35ff","u16":[9927,11859,13620,65535]}}, +{"amount":0,"gain":0,"in":"#232629ff","out":{"hex":"#262626ff","u16":[9668,9668,9668,65535]}}, +{"amount":0,"gain":0.5,"in":"#232629ff","out":{"hex":"#242627ff","u16":[9339,9717,10113,65535]}}, +{"amount":0,"gain":1,"in":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"gain":2,"in":"#232629ff","out":{"hex":"#20262cff","u16":[8256,9863,11328,65535]}}, +{"amount":0.1,"gain":0,"in":"#232629ff","out":{"hex":"#242424ff","u16":[9215,9215,9215,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#232629ff","out":{"hex":"#232426ff","u16":[8902,9263,9641,65535]}}, +{"amount":0.1,"gain":1,"in":"#232629ff","out":{"hex":"#212427ff","u16":[8574,9309,10044,65535]}}, +{"amount":0.1,"gain":2,"in":"#232629ff","out":{"hex":"#1f252aff","u16":[7870,9402,10798,65535]}}, +{"amount":0.25,"gain":0,"in":"#232629ff","out":{"hex":"#212121ff","u16":[8483,8483,8483,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#232629ff","out":{"hex":"#202123ff","u16":[8194,8526,8874,65535]}}, +{"amount":0.25,"gain":1,"in":"#232629ff","out":{"hex":"#1f2124ff","u16":[7892,8569,9245,65535]}}, +{"amount":0.25,"gain":2,"in":"#232629ff","out":{"hex":"#1c2227ff","u16":[7244,8654,9939,65535]}}, +{"amount":0.5,"gain":0,"in":"#232629ff","out":{"hex":"#1b1b1bff","u16":[7055,7055,7055,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#232629ff","out":{"hex":"#1b1c1dff","u16":[6815,7091,7380,65535]}}, +{"amount":0.5,"gain":1,"in":"#232629ff","out":{"hex":"#1a1c1eff","u16":[6564,7127,7689,65535]}}, +{"amount":0.5,"gain":2,"in":"#232629ff","out":{"hex":"#171c20ff","u16":[6025,7198,8266,65535]}}, +{"amount":0.75,"gain":0,"in":"#232629ff","out":{"hex":"#141414ff","u16":[5148,5148,5148,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#232629ff","out":{"hex":"#131415ff","u16":[4973,5174,5386,65535]}}, +{"amount":0.75,"gain":1,"in":"#232629ff","out":{"hex":"#131416ff","u16":[4790,5201,5611,65535]}}, +{"amount":0.75,"gain":2,"in":"#232629ff","out":{"hex":"#111417ff","u16":[4396,5252,6032,65535]}}, +{"amount":0.9,"gain":0,"in":"#232629ff","out":{"hex":"#0d0d0dff","u16":[3394,3394,3394,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#232629ff","out":{"hex":"#0d0d0eff","u16":[3279,3412,3551,65535]}}, +{"amount":0.9,"gain":1,"in":"#232629ff","out":{"hex":"#0c0d0eff","u16":[3158,3429,3700,65535]}}, +{"amount":0.9,"gain":2,"in":"#232629ff","out":{"hex":"#0b0d0fff","u16":[2899,3463,3977,65535]}}, +{"amount":1,"gain":0,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#eff0f1ff","out":{"hex":"#f0f0f0ff","u16":[61644,61644,61644,65535]}}, +{"amount":0,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#eff0f0ff","u16":[61534,61662,61791,65535]}}, +{"amount":0,"gain":1,"in":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"gain":2,"in":"#eff0f1ff","out":{"hex":"#eef0f2ff","u16":[61201,61716,62228,65535]}}, +{"amount":0.1,"gain":0,"in":"#eff0f1ff","out":{"hex":"#e5e5e5ff","u16":[58761,58761,58761,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#e4e5e6ff","u16":[58563,58794,59024,65535]}}, +{"amount":0.1,"gain":1,"in":"#eff0f1ff","out":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]}}, +{"amount":0.1,"gain":2,"in":"#eff0f1ff","out":{"hex":"#e2e5e9ff","u16":[57963,58890,59804,65535]}}, +{"amount":0.25,"gain":0,"in":"#eff0f1ff","out":{"hex":"#d2d2d2ff","u16":[54088,54088,54088,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#d1d3d4ff","u16":[53734,54145,54555,65535]}}, +{"amount":0.25,"gain":1,"in":"#eff0f1ff","out":{"hex":"#d0d3d6ff","u16":[53377,54203,55018,65535]}}, +{"amount":0.25,"gain":2,"in":"#eff0f1ff","out":{"hex":"#cdd3daff","u16":[52654,54317,55930,65535]}}, +{"amount":0.5,"gain":0,"in":"#eff0f1ff","out":{"hex":"#afafafff","u16":[44984,44984,44984,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#acafb3ff","u16":[44258,45101,45930,65535]}}, +{"amount":0.5,"gain":1,"in":"#eff0f1ff","out":{"hex":"#a9b0b6ff","u16":[43517,45218,46853,65535]}}, +{"amount":0.5,"gain":2,"in":"#eff0f1ff","out":{"hex":"#a3b1bdff","u16":[41987,45450,48637,65535]}}, +{"amount":0.75,"gain":0,"in":"#eff0f1ff","out":{"hex":"#808080ff","u16":[32827,32827,32827,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#7e8083ff","u16":[32279,32915,33540,65535]}}, +{"amount":0.75,"gain":1,"in":"#eff0f1ff","out":{"hex":"#7b8085ff","u16":[31719,33003,34235,65535]}}, +{"amount":0.75,"gain":2,"in":"#eff0f1ff","out":{"hex":"#77818aff","u16":[30563,33178,35578,65535]}}, +{"amount":0.9,"gain":0,"in":"#eff0f1ff","out":{"hex":"#545454ff","u16":[21644,21644,21644,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#535456ff","u16":[21283,21703,22115,65535]}}, +{"amount":0.9,"gain":1,"in":"#eff0f1ff","out":{"hex":"#515558ff","u16":[20914,21761,22573,65535]}}, +{"amount":0.9,"gain":2,"in":"#eff0f1ff","out":{"hex":"#4e555bff","u16":[20152,21876,23458,65535]}}, +{"amount":1,"gain":0,"in":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"gain":1,"in":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"gain":2,"in":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"gain":0,"in":"#fcfcfcff","out":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]}}, +{"amount":0.1,"gain":1,"in":"#fcfcfcff","out":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]}}, +{"amount":0.1,"gain":2,"in":"#fcfcfcff","out":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]}}, +{"amount":0.25,"gain":0,"in":"#fcfcfcff","out":{"hex":"#ddddddff","u16":[56826,56826,56826,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#ddddddff","u16":[56826,56826,56826,65535]}}, +{"amount":0.25,"gain":1,"in":"#fcfcfcff","out":{"hex":"#ddddddff","u16":[56826,56826,56826,65535]}}, +{"amount":0.25,"gain":2,"in":"#fcfcfcff","out":{"hex":"#ddddddff","u16":[56826,56826,56826,65535]}}, +{"amount":0.5,"gain":0,"in":"#fcfcfcff","out":{"hex":"#b8b8b8ff","u16":[47261,47261,47261,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#b8b8b8ff","u16":[47261,47261,47261,65535]}}, +{"amount":0.5,"gain":1,"in":"#fcfcfcff","out":{"hex":"#b8b8b8ff","u16":[47261,47261,47261,65535]}}, +{"amount":0.5,"gain":2,"in":"#fcfcfcff","out":{"hex":"#b8b8b8ff","u16":[47261,47261,47261,65535]}}, +{"amount":0.75,"gain":0,"in":"#fcfcfcff","out":{"hex":"#868686ff","u16":[34488,34488,34488,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#868686ff","u16":[34488,34488,34488,65535]}}, +{"amount":0.75,"gain":1,"in":"#fcfcfcff","out":{"hex":"#868686ff","u16":[34488,34488,34488,65535]}}, +{"amount":0.75,"gain":2,"in":"#fcfcfcff","out":{"hex":"#868686ff","u16":[34488,34488,34488,65535]}}, +{"amount":0.9,"gain":0,"in":"#fcfcfcff","out":{"hex":"#585858ff","u16":[22740,22740,22740,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#585858ff","u16":[22740,22740,22740,65535]}}, +{"amount":0.9,"gain":1,"in":"#fcfcfcff","out":{"hex":"#585858ff","u16":[22740,22740,22740,65535]}}, +{"amount":0.9,"gain":2,"in":"#fcfcfcff","out":{"hex":"#585858ff","u16":[22740,22740,22740,65535]}}, +{"amount":1,"gain":0,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#31363bff","out":{"hex":"#404040ff","u16":[16493,16493,16493,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#31363bff","out":{"hex":"#3e4144ff","u16":[15834,16590,17388,65535]}}, +{"amount":-0.5,"gain":1,"in":"#31363bff","out":{"hex":"#3b4147ff","u16":[15142,16687,18232,65535]}}, +{"amount":-0.5,"gain":2,"in":"#31363bff","out":{"hex":"#35424dff","u16":[13627,16878,19791,65535]}}, +{"amount":0,"gain":0,"in":"#31363bff","out":{"hex":"#353535ff","u16":[13717,13717,13717,65535]}}, +{"amount":0,"gain":0.5,"in":"#31363bff","out":{"hex":"#333638ff","u16":[13169,13798,14462,65535]}}, +{"amount":0,"gain":1,"in":"#31363bff","out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"amount":0,"gain":2,"in":"#31363bff","out":{"hex":"#2c3740ff","u16":[11334,14037,16460,65535]}}, +{"amount":0.1,"gain":0,"in":"#31363bff","out":{"hex":"#333333ff","u16":[13075,13075,13075,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#31363bff","out":{"hex":"#313336ff","u16":[12553,13152,13785,65535]}}, +{"amount":0.1,"gain":1,"in":"#31363bff","out":{"hex":"#2f3338ff","u16":[12004,13229,14454,65535]}}, +{"amount":0.1,"gain":2,"in":"#31363bff","out":{"hex":"#2a343dff","u16":[10804,13381,15690,65535]}}, +{"amount":0.25,"gain":0,"in":"#31363bff","out":{"hex":"#2f2f2fff","u16":[12035,12035,12035,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#31363bff","out":{"hex":"#2d2f31ff","u16":[11555,12106,12689,65535]}}, +{"amount":0.25,"gain":1,"in":"#31363bff","out":{"hex":"#2b2f34ff","u16":[11049,12177,13304,65535]}}, +{"amount":0.25,"gain":2,"in":"#31363bff","out":{"hex":"#273038ff","u16":[9944,12316,14442,65535]}}, +{"amount":0.5,"gain":0,"in":"#31363bff","out":{"hex":"#272727ff","u16":[10010,10010,10010,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#31363bff","out":{"hex":"#252729ff","u16":[9610,10069,10553,65535]}}, +{"amount":0.5,"gain":1,"in":"#31363bff","out":{"hex":"#24272bff","u16":[9190,10127,11065,65535]}}, +{"amount":0.5,"gain":2,"in":"#31363bff","out":{"hex":"#20282fff","u16":[8271,10243,12012,65535]}}, +{"amount":0.75,"gain":0,"in":"#31363bff","out":{"hex":"#1c1c1cff","u16":[7304,7304,7304,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#31363bff","out":{"hex":"#1b1d1eff","u16":[7013,7348,7701,65535]}}, +{"amount":0.75,"gain":1,"in":"#31363bff","out":{"hex":"#1a1d1fff","u16":[6706,7390,8075,65535]}}, +{"amount":0.75,"gain":2,"in":"#31363bff","out":{"hex":"#171d22ff","u16":[6035,7475,8765,65535]}}, +{"amount":0.9,"gain":0,"in":"#31363bff","out":{"hex":"#131313ff","u16":[4816,4816,4816,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#31363bff","out":{"hex":"#121314ff","u16":[4624,4845,5078,65535]}}, +{"amount":0.9,"gain":1,"in":"#31363bff","out":{"hex":"#111315ff","u16":[4422,4873,5324,65535]}}, +{"amount":0.9,"gain":2,"in":"#31363bff","out":{"hex":"#0f1316ff","u16":[3979,4929,5779,65535]}}, +{"amount":1,"gain":0,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#1b1e20ff","out":{"hex":"#242424ff","u16":[9130,9130,9130,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#222425ff","u16":[8747,9201,9518,65535]}}, +{"amount":-0.5,"gain":1,"in":"#1b1e20ff","out":{"hex":"#202426ff","u16":[8343,9270,9888,65535]}}, +{"amount":-0.5,"gain":2,"in":"#1b1e20ff","out":{"hex":"#1d2529ff","u16":[7455,9408,10583,65535]}}, +{"amount":0,"gain":0,"in":"#1b1e20ff","out":{"hex":"#1e1e1eff","u16":[7593,7593,7593,65535]}}, +{"amount":0,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#1c1e1fff","u16":[7275,7652,7916,65535]}}, +{"amount":0,"gain":1,"in":"#1b1e20ff","out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"amount":0,"gain":2,"in":"#1b1e20ff","out":{"hex":"#181e22ff","u16":[6201,7825,8801,65535]}}, +{"amount":0.1,"gain":0,"in":"#1b1e20ff","out":{"hex":"#1c1c1cff","u16":[7238,7238,7238,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#1b1c1dff","u16":[6935,7294,7546,65535]}}, +{"amount":0.1,"gain":1,"in":"#1b1e20ff","out":{"hex":"#1a1d1fff","u16":[6615,7349,7839,65535]}}, +{"amount":0.1,"gain":2,"in":"#1b1e20ff","out":{"hex":"#171d21ff","u16":[5911,7459,8390,65535]}}, +{"amount":0.25,"gain":0,"in":"#1b1e20ff","out":{"hex":"#1a1a1aff","u16":[6663,6663,6663,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#191a1bff","u16":[6383,6714,6946,65535]}}, +{"amount":0.25,"gain":1,"in":"#1b1e20ff","out":{"hex":"#181a1cff","u16":[6088,6765,7216,65535]}}, +{"amount":0.25,"gain":2,"in":"#1b1e20ff","out":{"hex":"#151b1eff","u16":[5440,6865,7722,65535]}}, +{"amount":0.5,"gain":0,"in":"#1b1e20ff","out":{"hex":"#161616ff","u16":[5541,5541,5541,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#151616ff","u16":[5309,5584,5777,65535]}}, +{"amount":0.5,"gain":1,"in":"#1b1e20ff","out":{"hex":"#141617ff","u16":[5064,5626,6001,65535]}}, +{"amount":0.5,"gain":2,"in":"#1b1e20ff","out":{"hex":"#121619ff","u16":[4525,5710,6423,65535]}}, +{"amount":0.75,"gain":0,"in":"#1b1e20ff","out":{"hex":"#101010ff","u16":[4044,4044,4044,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#0f1010ff","u16":[3874,4075,4216,65535]}}, +{"amount":0.75,"gain":1,"in":"#1b1e20ff","out":{"hex":"#0e1011ff","u16":[3695,4106,4379,65535]}}, +{"amount":0.75,"gain":2,"in":"#1b1e20ff","out":{"hex":"#0d1012ff","u16":[3302,4167,4687,65535]}}, +{"amount":0.9,"gain":0,"in":"#1b1e20ff","out":{"hex":"#0a0a0aff","u16":[2666,2666,2666,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#0a0a0bff","u16":[2554,2687,2780,65535]}}, +{"amount":0.9,"gain":1,"in":"#1b1e20ff","out":{"hex":"#090b0bff","u16":[2436,2707,2888,65535]}}, +{"amount":0.9,"gain":2,"in":"#1b1e20ff","out":{"hex":"#080b0cff","u16":[2177,2747,3090,65535]}}, +{"amount":1,"gain":0,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#da4453ff","out":{"hex":"#929292ff","u16":[37545,37545,37545,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#da4453ff","out":{"hex":"#c28185ff","u16":[49871,33164,34153,65535]}}, +{"amount":-0.5,"gain":1,"in":"#da4453ff","out":{"hex":"#e76d76ff","u16":[59314,27946,30300,65535]}}, +{"amount":-0.5,"gain":2,"in":"#da4453ff","out":{"hex":"#ff5969ff","u16":[65535,22915,26857,65535]}}, +{"amount":0,"gain":0,"in":"#da4453ff","out":{"hex":"#797979ff","u16":[31225,31225,31225,65535]}}, +{"amount":0,"gain":0.5,"in":"#da4453ff","out":{"hex":"#b26368ff","u16":[45680,25482,26830,65535]}}, +{"amount":0,"gain":1,"in":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"gain":2,"in":"#da4453ff","out":{"hex":"#f4003cff","u16":[62676,0,15457,65535]}}, +{"amount":0.1,"gain":0,"in":"#da4453ff","out":{"hex":"#747474ff","u16":[29765,29765,29765,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#da4453ff","out":{"hex":"#a95f64ff","u16":[43544,24291,25575,65535]}}, +{"amount":0.1,"gain":1,"in":"#da4453ff","out":{"hex":"#d0414fff","u16":[53406,16659,20334,65535]}}, +{"amount":0.1,"gain":2,"in":"#da4453ff","out":{"hex":"#e80039ff","u16":[59746,0,14734,65535]}}, +{"amount":0.25,"gain":0,"in":"#da4453ff","out":{"hex":"#6b6b6bff","u16":[27398,27398,27398,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#da4453ff","out":{"hex":"#9c575cff","u16":[40081,22359,23541,65535]}}, +{"amount":0.25,"gain":1,"in":"#da4453ff","out":{"hex":"#bf3c49ff","u16":[49159,15334,18716,65535]}}, +{"amount":0.25,"gain":2,"in":"#da4453ff","out":{"hex":"#d60035ff","u16":[54994,0,13562,65535]}}, +{"amount":0.5,"gain":0,"in":"#da4453ff","out":{"hex":"#595959ff","u16":[22786,22786,22786,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#da4453ff","out":{"hex":"#82484cff","u16":[33335,18596,19579,65535]}}, +{"amount":0.5,"gain":1,"in":"#da4453ff","out":{"hex":"#9f323dff","u16":[40884,12753,15566,65535]}}, +{"amount":0.5,"gain":2,"in":"#da4453ff","out":{"hex":"#b2002cff","u16":[45738,0,11280,65535]}}, +{"amount":0.75,"gain":0,"in":"#da4453ff","out":{"hex":"#414141ff","u16":[16628,16628,16628,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#da4453ff","out":{"hex":"#5f3538ff","u16":[24326,13570,14287,65535]}}, +{"amount":0.75,"gain":1,"in":"#da4453ff","out":{"hex":"#74242cff","u16":[29835,9306,11359,65535]}}, +{"amount":0.75,"gain":2,"in":"#da4453ff","out":{"hex":"#820020ff","u16":[33376,0,8231,65535]}}, +{"amount":0.9,"gain":0,"in":"#da4453ff","out":{"hex":"#2b2b2bff","u16":[10964,10964,10964,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#da4453ff","out":{"hex":"#3e2325ff","u16":[16039,8947,9420,65535]}}, +{"amount":0.9,"gain":1,"in":"#da4453ff","out":{"hex":"#4d181dff","u16":[19672,6136,7490,65535]}}, +{"amount":0.9,"gain":2,"in":"#da4453ff","out":{"hex":"#560015ff","u16":[22007,0,5427,65535]}}, +{"amount":1,"gain":0,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#27ae60ff","out":{"hex":"#b7b7b7ff","u16":[46966,46966,46966,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#88c59aff","u16":[35044,50505,39466,65535]}}, +{"amount":-0.5,"gain":1,"in":"#27ae60ff","out":{"hex":"#2fd173ff","u16":[12051,53768,29665,65535]}}, +{"amount":-0.5,"gain":2,"in":"#27ae60ff","out":{"hex":"#00d36eff","u16":[0,54099,28388,65535]}}, +{"amount":0,"gain":0,"in":"#27ae60ff","out":{"hex":"#989898ff","u16":[39061,39061,39061,65535]}}, +{"amount":0,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#71a380ff","u16":[29146,42004,32823,65535]}}, +{"amount":0,"gain":1,"in":"#27ae60ff","out":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]}}, +{"amount":0,"gain":2,"in":"#27ae60ff","out":{"hex":"#00af5cff","u16":[0,44993,23610,65535]}}, +{"amount":0.1,"gain":0,"in":"#27ae60ff","out":{"hex":"#919191ff","u16":[37234,37234,37234,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#6c9c7aff","u16":[27783,40040,31288,65535]}}, +{"amount":0.1,"gain":1,"in":"#27ae60ff","out":{"hex":"#25a65cff","u16":[9554,42627,23518,65535]}}, +{"amount":0.1,"gain":2,"in":"#27ae60ff","out":{"hex":"#00a758ff","u16":[0,42889,22505,65535]}}, +{"amount":0.25,"gain":0,"in":"#27ae60ff","out":{"hex":"#858585ff","u16":[34273,34273,34273,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#648f70ff","u16":[25573,36855,28800,65535]}}, +{"amount":0.25,"gain":1,"in":"#27ae60ff","out":{"hex":"#229954ff","u16":[8794,39237,21648,65535]}}, +{"amount":0.25,"gain":2,"in":"#27ae60ff","out":{"hex":"#009a51ff","u16":[0,39478,20716,65535]}}, +{"amount":0.5,"gain":0,"in":"#27ae60ff","out":{"hex":"#6f6f6fff","u16":[28504,28504,28504,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#53775dff","u16":[21269,30652,23952,65535]}}, +{"amount":0.5,"gain":1,"in":"#27ae60ff","out":{"hex":"#1c7f46ff","u16":[7314,32633,18004,65535]}}, +{"amount":0.5,"gain":2,"in":"#27ae60ff","out":{"hex":"#008043ff","u16":[0,32833,17229,65535]}}, +{"amount":0.75,"gain":0,"in":"#27ae60ff","out":{"hex":"#515151ff","u16":[20801,20801,20801,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#3c5744ff","u16":[15521,22368,17479,65535]}}, +{"amount":0.75,"gain":1,"in":"#27ae60ff","out":{"hex":"#155d33ff","u16":[5337,23813,13138,65535]}}, +{"amount":0.75,"gain":2,"in":"#27ae60ff","out":{"hex":"#005d31ff","u16":[0,23960,12573,65535]}}, +{"amount":0.9,"gain":0,"in":"#27ae60ff","out":{"hex":"#353535ff","u16":[13715,13715,13715,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#28392dff","u16":[10234,14748,11525,65535]}}, +{"amount":0.9,"gain":1,"in":"#27ae60ff","out":{"hex":"#0e3d22ff","u16":[3519,15701,8663,65535]}}, +{"amount":0.9,"gain":2,"in":"#27ae60ff","out":{"hex":"#003d20ff","u16":[0,15798,8290,65535]}}, +{"amount":1,"gain":0,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#27ae60ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#f67400ff","out":{"hex":"#b7b7b7ff","u16":[47135,47135,47135,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#f67400ff","out":{"hex":"#dfac9dff","u16":[57228,44258,40444,65535]}}, +{"amount":-0.5,"gain":1,"in":"#f67400ff","out":{"hex":"#ffa07dff","u16":[65535,41137,32052,65535]}}, +{"amount":-0.5,"gain":2,"in":"#f67400ff","out":{"hex":"#ffa07dff","u16":[65535,41137,32052,65535]}}, +{"amount":0,"gain":0,"in":"#f67400ff","out":{"hex":"#999999ff","u16":[39202,39202,39202,65535]}}, +{"amount":0,"gain":0.5,"in":"#f67400ff","out":{"hex":"#ce886fff","u16":[52868,34887,28607,65535]}}, +{"amount":0,"gain":1,"in":"#f67400ff","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"amount":0,"gain":2,"in":"#f67400ff","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#f67400ff","out":{"hex":"#919191ff","u16":[37368,37368,37368,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#f67400ff","out":{"hex":"#c4816aff","u16":[50396,33256,27269,65535]}}, +{"amount":0.1,"gain":1,"in":"#f67400ff","out":{"hex":"#ea6f00ff","u16":[60266,28418,0,65535]}}, +{"amount":0.1,"gain":2,"in":"#f67400ff","out":{"hex":"#ea6f00ff","u16":[60266,28418,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#f67400ff","out":{"hex":"#868686ff","u16":[34396,34396,34396,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#f67400ff","out":{"hex":"#b47762ff","u16":[46388,30611,25100,65535]}}, +{"amount":0.25,"gain":1,"in":"#f67400ff","out":{"hex":"#d86600ff","u16":[55473,26158,0,65535]}}, +{"amount":0.25,"gain":2,"in":"#f67400ff","out":{"hex":"#d86600ff","u16":[55473,26158,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#f67400ff","out":{"hex":"#6f6f6fff","u16":[28607,28607,28607,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#f67400ff","out":{"hex":"#966351ff","u16":[38580,25459,20876,65535]}}, +{"amount":0.5,"gain":1,"in":"#f67400ff","out":{"hex":"#b45500ff","u16":[46136,21755,0,65535]}}, +{"amount":0.5,"gain":2,"in":"#f67400ff","out":{"hex":"#b45500ff","u16":[46136,21755,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#f67400ff","out":{"hex":"#515151ff","u16":[20876,20876,20876,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#f67400ff","out":{"hex":"#6e483bff","u16":[28153,18578,15234,65535]}}, +{"amount":0.75,"gain":1,"in":"#f67400ff","out":{"hex":"#833e00ff","u16":[33667,15876,0,65535]}}, +{"amount":0.75,"gain":2,"in":"#f67400ff","out":{"hex":"#833e00ff","u16":[33667,15876,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#f67400ff","out":{"hex":"#363636ff","u16":[13764,13764,13764,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#f67400ff","out":{"hex":"#483027ff","u16":[18563,12250,10044,65535]}}, +{"amount":0.9,"gain":1,"in":"#f67400ff","out":{"hex":"#562900ff","u16":[22198,10468,0,65535]}}, +{"amount":0.9,"gain":2,"in":"#f67400ff","out":{"hex":"#562900ff","u16":[22198,10468,0,65535]}}, +{"amount":1,"gain":0,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#f67400ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#2980b9ff","out":{"hex":"#929292ff","u16":[37607,37607,37607,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#6f96bdff","u16":[28555,38595,48579,65535]}}, +{"amount":-0.5,"gain":1,"in":"#2980b9ff","out":{"hex":"#319adeff","u16":[12669,39553,57167,65535]}}, +{"amount":-0.5,"gain":2,"in":"#2980b9ff","out":{"hex":"#009be4ff","u16":[0,39743,58714,65535]}}, +{"amount":0,"gain":0,"in":"#2980b9ff","out":{"hex":"#7a7a7aff","u16":[31277,31277,31277,65535]}}, +{"amount":0,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#5c7d9dff","u16":[23749,32099,40403,65535]}}, +{"amount":0,"gain":1,"in":"#2980b9ff","out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"amount":0,"gain":2,"in":"#2980b9ff","out":{"hex":"#0081beff","u16":[0,33053,48831,65535]}}, +{"amount":0.1,"gain":0,"in":"#2980b9ff","out":{"hex":"#747474ff","u16":[29815,29815,29815,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#587796ff","u16":[22638,30598,38513,65535]}}, +{"amount":0.1,"gain":1,"in":"#2980b9ff","out":{"hex":"#277ab0ff","u16":[10044,31358,45322,65535]}}, +{"amount":0.1,"gain":2,"in":"#2980b9ff","out":{"hex":"#007bb5ff","u16":[0,31508,46548,65535]}}, +{"amount":0.25,"gain":0,"in":"#2980b9ff","out":{"hex":"#6b6b6bff","u16":[27443,27443,27443,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#516e8aff","u16":[20838,28164,35450,65535]}}, +{"amount":0.25,"gain":1,"in":"#2980b9ff","out":{"hex":"#2470a2ff","u16":[9245,28864,41717,65535]}}, +{"amount":0.25,"gain":2,"in":"#2980b9ff","out":{"hex":"#0071a7ff","u16":[0,29002,42846,65535]}}, +{"amount":0.5,"gain":0,"in":"#2980b9ff","out":{"hex":"#595959ff","u16":[22824,22824,22824,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#435b73ff","u16":[17331,23424,29484,65535]}}, +{"amount":0.5,"gain":1,"in":"#2980b9ff","out":{"hex":"#1e5d87ff","u16":[7689,24006,34695,65535]}}, +{"amount":0.5,"gain":2,"in":"#2980b9ff","out":{"hex":"#005e8bff","u16":[0,24120,35634,65535]}}, +{"amount":0.75,"gain":0,"in":"#2980b9ff","out":{"hex":"#414141ff","u16":[16656,16656,16656,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#314354ff","u16":[12647,17093,21515,65535]}}, +{"amount":0.75,"gain":1,"in":"#2980b9ff","out":{"hex":"#164463ff","u16":[5611,17518,25319,65535]}}, +{"amount":0.75,"gain":2,"in":"#2980b9ff","out":{"hex":"#004465ff","u16":[0,17602,26004,65535]}}, +{"amount":0.9,"gain":0,"in":"#2980b9ff","out":{"hex":"#2b2b2bff","u16":[10982,10982,10982,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#202c37ff","u16":[8339,11271,14186,65535]}}, +{"amount":0.9,"gain":1,"in":"#2980b9ff","out":{"hex":"#0e2d41ff","u16":[3700,11550,16694,65535]}}, +{"amount":0.9,"gain":2,"in":"#2980b9ff","out":{"hex":"#002d43ff","u16":[0,11606,17146,65535]}}, +{"amount":1,"gain":0,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#2980b9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#7f8c8dff","out":{"hex":"#a5a5a5ff","u16":[42469,42469,42469,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#9fa7a7ff","u16":[40895,42867,43024,65535]}}, +{"amount":-0.5,"gain":1,"in":"#7f8c8dff","out":{"hex":"#99a8aaff","u16":[39244,43262,43571,65535]}}, +{"amount":-0.5,"gain":2,"in":"#7f8c8dff","out":{"hex":"#8babaeff","u16":[35666,44038,44640,65535]}}, +{"amount":0,"gain":0,"in":"#7f8c8dff","out":{"hex":"#898989ff","u16":[35320,35320,35320,65535]}}, +{"amount":0,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#848b8bff","u16":[34011,35652,35782,65535]}}, +{"amount":0,"gain":1,"in":"#7f8c8dff","out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"amount":0,"gain":2,"in":"#7f8c8dff","out":{"hex":"#738f90ff","u16":[29663,36625,37127,65535]}}, +{"amount":0.1,"gain":0,"in":"#7f8c8dff","out":{"hex":"#838383ff","u16":[33669,33669,33669,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#7e8485ff","u16":[32421,33985,34109,65535]}}, +{"amount":0.1,"gain":1,"in":"#7f8c8dff","out":{"hex":"#798586ff","u16":[31113,34297,34542,65535]}}, +{"amount":0.1,"gain":2,"in":"#7f8c8dff","out":{"hex":"#6e888aff","u16":[28276,34913,35390,65535]}}, +{"amount":0.25,"gain":0,"in":"#7f8c8dff","out":{"hex":"#797979ff","u16":[30991,30991,30991,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#747a7aff","u16":[29842,31282,31396,65535]}}, +{"amount":0.25,"gain":1,"in":"#7f8c8dff","out":{"hex":"#6f7b7cff","u16":[28638,31570,31795,65535]}}, +{"amount":0.25,"gain":2,"in":"#7f8c8dff","out":{"hex":"#657d7fff","u16":[26027,32136,32576,65535]}}, +{"amount":0.5,"gain":0,"in":"#7f8c8dff","out":{"hex":"#646464ff","u16":[25775,25775,25775,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#616566ff","u16":[24820,26017,26112,65535]}}, +{"amount":0.5,"gain":1,"in":"#7f8c8dff","out":{"hex":"#5d6667ff","u16":[23818,26256,26444,65535]}}, +{"amount":0.5,"gain":2,"in":"#7f8c8dff","out":{"hex":"#546869ff","u16":[21646,26727,27093,65535]}}, +{"amount":0.75,"gain":0,"in":"#7f8c8dff","out":{"hex":"#494949ff","u16":[18809,18809,18809,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#464a4aff","u16":[18112,18985,19055,65535]}}, +{"amount":0.75,"gain":1,"in":"#7f8c8dff","out":{"hex":"#444b4bff","u16":[17381,19160,19297,65535]}}, +{"amount":0.75,"gain":2,"in":"#7f8c8dff","out":{"hex":"#3d4c4dff","u16":[15796,19504,19771,65535]}}, +{"amount":0.9,"gain":0,"in":"#7f8c8dff","out":{"hex":"#303030ff","u16":[12402,12402,12402,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#2e3131ff","u16":[11942,12518,12564,65535]}}, +{"amount":0.9,"gain":1,"in":"#7f8c8dff","out":{"hex":"#2d3132ff","u16":[11460,12633,12724,65535]}}, +{"amount":0.9,"gain":2,"in":"#7f8c8dff","out":{"hex":"#293233ff","u16":[10415,12860,13036,65535]}}, +{"amount":1,"gain":0,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#7f8c8dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#9b59b6ff","out":{"hex":"#8b8b8bff","u16":[35780,35780,35780,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#9e81adff","u16":[40637,33092,44456,65535]}}, +{"amount":-0.5,"gain":1,"in":"#9b59b6ff","out":{"hex":"#af75c8ff","u16":[44880,30112,51462,65535]}}, +{"amount":-0.5,"gain":2,"in":"#9b59b6ff","out":{"hex":"#cb58f4ff","u16":[52171,22729,62814,65535]}}, +{"amount":0,"gain":0,"in":"#9b59b6ff","out":{"hex":"#747474ff","u16":[29758,29758,29758,65535]}}, +{"amount":0,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#896799ff","u16":[35231,26584,39381,65535]}}, +{"amount":0,"gain":1,"in":"#9b59b6ff","out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"amount":0,"gain":2,"in":"#9b59b6ff","out":{"hex":"#b92ce3ff","u16":[47511,11396,58409,65535]}}, +{"amount":0.1,"gain":0,"in":"#9b59b6ff","out":{"hex":"#6e6e6eff","u16":[28366,28366,28366,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#836392ff","u16":[33583,25341,37539,65535]}}, +{"amount":0.1,"gain":1,"in":"#9b59b6ff","out":{"hex":"#9455adff","u16":[37972,21803,44587,65535]}}, +{"amount":0.1,"gain":2,"in":"#9b59b6ff","out":{"hex":"#b02ad9ff","u16":[45290,10863,55678,65535]}}, +{"amount":0.25,"gain":0,"in":"#9b59b6ff","out":{"hex":"#666666ff","u16":[26110,26110,26110,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#785b86ff","u16":[30912,23325,34554,65535]}}, +{"amount":0.25,"gain":1,"in":"#9b59b6ff","out":{"hex":"#884ea0ff","u16":[34952,20069,41041,65535]}}, +{"amount":0.25,"gain":2,"in":"#9b59b6ff","out":{"hex":"#a227c7ff","u16":[41688,9999,51250,65535]}}, +{"amount":0.5,"gain":0,"in":"#9b59b6ff","out":{"hex":"#545454ff","u16":[21715,21715,21715,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#644b70ff","u16":[25709,19399,28738,65535]}}, +{"amount":0.5,"gain":1,"in":"#9b59b6ff","out":{"hex":"#714185ff","u16":[29069,16691,34133,65535]}}, +{"amount":0.5,"gain":2,"in":"#9b59b6ff","out":{"hex":"#8720a6ff","u16":[34671,8316,42624,65535]}}, +{"amount":0.75,"gain":0,"in":"#9b59b6ff","out":{"hex":"#3e3e3eff","u16":[15847,15847,15847,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#493752ff","u16":[18761,14156,20971,65535]}}, +{"amount":0.75,"gain":1,"in":"#9b59b6ff","out":{"hex":"#532f61ff","u16":[21213,12180,24908,65535]}}, +{"amount":0.75,"gain":2,"in":"#9b59b6ff","out":{"hex":"#621879ff","u16":[25301,6068,31104,65535]}}, +{"amount":0.9,"gain":0,"in":"#9b59b6ff","out":{"hex":"#292929ff","u16":[10449,10449,10449,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#302436ff","u16":[12370,9334,13827,65535]}}, +{"amount":0.9,"gain":1,"in":"#9b59b6ff","out":{"hex":"#361f40ff","u16":[13987,8031,16423,65535]}}, +{"amount":0.9,"gain":2,"in":"#9b59b6ff","out":{"hex":"#411050ff","u16":[16682,4001,20509,65535]}}, +{"amount":1,"gain":0,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#9b59b6ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#1d99f3ff","out":{"hex":"#b1b1b1ff","u16":[45531,45531,45531,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#98b3dbff","u16":[38986,46059,56326,65535]}}, +{"amount":-0.5,"gain":1,"in":"#1d99f3ff","out":{"hex":"#78b5fdff","u16":[30745,46580,65074,65535]}}, +{"amount":-0.5,"gain":2,"in":"#1d99f3ff","out":{"hex":"#75b5ffff","u16":[30186,46610,65535,65535]}}, +{"amount":0,"gain":0,"in":"#1d99f3ff","out":{"hex":"#939393ff","u16":[37867,37867,37867,65535]}}, +{"amount":0,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#6d96caff","u16":[27982,38602,51927,65535]}}, +{"amount":0,"gain":1,"in":"#1d99f3ff","out":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]}}, +{"amount":0,"gain":2,"in":"#1d99f3ff","out":{"hex":"#0099f5ff","u16":[0,39362,62994,65535]}}, +{"amount":0.1,"gain":0,"in":"#1d99f3ff","out":{"hex":"#8c8c8cff","u16":[36096,36096,36096,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#688fc1ff","u16":[26674,36797,49499,65535]}}, +{"amount":0.1,"gain":1,"in":"#1d99f3ff","out":{"hex":"#1c92e8ff","u16":[7104,37482,59531,65535]}}, +{"amount":0.1,"gain":2,"in":"#1d99f3ff","out":{"hex":"#0092eaff","u16":[0,37521,60048,65535]}}, +{"amount":0.25,"gain":0,"in":"#1d99f3ff","out":{"hex":"#818181ff","u16":[33226,33226,33226,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#6084b1ff","u16":[24552,33871,45562,65535]}}, +{"amount":0.25,"gain":1,"in":"#1d99f3ff","out":{"hex":"#1986d5ff","u16":[6539,34501,54796,65535]}}, +{"amount":0.25,"gain":2,"in":"#1d99f3ff","out":{"hex":"#0086d7ff","u16":[0,34537,55272,65535]}}, +{"amount":0.5,"gain":0,"in":"#1d99f3ff","out":{"hex":"#6c6c6cff","u16":[27633,27633,27633,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#4f6e93ff","u16":[20420,28170,37894,65535]}}, +{"amount":0.5,"gain":1,"in":"#1d99f3ff","out":{"hex":"#1570b1ff","u16":[5439,28694,45573,65535]}}, +{"amount":0.5,"gain":2,"in":"#1d99f3ff","out":{"hex":"#0070b3ff","u16":[0,28724,45969,65535]}}, +{"amount":0.75,"gain":0,"in":"#1d99f3ff","out":{"hex":"#4e4e4eff","u16":[20165,20165,20165,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#3a506cff","u16":[14901,20557,27652,65535]}}, +{"amount":0.75,"gain":1,"in":"#1d99f3ff","out":{"hex":"#0f5181ff","u16":[3969,20939,33256,65535]}}, +{"amount":0.75,"gain":2,"in":"#1d99f3ff","out":{"hex":"#005283ff","u16":[0,20961,33545,65535]}}, +{"amount":0.9,"gain":0,"in":"#1d99f3ff","out":{"hex":"#343434ff","u16":[13296,13296,13296,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#263547ff","u16":[9825,13554,18233,65535]}}, +{"amount":0.9,"gain":1,"in":"#1d99f3ff","out":{"hex":"#0a3655ff","u16":[2617,13806,21928,65535]}}, +{"amount":0.9,"gain":2,"in":"#1d99f3ff","out":{"hex":"#003656ff","u16":[0,13821,22118,65535]}}, +{"amount":1,"gain":0,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#1d99f3ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":1,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"gain":2,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"gain":0,"in":"#dee0e2ff","out":{"hex":"#e0e0e0ff","u16":[57497,57497,57497,65535]}}, +{"amount":0,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#dfe0e1ff","u16":[57276,57532,57790,65535]}}, +{"amount":0,"gain":1,"in":"#dee0e2ff","out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"amount":0,"gain":2,"in":"#dee0e2ff","out":{"hex":"#dce0e4ff","u16":[56607,57639,58660,65535]}}, +{"amount":0.1,"gain":0,"in":"#dee0e2ff","out":{"hex":"#d5d5d5ff","u16":[54808,54808,54808,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#d4d5d7ff","u16":[54504,54857,55212,65535]}}, +{"amount":0.1,"gain":1,"in":"#dee0e2ff","out":{"hex":"#d3d6d8ff","u16":[54197,54906,55612,65535]}}, +{"amount":0.1,"gain":2,"in":"#dee0e2ff","out":{"hex":"#d0d6dbff","u16":[53578,55004,56402,65535]}}, +{"amount":0.25,"gain":0,"in":"#dee0e2ff","out":{"hex":"#c4c4c4ff","u16":[50449,50449,50449,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#c3c5c7ff","u16":[49996,50522,51047,65535]}}, +{"amount":0.25,"gain":1,"in":"#dee0e2ff","out":{"hex":"#c1c5c9ff","u16":[49537,50595,51638,65535]}}, +{"amount":0.25,"gain":2,"in":"#dee0e2ff","out":{"hex":"#bdc5cdff","u16":[48605,50740,52794,65535]}}, +{"amount":0.5,"gain":0,"in":"#dee0e2ff","out":{"hex":"#a3a3a3ff","u16":[41958,41958,41958,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#a1a4a7ff","u16":[41307,42062,42809,65535]}}, +{"amount":0.5,"gain":1,"in":"#dee0e2ff","out":{"hex":"#9ea4aaff","u16":[40645,42166,43640,65535]}}, +{"amount":0.5,"gain":2,"in":"#dee0e2ff","out":{"hex":"#99a5b0ff","u16":[39279,42373,45247,65535]}}, +{"amount":0.75,"gain":0,"in":"#dee0e2ff","out":{"hex":"#777777ff","u16":[30618,30618,30618,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#75777aff","u16":[30144,30694,31239,65535]}}, +{"amount":0.75,"gain":1,"in":"#dee0e2ff","out":{"hex":"#73787cff","u16":[29660,30770,31846,65535]}}, +{"amount":0.75,"gain":2,"in":"#dee0e2ff","out":{"hex":"#707880ff","u16":[28663,30921,33019,65535]}}, +{"amount":0.9,"gain":0,"in":"#dee0e2ff","out":{"hex":"#4f4f4fff","u16":[20188,20188,20188,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#4d4f50ff","u16":[19875,20238,20598,65535]}}, +{"amount":0.9,"gain":1,"in":"#dee0e2ff","out":{"hex":"#4c4f52ff","u16":[19556,20288,20998,65535]}}, +{"amount":0.9,"gain":2,"in":"#dee0e2ff","out":{"hex":"#4a4f55ff","u16":[18899,20388,21771,65535]}}, +{"amount":1,"gain":0,"in":"#dee0e2ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#dee0e2ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#dee0e2ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#dee0e2ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#dee0e2ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#dee0e2ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#dee0e2ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#292c30ff","out":{"hex":"#353535ff","u16":[13501,13501,13501,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#292c30ff","out":{"hex":"#333537ff","u16":[13093,13549,14186,65535]}}, +{"amount":-0.5,"gain":1,"in":"#292c30ff","out":{"hex":"#31353aff","u16":[12669,13597,14833,65535]}}, +{"amount":-0.5,"gain":2,"in":"#292c30ff","out":{"hex":"#2e353eff","u16":[11766,13691,16034,65535]}}, +{"amount":0,"gain":0,"in":"#292c30ff","out":{"hex":"#2c2c2cff","u16":[11229,11229,11229,65535]}}, +{"amount":0,"gain":0.5,"in":"#292c30ff","out":{"hex":"#2a2c2eff","u16":[10889,11268,11798,65535]}}, +{"amount":0,"gain":1,"in":"#292c30ff","out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"amount":0,"gain":2,"in":"#292c30ff","out":{"hex":"#262c34ff","u16":[9786,11387,13335,65535]}}, +{"amount":0.1,"gain":0,"in":"#292c30ff","out":{"hex":"#2a2a2aff","u16":[10704,10704,10704,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#292c30ff","out":{"hex":"#282a2cff","u16":[10380,10742,11246,65535]}}, +{"amount":0.1,"gain":1,"in":"#292c30ff","out":{"hex":"#272a2eff","u16":[10044,10779,11759,65535]}}, +{"amount":0.1,"gain":2,"in":"#292c30ff","out":{"hex":"#242a31ff","u16":[9328,10854,12712,65535]}}, +{"amount":0.25,"gain":0,"in":"#292c30ff","out":{"hex":"#262626ff","u16":[9852,9852,9852,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#292c30ff","out":{"hex":"#252628ff","u16":[9555,9887,10352,65535]}}, +{"amount":0.25,"gain":1,"in":"#292c30ff","out":{"hex":"#24272aff","u16":[9245,9922,10824,65535]}}, +{"amount":0.25,"gain":2,"in":"#292c30ff","out":{"hex":"#21272eff","u16":[8586,9991,11701,65535]}}, +{"amount":0.5,"gain":0,"in":"#292c30ff","out":{"hex":"#202020ff","u16":[8194,8194,8194,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#292c30ff","out":{"hex":"#1f2021ff","u16":[7946,8223,8609,65535]}}, +{"amount":0.5,"gain":1,"in":"#292c30ff","out":{"hex":"#1e2023ff","u16":[7689,8252,9002,65535]}}, +{"amount":0.5,"gain":2,"in":"#292c30ff","out":{"hex":"#1c2026ff","u16":[7141,8309,9731,65535]}}, +{"amount":0.75,"gain":0,"in":"#292c30ff","out":{"hex":"#171717ff","u16":[5980,5980,5980,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#292c30ff","out":{"hex":"#171718ff","u16":[5799,6001,6283,65535]}}, +{"amount":0.75,"gain":1,"in":"#292c30ff","out":{"hex":"#16171aff","u16":[5611,6022,6569,65535]}}, +{"amount":0.75,"gain":2,"in":"#292c30ff","out":{"hex":"#14181cff","u16":[5211,6064,7101,65535]}}, +{"amount":0.9,"gain":0,"in":"#292c30ff","out":{"hex":"#0f0f0fff","u16":[3943,3943,3943,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#292c30ff","out":{"hex":"#0f0f10ff","u16":[3823,3957,4142,65535]}}, +{"amount":0.9,"gain":1,"in":"#292c30ff","out":{"hex":"#0e0f11ff","u16":[3700,3970,4331,65535]}}, +{"amount":0.9,"gain":2,"in":"#292c30ff","out":{"hex":"#0d1012ff","u16":[3436,3998,4682,65535]}}, +{"amount":1,"gain":0,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#e93a9aff","out":{"hex":"#9c9c9cff","u16":[40051,40051,40051,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#cc88a6ff","u16":[52458,35029,42543,65535]}}, +{"amount":-0.5,"gain":1,"in":"#e93a9aff","out":{"hex":"#f171afff","u16":[62065,28944,44870,65535]}}, +{"amount":-0.5,"gain":2,"in":"#e93a9aff","out":{"hex":"#ff65b2ff","u16":[65535,25937,45786,65535]}}, +{"amount":0,"gain":0,"in":"#e93a9aff","out":{"hex":"#828282ff","u16":[33310,33310,33310,65535]}}, +{"amount":0,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#be668eff","u16":[48803,26111,36605,65535]}}, +{"amount":0,"gain":1,"in":"#e93a9aff","out":{"hex":"#e93a9aff","u16":[59881,14906,39578,65535]}}, +{"amount":0,"gain":2,"in":"#e93a9aff","out":{"hex":"#f8009eff","u16":[63783,0,40725,65535]}}, +{"amount":0.1,"gain":0,"in":"#e93a9aff","out":{"hex":"#7c7c7cff","u16":[31753,31753,31753,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#b56188ff","u16":[46521,24890,34894,65535]}}, +{"amount":0.1,"gain":1,"in":"#e93a9aff","out":{"hex":"#de3793ff","u16":[57081,14209,37727,65535]}}, +{"amount":0.1,"gain":2,"in":"#e93a9aff","out":{"hex":"#ed0097ff","u16":[60800,0,38821,65535]}}, +{"amount":0.25,"gain":0,"in":"#e93a9aff","out":{"hex":"#727272ff","u16":[29227,29227,29227,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#a7597dff","u16":[42821,22910,32118,65535]}}, +{"amount":0.25,"gain":1,"in":"#e93a9aff","out":{"hex":"#cc3387ff","u16":[52541,13079,34727,65535]}}, +{"amount":0.25,"gain":2,"in":"#e93a9aff","out":{"hex":"#da008bff","u16":[55965,0,35733,65535]}}, +{"amount":0.5,"gain":0,"in":"#e93a9aff","out":{"hex":"#5f5f5fff","u16":[24308,24308,24308,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#8b4a68ff","u16":[35613,19054,26712,65535]}}, +{"amount":0.5,"gain":1,"in":"#e93a9aff","out":{"hex":"#aa2a70ff","u16":[43698,10878,28882,65535]}}, +{"amount":0.5,"gain":2,"in":"#e93a9aff","out":{"hex":"#b50074ff","u16":[46545,0,29719,65535]}}, +{"amount":0.75,"gain":0,"in":"#e93a9aff","out":{"hex":"#454545ff","u16":[17738,17738,17738,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#65364cff","u16":[25989,13905,19493,65535]}}, +{"amount":0.75,"gain":1,"in":"#e93a9aff","out":{"hex":"#7c1f52ff","u16":[31888,7938,21076,65535]}}, +{"amount":0.75,"gain":2,"in":"#e93a9aff","out":{"hex":"#840054ff","u16":[33966,0,21687,65535]}}, +{"amount":0.9,"gain":0,"in":"#e93a9aff","out":{"hex":"#2e2e2eff","u16":[11696,11696,11696,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#432432ff","u16":[17136,9168,12853,65535]}}, +{"amount":0.9,"gain":1,"in":"#e93a9aff","out":{"hex":"#521436ff","u16":[21025,5234,13897,65535]}}, +{"amount":0.9,"gain":2,"in":"#e93a9aff","out":{"hex":"#570038ff","u16":[22395,0,14299,65535]}}, +{"amount":1,"gain":0,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#e93a9aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#e93d58ff","out":{"hex":"#979797ff","u16":[38933,38933,38933,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#c9868cff","u16":[51555,34415,35913,65535]}}, +{"amount":-0.5,"gain":1,"in":"#e93d58ff","out":{"hex":"#ee717fff","u16":[61249,29040,32552,65535]}}, +{"amount":-0.5,"gain":2,"in":"#e93d58ff","out":{"hex":"#ff6477ff","u16":[65535,25781,30671,65535]}}, +{"amount":0,"gain":0,"in":"#e93d58ff","out":{"hex":"#7e7e7eff","u16":[32380,32380,32380,65535]}}, +{"amount":0,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#bd646dff","u16":[48513,25698,28012,65535]}}, +{"amount":0,"gain":1,"in":"#e93d58ff","out":{"hex":"#e93d58ff","u16":[59881,15677,22616,65535]}}, +{"amount":0,"gain":2,"in":"#e93d58ff","out":{"hex":"#fc004bff","u16":[64772,0,19159,65535]}}, +{"amount":0.1,"gain":0,"in":"#e93d58ff","out":{"hex":"#787878ff","u16":[30866,30866,30866,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#b45f68ff","u16":[46244,24496,26702,65535]}}, +{"amount":0.1,"gain":1,"in":"#e93d58ff","out":{"hex":"#de3a54ff","u16":[57081,14944,21558,65535]}}, +{"amount":0.1,"gain":2,"in":"#e93d58ff","out":{"hex":"#f00047ff","u16":[61743,0,18263,65535]}}, +{"amount":0.25,"gain":0,"in":"#e93d58ff","out":{"hex":"#6f6f6fff","u16":[28411,28411,28411,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#a65860ff","u16":[42566,22548,24579,65535]}}, +{"amount":0.25,"gain":1,"in":"#e93d58ff","out":{"hex":"#cc364dff","u16":[52541,13755,19844,65535]}}, +{"amount":0.25,"gain":2,"in":"#e93d58ff","out":{"hex":"#dd0041ff","u16":[56833,0,16810,65535]}}, +{"amount":0.5,"gain":0,"in":"#e93d58ff","out":{"hex":"#5c5c5cff","u16":[23629,23629,23629,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#8a4950ff","u16":[35402,18753,20442,65535]}}, +{"amount":0.5,"gain":1,"in":"#e93d58ff","out":{"hex":"#aa2d40ff","u16":[43698,11440,16504,65535]}}, +{"amount":0.5,"gain":2,"in":"#e93d58ff","out":{"hex":"#b80036ff","u16":[47267,0,13981,65535]}}, +{"amount":0.75,"gain":0,"in":"#e93d58ff","out":{"hex":"#434343ff","u16":[17243,17243,17243,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#65353aff","u16":[25834,13684,14917,65535]}}, +{"amount":0.75,"gain":1,"in":"#e93d58ff","out":{"hex":"#7c202fff","u16":[31888,8348,12043,65535]}}, +{"amount":0.75,"gain":2,"in":"#e93d58ff","out":{"hex":"#860028ff","u16":[34493,0,10202,65535]}}, +{"amount":0.9,"gain":0,"in":"#e93d58ff","out":{"hex":"#2c2c2cff","u16":[11369,11369,11369,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#422326ff","u16":[17034,9023,9836,65535]}}, +{"amount":0.9,"gain":1,"in":"#e93d58ff","out":{"hex":"#52151fff","u16":[21025,5504,7941,65535]}}, +{"amount":0.9,"gain":2,"in":"#e93d58ff","out":{"hex":"#58001aff","u16":[22743,0,6727,65535]}}, +{"amount":1,"gain":0,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#e93d58ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#e9643aff","out":{"hex":"#a9a9a9ff","u16":[43332,43332,43332,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#e9643aff","out":{"hex":"#d39c93ff","u16":[54196,39981,37674,65535]}}, +{"amount":-0.5,"gain":1,"in":"#e9643aff","out":{"hex":"#f58d78ff","u16":[62920,36253,30748,65535]}}, +{"amount":-0.5,"gain":2,"in":"#e9643aff","out":{"hex":"#ff886dff","u16":[65535,34897,27960,65535]}}, +{"amount":0,"gain":0,"in":"#e9643aff","out":{"hex":"#8c8c8cff","u16":[36038,36038,36038,65535]}}, +{"amount":0,"gain":0.5,"in":"#e9643aff","out":{"hex":"#c17a6dff","u16":[49698,31383,27950,65535]}}, +{"amount":0,"gain":1,"in":"#e9643aff","out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"amount":0,"gain":2,"in":"#e9643aff","out":{"hex":"#f55b00ff","u16":[62857,23421,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#e9643aff","out":{"hex":"#868686ff","u16":[34353,34353,34353,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#e9643aff","out":{"hex":"#b87468ff","u16":[47374,29916,26643,65535]}}, +{"amount":0.1,"gain":1,"in":"#e9643aff","out":{"hex":"#de5f37ff","u16":[57081,24498,14209,65535]}}, +{"amount":0.1,"gain":2,"in":"#e9643aff","out":{"hex":"#e95700ff","u16":[59918,22326,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#e9643aff","out":{"hex":"#7b7b7bff","u16":[31621,31621,31621,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#e9643aff","out":{"hex":"#aa6b5fff","u16":[43606,27536,24524,65535]}}, +{"amount":0.25,"gain":1,"in":"#e9643aff","out":{"hex":"#cc5833ff","u16":[52541,22550,13079,65535]}}, +{"amount":0.25,"gain":2,"in":"#e9643aff","out":{"hex":"#d75000ff","u16":[55152,20550,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#e9643aff","out":{"hex":"#666666ff","u16":[26299,26299,26299,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#e9643aff","out":{"hex":"#8d594fff","u16":[36267,22902,20396,65535]}}, +{"amount":0.5,"gain":1,"in":"#e9643aff","out":{"hex":"#aa492aff","u16":[43698,18754,10878,65535]}}, +{"amount":0.5,"gain":2,"in":"#e9643aff","out":{"hex":"#b24300ff","u16":[45869,17091,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#e9643aff","out":{"hex":"#4b4b4bff","u16":[19191,19191,19191,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#e9643aff","out":{"hex":"#67413aff","u16":[26465,16712,14884,65535]}}, +{"amount":0.75,"gain":1,"in":"#e9643aff","out":{"hex":"#7c351fff","u16":[31888,13686,7938,65535]}}, +{"amount":0.75,"gain":2,"in":"#e9643aff","out":{"hex":"#823100ff","u16":[33473,12472,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#e9643aff","out":{"hex":"#313131ff","u16":[12654,12654,12654,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#e9643aff","out":{"hex":"#442b26ff","u16":[17450,11019,9814,65535]}}, +{"amount":0.9,"gain":1,"in":"#e9643aff","out":{"hex":"#522314ff","u16":[21025,9024,5234,65535]}}, +{"amount":0.9,"gain":2,"in":"#e9643aff","out":{"hex":"#562000ff","u16":[22070,8224,0,65535]}}, +{"amount":1,"gain":0,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#e9643aff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#e8cb2dff","out":{"hex":"#f4f4f4ff","u16":[62815,62815,62815,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#faf4e5ff","u16":[64143,62803,58823,65535]}}, +{"amount":-0.5,"gain":1,"in":"#e8cb2dff","out":{"hex":"#fff4d4ff","u16":[65439,62791,54476,65535]}}, +{"amount":-0.5,"gain":2,"in":"#e8cb2dff","out":{"hex":"#fff4d3ff","u16":[65535,62791,54132,65535]}}, +{"amount":0,"gain":0,"in":"#e8cb2dff","out":{"hex":"#cbcbcbff","u16":[52243,52243,52243,65535]}}, +{"amount":0,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#dacb97ff","u16":[56079,52207,38746,65535]}}, +{"amount":0,"gain":1,"in":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"gain":2,"in":"#e8cb2dff","out":{"hex":"#e9cb00ff","u16":[59881,52168,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#e8cb2dff","out":{"hex":"#c2c2c2ff","u16":[49800,49800,49800,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#d0c290ff","u16":[53457,49766,36934,65535]}}, +{"amount":0.1,"gain":1,"in":"#e8cb2dff","out":{"hex":"#ddc22bff","u16":[56836,49731,11024,65535]}}, +{"amount":0.1,"gain":2,"in":"#e8cb2dff","out":{"hex":"#dec100ff","u16":[57080,49729,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#e8cb2dff","out":{"hex":"#b2b2b2ff","u16":[45839,45839,45839,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#bfb284ff","u16":[49205,45808,33996,65535]}}, +{"amount":0.25,"gain":1,"in":"#e8cb2dff","out":{"hex":"#ccb227ff","u16":[52316,45776,10147,65535]}}, +{"amount":0.25,"gain":2,"in":"#e8cb2dff","out":{"hex":"#ccb200ff","u16":[52541,45774,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#e8cb2dff","out":{"hex":"#949494ff","u16":[38124,38124,38124,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#9f946eff","u16":[40923,38097,28274,65535]}}, +{"amount":0.5,"gain":1,"in":"#e8cb2dff","out":{"hex":"#a99421ff","u16":[43510,38071,8439,65535]}}, +{"amount":0.5,"gain":2,"in":"#e8cb2dff","out":{"hex":"#aa9400ff","u16":[43697,38069,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#e8cb2dff","out":{"hex":"#6c6c6cff","u16":[27820,27820,27820,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#746c50ff","u16":[29863,27801,20633,65535]}}, +{"amount":0.75,"gain":1,"in":"#e8cb2dff","out":{"hex":"#7c6c18ff","u16":[31751,27782,6159,65535]}}, +{"amount":0.75,"gain":2,"in":"#e8cb2dff","out":{"hex":"#7c6c00ff","u16":[31888,27781,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#e8cb2dff","out":{"hex":"#474747ff","u16":[18343,18343,18343,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#4d4735ff","u16":[19690,18331,13604,65535]}}, +{"amount":0.9,"gain":1,"in":"#e8cb2dff","out":{"hex":"#514710ff","u16":[20935,18318,4061,65535]}}, +{"amount":0.9,"gain":2,"in":"#e8cb2dff","out":{"hex":"#524700ff","u16":[21025,18317,0,65535]}}, +{"amount":1,"gain":0,"in":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#3dd425ff","out":{"hex":"#ddddddff","u16":[56795,56795,56795,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#a9eea5ff","u16":[43503,61224,42468,65535]}}, +{"amount":-0.5,"gain":1,"in":"#3dd425ff","out":{"hex":"#52fe3bff","u16":[21034,65298,15206,65535]}}, +{"amount":-0.5,"gain":2,"in":"#3dd425ff","out":{"hex":"#48ff2aff","u16":[18577,65535,10893,65535]}}, +{"amount":0,"gain":0,"in":"#3dd425ff","out":{"hex":"#b8b8b8ff","u16":[47236,47236,47236,65535]}}, +{"amount":0,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#8bc788ff","u16":[35822,51015,34927,65535]}}, +{"amount":0,"gain":1,"in":"#3dd425ff","out":{"hex":"#3dd425ff","u16":[15677,54484,9509,65535]}}, +{"amount":0,"gain":2,"in":"#3dd425ff","out":{"hex":"#33d500ff","u16":[13220,54686,0,65535]}}, +{"amount":0.1,"gain":0,"in":"#3dd425ff","out":{"hex":"#afafafff","u16":[45027,45027,45027,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#85bd82ff","u16":[34147,48629,33294,65535]}}, +{"amount":0.1,"gain":1,"in":"#3dd425ff","out":{"hex":"#3aca23ff","u16":[14944,51936,9064,65535]}}, +{"amount":0.1,"gain":2,"in":"#3dd425ff","out":{"hex":"#31cb00ff","u16":[12602,52129,0,65535]}}, +{"amount":0.25,"gain":0,"in":"#3dd425ff","out":{"hex":"#a1a1a1ff","u16":[41446,41446,41446,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#7aae77ff","u16":[31431,44761,30646,65535]}}, +{"amount":0.25,"gain":1,"in":"#3dd425ff","out":{"hex":"#36ba20ff","u16":[13755,47806,8343,65535]}}, +{"amount":0.25,"gain":2,"in":"#3dd425ff","out":{"hex":"#2dbb00ff","u16":[11600,47983,0,65535]}}, +{"amount":0.5,"gain":0,"in":"#3dd425ff","out":{"hex":"#868686ff","u16":[34470,34470,34470,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#669163ff","u16":[26141,37227,25488,65535]}}, +{"amount":0.5,"gain":1,"in":"#3dd425ff","out":{"hex":"#2d9b1bff","u16":[11440,39759,6939,65535]}}, +{"amount":0.5,"gain":2,"in":"#3dd425ff","out":{"hex":"#269b00ff","u16":[9647,39906,0,65535]}}, +{"amount":0.75,"gain":0,"in":"#3dd425ff","out":{"hex":"#626262ff","u16":[25154,25154,25154,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#4a6a48ff","u16":[19076,27166,18599,65535]}}, +{"amount":0.75,"gain":1,"in":"#3dd425ff","out":{"hex":"#207114ff","u16":[8348,29014,5064,65535]}}, +{"amount":0.75,"gain":2,"in":"#3dd425ff","out":{"hex":"#1b7100ff","u16":[7040,29121,0,65535]}}, +{"amount":0.9,"gain":0,"in":"#3dd425ff","out":{"hex":"#414141ff","u16":[16585,16585,16585,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#314630ff","u16":[12578,17912,12264,65535]}}, +{"amount":0.9,"gain":1,"in":"#3dd425ff","out":{"hex":"#154a0dff","u16":[5504,19130,3339,65535]}}, +{"amount":0.9,"gain":2,"in":"#3dd425ff","out":{"hex":"#124b00ff","u16":[4642,19201,0,65535]}}, +{"amount":1,"gain":0,"in":"#3dd425ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#3dd425ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#3dd425ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#3dd425ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#3dd425ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#3dd425ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#3dd425ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#00d3b8ff","out":{"hex":"#e1e1e1ff","u16":[57851,57851,57851,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#a4f0dfff","u16":[42216,61658,57357,65535]}}, +{"amount":-0.5,"gain":1,"in":"#00d3b8ff","out":{"hex":"#00feddff","u16":[0,65201,56858,65535]}}, +{"amount":-0.5,"gain":2,"in":"#00d3b8ff","out":{"hex":"#00feddff","u16":[0,65201,56858,65535]}}, +{"amount":0,"gain":0,"in":"#00d3b8ff","out":{"hex":"#bbbbbbff","u16":[48113,48113,48113,65535]}}, +{"amount":0,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#89c8baff","u16":[35110,51280,47703,65535]}}, +{"amount":0,"gain":1,"in":"#00d3b8ff","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"amount":0,"gain":2,"in":"#00d3b8ff","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"amount":0.1,"gain":0,"in":"#00d3b8ff","out":{"hex":"#b2b2b2ff","u16":[45864,45864,45864,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#82beb1ff","u16":[33468,48882,45472,65535]}}, +{"amount":0.1,"gain":1,"in":"#00d3b8ff","out":{"hex":"#00c9afff","u16":[0,51691,45077,65535]}}, +{"amount":0.1,"gain":2,"in":"#00d3b8ff","out":{"hex":"#00c9afff","u16":[0,51691,45077,65535]}}, +{"amount":0.25,"gain":0,"in":"#00d3b8ff","out":{"hex":"#a4a4a4ff","u16":[42216,42216,42216,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#78afa3ff","u16":[30807,44994,41856,65535]}}, +{"amount":0.25,"gain":1,"in":"#00d3b8ff","out":{"hex":"#00b9a1ff","u16":[0,47580,41492,65535]}}, +{"amount":0.25,"gain":2,"in":"#00d3b8ff","out":{"hex":"#00b9a1ff","u16":[0,47580,41492,65535]}}, +{"amount":0.5,"gain":0,"in":"#00d3b8ff","out":{"hex":"#898989ff","u16":[35110,35110,35110,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#649287ff","u16":[25621,37421,34811,65535]}}, +{"amount":0.5,"gain":1,"in":"#00d3b8ff","out":{"hex":"#009a86ff","u16":[0,39572,34508,65535]}}, +{"amount":0.5,"gain":2,"in":"#00d3b8ff","out":{"hex":"#009a86ff","u16":[0,39572,34508,65535]}}, +{"amount":0.75,"gain":0,"in":"#00d3b8ff","out":{"hex":"#646464ff","u16":[25621,25621,25621,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#496a63ff","u16":[18697,27307,25403,65535]}}, +{"amount":0.75,"gain":1,"in":"#00d3b8ff","out":{"hex":"#007062ff","u16":[0,28877,25182,65535]}}, +{"amount":0.75,"gain":2,"in":"#00d3b8ff","out":{"hex":"#007062ff","u16":[0,28877,25182,65535]}}, +{"amount":0.9,"gain":0,"in":"#00d3b8ff","out":{"hex":"#424242ff","u16":[16894,16894,16894,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#304641ff","u16":[12328,18005,16749,65535]}}, +{"amount":0.9,"gain":1,"in":"#00d3b8ff","out":{"hex":"#004a41ff","u16":[0,19040,16604,65535]}}, +{"amount":0.9,"gain":2,"in":"#00d3b8ff","out":{"hex":"#004a41ff","u16":[0,19040,16604,65535]}}, +{"amount":1,"gain":0,"in":"#00d3b8ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#00d3b8ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#00d3b8ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#00d3b8ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#00d3b8ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#00d3b8ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#00d3b8ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#b875dcff","out":{"hex":"#adadadff","u16":[44516,44516,44516,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#b875dcff","out":{"hex":"#bba5cbff","u16":[48187,42476,52053,65535]}}, +{"amount":-0.5,"gain":1,"in":"#b875dcff","out":{"hex":"#c99de3ff","u16":[51549,40311,58463,65535]}}, +{"amount":-0.5,"gain":2,"in":"#b875dcff","out":{"hex":"#d891ffff","u16":[55462,37350,65535,65535]}}, +{"amount":0,"gain":0,"in":"#b875dcff","out":{"hex":"#909090ff","u16":[37024,37024,37024,65535]}}, +{"amount":0,"gain":0.5,"in":"#b875dcff","out":{"hex":"#a583bbff","u16":[42529,33762,47984,65535]}}, +{"amount":0,"gain":1,"in":"#b875dcff","out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"amount":0,"gain":2,"in":"#b875dcff","out":{"hex":"#cd5fffff","u16":[52594,24416,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#b875dcff","out":{"hex":"#898989ff","u16":[35292,35292,35292,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#b875dcff","out":{"hex":"#a17bb7ff","u16":[41284,31667,47119,65535]}}, +{"amount":0.1,"gain":1,"in":"#b875dcff","out":{"hex":"#b46bdaff","u16":[46379,27458,56146,65535]}}, +{"amount":0.1,"gain":2,"in":"#b875dcff","out":{"hex":"#ca50ffff","u16":[51998,20632,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#b875dcff","out":{"hex":"#7e7e7eff","u16":[32485,32485,32485,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#b875dcff","out":{"hex":"#996eb2ff","u16":[39328,28169,45785,65535]}}, +{"amount":0.25,"gain":1,"in":"#b875dcff","out":{"hex":"#af59d8ff","u16":[44974,22859,55548,65535]}}, +{"amount":0.25,"gain":2,"in":"#b875dcff","out":{"hex":"#c730ffff","u16":[51088,12420,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#b875dcff","out":{"hex":"#696969ff","u16":[27018,27018,27018,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#b875dcff","out":{"hex":"#825999ff","u16":[33400,22882,39313,65535]}}, +{"amount":0.5,"gain":1,"in":"#b875dcff","out":{"hex":"#9644bbff","u16":[38574,17563,48144,65535]}}, +{"amount":0.5,"gain":2,"in":"#b875dcff","out":{"hex":"#ac00deff","u16":[44121,0,57102,65535]}}, +{"amount":0.75,"gain":0,"in":"#b875dcff","out":{"hex":"#4d4d4dff","u16":[19716,19716,19716,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#b875dcff","out":{"hex":"#5f4170ff","u16":[24373,16698,28688,65535]}}, +{"amount":0.75,"gain":1,"in":"#b875dcff","out":{"hex":"#6e3289ff","u16":[28149,12817,35133,65535]}}, +{"amount":0.75,"gain":2,"in":"#b875dcff","out":{"hex":"#7d00a2ff","u16":[32197,0,41670,65535]}}, +{"amount":0.9,"gain":0,"in":"#b875dcff","out":{"hex":"#333333ff","u16":[13000,13000,13000,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#b875dcff","out":{"hex":"#3f2b4aff","u16":[16070,11010,18915,65535]}}, +{"amount":0.9,"gain":1,"in":"#b875dcff","out":{"hex":"#48215aff","u16":[18560,8451,23165,65535]}}, +{"amount":0.9,"gain":2,"in":"#b875dcff","out":{"hex":"#53006bff","u16":[21229,0,27475,65535]}}, +{"amount":1,"gain":0,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#b875dcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#926ee4ff","out":{"hex":"#9e9e9eff","u16":[40502,40502,40502,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#a397c7ff","u16":[41871,38791,51191,65535]}}, +{"amount":-0.5,"gain":1,"in":"#926ee4ff","out":{"hex":"#a890e8ff","u16":[43188,36984,59702,65535]}}, +{"amount":-0.5,"gain":2,"in":"#926ee4ff","out":{"hex":"#ac8affff","u16":[44199,35472,65535,65535]}}, +{"amount":0,"gain":0,"in":"#926ee4ff","out":{"hex":"#838383ff","u16":[33685,33685,33685,65535]}}, +{"amount":0,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#8b79bbff","u16":[35665,31119,48106,65535]}}, +{"amount":0,"gain":1,"in":"#926ee4ff","out":{"hex":"#926ee4ff","u16":[37522,28270,58596,65535]}}, +{"amount":0,"gain":2,"in":"#926ee4ff","out":{"hex":"#9764ffff","u16":[38919,25738,65535,65535]}}, +{"amount":0.1,"gain":0,"in":"#926ee4ff","out":{"hex":"#7d7d7dff","u16":[32110,32110,32110,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#8572b9ff","u16":[34260,29290,47459,65535]}}, +{"amount":0.1,"gain":1,"in":"#926ee4ff","out":{"hex":"#8d66e3ff","u16":[36260,26099,58372,65535]}}, +{"amount":0.1,"gain":2,"in":"#926ee4ff","out":{"hex":"#935affff","u16":[37756,23189,65535,65535]}}, +{"amount":0.25,"gain":0,"in":"#926ee4ff","out":{"hex":"#737373ff","u16":[29556,29556,29556,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#7d66b5ff","u16":[32012,26256,46470,65535]}}, +{"amount":0.25,"gain":1,"in":"#926ee4ff","out":{"hex":"#8557e2ff","u16":[34261,22362,58034,65535]}}, +{"amount":0.25,"gain":2,"in":"#926ee4ff","out":{"hex":"#8c48ffff","u16":[35925,18565,65535,65535]}}, +{"amount":0.5,"gain":0,"in":"#926ee4ff","out":{"hex":"#606060ff","u16":[24581,24581,24581,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#6c4eadff","u16":[27693,20096,44355,65535]}}, +{"amount":0.5,"gain":1,"in":"#926ee4ff","out":{"hex":"#7636ddff","u16":[30432,13869,56861,65535]}}, +{"amount":0.5,"gain":2,"in":"#926ee4ff","out":{"hex":"#7e00fcff","u16":[32410,0,64815,65535]}}, +{"amount":0.75,"gain":0,"in":"#926ee4ff","out":{"hex":"#464646ff","u16":[17938,17938,17938,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#4f397eff","u16":[20208,14665,32368,65535]}}, +{"amount":0.75,"gain":1,"in":"#926ee4ff","out":{"hex":"#5627a1ff","u16":[22208,10121,41494,65535]}}, +{"amount":0.75,"gain":2,"in":"#926ee4ff","out":{"hex":"#5c00b8ff","u16":[23651,0,47298,65535]}}, +{"amount":0.9,"gain":0,"in":"#926ee4ff","out":{"hex":"#2e2e2eff","u16":[11827,11827,11827,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#342653ff","u16":[13324,9669,21342,65535]}}, +{"amount":0.9,"gain":1,"in":"#926ee4ff","out":{"hex":"#391a6aff","u16":[14643,6673,27359,65535]}}, +{"amount":0.9,"gain":2,"in":"#926ee4ff","out":{"hex":"#3d0079ff","u16":[15594,0,31186,65535]}}, +{"amount":1,"gain":0,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#926ee4ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#686b6fff","out":{"hex":"#808080ff","u16":[32962,32962,32962,65535]}}, +{"amount":-0.5,"gain":0.5,"in":"#686b6fff","out":{"hex":"#7f8083ff","u16":[32552,33013,33639,65535]}}, +{"amount":-0.5,"gain":1,"in":"#686b6fff","out":{"hex":"#7d8185ff","u16":[32137,33064,34300,65535]}}, +{"amount":-0.5,"gain":2,"in":"#686b6fff","out":{"hex":"#7a818aff","u16":[31287,33167,35579,65535]}}, +{"amount":0,"gain":0,"in":"#686b6fff","out":{"hex":"#6b6b6bff","u16":[27414,27414,27414,65535]}}, +{"amount":0,"gain":0.5,"in":"#686b6fff","out":{"hex":"#696b6dff","u16":[27073,27456,27977,65535]}}, +{"amount":0,"gain":1,"in":"#686b6fff","out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"amount":0,"gain":2,"in":"#686b6fff","out":{"hex":"#656b73ff","u16":[26021,27584,29591,65535]}}, +{"amount":0.1,"gain":0,"in":"#686b6fff","out":{"hex":"#666666ff","u16":[26132,26132,26132,65535]}}, +{"amount":0.1,"gain":0.5,"in":"#686b6fff","out":{"hex":"#646668ff","u16":[25807,26172,26669,65535]}}, +{"amount":0.1,"gain":1,"in":"#686b6fff","out":{"hex":"#63666aff","u16":[25478,26213,27193,65535]}}, +{"amount":0.1,"gain":2,"in":"#686b6fff","out":{"hex":"#61666eff","u16":[24804,26294,28207,65535]}}, +{"amount":0.25,"gain":0,"in":"#686b6fff","out":{"hex":"#5e5e5eff","u16":[24053,24053,24053,65535]}}, +{"amount":0.25,"gain":0.5,"in":"#686b6fff","out":{"hex":"#5c5e60ff","u16":[23755,24091,24548,65535]}}, +{"amount":0.25,"gain":1,"in":"#686b6fff","out":{"hex":"#5b5e61ff","u16":[23452,24128,25030,65535]}}, +{"amount":0.25,"gain":2,"in":"#686b6fff","out":{"hex":"#595e65ff","u16":[22831,24203,25963,65535]}}, +{"amount":0.5,"gain":0,"in":"#686b6fff","out":{"hex":"#4e4e4eff","u16":[20005,20005,20005,65535]}}, +{"amount":0.5,"gain":0.5,"in":"#686b6fff","out":{"hex":"#4d4e4fff","u16":[19757,20036,20416,65535]}}, +{"amount":0.5,"gain":1,"in":"#686b6fff","out":{"hex":"#4c4e51ff","u16":[19504,20067,20817,65535]}}, +{"amount":0.5,"gain":2,"in":"#686b6fff","out":{"hex":"#4a4e54ff","u16":[18988,20129,21593,65535]}}, +{"amount":0.75,"gain":0,"in":"#686b6fff","out":{"hex":"#393939ff","u16":[14598,14598,14598,65535]}}, +{"amount":0.75,"gain":0.5,"in":"#686b6fff","out":{"hex":"#38393aff","u16":[14417,14621,14898,65535]}}, +{"amount":0.75,"gain":1,"in":"#686b6fff","out":{"hex":"#37393bff","u16":[14233,14644,15191,65535]}}, +{"amount":0.75,"gain":2,"in":"#686b6fff","out":{"hex":"#36393dff","u16":[13857,14689,15758,65535]}}, +{"amount":0.9,"gain":0,"in":"#686b6fff","out":{"hex":"#252525ff","u16":[9625,9625,9625,65535]}}, +{"amount":0.9,"gain":0.5,"in":"#686b6fff","out":{"hex":"#252626ff","u16":[9506,9640,9823,65535]}}, +{"amount":0.9,"gain":1,"in":"#686b6fff","out":{"hex":"#252627ff","u16":[9385,9655,10016,65535]}}, +{"amount":0.9,"gain":2,"in":"#686b6fff","out":{"hex":"#242628ff","u16":[9136,9685,10390,65535]}}, +{"amount":1,"gain":0,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":0.5,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":1,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"gain":2,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":0.5,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":1,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"gain":2,"in":"#686b6fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"gain":0,"in":"#3daee980","out":{"hex":"#c5c5c580","u16":[50566,50566,50566,32896]}}, +{"amount":-0.5,"gain":0.5,"in":"#3daee980","out":{"hex":"#acc8e180","u16":[44188,51518,57774,32896]}}, +{"amount":-0.5,"gain":1,"in":"#3daee980","out":{"hex":"#8eccf980","u16":[36449,52450,64037,32896]}}, +{"amount":-0.5,"gain":2,"in":"#3daee980","out":{"hex":"#85cdff80","u16":[34109,52687,65535,32896]}}, +{"amount":0,"gain":0,"in":"#3daee980","out":{"hex":"#a4a4a480","u16":[42055,42055,42055,32896]}}, +{"amount":0,"gain":0.5,"in":"#3daee980","out":{"hex":"#7da9ca80","u16":[32234,43411,51893,32896]}}, +{"amount":0,"gain":1,"in":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"gain":2,"in":"#3daee980","out":{"hex":"#00aff080","u16":[0,45047,61740,32896]}}, +{"amount":0.1,"gain":0,"in":"#3daee980","out":{"hex":"#9c9c9c80","u16":[40088,40088,40088,32896]}}, +{"amount":0.1,"gain":0.5,"in":"#3daee980","out":{"hex":"#78a1c080","u16":[30726,41381,49466,32896]}}, +{"amount":0.1,"gain":1,"in":"#3daee980","out":{"hex":"#3aa6de80","u16":[14944,42627,57081,32896]}}, +{"amount":0.1,"gain":2,"in":"#3daee980","out":{"hex":"#00a7e580","u16":[0,42941,58853,32896]}}, +{"amount":0.25,"gain":0,"in":"#3daee980","out":{"hex":"#90909080","u16":[36900,36900,36900,32896]}}, +{"amount":0.25,"gain":0.5,"in":"#3daee980","out":{"hex":"#6e94b180","u16":[28282,38090,45532,32896]}}, +{"amount":0.25,"gain":1,"in":"#3daee980","out":{"hex":"#3699cc80","u16":[13755,39237,52541,32896]}}, +{"amount":0.25,"gain":2,"in":"#3daee980","out":{"hex":"#009ad380","u16":[0,39526,54172,32896]}}, +{"amount":0.5,"gain":0,"in":"#3daee980","out":{"hex":"#77777780","u16":[30689,30689,30689,32896]}}, +{"amount":0.5,"gain":0.5,"in":"#3daee980","out":{"hex":"#5c7b9380","u16":[23522,31679,37868,32896]}}, +{"amount":0.5,"gain":1,"in":"#3daee980","out":{"hex":"#2d7faa80","u16":[11440,32633,43698,32896]}}, +{"amount":0.5,"gain":2,"in":"#3daee980","out":{"hex":"#0080af80","u16":[0,32873,45054,32896]}}, +{"amount":0.75,"gain":0,"in":"#3daee980","out":{"hex":"#57575780","u16":[22395,22395,22395,32896]}}, +{"amount":0.75,"gain":0.5,"in":"#3daee980","out":{"hex":"#435a6c80","u16":[17165,23117,27634,32896]}}, +{"amount":0.75,"gain":1,"in":"#3daee980","out":{"hex":"#205d7c80","u16":[8348,23813,31888,32896]}}, +{"amount":0.75,"gain":2,"in":"#3daee980","out":{"hex":"#005d8080","u16":[0,23989,32878,32896]}}, +{"amount":0.9,"gain":0,"in":"#3daee980","out":{"hex":"#39393980","u16":[14766,14766,14766,32896]}}, +{"amount":0.9,"gain":0.5,"in":"#3daee980","out":{"hex":"#2c3b4780","u16":[11318,15242,18220,32896]}}, +{"amount":0.9,"gain":1,"in":"#3daee980","out":{"hex":"#153d5280","u16":[5504,15701,21025,32896]}}, +{"amount":0.9,"gain":2,"in":"#3daee980","out":{"hex":"#003e5480","u16":[0,15817,21678,32896]}}, +{"amount":1,"gain":0,"in":"#3daee980","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1,"gain":0.5,"in":"#3daee980","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1,"gain":1,"in":"#3daee980","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1,"gain":2,"in":"#3daee980","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1.5,"gain":0,"in":"#3daee980","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1.5,"gain":0.5,"in":"#3daee980","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1.5,"gain":1,"in":"#3daee980","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1.5,"gain":2,"in":"#3daee980","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":-0.5,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"gain":0,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"gain":0.5,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"gain":1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"gain":2,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"gain":0,"in":"#ffffff00","out":{"hex":"#f3f3f300","u16":[62470,62470,62470,0]}}, +{"amount":0.1,"gain":0.5,"in":"#ffffff00","out":{"hex":"#f3f3f300","u16":[62470,62470,62470,0]}}, +{"amount":0.1,"gain":1,"in":"#ffffff00","out":{"hex":"#f3f3f300","u16":[62470,62470,62470,0]}}, +{"amount":0.1,"gain":2,"in":"#ffffff00","out":{"hex":"#f3f3f300","u16":[62470,62470,62470,0]}}, +{"amount":0.25,"gain":0,"in":"#ffffff00","out":{"hex":"#e0e0e000","u16":[57502,57502,57502,0]}}, +{"amount":0.25,"gain":0.5,"in":"#ffffff00","out":{"hex":"#e0e0e000","u16":[57502,57502,57502,0]}}, +{"amount":0.25,"gain":1,"in":"#ffffff00","out":{"hex":"#e0e0e000","u16":[57502,57502,57502,0]}}, +{"amount":0.25,"gain":2,"in":"#ffffff00","out":{"hex":"#e0e0e000","u16":[57502,57502,57502,0]}}, +{"amount":0.5,"gain":0,"in":"#ffffff00","out":{"hex":"#bababa00","u16":[47824,47824,47824,0]}}, +{"amount":0.5,"gain":0.5,"in":"#ffffff00","out":{"hex":"#bababa00","u16":[47824,47824,47824,0]}}, +{"amount":0.5,"gain":1,"in":"#ffffff00","out":{"hex":"#bababa00","u16":[47824,47824,47824,0]}}, +{"amount":0.5,"gain":2,"in":"#ffffff00","out":{"hex":"#bababa00","u16":[47824,47824,47824,0]}}, +{"amount":0.75,"gain":0,"in":"#ffffff00","out":{"hex":"#88888800","u16":[34899,34899,34899,0]}}, +{"amount":0.75,"gain":0.5,"in":"#ffffff00","out":{"hex":"#88888800","u16":[34899,34899,34899,0]}}, +{"amount":0.75,"gain":1,"in":"#ffffff00","out":{"hex":"#88888800","u16":[34899,34899,34899,0]}}, +{"amount":0.75,"gain":2,"in":"#ffffff00","out":{"hex":"#88888800","u16":[34899,34899,34899,0]}}, +{"amount":0.9,"gain":0,"in":"#ffffff00","out":{"hex":"#5a5a5a00","u16":[23011,23011,23011,0]}}, +{"amount":0.9,"gain":0.5,"in":"#ffffff00","out":{"hex":"#5a5a5a00","u16":[23011,23011,23011,0]}}, +{"amount":0.9,"gain":1,"in":"#ffffff00","out":{"hex":"#5a5a5a00","u16":[23011,23011,23011,0]}}, +{"amount":0.9,"gain":2,"in":"#ffffff00","out":{"hex":"#5a5a5a00","u16":[23011,23011,23011,0]}}, +{"amount":1,"gain":0,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"amount":1,"gain":0.5,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"amount":1,"gain":1,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"amount":1,"gain":2,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"amount":1.5,"gain":0,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"amount":1.5,"gain":0.5,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"amount":1.5,"gain":1,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"amount":1.5,"gain":2,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"amount":-0.5,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":-0.5,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":-0.5,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":-0.5,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.1,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.1,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.1,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.1,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.25,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.25,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.25,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.25,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.5,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.5,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.5,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.5,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.75,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.75,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.75,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.75,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.9,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.9,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.9,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":0.9,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1.5,"gain":0,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1.5,"gain":0.5,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1.5,"gain":1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":1.5,"gain":2,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"amount":-0.5,"gain":0,"in":"#ff000033","out":{"hex":"#98989833","u16":[38982,38982,38982,13107]}}, +{"amount":-0.5,"gain":0.5,"in":"#ff000033","out":{"hex":"#d3828233","u16":[54235,33398,33398,13107]}}, +{"amount":-0.5,"gain":1,"in":"#ff000033","out":{"hex":"#ff676733","u16":[65535,26374,26374,13107]}}, +{"amount":-0.5,"gain":2,"in":"#ff000033","out":{"hex":"#ff676733","u16":[65535,26374,26374,13107]}}, +{"amount":0,"gain":0,"in":"#ff000033","out":{"hex":"#7e7e7e33","u16":[32421,32421,32421,13107]}}, +{"amount":0,"gain":0.5,"in":"#ff000033","out":{"hex":"#cb5c5c33","u16":[52203,23659,23659,13107]}}, +{"amount":0,"gain":1,"in":"#ff000033","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"amount":0,"gain":2,"in":"#ff000033","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"amount":0.1,"gain":0,"in":"#ff000033","out":{"hex":"#78787833","u16":[30904,30904,30904,13107]}}, +{"amount":0.1,"gain":0.5,"in":"#ff000033","out":{"hex":"#c2585833","u16":[49762,22552,22552,13107]}}, +{"amount":0.1,"gain":1,"in":"#ff000033","out":{"hex":"#f3000033","u16":[62470,0,0,13107]}}, +{"amount":0.1,"gain":2,"in":"#ff000033","out":{"hex":"#f3000033","u16":[62470,0,0,13107]}}, +{"amount":0.25,"gain":0,"in":"#ff000033","out":{"hex":"#6f6f6f33","u16":[28447,28447,28447,13107]}}, +{"amount":0.25,"gain":0.5,"in":"#ff000033","out":{"hex":"#b2515133","u16":[45804,20759,20759,13107]}}, +{"amount":0.25,"gain":1,"in":"#ff000033","out":{"hex":"#e0000033","u16":[57502,0,0,13107]}}, +{"amount":0.25,"gain":2,"in":"#ff000033","out":{"hex":"#e0000033","u16":[57502,0,0,13107]}}, +{"amount":0.5,"gain":0,"in":"#ff000033","out":{"hex":"#5c5c5c33","u16":[23659,23659,23659,13107]}}, +{"amount":0.5,"gain":0.5,"in":"#ff000033","out":{"hex":"#94434333","u16":[38095,17265,17265,13107]}}, +{"amount":0.5,"gain":1,"in":"#ff000033","out":{"hex":"#ba000033","u16":[47824,0,0,13107]}}, +{"amount":0.5,"gain":2,"in":"#ff000033","out":{"hex":"#ba000033","u16":[47824,0,0,13107]}}, +{"amount":0.75,"gain":0,"in":"#ff000033","out":{"hex":"#43434333","u16":[17265,17265,17265,13107]}}, +{"amount":0.75,"gain":0.5,"in":"#ff000033","out":{"hex":"#6c313133","u16":[27799,12599,12599,13107]}}, +{"amount":0.75,"gain":1,"in":"#ff000033","out":{"hex":"#88000033","u16":[34899,0,0,13107]}}, +{"amount":0.75,"gain":2,"in":"#ff000033","out":{"hex":"#88000033","u16":[34899,0,0,13107]}}, +{"amount":0.9,"gain":0,"in":"#ff000033","out":{"hex":"#2c2c2c33","u16":[11383,11383,11383,13107]}}, +{"amount":0.9,"gain":0.5,"in":"#ff000033","out":{"hex":"#47202033","u16":[18329,8307,8307,13107]}}, +{"amount":0.9,"gain":1,"in":"#ff000033","out":{"hex":"#5a000033","u16":[23011,0,0,13107]}}, +{"amount":0.9,"gain":2,"in":"#ff000033","out":{"hex":"#5a000033","u16":[23011,0,0,13107]}}, +{"amount":1,"gain":0,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":1,"gain":0.5,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":1,"gain":1,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":1,"gain":2,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":1.5,"gain":0,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":1.5,"gain":0.5,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":1.5,"gain":1,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"amount":1.5,"gain":2,"in":"#ff000033","out":{"hex":"#00000033","u16":[0,0,0,13107]}} +], +"shade": [ +{"chroma":-1,"in":"#000000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000000ff","luma":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000000ff","luma":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000000ff","luma":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000000ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"chroma":0,"in":"#000000ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":0.1,"out":{"hex":"#904141ff","u16":[37051,16792,16792,65535]}}, +{"chroma":1,"in":"#000000ff","luma":0.1,"out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"chroma":-1,"in":"#000000ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"chroma":0,"in":"#000000ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":0.5,"out":{"hex":"#e0aeaeff","u16":[57502,44773,44773,65535]}}, +{"chroma":1,"in":"#000000ff","luma":0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"chroma":-1,"in":"#000000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#000000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#000000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#000000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#000000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#000000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#000000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":-0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":-0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":-0.5,"out":{"hex":"#e0aeaeff","u16":[57502,44773,44773,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":-0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":-0.1,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":-0.1,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":-0.1,"out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":-0.1,"out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffffffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffffffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffffffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffffffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#010101ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#010101ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#010101ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#010101ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#010101ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#010101ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#010101ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#010101ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":0,"out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"chroma":0,"in":"#010101ff","luma":0,"out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":0,"out":{"hex":"#020101ff","u16":[414,188,188,65535]}}, +{"chroma":1,"in":"#010101ff","luma":0,"out":{"hex":"#020000ff","u16":[520,0,0,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"chroma":0,"in":"#010101ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":0.1,"out":{"hex":"#904141ff","u16":[37052,16792,16792,65535]}}, +{"chroma":1,"in":"#010101ff","luma":0.1,"out":{"hex":"#b50000ff","u16":[46515,0,0,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"chroma":0,"in":"#010101ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":0.5,"out":{"hex":"#e0aeaeff","u16":[57502,44773,44773,65535]}}, +{"chroma":1,"in":"#010101ff","luma":0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#010101ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#010101ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#010101ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#010101ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#010101ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#010101ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#020202ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#020202ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#020202ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#020202ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#020202ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#020202ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#020202ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#020202ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":0,"out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"chroma":0,"in":"#020202ff","luma":0,"out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":0,"out":{"hex":"#030101ff","u16":[828,375,375,65535]}}, +{"chroma":1,"in":"#020202ff","luma":0,"out":{"hex":"#040000ff","u16":[1039,0,0,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23013,23013,23013,65535]}}, +{"chroma":0,"in":"#020202ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23013,23013,23013,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":0.1,"out":{"hex":"#904141ff","u16":[37055,16794,16794,65535]}}, +{"chroma":1,"in":"#020202ff","luma":0.1,"out":{"hex":"#b50000ff","u16":[46519,0,0,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47825,47825,47825,65535]}}, +{"chroma":0,"in":"#020202ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47825,47825,47825,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":0.5,"out":{"hex":"#e0aeaeff","u16":[57502,44774,44774,65535]}}, +{"chroma":1,"in":"#020202ff","luma":0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41451,41451,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#020202ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#020202ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#020202ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#020202ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#020202ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#020202ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#040404ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#040404ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#040404ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#040404ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#040404ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#040404ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#040404ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#040404ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":0,"out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"chroma":0,"in":"#040404ff","luma":0,"out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":0,"out":{"hex":"#060303ff","u16":[1655,750,750,65535]}}, +{"chroma":1,"in":"#040404ff","luma":0,"out":{"hex":"#080000ff","u16":[2078,0,0,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23022,23022,23022,65535]}}, +{"chroma":0,"in":"#040404ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23022,23022,23022,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":0.1,"out":{"hex":"#904141ff","u16":[37069,16800,16800,65535]}}, +{"chroma":1,"in":"#040404ff","luma":0.1,"out":{"hex":"#b50000ff","u16":[46536,0,0,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47828,47828,47828,65535]}}, +{"chroma":0,"in":"#040404ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47828,47828,47828,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":0.5,"out":{"hex":"#e0aeaeff","u16":[57504,44778,44778,65535]}}, +{"chroma":1,"in":"#040404ff","luma":0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41456,41456,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#040404ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#040404ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#040404ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#040404ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#040404ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#040404ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#080808ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#080808ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#080808ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#080808ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#080808ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#080808ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#080808ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#080808ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":0,"out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"chroma":0,"in":"#080808ff","luma":0,"out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":0,"out":{"hex":"#0d0606ff","u16":[3311,1500,1500,65535]}}, +{"chroma":1,"in":"#080808ff","luma":0,"out":{"hex":"#100000ff","u16":[4156,0,0,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23062,23062,23062,65535]}}, +{"chroma":0,"in":"#080808ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23062,23062,23062,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":0.1,"out":{"hex":"#904141ff","u16":[37134,16829,16829,65535]}}, +{"chroma":1,"in":"#080808ff","luma":0.1,"out":{"hex":"#b50000ff","u16":[46618,0,0,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47845,47845,47845,65535]}}, +{"chroma":0,"in":"#080808ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47845,47845,47845,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":0.5,"out":{"hex":"#e0aeaeff","u16":[57511,44799,44799,65535]}}, +{"chroma":1,"in":"#080808ff","luma":0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41481,41481,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#080808ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#080808ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#080808ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#080808ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#080808ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#080808ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#101010ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#101010ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#101010ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#101010ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#101010ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#101010ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#101010ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#101010ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":0,"out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"chroma":0,"in":"#101010ff","luma":0,"out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":0,"out":{"hex":"#1a0c0cff","u16":[6621,3001,3001,65535]}}, +{"chroma":1,"in":"#101010ff","luma":0,"out":{"hex":"#200000ff","u16":[8312,0,0,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23246,23246,23246,65535]}}, +{"chroma":0,"in":"#101010ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23246,23246,23246,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":0.1,"out":{"hex":"#924242ff","u16":[37430,16963,16963,65535]}}, +{"chroma":1,"in":"#101010ff","luma":0.1,"out":{"hex":"#b70000ff","u16":[46989,0,0,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47922,47922,47922,65535]}}, +{"chroma":0,"in":"#101010ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47922,47922,47922,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":0.5,"out":{"hex":"#e0afafff","u16":[57541,44893,44893,65535]}}, +{"chroma":1,"in":"#101010ff","luma":0.5,"out":{"hex":"#ffa2a2ff","u16":[65535,41597,41597,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#101010ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#101010ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#101010ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#101010ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#101010ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#101010ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#202020ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#202020ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#202020ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#202020ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#202020ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#202020ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#202020ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#202020ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":0,"out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"chroma":0,"in":"#202020ff","luma":0,"out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":0,"out":{"hex":"#341717ff","u16":[13242,6001,6001,65535]}}, +{"chroma":1,"in":"#202020ff","luma":0,"out":{"hex":"#410000ff","u16":[16624,0,0,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":0.1,"out":{"hex":"#5e5e5eff","u16":[24069,24069,24069,65535]}}, +{"chroma":0,"in":"#202020ff","luma":0.1,"out":{"hex":"#5e5e5eff","u16":[24069,24069,24069,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":0.1,"out":{"hex":"#974444ff","u16":[38755,17564,17564,65535]}}, +{"chroma":1,"in":"#202020ff","luma":0.1,"out":{"hex":"#bd0000ff","u16":[48653,0,0,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":0.5,"out":{"hex":"#bcbcbcff","u16":[48273,48273,48273,65535]}}, +{"chroma":0,"in":"#202020ff","luma":0.5,"out":{"hex":"#bcbcbcff","u16":[48273,48273,48273,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":0.5,"out":{"hex":"#e0b0b0ff","u16":[57683,45324,45324,65535]}}, +{"chroma":1,"in":"#202020ff","luma":0.5,"out":{"hex":"#ffa4a4ff","u16":[65535,42124,42124,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#202020ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#202020ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#202020ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#202020ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#202020ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#202020ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#404040ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#404040ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#404040ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#404040ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#404040ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#404040ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#404040ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#404040ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":0,"out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"chroma":0,"in":"#404040ff","luma":0,"out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":0,"out":{"hex":"#672f2fff","u16":[26484,12003,12003,65535]}}, +{"chroma":1,"in":"#404040ff","luma":0,"out":{"hex":"#810000ff","u16":[33248,0,0,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":0.1,"out":{"hex":"#6b6b6bff","u16":[27480,27480,27480,65535]}}, +{"chroma":0,"in":"#404040ff","luma":0.1,"out":{"hex":"#6b6b6bff","u16":[27480,27480,27480,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":0.1,"out":{"hex":"#ac4e4eff","u16":[44248,20053,20053,65535]}}, +{"chroma":1,"in":"#404040ff","luma":0.1,"out":{"hex":"#d80000ff","u16":[55549,0,0,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":0.5,"out":{"hex":"#c2c2c2ff","u16":[49849,49849,49849,65535]}}, +{"chroma":0,"in":"#404040ff","luma":0.5,"out":{"hex":"#c2c2c2ff","u16":[49849,49849,49849,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":0.5,"out":{"hex":"#e3b8b8ff","u16":[58327,47242,47242,65535]}}, +{"chroma":1,"in":"#404040ff","luma":0.5,"out":{"hex":"#ffadadff","u16":[65535,44450,44450,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#404040ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#404040ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#404040ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#404040ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#404040ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#404040ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":-0.1,"out":{"hex":"#606060ff","u16":[24594,24594,24594,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":-0.1,"out":{"hex":"#606060ff","u16":[24594,24594,24594,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":-0.1,"out":{"hex":"#9a4646ff","u16":[39600,17947,17947,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":-0.1,"out":{"hex":"#c10000ff","u16":[49714,0,0,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":0,"out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":0,"out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":0,"out":{"hex":"#cb5d5dff","u16":[52265,24019,24019,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":0,"out":{"hex":"#ff1515ff","u16":[65535,5339,5339,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":0.1,"out":{"hex":"#979797ff","u16":[38807,38807,38807,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":0.1,"out":{"hex":"#979797ff","u16":[38807,38807,38807,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":0.1,"out":{"hex":"#d38181ff","u16":[54177,33158,33158,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":0.1,"out":{"hex":"#ff6565ff","u16":[65535,26017,26017,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":0.5,"out":{"hex":"#dbdbdbff","u16":[56294,56294,56294,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":0.5,"out":{"hex":"#dbdbdbff","u16":[56294,56294,56294,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":0.5,"out":{"hex":"#eed6d6ff","u16":[61124,54901,54901,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":0.5,"out":{"hex":"#ffd0d0ff","u16":[65535,53465,53465,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#7f7f7fff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#7f7f7fff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#7f7f7fff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#7f7f7fff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#808080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#808080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#808080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#808080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#808080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#808080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":-0.1,"out":{"hex":"#616161ff","u16":[24953,24953,24953,65535]}}, +{"chroma":0,"in":"#808080ff","luma":-0.1,"out":{"hex":"#616161ff","u16":[24953,24953,24953,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":-0.1,"out":{"hex":"#9c4747ff","u16":[40179,18209,18209,65535]}}, +{"chroma":1,"in":"#808080ff","luma":-0.1,"out":{"hex":"#c40000ff","u16":[50441,0,0,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":0,"out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"chroma":0,"in":"#808080ff","luma":0,"out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":0,"out":{"hex":"#cc5f5fff","u16":[52338,24438,24438,65535]}}, +{"chroma":1,"in":"#808080ff","luma":0,"out":{"hex":"#ff1e1eff","u16":[65535,7619,7619,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":0.1,"out":{"hex":"#989898ff","u16":[39016,39016,39016,65535]}}, +{"chroma":0,"in":"#808080ff","luma":0.1,"out":{"hex":"#989898ff","u16":[39016,39016,39016,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":0.1,"out":{"hex":"#d38282ff","u16":[54247,33445,33445,65535]}}, +{"chroma":1,"in":"#808080ff","luma":0.1,"out":{"hex":"#ff6767ff","u16":[65535,26444,26444,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":0.5,"out":{"hex":"#dcdcdcff","u16":[56428,56428,56428,65535]}}, +{"chroma":0,"in":"#808080ff","luma":0.5,"out":{"hex":"#dcdcdcff","u16":[56428,56428,56428,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":0.5,"out":{"hex":"#eed6d6ff","u16":[61185,55058,55058,65535]}}, +{"chroma":1,"in":"#808080ff","luma":0.5,"out":{"hex":"#ffd1d1ff","u16":[65535,53646,53646,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#808080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#808080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#808080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#808080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#808080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#808080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#818181ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#818181ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#818181ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#818181ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#818181ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#818181ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":-0.1,"out":{"hex":"#626262ff","u16":[25310,25310,25310,65535]}}, +{"chroma":0,"in":"#818181ff","luma":-0.1,"out":{"hex":"#626262ff","u16":[25310,25310,25310,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":-0.1,"out":{"hex":"#9f4848ff","u16":[40754,18470,18470,65535]}}, +{"chroma":1,"in":"#818181ff","luma":-0.1,"out":{"hex":"#c70000ff","u16":[51162,0,0,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":0,"out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"chroma":0,"in":"#818181ff","luma":0,"out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":0,"out":{"hex":"#cc6161ff","u16":[52412,24852,24852,65535]}}, +{"chroma":1,"in":"#818181ff","luma":0,"out":{"hex":"#ff2424ff","u16":[65535,9292,9292,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":0.1,"out":{"hex":"#999999ff","u16":[39226,39226,39226,65535]}}, +{"chroma":0,"in":"#818181ff","luma":0.1,"out":{"hex":"#999999ff","u16":[39226,39226,39226,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":0.1,"out":{"hex":"#d38383ff","u16":[54318,33730,33730,65535]}}, +{"chroma":1,"in":"#818181ff","luma":0.1,"out":{"hex":"#ff6969ff","u16":[65535,26866,26866,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":0.5,"out":{"hex":"#dcdcdcff","u16":[56563,56563,56563,65535]}}, +{"chroma":0,"in":"#818181ff","luma":0.5,"out":{"hex":"#dcdcdcff","u16":[56563,56563,56563,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":0.5,"out":{"hex":"#eed7d7ff","u16":[61246,55216,55216,65535]}}, +{"chroma":1,"in":"#818181ff","luma":0.5,"out":{"hex":"#ffd1d1ff","u16":[65535,53828,53828,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#818181ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#818181ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#818181ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#818181ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#818181ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#818181ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":-0.5,"out":{"hex":"#333333ff","u16":[13216,13216,13216,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":-0.5,"out":{"hex":"#333333ff","u16":[13216,13216,13216,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":-0.5,"out":{"hex":"#532626ff","u16":[21280,9644,9644,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":-0.5,"out":{"hex":"#680000ff","u16":[26714,0,0,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":-0.1,"out":{"hex":"#aeaeaeff","u16":[44632,44632,44632,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":-0.1,"out":{"hex":"#aeaeaeff","u16":[44632,44632,44632,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":-0.1,"out":{"hex":"#db9f9fff","u16":[56258,40798,40798,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":-0.1,"out":{"hex":"#ff8e8eff","u16":[65535,36474,36474,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":0,"out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":0,"out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":0,"out":{"hex":"#e2b4b4ff","u16":[58014,46317,46317,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":0,"out":{"hex":"#ffa9a9ff","u16":[65535,43333,43333,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":0.1,"out":{"hex":"#cfcfcfff","u16":[53103,53103,53103,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":0.1,"out":{"hex":"#cfcfcfff","u16":[53103,53103,53103,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":0.1,"out":{"hex":"#e8c7c7ff","u16":[59708,51141,51141,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":0.1,"out":{"hex":"#ffbfbfff","u16":[65535,49086,49086,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#bfbfbfff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#bfbfbfff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#bfbfbfff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#bfbfbfff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":-0.5,"out":{"hex":"#383838ff","u16":[14397,14397,14397,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":-0.5,"out":{"hex":"#383838ff","u16":[14397,14397,14397,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":-0.5,"out":{"hex":"#5a2929ff","u16":[23182,10506,10506,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":-0.5,"out":{"hex":"#710000ff","u16":[29102,0,0,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":-0.1,"out":{"hex":"#afafafff","u16":[44920,44920,44920,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":-0.1,"out":{"hex":"#afafafff","u16":[44920,44920,44920,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":-0.1,"out":{"hex":"#dba0a0ff","u16":[56367,41162,41162,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":-0.1,"out":{"hex":"#ff9090ff","u16":[65535,36938,36938,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":0,"out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":0,"out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":0,"out":{"hex":"#e2b5b5ff","u16":[58119,46630,46630,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":0,"out":{"hex":"#ffaaaaff","u16":[65535,43711,43711,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":0.1,"out":{"hex":"#d0d0d0ff","u16":[53336,53336,53336,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":0.1,"out":{"hex":"#d0d0d0ff","u16":[53336,53336,53336,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":0.1,"out":{"hex":"#e9c8c8ff","u16":[59810,51419,51419,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":0.1,"out":{"hex":"#ffc0c0ff","u16":[65535,49412,49412,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#c0c0c0ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#c0c0c0ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#c0c0c0ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#c0c0c0ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":-0.5,"out":{"hex":"#b7b7b7ff","u16":[47070,47070,47070,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":-0.5,"out":{"hex":"#b7b7b7ff","u16":[47070,47070,47070,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":-0.5,"out":{"hex":"#dfababff","u16":[57202,43844,43844,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":-0.5,"out":{"hex":"#ff9d9dff","u16":[65535,40304,40304,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":-0.1,"out":{"hex":"#f1f1f1ff","u16":[61926,61926,61926,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":-0.1,"out":{"hex":"#f1f1f1ff","u16":[61926,61926,61926,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":-0.1,"out":{"hex":"#f8efefff","u16":[63761,61419,61419,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":-0.1,"out":{"hex":"#ffededff","u16":[65535,60907,60907,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":0,"out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":0,"out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":0,"out":{"hex":"#fefdfdff","u16":[65279,64951,64951,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":0,"out":{"hex":"#fffcfcff","u16":[65535,64881,64881,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fdfdfdff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fdfdfdff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fdfdfdff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fdfdfdff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":-0.5,"out":{"hex":"#b9b9b9ff","u16":[47448,47448,47448,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":-0.5,"out":{"hex":"#b9b9b9ff","u16":[47448,47448,47448,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":-0.5,"out":{"hex":"#dfacacff","u16":[57352,44310,44310,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":-0.5,"out":{"hex":"#ff9f9fff","u16":[65535,40880,40880,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":-0.1,"out":{"hex":"#f2f2f2ff","u16":[62198,62198,62198,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":-0.1,"out":{"hex":"#f2f2f2ff","u16":[62198,62198,62198,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":-0.1,"out":{"hex":"#f9f0f0ff","u16":[63893,61731,61731,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":-0.1,"out":{"hex":"#ffeeeeff","u16":[65535,61260,61260,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":0,"out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":0,"out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":0,"out":{"hex":"#fffefeff","u16":[65407,65243,65243,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":0,"out":{"hex":"#fffefeff","u16":[65535,65208,65208,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fefefeff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fefefeff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fefefeff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fefefeff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":-0.1,"out":{"hex":"#5e5e5eff","u16":[24286,24286,24286,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":-0.1,"out":{"hex":"#bf0000ff","u16":[49092,0,0,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":-0.1,"out":{"hex":"#bf0000ff","u16":[49092,0,0,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":-0.1,"out":{"hex":"#bf0000ff","u16":[49092,0,0,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":0,"out":{"hex":"#7e7e7eff","u16":[32421,32421,32421,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":0,"out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":0,"out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":0,"out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":0.1,"out":{"hex":"#969696ff","u16":[38630,38630,38630,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":0.1,"out":{"hex":"#ff6464ff","u16":[65535,25651,25651,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":0.1,"out":{"hex":"#ff6464ff","u16":[65535,25651,25651,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":0.1,"out":{"hex":"#ff6464ff","u16":[65535,25651,25651,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":0.5,"out":{"hex":"#dbdbdbff","u16":[56180,56180,56180,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":0.5,"out":{"hex":"#ffcfcfff","u16":[65535,53312,53312,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":0.5,"out":{"hex":"#ffcfcfff","u16":[65535,53312,53312,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":0.5,"out":{"hex":"#ffcfcfff","u16":[65535,53312,53312,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ff0000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ff0000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ff0000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ff0000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":-0.5,"out":{"hex":"#7f7f7fff","u16":[32600,32600,32600,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":-0.5,"out":{"hex":"#009400ff","u16":[0,37966,0,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":-0.5,"out":{"hex":"#009400ff","u16":[0,37966,0,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":-0.5,"out":{"hex":"#009400ff","u16":[0,37966,0,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":-0.1,"out":{"hex":"#ccccccff","u16":[52550,52550,52550,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":-0.1,"out":{"hex":"#00ee00ff","u16":[0,61199,0,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":-0.1,"out":{"hex":"#00ee00ff","u16":[0,61199,0,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":-0.1,"out":{"hex":"#00ee00ff","u16":[0,61199,0,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":0,"out":{"hex":"#dbdbdbff","u16":[56274,56274,56274,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":0,"out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":0,"out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":0,"out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":0.1,"out":{"hex":"#e8e8e8ff","u16":[59723,59723,59723,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":0.1,"out":{"hex":"#9eff9eff","u16":[40725,65535,40725,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":0.1,"out":{"hex":"#9eff9eff","u16":[40725,65535,40725,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":0.1,"out":{"hex":"#9eff9eff","u16":[40725,65535,40725,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00ff00ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00ff00ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00ff00ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00ff00ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":0,"out":{"hex":"#4d4d4dff","u16":[19844,19844,19844,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":0,"out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":0,"out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":0,"out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":0.1,"out":{"hex":"#737373ff","u16":[29459,29459,29459,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":0.1,"out":{"hex":"#5d5dffff","u16":[23808,23808,65535,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":0.1,"out":{"hex":"#5d5dffff","u16":[23808,23808,65535,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":0.1,"out":{"hex":"#5d5dffff","u16":[23808,23808,65535,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":0.5,"out":{"hex":"#c6c6c6ff","u16":[50847,50847,50847,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":0.5,"out":{"hex":"#c1c1ffff","u16":[49481,49481,65535,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":0.5,"out":{"hex":"#c1c1ffff","u16":[49481,49481,65535,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":0.5,"out":{"hex":"#c1c1ffff","u16":[49481,49481,65535,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#0000ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#0000ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#0000ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#0000ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":-0.5,"out":{"hex":"#adadadff","u16":[44551,44551,44551,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":-0.5,"out":{"hex":"#b3b300ff","u16":[46094,46094,0,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":-0.5,"out":{"hex":"#b3b300ff","u16":[46094,46094,0,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":-0.5,"out":{"hex":"#b3b300ff","u16":[46094,46094,0,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":-0.1,"out":{"hex":"#eaeaeaff","u16":[60140,60140,60140,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":-0.1,"out":{"hex":"#f2f200ff","u16":[62224,62224,0,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":-0.1,"out":{"hex":"#f2f200ff","u16":[62224,62224,0,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":-0.1,"out":{"hex":"#f2f200ff","u16":[62224,62224,0,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":0,"out":{"hex":"#f6f6f6ff","u16":[63340,63340,63340,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":0,"out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":0,"out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":0,"out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ffff00ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ffff00ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ffff00ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ffff00ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":-0.5,"out":{"hex":"#919191ff","u16":[37182,37182,37182,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":-0.5,"out":{"hex":"#00a1a1ff","u16":[0,41449,41449,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":-0.5,"out":{"hex":"#00a1a1ff","u16":[0,41449,41449,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":-0.5,"out":{"hex":"#00a1a1ff","u16":[0,41449,41449,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":-0.1,"out":{"hex":"#d7d7d7ff","u16":[55268,55268,55268,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":-0.1,"out":{"hex":"#00f0f0ff","u16":[0,61611,61611,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":-0.1,"out":{"hex":"#00f0f0ff","u16":[0,61611,61611,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":-0.1,"out":{"hex":"#00f0f0ff","u16":[0,61611,61611,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":0,"out":{"hex":"#e5e5e5ff","u16":[58788,58788,58788,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":0,"out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":0,"out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":0,"out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":0.1,"out":{"hex":"#f2f2f2ff","u16":[62071,62071,62071,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":0.1,"out":{"hex":"#b5ffffff","u16":[46514,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":0.1,"out":{"hex":"#b5ffffff","u16":[46514,65535,65535,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":0.1,"out":{"hex":"#b5ffffff","u16":[46514,65535,65535,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00ffffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00ffffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00ffffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00ffffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":-0.1,"out":{"hex":"#767676ff","u16":[30420,30420,30420,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":-0.1,"out":{"hex":"#d100d1ff","u16":[53838,0,53838,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":-0.1,"out":{"hex":"#d100d1ff","u16":[53838,0,53838,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":-0.1,"out":{"hex":"#d100d1ff","u16":[53838,0,53838,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":0,"out":{"hex":"#909090ff","u16":[37029,37029,37029,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":0,"out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":0,"out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":0,"out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":0.1,"out":{"hex":"#a5a5a5ff","u16":[42456,42456,42456,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":0.1,"out":{"hex":"#ff68ffff","u16":[65535,26798,65535,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":0.1,"out":{"hex":"#ff68ffff","u16":[65535,26798,65535,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":0.1,"out":{"hex":"#ff68ffff","u16":[65535,26798,65535,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":0.5,"out":{"hex":"#e4e4e4ff","u16":[58700,58700,58700,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":0.5,"out":{"hex":"#ffd9ffff","u16":[65535,55694,65535,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":0.5,"out":{"hex":"#ffd9ffff","u16":[65535,55694,65535,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":0.5,"out":{"hex":"#ffd9ffff","u16":[65535,55694,65535,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ff00ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ff00ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ff00ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ff00ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#800000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#800000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#800000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#800000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#800000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#800000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#800000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#800000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":0,"out":{"hex":"#3f3f3fff","u16":[16274,16274,16274,65535]}}, +{"chroma":0,"in":"#800000ff","luma":0,"out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":0,"out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"chroma":1,"in":"#800000ff","luma":0,"out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":0.1,"out":{"hex":"#6b6b6bff","u16":[27387,27387,27387,65535]}}, +{"chroma":0,"in":"#800000ff","luma":0.1,"out":{"hex":"#d70000ff","u16":[55359,0,0,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":0.1,"out":{"hex":"#d70000ff","u16":[55359,0,0,65535]}}, +{"chroma":1,"in":"#800000ff","luma":0.1,"out":{"hex":"#d70000ff","u16":[55359,0,0,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":0.5,"out":{"hex":"#c2c2c2ff","u16":[49803,49803,49803,65535]}}, +{"chroma":0,"in":"#800000ff","luma":0.5,"out":{"hex":"#ffadadff","u16":[65535,44383,44383,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":0.5,"out":{"hex":"#ffadadff","u16":[65535,44383,44383,65535]}}, +{"chroma":1,"in":"#800000ff","luma":0.5,"out":{"hex":"#ffadadff","u16":[65535,44383,44383,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#800000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#800000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#800000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#800000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#800000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#800000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#008000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#008000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#008000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#008000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#008000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#008000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":-0.1,"out":{"hex":"#454545ff","u16":[17822,17822,17822,65535]}}, +{"chroma":0,"in":"#008000ff","luma":-0.1,"out":{"hex":"#005100ff","u16":[0,20755,0,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":-0.1,"out":{"hex":"#005100ff","u16":[0,20755,0,65535]}}, +{"chroma":1,"in":"#008000ff","luma":-0.1,"out":{"hex":"#005100ff","u16":[0,20755,0,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":0,"out":{"hex":"#6e6e6eff","u16":[28247,28247,28247,65535]}}, +{"chroma":0,"in":"#008000ff","luma":0,"out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":0,"out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"chroma":1,"in":"#008000ff","luma":0,"out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":0.1,"out":{"hex":"#8a8a8aff","u16":[35340,35340,35340,65535]}}, +{"chroma":0,"in":"#008000ff","luma":0.1,"out":{"hex":"#00a000ff","u16":[0,41156,0,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":0.1,"out":{"hex":"#00a000ff","u16":[0,41156,0,65535]}}, +{"chroma":1,"in":"#008000ff","luma":0.1,"out":{"hex":"#00a000ff","u16":[0,41156,0,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":0.5,"out":{"hex":"#d3d3d3ff","u16":[54144,54144,54144,65535]}}, +{"chroma":0,"in":"#008000ff","luma":0.5,"out":{"hex":"#00f500ff","u16":[0,63055,0,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":0.5,"out":{"hex":"#00f500ff","u16":[0,63055,0,65535]}}, +{"chroma":1,"in":"#008000ff","luma":0.5,"out":{"hex":"#00f500ff","u16":[0,63055,0,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#008000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#008000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#008000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#008000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#008000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#008000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000080ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000080ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":0,"out":{"hex":"#272727ff","u16":[9961,9961,9961,65535]}}, +{"chroma":0,"in":"#000080ff","luma":0,"out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":0,"out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"chroma":1,"in":"#000080ff","luma":0,"out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":0.1,"out":{"hex":"#606060ff","u16":[24602,24602,24602,65535]}}, +{"chroma":0,"in":"#000080ff","luma":0.1,"out":{"hex":"#4040ffff","u16":[16333,16333,65535,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":0.1,"out":{"hex":"#4040ffff","u16":[16333,16333,65535,65535]}}, +{"chroma":1,"in":"#000080ff","luma":0.1,"out":{"hex":"#4040ffff","u16":[16333,16333,65535,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":0.5,"out":{"hex":"#bdbdbdff","u16":[48507,48507,48507,65535]}}, +{"chroma":0,"in":"#000080ff","luma":0.5,"out":{"hex":"#b6b6ffff","u16":[46863,46863,65535,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":0.5,"out":{"hex":"#b6b6ffff","u16":[46863,46863,65535,65535]}}, +{"chroma":1,"in":"#000080ff","luma":0.5,"out":{"hex":"#b6b6ffff","u16":[46863,46863,65535,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#000080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#000080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#000080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#000080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#000080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#000080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":-0.1,"out":{"hex":"#909090ff","u16":[37067,37067,37067,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":-0.1,"out":{"hex":"#ff5656ff","u16":[65535,22212,22212,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":-0.1,"out":{"hex":"#ff5656ff","u16":[65535,22212,22212,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":-0.1,"out":{"hex":"#ff5656ff","u16":[65535,22212,22212,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":0,"out":{"hex":"#a5a5a5ff","u16":[42489,42489,42489,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":0,"out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":0,"out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":0,"out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":0.1,"out":{"hex":"#b8b8b8ff","u16":[47186,47186,47186,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":0.1,"out":{"hex":"#ff9e9eff","u16":[65535,40482,40482,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":0.1,"out":{"hex":"#ff9e9eff","u16":[65535,40482,40482,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":0.1,"out":{"hex":"#ff9e9eff","u16":[65535,40482,40482,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":0.5,"out":{"hex":"#f1f1f1ff","u16":[62009,62009,62009,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":0.5,"out":{"hex":"#ffededff","u16":[65535,61015,61015,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":0.5,"out":{"hex":"#ffededff","u16":[65535,61015,61015,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":0.5,"out":{"hex":"#ffededff","u16":[65535,61015,61015,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#ff8080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#ff8080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#ff8080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#ff8080ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":-0.5,"out":{"hex":"#8e8e8eff","u16":[36607,36607,36607,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":-0.5,"out":{"hex":"#00a600ff","u16":[0,42632,0,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":-0.5,"out":{"hex":"#00a600ff","u16":[0,42632,0,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":-0.5,"out":{"hex":"#00a600ff","u16":[0,42632,0,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":-0.1,"out":{"hex":"#d6d6d6ff","u16":[54913,54913,54913,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":-0.1,"out":{"hex":"#00f900ff","u16":[0,63951,0,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":-0.1,"out":{"hex":"#00f900ff","u16":[0,63951,0,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":-0.1,"out":{"hex":"#00f900ff","u16":[0,63951,0,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":0,"out":{"hex":"#e3e3e3ff","u16":[58458,58458,58458,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":0,"out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":0,"out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":0,"out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":0.1,"out":{"hex":"#f0f0f0ff","u16":[61763,61763,61763,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":0.1,"out":{"hex":"#c6ffc6ff","u16":[50784,65535,50784,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":0.1,"out":{"hex":"#c6ffc6ff","u16":[50784,65535,50784,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":0.1,"out":{"hex":"#c6ffc6ff","u16":[50784,65535,50784,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#80ff80ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#80ff80ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#80ff80ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#80ff80ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":-0.1,"out":{"hex":"#747474ff","u16":[29743,29743,29743,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":-0.1,"out":{"hex":"#5e5effff","u16":[24201,24201,65535,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":-0.1,"out":{"hex":"#5e5effff","u16":[24201,24201,65535,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":-0.1,"out":{"hex":"#5e5effff","u16":[24201,24201,65535,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":0,"out":{"hex":"#8e8e8eff","u16":[36496,36496,36496,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":0,"out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":0,"out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":0,"out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":0.1,"out":{"hex":"#a3a3a3ff","u16":[42006,42006,42006,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":0.1,"out":{"hex":"#9999ffff","u16":[39445,39445,65535,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":0.1,"out":{"hex":"#9999ffff","u16":[39445,39445,65535,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":0.1,"out":{"hex":"#9999ffff","u16":[39445,39445,65535,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":0.5,"out":{"hex":"#e3e3e3ff","u16":[58395,58395,58395,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":0.5,"out":{"hex":"#e1e1ffff","u16":[57795,57795,65535,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":0.5,"out":{"hex":"#e1e1ffff","u16":[57795,57795,65535,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":0.5,"out":{"hex":"#e1e1ffff","u16":[57795,57795,65535,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#8080ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#8080ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#8080ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#8080ffff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#030201ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#030201ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#030201ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#030201ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#030201ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#030201ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#030201ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#030201ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":0,"out":{"hex":"#020202ff","u16":[569,569,569,65535]}}, +{"chroma":0,"in":"#030201ff","luma":0,"out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":0,"out":{"hex":"#030200ff","u16":[806,502,0,65535]}}, +{"chroma":1,"in":"#030201ff","luma":0,"out":{"hex":"#030200ff","u16":[806,502,0,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23014,23014,23014,65535]}}, +{"chroma":0,"in":"#030201ff","luma":0.1,"out":{"hex":"#795128ff","u16":[31195,20796,10398,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":0.1,"out":{"hex":"#7f4f00ff","u16":[32614,20291,0,65535]}}, +{"chroma":1,"in":"#030201ff","luma":0.1,"out":{"hex":"#7f4f00ff","u16":[32614,20291,0,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47825,47825,47825,65535]}}, +{"chroma":0,"in":"#030201ff","luma":0.5,"out":{"hex":"#f5ab69ff","u16":[62877,43864,26992,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":0.5,"out":{"hex":"#ffa74aff","u16":[65535,42973,19125,65535]}}, +{"chroma":1,"in":"#030201ff","luma":0.5,"out":{"hex":"#ffa74aff","u16":[65535,42973,19125,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#030201ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#030201ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#030201ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#030201ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#030201ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#030201ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":-0.5,"out":{"hex":"#b9b9b9ff","u16":[47501,47501,47501,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":-0.5,"out":{"hex":"#efae00ff","u16":[61361,44729,0,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":-0.5,"out":{"hex":"#efae00ff","u16":[61361,44729,0,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":-0.5,"out":{"hex":"#efae00ff","u16":[61361,44729,0,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":-0.1,"out":{"hex":"#f2f2f2ff","u16":[62237,62237,62237,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":-0.1,"out":{"hex":"#fff0e0ff","u16":[65535,61676,57523,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":-0.1,"out":{"hex":"#fff0e0ff","u16":[65535,61676,57523,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":-0.1,"out":{"hex":"#fff0e0ff","u16":[65535,61676,57523,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":0,"out":{"hex":"#fefefeff","u16":[65314,65314,65314,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":0,"out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":0,"out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":0,"out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fffefdff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fffefdff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fffefdff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fffefdff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":-0.1,"out":{"hex":"#616161ff","u16":[24903,24903,24903,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":-0.1,"out":{"hex":"#606162ff","u16":[24736,24931,25125,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":-0.1,"out":{"hex":"#46657bff","u16":[17927,25829,31591,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":-0.1,"out":{"hex":"#00688eff","u16":[0,26667,36621,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":0,"out":{"hex":"#808080ff","u16":[32860,32860,32860,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":0,"out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":0,"out":{"hex":"#5c85a2ff","u16":[23655,34082,41684,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":0,"out":{"hex":"#0089bcff","u16":[0,35187,48321,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":0.1,"out":{"hex":"#989898ff","u16":[38987,38987,38987,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":0.1,"out":{"hex":"#979899ff","u16":[38725,39030,39334,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":0.1,"out":{"hex":"#6d9dc0ff","u16":[28066,40436,49457,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":0.1,"out":{"hex":"#00a2dfff","u16":[0,41747,57331,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":0.5,"out":{"hex":"#dbdbdbff","u16":[56409,56409,56409,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":0.5,"out":{"hex":"#dbdcdcff","u16":[56298,56427,56557,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":0.5,"out":{"hex":"#ccdeefff","u16":[52375,57032,61310,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":0.5,"out":{"hex":"#bce0ffff","u16":[48197,57612,65535,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#7f8081ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#7f8081ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#7f8081ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#7f8081ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#010000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#010000ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#010000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#010000ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#010000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#010000ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#010000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#010000ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":0,"out":{"hex":"#000000ff","u16":[127,127,127,65535]}}, +{"chroma":0,"in":"#010000ff","luma":0,"out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":0,"out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"chroma":1,"in":"#010000ff","luma":0,"out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"chroma":0,"in":"#010000ff","luma":0.1,"out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":0.1,"out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"chroma":1,"in":"#010000ff","luma":0.1,"out":{"hex":"#b50000ff","u16":[46514,0,0,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"chroma":0,"in":"#010000ff","luma":0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"chroma":1,"in":"#010000ff","luma":0.5,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#010000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#010000ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#010000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#010000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#010000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#010000ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000001ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000001ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000001ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000001ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000001ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000001ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#000001ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#000001ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":0,"out":{"hex":"#000000ff","u16":[78,78,78,65535]}}, +{"chroma":0,"in":"#000001ff","luma":0,"out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":0,"out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"chroma":1,"in":"#000001ff","luma":0,"out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":0.1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]}}, +{"chroma":0,"in":"#000001ff","luma":0.1,"out":{"hex":"#3434ffff","u16":[13305,13305,65535,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":0.1,"out":{"hex":"#3434ffff","u16":[13305,13305,65535,65535]}}, +{"chroma":1,"in":"#000001ff","luma":0.1,"out":{"hex":"#3434ffff","u16":[13305,13305,65535,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]}}, +{"chroma":0,"in":"#000001ff","luma":0.5,"out":{"hex":"#b3b3ffff","u16":[46095,46095,65535,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":0.5,"out":{"hex":"#b3b3ffff","u16":[46095,46095,65535,65535]}}, +{"chroma":1,"in":"#000001ff","luma":0.5,"out":{"hex":"#b3b3ffff","u16":[46095,46095,65535,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#000001ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#000001ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#000001ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#000001ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#000001ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#000001ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":-0.5,"out":{"hex":"#bababaff","u16":[47717,47717,47717,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":-0.5,"out":{"hex":"#00d800ff","u16":[0,55570,0,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":-0.5,"out":{"hex":"#00d800ff","u16":[0,55570,0,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":-0.5,"out":{"hex":"#00d800ff","u16":[0,55570,0,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":-0.1,"out":{"hex":"#f3f3f3ff","u16":[62393,62393,62393,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":-0.1,"out":{"hex":"#d0ffd0ff","u16":[53513,65535,53513,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":-0.1,"out":{"hex":"#d0ffd0ff","u16":[53513,65535,53513,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":-0.1,"out":{"hex":"#d0ffd0ff","u16":[53513,65535,53513,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":0,"out":{"hex":"#ffffffff","u16":[65462,65462,65462,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":0,"out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":0,"out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":0,"out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fefffeff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fefffeff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fefffeff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fefffeff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":-0.1,"out":{"hex":"#8e8e8eff","u16":[36554,36554,36554,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":-0.1,"out":{"hex":"#3597cbff","u16":[13627,38869,52049,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":-0.1,"out":{"hex":"#0098d1ff","u16":[0,39155,53665,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":-0.1,"out":{"hex":"#0098d1ff","u16":[0,39155,53665,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":0,"out":{"hex":"#a4a4a4ff","u16":[42055,42055,42055,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":0,"out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":0,"out":{"hex":"#00aff0ff","u16":[0,45047,61740,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":0,"out":{"hex":"#00aff0ff","u16":[0,45047,61740,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":0.1,"out":{"hex":"#b6b6b6ff","u16":[46803,46803,46803,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":0.1,"out":{"hex":"#64c0f8ff","u16":[25711,49269,63727,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":0.1,"out":{"hex":"#52c1ffff","u16":[21106,49575,65535,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":0.1,"out":{"hex":"#52c1ffff","u16":[21106,49575,65535,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":0.5,"out":{"hex":"#f0f0f0ff","u16":[61734,61734,61734,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":0.5,"out":{"hex":"#e6f2fdff","u16":[59033,62162,65115,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":0.5,"out":{"hex":"#e4f2ffff","u16":[58674,62217,65535,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":0.5,"out":{"hex":"#e4f2ffff","u16":[58674,62217,65535,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#3daee9ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#3daee9ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#3daee9ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#3daee9ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#232629ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#232629ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#232629ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#232629ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#232629ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#232629ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#232629ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#232629ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":0,"out":{"hex":"#262626ff","u16":[9668,9668,9668,65535]}}, +{"chroma":0,"in":"#232629ff","luma":0,"out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":0,"out":{"hex":"#172733ff","u16":[6025,10093,13003,65535]}}, +{"chroma":1,"in":"#232629ff","luma":0,"out":{"hex":"#002838ff","u16":[0,10317,14453,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":0.1,"out":{"hex":"#5f5f5fff","u16":[24504,24504,24504,65535]}}, +{"chroma":0,"in":"#232629ff","luma":0.1,"out":{"hex":"#596068ff","u16":[22800,24754,26708,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":0.1,"out":{"hex":"#3b6480ff","u16":[15271,25583,32960,65535]}}, +{"chroma":1,"in":"#232629ff","luma":0.1,"out":{"hex":"#00668fff","u16":[0,26150,36633,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":0.5,"out":{"hex":"#bdbdbdff","u16":[48464,48464,48464,65535]}}, +{"chroma":0,"in":"#232629ff","luma":0.5,"out":{"hex":"#b4bec8ff","u16":[46263,48791,51401,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":0.5,"out":{"hex":"#92c2eaff","u16":[37580,49889,60166,65535]}}, +{"chroma":1,"in":"#232629ff","luma":0.5,"out":{"hex":"#73c5ffff","u16":[29574,50648,65535,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#232629ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#232629ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#232629ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#232629ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#232629ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#232629ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":-0.5,"out":{"hex":"#a3a3a3ff","u16":[41911,41911,41911,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":-0.5,"out":{"hex":"#9ea4aaff","u16":[40497,42136,43710,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":-0.5,"out":{"hex":"#6faad3ff","u16":[28475,43645,54228,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":-0.5,"out":{"hex":"#00aff0ff","u16":[0,44886,61580,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":-0.1,"out":{"hex":"#e3e3e3ff","u16":[58332,58332,58332,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":-0.1,"out":{"hex":"#e1e3e5ff","u16":[57907,58401,58892,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":-0.1,"out":{"hex":"#d6e5f3ff","u16":[54880,58871,62579,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":-0.1,"out":{"hex":"#cbe7ffff","u16":[52125,59269,65535,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":0,"out":{"hex":"#f0f0f0ff","u16":[61644,61644,61644,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":0,"out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":0,"out":{"hex":"#e9f1f9ff","u16":[59876,61926,63907,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":0,"out":{"hex":"#e4f2ffff","u16":[58516,62136,65535,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":0.1,"out":{"hex":"#fcfcfcff","u16":[64755,64755,64755,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":0.1,"out":{"hex":"#fcfcfcff","u16":[64713,64762,64813,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":0.1,"out":{"hex":"#fbfcfeff","u16":[64417,64810,65203,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":0.1,"out":{"hex":"#fafcffff","u16":[64162,64851,65535,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#eff0f1ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#eff0f1ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#eff0f1ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#eff0f1ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":-0.5,"out":{"hex":"#b6b6b6ff","u16":[46690,46690,46690,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":-0.5,"out":{"hex":"#b6b6b6ff","u16":[46690,46690,46690,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":-0.5,"out":{"hex":"#dea9a9ff","u16":[57052,43374,43374,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":-0.5,"out":{"hex":"#ff9b9bff","u16":[65535,39721,39721,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":-0.1,"out":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":-0.1,"out":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":-0.1,"out":{"hex":"#f8eeeeff","u16":[63630,61106,61106,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":-0.1,"out":{"hex":"#ffececff","u16":[65535,60554,60554,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":0,"out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":0,"out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":0,"out":{"hex":"#fefcfcff","u16":[65151,64659,64659,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":0,"out":{"hex":"#fffbfbff","u16":[65535,64554,64554,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#fcfcfcff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#fcfcfcff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#fcfcfcff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#fcfcfcff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#31363bff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#31363bff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#31363bff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#31363bff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#31363bff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#31363bff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#31363bff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#31363bff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":0,"out":{"hex":"#353535ff","u16":[13717,13717,13717,65535]}}, +{"chroma":0,"in":"#31363bff","luma":0,"out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":0,"out":{"hex":"#203849ff","u16":[8271,14336,18653,65535]}}, +{"chroma":1,"in":"#31363bff","luma":0,"out":{"hex":"#003950ff","u16":[0,14628,20572,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":0.1,"out":{"hex":"#666666ff","u16":[26109,26109,26109,65535]}}, +{"chroma":0,"in":"#31363bff","luma":0.1,"out":{"hex":"#5d6770ff","u16":[23970,26416,28862,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":0.1,"out":{"hex":"#3d6a8aff","u16":[15743,27288,35505,65535]}}, +{"chroma":1,"in":"#31363bff","luma":0.1,"out":{"hex":"#006c98ff","u16":[0,27843,39159,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":0.5,"out":{"hex":"#bfbfbfff","u16":[49193,49193,49193,65535]}}, +{"chroma":0,"in":"#31363bff","luma":0.5,"out":{"hex":"#b6c1ccff","u16":[46780,49547,52436,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":0.5,"out":{"hex":"#97c5ecff","u16":[38694,50564,60746,65535]}}, +{"chroma":1,"in":"#31363bff","luma":0.5,"out":{"hex":"#7dc7ffff","u16":[32024,51219,65535,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#31363bff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#31363bff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#31363bff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#31363bff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#31363bff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#31363bff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":0,"out":{"hex":"#1e1e1eff","u16":[7593,7593,7593,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":0,"out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":0,"out":{"hex":"#121f26ff","u16":[4525,8024,9730,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":0,"out":{"hex":"#002029ff","u16":[0,8217,10562,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":0.1,"out":{"hex":"#5d5d5dff","u16":[23902,23902,23902,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":0.1,"out":{"hex":"#555e65ff","u16":[21842,24269,25887,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":0.1,"out":{"hex":"#376277ff","u16":[14243,25256,30626,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":0.1,"out":{"hex":"#006581ff","u16":[0,25865,33247,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":0.5,"out":{"hex":"#bcbcbcff","u16":[48201,48201,48201,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":0.5,"out":{"hex":"#adbecaff","u16":[44461,48872,51841,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":0.5,"out":{"hex":"#79c5ecff","u16":[31218,50680,60631,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":0.5,"out":{"hex":"#40caffff","u16":[16513,51799,65535,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#1b1e20ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#1b1e20ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#1b1e20ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#1b1e20ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":-0.1,"out":{"hex":"#585858ff","u16":[22560,22560,22560,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":-0.1,"out":{"hex":"#9e313cff","u16":[40478,12626,15411,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":-0.1,"out":{"hex":"#b0002bff","u16":[45282,0,11167,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":-0.1,"out":{"hex":"#b0002bff","u16":[45282,0,11167,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":0,"out":{"hex":"#797979ff","u16":[31225,31225,31225,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":0,"out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":0,"out":{"hex":"#f4003cff","u16":[62676,0,15457,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":0,"out":{"hex":"#f4003cff","u16":[62676,0,15457,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":0.1,"out":{"hex":"#939393ff","u16":[37668,37668,37668,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":0.1,"out":{"hex":"#e76e77ff","u16":[59334,28156,30484,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":0.1,"out":{"hex":"#ff5a69ff","u16":[65535,23198,27081,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":0.1,"out":{"hex":"#ff5a69ff","u16":[65535,23198,27081,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":0.5,"out":{"hex":"#d8d8d8ff","u16":[55572,55572,55572,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":0.5,"out":{"hex":"#f5cfd1ff","u16":[62946,53324,53812,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":0.5,"out":{"hex":"#ffcccfff","u16":[65535,52424,53113,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":0.5,"out":{"hex":"#ffcccfff","u16":[65535,52424,53113,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#da4453ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#da4453ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#da4453ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#da4453ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":-0.1,"out":{"hex":"#808080ff","u16":[32951,32951,32951,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":-0.1,"out":{"hex":"#219351ff","u16":[8455,37723,20813,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":-0.1,"out":{"hex":"#00944dff","u16":[0,37955,19916,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":-0.1,"out":{"hex":"#00944dff","u16":[0,37955,19916,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":0,"out":{"hex":"#989898ff","u16":[39061,39061,39061,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":0,"out":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":0,"out":{"hex":"#00af5cff","u16":[0,44993,23610,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":0,"out":{"hex":"#00af5cff","u16":[0,44993,23610,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":0.1,"out":{"hex":"#acacacff","u16":[44195,44195,44195,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":0.1,"out":{"hex":"#2cc56dff","u16":[11340,50596,27915,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":0.1,"out":{"hex":"#00c668ff","u16":[0,50907,26713,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":0.1,"out":{"hex":"#00c668ff","u16":[0,50907,26713,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":0.5,"out":{"hex":"#e9e9e9ff","u16":[59893,59893,59893,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":0.5,"out":{"hex":"#9ffebbff","u16":[40802,65266,48131,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":0.5,"out":{"hex":"#9affb8ff","u16":[39477,65535,47411,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":0.5,"out":{"hex":"#9affb8ff","u16":[39477,65535,47411,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#27ae60ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#27ae60ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#27ae60ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#27ae60ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":-0.1,"out":{"hex":"#818181ff","u16":[33123,33123,33123,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":-0.1,"out":{"hex":"#d06200ff","u16":[53419,25190,0,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":-0.1,"out":{"hex":"#d06200ff","u16":[53419,25190,0,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":-0.1,"out":{"hex":"#d06200ff","u16":[53419,25190,0,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":0,"out":{"hex":"#999999ff","u16":[39202,39202,39202,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":0,"out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":0,"out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":0,"out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":0.1,"out":{"hex":"#acacacff","u16":[44317,44317,44317,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":0.1,"out":{"hex":"#ff905fff","u16":[65535,36899,24314,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":0.1,"out":{"hex":"#ff905fff","u16":[65535,36899,24314,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":0.1,"out":{"hex":"#ff905fff","u16":[65535,36899,24314,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":0.5,"out":{"hex":"#e9e9e9ff","u16":[59977,59977,59977,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":0.5,"out":{"hex":"#ffe4ddff","u16":[65535,58530,56720,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":0.5,"out":{"hex":"#ffe4ddff","u16":[65535,58530,56720,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":0.5,"out":{"hex":"#ffe4ddff","u16":[65535,58530,56720,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#f67400ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#f67400ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#f67400ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#f67400ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":-0.1,"out":{"hex":"#585858ff","u16":[22636,22636,22636,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":-0.1,"out":{"hex":"#1e5d86ff","u16":[7626,23808,34409,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":-0.1,"out":{"hex":"#005d8aff","u16":[0,23921,35340,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":-0.1,"out":{"hex":"#005d8aff","u16":[0,23921,35340,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":0,"out":{"hex":"#7a7a7aff","u16":[31277,31277,31277,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":0,"out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":0,"out":{"hex":"#0081beff","u16":[0,33053,48831,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":0,"out":{"hex":"#0081beff","u16":[0,33053,48831,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":0.1,"out":{"hex":"#939393ff","u16":[37710,37710,37710,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":0.1,"out":{"hex":"#319adfff","u16":[12704,39662,57323,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":0.1,"out":{"hex":"#009be5ff","u16":[0,39851,58874,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":0.1,"out":{"hex":"#009be5ff","u16":[0,39851,58874,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":0.5,"out":{"hex":"#d8d8d8ff","u16":[55598,55598,55598,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":0.5,"out":{"hex":"#bfdbfcff","u16":[49139,56369,64703,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":0.5,"out":{"hex":"#bcdcffff","u16":[48432,56445,65535,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":0.5,"out":{"hex":"#bcdcffff","u16":[48432,56445,65535,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#2980b9ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#2980b9ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#2980b9ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#2980b9ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":-0.1,"out":{"hex":"#6e6e6eff","u16":[28222,28222,28222,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":-0.1,"out":{"hex":"#657071ff","u16":[26079,28749,28954,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":-0.1,"out":{"hex":"#437679ff","u16":[17296,30331,31119,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":-0.1,"out":{"hex":"#007a7eff","u16":[0,31354,32495,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":0,"out":{"hex":"#898989ff","u16":[35320,35320,35320,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":0,"out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":0,"out":{"hex":"#549498ff","u16":[21646,37960,38946,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":0,"out":{"hex":"#00999eff","u16":[0,39241,40668,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":0.1,"out":{"hex":"#a0a0a0ff","u16":[41018,41018,41018,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":0.1,"out":{"hex":"#93a3a4ff","u16":[37904,41784,42082,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":0.1,"out":{"hex":"#62acb0ff","u16":[25138,44083,45228,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":0.1,"out":{"hex":"#00b1b8ff","u16":[0,45570,47228,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":0.5,"out":{"hex":"#e1e1e1ff","u16":[57735,57735,57735,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":0.5,"out":{"hex":"#d2e4e6ff","u16":[53881,58689,59062,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":0.5,"out":{"hex":"#97f0f5ff","u16":[38790,61568,63008,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":0.5,"out":{"hex":"#54f7ffff","u16":[21667,63440,65535,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#7f8c8dff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#7f8c8dff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#7f8c8dff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#7f8c8dff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":-0.1,"out":{"hex":"#4f4f4fff","u16":[20320,20320,20320,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":-0.1,"out":{"hex":"#6a3d7cff","u16":[27202,15619,31940,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":-0.1,"out":{"hex":"#81169fff","u16":[33088,5679,40837,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":-0.1,"out":{"hex":"#8300a2ff","u16":[33718,0,41762,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":0,"out":{"hex":"#747474ff","u16":[29758,29758,29758,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":0,"out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":0,"out":{"hex":"#bd20e9ff","u16":[48456,8316,59803,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":0,"out":{"hex":"#c000eeff","u16":[49379,0,61158,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":0.1,"out":{"hex":"#8e8e8eff","u16":[36508,36508,36508,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":0.1,"out":{"hex":"#b079c9ff","u16":[45309,31102,51728,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":0.1,"out":{"hex":"#cf5afaff","u16":[53313,23085,64214,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":0.1,"out":{"hex":"#d355ffff","u16":[54186,21869,65535,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":0.5,"out":{"hex":"#d5d5d5ff","u16":[54852,54852,54852,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":0.5,"out":{"hex":"#e0d0e9ff","u16":[57544,53478,59830,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":0.5,"out":{"hex":"#ebcafdff","u16":[60433,51861,64948,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":0.5,"out":{"hex":"#ecc9ffff","u16":[60772,51661,65535,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#9b59b6ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#9b59b6ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#9b59b6ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#9b59b6ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":-0.1,"out":{"hex":"#7a7a7aff","u16":[31474,31474,31474,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":-0.1,"out":{"hex":"#187fcaff","u16":[6195,32682,51907,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":-0.1,"out":{"hex":"#007fccff","u16":[0,32716,52358,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":-0.1,"out":{"hex":"#007fccff","u16":[0,32716,52358,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":0,"out":{"hex":"#939393ff","u16":[37867,37867,37867,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":0,"out":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":0,"out":{"hex":"#0099f5ff","u16":[0,39362,62994,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":0,"out":{"hex":"#0099f5ff","u16":[0,39362,62994,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":0.1,"out":{"hex":"#a8a8a8ff","u16":[43171,43171,43171,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":0.1,"out":{"hex":"#60adfdff","u16":[24638,44387,65032,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":0.1,"out":{"hex":"#5dadffff","u16":[23837,44421,65535,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":0.1,"out":{"hex":"#5dadffff","u16":[23837,44421,65535,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":0.5,"out":{"hex":"#e6e6e6ff","u16":[59186,59186,59186,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":0.5,"out":{"hex":"#d9e7feff","u16":[55893,59469,65367,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":0.5,"out":{"hex":"#d9e7ffff","u16":[55795,59477,65535,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":0.5,"out":{"hex":"#d9e7ffff","u16":[55795,59477,65535,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#1d99f3ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#1d99f3ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#1d99f3ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#1d99f3ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":-0.5,"out":{"hex":"#888888ff","u16":[34889,34889,34889,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":-0.5,"out":{"hex":"#84888dff","u16":[33797,35062,36288,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":-0.5,"out":{"hex":"#5d8dafff","u16":[23834,36314,45098,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":-0.5,"out":{"hex":"#0091c8ff","u16":[0,37357,51317,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":-0.1,"out":{"hex":"#d2d2d2ff","u16":[53875,53875,53875,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":-0.1,"out":{"hex":"#cfd2d5ff","u16":[53203,53983,54758,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":-0.1,"out":{"hex":"#bad5edff","u16":[47876,54775,60822,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":-0.1,"out":{"hex":"#a6d8ffff","u16":[42615,55449,65535,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":0,"out":{"hex":"#e0e0e0ff","u16":[57497,57497,57497,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":0,"out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":0,"out":{"hex":"#d1e2f2ff","u16":[53641,58093,62212,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":0,"out":{"hex":"#c4e4ffff","u16":[50464,58543,65535,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":0.1,"out":{"hex":"#edededff","u16":[60863,60863,60863,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":0.1,"out":{"hex":"#ecedeeff","u16":[60616,60903,61192,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":0.1,"out":{"hex":"#e5eef7ff","u16":[58744,61199,63565,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":0.1,"out":{"hex":"#deefffff","u16":[57066,61453,65535,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#dee0e2ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#dee0e2ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#dee0e2ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#dee0e2ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":-0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":0,"out":{"hex":"#2c2c2cff","u16":[11229,11229,11229,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":0,"out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":0,"out":{"hex":"#1c2d3dff","u16":[7141,11606,15767,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":0,"out":{"hex":"#002e45ff","u16":[0,11820,17816,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":0.1,"out":{"hex":"#626262ff","u16":[25058,25058,25058,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":0.1,"out":{"hex":"#5b626bff","u16":[23515,25235,27529,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":0.1,"out":{"hex":"#3e6589ff","u16":[15936,25900,35187,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":0.1,"out":{"hex":"#00679bff","u16":[0,26378,39758,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":0.5,"out":{"hex":"#bebebeff","u16":[48711,48711,48711,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":0.5,"out":{"hex":"#b8bec8ff","u16":[47170,48891,51290,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":0.5,"out":{"hex":"#9ec1e9ff","u16":[40612,49574,59978,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":0.5,"out":{"hex":"#87c3ffff","u16":[34797,50071,65535,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#292c30ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#292c30ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#292c30ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#292c30ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":-0.1,"out":{"hex":"#636363ff","u16":[25527,25527,25527,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":-0.1,"out":{"hex":"#b32c76ff","u16":[45889,11423,30330,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":-0.1,"out":{"hex":"#be0079ff","u16":[48879,0,31209,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":-0.1,"out":{"hex":"#be0079ff","u16":[48879,0,31209,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":0,"out":{"hex":"#828282ff","u16":[33310,33310,33310,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":0,"out":{"hex":"#e93a9aff","u16":[59881,14906,39578,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":0,"out":{"hex":"#f8009eff","u16":[63783,0,40725,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":0,"out":{"hex":"#f8009eff","u16":[63783,0,40725,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":0.1,"out":{"hex":"#999999ff","u16":[39354,39354,39354,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":0.1,"out":{"hex":"#f16badff","u16":[61996,27624,44352,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":0.1,"out":{"hex":"#ff5fb0ff","u16":[65535,24351,45298,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":0.1,"out":{"hex":"#ff5fb0ff","u16":[65535,24351,45298,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":0.5,"out":{"hex":"#dcdcdcff","u16":[56646,56646,56646,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":0.5,"out":{"hex":"#fad2e2ff","u16":[64123,54030,58039,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":0.5,"out":{"hex":"#ffd0e3ff","u16":[65535,53474,58321,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":0.5,"out":{"hex":"#ffd0e3ff","u16":[65535,53474,58321,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e93a9aff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e93a9aff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e93a9aff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e93a9aff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":-0.1,"out":{"hex":"#5e5e5eff","u16":[24229,24229,24229,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":-0.1,"out":{"hex":"#ae2e42ff","u16":[44807,11730,16923,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":-0.1,"out":{"hex":"#bd0038ff","u16":[48467,0,14336,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":-0.1,"out":{"hex":"#bd0038ff","u16":[48467,0,14336,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":0,"out":{"hex":"#7e7e7eff","u16":[32380,32380,32380,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":0,"out":{"hex":"#e93d58ff","u16":[59881,15677,22616,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":0,"out":{"hex":"#fc004bff","u16":[64772,0,19159,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":0,"out":{"hex":"#fc004bff","u16":[64772,0,19159,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":0.1,"out":{"hex":"#969696ff","u16":[38597,38597,38597,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":0.1,"out":{"hex":"#ee6f7dff","u16":[61209,28452,32071,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":0.1,"out":{"hex":"#ff6275ff","u16":[65535,25068,30136,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":0.1,"out":{"hex":"#ff6275ff","u16":[65535,25068,30136,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":0.5,"out":{"hex":"#dbdbdbff","u16":[56160,56160,56160,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":0.5,"out":{"hex":"#f8d1d4ff","u16":[63767,53812,54565,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":0.5,"out":{"hex":"#ffcfd3ff","u16":[65535,53195,54151,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":0.5,"out":{"hex":"#ffcfd3ff","u16":[65535,53195,54151,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e93d58ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e93d58ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e93d58ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e93d58ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":-0.1,"out":{"hex":"#717171ff","u16":[29155,29155,29155,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":-0.1,"out":{"hex":"#bc512fff","u16":[48443,20791,12059,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":-0.1,"out":{"hex":"#c64a00ff","u16":[50851,18947,0,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":-0.1,"out":{"hex":"#c64a00ff","u16":[50851,18947,0,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":0,"out":{"hex":"#8c8c8cff","u16":[36038,36038,36038,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":0,"out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":0,"out":{"hex":"#f55b00ff","u16":[62857,23421,0,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":0,"out":{"hex":"#f55b00ff","u16":[62857,23421,0,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":0.1,"out":{"hex":"#a2a2a2ff","u16":[41620,41620,41620,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":0.1,"out":{"hex":"#f48369ff","u16":[62767,33615,27061,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":0.1,"out":{"hex":"#ff7d5cff","u16":[65535,32037,23540,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":0.1,"out":{"hex":"#ff7d5cff","u16":[65535,32037,23540,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":0.5,"out":{"hex":"#e2e2e2ff","u16":[58136,58136,58136,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":0.5,"out":{"hex":"#fbdbd7ff","u16":[64536,56361,55203,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":0.5,"out":{"hex":"#ffdad5ff","u16":[65535,56058,54694,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":0.5,"out":{"hex":"#ffdad5ff","u16":[65535,56058,54694,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e9643aff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e9643aff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e9643aff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e9643aff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":-0.5,"out":{"hex":"#5c5c5cff","u16":[23761,23761,23761,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":-0.5,"out":{"hex":"#6a5c14ff","u16":[27118,23728,5260,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":-0.5,"out":{"hex":"#6a5c00ff","u16":[27235,23727,0,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":-0.5,"out":{"hex":"#6a5c00ff","u16":[27235,23727,0,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":-0.1,"out":{"hex":"#bbbbbbff","u16":[48140,48140,48140,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":-0.1,"out":{"hex":"#d6bb29ff","u16":[54942,48074,10657,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":-0.1,"out":{"hex":"#d7bb00ff","u16":[55178,48072,0,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":-0.1,"out":{"hex":"#d7bb00ff","u16":[55178,48072,0,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":0,"out":{"hex":"#cbcbcbff","u16":[52243,52243,52243,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":0,"out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":0,"out":{"hex":"#e9cb00ff","u16":[59881,52168,0,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":0,"out":{"hex":"#e9cb00ff","u16":[59881,52168,0,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":0.1,"out":{"hex":"#dadadaff","u16":[55991,55991,55991,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":0.1,"out":{"hex":"#f9da30ff","u16":[63902,55914,12395,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":0.1,"out":{"hex":"#fada00ff","u16":[64176,55911,0,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":0.1,"out":{"hex":"#fada00ff","u16":[64176,55911,0,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#e8cb2dff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#e8cb2dff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#e8cb2dff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#e8cb2dff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":-0.1,"out":{"hex":"#a6a6a6ff","u16":[42546,42546,42546,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":-0.1,"out":{"hex":"#37bf21ff","u16":[14120,49074,8565,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":-0.1,"out":{"hex":"#2ec000ff","u16":[11908,49256,0,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":-0.1,"out":{"hex":"#2ec000ff","u16":[11908,49256,0,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":0,"out":{"hex":"#b8b8b8ff","u16":[47236,47236,47236,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":0,"out":{"hex":"#3dd425ff","u16":[15677,54484,9509,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":0,"out":{"hex":"#33d500ff","u16":[13220,54686,0,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":0,"out":{"hex":"#33d500ff","u16":[13220,54686,0,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":0.1,"out":{"hex":"#c8c8c8ff","u16":[51424,51424,51424,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":0.1,"out":{"hex":"#42e728ff","u16":[17067,59315,10352,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":0.1,"out":{"hex":"#38e800ff","u16":[14393,59535,0,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":0.1,"out":{"hex":"#38e800ff","u16":[14393,59535,0,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":0.5,"out":{"hex":"#fdfdfdff","u16":[65134,65134,65134,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":0.5,"out":{"hex":"#fafff9ff","u16":[64159,65523,64096,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":0.5,"out":{"hex":"#fafff9ff","u16":[64129,65535,64064,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":0.5,"out":{"hex":"#fafff9ff","u16":[64129,65535,64064,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#3dd425ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#3dd425ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#3dd425ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#3dd425ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":-0.5,"out":{"hex":"#1a1a1aff","u16":[6732,6732,6732,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":-0.5,"out":{"hex":"#001e1aff","u16":[0,7587,6616,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":-0.5,"out":{"hex":"#001e1aff","u16":[0,7587,6616,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":-0.5,"out":{"hex":"#001e1aff","u16":[0,7587,6616,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":-0.1,"out":{"hex":"#a9a9a9ff","u16":[43538,43538,43538,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":-0.1,"out":{"hex":"#00bfa7ff","u16":[0,49070,42791,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":-0.1,"out":{"hex":"#00bfa7ff","u16":[0,49070,42791,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":-0.1,"out":{"hex":"#00bfa7ff","u16":[0,49070,42791,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":0,"out":{"hex":"#bbbbbbff","u16":[48113,48113,48113,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":0,"out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":0,"out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":0,"out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":0.1,"out":{"hex":"#cbcbcbff","u16":[52218,52218,52218,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":0.1,"out":{"hex":"#00e5c8ff","u16":[0,58853,51322,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":0.1,"out":{"hex":"#00e5c8ff","u16":[0,58853,51322,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":0.1,"out":{"hex":"#00e5c8ff","u16":[0,58853,51322,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#00d3b8ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#00d3b8ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#00d3b8ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#00d3b8ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":-0.1,"out":{"hex":"#767676ff","u16":[30413,30413,30413,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":-0.1,"out":{"hex":"#a94dd3ff","u16":[43422,19771,54196,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":-0.1,"out":{"hex":"#c100faff","u16":[49667,0,64279,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":-0.1,"out":{"hex":"#c100faff","u16":[49667,0,64279,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":0,"out":{"hex":"#909090ff","u16":[37024,37024,37024,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":0,"out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":0,"out":{"hex":"#cd5fffff","u16":[52594,24416,65535,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":0,"out":{"hex":"#cd5fffff","u16":[52594,24416,65535,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":0.1,"out":{"hex":"#a5a5a5ff","u16":[42452,42452,42452,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":0.1,"out":{"hex":"#c492e1ff","u16":[50326,37612,57899,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":0.1,"out":{"hex":"#d585ffff","u16":[54627,34114,65535,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":0.1,"out":{"hex":"#d585ffff","u16":[54627,34114,65535,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":0.5,"out":{"hex":"#e4e4e4ff","u16":[58697,58697,58697,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":0.5,"out":{"hex":"#ece0f5ff","u16":[60732,57615,62989,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":0.5,"out":{"hex":"#f1ddffff","u16":[61980,56917,65535,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":0.5,"out":{"hex":"#f1ddffff","u16":[61980,56917,65535,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#b875dcff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#b875dcff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#b875dcff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#b875dcff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":-0.1,"out":{"hex":"#656565ff","u16":[26040,26040,26040,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":-0.1,"out":{"hex":"#7b40e0ff","u16":[31624,16429,57619,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":-0.1,"out":{"hex":"#8225ffff","u16":[33536,9568,65535,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":-0.1,"out":{"hex":"#8225ffff","u16":[33536,9568,65535,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":0,"out":{"hex":"#838383ff","u16":[33685,33685,33685,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":0,"out":{"hex":"#926ee4ff","u16":[37522,28270,58596,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":0,"out":{"hex":"#9764ffff","u16":[38919,25738,65535,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":0,"out":{"hex":"#9764ffff","u16":[38919,25738,65535,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":0.1,"out":{"hex":"#9a9a9aff","u16":[39662,39662,39662,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":0.1,"out":{"hex":"#a58ce8ff","u16":[42474,35952,59554,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":0.1,"out":{"hex":"#a986ffff","u16":[43530,34347,65535,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":0.1,"out":{"hex":"#a986ffff","u16":[43530,34347,65535,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":0.5,"out":{"hex":"#ddddddff","u16":[56845,56845,56845,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":0.5,"out":{"hex":"#e0daf6ff","u16":[57604,55922,63213,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":0.5,"out":{"hex":"#e1d8ffff","u16":[57901,55551,65535,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":0.5,"out":{"hex":"#e1d8ffff","u16":[57901,55551,65535,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#926ee4ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#926ee4ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#926ee4ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#926ee4ff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":-1.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":-1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":-0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":-0.1,"out":{"hex":"#404040ff","u16":[16324,16324,16324,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":-0.1,"out":{"hex":"#3e4042ff","u16":[15916,16375,16987,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":-0.1,"out":{"hex":"#2c4256ff","u16":[11307,16836,22042,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":-0.1,"out":{"hex":"#004364ff","u16":[0,17234,25592,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":0,"out":{"hex":"#6b6b6bff","u16":[27414,27414,27414,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":0,"out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":0,"out":{"hex":"#4a6e90ff","u16":[18988,28273,37016,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":0,"out":{"hex":"#0071a7ff","u16":[0,28942,42977,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":0.1,"out":{"hex":"#878787ff","u16":[34707,34707,34707,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":0.1,"out":{"hex":"#84878dff","u16":[33839,34815,36117,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":0.1,"out":{"hex":"#5e8bb6ff","u16":[24040,35795,46864,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":0.1,"out":{"hex":"#008fd4ff","u16":[0,36642,54412,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":0.5,"out":{"hex":"#d1d1d1ff","u16":[53767,53767,53767,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":0.5,"out":{"hex":"#d0d1d4ff","u16":[53337,53821,54484,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":0.5,"out":{"hex":"#bfd3ecff","u16":[49156,54318,60628,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":0.5,"out":{"hex":"#afd5ffff","u16":[45030,54757,65535,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#686b6fff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0,"in":"#686b6fff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":0.5,"in":"#686b6fff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":1,"in":"#686b6fff","luma":1.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"chroma":-1,"in":"#3daee980","luma":-1.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0,"in":"#3daee980","luma":-1.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":-1.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"in":"#3daee980","luma":-1.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":-1,"in":"#3daee980","luma":-1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0,"in":"#3daee980","luma":-1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":-1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"in":"#3daee980","luma":-1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":-1,"in":"#3daee980","luma":-0.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0,"in":"#3daee980","luma":-0.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":-0.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"in":"#3daee980","luma":-0.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":-1,"in":"#3daee980","luma":-0.1,"out":{"hex":"#8e8e8e80","u16":[36554,36554,36554,32896]}}, +{"chroma":0,"in":"#3daee980","luma":-0.1,"out":{"hex":"#3597cb80","u16":[13627,38869,52049,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":-0.1,"out":{"hex":"#0098d180","u16":[0,39155,53665,32896]}}, +{"chroma":1,"in":"#3daee980","luma":-0.1,"out":{"hex":"#0098d180","u16":[0,39155,53665,32896]}}, +{"chroma":-1,"in":"#3daee980","luma":0,"out":{"hex":"#a4a4a480","u16":[42055,42055,42055,32896]}}, +{"chroma":0,"in":"#3daee980","luma":0,"out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":0,"out":{"hex":"#00aff080","u16":[0,45047,61740,32896]}}, +{"chroma":1,"in":"#3daee980","luma":0,"out":{"hex":"#00aff080","u16":[0,45047,61740,32896]}}, +{"chroma":-1,"in":"#3daee980","luma":0.1,"out":{"hex":"#b6b6b680","u16":[46803,46803,46803,32896]}}, +{"chroma":0,"in":"#3daee980","luma":0.1,"out":{"hex":"#64c0f880","u16":[25711,49269,63727,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":0.1,"out":{"hex":"#52c1ff80","u16":[21106,49575,65535,32896]}}, +{"chroma":1,"in":"#3daee980","luma":0.1,"out":{"hex":"#52c1ff80","u16":[21106,49575,65535,32896]}}, +{"chroma":-1,"in":"#3daee980","luma":0.5,"out":{"hex":"#f0f0f080","u16":[61734,61734,61734,32896]}}, +{"chroma":0,"in":"#3daee980","luma":0.5,"out":{"hex":"#e6f2fd80","u16":[59033,62162,65115,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":0.5,"out":{"hex":"#e4f2ff80","u16":[58674,62217,65535,32896]}}, +{"chroma":1,"in":"#3daee980","luma":0.5,"out":{"hex":"#e4f2ff80","u16":[58674,62217,65535,32896]}}, +{"chroma":-1,"in":"#3daee980","luma":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":0,"in":"#3daee980","luma":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":1,"in":"#3daee980","luma":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":-1,"in":"#3daee980","luma":1.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":0,"in":"#3daee980","luma":1.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":0.5,"in":"#3daee980","luma":1.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":1,"in":"#3daee980","luma":1.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":-1,"in":"#ffffff00","luma":-1.5,"out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"chroma":0,"in":"#ffffff00","luma":-1.5,"out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":-1.5,"out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"chroma":1,"in":"#ffffff00","luma":-1.5,"out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"chroma":-1,"in":"#ffffff00","luma":-1,"out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"chroma":0,"in":"#ffffff00","luma":-1,"out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":-1,"out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"chroma":1,"in":"#ffffff00","luma":-1,"out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"chroma":-1,"in":"#ffffff00","luma":-0.5,"out":{"hex":"#bababa00","u16":[47824,47824,47824,0]}}, +{"chroma":0,"in":"#ffffff00","luma":-0.5,"out":{"hex":"#bababa00","u16":[47824,47824,47824,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":-0.5,"out":{"hex":"#e0aeae00","u16":[57502,44773,44773,0]}}, +{"chroma":1,"in":"#ffffff00","luma":-0.5,"out":{"hex":"#ffa1a100","u16":[65535,41449,41449,0]}}, +{"chroma":-1,"in":"#ffffff00","luma":-0.1,"out":{"hex":"#f3f3f300","u16":[62470,62470,62470,0]}}, +{"chroma":0,"in":"#ffffff00","luma":-0.1,"out":{"hex":"#f3f3f300","u16":[62470,62470,62470,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":-0.1,"out":{"hex":"#f9f1f100","u16":[64025,62043,62043,0]}}, +{"chroma":1,"in":"#ffffff00","luma":-0.1,"out":{"hex":"#fff0f000","u16":[65535,61611,61611,0]}}, +{"chroma":-1,"in":"#ffffff00","luma":0,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0,"in":"#ffffff00","luma":0,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":0,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":1,"in":"#ffffff00","luma":0,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":-1,"in":"#ffffff00","luma":0.1,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0,"in":"#ffffff00","luma":0.1,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":0.1,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":1,"in":"#ffffff00","luma":0.1,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":-1,"in":"#ffffff00","luma":0.5,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0,"in":"#ffffff00","luma":0.5,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":0.5,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":1,"in":"#ffffff00","luma":0.5,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":-1,"in":"#ffffff00","luma":1,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0,"in":"#ffffff00","luma":1,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":1,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":1,"in":"#ffffff00","luma":1,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":-1,"in":"#ffffff00","luma":1.5,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0,"in":"#ffffff00","luma":1.5,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":0.5,"in":"#ffffff00","luma":1.5,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":1,"in":"#ffffff00","luma":1.5,"out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"chroma":-1,"in":"#00000080","luma":-1.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0,"in":"#00000080","luma":-1.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":-1.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"in":"#00000080","luma":-1.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":-1,"in":"#00000080","luma":-1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0,"in":"#00000080","luma":-1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":-1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"in":"#00000080","luma":-1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":-1,"in":"#00000080","luma":-0.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0,"in":"#00000080","luma":-0.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":-0.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"in":"#00000080","luma":-0.5,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":-1,"in":"#00000080","luma":-0.1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0,"in":"#00000080","luma":-0.1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":-0.1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"in":"#00000080","luma":-0.1,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":-1,"in":"#00000080","luma":0,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0,"in":"#00000080","luma":0,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":0,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":1,"in":"#00000080","luma":0,"out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"chroma":-1,"in":"#00000080","luma":0.1,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32896]}}, +{"chroma":0,"in":"#00000080","luma":0.1,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":0.1,"out":{"hex":"#90414180","u16":[37051,16792,16792,32896]}}, +{"chroma":1,"in":"#00000080","luma":0.1,"out":{"hex":"#b5000080","u16":[46514,0,0,32896]}}, +{"chroma":-1,"in":"#00000080","luma":0.5,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32896]}}, +{"chroma":0,"in":"#00000080","luma":0.5,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":0.5,"out":{"hex":"#e0aeae80","u16":[57502,44773,44773,32896]}}, +{"chroma":1,"in":"#00000080","luma":0.5,"out":{"hex":"#ffa1a180","u16":[65535,41449,41449,32896]}}, +{"chroma":-1,"in":"#00000080","luma":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":0,"in":"#00000080","luma":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":1,"in":"#00000080","luma":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":-1,"in":"#00000080","luma":1.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":0,"in":"#00000080","luma":1.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":0.5,"in":"#00000080","luma":1.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":1,"in":"#00000080","luma":1.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"chroma":-1,"in":"#ff000033","luma":-1.5,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":0,"in":"#ff000033","luma":-1.5,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":-1.5,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":1,"in":"#ff000033","luma":-1.5,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":-1,"in":"#ff000033","luma":-1,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":0,"in":"#ff000033","luma":-1,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":-1,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":1,"in":"#ff000033","luma":-1,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":-1,"in":"#ff000033","luma":-0.5,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":0,"in":"#ff000033","luma":-0.5,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":-0.5,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":1,"in":"#ff000033","luma":-0.5,"out":{"hex":"#00000033","u16":[0,0,0,13107]}}, +{"chroma":-1,"in":"#ff000033","luma":-0.1,"out":{"hex":"#5e5e5e33","u16":[24286,24286,24286,13107]}}, +{"chroma":0,"in":"#ff000033","luma":-0.1,"out":{"hex":"#bf000033","u16":[49092,0,0,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":-0.1,"out":{"hex":"#bf000033","u16":[49092,0,0,13107]}}, +{"chroma":1,"in":"#ff000033","luma":-0.1,"out":{"hex":"#bf000033","u16":[49092,0,0,13107]}}, +{"chroma":-1,"in":"#ff000033","luma":0,"out":{"hex":"#7e7e7e33","u16":[32421,32421,32421,13107]}}, +{"chroma":0,"in":"#ff000033","luma":0,"out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":0,"out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"chroma":1,"in":"#ff000033","luma":0,"out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"chroma":-1,"in":"#ff000033","luma":0.1,"out":{"hex":"#96969633","u16":[38630,38630,38630,13107]}}, +{"chroma":0,"in":"#ff000033","luma":0.1,"out":{"hex":"#ff646433","u16":[65535,25651,25651,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":0.1,"out":{"hex":"#ff646433","u16":[65535,25651,25651,13107]}}, +{"chroma":1,"in":"#ff000033","luma":0.1,"out":{"hex":"#ff646433","u16":[65535,25651,25651,13107]}}, +{"chroma":-1,"in":"#ff000033","luma":0.5,"out":{"hex":"#dbdbdb33","u16":[56180,56180,56180,13107]}}, +{"chroma":0,"in":"#ff000033","luma":0.5,"out":{"hex":"#ffcfcf33","u16":[65535,53312,53312,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":0.5,"out":{"hex":"#ffcfcf33","u16":[65535,53312,53312,13107]}}, +{"chroma":1,"in":"#ff000033","luma":0.5,"out":{"hex":"#ffcfcf33","u16":[65535,53312,53312,13107]}}, +{"chroma":-1,"in":"#ff000033","luma":1,"out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"chroma":0,"in":"#ff000033","luma":1,"out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":1,"out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"chroma":1,"in":"#ff000033","luma":1,"out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"chroma":-1,"in":"#ff000033","luma":1.5,"out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"chroma":0,"in":"#ff000033","luma":1.5,"out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"chroma":0.5,"in":"#ff000033","luma":1.5,"out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"chroma":1,"in":"#ff000033","luma":1.5,"out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}} +], +"tint": [ +{"amount":-0.5,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#0b0b0bff","u16":[2942,2942,2942,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#1e1e1eff","u16":[7632,7632,7632,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#343434ff","u16":[13263,13263,13263,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#4c4c4cff","u16":[19632,19632,19632,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#676767ff","u16":[26586,26586,26586,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#a4a4a4ff","u16":[42079,42079,42079,65535]}}, +{"amount":1,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#070707ff","u16":[1691,1691,1691,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#111111ff","u16":[4322,4322,4322,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#1d1d1dff","u16":[7516,7516,7516,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#2b2b2bff","u16":[11119,11119,11119,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#3b3b3bff","u16":[15087,15087,15087,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#5d5d5dff","u16":[23866,23866,23866,65535]}}, +{"amount":1,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#808080ff","out":{"hex":"#070707ff","u16":[1704,1704,1704,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#808080ff","out":{"hex":"#111111ff","u16":[4356,4356,4356,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#808080ff","out":{"hex":"#1d1d1dff","u16":[7556,7556,7556,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#808080ff","out":{"hex":"#2c2c2cff","u16":[11189,11189,11189,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#808080ff","out":{"hex":"#3b3b3bff","u16":[15159,15159,15159,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#808080ff","out":{"hex":"#5d5d5dff","u16":[23987,23987,23987,65535]}}, +{"amount":1,"base":"#000000ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#0d0000ff","u16":[3395,0,0,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#220000ff","u16":[8678,0,0,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#3b0000ff","u16":[15130,0,0,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#570000ff","u16":[22394,0,0,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#760000ff","u16":[30355,0,0,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#bb0000ff","u16":[47999,0,0,65535]}}, +{"amount":1,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#000c00ff","u16":[0,3008,0,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#001e00ff","u16":[0,7729,0,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#003400ff","u16":[0,13466,0,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#004e00ff","u16":[0,19953,0,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#006900ff","u16":[0,27038,0,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#00a600ff","u16":[0,42774,0,65535]}}, +{"amount":1,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#000010ff","u16":[0,0,4188,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#00002aff","u16":[0,0,10833,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#000049ff","u16":[0,0,18840,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#00006dff","u16":[0,0,27903,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#000093ff","u16":[0,0,37795,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#0000e9ff","u16":[0,0,59815,65535]}}, +{"amount":1,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#0b0b00ff","u16":[2942,2942,0,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#1e1e00ff","u16":[7632,7632,0,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#343400ff","u16":[13304,13304,0,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#4d4d00ff","u16":[19668,19668,0,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#686800ff","u16":[26683,26683,0,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#a4a400ff","u16":[42218,42218,0,65535]}}, +{"amount":1,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1,"base":"#000000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#03080bff","u16":[751,2142,2868,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#08161dff","u16":[1941,5536,7413,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#0d2532ff","u16":[3375,9626,12890,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#13384aff","u16":[5001,14265,19102,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#1a4b65ff","u16":[6781,19343,25902,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#2a779fff","u16":[10726,30596,40970,65535]}}, +{"amount":1,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#232629ff","out":{"hex":"#040404ff","u16":[987,1071,1156,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#232629ff","out":{"hex":"#0a0b0cff","u16":[2541,2759,2977,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#232629ff","out":{"hex":"#111314ff","u16":[4415,4793,5172,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#232629ff","out":{"hex":"#191c1eff","u16":[6537,7097,7657,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#232629ff","out":{"hex":"#222528ff","u16":[8860,9620,10379,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8993,9764,10535,65535]}}, +{"amount":1,"base":"#000000ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#0b0b0bff","u16":[2819,2831,2843,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#1c1c1cff","u16":[7199,7229,7259,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#313131ff","u16":[12507,12560,12612,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#484849ff","u16":[18501,18578,18656,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#626262ff","u16":[25069,25174,25279,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#9a9b9cff","u16":[39535,39862,40188,65535]}}, +{"amount":1,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#0b0b0bff","u16":[2907,2907,2907,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#1d1d1dff","u16":[7542,7542,7542,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#333333ff","u16":[13108,13108,13108,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#4b4b4bff","u16":[19401,19401,19401,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#666666ff","u16":[26305,26305,26305,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#a2a2a2ff","u16":[41639,41639,41639,65535]}}, +{"amount":1,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#0b0404ff","u16":[2902,905,1105,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#1d090bff","u16":[7538,2351,2870,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#331013ff","u16":[13101,4087,4988,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#4b181dff","u16":[19378,6044,7378,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#662027ff","u16":[26268,8193,10001,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#a2323eff","u16":[41559,12963,15823,65535]}}, +{"amount":1,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#0b0902ff","u16":[2737,2395,531,65535]}}, +{"amount":0.2,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#1c1805ff","u16":[7120,6230,1381,65535]}}, +{"amount":0.3,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#302a09ff","u16":[12361,10817,2398,65535]}}, +{"amount":0.4,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#473e0eff","u16":[18315,16025,3552,65535]}}, +{"amount":0.5,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#615513ff","u16":[24833,21729,4817,65535]}}, +{"amount":0.7,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#99861eff","u16":[39294,34383,7622,65535]}}, +{"amount":1,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#000000ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#000000ff","color":"#3daee980","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#3daee980","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#3daee980","out":{"hex":"#03080bd1","u16":[756,2158,2889,53656]}}, +{"amount":0.2,"base":"#000000ff","color":"#3daee980","out":{"hex":"#08161dbe","u16":[1943,5542,7421,48879]}}, +{"amount":0.3,"base":"#000000ff","color":"#3daee980","out":{"hex":"#0d2632b1","u16":[3379,9639,12908,45382]}}, +{"amount":0.4,"base":"#000000ff","color":"#3daee980","out":{"hex":"#13384aa6","u16":[5004,14274,19114,42570]}}, +{"amount":0.5,"base":"#000000ff","color":"#3daee980","out":{"hex":"#1a4b659c","u16":[6779,19335,25891,40207]}}, +{"amount":0.7,"base":"#000000ff","color":"#3daee980","out":{"hex":"#2a779f8d","u16":[10730,30607,40986,36332]}}, +{"amount":1,"base":"#000000ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#000000ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":0.1,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,5]}}, +{"amount":0.2,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,5]}}, +{"amount":0.3,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,5]}}, +{"amount":0.4,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,5]}}, +{"amount":0.5,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,5]}}, +{"amount":0.7,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,5]}}, +{"amount":1,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#000000ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#fcfcfcff","u16":[64862,64862,64862,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#ecececff","u16":[60636,60636,60636,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#cbcbcbff","u16":[52290,52290,52290,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#a5a5a5ff","u16":[42517,42517,42517,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#838383ff","u16":[33590,33590,33590,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#4d4d4dff","u16":[19909,19909,19909,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#fefefeff","u16":[65381,65381,65381,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#fafafaff","u16":[64331,64331,64331,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#f0f0f0ff","u16":[61717,61717,61717,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#dfdfdfff","u16":[57414,57414,57414,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#cacacaff","u16":[51894,51894,51894,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#9c9c9cff","u16":[40095,40095,40095,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#fefefeff","u16":[65382,65382,65382,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#fafafaff","u16":[64347,64347,64347,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#f0f0f0ff","u16":[61752,61752,61752,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#e0e0e0ff","u16":[57491,57491,57491,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#cacacaff","u16":[52011,52011,52011,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#9d9d9dff","u16":[40246,40246,40246,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#fffefeff","u16":[65535,65341,65341,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#fff9f9ff","u16":[65535,63985,63985,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#ffececff","u16":[65535,60588,60588,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#ffd6d6ff","u16":[65535,54885,54885,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#ffb8b8ff","u16":[65535,47269,47269,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#ff6e6eff","u16":[65535,28299,28299,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#fefffeff","u16":[65276,65535,65276,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#f7fff7ff","u16":[63446,65535,63446,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#e4ffe4ff","u16":[58471,65535,58471,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#bcffbcff","u16":[48423,65535,48423,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#6eff6eff","u16":[28218,65535,28218,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#06ff06ff","u16":[1494,65535,1494,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#fefeffff","u16":[65217,65217,65535,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#f5f5ffff","u16":[63083,63083,65535,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#e2e2ffff","u16":[58093,58093,65535,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#c5c5ffff","u16":[50744,50744,65535,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#a5a5ffff","u16":[42364,42364,65535,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#6666ffff","u16":[26095,26095,65535,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#fffffbff","u16":[65535,65535,64628,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffffe1ff","u16":[65535,65535,57937,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffff82ff","u16":[65535,65535,33524,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffff06ff","u16":[65535,65535,1494,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffff06ff","u16":[65535,65535,1494,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffff06ff","u16":[65535,65535,1494,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#fcfcfcff","u16":[64859,64854,64850,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#ecececff","u16":[60716,60630,60544,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#cdcbc9ff","u16":[52780,52233,51684,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#a9a5a1ff","u16":[43421,42388,41355,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#88827cff","u16":[34866,33379,31892,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#564c42ff","u16":[22031,19536,17042,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#feffffff","u16":[65336,65445,65509,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#f9fcfeff","u16":[64027,64815,65327,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#ecf6fcff","u16":[60771,63172,64842,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#d7ebf9ff","u16":[55189,60293,63998,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#b7dcf6ff","u16":[46919,56413,63270,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#45b9f5ff","u16":[17645,47484,63063,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#fdfdfdff","u16":[64995,65000,65006,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#eff0f0ff","u16":[61534,61624,61715,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#d3d4d6ff","u16":[54105,54583,55070,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#acb2b8ff","u16":[44289,45776,47267,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#8a9198ff","u16":[35364,37173,38978,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#555b61ff","u16":[21874,23445,25015,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65465,65469,65473,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#fdfdfdff","u16":[64978,65012,65047,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#f8f8f9ff","u16":[63705,63817,63930,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65472,65472,65472,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#fdfdfdff","u16":[65034,65034,65034,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#fffefeff","u16":[65491,65335,65349,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#fef9f9ff","u16":[65171,63991,64087,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#faecedff","u16":[64334,60663,60940,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#f5d7d9ff","u16":[62936,55180,55727,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#eebbbfff","u16":[61197,48097,48987,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#e37b82ff","u16":[58322,31508,33488,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#fffffeff","u16":[65501,65459,65263,65535]}}, +{"amount":0.2,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#fefdf7ff","u16":[65278,64925,63420,65535]}}, +{"amount":0.3,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#fdf7e0ff","u16":[65019,63536,57529,65535]}}, +{"amount":0.4,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#fcedaeff","u16":[64880,61011,44815,65535]}}, +{"amount":0.5,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#fbdf46ff","u16":[64535,57326,18053,65535]}}, +{"amount":0.7,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59628,52176,11570,65535]}}, +{"amount":1,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#ffffffff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#feffffd6","u16":[65336,65448,65511,55050]}}, +{"amount":0.2,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#f9fcfebe","u16":[63984,64825,65333,48902]}}, +{"amount":0.3,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#ecf6fcac","u16":[60619,63212,64868,44109]}}, +{"amount":0.4,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#d5ebf99d","u16":[54869,60383,64051,40302]}}, +{"amount":0.5,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#b6dcf591","u16":[46857,56456,62938,37346]}}, +{"amount":0.7,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#47b9f482","u16":[18310,47464,62686,33524]}}, +{"amount":1,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#ffffffff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.1,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,5]}}, +{"amount":0.2,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,5]}}, +{"amount":0.3,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,5]}}, +{"amount":0.4,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,5]}}, +{"amount":0.5,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,5]}}, +{"amount":0.7,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,5]}}, +{"amount":1,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#ffffffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#7f7f7fff","u16":[32524,32524,32524,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#7c7c7cff","u16":[31748,31748,31748,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#747474ff","u16":[29837,29837,29837,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#686868ff","u16":[26747,26747,26747,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#595959ff","u16":[22857,22857,22857,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#373737ff","u16":[14074,14074,14074,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#7f7f7fff","u16":[32728,32728,32728,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#828282ff","u16":[33349,33349,33349,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#888888ff","u16":[34978,34978,34978,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#939393ff","u16":[37906,37906,37906,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#a4a4a4ff","u16":[42234,42234,42234,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#d4d4d4ff","u16":[54512,54512,54512,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#7f7f7fff","u16":[32676,32676,32676,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#b46b6bff","u16":[46141,27547,27547,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#d65353ff","u16":[54982,21394,21394,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#ec3434ff","u16":[60547,13272,13272,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,2,2,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,2,2,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,2,2,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#508d50ff","u16":[20609,36257,20609,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#3b933bff","u16":[15149,37760,15149,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#2d9a2dff","u16":[11450,39706,11450,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#22a622ff","u16":[8716,42680,8716,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#19b719ff","u16":[6443,46916,6443,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#08e608ff","u16":[2071,59069,2071,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#7a7aabff","u16":[31478,31478,44013,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#7676c0ff","u16":[30348,30348,49304,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#6f6fcfff","u16":[28531,28531,53326,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#6262e6ff","u16":[25203,25203,59119,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#5151f6ff","u16":[20713,20713,63204,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#1818ffff","u16":[6130,6130,65529,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#82824eff","u16":[33478,33478,20001,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#858539ff","u16":[34282,34282,14662,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#8c8c2cff","u16":[35963,35963,11286,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#979722ff","u16":[38894,38894,8859,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#a8a81bff","u16":[43183,43183,6867,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#d8d80cff","u16":[55447,55447,2970,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#7f7f7eff","u16":[32612,32511,32412,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#7c7b7bff","u16":[31932,31714,31497,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#757472ff","u16":[30160,29784,29410,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#6a6865ff","u16":[27251,26663,26077,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#5c5855ff","u16":[23592,22731,21875,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#3c3630ff","u16":[15450,13837,12231,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#648495ff","u16":[25650,33875,38169,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#5687a0ff","u16":[22023,34644,41234,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#4c8bacff","u16":[19540,35800,44290,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#4593bbff","u16":[17816,37668,48034,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#419dceff","u16":[16628,40420,52842,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44715,59876,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#7e7f80ff","u16":[32306,32585,32863,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#7a7c7fff","u16":[31457,31988,32521,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#73767aff","u16":[29641,30436,31233,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#686c70ff","u16":[26809,27837,28864,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#5a5f64ff","u16":[23243,24418,25593,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#3c4044ff","u16":[15309,16428,17550,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#7f7f80ff","u16":[32676,32729,32782,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#818282ff","u16":[33221,33298,33376,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#878788ff","u16":[34650,34778,34905,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#919293ff","u16":[37264,37476,37686,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#a0a1a3ff","u16":[41114,41464,41813,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#ccced0ff","u16":[52480,52950,53418,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#7f7f7fff","u16":[32726,32726,32726,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#828282ff","u16":[33340,33340,33340,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#888888ff","u16":[34939,34939,34939,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#939393ff","u16":[37823,37823,37823,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#a4a4a4ff","u16":[42061,42061,42061,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#d3d3d3ff","u16":[54188,54188,54188,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#a57177ff","u16":[42358,29159,30479,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#bd646dff","u16":[48513,25630,27918,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#d0515eff","u16":[53402,20932,24179,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56025,17477,21332,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56025,17477,21332,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56025,17477,21332,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#87805cff","u16":[34609,32890,23529,65535]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#8c824cff","u16":[35889,33335,19419,65535]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#938641ff","u16":[37726,34471,16735,65535]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#9e8e3aff","u16":[40537,36568,14946,65535]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#ad9b35ff","u16":[44517,39741,13720,65535]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#dabf2fff","u16":[56003,49176,11985,65535]}}, +{"amount":1,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#6c838ed3","u16":[27763,33579,36616,54245]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#60869abb","u16":[24550,34385,39521,48116]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#558ba6a9","u16":[21923,35624,42779,43385]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#4d92b69a","u16":[19728,37573,46890,39480]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#459dcb8d","u16":[17806,40386,52175,36160]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15678,44714,59874,32898]}}, +{"amount":1,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.1,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#7f7f7f00","u16":[32639,32639,32639,5]}}, +{"amount":0.2,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#7f7f7f00","u16":[32639,32639,32639,5]}}, +{"amount":0.3,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#7f7f7f00","u16":[32639,32639,32639,5]}}, +{"amount":0.4,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#7f7f7f00","u16":[32639,32639,32639,5]}}, +{"amount":0.5,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#7f7f7f00","u16":[32639,32639,32639,5]}}, +{"amount":0.7,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#7f7f7f00","u16":[32639,32639,32639,5]}}, +{"amount":1,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#7f7f7fff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#808080ff","color":"#000000ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#000000ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#000000ff","out":{"hex":"#808080ff","u16":[32780,32780,32780,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#000000ff","out":{"hex":"#7c7c7cff","u16":[31991,31991,31991,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#000000ff","out":{"hex":"#757575ff","u16":[30047,30047,30047,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#000000ff","out":{"hex":"#696969ff","u16":[26928,26928,26928,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#000000ff","out":{"hex":"#595959ff","u16":[22991,22991,22991,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#000000ff","out":{"hex":"#373737ff","u16":[14165,14165,14165,65535]}}, +{"amount":1,"base":"#808080ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#808080ff","u16":[32984,32984,32984,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#838383ff","u16":[33609,33609,33609,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#898989ff","u16":[35223,35223,35223,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#949494ff","u16":[38138,38138,38138,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#a5a5a5ff","u16":[42445,42445,42445,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#d5d5d5ff","u16":[54715,54715,54715,65535]}}, +{"amount":1,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#808080ff","u16":[32859,32859,32859,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#b46d6dff","u16":[46179,27926,27926,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#d65555ff","u16":[55017,21887,21887,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#ed3535ff","u16":[60818,13700,13700,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,2,2,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,2,2,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,2,2,65535]}}, +{"amount":1,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#518e51ff","u16":[20833,36528,20833,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#3c943cff","u16":[15295,38045,15295,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#2d9c2dff","u16":[11557,39986,11557,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#22a722ff","u16":[8779,42954,8779,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#19b819ff","u16":[6471,47174,6471,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#08e708ff","u16":[2024,59317,2024,65535]}}, +{"amount":1,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#7b7bacff","u16":[31739,31739,44238,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#7777c1ff","u16":[30614,30614,49487,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#7070d0ff","u16":[28786,28786,53478,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#6363e6ff","u16":[25492,25492,59067,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#5252f6ff","u16":[20998,20998,63163,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#1a1affff","u16":[6731,6731,65527,65535]}}, +{"amount":1,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#83834fff","u16":[33737,33737,20219,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#86863aff","u16":[34537,34537,14838,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#8d8d2cff","u16":[36213,36213,11404,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#989823ff","u16":[39130,39130,8941,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#a9a91bff","u16":[43412,43412,6913,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#d9d90cff","u16":[55644,55644,2957,65535]}}, +{"amount":1,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#030201ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#030201ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#030201ff","out":{"hex":"#807f7fff","u16":[32864,32763,32660,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#030201ff","out":{"hex":"#7d7c7cff","u16":[32175,31957,31740,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#030201ff","out":{"hex":"#767573ff","u16":[30373,29993,29616,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#030201ff","out":{"hex":"#6b6866ff","u16":[27431,26844,26254,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#030201ff","out":{"hex":"#5c5956ff","u16":[23735,22877,22014,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#030201ff","out":{"hex":"#3c3630ff","u16":[15540,13929,12312,65535]}}, +{"amount":1,"base":"#808080ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#658596ff","u16":[25837,34143,38482,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#5688a2ff","u16":[22168,34914,41571,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#4c8caeff","u16":[19636,36072,44653,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#4694bcff","u16":[17869,37935,48413,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#419ecfff","u16":[16638,40674,53223,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44715,59876,65535]}}, +{"amount":1,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#232629ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#232629ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#232629ff","out":{"hex":"#7f8081ff","u16":[32562,32841,33119,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#232629ff","out":{"hex":"#7b7d7fff","u16":[31697,32233,32767,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#232629ff","out":{"hex":"#74777aff","u16":[29864,30663,31462,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#232629ff","out":{"hex":"#696d71ff","u16":[26995,28028,29059,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#232629ff","out":{"hex":"#5b6064ff","u16":[23392,24571,25751,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#232629ff","out":{"hex":"#3c4045ff","u16":[15395,16520,17645,65535]}}, +{"amount":1,"base":"#808080ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#808081ff","u16":[32933,32985,33039,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#828383ff","u16":[33471,33548,33625,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#888889ff","u16":[34891,35022,35153,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#929394ff","u16":[37489,37705,37921,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#a1a2a4ff","u16":[41328,41685,42038,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#cdcfd1ff","u16":[52679,53147,53614,65535]}}, +{"amount":1,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#808080ff","u16":[32982,32982,32982,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#838383ff","u16":[33589,33589,33589,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#898989ff","u16":[35184,35184,35184,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#949494ff","u16":[38054,38054,38054,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#a4a4a4ff","u16":[42271,42271,42271,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#d4d4d4ff","u16":[54371,54371,54371,65535]}}, +{"amount":1,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#a57378ff","u16":[42495,29474,30776,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#bd656eff","u16":[48628,25986,28250,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#cf5460ff","u16":[53252,21557,24726,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56025,17477,21332,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56025,17477,21332,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56025,17477,21332,65535]}}, +{"amount":1,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#88815cff","u16":[34871,33147,23760,65535]}}, +{"amount":0.2,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#8d834cff","u16":[36160,33591,19589,65535]}}, +{"amount":0.3,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#948742ff","u16":[38004,34725,16862,65535]}}, +{"amount":0.4,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#9f8f3bff","u16":[40808,36811,15042,65535]}}, +{"amount":0.5,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#ae9c36ff","u16":[44778,39971,13781,65535]}}, +{"amount":0.7,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#dbc02fff","u16":[56251,49385,11976,65535]}}, +{"amount":1,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#808080ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#808080ff","color":"#3daee980","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#3daee980","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#3daee980","out":{"hex":"#6d8490d3","u16":[27982,33843,36903,54188]}}, +{"amount":0.2,"base":"#808080ff","color":"#3daee980","out":{"hex":"#60879bbb","u16":[24715,34653,39842,48014]}}, +{"amount":0.3,"base":"#808080ff","color":"#3daee980","out":{"hex":"#568ca8a8","u16":[22024,35898,43142,43244]}}, +{"amount":0.4,"base":"#808080ff","color":"#3daee980","out":{"hex":"#4d93b899","u16":[19774,37840,47274,39335]}}, +{"amount":0.5,"base":"#808080ff","color":"#3daee980","out":{"hex":"#459ecd8c","u16":[17781,40647,52585,36008]}}, +{"amount":0.7,"base":"#808080ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15679,44714,59874,32898]}}, +{"amount":1,"base":"#808080ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#808080ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":0.1,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#80808000","u16":[32896,32896,32896,5]}}, +{"amount":0.2,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#80808000","u16":[32896,32896,32896,5]}}, +{"amount":0.3,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#80808000","u16":[32896,32896,32896,5]}}, +{"amount":0.4,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#80808000","u16":[32896,32896,32896,5]}}, +{"amount":0.5,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#80808000","u16":[32896,32896,32896,5]}}, +{"amount":0.7,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#80808000","u16":[32896,32896,32896,5]}}, +{"amount":1,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#808080ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#fe0000ff","u16":[65304,0,0,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#f80000ff","u16":[63759,0,0,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#e90000ff","u16":[59942,0,0,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#d10000ff","u16":[53762,0,0,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#b30000ff","u16":[45965,0,0,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#6e0000ff","u16":[28299,0,0,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ff0e0eff","u16":[65535,3583,3583,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ff2424ff","u16":[65535,9186,9186,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ff3e3eff","u16":[65535,16017,16017,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ff5c5cff","u16":[65535,23683,23683,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ff7d7dff","u16":[65535,32137,32137,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ffc6c6ff","u16":[65535,50831,50831,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#f92525ff","u16":[63881,9445,9445,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#e64141ff","u16":[59022,16829,16829,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#c15c5cff","u16":[49616,23672,23672,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32641,32637,32637,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32641,32637,32637,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32641,32637,32637,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#f82626ff","u16":[63804,9640,9640,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#e54242ff","u16":[58861,17020,17020,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#bf5e5eff","u16":[49006,24055,24055,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32898,32894,32894,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32898,32894,32894,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32898,32894,32894,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#758700ff","u16":[29945,34620,0,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#608e00ff","u16":[24595,36404,0,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#4a9700ff","u16":[18933,38863,0,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#38a400ff","u16":[14282,42162,0,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#28b500ff","u16":[10394,46580,0,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#0de500ff","u16":[3279,58877,0,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#fb0051ff","u16":[64597,0,20808,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#ed009fff","u16":[60831,0,40958,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#ca00f2ff","u16":[51968,0,62203,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#8847ffff","u16":[34933,18273,65535,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#5e47ffff","u16":[24094,18200,65535,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#1616ffff","u16":[5575,5533,65535,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#e54900ff","u16":[58971,18696,0,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#d16100ff","u16":[53827,24838,0,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#c77200ff","u16":[51161,29411,0,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#c68500ff","u16":[50812,34149,0,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#cc9b00ff","u16":[52375,39733,0,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#e7d200ff","u16":[59320,54002,0,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#fe0100ff","u16":[65306,201,101,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#f80201ff","u16":[63758,433,217,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#e90301ff","u16":[59937,754,376,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#d10502ff","u16":[53759,1183,591,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#b30703ff","u16":[45906,1747,873,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#6d0d07ff","u16":[27889,3423,1708,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#f03546ff","u16":[61592,13516,18099,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#6c80abff","u16":[27760,32804,43928,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#6086b3ff","u16":[24695,34392,46054,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#548fbfff","u16":[21533,36723,49175,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#499bd0ff","u16":[18704,39850,53362,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15680,44713,59874,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#fe0e0fff","u16":[65165,3541,3821,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#f61c1eff","u16":[63218,7083,7642,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#e32b2eff","u16":[58419,11071,11944,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#c33a3eff","u16":[50181,14783,15949,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#9a4247ff","u16":[39485,17019,18363,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#4b3b40ff","u16":[19198,15144,16339,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#f92526ff","u16":[64099,9505,9723,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#f63537ff","u16":[63284,13687,14028,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#f34b4cff","u16":[62468,19226,19627,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#f06567ff","u16":[61612,25941,26370,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#ec8384ff","u16":[60739,33553,33996,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#e8c5c7ff","u16":[59524,50664,51083,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#fe1515ff","u16":[65271,5343,5343,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#fd2828ff","u16":[65111,10267,10267,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#fd4141ff","u16":[64945,16718,16718,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#fc5e5eff","u16":[64765,24174,24174,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#fb7e7eff","u16":[64564,32464,32464,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#fac6c6ff","u16":[64141,50892,50892,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#fc191eff","u16":[64777,6324,7719,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#f42a34ff","u16":[62614,10910,13316,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#e23d4bff","u16":[58090,15695,19158,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56028,17475,21329,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56028,17475,21329,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56028,17475,21329,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#e9440fff","u16":[59949,17387,3854,65535]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#d85a14ff","u16":[55473,23135,5128,65535]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#cd6b18ff","u16":[52766,27402,6074,65535]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#ca7b1bff","u16":[51879,31611,7007,65535]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#cd8d1fff","u16":[52627,36362,8061,65535]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#e1bc2aff","u16":[57698,48346,10717,65535]}}, +{"amount":1,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#fa202bdb","u16":[64197,8179,10952,56388]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#6f7faa93","u16":[28614,32625,43688,37882]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#6685b290","u16":[26174,34137,45713,36968]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#5a8ebe8b","u16":[23065,36514,48895,35763]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#4d9bcf86","u16":[19757,39741,53216,34456]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15683,44709,59869,32898]}}, +{"amount":1,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":0.1,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ff000000","u16":[65535,0,0,5]}}, +{"amount":0.2,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ff000000","u16":[65535,0,0,5]}}, +{"amount":0.3,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ff000000","u16":[65535,0,0,5]}}, +{"amount":0.4,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ff000000","u16":[65535,0,0,5]}}, +{"amount":0.5,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ff000000","u16":[65535,0,0,5]}}, +{"amount":0.7,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ff000000","u16":[65535,0,0,5]}}, +{"amount":1,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#ff0000ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#00fd00ff","u16":[0,65018,0,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#00f000ff","u16":[0,61728,0,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#00d500ff","u16":[0,54735,0,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#00b200ff","u16":[0,45755,0,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#009000ff","u16":[0,36891,0,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#005600ff","u16":[0,22199,0,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#1aff1aff","u16":[6587,65535,6587,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#42ff42ff","u16":[16925,65535,16925,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#73ff73ff","u16":[29451,65535,29451,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#aaffaaff","u16":[43604,65535,43604,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#e6ffe6ff","u16":[59091,65535,59091,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65523,65535,65523,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#2afe2aff","u16":[10712,65189,10712,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#4af84aff","u16":[19071,63762,19071,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#68ec68ff","u16":[26628,60660,26628,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#7ed97eff","u16":[32396,55825,32396,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#8bc28bff","u16":[35600,49799,35600,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#879187ff","u16":[34810,37335,34810,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#2afe2aff","u16":[10783,65186,10783,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#4bf84bff","u16":[19176,63760,19176,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#68ec68ff","u16":[26725,60680,26725,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#7fd97fff","u16":[32521,55856,32521,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#8bc28bff","u16":[35731,49872,35731,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#889288ff","u16":[34981,37451,34981,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#53fc00ff","u16":[21267,64655,0,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#a4ed00ff","u16":[42131,61031,0,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#facc00ff","u16":[64302,52443,0,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#ffb298ff","u16":[65535,45855,39121,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#ff998fff","u16":[65535,39201,36778,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#ff5251ff","u16":[65535,20966,20778,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#00fd64ff","u16":[0,64923,25634,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#00f0d4ff","u16":[0,61638,54508,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#a9cbffff","u16":[43545,52068,65535,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#a6b0ffff","u16":[42595,45317,65535,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#9095ffff","u16":[37103,38195,65535,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#5b5cffff","u16":[23377,23524,65535,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#69fa00ff","u16":[27011,64335,0,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#96f600ff","u16":[38676,63294,0,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#bbf500ff","u16":[47954,62981,0,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#ddf800ff","u16":[56700,63711,0,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65528,65533,0,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65528,65533,0,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#02fd01ff","u16":[399,65021,133,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#04f001ff","u16":[927,61729,310,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#07d502ff","u16":[1689,54746,563,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#0ab204ff","u16":[2697,45738,900,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#0f8f05ff","u16":[3927,36835,1309,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#1b5609ff","u16":[6948,21986,2319,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#13fe4aff","u16":[4955,65221,18928,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#21f97fff","u16":[8543,64069,32630,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#2df0acff","u16":[11590,61701,44271,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#36e2cfff","u16":[13920,57993,53168,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#3ccfe4ff","u16":[15322,53208,58526,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15678,44724,59883,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#10fd13ff","u16":[4188,65096,4905,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#22f328ff","u16":[8725,62331,10221,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#35da3fff","u16":[13747,56126,16105,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#45b951ff","u16":[17856,47512,20917,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#4d965aff","u16":[19828,38489,23227,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#455b50ff","u16":[17622,23443,20643,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#76f777ff","u16":[30241,63387,30555,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#92f394ff","u16":[37583,62499,38046,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#b5f0b7ff","u16":[46568,61666,47130,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#dbefdeff","u16":[56399,61301,56976,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61418,61679,61931,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61418,61679,61931,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#39fe39ff","u16":[14591,65173,14591,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#5afd5aff","u16":[23135,64927,23135,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#86fc86ff","u16":[34454,64656,34454,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#bafbbaff","u16":[47739,64383,47739,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#f1fbf1ff","u16":[61836,64405,61836,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64757,64762,64757,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#47fc1bff","u16":[18240,64847,6945,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#85f233ff","u16":[34185,62222,13015,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#c1dc49ff","u16":[49523,56663,18854,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#eebc5bff","u16":[61159,48317,23285,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#eda083ff","u16":[60913,41201,33655,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#dd696bff","u16":[56800,26969,27467,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#4cfc0fff","u16":[19551,64836,3793,65535]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#82f519ff","u16":[33294,62917,6458,65535]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#b2e722ff","u16":[45619,59405,8848,65535]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#ddd22bff","u16":[56819,53906,11021,65535]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59621,52173,11564,65535]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59621,52173,11564,65535]}}, +{"amount":1,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#0dfe30d6","u16":[3222,65359,12308,54983]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#17fb59be","u16":[5996,64483,22901,48788]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#22f383ab","u16":[8817,62410,33681,43882]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#2de5ab9b","u16":[11508,58855,43960,39866]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#35d2cc8f","u16":[13730,53947,52442,36633]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44725,59878,32898]}}, +{"amount":1,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":0.1,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#00ff0000","u16":[0,65535,0,5]}}, +{"amount":0.2,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#00ff0000","u16":[0,65535,0,5]}}, +{"amount":0.3,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#00ff0000","u16":[0,65535,0,5]}}, +{"amount":0.4,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#00ff0000","u16":[0,65535,0,5]}}, +{"amount":0.5,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#00ff0000","u16":[0,65535,0,5]}}, +{"amount":0.7,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#00ff0000","u16":[0,65535,0,5]}}, +{"amount":1,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#00ff00ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#0000feff","u16":[0,0,65359,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#0000faff","u16":[0,0,64167,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#0000eeff","u16":[0,0,61063,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#0000d8ff","u16":[0,0,55518,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#0000b9ff","u16":[0,0,47426,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#000053ff","u16":[0,0,21205,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#0c0cffff","u16":[3139,3139,65535,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#2020ffff","u16":[8113,8113,65535,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#3737ffff","u16":[14107,14107,65535,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#5151ffff","u16":[20906,20906,65535,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#6e6effff","u16":[28316,28316,65535,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#aeaeffff","u16":[44812,44812,65535,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#3838c3ff","u16":[14278,14278,50176,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#3e3eb4ff","u16":[15978,15978,46359,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#4646a6ff","u16":[18109,18109,42541,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#515198ff","u16":[20759,20759,39027,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#5d5d8cff","u16":[23958,23958,35992,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#7c7c7fff","u16":[31846,31846,32666,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#3737c4ff","u16":[14175,14175,50468,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#3e3eb5ff","u16":[15916,15916,46603,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#4646a7ff","u16":[18069,18069,42797,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#515199ff","u16":[20737,20737,39300,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#5d5d8dff","u16":[23968,23968,36265,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#7c7c80ff","u16":[31927,31927,32925,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#5800daff","u16":[22720,0,56027,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#980057ff","u16":[39148,0,22471,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#a20042ff","u16":[41753,0,17065,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#b10030ff","u16":[45431,0,12432,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#c40021ff","u16":[50445,0,8387,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#fa0002ff","u16":[64336,0,601,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#004e8eff","u16":[0,20089,36599,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#005962ff","u16":[0,22850,25234,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#00644aff","u16":[0,25739,18953,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#00743bff","u16":[0,29771,15157,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#008930ff","u16":[0,35110,12282,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#00be1aff","u16":[0,48927,6721,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#4141a4ff","u16":[16734,16734,42098,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#4f4f6eff","u16":[20183,20183,28370,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#5c5c47ff","u16":[23537,23537,18281,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#6c6c3aff","u16":[27728,27728,14896,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#818130ff","u16":[33198,33198,12352,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#b7b71cff","u16":[47115,47115,7217,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#0101feff","u16":[276,184,65359,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#0202faff","u16":[584,390,64164,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#0403eeff","u16":[1011,673,61049,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#0604d8ff","u16":[1620,1082,55444,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#0a07b8ff","u16":[2525,1685,47191,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#18103eff","u16":[6198,4135,15905,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#1232dbff","u16":[4546,12965,56408,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#1741c3ff","u16":[5872,16747,50087,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#1b4db7ff","u16":[6903,19691,46987,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#1f59b5ff","u16":[8049,22959,46573,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#2569bbff","u16":[9449,26953,48134,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#3391d5ff","u16":[13045,37210,54725,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#0c0dfcff","u16":[3035,3296,64871,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#1719f2ff","u16":[5878,6382,62099,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#2225d8ff","u16":[8855,9614,55605,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#2c2faeff","u16":[11241,12205,44656,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#2f3379ff","u16":[12081,13116,30995,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8998,9769,10546,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#1415faff","u16":[5120,5495,64373,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#2325f8ff","u16":[8950,9430,63709,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#3739f5ff","u16":[14217,14705,63054,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#5051f3ff","u16":[20453,20938,62367,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#6a6cf0ff","u16":[27368,27851,61650,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#a7a9ebff","u16":[42839,43310,60315,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#0f0ffeff","u16":[3807,3807,65286,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#2121fdff","u16":[8414,8414,65139,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#3737fdff","u16":[14220,14220,64992,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#5151fcff","u16":[20911,20911,64830,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#6e6efcff","u16":[28225,28225,64654,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#adadfaff","u16":[44526,44526,64258,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#7324a0ff","u16":[29609,9236,41156,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#7d278cff","u16":[32172,10035,35880,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#892b78ff","u16":[35220,10986,30931,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#982f6aff","u16":[38954,12151,27318,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#aa3560ff","u16":[43687,13627,24784,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56018,17474,21333,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#483da8ff","u16":[18566,15728,43135,65535]}}, +{"amount":0.2,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#56487eff","u16":[22019,18434,32329,65535]}}, +{"amount":0.3,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#5f535eff","u16":[24455,21398,24114,65535]}}, +{"amount":0.4,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#6e6051ff","u16":[28163,24643,20773,65535]}}, +{"amount":0.5,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#817048ff","u16":[33029,28900,18628,65535]}}, +{"amount":0.7,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#b29b3bff","u16":[45655,39948,15208,65535]}}, +{"amount":1,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#0000ffff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#0e29eacc","u16":[3683,10503,60081,52482]}}, +{"amount":0.2,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#1439d6b9","u16":[5152,14696,55009,47635]}}, +{"amount":0.3,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#1947caab","u16":[6366,18160,51920,44071]}}, +{"amount":0.4,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#1e55c7a0","u16":[7660,21849,51031,41202]}}, +{"amount":0.5,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#2466ca97","u16":[9169,26154,51875,38797]}}, +{"amount":0.7,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#3290dc88","u16":[12952,36947,56419,34870]}}, +{"amount":1,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#0000ffff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":0.1,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#0000ff00","u16":[0,0,65535,5]}}, +{"amount":0.2,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#0000ff00","u16":[0,0,65535,5]}}, +{"amount":0.3,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#0000ff00","u16":[0,0,65535,5]}}, +{"amount":0.4,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#0000ff00","u16":[0,0,65535,5]}}, +{"amount":0.5,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#0000ff00","u16":[0,0,65535,5]}}, +{"amount":0.7,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#0000ff00","u16":[0,0,65535,5]}}, +{"amount":1,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#0000ffff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#fdfd00ff","u16":[64896,64896,0,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#eded00ff","u16":[60905,60905,0,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#cece00ff","u16":[52885,52885,0,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#a8a800ff","u16":[43271,43271,0,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#868600ff","u16":[34344,34344,0,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#505000ff","u16":[20451,20451,0,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffff32ff","u16":[65535,65535,12937,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffff81ff","u16":[65535,65535,33257,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffffe1ff","u16":[65535,65535,57788,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65523,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65523,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65523,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#fefe2cff","u16":[65339,65339,11211,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#fafa4eff","u16":[64215,64215,20050,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#efef6dff","u16":[61503,61503,28067,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#dede85ff","u16":[57099,57099,34132,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#c8c892ff","u16":[51499,51499,37406,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#9a9a8eff","u16":[39563,39563,36379,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#fefe2cff","u16":[65339,65339,11285,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#fafa4eff","u16":[64230,64230,20118,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#efef6eff","u16":[61546,61546,28150,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#dede85ff","u16":[57173,57173,34242,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#c9c992ff","u16":[51597,51597,37541,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#9a9a8eff","u16":[39695,39695,36547,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#fff4e9ff","u16":[65535,62802,59939,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ffefe9ff","u16":[65535,61444,59829,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ffe3deff","u16":[65535,58231,56983,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ffcecaff","u16":[65535,52820,51882,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ffb1afff","u16":[65535,45516,44923,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ff6968ff","u16":[65535,26864,26767,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#e4ffcbff","u16":[58667,65535,52197,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#daffceff","u16":[55907,65535,52980,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#c3ffbdff","u16":[50218,65535,48682,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#96ff94ff","u16":[38612,65535,38048,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#05ff05ff","u16":[1309,65535,1309,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#05ff05ff","u16":[1309,65535,1309,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#fdfd6cff","u16":[64899,64899,27648,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#efefe7ff","u16":[61411,61411,59283,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#dddde9ff","u16":[56807,56807,59756,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#c3c3e6ff","u16":[50051,50051,59117,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#a3a3f0ff","u16":[41916,41916,61644,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#6464fdff","u16":[25674,25674,65133,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#fdfc01ff","u16":[65011,64867,144,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#eeed01ff","u16":[61175,60833,341,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#d0cd02ff","u16":[53369,52743,626,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#aba704ff","u16":[44035,43038,997,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#8a8406ff","u16":[35457,34019,1438,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#574d09ff","u16":[22258,19826,2432,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#ecfeb6ff","u16":[60571,65373,46821,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#e1fccaff","u16":[57852,64805,51970,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#d1f6d4ff","u16":[53770,63289,54517,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#8cf4d8ff","u16":[35924,62801,55420,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#6fe2edff","u16":[28436,58022,61005,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#42b5edff","u16":[17017,46548,60890,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#fcfd14ff","u16":[64735,65117,5241,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#eef12bff","u16":[61146,61961,11131,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#d2d744ff","u16":[53888,55166,17475,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#aeb457ff","u16":[44687,46323,22368,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#8b925fff","u16":[35780,37568,24427,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#555b53ff","u16":[21921,23479,21318,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#fdfe64ff","u16":[65092,65198,25707,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#f9f9aaff","u16":[63915,64096,43726,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#f0f1eeff","u16":[61574,61822,61101,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61932,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61932,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61932,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#fdfd8aff","u16":[64996,64996,35479,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcc7ff","u16":[64703,64703,51091,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64759,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64759,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64759,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64759,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#fff5e1ff","u16":[65515,62983,57946,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#fff0e2ff","u16":[65422,61556,58153,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#fde3d7ff","u16":[65074,58317,55157,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#facec3ff","u16":[64139,53013,50166,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#f3b4acff","u16":[62409,46206,44103,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#e27779ff","u16":[58113,30602,30997,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#fffaadff","u16":[65530,64142,44398,65535]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#fff6b2ff","u16":[65517,63344,45688,65535]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#fff199ff","u16":[65482,61886,39344,65535]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#fee754ff","u16":[65405,59373,21592,65535]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#f5d92cff","u16":[62921,55690,11220,65535]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59627,52175,11565,65535]}}, +{"amount":1,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#f0ff9dd7","u16":[61668,65475,40370,55258]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#e4feaabf","u16":[58493,65218,43664,49208]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#cefaabad","u16":[52927,64265,43957,44408]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#adf1b69e","u16":[44430,61982,46889,40540]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#7fe3cd92","u16":[32646,58259,52813,37481]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#44b5e982","u16":[17573,46636,59924,33360]}}, +{"amount":1,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.1,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffff0000","u16":[65535,65535,0,5]}}, +{"amount":0.2,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffff0000","u16":[65535,65535,0,5]}}, +{"amount":0.3,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffff0000","u16":[65535,65535,0,5]}}, +{"amount":0.4,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffff0000","u16":[65535,65535,0,5]}}, +{"amount":0.5,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffff0000","u16":[65535,65535,0,5]}}, +{"amount":0.7,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffff0000","u16":[65535,65535,0,5]}}, +{"amount":1,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#ffff00ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#030201ff","color":"#000000ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#000000ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[13,13,13,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[13,13,13,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[13,13,13,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[13,13,13,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[13,13,13,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[13,13,13,65535]}}, +{"amount":1,"base":"#030201ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#0c0c0bff","u16":[2994,2966,2938,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#1e1e1eff","u16":[7666,7626,7586,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#343433ff","u16":[13285,13239,13193,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#4d4c4cff","u16":[19670,19622,19575,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#686767ff","u16":[26625,26580,26535,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#a4a4a4ff","u16":[42136,42085,42033,65535]}}, +{"amount":1,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#070707ff","u16":[1784,1753,1723,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#111111ff","u16":[4376,4334,4292,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#1d1d1dff","u16":[7570,7523,7476,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#2b2b2bff","u16":[11179,11133,11086,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#3b3b3bff","u16":[15126,15083,15040,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#5d5d5dff","u16":[23888,23863,23839,65535]}}, +{"amount":1,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#808080ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#808080ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#808080ff","out":{"hex":"#070707ff","u16":[1796,1765,1735,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#808080ff","out":{"hex":"#111111ff","u16":[4409,4367,4325,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#808080ff","out":{"hex":"#1e1d1dff","u16":[7608,7561,7515,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#808080ff","out":{"hex":"#2c2c2bff","u16":[11232,11185,11138,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#808080ff","out":{"hex":"#3b3b3bff","u16":[15197,15154,15111,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#808080ff","out":{"hex":"#5d5d5dff","u16":[24009,23984,23959,65535]}}, +{"amount":1,"base":"#030201ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#0e0000ff","u16":[3494,61,31,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#220000ff","u16":[8754,84,42,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#3b0000ff","u16":[15136,94,47,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#570000ff","u16":[22409,93,47,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#760000ff","u16":[30365,85,43,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#bb0000ff","u16":[48023,50,25,65535]}}, +{"amount":1,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#000c00ff","u16":[86,3046,29,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#001e00ff","u16":[122,7776,40,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#013400ff","u16":[139,13485,46,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#014e00ff","u16":[142,19945,47,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#016900ff","u16":[135,27033,45,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#00a600ff","u16":[93,42754,31,65535]}}, +{"amount":1,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#000012ff","u16":[105,70,4522,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#01002bff","u16":[134,89,10928,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#01004aff","u16":[143,95,18895,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#01006dff","u16":[133,88,27938,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#000093ff","u16":[109,73,37844,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#0000e9ff","u16":[26,17,59825,65535]}}, +{"amount":1,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#0c0c00ff","u16":[3063,3035,28,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#1e1e00ff","u16":[7667,7627,40,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#343400ff","u16":[13326,13280,46,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#4d4c00ff","u16":[19706,19658,47,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#686800ff","u16":[26723,26677,45,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#a4a400ff","u16":[42231,42199,31,65535]}}, +{"amount":1,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#03090bff","u16":[843,2212,2913,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#08161dff","u16":[2048,5571,7391,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#0e2632ff","u16":[3489,9647,12839,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#14384aff","u16":[5111,14268,19027,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#1b4b65ff","u16":[6884,19347,25834,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#2a779fff","u16":[10793,30598,40926,65535]}}, +{"amount":1,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#232629ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#232629ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#232629ff","out":{"hex":"#040505ff","u16":[1140,1184,1228,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#232629ff","out":{"hex":"#0a0b0cff","u16":[2628,2793,2958,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#232629ff","out":{"hex":"#111314ff","u16":[4476,4807,5139,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#232629ff","out":{"hex":"#1a1c1eff","u16":[6574,7105,7636,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#232629ff","out":{"hex":"#232528ff","u16":[8871,9630,10389,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8993,9764,10535,65535]}}, +{"amount":1,"base":"#030201ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#0b0b0bff","u16":[2879,2862,2845,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#1c1c1cff","u16":[7240,7230,7219,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#313131ff","u16":[12537,12543,12549,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#484848ff","u16":[18545,18575,18605,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#626262ff","u16":[25115,25174,25234,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#9a9b9cff","u16":[39578,39843,40109,65535]}}, +{"amount":1,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#0c0b0bff","u16":[2961,2933,2905,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#1d1d1dff","u16":[7578,7538,7497,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#333333ff","u16":[13171,13125,13078,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#4c4b4bff","u16":[19440,19393,19345,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#676666ff","u16":[26346,26301,26255,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#a2a2a2ff","u16":[41667,41618,41571,65535]}}, +{"amount":1,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#0c0404ff","u16":[3007,971,1141,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#1d090bff","u16":[7544,2398,2866,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#331013ff","u16":[13094,4134,4979,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#4b181dff","u16":[19354,6087,7362,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#662027ff","u16":[26243,8231,9985,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#a2333eff","u16":[41542,12983,15813,65535]}}, +{"amount":1,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#0b0a02ff","u16":[2812,2443,557,65535]}}, +{"amount":0.2,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#1c1805ff","u16":[7169,6248,1408,65535]}}, +{"amount":0.3,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#302a09ff","u16":[12398,10819,2424,65535]}}, +{"amount":0.4,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#473e0eff","u16":[18332,16011,3576,65535]}}, +{"amount":0.5,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#615513ff","u16":[24853,21718,4839,65535]}}, +{"amount":0.7,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#99861eff","u16":[39312,34379,7638,65535]}}, +{"amount":1,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#030201ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#030201ff","color":"#3daee980","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#3daee980","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#3daee980","out":{"hex":"#03090bd1","u16":[880,2224,2905,53810]}}, +{"amount":0.2,"base":"#030201ff","color":"#3daee980","out":{"hex":"#08161dbf","u16":[2093,5571,7358,48995]}}, +{"amount":0.3,"base":"#030201ff","color":"#3daee980","out":{"hex":"#0e2632b1","u16":[3543,9659,12819,45471]}}, +{"amount":0.4,"base":"#030201ff","color":"#3daee980","out":{"hex":"#14384aa6","u16":[5162,14272,18995,42646]}}, +{"amount":0.5,"base":"#030201ff","color":"#3daee980","out":{"hex":"#1b4b649d","u16":[6927,19341,25792,40259]}}, +{"amount":0.7,"base":"#030201ff","color":"#3daee980","out":{"hex":"#2a779f8d","u16":[10825,30605,40913,36357]}}, +{"amount":1,"base":"#030201ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#030201ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":0.1,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#03020100","u16":[771,514,257,5]}}, +{"amount":0.2,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#03020100","u16":[771,514,257,5]}}, +{"amount":0.3,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#03020100","u16":[771,514,257,5]}}, +{"amount":0.4,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#03020100","u16":[771,514,257,5]}}, +{"amount":0.5,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#03020100","u16":[771,514,257,5]}}, +{"amount":0.7,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#03020100","u16":[771,514,257,5]}}, +{"amount":1,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#030201ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#3dade8ff","u16":[15600,44501,59589,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#3ba7e0ff","u16":[15090,43045,57639,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#369acfff","u16":[13905,39663,53112,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#2f87b5ff","u16":[12147,34654,46403,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#277197ff","u16":[10147,28942,38756,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#18455dff","u16":[6256,17836,23890,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#5aaed7ff","u16":[23080,44637,55287,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#59b0d7ff","u16":[22886,45340,55275,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#5ab7dcff","u16":[23235,46907,56572,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#5ec1e7ff","u16":[24228,49700,59454,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#77d0f3ff","u16":[30668,53446,62427,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#e3f4fcff","u16":[58439,62656,64762,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#58addaff","u16":[22494,44458,55926,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#69aaccff","u16":[27044,43722,52430,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#77a4bcff","u16":[30584,42236,48320,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#809baaff","u16":[32833,39889,43573,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#838f96ff","u16":[33572,36786,38463,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32641,32643,32644,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#58addaff","u16":[22535,44457,55902,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#6aaaccff","u16":[27138,43719,52375,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#77a4bcff","u16":[30705,42234,48254,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#809ba9ff","u16":[32973,39896,43512,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#838f95ff","u16":[33727,36807,38415,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32898,32900,32901,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#8fa4ccff","u16":[36861,42213,52324,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#b49ab8ff","u16":[46177,39479,47198,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#d28a9fff","u16":[53935,35417,40769,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#e97380ff","u16":[59983,29624,32838,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#f9545aff","u16":[63898,21713,23143,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#ff0202ff","u16":[65535,416,416,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#28b998ff","u16":[10196,47452,38946,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#1dbe6eff","u16":[7416,48760,28329,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#15c450ff","u16":[5365,50468,20493,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#0ecf37ff","u16":[3714,53100,14186,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#09de21ff","u16":[2252,56936,8603,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[1,65530,4,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#7aa2f8ff","u16":[31465,41718,63693,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#889bf9ff","u16":[34955,39898,64035,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#8892fbff","u16":[34828,37399,64447,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#7e83fcff","u16":[32390,33715,64883,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#6e71feff","u16":[28293,28927,65232,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#4141ffff","u16":[16650,16722,65515,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#69b297ff","u16":[26962,45806,38855,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#7eb46fff","u16":[32370,46203,28523,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#90b853ff","u16":[36937,47261,21287,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#a3c03dff","u16":[41823,49420,15664,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#b9ce2aff","u16":[47598,52862,10854,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#f4f706ff","u16":[62669,63438,1587,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#3eade7ff","u16":[15860,44507,59410,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#3da8dfff","u16":[15669,43055,57235,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#3a9accff","u16":[14926,39680,52394,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#3687b0ff","u16":[13759,34680,45270,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#317190ff","u16":[12496,28961,37068,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#294550ff","u16":[10521,17755,20631,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#45ade4ff","u16":[17807,44498,58580,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#4ca8daff","u16":[19557,43254,55916,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#519dc6ff","u16":[20870,40325,50930,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#538babff","u16":[21318,35828,43984,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#50778dff","u16":[20655,30484,36276,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#404d57ff","u16":[16350,19851,22301,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#60add5ff","u16":[24800,44469,54637,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#65afd2ff","u16":[26036,44981,54033,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#6db4d4ff","u16":[28075,46244,54545,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#7abddbff","u16":[31342,48553,56265,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#94cae3ff","u16":[37916,51797,58255,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#e9eceeff","u16":[59865,60680,61250,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#5baed7ff","u16":[23477,44599,55138,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#5cb0d6ff","u16":[23661,45261,54979,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#5fb6daff","u16":[24519,46759,56078,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#66c0e4ff","u16":[26312,49429,58640,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#80ceefff","u16":[32868,53043,61321,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#e8f2f7ff","u16":[59549,62101,63406,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#7ca7d8ff","u16":[31827,42929,55586,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#a19fbbff","u16":[41281,40975,47941,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#b893b0ff","u16":[47280,37710,45132,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#c78499ff","u16":[51017,33849,39346,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#d46d80ff","u16":[54432,28095,32769,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56029,17480,21336,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#72b0a2ff","u16":[29223,45170,41604,65535]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#8baf7eff","u16":[35710,45100,32328,65535]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#a0b163ff","u16":[41097,45525,25510,65535]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#b5b64eff","u16":[46540,46785,20144,65535]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#cdbf3dff","u16":[52671,49032,15615,65535]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59618,52168,11569,65535]}}, +{"amount":1,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32898]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32898]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32898]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32898]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32898]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32898]}}, +{"amount":1,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":0.1,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,5]}}, +{"amount":0.2,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,5]}}, +{"amount":0.3,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,5]}}, +{"amount":0.4,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,5]}}, +{"amount":0.5,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,5]}}, +{"amount":0.7,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,5]}}, +{"amount":1,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#3daee9ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#232629ff","color":"#000000ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#000000ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#000000ff","out":{"hex":"#232629ff","u16":[8954,9722,10489,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#000000ff","out":{"hex":"#222528ff","u16":[8666,9409,10153,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#000000ff","out":{"hex":"#1f2124ff","u16":[7868,8542,9217,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#000000ff","out":{"hex":"#181a1cff","u16":[6195,6723,7255,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#000000ff","out":{"hex":"#070708ff","u16":[1702,1848,1994,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#000000ff","out":{"hex":"#010101ff","u16":[220,220,220,65535]}}, +{"amount":1,"base":"#232629ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#262728ff","u16":[9791,10025,10259,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#2e2f2fff","u16":[11819,11993,12166,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#3e3e3fff","u16":[15863,16023,16183,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#535454ff","u16":[21375,21528,21680,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#6c6d6eff","u16":[27853,28044,28235,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#a6a8a9ff","u16":[42789,43117,43442,65535]}}, +{"amount":1,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#252628ff","u16":[9456,9810,10165,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#28292aff","u16":[10205,10452,10700,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#2e2f2fff","u16":[11807,12005,12204,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#383839ff","u16":[14315,14483,14652,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#444546ff","u16":[17578,17720,17862,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#646465ff","u16":[25704,25775,25847,65535]}}, +{"amount":1,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#808080ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#808080ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#808080ff","out":{"hex":"#252628ff","u16":[9459,9811,10164,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#808080ff","out":{"hex":"#28292aff","u16":[10206,10454,10701,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#808080ff","out":{"hex":"#2e2f30ff","u16":[11828,12027,12225,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#808080ff","out":{"hex":"#383939ff","u16":[14355,14524,14693,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#808080ff","out":{"hex":"#454546ff","u16":[17644,17786,17928,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#808080ff","out":{"hex":"#646565ff","u16":[25825,25897,25969,65535]}}, +{"amount":1,"base":"#232629ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#471314ff","u16":[18152,4803,5181,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#500d0eff","u16":[20444,3247,3504,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#5d0a0bff","u16":[23886,2529,2729,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#710809ff","u16":[29005,2100,2266,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#8b0707ff","u16":[35598,1730,1866,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#ca0304ff","u16":[51869,846,913,65535]}}, +{"amount":1,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#0c2c0eff","u16":[3074,11415,3601,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#09340aff","u16":[2206,13272,2584,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#084309ff","u16":[1945,17092,2279,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#075708ff","u16":[1812,22470,2123,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#067108ff","u16":[1653,28945,1936,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#04ac05ff","u16":[1107,44123,1297,65535]}}, +{"amount":1,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#13156eff","u16":[4932,5355,28336,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#0d0e7cff","u16":[3333,3619,31903,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#090a8cff","u16":[2380,2584,35862,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#0707a2ff","u16":[1708,1854,41530,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#0405bfff","u16":[1117,1212,49007,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[1,1,65525,65535]}}, +{"amount":1,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#27280dff","u16":[10096,10337,3300,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#2f3009ff","u16":[12099,12275,2420,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#3f3f09ff","u16":[16098,16259,2200,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#545508ff","u16":[21580,21733,2091,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#6d6e08ff","u16":[28086,28228,1932,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#a8a805ff","u16":[43197,43294,1330,65535]}}, +{"amount":1,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#030201ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#030201ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#030201ff","out":{"hex":"#232628ff","u16":[9068,9707,10347,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#030201ff","out":{"hex":"#232426ff","u16":[8912,9374,9836,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#030201ff","out":{"hex":"#202122ff","u16":[8315,8478,8644,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#030201ff","out":{"hex":"#1b1a18ff","u16":[6954,6607,6264,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#030201ff","out":{"hex":"#090704ff","u16":[2404,1679,954,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[814,543,272,65535]}}, +{"amount":1,"base":"#232629ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#172832ff","u16":[5870,10334,12814,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#162c39ff","u16":[5576,11384,14522,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#183545ff","u16":[6128,13710,17756,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#1c4358ff","u16":[7211,17219,22521,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#22546fff","u16":[8613,21622,28482,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#2e7da7ff","u16":[11924,32224,42865,65535]}}, +{"amount":1,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#262728ff","u16":[9724,9997,10269,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#2d2e2fff","u16":[11549,11768,11987,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#3b3c3dff","u16":[15233,15451,15669,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#4f5051ff","u16":[20373,20604,20836,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#676869ff","u16":[26456,26720,26985,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#9d9fa2ff","u16":[40360,40959,41551,65535]}}, +{"amount":1,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#262728ff","u16":[9783,10019,10255,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#2e2e2fff","u16":[11772,11946,12121,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#3d3e3fff","u16":[15747,15907,16067,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#525354ff","u16":[21177,21330,21482,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#6b6c6dff","u16":[27604,27786,27967,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#a5a6a7ff","u16":[42382,42681,42977,65535]}}, +{"amount":1,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#381e23ff","u16":[14418,7822,8887,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#411d22ff","u16":[16628,7498,8692,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#4d1f25ff","u16":[19784,7994,9395,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#5f232aff","u16":[24334,9112,10820,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#752932ff","u16":[30159,10663,12765,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#ae3945ff","u16":[44654,14525,17610,65535]}}, +{"amount":1,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#292714ff","u16":[10648,9987,5172,65535]}}, +{"amount":0.2,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#302c12ff","u16":[12363,11298,4629,65535]}}, +{"amount":0.3,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#3d3713ff","u16":[15782,14228,5002,65535]}}, +{"amount":0.4,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#504817ff","u16":[20628,18436,5793,65535]}}, +{"amount":0.5,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#675c1aff","u16":[26563,23592,6776,65535]}}, +{"amount":0.7,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#9e8b23ff","u16":[40525,35691,8930,65535]}}, +{"amount":1,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#232629ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#232629ff","color":"#3daee980","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#3daee980","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#3daee980","out":{"hex":"#192830d8","u16":[6531,10275,12424,55627]}}, +{"amount":0.2,"base":"#232629ff","color":"#3daee980","out":{"hex":"#182c37c5","u16":[6166,11352,14205,50503]}}, +{"amount":0.3,"base":"#232629ff","color":"#3daee980","out":{"hex":"#1a3544b6","u16":[6673,13679,17461,46651]}}, +{"amount":0.4,"base":"#232629ff","color":"#3daee980","out":{"hex":"#1e4357a9","u16":[7726,17199,22257,43492]}}, +{"amount":0.5,"base":"#232629ff","color":"#3daee980","out":{"hex":"#23546e9f","u16":[9080,21597,28232,40820]}}, +{"amount":0.7,"base":"#232629ff","color":"#3daee980","out":{"hex":"#307da68e","u16":[12221,32222,42726,36438]}}, +{"amount":1,"base":"#232629ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#232629ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":0.1,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#23262900","u16":[8995,9766,10537,5]}}, +{"amount":0.2,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#23262900","u16":[8995,9766,10537,5]}}, +{"amount":0.3,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#23262900","u16":[8995,9766,10537,5]}}, +{"amount":0.4,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#23262900","u16":[8995,9766,10537,5]}}, +{"amount":0.5,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#23262900","u16":[8995,9766,10537,5]}}, +{"amount":0.7,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#23262900","u16":[8995,9766,10537,5]}}, +{"amount":1,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#232629ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#eeeeeeff","u16":[61047,61079,61111,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#dfe0e0ff","u16":[57430,57490,57550,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#c3c3c4ff","u16":[50063,50186,50312,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#a0a1a1ff","u16":[41112,41284,41456,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#7f8080ff","u16":[32730,32867,33004,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#4c4c4dff","u16":[19556,19633,19710,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#eff0f1ff","u16":[61487,61744,62000,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#f1f2f3ff","u16":[61951,62181,62408,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#f6f6f7ff","u16":[63196,63345,63493,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65534,65534,65534,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65534,65534,65534,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65534,65534,65534,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#efeff0ff","u16":[61466,61520,61573,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#ecececff","u16":[60582,60614,60646,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#e3e3e3ff","u16":[58320,58349,58377,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#d4d4d4ff","u16":[54528,54562,54595,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#c1c1c1ff","u16":[49592,49628,49662,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#979797ff","u16":[38728,38739,38752,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#efeff0ff","u16":[61467,61521,61575,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#ecececff","u16":[60590,60622,60654,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#e3e3e3ff","u16":[58354,58382,58410,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#d4d5d5ff","u16":[54603,54636,54669,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#c1c1c2ff","u16":[49694,49729,49763,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#979797ff","u16":[38849,38860,38872,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#fcececff","u16":[64703,60610,60647,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#fde7e7ff","u16":[64953,59358,59377,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#fddbdbff","u16":[65000,56345,56358,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#fdc7c7ff","u16":[65024,51210,51219,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#fdacacff","u16":[65095,44188,44193,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#ff6464ff","u16":[65428,25719,25720,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#d4f9d5ff","u16":[54604,64093,54749,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#c7fbc7ff","u16":[51201,64577,51270,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#abfdabff","u16":[43853,65080,43887,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#6fff6fff","u16":[28614,65470,28622,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#04ff04ff","u16":[1146,65535,1146,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#04ff04ff","u16":[1146,65535,1146,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#eeeefdff","u16":[61085,61114,64946,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#e7e7fdff","u16":[59250,59265,65091,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#d6d6fdff","u16":[54923,54933,65096,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#bcbcfdff","u16":[48378,48384,65124,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#9e9efeff","u16":[40685,40689,65202,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#6262feff","u16":[25158,25158,65395,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#f7f767ff","u16":[63434,63544,26390,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#fafa2fff","u16":[64185,64236,12132,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#ffff02ff","u16":[65455,65457,508,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#eeeeeeff","u16":[61066,61081,61095,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#e0e0dfff","u16":[57524,57481,57437,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#c5c3c1ff","u16":[50520,50114,49705,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#a3a09dff","u16":[41952,41144,40335,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#847f7aff","u16":[33951,32674,31396,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#544b42ff","u16":[21547,19227,16897,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#e6f1f8ff","u16":[59023,62048,63819,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#e1eff9ff","u16":[57724,61512,63907,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#a0d3f5ff","u16":[41247,54108,62918,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#3fb1edff","u16":[16319,45586,60867,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#eeeeeeff","u16":[61136,61211,61285,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#e2e3e4ff","u16":[58129,58343,58562,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#c9cbceff","u16":[51549,52217,52896,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#a6acb3ff","u16":[42605,44294,45983,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#868d94ff","u16":[34448,36249,38048,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#545a60ff","u16":[21498,23048,24598,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#eff0f1ff","u16":[61513,61738,61964,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#f1f2f3ff","u16":[62000,62165,62332,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#f6f6f7ff","u16":[63245,63313,63379,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64763,64763,64763,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64763,64763,64763,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64763,64763,64763,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#f8edeeff","u16":[63791,60841,61162,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#f8e8e9ff","u16":[63688,59634,60003,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#f0cacdff","u16":[61780,51842,52578,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#ebb1b5ff","u16":[60292,45382,46434,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#e2727bff","u16":[58169,29426,31641,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#f6f0d2ff","u16":[63236,61785,53919,65535]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#f9efbcff","u16":[63938,61330,48429,65535]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#faea99ff","u16":[64345,60053,39216,65535]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#fae154ff","u16":[64287,57709,21665,65535]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#efd339ff","u16":[61544,54282,14657,65535]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59627,52175,11570,65535]}}, +{"amount":1,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#e7f1f7d6","u16":[59311,61997,63523,54983]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#e1eff8be","u16":[57824,61503,63688,48833]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#d5eaf7ab","u16":[54795,60086,63388,44008]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#c1e0f49c","u16":[49586,57567,62741,40138]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#a3d2f290","u16":[41803,54089,62099,37112]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#40b1ec81","u16":[16550,45578,60736,33130]}}, +{"amount":1,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":0.1,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#eff0f100","u16":[61423,61680,61937,5]}}, +{"amount":0.2,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#eff0f100","u16":[61423,61680,61937,5]}}, +{"amount":0.3,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#eff0f100","u16":[61423,61680,61937,5]}}, +{"amount":0.4,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#eff0f100","u16":[61423,61680,61937,5]}}, +{"amount":0.5,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#eff0f100","u16":[61423,61680,61937,5]}}, +{"amount":0.7,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#eff0f100","u16":[61423,61680,61937,5]}}, +{"amount":1,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#eff0f1ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#f9f9f9ff","u16":[64110,64110,64110,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#eaeaeaff","u16":[60017,60017,60017,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#cacacaff","u16":[51891,51891,51891,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#a4a4a4ff","u16":[42267,42267,42267,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#828282ff","u16":[33456,33456,33456,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#4d4d4dff","u16":[19856,19856,19856,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#fcfcfcff","u16":[64826,64826,64826,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#fefefeff","u16":[65263,65263,65263,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#fbfbfbff","u16":[64613,64613,64613,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#f7f7f7ff","u16":[63594,63594,63594,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#eeeeeeff","u16":[61049,61049,61049,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#ddddddff","u16":[56853,56853,56853,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#c8c8c8ff","u16":[51458,51458,51458,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#9b9b9bff","u16":[39839,39839,39839,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#fbfbfbff","u16":[64614,64614,64614,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#f8f8f8ff","u16":[63610,63610,63610,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#eeeeeeff","u16":[61083,61083,61083,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#dededeff","u16":[56929,56929,56929,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#c9c9c9ff","u16":[51559,51559,51559,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#9c9c9cff","u16":[39969,39969,39969,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#fffafaff","u16":[65493,64374,64374,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#fff5f5ff","u16":[65488,63062,63062,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#ffe8e8ff","u16":[65470,59752,59752,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#ffd3d3ff","u16":[65458,54163,54163,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#ffb6b6ff","u16":[65461,46676,46676,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#ff6c6cff","u16":[65503,27837,27837,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#f4fff4ff","u16":[62751,65445,62751,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#edffedff","u16":[60877,65459,60877,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#d9ffd9ff","u16":[55842,65450,55842,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#b1ffb1ff","u16":[45488,65458,45488,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#58ff58ff","u16":[22645,65529,22645,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#06ff06ff","u16":[1432,65535,1432,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#fbfbffff","u16":[64392,64392,65499,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#f3f3ffff","u16":[62327,62327,65484,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#e0e0ffff","u16":[57477,57477,65463,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#c4c4ffff","u16":[50285,50285,65458,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#a4a4ffff","u16":[42054,42054,65470,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#6565ffff","u16":[25940,25940,65507,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#fefed9ff","u16":[65342,65342,55657,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#ffffb1ff","u16":[65456,65456,45571,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#ffff05ff","u16":[65535,65535,1224,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#ffff05ff","u16":[65535,65535,1224,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#ffff05ff","u16":[65535,65535,1224,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#ffff05ff","u16":[65535,65535,1224,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#faf9f9ff","u16":[64122,64113,64103,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#eae9e9ff","u16":[60107,60009,59912,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#cccac7ff","u16":[52378,51812,51247,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#a8a4a0ff","u16":[43164,42140,41121,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#87817cff","u16":[34716,33250,31778,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#554c42ff","u16":[21967,19484,17012,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#f9fcfeff","u16":[63976,64797,65280,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#f4fafdff","u16":[62736,64174,65100,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#d3e8f8ff","u16":[54143,59747,63786,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#b2daf6ff","u16":[45839,55956,63190,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#44b7f4ff","u16":[17398,47117,62635,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#fafafaff","u16":[64234,64248,64261,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#ededeeff","u16":[60872,60977,61084,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#d1d3d5ff","u16":[53619,54120,54630,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#abb1b7ff","u16":[43982,45492,47006,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#899097ff","u16":[35206,36999,38795,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#555b61ff","u16":[21803,23368,24933,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#fcfcfcff","u16":[64662,64705,64747,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#fafafaff","u16":[64183,64259,64335,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#f5f5f6ff","u16":[62936,63093,63249,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#fefbfbff","u16":[65254,64424,64499,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#fdf6f6ff","u16":[64930,63122,63271,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#f4d4d6ff","u16":[62726,54533,55114,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#edb9bdff","u16":[61028,47569,48488,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#e37981ff","u16":[58290,31115,33136,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#fdfcf5ff","u16":[65122,64733,62877,65535]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#fdfaedff","u16":[64987,64210,60834,65535]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#fcf5d4ff","u16":[64862,62846,54475,65535]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#fceba0ff","u16":[64818,60365,40992,65535]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#f9dd44ff","u16":[63945,56736,17454,65535]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59628,52176,11570,65535]}}, +{"amount":1,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#f9fcfed6","u16":[63980,64797,65256,54983]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#f4fafdbe","u16":[62688,64187,65086,48902]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#e7f4fbac","u16":[59435,62603,64612,44096]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#d1e9f89d","u16":[53818,59830,63804,40276]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#b3daf491","u16":[45949,55989,62727,37312]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#46b7f282","u16":[17989,47102,62303,33459]}}, +{"amount":1,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":0.1,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#fcfcfc00","u16":[64764,64764,64764,5]}}, +{"amount":0.2,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#fcfcfc00","u16":[64764,64764,64764,5]}}, +{"amount":0.3,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#fcfcfc00","u16":[64764,64764,64764,5]}}, +{"amount":0.4,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#fcfcfc00","u16":[64764,64764,64764,5]}}, +{"amount":0.5,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#fcfcfc00","u16":[64764,64764,64764,5]}}, +{"amount":0.7,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#fcfcfc00","u16":[64764,64764,64764,5]}}, +{"amount":1,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#fcfcfcff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#d94453ff","u16":[55837,17416,21259,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#d44251ff","u16":[54554,17017,20771,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#c83e4cff","u16":[51398,16032,19569,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#b43844ff","u16":[46225,14418,17601,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#9a303bff","u16":[39612,12356,15081,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#5f1e24ff","u16":[24293,7583,9248,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#d6495bff","u16":[54982,18656,23404,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#dc485eff","u16":[56564,18495,24169,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#e45066ff","u16":[58545,20451,26308,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#e76678ff","u16":[59287,26105,30782,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#ea818fff","u16":[60209,33271,36714,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#f4c5caff","u16":[62792,50522,51857,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#ba5e67ff","u16":[47677,24181,26531,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#8c7578ff","u16":[35939,30139,30719,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#827c7dff","u16":[33485,31838,32003,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32641,32638,32638,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32641,32638,32638,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32641,32638,32638,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#996f73ff","u16":[39228,28548,29616,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#8f7477ff","u16":[36735,29861,30549,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#857b7cff","u16":[34153,31644,31894,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32898,32895,32895,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32898,32895,32895,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32898,32895,32895,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#f41216ff","u16":[62821,4724,5765,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#f70b0dff","u16":[63603,2711,3310,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#fd0202ff","u16":[65091,473,577,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,1,2,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,1,2,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65533,1,2,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#857b33ff","u16":[34163,31626,13007,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#618725ff","u16":[24808,34800,9446,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#48921bff","u16":[18509,37492,7047,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#369f15ff","u16":[13977,40869,5322,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#28b00fff","u16":[10334,45359,3934,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#0ee105ff","u16":[3640,57788,1386,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#cf419fff","u16":[53252,16610,40764,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#bb3ae0ff","u16":[48116,15009,57553,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#8e4ff1ff","u16":[36446,20426,61949,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#6c50f8ff","u16":[27819,20592,63663,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#5146fcff","u16":[20728,17877,64875,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#0202ffff","u16":[527,527,65535,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#bc6132ff","u16":[48414,24986,12964,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#b06d25ff","u16":[45153,28076,9449,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#ac791cff","u16":[44171,31118,7223,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#b08816ff","u16":[45215,34987,5660,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#bb9c11ff","u16":[47969,40013,4402,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#ded008ff","u16":[57126,53521,1995,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#d94453ff","u16":[55773,17502,21221,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#d44350ff","u16":[54416,17200,20687,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#c7404cff","u16":[51148,16347,19421,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#b23a44ff","u16":[45848,14913,17376,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#983339ff","u16":[39030,13070,14739,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#5a2321ff","u16":[23108,8909,8569,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#946e8eff","u16":[38061,28217,36616,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#7977a2ff","u16":[31159,30498,41744,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#6680a9ff","u16":[26095,32867,43406,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#578ab7ff","u16":[22248,35387,46977,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#4b96c8ff","u16":[19260,38587,51433,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15679,44714,59875,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#d24b5aff","u16":[53954,19319,23086,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#c6505eff","u16":[50918,20678,24286,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#b35461ff","u16":[45897,21547,24864,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#98545fff","u16":[39005,21551,24441,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#795059ff","u16":[31153,20455,22833,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#413b41ff","u16":[16765,15240,16611,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#d24c5dff","u16":[53993,19590,23979,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#d54e62ff","u16":[54693,20168,25161,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#da576bff","u16":[55904,22322,27443,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#d96b7cff","u16":[55775,27619,31760,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#d98591ff","u16":[55811,34261,37356,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#dfc3c8ff","u16":[57206,50160,51400,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#d5495bff","u16":[54796,18838,23489,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#db495fff","u16":[56216,18831,24320,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#e25167ff","u16":[58036,20844,26465,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#e46778ff","u16":[58595,26446,30908,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#e7828fff","u16":[59314,33513,36765,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#efc5c9ff","u16":[61472,50531,51705,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#c25c40ff","u16":[49982,23668,16351,65535]}}, +{"amount":0.2,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#b76737ff","u16":[47103,26521,14010,65535]}}, +{"amount":0.3,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#b37231ff","u16":[46002,29178,12596,65535]}}, +{"amount":0.4,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#b57e2eff","u16":[46555,32379,11802,65535]}}, +{"amount":0.5,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#bd8e2dff","u16":[48610,36461,11444,65535]}}, +{"amount":0.7,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#dbb92dff","u16":[56201,47497,11457,65535]}}, +{"amount":1,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#da4453ff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#9f6a89bb","u16":[40747,27203,35132,48014]}}, +{"amount":0.2,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#8a7299ac","u16":[35439,29407,39315,44199]}}, +{"amount":0.3,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#727daa9f","u16":[29337,32034,43665,40930]}}, +{"amount":0.4,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#6288b495","u16":[25302,34923,46222,38189]}}, +{"amount":0.5,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#5395c78b","u16":[21267,38367,51051,35828]}}, +{"amount":0.7,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15682,44712,59872,32898]}}, +{"amount":1,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#da4453ff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":0.1,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#da445300","u16":[56026,17476,21331,5]}}, +{"amount":0.2,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#da445300","u16":[56026,17476,21331,5]}}, +{"amount":0.3,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#da445300","u16":[56026,17476,21331,5]}}, +{"amount":0.4,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#da445300","u16":[56026,17476,21331,5]}}, +{"amount":0.5,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#da445300","u16":[56026,17476,21331,5]}}, +{"amount":0.7,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#da445300","u16":[56026,17476,21331,5]}}, +{"amount":1,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#da4453ff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#e6ca2dff","u16":[59212,51811,11485,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#dcc12bff","u16":[56545,49477,10967,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#c5ad26ff","u16":[50728,44388,9840,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#a79220ff","u16":[42941,37577,8328,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#88771aff","u16":[34940,30576,6776,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#524810ff","u16":[21192,18547,4107,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#e0cd65ff","u16":[57467,52574,25918,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#dccf83ff","u16":[56631,53090,33792,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#dfd48fff","u16":[57433,54369,36712,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#e9dd93ff","u16":[59792,56756,37899,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#f6eba1ff","u16":[63300,60385,41475,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65531,65520,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#e3cc4eff","u16":[58259,52314,19921,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#dcca65ff","u16":[56555,51808,25942,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#d1c479ff","u16":[53814,50277,31011,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#c2b986ff","u16":[49942,47553,34540,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#b0aa8dff","u16":[45224,43812,36113,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#898884ff","u16":[35101,34924,33960,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#e3cc4eff","u16":[58252,52316,19973,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#dcca65ff","u16":[56551,51817,26019,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#d1c479ff","u16":[53827,50302,31103,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#c2b987ff","u16":[49975,47599,34652,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#b0ab8dff","u16":[45279,43877,36244,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#898885ff","u16":[35194,35028,34120,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#febe8dff","u16":[65206,48877,36218,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#feb9a3ff","u16":[65288,47450,41934,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#feafa4ff","u16":[65367,44937,42110,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#ff9f99ff","u16":[65438,40754,39276,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#ff8785ff","u16":[65492,34805,34099,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#ff3f3fff","u16":[65534,16119,16082,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#5ee912ff","u16":[24068,59817,4668,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#3eed0cff","u16":[16062,60917,3116,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#21f306ff","u16":[8483,62455,1646,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#06fc01ff","u16":[1563,64826,303,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[4,65533,1,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[4,65533,1,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#e4c88cff","u16":[58627,51299,36104,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#c9c5ceff","u16":[51682,50569,52910,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#bbb9d7ff","u16":[48170,47551,55222,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#a8a6dfff","u16":[43057,42699,57312,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#8e8defff","u16":[36468,36253,61469,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#5656feff","u16":[22108,22073,65177,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#e1ce1dff","u16":[57814,52928,7581,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#dfd115ff","u16":[57249,53738,5448,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#e0d70fff","u16":[57668,55227,3787,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#e7e109ff","u16":[59273,57744,2374,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#f2ef04ff","u16":[62199,61514,1062,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65532,65531,0,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#e7ca2dff","u16":[59280,51790,11552,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#ddc02bff","u16":[56697,49428,11119,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#c6ac27ff","u16":[51001,44297,10111,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#a99222ff","u16":[43371,37432,8760,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#8a761dff","u16":[35549,30350,7396,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#574714ff","u16":[22233,18135,5185,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#c6d36cff","u16":[50859,54304,27838,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#aad698ff","u16":[43587,54892,39170,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#8cd3bdff","u16":[35996,54193,48597,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#6fcbd8ff","u16":[28401,52092,55458,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#53bee6ff","u16":[21401,48752,59204,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15681,44722,59881,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#e4cb3bff","u16":[58544,52070,15270,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#d8c449ff","u16":[55596,50371,18784,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#c2b456ff","u16":[49817,46269,22097,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#a49d5eff","u16":[42080,40293,24276,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#858360ff","u16":[34097,33720,24640,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#515550ff","u16":[20867,21800,20474,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#dfcd66ff","u16":[57343,52587,26331,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#dbcf87ff","u16":[56345,53071,34687,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#dbd3a3ff","u16":[56312,54187,41906,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#dfdbbfff","u16":[57403,56262,49179,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#e8e7dfff","u16":[59715,59491,57226,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61421,61677,61931,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#e0cd65ff","u16":[57464,52572,25915,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#dccf84ff","u16":[56602,53082,33901,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#ded396ff","u16":[57107,54314,38612,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#e6dca1ff","u16":[59210,56630,41298,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#f2eab8ff","u16":[62264,60146,47352,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64761,64760,64757,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#fbc172ff","u16":[64589,49559,29187,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#f9bc96ff","u16":[63966,48253,38448,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#f5b39dff","u16":[63013,45980,40413,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#f0a598ff","u16":[61603,42324,39111,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#e8918cff","u16":[59733,37370,35852,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#e25860ff","u16":[58067,22702,24785,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#d2d156d6","u16":[54038,53622,22103,54983]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#bcd379bd","u16":[48441,54198,31051,48697]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#a2d19caa","u16":[41509,53771,40048,43612]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#81cabd99","u16":[33268,52026,48470,39359]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#5ebed88b","u16":[24267,48873,55385,35786]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15684,44722,59878,32898]}}, +{"amount":1,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":0.1,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#e8cb2d00","u16":[59624,52171,11565,5]}}, +{"amount":0.2,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#e8cb2d00","u16":[59624,52171,11565,5]}}, +{"amount":0.3,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#e8cb2d00","u16":[59624,52171,11565,5]}}, +{"amount":0.4,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#e8cb2d00","u16":[59624,52171,11565,5]}}, +{"amount":0.5,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#e8cb2d00","u16":[59624,52171,11565,5]}}, +{"amount":0.7,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#e8cb2d00","u16":[59624,52171,11565,5]}}, +{"amount":1,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#e8cb2dff","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#3daee980","color":"#000000ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#000000ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#000000ff","out":{"hex":"#3dade8a4","u16":[15600,44499,59588,42135]}}, +{"amount":0.2,"base":"#3daee980","color":"#000000ff","out":{"hex":"#3ba7e0bd","u16":[15092,43047,57645,48698]}}, +{"amount":0.3,"base":"#3daee980","color":"#000000ff","out":{"hex":"#369acfd3","u16":[13904,39659,53110,54167]}}, +{"amount":0.4,"base":"#3daee980","color":"#000000ff","out":{"hex":"#2f87b5e3","u16":[12146,34654,46397,58302]}}, +{"amount":0.5,"base":"#3daee980","color":"#000000ff","out":{"hex":"#277197ee","u16":[10146,28943,38757,61146]}}, +{"amount":0.7,"base":"#3daee980","color":"#000000ff","out":{"hex":"#18455dfa","u16":[6270,17858,23922,64172]}}, +{"amount":1,"base":"#3daee980","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#58aed59e","u16":[22737,44746,54775,40580]}}, +{"amount":0.2,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#58b1d5b1","u16":[22510,45446,54808,45439]}}, +{"amount":0.3,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#59b7dbc1","u16":[22887,47005,56196,49644]}}, +{"amount":0.4,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#5dc2e6d0","u16":[23930,49771,59151,53480]}}, +{"amount":0.5,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#74d0f3de","u16":[29763,53563,62490,57105]}}, +{"amount":0.7,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#e3f4fcf9","u16":[58417,62662,64769,63905]}}, +{"amount":1,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#64acd2a3","u16":[25712,44256,53938,41852]}}, +{"amount":0.2,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#78a9c2ba","u16":[30791,43351,49910,47928]}}, +{"amount":0.3,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#84a3b3cf","u16":[33872,41802,45943,53295]}}, +{"amount":0.4,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#899aa3e2","u16":[35117,39521,41822,57980]}}, +{"amount":0.5,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#878e92f1","u16":[34707,36576,37551,61930]}}, +{"amount":0.7,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32641,32642,32643,65533]}}, +{"amount":1,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#808080ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#808080ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#808080ff","out":{"hex":"#64acd2a3","u16":[25763,44254,53909,41852]}}, +{"amount":0.2,"base":"#3daee980","color":"#808080ff","out":{"hex":"#78a9c2bb","u16":[30890,43344,49847,47986]}}, +{"amount":0.3,"base":"#3daee980","color":"#808080ff","out":{"hex":"#84a3b3d0","u16":[33977,41799,45883,53381]}}, +{"amount":0.4,"base":"#3daee980","color":"#808080ff","out":{"hex":"#899aa3e2","u16":[35222,39534,41785,58085]}}, +{"amount":0.5,"base":"#3daee980","color":"#808080ff","out":{"hex":"#878e92f2","u16":[34818,36601,37531,62068]}}, +{"amount":0.7,"base":"#3daee980","color":"#808080ff","out":{"hex":"#808080ff","u16":[32898,32899,32900,65533]}}, +{"amount":1,"base":"#3daee980","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#a9a0b59c","u16":[43471,41047,46546,39966]}}, +{"amount":0.2,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#d58ea1b0","u16":[54849,36566,41364,45165]}}, +{"amount":0.3,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#ee7d86c4","u16":[61118,32091,34472,50273]}}, +{"amount":0.4,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#fa696dd7","u16":[64150,26981,27931,55279]}}, +{"amount":0.5,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#fe4f50ea","u16":[65272,20415,20671,60104]}}, +{"amount":0.7,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#ff0202ff","u16":[65535,600,600,65533]}}, +{"amount":1,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#20bb7aa2","u16":[8207,48064,31350,41547]}}, +{"amount":0.2,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#15bf51b6","u16":[5419,49148,20698,46848]}}, +{"amount":0.3,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#0ec537c8","u16":[3668,50677,14007,51447]}}, +{"amount":0.4,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#09cf24d9","u16":[2412,53200,9212,55664]}}, +{"amount":0.5,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#05de15e8","u16":[1406,56965,5369,59626]}}, +{"amount":0.7,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[1,65531,2,65533]}}, +{"amount":1,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#889ff99f","u16":[34911,40892,63938,40848]}}, +{"amount":0.2,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#9099fbb5","u16":[36982,39276,64499,46636]}}, +{"amount":0.3,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#8c90fdc9","u16":[35982,36969,64976,51694]}}, +{"amount":0.4,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#8082feda","u16":[33023,33451,65287,56023]}}, +{"amount":0.5,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#6f70ffe7","u16":[28606,28782,65444,59488]}}, +{"amount":0.7,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#4141fff9","u16":[16688,16705,65530,63970]}}, +{"amount":1,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#77b27a9f","u16":[30590,45744,31247,40975]}}, +{"amount":0.2,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#8bb251b3","u16":[35757,45869,20852,45949]}}, +{"amount":0.3,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#9bb639c3","u16":[39741,46830,14617,50190]}}, +{"amount":0.4,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#abbf28d2","u16":[43997,48985,10286,54050]}}, +{"amount":0.5,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#bfcc1be0","u16":[49156,52491,6879,57684]}}, +{"amount":0.7,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#f5f704fb","u16":[62914,63375,951,64490]}}, +{"amount":1,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#030201ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#030201ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#030201ff","out":{"hex":"#3eade7a4","u16":[16061,44508,59265,42135]}}, +{"amount":0.2,"base":"#3daee980","color":"#030201ff","out":{"hex":"#3fa8ddbe","u16":[16139,43059,56900,48724]}}, +{"amount":0.3,"base":"#3daee980","color":"#030201ff","out":{"hex":"#3d9ac9d3","u16":[15787,39683,51768,54180]}}, +{"amount":0.4,"base":"#3daee980","color":"#030201ff","out":{"hex":"#3b87ace3","u16":[15124,34674,44259,58311]}}, +{"amount":0.5,"base":"#3daee980","color":"#030201ff","out":{"hex":"#38718bee","u16":[14419,28925,35612,61153]}}, +{"amount":0.7,"base":"#3daee980","color":"#030201ff","out":{"hex":"#334448fa","u16":[13111,17542,18408,64177]}}, +{"amount":1,"base":"#3daee980","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65533]}}, +{"amount":0.2,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65533]}}, +{"amount":0.3,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65533]}}, +{"amount":0.4,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65533]}}, +{"amount":0.5,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65533]}}, +{"amount":0.7,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65533]}}, +{"amount":1,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#232629ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#232629ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#232629ff","out":{"hex":"#4bade1a4","u16":[19255,44445,57841,42043]}}, +{"amount":0.2,"base":"#3daee980","color":"#232629ff","out":{"hex":"#56a8d4bc","u16":[22038,43125,54593,48352]}}, +{"amount":0.3,"base":"#3daee980","color":"#232629ff","out":{"hex":"#5d9cbfd1","u16":[23942,40101,49192,53660]}}, +{"amount":0.4,"base":"#3daee980","color":"#232629ff","out":{"hex":"#5e8aa4e1","u16":[24280,35535,42196,57793]}}, +{"amount":0.5,"base":"#3daee980","color":"#232629ff","out":{"hex":"#597587ec","u16":[22935,30195,34801,60751]}}, +{"amount":0.7,"base":"#3daee980","color":"#232629ff","out":{"hex":"#424d55f9","u16":[17082,19743,21804,64066]}}, +{"amount":1,"base":"#3daee980","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#63add19f","u16":[25436,44486,53768,40848]}}, +{"amount":0.2,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#6aafceb2","u16":[27236,44919,53005,45825]}}, +{"amount":0.3,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#74b3d0c3","u16":[29831,46090,53373,50168]}}, +{"amount":0.4,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#83bcd6d3","u16":[33618,48292,54919,54154]}}, +{"amount":0.5,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#9cc8dde1","u16":[40127,51485,56911,57918]}}, +{"amount":0.7,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#eaeceefd","u16":[60012,60641,61121,64967]}}, +{"amount":1,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#5baed49e","u16":[23383,44685,54545,40580]}}, +{"amount":0.2,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#5cb0d4b1","u16":[23770,45328,54360,45527]}}, +{"amount":0.3,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#61b6d8c2","u16":[24950,46779,55449,49734]}}, +{"amount":0.4,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#6ac0e1d1","u16":[27213,49390,57952,53605]}}, +{"amount":0.5,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#83ceecdf","u16":[33614,52986,60771,57248]}}, +{"amount":0.7,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#eaf1f5f9","u16":[60033,62001,63008,64092]}}, +{"amount":1,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#9ba3c09e","u16":[39899,41783,49333,40717]}}, +{"amount":0.2,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#ba98b3b5","u16":[47887,38965,46025,46490]}}, +{"amount":0.3,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#ca8da0c9","u16":[51967,36222,41061,51711]}}, +{"amount":0.4,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#d57d8ddc","u16":[54849,32214,36192,56502]}}, +{"amount":0.5,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#dd6877ec","u16":[56748,26765,30496,60689]}}, +{"amount":0.7,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56031,17479,21334,65533]}}, +{"amount":1,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#82af89a6","u16":[33337,44905,35316,42566]}}, +{"amount":0.2,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#99ad66bc","u16":[39394,44587,26190,48213]}}, +{"amount":0.3,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#abaf50cf","u16":[43918,44970,20513,53078]}}, +{"amount":0.4,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#bcb441e0","u16":[48431,46323,16628,57519]}}, +{"amount":0.5,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#d1be36f0","u16":[53625,48762,13775,61683]}}, +{"amount":0.7,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59619,52169,11567,65533]}}, +{"amount":1,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#3daee980","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.2,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.3,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.4,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.5,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.7,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#3daee980","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":0.1,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,2]}}, +{"amount":0.2,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,2]}}, +{"amount":0.3,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,2]}}, +{"amount":0.4,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,2]}}, +{"amount":0.5,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,2]}}, +{"amount":0.7,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#3daee900","u16":[15677,44718,59881,2]}}, +{"amount":1,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#3daee980","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#fcfcfc52","u16":[64854,64854,64854,21053]}}, +{"amount":0.2,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#ececec92","u16":[60640,60640,60640,37604]}}, +{"amount":0.3,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#cbcbcbc0","u16":[52295,52295,52295,49459]}}, +{"amount":0.4,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#a5a5a5dc","u16":[42516,42516,42516,56614]}}, +{"amount":0.5,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#838383ec","u16":[33616,33616,33616,60587]}}, +{"amount":0.7,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#4e4e4ef9","u16":[19943,19943,19943,64062]}}, +{"amount":1,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65530]}}, +{"amount":0.2,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65530]}}, +{"amount":0.3,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65530]}}, +{"amount":0.4,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65530]}}, +{"amount":0.5,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65530]}}, +{"amount":0.7,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65530]}}, +{"amount":1,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#fefefe39","u16":[65381,65381,65381,14527]}}, +{"amount":0.2,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#fafafa68","u16":[64330,64330,64330,26842]}}, +{"amount":0.3,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#f0f0f093","u16":[61712,61712,61712,37674]}}, +{"amount":0.4,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#dfdfdfb6","u16":[57413,57413,57413,46649]}}, +{"amount":0.5,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#cacacad1","u16":[51897,51897,51897,53610]}}, +{"amount":0.7,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#9c9c9cf2","u16":[40094,40094,40094,62251]}}, +{"amount":1,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#fefefe39","u16":[65382,65382,65382,14527]}}, +{"amount":0.2,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#fafafa68","u16":[64347,64347,64347,26764]}}, +{"amount":0.3,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#f0f0f092","u16":[61755,61755,61755,37604]}}, +{"amount":0.4,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#e0e0e0b5","u16":[57482,57482,57482,46606]}}, +{"amount":0.5,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#cacacad0","u16":[52002,52002,52002,53579]}}, +{"amount":0.7,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#9d9d9df2","u16":[40243,40243,40243,62262]}}, +{"amount":1,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#fffefe39","u16":[65535,65338,65338,14527]}}, +{"amount":0.2,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#fff9f969","u16":[65535,63978,63978,26918]}}, +{"amount":0.3,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#ffecec93","u16":[65535,60593,60593,37708]}}, +{"amount":0.4,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#ffd6d6b6","u16":[65535,54887,54887,46691]}}, +{"amount":0.5,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#ffb8b8d1","u16":[65535,47257,47257,53641]}}, +{"amount":0.7,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#ff6e6ef2","u16":[65535,28325,28325,62241]}}, +{"amount":1,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#fefffe3d","u16":[65280,65535,65280,15703]}}, +{"amount":0.2,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#f7fff772","u16":[63452,65535,63452,29334]}}, +{"amount":0.3,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#e4ffe4a2","u16":[58471,65535,58471,41719]}}, +{"amount":0.4,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#bcffbccd","u16":[48424,65535,48424,52782]}}, +{"amount":0.5,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#6eff6ef2","u16":[28246,65535,28246,62262]}}, +{"amount":0.7,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#06ff06ff","u16":[1494,65535,1494,65530]}}, +{"amount":1,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#fefeff42","u16":[65207,65207,65535,16933]}}, +{"amount":0.2,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#f5f5ff78","u16":[63072,63072,65535,30812]}}, +{"amount":0.3,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#e2e2ffa5","u16":[58102,58102,65535,42315]}}, +{"amount":0.4,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#c5c5ffc6","u16":[50741,50741,65535,50891]}}, +{"amount":0.5,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#a5a5ffdd","u16":[42369,42369,65535,56695]}}, +{"amount":0.7,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#6666fff4","u16":[26113,26113,65535,62808]}}, +{"amount":1,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#fffffb59","u16":[65535,65535,64633,22895]}}, +{"amount":0.2,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffffe1a6","u16":[65535,65535,57934,42579]}}, +{"amount":0.3,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffff82ec","u16":[65535,65535,33518,60622]}}, +{"amount":0.4,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffff06ff","u16":[65535,65535,1494,65530]}}, +{"amount":0.5,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffff06ff","u16":[65535,65535,1494,65530]}}, +{"amount":0.7,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffff06ff","u16":[65535,65535,1494,65530]}}, +{"amount":1,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#fffcfa52","u16":[65417,64736,64362,21053]}}, +{"amount":0.2,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#fce8dd92","u16":[64714,59744,56822,37604]}}, +{"amount":0.3,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#f7c19ac0","u16":[63466,49562,39509,49459]}}, +{"amount":0.4,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#e0954bdc","u16":[57631,38420,19210,56614]}}, +{"amount":0.5,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#b1763bec","u16":[45567,30378,15189,60587]}}, +{"amount":0.7,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#694623f9","u16":[27037,18025,9012,64062]}}, +{"amount":1,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#feffff36","u16":[65361,65442,65523,13845]}}, +{"amount":0.2,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#f9fcff65","u16":[64119,64783,65439,25877]}}, +{"amount":0.3,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#edf6fe8f","u16":[60887,63102,65230,36627]}}, +{"amount":0.4,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#d6eafcb3","u16":[55094,60227,64884,45914]}}, +{"amount":0.5,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#b5dbfbd0","u16":[46568,56360,64447,53502]}}, +{"amount":0.7,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#41b9f7f8","u16":[16784,47488,63563,63797]}}, +{"amount":1,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#fdfdfd4c","u16":[64955,65018,65087,19578]}}, +{"amount":0.2,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#eef0f28a","u16":[61198,61684,62212,35425]}}, +{"amount":0.3,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#cfd5dbb8","u16":[53217,54708,56293,47337]}}, +{"amount":0.4,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#a7b3bfd6","u16":[42875,45984,49133,55023]}}, +{"amount":0.5,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#86919de8","u16":[34381,37328,40275,59564]}}, +{"amount":0.7,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#545b63f8","u16":[21618,23471,25324,63736]}}, +{"amount":1,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#ffffff4c","u16":[65464,65469,65473,19578]}}, +{"amount":0.2,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#fdfdfd8d","u16":[64978,65012,65046,36326]}}, +{"amount":0.3,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#f8f8f9c9","u16":[63706,63818,63930,51724]}}, +{"amount":0.4,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65530]}}, +{"amount":0.5,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65530]}}, +{"amount":0.7,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61424,61681,61938,65530]}}, +{"amount":1,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#ffffff79","u16":[65472,65472,65472,31033]}}, +{"amount":0.2,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#fdfdfde0","u16":[65034,65034,65034,57620]}}, +{"amount":0.3,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65530]}}, +{"amount":0.4,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65530]}}, +{"amount":0.5,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65530]}}, +{"amount":0.7,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65530]}}, +{"amount":1,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#fffefe3a","u16":[65488,65331,65339,14842]}}, +{"amount":0.2,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#fef9f96a","u16":[65180,63992,64047,27146]}}, +{"amount":0.3,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#fbeced94","u16":[64436,60641,60821,38019]}}, +{"amount":0.4,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#f6d6d8b7","u16":[63288,55090,55499,46964]}}, +{"amount":0.5,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#f1babdd1","u16":[61951,47819,48579,53824]}}, +{"amount":0.7,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#e87880f2","u16":[59598,30785,32812,62208]}}, +{"amount":1,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#fffffe3a","u16":[65532,65451,65223,14842]}}, +{"amount":0.2,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#fffcf56a","u16":[65512,64889,63091,27369]}}, +{"amount":0.3,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#fff7df97","u16":[65461,63429,57224,38853]}}, +{"amount":0.4,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#feedafbf","u16":[65373,60838,45050,49067]}}, +{"amount":0.5,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#fede37e1","u16":[65256,57165,14097,57736]}}, +{"amount":0.7,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59628,52175,11566,65530]}}, +{"amount":1,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":1.5,"base":"#ffffff00","color":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#feffff1b","u16":[65361,65442,65523,6949]}}, +{"amount":0.2,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#f9fcff33","u16":[64119,64783,65439,12989]}}, +{"amount":0.3,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#edf6fe48","u16":[60887,63102,65230,18385]}}, +{"amount":0.4,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#d6eafc5a","u16":[55094,60227,64884,23047]}}, +{"amount":0.5,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#b5dbfb68","u16":[46568,56360,64447,26856]}}, +{"amount":0.7,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#41b9f77d","u16":[16784,47488,63563,32024]}}, +{"amount":1,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":1.5,"base":"#ffffff00","color":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"amount":-0.5,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":0.1,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65470,65470,65470,0]}}, +{"amount":0.2,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#fdfdfd00","u16":[65046,65046,65046,0]}}, +{"amount":0.3,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#f9f9f900","u16":[63903,63903,63903,0]}}, +{"amount":0.4,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#f1f1f100","u16":[61855,61855,61855,0]}}, +{"amount":0.5,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#e5e5e500","u16":[58869,58869,58869,0]}}, +{"amount":0.7,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#c6c6c600","u16":[50880,50880,50880,0]}}, +{"amount":1,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"amount":1.5,"base":"#ffffff00","color":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}} +], +"mix": [ +{"bias":-0.5,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#1a1a1aff","u16":[6554,6554,6554,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#404040ff","u16":[16384,16384,16384,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#808080ff","u16":[32768,32768,32768,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#bfbfbfff","u16":[49151,49151,49151,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#e6e6e6ff","u16":[58982,58982,58982,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#0d0d0dff","u16":[3264,3264,3264,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#202020ff","u16":[8160,8160,8160,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#404040ff","u16":[16320,16320,16320,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#5f5f5fff","u16":[24479,24479,24479,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#727272ff","u16":[29375,29375,29375,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#0d0d0dff","u16":[3290,3290,3290,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#606060ff","u16":[24672,24672,24672,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#737373ff","u16":[29606,29606,29606,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#1a0000ff","u16":[6554,0,0,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#400000ff","u16":[16384,0,0,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#800000ff","u16":[32768,0,0,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#bf0000ff","u16":[49151,0,0,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#e60000ff","u16":[58982,0,0,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#001a00ff","u16":[0,6554,0,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#004000ff","u16":[0,16384,0,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#008000ff","u16":[0,32768,0,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#00bf00ff","u16":[0,49151,0,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#00e600ff","u16":[0,58982,0,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#00001aff","u16":[0,0,6554,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#000040ff","u16":[0,0,16384,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#000080ff","u16":[0,0,32768,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#0000bfff","u16":[0,0,49151,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#0000e6ff","u16":[0,0,58982,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#1a1a00ff","u16":[6554,6554,0,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#404000ff","u16":[16384,16384,0,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#808000ff","u16":[32768,32768,0,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#bfbf00ff","u16":[49151,49151,0,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#e6e600ff","u16":[58982,58982,0,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#000000ff","u16":[77,51,26,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#010100ff","u16":[193,129,64,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#020101ff","u16":[386,257,129,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#020201ff","u16":[578,386,193,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[694,463,231,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#061117ff","u16":[1568,4472,5988,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#0f2c3aff","u16":[3919,11180,14970,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#1f5775ff","u16":[7839,22359,29941,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#2e83afff","u16":[11758,33539,44911,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#379dd2ff","u16":[14109,40246,53893,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#040404ff","u16":[900,977,1054,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#090a0aff","u16":[2249,2442,2634,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#121315ff","u16":[4498,4883,5269,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#1a1d1fff","u16":[6746,7325,7903,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#202225ff","u16":[8096,8789,9483,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#181818ff","u16":[6142,6168,6194,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#3c3c3cff","u16":[15356,15420,15484,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#787879ff","u16":[30712,30840,30969,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#b3b4b5ff","u16":[46067,46260,46453,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#d7d8d9ff","u16":[55281,55512,55743,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#191919ff","u16":[6476,6476,6476,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#3f3f3fff","u16":[16191,16191,16191,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#7e7e7eff","u16":[32382,32382,32382,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#bdbdbdff","u16":[48573,48573,48573,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#e3e3e3ff","u16":[58288,58288,58288,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#160708ff","u16":[5603,1748,2133,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#371115ff","u16":[14007,4369,5333,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#6d222aff","u16":[28013,8738,10666,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#a4333eff","u16":[42020,13107,15998,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#c43d4bff","u16":[50423,15728,19198,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#171405ff","u16":[5962,5217,1157,65535]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#3a330bff","u16":[14906,13043,2891,65535]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#746617ff","u16":[29812,26086,5783,65535]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#ae9822ff","u16":[44718,39128,8674,65535]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#d1b729ff","u16":[53662,46954,10409,65535]}}, +{"bias":1,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#03090cf2","u16":[828,2362,3163,62271]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#091921df","u16":[2247,6410,8583,57375]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#143a4ec0","u16":[5239,14945,20012,49216]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#25698ca0","u16":[9421,26873,35985,41056]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#328ebf8d","u16":[12836,36613,49028,36160]}}, +{"bias":1,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":0.1,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#000000e6","u16":[0,0,0,58982]}}, +{"bias":0.25,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#000000bf","u16":[0,0,0,49151]}}, +{"bias":0.5,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#00000080","u16":[0,0,0,32768]}}, +{"bias":0.75,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#00000040","u16":[0,0,0,16384]}}, +{"bias":0.9,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#0000001a","u16":[0,0,0,6554]}}, +{"bias":1,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#000000ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#e6e6e6ff","u16":[58982,58982,58982,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#bfbfbfff","u16":[49151,49151,49151,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#808080ff","u16":[32768,32768,32768,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#404040ff","u16":[16384,16384,16384,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#1a1a1aff","u16":[6554,6554,6554,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#f2f2f2ff","u16":[62245,62245,62245,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#dfdfdfff","u16":[57311,57311,57311,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#9f9f9fff","u16":[40863,40863,40863,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#8c8c8cff","u16":[35929,35929,35929,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#f2f2f2ff","u16":[62271,62271,62271,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#dfdfdfff","u16":[57375,57375,57375,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#c0c0c0ff","u16":[49216,49216,49216,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#a0a0a0ff","u16":[41056,41056,41056,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#8d8d8dff","u16":[36160,36160,36160,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ffe6e6ff","u16":[65535,58982,58982,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ffbfbfff","u16":[65535,49151,49151,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ff8080ff","u16":[65535,32768,32768,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ff4040ff","u16":[65535,16384,16384,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ff1a1aff","u16":[65535,6554,6554,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#e6ffe6ff","u16":[58982,65535,58982,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#bfffbfff","u16":[49151,65535,49151,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#80ff80ff","u16":[32768,65535,32768,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#40ff40ff","u16":[16384,65535,16384,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#1aff1aff","u16":[6554,65535,6554,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#e6e6ffff","u16":[58982,58982,65535,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#bfbfffff","u16":[49151,49151,65535,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#8080ffff","u16":[32768,32768,65535,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#4040ffff","u16":[16384,16384,65535,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#1a1affff","u16":[6554,6554,65535,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffffe6ff","u16":[65535,65535,58982,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffffbfff","u16":[65535,65535,49151,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffff80ff","u16":[65535,65535,32768,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffff40ff","u16":[65535,65535,16384,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffff1aff","u16":[65535,65535,6554,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#e6e6e6ff","u16":[59059,59033,59007,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#c0c0c0ff","u16":[49344,49280,49216,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#818180ff","u16":[33153,33025,32896,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#424141ff","u16":[16962,16769,16577,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#1c1b1aff","u16":[7247,7016,6785,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#ecf7fdff","u16":[60549,63453,64970,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#cfebfaff","u16":[53071,60331,64122,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#9ed7f4ff","u16":[40606,55127,62708,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#6ec2efff","u16":[28142,49922,61295,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#50b6ebff","u16":[20663,46800,60446,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#e9e9eaff","u16":[59881,59958,60035,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#c8c9caff","u16":[51400,51593,51786,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#919394ff","u16":[37265,37651,38036,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#5a5c5fff","u16":[23130,23708,24287,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#393c3eff","u16":[14649,15343,16037,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#fdfefeff","u16":[65124,65150,65175,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#fbfbfcff","u16":[64507,64571,64636,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#f7f8f8ff","u16":[63479,63608,63736,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#f3f4f5ff","u16":[62451,62644,62837,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#f1f2f2ff","u16":[61834,62066,62297,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65458,65458,65458,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#fefefeff","u16":[65342,65342,65342,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#fefefeff","u16":[65150,65150,65150,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#fdfdfdff","u16":[64957,64957,64957,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64841,64841,64841,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#fbeceeff","u16":[64584,60729,61115,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#f6d0d4ff","u16":[63158,53520,54484,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#eda2a9ff","u16":[60781,41506,43433,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#e3737eff","u16":[58403,29491,32382,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#de5764ff","u16":[56977,22282,25751,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#fdfaeaff","u16":[64944,64199,60138,65535]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#f9f2cbff","u16":[64057,62194,52043,65535]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#f4e596ff","u16":[62580,58853,38550,65535]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#eed862ff","u16":[61102,55512,25058,65535]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#ead042ff","u16":[60215,53507,16962,65535]}}, +{"bias":1,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#f5fbfef2","u16":[62901,64435,65236,62271]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#e3f3fcdf","u16":[58389,62551,64725,57375]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#bee4f8c0","u16":[48872,58578,63645,49216]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#8acef2a0","u16":[35573,53025,62137,41056]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#60bded8d","u16":[24713,48491,60906,36160]}}, +{"bias":1,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":0.1,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffffe6","u16":[65535,65535,65535,58982]}}, +{"bias":0.25,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffffbf","u16":[65535,65535,65535,49151]}}, +{"bias":0.5,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]}}, +{"bias":0.75,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffff40","u16":[65535,65535,65535,16384]}}, +{"bias":0.9,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffff1a","u16":[65535,65535,65535,6554]}}, +{"bias":1,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#ffffffff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#727272ff","u16":[29375,29375,29375,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#5f5f5fff","u16":[24479,24479,24479,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#404040ff","u16":[16320,16320,16320,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#202020ff","u16":[8160,8160,8160,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#0d0d0dff","u16":[3264,3264,3264,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#8c8c8cff","u16":[35929,35929,35929,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#9f9f9fff","u16":[40863,40863,40863,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#dfdfdfff","u16":[57311,57311,57311,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#f2f2f2ff","u16":[62245,62245,62245,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#7f7f7fff","u16":[32665,32665,32665,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#7f7f7fff","u16":[32703,32703,32703,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32768,32768,32768,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32832,32832,32832,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32870,32870,32870,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#8c7272ff","u16":[35929,29375,29375,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#9f5f5fff","u16":[40863,24479,24479,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#bf4040ff","u16":[49087,16320,16320,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#df2020ff","u16":[57311,8160,8160,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#f20d0dff","u16":[62245,3264,3264,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#728c72ff","u16":[29375,35929,29375,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#5f9f5fff","u16":[24479,40863,24479,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#40bf40ff","u16":[16320,49087,16320,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#20df20ff","u16":[8160,57311,8160,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#0df20dff","u16":[3264,62245,3264,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#72728cff","u16":[29375,29375,35929,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#5f5f9fff","u16":[24479,24479,40863,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#4040bfff","u16":[16320,16320,49087,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#2020dfff","u16":[8160,8160,57311,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#0d0df2ff","u16":[3264,3264,62245,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#8c8c72ff","u16":[35929,35929,29375,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#9f9f5fff","u16":[40863,40863,24479,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#bfbf40ff","u16":[49087,49087,16320,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#dfdf20ff","u16":[57311,57311,8160,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#f2f20dff","u16":[62245,62245,3264,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#737372ff","u16":[29452,29427,29401,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#606060ff","u16":[24672,24608,24544,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#414140ff","u16":[16705,16577,16448,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#222121ff","u16":[8738,8545,8353,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#0f0f0eff","u16":[3958,3727,3495,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#78848aff","u16":[30943,33847,35363,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#6f8b9aff","u16":[28399,35659,39450,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#5e97b4ff","u16":[24158,38679,46260,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#4ea2cfff","u16":[19918,41698,53071,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#44a9deff","u16":[17373,43510,57157,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#767676ff","u16":[30275,30352,30429,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#68696aff","u16":[26728,26921,27114,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#515354ff","u16":[20817,21203,21588,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#3a3c3fff","u16":[14906,15484,16063,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#2c2f32ff","u16":[11359,12053,12747,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#8a8a8aff","u16":[35517,35543,35569,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#9b9b9cff","u16":[39835,39899,39964,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#b7b8b8ff","u16":[47031,47160,47288,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#d3d4d5ff","u16":[54227,54420,54613,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#e4e5e6ff","u16":[58545,58776,59007,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#8c8c8cff","u16":[35852,35852,35852,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#9e9e9eff","u16":[40670,40670,40670,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#bebebeff","u16":[48702,48702,48702,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#ddddddff","u16":[56733,56733,56733,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#f0f0f0ff","u16":[61552,61552,61552,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#88797bff","u16":[34978,31123,31508,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#967074ff","u16":[38486,28848,29812,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#ad6269ff","u16":[44333,25058,26985,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#c3535eff","u16":[50179,21267,24158,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#d14a57ff","u16":[53687,18992,22462,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#8a8777ff","u16":[35338,34592,30532,65535]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#99926bff","u16":[39385,37522,27371,65535]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#b4a556ff","u16":[46132,42405,22102,65535]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#ceb842ff","u16":[52878,47288,16834,65535]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#dec335ff","u16":[56926,50218,13672,65535]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#7c8185f2","u16":[31743,33277,34078,62271]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#76868edf","u16":[30208,34370,36544,57375]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#698fa2c0","u16":[26970,36676,41743,49216]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#579bbfa0","u16":[22446,39898,49010,41056]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#49a5d68d","u16":[18751,42529,54944,36160]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":0.1,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#7f7f7fe6","u16":[32639,32639,32639,58982]}}, +{"bias":0.25,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#7f7f7fbf","u16":[32639,32639,32639,49151]}}, +{"bias":0.5,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#7f7f7f80","u16":[32639,32639,32639,32768]}}, +{"bias":0.75,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#7f7f7f40","u16":[32639,32639,32639,16384]}}, +{"bias":0.9,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#7f7f7f1a","u16":[32639,32639,32639,6554]}}, +{"bias":1,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#7f7f7fff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#737373ff","u16":[29606,29606,29606,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#606060ff","u16":[24672,24672,24672,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#0d0d0dff","u16":[3290,3290,3290,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#8d8d8dff","u16":[36160,36160,36160,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#a0a0a0ff","u16":[41056,41056,41056,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#c0c0c0ff","u16":[49216,49216,49216,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#dfdfdfff","u16":[57375,57375,57375,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#f2f2f2ff","u16":[62271,62271,62271,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#808080ff","u16":[32870,32870,32870,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#808080ff","u16":[32832,32832,32832,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#808080ff","u16":[32768,32768,32768,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32703,32703,32703,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32665,32665,32665,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#8d7373ff","u16":[36160,29606,29606,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#a06060ff","u16":[41056,24672,24672,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#c04040ff","u16":[49216,16448,16448,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#df2020ff","u16":[57375,8224,8224,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#f20d0dff","u16":[62271,3290,3290,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#738d73ff","u16":[29606,36160,29606,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#60a060ff","u16":[24672,41056,24672,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#40c040ff","u16":[16448,49216,16448,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#20df20ff","u16":[8224,57375,8224,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#0df20dff","u16":[3290,62271,3290,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#73738dff","u16":[29606,29606,36160,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#6060a0ff","u16":[24672,24672,41056,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#4040c0ff","u16":[16448,16448,49216,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#2020dfff","u16":[8224,8224,57375,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#0d0df2ff","u16":[3290,3290,62271,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#8d8d73ff","u16":[36160,36160,29606,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#a0a060ff","u16":[41056,41056,24672,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#c0c040ff","u16":[49216,49216,16448,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#dfdf20ff","u16":[57375,57375,8224,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#f2f20dff","u16":[62271,62271,3290,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#747373ff","u16":[29684,29658,29632,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#616160ff","u16":[24865,24801,24736,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#424141ff","u16":[16834,16705,16577,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#222221ff","u16":[8802,8610,8417,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#100f0eff","u16":[3984,3752,3521,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#79858bff","u16":[31174,34078,35595,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#6f8c9aff","u16":[28591,35852,39642,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#5f97b5ff","u16":[24287,38807,46389,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#4ea3cfff","u16":[19982,41763,53135,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#44a9dfff","u16":[17399,43536,57183,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#777777ff","u16":[30506,30583,30660,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#696a6aff","u16":[26921,27114,27306,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#525355ff","u16":[20946,21331,21717,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#3a3d3fff","u16":[14970,15549,16127,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#2c2f32ff","u16":[11385,12079,12773,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#8b8b8bff","u16":[35749,35774,35800,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#9c9c9cff","u16":[40028,40092,40156,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#b8b8b9ff","u16":[47160,47288,47417,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#d3d4d5ff","u16":[54291,54484,54677,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#e4e5e6ff","u16":[58570,58802,59033,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#8c8c8cff","u16":[36083,36083,36083,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#9f9f9fff","u16":[40863,40863,40863,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#bebebeff","u16":[48830,48830,48830,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#ddddddff","u16":[56797,56797,56797,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#f0f0f0ff","u16":[61577,61577,61577,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#897a7cff","u16":[35209,31354,31740,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#977175ff","u16":[38679,29041,30005,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#ad626aff","u16":[44461,25186,27114,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#c4535eff","u16":[50244,21331,24222,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#d14a58ff","u16":[53713,19018,22488,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#8a8878ff","u16":[35569,34824,30763,65535]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#9a936bff","u16":[39578,37715,27563,65535]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#b4a657ff","u16":[46260,42534,22231,65535]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#ceb842ff","u16":[52942,47352,16898,65535]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#dec435ff","u16":[56951,50244,13698,65535]}}, +{"bias":1,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#7c8286f2","u16":[31986,33521,34322,62271]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#76878fdf","u16":[30428,34591,36764,57375]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#6a8fa3c0","u16":[27141,36847,41914,49216]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#589cbfa0","u16":[22548,40000,49112,41056]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#49a6d68d","u16":[18798,42575,54990,36160]}}, +{"bias":1,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":0.1,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#808080e6","u16":[32896,32896,32896,58982]}}, +{"bias":0.25,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#808080bf","u16":[32896,32896,32896,49151]}}, +{"bias":0.5,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#80808080","u16":[32896,32896,32896,32768]}}, +{"bias":0.75,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#80808040","u16":[32896,32896,32896,16384]}}, +{"bias":0.9,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#8080801a","u16":[32896,32896,32896,6554]}}, +{"bias":1,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#808080ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#e60000ff","u16":[58982,0,0,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#bf0000ff","u16":[49151,0,0,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#800000ff","u16":[32768,0,0,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#400000ff","u16":[16384,0,0,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#1a0000ff","u16":[6554,0,0,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ff1a1aff","u16":[65535,6554,6554,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ff4040ff","u16":[65535,16384,16384,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ff8080ff","u16":[65535,32768,32768,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ffbfbfff","u16":[65535,49151,49151,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ffe6e6ff","u16":[65535,58982,58982,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#f20d0dff","u16":[62245,3264,3264,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#df2020ff","u16":[57311,8160,8160,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#bf4040ff","u16":[49087,16320,16320,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#9f5f5fff","u16":[40863,24479,24479,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#8c7272ff","u16":[35929,29375,29375,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#f20d0dff","u16":[62271,3290,3290,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#df2020ff","u16":[57375,8224,8224,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#c04040ff","u16":[49216,16448,16448,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#a06060ff","u16":[41056,24672,24672,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#8d7373ff","u16":[36160,29606,29606,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#e61a00ff","u16":[58982,6554,0,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#bf4000ff","u16":[49151,16384,0,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#808000ff","u16":[32768,32768,0,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#40bf00ff","u16":[16384,49151,0,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#1ae600ff","u16":[6554,58982,0,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#e6001aff","u16":[58982,0,6554,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#bf0040ff","u16":[49151,0,16384,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#800080ff","u16":[32768,0,32768,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#4000bfff","u16":[16384,0,49151,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#1a00e6ff","u16":[6554,0,58982,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ff1a00ff","u16":[65535,6554,0,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ff4000ff","u16":[65535,16384,0,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ff8000ff","u16":[65535,32768,0,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ffbf00ff","u16":[65535,49151,0,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ffe600ff","u16":[65535,58982,0,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#e60000ff","u16":[59059,51,26,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#c00100ff","u16":[49344,129,64,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#810101ff","u16":[33153,257,129,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#420201ff","u16":[16962,386,193,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#1c0201ff","u16":[7247,463,231,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#ec1117ff","u16":[60549,4472,5988,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#cf2c3aff","u16":[53071,11180,14970,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#9e5775ff","u16":[40606,22359,29941,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#6e83afff","u16":[28142,33539,44911,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#509dd2ff","u16":[20663,40246,53893,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#e90404ff","u16":[59881,977,1054,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#c80a0aff","u16":[51400,2442,2634,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#911315ff","u16":[37265,4883,5269,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#5a1d1fff","u16":[23130,7325,7903,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#392225ff","u16":[14649,8789,9483,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#fd1818ff","u16":[65124,6168,6194,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#fb3c3cff","u16":[64507,15420,15484,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#f77879ff","u16":[63479,30840,30969,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#f3b4b5ff","u16":[62451,46260,46453,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#f1d8d9ff","u16":[61834,55512,55743,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#ff1919ff","u16":[65458,6476,6476,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#fe3f3fff","u16":[65342,16191,16191,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#fe7e7eff","u16":[65150,32382,32382,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#fdbdbdff","u16":[64957,48573,48573,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#fce3e3ff","u16":[64841,58288,58288,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#fb0708ff","u16":[64584,1748,2133,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#f61115ff","u16":[63158,4369,5333,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#ed222aff","u16":[60781,8738,10666,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#e3333eff","u16":[58403,13107,15998,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#de3d4bff","u16":[56977,15728,19198,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#fd1405ff","u16":[64944,5217,1157,65535]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#f9330bff","u16":[64057,13043,2891,65535]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#f46617ff","u16":[62580,26086,5783,65535]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#ee9822ff","u16":[61102,39128,8674,65535]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#eab729ff","u16":[60215,46954,10409,65535]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#f5090cf2","u16":[62901,2362,3163,62271]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#e31921df","u16":[58389,6410,8583,57375]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#be3a4ec0","u16":[48872,14945,20012,49216]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#8a698ca0","u16":[35573,26873,35985,41056]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#608ebf8d","u16":[24713,36613,49028,36160]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":0.1,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ff0000e6","u16":[65535,0,0,58982]}}, +{"bias":0.25,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ff0000bf","u16":[65535,0,0,49151]}}, +{"bias":0.5,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ff000080","u16":[65535,0,0,32768]}}, +{"bias":0.75,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ff000040","u16":[65535,0,0,16384]}}, +{"bias":0.9,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ff00001a","u16":[65535,0,0,6554]}}, +{"bias":1,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#ff0000ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#00e600ff","u16":[0,58982,0,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#00bf00ff","u16":[0,49151,0,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#008000ff","u16":[0,32768,0,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#004000ff","u16":[0,16384,0,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#001a00ff","u16":[0,6554,0,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#1aff1aff","u16":[6554,65535,6554,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#40ff40ff","u16":[16384,65535,16384,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#80ff80ff","u16":[32768,65535,32768,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#bfffbfff","u16":[49151,65535,49151,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#e6ffe6ff","u16":[58982,65535,58982,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#0df20dff","u16":[3264,62245,3264,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#20df20ff","u16":[8160,57311,8160,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#40bf40ff","u16":[16320,49087,16320,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#5f9f5fff","u16":[24479,40863,24479,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#728c72ff","u16":[29375,35929,29375,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#0df20dff","u16":[3290,62271,3290,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#20df20ff","u16":[8224,57375,8224,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#40c040ff","u16":[16448,49216,16448,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#60a060ff","u16":[24672,41056,24672,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#738d73ff","u16":[29606,36160,29606,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#1ae600ff","u16":[6554,58982,0,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#40bf00ff","u16":[16384,49151,0,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#808000ff","u16":[32768,32768,0,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#bf4000ff","u16":[49151,16384,0,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#e61a00ff","u16":[58982,6554,0,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#00e61aff","u16":[0,58982,6554,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#00bf40ff","u16":[0,49151,16384,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#008080ff","u16":[0,32768,32768,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#0040bfff","u16":[0,16384,49151,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#001ae6ff","u16":[0,6554,58982,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#1aff00ff","u16":[6554,65535,0,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#40ff00ff","u16":[16384,65535,0,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#80ff00ff","u16":[32768,65535,0,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#bfff00ff","u16":[49151,65535,0,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#e6ff00ff","u16":[58982,65535,0,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#00e600ff","u16":[77,59033,26,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#01c000ff","u16":[193,49280,64,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#028101ff","u16":[386,33025,129,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#024101ff","u16":[578,16769,193,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#031b01ff","u16":[694,7016,231,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#06f717ff","u16":[1568,63453,5988,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#0feb3aff","u16":[3919,60331,14970,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#1fd775ff","u16":[7839,55127,29941,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#2ec2afff","u16":[11758,49922,44911,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#37b6d2ff","u16":[14109,46800,53893,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#04e904ff","u16":[900,59958,1054,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#09c90aff","u16":[2249,51593,2634,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#129315ff","u16":[4498,37651,5269,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#1a5c1fff","u16":[6746,23708,7903,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#203c25ff","u16":[8096,15343,9483,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#18fe18ff","u16":[6142,65150,6194,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#3cfb3cff","u16":[15356,64571,15484,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#78f879ff","u16":[30712,63608,30969,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#b3f4b5ff","u16":[46067,62644,46453,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#d7f2d9ff","u16":[55281,62066,55743,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#19ff19ff","u16":[6476,65458,6476,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#3ffe3fff","u16":[16191,65342,16191,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#7efe7eff","u16":[32382,65150,32382,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#bdfdbdff","u16":[48573,64957,48573,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#e3fce3ff","u16":[58288,64841,58288,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#16ec08ff","u16":[5603,60729,2133,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#37d015ff","u16":[14007,53520,5333,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#6da22aff","u16":[28013,41506,10666,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#a4733eff","u16":[42020,29491,15998,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#c4574bff","u16":[50423,22282,19198,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#17fa05ff","u16":[5962,64199,1157,65535]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#3af20bff","u16":[14906,62194,2891,65535]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#74e517ff","u16":[29812,58853,5783,65535]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#aed822ff","u16":[44718,55512,8674,65535]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#d1d029ff","u16":[53662,53507,10409,65535]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#03fb0cf2","u16":[828,64435,3163,62271]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#09f321df","u16":[2247,62551,8583,57375]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#14e44ec0","u16":[5239,58578,20012,49216]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#25ce8ca0","u16":[9421,53025,35985,41056]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#32bdbf8d","u16":[12836,48491,49028,36160]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":0.1,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#00ff00e6","u16":[0,65535,0,58982]}}, +{"bias":0.25,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#00ff00bf","u16":[0,65535,0,49151]}}, +{"bias":0.5,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#00ff0080","u16":[0,65535,0,32768]}}, +{"bias":0.75,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#00ff0040","u16":[0,65535,0,16384]}}, +{"bias":0.9,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#00ff001a","u16":[0,65535,0,6554]}}, +{"bias":1,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#00ff00ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#0000e6ff","u16":[0,0,58982,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#0000bfff","u16":[0,0,49151,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#000080ff","u16":[0,0,32768,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#000040ff","u16":[0,0,16384,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#00001aff","u16":[0,0,6554,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#1a1affff","u16":[6554,6554,65535,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#4040ffff","u16":[16384,16384,65535,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#8080ffff","u16":[32768,32768,65535,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#bfbfffff","u16":[49151,49151,65535,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#e6e6ffff","u16":[58982,58982,65535,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#0d0df2ff","u16":[3264,3264,62245,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#2020dfff","u16":[8160,8160,57311,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#4040bfff","u16":[16320,16320,49087,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#5f5f9fff","u16":[24479,24479,40863,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#72728cff","u16":[29375,29375,35929,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#0d0df2ff","u16":[3290,3290,62271,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#2020dfff","u16":[8224,8224,57375,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#4040c0ff","u16":[16448,16448,49216,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#6060a0ff","u16":[24672,24672,41056,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#73738dff","u16":[29606,29606,36160,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#1a00e6ff","u16":[6554,0,58982,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#4000bfff","u16":[16384,0,49151,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#800080ff","u16":[32768,0,32768,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#bf0040ff","u16":[49151,0,16384,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#e6001aff","u16":[58982,0,6554,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#001ae6ff","u16":[0,6554,58982,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#0040bfff","u16":[0,16384,49151,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#008080ff","u16":[0,32768,32768,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#00bf40ff","u16":[0,49151,16384,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#00e61aff","u16":[0,58982,6554,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#1a1ae6ff","u16":[6554,6554,58982,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#4040bfff","u16":[16384,16384,49151,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#808080ff","u16":[32768,32768,32768,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#bfbf40ff","u16":[49151,49151,16384,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#e6e61aff","u16":[58982,58982,6554,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#0000e6ff","u16":[77,51,59007,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#0101c0ff","u16":[193,129,49216,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#020180ff","u16":[386,257,32896,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#020241ff","u16":[578,386,16577,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#03021aff","u16":[694,463,6785,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#0611fdff","u16":[1568,4472,64970,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#0f2cfaff","u16":[3919,11180,64122,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#1f57f4ff","u16":[7839,22359,62708,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#2e83efff","u16":[11758,33539,61295,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#379debff","u16":[14109,40246,60446,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#0404eaff","u16":[900,977,60035,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#090acaff","u16":[2249,2442,51786,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#121394ff","u16":[4498,4883,38036,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#1a1d5fff","u16":[6746,7325,24287,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#20223eff","u16":[8096,8789,16037,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#1818feff","u16":[6142,6168,65175,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#3c3cfcff","u16":[15356,15420,64636,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#7878f8ff","u16":[30712,30840,63736,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#b3b4f5ff","u16":[46067,46260,62837,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#d7d8f2ff","u16":[55281,55512,62297,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#1919ffff","u16":[6476,6476,65458,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#3f3ffeff","u16":[16191,16191,65342,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#7e7efeff","u16":[32382,32382,65150,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#bdbdfdff","u16":[48573,48573,64957,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#e3e3fcff","u16":[58288,58288,64841,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#1607eeff","u16":[5603,1748,61115,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#3711d4ff","u16":[14007,4369,54484,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#6d22a9ff","u16":[28013,8738,43433,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#a4337eff","u16":[42020,13107,32382,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#c43d64ff","u16":[50423,15728,25751,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#1714eaff","u16":[5962,5217,60138,65535]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#3a33cbff","u16":[14906,13043,52043,65535]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#746696ff","u16":[29812,26086,38550,65535]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#ae9862ff","u16":[44718,39128,25058,65535]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#d1b742ff","u16":[53662,46954,16962,65535]}}, +{"bias":1,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#0309fef2","u16":[828,2362,65236,62271]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#0919fcdf","u16":[2247,6410,64725,57375]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#143af8c0","u16":[5239,14945,63645,49216]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#2569f2a0","u16":[9421,26873,62137,41056]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#328eed8d","u16":[12836,36613,60906,36160]}}, +{"bias":1,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":0.1,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#0000ffe6","u16":[0,0,65535,58982]}}, +{"bias":0.25,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#0000ffbf","u16":[0,0,65535,49151]}}, +{"bias":0.5,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#0000ff80","u16":[0,0,65535,32768]}}, +{"bias":0.75,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#0000ff40","u16":[0,0,65535,16384]}}, +{"bias":0.9,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#0000ff1a","u16":[0,0,65535,6554]}}, +{"bias":1,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#0000ffff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#e6e600ff","u16":[58982,58982,0,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#bfbf00ff","u16":[49151,49151,0,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#808000ff","u16":[32768,32768,0,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#404000ff","u16":[16384,16384,0,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#1a1a00ff","u16":[6554,6554,0,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffff1aff","u16":[65535,65535,6554,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffff40ff","u16":[65535,65535,16384,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffff80ff","u16":[65535,65535,32768,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffffbfff","u16":[65535,65535,49151,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffffe6ff","u16":[65535,65535,58982,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#f2f20dff","u16":[62245,62245,3264,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#dfdf20ff","u16":[57311,57311,8160,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#bfbf40ff","u16":[49087,49087,16320,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#9f9f5fff","u16":[40863,40863,24479,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#8c8c72ff","u16":[35929,35929,29375,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#f2f20dff","u16":[62271,62271,3290,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#dfdf20ff","u16":[57375,57375,8224,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#c0c040ff","u16":[49216,49216,16448,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#a0a060ff","u16":[41056,41056,24672,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#8d8d73ff","u16":[36160,36160,29606,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ffe600ff","u16":[65535,58982,0,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ffbf00ff","u16":[65535,49151,0,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ff8000ff","u16":[65535,32768,0,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ff4000ff","u16":[65535,16384,0,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ff1a00ff","u16":[65535,6554,0,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#e6ff00ff","u16":[58982,65535,0,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#bfff00ff","u16":[49151,65535,0,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#80ff00ff","u16":[32768,65535,0,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#40ff00ff","u16":[16384,65535,0,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#1aff00ff","u16":[6554,65535,0,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#e6e61aff","u16":[58982,58982,6554,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#bfbf40ff","u16":[49151,49151,16384,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#808080ff","u16":[32768,32768,32768,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#4040bfff","u16":[16384,16384,49151,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#1a1ae6ff","u16":[6554,6554,58982,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#e6e600ff","u16":[59059,59033,26,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#c0c000ff","u16":[49344,49280,64,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#818101ff","u16":[33153,33025,129,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#424101ff","u16":[16962,16769,193,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#1c1b01ff","u16":[7247,7016,231,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#ecf717ff","u16":[60549,63453,5988,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#cfeb3aff","u16":[53071,60331,14970,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#9ed775ff","u16":[40606,55127,29941,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#6ec2afff","u16":[28142,49922,44911,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#50b6d2ff","u16":[20663,46800,53893,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#e9e904ff","u16":[59881,59958,1054,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#c8c90aff","u16":[51400,51593,2634,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#919315ff","u16":[37265,37651,5269,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#5a5c1fff","u16":[23130,23708,7903,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#393c25ff","u16":[14649,15343,9483,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#fdfe18ff","u16":[65124,65150,6194,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#fbfb3cff","u16":[64507,64571,15484,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#f7f879ff","u16":[63479,63608,30969,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#f3f4b5ff","u16":[62451,62644,46453,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#f1f2d9ff","u16":[61834,62066,55743,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#ffff19ff","u16":[65458,65458,6476,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#fefe3fff","u16":[65342,65342,16191,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#fefe7eff","u16":[65150,65150,32382,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#fdfdbdff","u16":[64957,64957,48573,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#fcfce3ff","u16":[64841,64841,58288,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#fbec08ff","u16":[64584,60729,2133,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#f6d015ff","u16":[63158,53520,5333,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#eda22aff","u16":[60781,41506,10666,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#e3733eff","u16":[58403,29491,15998,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#de574bff","u16":[56977,22282,19198,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#fdfa05ff","u16":[64944,64199,1157,65535]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#f9f20bff","u16":[64057,62194,2891,65535]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#f4e517ff","u16":[62580,58853,5783,65535]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#eed822ff","u16":[61102,55512,8674,65535]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#ead029ff","u16":[60215,53507,10409,65535]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#f5fb0cf2","u16":[62901,64435,3163,62271]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#e3f321df","u16":[58389,62551,8583,57375]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#bee44ec0","u16":[48872,58578,20012,49216]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#8ace8ca0","u16":[35573,53025,35985,41056]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#60bdbf8d","u16":[24713,48491,49028,36160]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":0.1,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffff00e6","u16":[65535,65535,0,58982]}}, +{"bias":0.25,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffff00bf","u16":[65535,65535,0,49151]}}, +{"bias":0.5,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffff0080","u16":[65535,65535,0,32768]}}, +{"bias":0.75,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffff0040","u16":[65535,65535,0,16384]}}, +{"bias":0.9,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffff001a","u16":[65535,65535,0,6554]}}, +{"bias":1,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#ffff00ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#030201ff","u16":[694,463,231,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#020201ff","u16":[578,386,193,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#020101ff","u16":[386,257,129,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#010100ff","u16":[193,129,64,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[77,51,26,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#1c1b1aff","u16":[7247,7016,6785,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#424141ff","u16":[16962,16769,16577,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#818180ff","u16":[33153,33025,32896,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#c0c0c0ff","u16":[49344,49280,49216,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#e6e6e6ff","u16":[59059,59033,59007,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#0f0f0eff","u16":[3958,3727,3495,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#222121ff","u16":[8738,8545,8353,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#414140ff","u16":[16705,16577,16448,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#606060ff","u16":[24672,24608,24544,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#737372ff","u16":[29452,29427,29401,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#100f0eff","u16":[3984,3752,3521,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#222221ff","u16":[8802,8610,8417,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#424141ff","u16":[16834,16705,16577,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#616160ff","u16":[24865,24801,24736,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#747373ff","u16":[29684,29658,29632,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#1c0201ff","u16":[7247,463,231,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#420201ff","u16":[16962,386,193,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#810101ff","u16":[33153,257,129,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#c00100ff","u16":[49344,129,64,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#e60000ff","u16":[59059,51,26,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#031b01ff","u16":[694,7016,231,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#024101ff","u16":[578,16769,193,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#028101ff","u16":[386,33025,129,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#01c000ff","u16":[193,49280,64,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#00e600ff","u16":[77,59033,26,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#03021aff","u16":[694,463,6785,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#020241ff","u16":[578,386,16577,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#020180ff","u16":[386,257,32896,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#0101c0ff","u16":[193,129,49216,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#0000e6ff","u16":[77,51,59007,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#1c1b01ff","u16":[7247,7016,231,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#424101ff","u16":[16962,16769,193,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#818101ff","u16":[33153,33025,129,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#c0c000ff","u16":[49344,49280,64,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#e6e600ff","u16":[59059,59033,26,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#091318ff","u16":[2262,4934,6219,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#122d3bff","u16":[4498,11565,15163,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#205875ff","u16":[8224,22616,30069,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#2f83afff","u16":[11951,33667,44975,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#379dd2ff","u16":[14186,40298,53919,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#060605ff","u16":[1593,1439,1285,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#0b0b0bff","u16":[2827,2827,2827,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#131415ff","u16":[4883,5140,5397,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#1b1d1fff","u16":[6939,7453,7967,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#202225ff","u16":[8173,8841,9509,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#1b1a19ff","u16":[6836,6631,6425,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#3e3e3dff","u16":[15934,15806,15677,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#797979ff","u16":[31097,31097,31097,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#b4b5b5ff","u16":[46260,46389,46517,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#d7d8d9ff","u16":[55358,55563,55769,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#1c1b1aff","u16":[7170,6939,6708,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#414140ff","u16":[16769,16577,16384,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#807f7fff","u16":[32768,32639,32511,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#bebebdff","u16":[48766,48702,48637,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#e3e3e3ff","u16":[58365,58339,58313,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#190909ff","u16":[6297,2210,2364,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#391316ff","u16":[14585,4755,5526,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#6f232aff","u16":[28399,8995,10794,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#a4343fff","u16":[42212,13236,16063,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#c53d4bff","u16":[50501,15780,19224,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#1a1605ff","u16":[6656,5680,1388,65535]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#3c340cff","u16":[15484,13428,3084,65535]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#766717ff","u16":[30198,26343,5911,65535]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#af9922ff","u16":[44911,39257,8738,65535]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#d1b729ff","u16":[53739,47005,10434,65535]}}, +{"bias":1,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#060b0df2","u16":[1558,2849,3407,62271]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#0b1b22df","u16":[2908,6850,8803,57375]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#163b4fc0","u16":[5753,15287,20184,49216]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#26698ca0","u16":[9729,27078,36087,41056]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#328fbf8d","u16":[12975,36707,49075,36160]}}, +{"bias":1,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":0.1,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#030201e6","u16":[771,514,257,58982]}}, +{"bias":0.25,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#030201bf","u16":[771,514,257,49151]}}, +{"bias":0.5,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#03020180","u16":[771,514,257,32768]}}, +{"bias":0.75,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#03020140","u16":[771,514,257,16384]}}, +{"bias":0.9,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#0302011a","u16":[771,514,257,6554]}}, +{"bias":1,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#030201ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#379dd2ff","u16":[14109,40246,53893,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#2e83afff","u16":[11758,33539,44911,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#1f5775ff","u16":[7839,22359,29941,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#0f2c3aff","u16":[3919,11180,14970,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#061117ff","u16":[1568,4472,5988,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#50b6ebff","u16":[20663,46800,60446,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#6ec2efff","u16":[28142,49922,61295,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#9ed7f4ff","u16":[40606,55127,62708,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#cfebfaff","u16":[53071,60331,64122,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#ecf7fdff","u16":[60549,63453,64970,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#44a9deff","u16":[17373,43510,57157,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#4ea2cfff","u16":[19918,41698,53071,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#5e97b4ff","u16":[24158,38679,46260,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#6f8b9aff","u16":[28399,35659,39450,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#78848aff","u16":[30943,33847,35363,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#44a9dfff","u16":[17399,43536,57183,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#4ea3cfff","u16":[19982,41763,53135,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#5f97b5ff","u16":[24287,38807,46389,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#6f8c9aff","u16":[28591,35852,39642,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#79858bff","u16":[31174,34078,35595,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#509dd2ff","u16":[20663,40246,53893,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#6e83afff","u16":[28142,33539,44911,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#9e5775ff","u16":[40606,22359,29941,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#cf2c3aff","u16":[53071,11180,14970,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#ec1117ff","u16":[60549,4472,5988,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#37b6d2ff","u16":[14109,46800,53893,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#2ec2afff","u16":[11758,49922,44911,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#1fd775ff","u16":[7839,55127,29941,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#0feb3aff","u16":[3919,60331,14970,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#06f717ff","u16":[1568,63453,5988,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#379debff","u16":[14109,40246,60446,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#2e83efff","u16":[11758,33539,61295,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#1f57f4ff","u16":[7839,22359,62708,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#0f2cfaff","u16":[3919,11180,64122,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#0611fdff","u16":[1568,4472,64970,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#50b6d2ff","u16":[20663,46800,53893,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#6ec2afff","u16":[28142,49922,44911,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#9ed775ff","u16":[40606,55127,29941,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#cfeb3aff","u16":[53071,60331,14970,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#ecf717ff","u16":[60549,63453,5988,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#379dd2ff","u16":[14186,40298,53919,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#2f83afff","u16":[11951,33667,44975,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#205875ff","u16":[8224,22616,30069,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#122d3bff","u16":[4498,11565,15163,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#091318ff","u16":[2262,4934,6219,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#3aa0d6ff","u16":[15009,41223,54947,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#378cb9ff","u16":[14007,35980,47545,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#306a89ff","u16":[12336,27242,35209,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#2a4859ff","u16":[10666,18504,22873,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#26343cff","u16":[9663,13261,15471,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#4fb5eaff","u16":[20252,46414,60087,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#6abfebff","u16":[27114,48959,60395,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#96cfedff","u16":[38550,53199,60909,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#c3e0efff","u16":[49987,57440,61423,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#dde9f0ff","u16":[56848,59984,61731,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#50b6ebff","u16":[20586,46723,60369,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#6dc2eeff","u16":[27949,49730,61102,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#9dd5f3ff","u16":[40221,54741,62323,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#cce9f7ff","u16":[52492,59753,63543,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#e9f4faff","u16":[59855,62759,64276,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#4da3daff","u16":[19712,41994,56026,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#6494c4ff","u16":[25764,37908,50244,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#8c799eff","u16":[35852,31097,40606,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#b35f79ff","u16":[45939,24287,30969,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#ca4f62ff","u16":[51991,20200,25186,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#4eb1d6ff","u16":[20072,45463,55049,65535]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#68b5baff","u16":[26664,46581,47802,65535]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#93bd8bff","u16":[37651,48445,35723,65535]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#bdc45cff","u16":[48637,50308,23644,65535]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#d7c840ff","u16":[55229,51426,16397,65535]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee9f2","u16":[15677,44718,59881,62271]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee9df","u16":[15677,44718,59881,57375]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee9c0","u16":[15677,44718,59881,49216]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee9a0","u16":[15677,44718,59881,41056]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee98d","u16":[15677,44718,59881,36160]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":0.1,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#3daee9e6","u16":[15677,44718,59881,58982]}}, +{"bias":0.25,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#3daee9bf","u16":[15677,44718,59881,49151]}}, +{"bias":0.5,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#3daee980","u16":[15677,44718,59881,32768]}}, +{"bias":0.75,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#3daee940","u16":[15677,44718,59881,16384]}}, +{"bias":0.9,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#3daee91a","u16":[15677,44718,59881,6554]}}, +{"bias":1,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#3daee9ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#202225ff","u16":[8096,8789,9483,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#1a1d1fff","u16":[6746,7325,7903,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#121315ff","u16":[4498,4883,5269,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#090a0aff","u16":[2249,2442,2634,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#040404ff","u16":[900,977,1054,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#393c3eff","u16":[14649,15343,16037,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#5a5c5fff","u16":[23130,23708,24287,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#919394ff","u16":[37265,37651,38036,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#c8c9caff","u16":[51400,51593,51786,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#e9e9eaff","u16":[59881,59958,60035,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#2c2f32ff","u16":[11359,12053,12747,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#3a3c3fff","u16":[14906,15484,16063,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#515354ff","u16":[20817,21203,21588,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#68696aff","u16":[26728,26921,27114,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#767676ff","u16":[30275,30352,30429,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#2c2f32ff","u16":[11385,12079,12773,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#3a3d3fff","u16":[14970,15549,16127,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#525355ff","u16":[20946,21331,21717,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#696a6aff","u16":[26921,27114,27306,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#777777ff","u16":[30506,30583,30660,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#392225ff","u16":[14649,8789,9483,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#5a1d1fff","u16":[23130,7325,7903,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#911315ff","u16":[37265,4883,5269,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#c80a0aff","u16":[51400,2442,2634,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#e90404ff","u16":[59881,977,1054,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#203c25ff","u16":[8096,15343,9483,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#1a5c1fff","u16":[6746,23708,7903,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#129315ff","u16":[4498,37651,5269,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#09c90aff","u16":[2249,51593,2634,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#04e904ff","u16":[900,59958,1054,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#20223eff","u16":[8096,8789,16037,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#1a1d5fff","u16":[6746,7325,24287,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#121394ff","u16":[4498,4883,38036,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#090acaff","u16":[2249,2442,51786,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#0404eaff","u16":[900,977,60035,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#393c25ff","u16":[14649,15343,9483,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#5a5c1fff","u16":[23130,23708,7903,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#919315ff","u16":[37265,37651,5269,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#c8c90aff","u16":[51400,51593,2634,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#e9e904ff","u16":[59881,59958,1054,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#202225ff","u16":[8173,8841,9509,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#1b1d1fff","u16":[6939,7453,7967,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#131415ff","u16":[4883,5140,5397,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#0b0b0bff","u16":[2827,2827,2827,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#060605ff","u16":[1593,1439,1285,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#26343cff","u16":[9663,13261,15471,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#2a4859ff","u16":[10666,18504,22873,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#306a89ff","u16":[12336,27242,35209,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#378cb9ff","u16":[14007,35980,47545,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#3aa0d6ff","u16":[15009,41223,54947,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#373a3dff","u16":[14238,14957,15677,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#56595bff","u16":[22102,22745,23387,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#898b8dff","u16":[35209,35723,36237,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#bcbebfff","u16":[48316,48702,49087,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#dbdcddff","u16":[56180,56489,56797,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#393b3eff","u16":[14572,15266,15960,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#595c5eff","u16":[22937,23516,24094,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#909193ff","u16":[36880,37265,37651,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#c6c7c7ff","u16":[50822,51015,51207,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#e6e7e7ff","u16":[59187,59264,59341,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#35292dff","u16":[13698,10537,11616,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#512e34ff","u16":[20753,11694,13236,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#7f353eff","u16":[32511,13621,15934,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#ac3d49ff","u16":[44268,15549,18633,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#c8414fff","u16":[51323,16705,20252,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#373729ff","u16":[14058,14007,10640,65535]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#544f2aff","u16":[21652,20367,10794,65535]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#86792bff","u16":[34310,30969,11051,65535]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#b7a22cff","u16":[46967,41570,11308,65535]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#d4bb2dff","u16":[54561,47931,11462,65535]}}, +{"bias":1,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#242d33f2","u16":[9348,11612,13144,62271]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#273945df","u16":[9953,14776,17610,57375]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#2c5369c0","u16":[11228,21447,27028,49216]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#33789ca0","u16":[13010,30770,40190,41056]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#3895c68d","u16":[14466,38383,50938,36160]}}, +{"bias":1,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":0.1,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#232629e6","u16":[8995,9766,10537,58982]}}, +{"bias":0.25,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#232629bf","u16":[8995,9766,10537,49151]}}, +{"bias":0.5,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#23262980","u16":[8995,9766,10537,32768]}}, +{"bias":0.75,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#23262940","u16":[8995,9766,10537,16384]}}, +{"bias":0.9,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#2326291a","u16":[8995,9766,10537,6554]}}, +{"bias":1,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#232629ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#d7d8d9ff","u16":[55281,55512,55743,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#b3b4b5ff","u16":[46067,46260,46453,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#787879ff","u16":[30712,30840,30969,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#3c3c3cff","u16":[15356,15420,15484,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#181818ff","u16":[6142,6168,6194,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#f1f2f2ff","u16":[61834,62066,62297,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#f3f4f5ff","u16":[62451,62644,62837,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#f7f8f8ff","u16":[63479,63608,63736,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#fbfbfcff","u16":[64507,64571,64636,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#fdfefeff","u16":[65124,65150,65175,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#e4e5e6ff","u16":[58545,58776,59007,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#d3d4d5ff","u16":[54227,54420,54613,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#b7b8b8ff","u16":[47031,47160,47288,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#9b9b9cff","u16":[39835,39899,39964,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#8a8a8aff","u16":[35517,35543,35569,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#e4e5e6ff","u16":[58570,58802,59033,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#d3d4d5ff","u16":[54291,54484,54677,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#b8b8b9ff","u16":[47160,47288,47417,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#9c9c9cff","u16":[40028,40092,40156,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#8b8b8bff","u16":[35749,35774,35800,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#f1d8d9ff","u16":[61834,55512,55743,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#f3b4b5ff","u16":[62451,46260,46453,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#f77879ff","u16":[63479,30840,30969,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#fb3c3cff","u16":[64507,15420,15484,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#fd1818ff","u16":[65124,6168,6194,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#d7f2d9ff","u16":[55281,62066,55743,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#b3f4b5ff","u16":[46067,62644,46453,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#78f879ff","u16":[30712,63608,30969,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#3cfb3cff","u16":[15356,64571,15484,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#18fe18ff","u16":[6142,65150,6194,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#d7d8f2ff","u16":[55281,55512,62297,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#b3b4f5ff","u16":[46067,46260,62837,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#7878f8ff","u16":[30712,30840,63736,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#3c3cfcff","u16":[15356,15420,64636,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#1818feff","u16":[6142,6168,65175,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#f1f2d9ff","u16":[61834,62066,55743,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#f3f4b5ff","u16":[62451,62644,46453,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#f7f879ff","u16":[63479,63608,30969,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#fbfb3cff","u16":[64507,64571,15484,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#fdfe18ff","u16":[65124,65150,6194,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#d7d8d9ff","u16":[55358,55563,55769,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#b4b5b5ff","u16":[46260,46389,46517,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#797979ff","u16":[31097,31097,31097,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#3e3e3dff","u16":[15934,15806,15677,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#1b1a19ff","u16":[6836,6631,6425,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#dde9f0ff","u16":[56848,59984,61731,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#c3e0efff","u16":[49987,57440,61423,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#96cfedff","u16":[38550,53199,60909,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#6abfebff","u16":[27114,48959,60395,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#4fb5eaff","u16":[20252,46414,60087,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#dbdcddff","u16":[56180,56489,56797,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#bcbebfff","u16":[48316,48702,49087,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#898b8dff","u16":[35209,35723,36237,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#56595bff","u16":[22102,22745,23387,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#373a3dff","u16":[14238,14957,15677,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#f0f1f2ff","u16":[61757,61988,62220,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#f2f3f4ff","u16":[62258,62451,62644,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#f6f6f7ff","u16":[63094,63222,63351,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#f9f9f9ff","u16":[63929,63993,64057,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#fbfbfbff","u16":[64430,64456,64481,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#eddfe1ff","u16":[60883,57260,57876,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#eac5caff","u16":[60074,50629,51786,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#e59aa2ff","u16":[58725,39578,41634,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#df6f7bff","u16":[57375,28527,31483,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#dc5563ff","u16":[56566,21896,25392,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#eeecddff","u16":[61243,60729,56900,65535]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#ede7c0ff","u16":[60973,59303,49344,65535]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#ecde8fff","u16":[60524,56926,36751,65535]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#ead45eff","u16":[60074,54548,24158,65535]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#e9cf41ff","u16":[59804,53122,16602,65535]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#e6edf1f2","u16":[59006,60784,61828,62271]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#d5e7f0df","u16":[54866,59249,61642,57375]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#b4daeec0","u16":[46135,56011,61250,49216]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#84c8eca0","u16":[33932,51487,60701,41056]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#5dbaea8d","u16":[23968,47792,60254,36160]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":0.1,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#eff0f1e6","u16":[61423,61680,61937,58982]}}, +{"bias":0.25,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#eff0f1bf","u16":[61423,61680,61937,49151]}}, +{"bias":0.5,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#eff0f180","u16":[61423,61680,61937,32768]}}, +{"bias":0.75,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#eff0f140","u16":[61423,61680,61937,16384]}}, +{"bias":0.9,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#eff0f11a","u16":[61423,61680,61937,6554]}}, +{"bias":1,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#eff0f1ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#e3e3e3ff","u16":[58288,58288,58288,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#bdbdbdff","u16":[48573,48573,48573,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#7e7e7eff","u16":[32382,32382,32382,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#3f3f3fff","u16":[16191,16191,16191,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#191919ff","u16":[6476,6476,6476,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#fcfcfcff","u16":[64841,64841,64841,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#fdfdfdff","u16":[64957,64957,64957,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#fefefeff","u16":[65150,65150,65150,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#fefefeff","u16":[65342,65342,65342,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65458,65458,65458,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#f0f0f0ff","u16":[61552,61552,61552,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#ddddddff","u16":[56733,56733,56733,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#bebebeff","u16":[48702,48702,48702,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#9e9e9eff","u16":[40670,40670,40670,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#8c8c8cff","u16":[35852,35852,35852,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#f0f0f0ff","u16":[61577,61577,61577,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#ddddddff","u16":[56797,56797,56797,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#bebebeff","u16":[48830,48830,48830,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#9f9f9fff","u16":[40863,40863,40863,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#8c8c8cff","u16":[36083,36083,36083,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#fce3e3ff","u16":[64841,58288,58288,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#fdbdbdff","u16":[64957,48573,48573,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#fe7e7eff","u16":[65150,32382,32382,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#fe3f3fff","u16":[65342,16191,16191,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#ff1919ff","u16":[65458,6476,6476,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#e3fce3ff","u16":[58288,64841,58288,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#bdfdbdff","u16":[48573,64957,48573,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#7efe7eff","u16":[32382,65150,32382,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#3ffe3fff","u16":[16191,65342,16191,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#19ff19ff","u16":[6476,65458,6476,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#e3e3fcff","u16":[58288,58288,64841,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#bdbdfdff","u16":[48573,48573,64957,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#7e7efeff","u16":[32382,32382,65150,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#3f3ffeff","u16":[16191,16191,65342,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#1919ffff","u16":[6476,6476,65458,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#fcfce3ff","u16":[64841,64841,58288,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#fdfdbdff","u16":[64957,64957,48573,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#fefe7eff","u16":[65150,65150,32382,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#fefe3fff","u16":[65342,65342,16191,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#ffff19ff","u16":[65458,65458,6476,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#e3e3e3ff","u16":[58365,58339,58313,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#bebebdff","u16":[48766,48702,48637,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#807f7fff","u16":[32768,32639,32511,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#414140ff","u16":[16769,16577,16384,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#1c1b1aff","u16":[7170,6939,6708,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#e9f4faff","u16":[59855,62759,64276,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#cce9f7ff","u16":[52492,59753,63543,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#9dd5f3ff","u16":[40221,54741,62323,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#6dc2eeff","u16":[27949,49730,61102,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#50b6ebff","u16":[20586,46723,60369,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#e6e7e7ff","u16":[59187,59264,59341,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#c6c7c7ff","u16":[50822,51015,51207,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#909193ff","u16":[36880,37265,37651,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#595c5eff","u16":[22937,23516,24094,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#393b3eff","u16":[14572,15266,15960,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#fbfbfbff","u16":[64430,64456,64481,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#f9f9f9ff","u16":[63929,63993,64057,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#f6f6f7ff","u16":[63094,63222,63351,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#f2f3f4ff","u16":[62258,62451,62644,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#f0f1f2ff","u16":[61757,61988,62220,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#f9eaebff","u16":[63890,60035,60421,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#f4ced2ff","u16":[62580,52942,53906,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#eba0a8ff","u16":[60395,41120,43048,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#e3727dff","u16":[58211,29298,32189,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#dd5664ff","u16":[56900,22205,25674,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#faf7e7ff","u16":[64250,63505,59444,65535]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#f7f0c8ff","u16":[63479,61616,51464,65535]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#f2e495ff","u16":[62194,58468,38165,65535]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#edd761ff","u16":[60909,55319,24865,65535]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#ead042ff","u16":[60138,53430,16885,65535]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#f2f8fbf2","u16":[62171,63705,64506,62271]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#e1f1f9df","u16":[57728,61891,64064,57375]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#bce2f6c0","u16":[48359,58065,63132,49216]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#89cdf1a0","u16":[35266,52718,61830,41056]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#60bcec8d","u16":[24573,48351,60766,36160]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":0.1,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#fcfcfce6","u16":[64764,64764,64764,58982]}}, +{"bias":0.25,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#fcfcfcbf","u16":[64764,64764,64764,49151]}}, +{"bias":0.5,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#fcfcfc80","u16":[64764,64764,64764,32768]}}, +{"bias":0.75,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#fcfcfc40","u16":[64764,64764,64764,16384]}}, +{"bias":0.9,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#fcfcfc1a","u16":[64764,64764,64764,6554]}}, +{"bias":1,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#fcfcfcff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#c43d4bff","u16":[50423,15728,19198,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#a4333eff","u16":[42020,13107,15998,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#6d222aff","u16":[28013,8738,10666,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#371115ff","u16":[14007,4369,5333,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#160708ff","u16":[5603,1748,2133,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#de5764ff","u16":[56977,22282,25751,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#e3737eff","u16":[58403,29491,32382,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#eda2a9ff","u16":[60781,41506,43433,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#f6d0d4ff","u16":[63158,53520,54484,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#fbeceeff","u16":[64584,60729,61115,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#d14a57ff","u16":[53687,18992,22462,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#c3535eff","u16":[50179,21267,24158,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#ad6269ff","u16":[44333,25058,26985,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#967074ff","u16":[38486,28848,29812,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#88797bff","u16":[34978,31123,31508,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#d14a58ff","u16":[53713,19018,22488,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#c4535eff","u16":[50244,21331,24222,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#ad626aff","u16":[44461,25186,27114,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#977175ff","u16":[38679,29041,30005,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#897a7cff","u16":[35209,31354,31740,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#de3d4bff","u16":[56977,15728,19198,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#e3333eff","u16":[58403,13107,15998,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#ed222aff","u16":[60781,8738,10666,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#f61115ff","u16":[63158,4369,5333,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#fb0708ff","u16":[64584,1748,2133,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#c4574bff","u16":[50423,22282,19198,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#a4733eff","u16":[42020,29491,15998,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#6da22aff","u16":[28013,41506,10666,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#37d015ff","u16":[14007,53520,5333,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#16ec08ff","u16":[5603,60729,2133,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#c43d64ff","u16":[50423,15728,25751,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#a4337eff","u16":[42020,13107,32382,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#6d22a9ff","u16":[28013,8738,43433,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#3711d4ff","u16":[14007,4369,54484,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#1607eeff","u16":[5603,1748,61115,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#de574bff","u16":[56977,22282,19198,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#e3733eff","u16":[58403,29491,15998,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#eda22aff","u16":[60781,41506,10666,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#f6d015ff","u16":[63158,53520,5333,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#fbec08ff","u16":[64584,60729,2133,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#c53d4bff","u16":[50501,15780,19224,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#a4343fff","u16":[42212,13236,16063,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#6f232aff","u16":[28399,8995,10794,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#391316ff","u16":[14585,4755,5526,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#190909ff","u16":[6297,2210,2364,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#ca4f62ff","u16":[51991,20200,25186,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#b35f79ff","u16":[45939,24287,30969,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#8c799eff","u16":[35852,31097,40606,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#6494c4ff","u16":[25764,37908,50244,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#4da3daff","u16":[19712,41994,56026,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#c8414fff","u16":[51323,16705,20252,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#ac3d49ff","u16":[44268,15549,18633,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#7f353eff","u16":[32511,13621,15934,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#512e34ff","u16":[20753,11694,13236,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#35292dff","u16":[13698,10537,11616,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#dc5563ff","u16":[56566,21896,25392,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#df6f7bff","u16":[57375,28527,31483,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#e59aa2ff","u16":[58725,39578,41634,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#eac5caff","u16":[60074,50629,51786,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#eddfe1ff","u16":[60883,57260,57876,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#dd5664ff","u16":[56900,22205,25674,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#e3727dff","u16":[58211,29298,32189,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#eba0a8ff","u16":[60395,41120,43048,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#f4ced2ff","u16":[62580,52942,53906,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#f9eaebff","u16":[63890,60035,60421,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#db524fff","u16":[56386,20946,20354,65535]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#de664aff","u16":[56926,26150,18890,65535]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#e18840ff","u16":[57825,34824,16448,65535]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#e5a937ff","u16":[58725,43497,14007,65535]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#e7be31ff","u16":[59264,48702,12542,65535]}}, +{"bias":1,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#d24a5bf2","u16":[53894,18915,23367,62271]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#c35369df","u16":[50242,21381,26857,57375]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#a66785c0","u16":[42541,26580,34215,49216]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#7c84ada0","u16":[31779,33847,44497,41056]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#599bce8d","u16":[22990,39781,52894,36160]}}, +{"bias":1,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":0.1,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#da4453e6","u16":[56026,17476,21331,58982]}}, +{"bias":0.25,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#da4453bf","u16":[56026,17476,21331,49151]}}, +{"bias":0.5,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#da445380","u16":[56026,17476,21331,32768]}}, +{"bias":0.75,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#da445340","u16":[56026,17476,21331,16384]}}, +{"bias":0.9,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#da44531a","u16":[56026,17476,21331,6554]}}, +{"bias":1,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#da4453ff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#d1b729ff","u16":[53662,46954,10409,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#ae9822ff","u16":[44718,39128,8674,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#746617ff","u16":[29812,26086,5783,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#3a330bff","u16":[14906,13043,2891,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#171405ff","u16":[5962,5217,1157,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#ead042ff","u16":[60215,53507,16962,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#eed862ff","u16":[61102,55512,25058,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#f4e596ff","u16":[62580,58853,38550,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#f9f2cbff","u16":[64057,62194,52043,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#fdfaeaff","u16":[64944,64199,60138,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#dec335ff","u16":[56926,50218,13672,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#ceb842ff","u16":[52878,47288,16834,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#b4a556ff","u16":[46132,42405,22102,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#99926bff","u16":[39385,37522,27371,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#8a8777ff","u16":[35338,34592,30532,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#dec435ff","u16":[56951,50244,13698,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#ceb842ff","u16":[52942,47352,16898,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#b4a657ff","u16":[46260,42534,22231,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#9a936bff","u16":[39578,37715,27563,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#8a8878ff","u16":[35569,34824,30763,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#eab729ff","u16":[60215,46954,10409,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#ee9822ff","u16":[61102,39128,8674,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#f46617ff","u16":[62580,26086,5783,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#f9330bff","u16":[64057,13043,2891,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#fd1405ff","u16":[64944,5217,1157,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#d1d029ff","u16":[53662,53507,10409,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#aed822ff","u16":[44718,55512,8674,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#74e517ff","u16":[29812,58853,5783,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#3af20bff","u16":[14906,62194,2891,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#17fa05ff","u16":[5962,64199,1157,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#d1b742ff","u16":[53662,46954,16962,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#ae9862ff","u16":[44718,39128,25058,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#746696ff","u16":[29812,26086,38550,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#3a33cbff","u16":[14906,13043,52043,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#1714eaff","u16":[5962,5217,60138,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#ead029ff","u16":[60215,53507,10409,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#eed822ff","u16":[61102,55512,8674,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#f4e517ff","u16":[62580,58853,5783,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#f9f20bff","u16":[64057,62194,2891,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#fdfa05ff","u16":[64944,64199,1157,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#d1b729ff","u16":[53739,47005,10434,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#af9922ff","u16":[44911,39257,8738,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#766717ff","u16":[30198,26343,5911,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#3c340cff","u16":[15484,13428,3084,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#1a1605ff","u16":[6656,5680,1388,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#d7c840ff","u16":[55229,51426,16397,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#bdc45cff","u16":[48637,50308,23644,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#93bd8bff","u16":[37651,48445,35723,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#68b5baff","u16":[26664,46581,47802,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#4eb1d6ff","u16":[20072,45463,55049,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#d4bb2dff","u16":[54561,47931,11462,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#b7a22cff","u16":[46967,41570,11308,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#86792bff","u16":[34310,30969,11051,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#544f2aff","u16":[21652,20367,10794,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#373729ff","u16":[14058,14007,10640,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#e9cf41ff","u16":[59804,53122,16602,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#ead45eff","u16":[60074,54548,24158,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#ecde8fff","u16":[60524,56926,36751,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#ede7c0ff","u16":[60973,59303,49344,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#eeecddff","u16":[61243,60729,56900,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#ead042ff","u16":[60138,53430,16885,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#edd761ff","u16":[60909,55319,24865,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#f2e495ff","u16":[62194,58468,38165,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#f7f0c8ff","u16":[63479,61616,51464,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#faf7e7ff","u16":[64250,63505,59444,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#e7be31ff","u16":[59264,48702,12542,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#e5a937ff","u16":[58725,43497,14007,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#e18840ff","u16":[57825,34824,16448,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#de664aff","u16":[56926,26150,18890,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#db524fff","u16":[56386,20946,20354,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#dfc937f2","u16":[57302,51777,14117,62271]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#cfc748df","u16":[53325,51103,18490,57375]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#afc16cc0","u16":[44937,49680,27712,49216]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#81ba9ea0","u16":[33215,47692,40600,41056]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#5cb3c78d","u16":[23642,46069,51124,36160]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":0.1,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#e8cb2de6","u16":[59624,52171,11565,58982]}}, +{"bias":0.25,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#e8cb2dbf","u16":[59624,52171,11565,49151]}}, +{"bias":0.5,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#e8cb2d80","u16":[59624,52171,11565,32768]}}, +{"bias":0.75,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#e8cb2d40","u16":[59624,52171,11565,16384]}}, +{"bias":0.9,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#e8cb2d1a","u16":[59624,52171,11565,6554]}}, +{"bias":1,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#e8cb2dff","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#328ebf8d","u16":[12836,36613,49028,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#25698ca0","u16":[9421,26873,35985,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#143a4ec0","u16":[5239,14945,20012,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#091921df","u16":[2247,6410,8583,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#03090cf2","u16":[828,2362,3163,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#60bded8d","u16":[24713,48491,60906,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#8acef2a0","u16":[35573,53025,62137,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#bee4f8c0","u16":[48872,58578,63645,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#e3f3fcdf","u16":[58389,62551,64725,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#f5fbfef2","u16":[62901,64435,65236,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#49a5d68d","u16":[18751,42529,54944,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#579bbfa0","u16":[22446,39898,49010,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#698fa2c0","u16":[26970,36676,41743,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#76868edf","u16":[30208,34370,36544,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#7c8185f2","u16":[31743,33277,34078,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#49a6d68d","u16":[18798,42575,54990,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#589cbfa0","u16":[22548,40000,49112,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#6a8fa3c0","u16":[27141,36847,41914,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#76878fdf","u16":[30428,34591,36764,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#7c8286f2","u16":[31986,33521,34322,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#608ebf8d","u16":[24713,36613,49028,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#8a698ca0","u16":[35573,26873,35985,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#be3a4ec0","u16":[48872,14945,20012,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#e31921df","u16":[58389,6410,8583,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#f5090cf2","u16":[62901,2362,3163,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#32bdbf8d","u16":[12836,48491,49028,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#25ce8ca0","u16":[9421,53025,35985,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#14e44ec0","u16":[5239,58578,20012,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#09f321df","u16":[2247,62551,8583,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#03fb0cf2","u16":[828,64435,3163,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#328eed8d","u16":[12836,36613,60906,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#2569f2a0","u16":[9421,26873,62137,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#143af8c0","u16":[5239,14945,63645,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#0919fcdf","u16":[2247,6410,64725,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#0309fef2","u16":[828,2362,65236,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#60bdbf8d","u16":[24713,48491,49028,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#8ace8ca0","u16":[35573,53025,35985,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#bee44ec0","u16":[48872,58578,20012,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#e3f321df","u16":[58389,62551,8583,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#f5fb0cf2","u16":[62901,64435,3163,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#328fbf8d","u16":[12975,36707,49075,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#26698ca0","u16":[9729,27078,36087,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#163b4fc0","u16":[5753,15287,20184,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#0b1b22df","u16":[2908,6850,8803,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#060b0df2","u16":[1558,2849,3407,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee98d","u16":[15677,44718,59881,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee9a0","u16":[15677,44718,59881,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee9c0","u16":[15677,44718,59881,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee9df","u16":[15677,44718,59881,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee9f2","u16":[15677,44718,59881,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#3895c68d","u16":[14466,38383,50938,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#33789ca0","u16":[13010,30770,40190,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#2c5369c0","u16":[11228,21447,27028,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#273945df","u16":[9953,14776,17610,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#242d33f2","u16":[9348,11612,13144,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#5dbaea8d","u16":[23968,47792,60254,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#84c8eca0","u16":[33932,51487,60701,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#b4daeec0","u16":[46135,56011,61250,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#d5e7f0df","u16":[54866,59249,61642,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#e6edf1f2","u16":[59006,60784,61828,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#60bcec8d","u16":[24573,48351,60766,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#89cdf1a0","u16":[35266,52718,61830,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#bce2f6c0","u16":[48359,58065,63132,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#e1f1f9df","u16":[57728,61891,64064,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#f2f8fbf2","u16":[62171,63705,64506,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#599bce8d","u16":[22990,39781,52894,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#7c84ada0","u16":[31779,33847,44497,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#a66785c0","u16":[42541,26580,34215,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#c35369df","u16":[50242,21381,26857,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#d24a5bf2","u16":[53894,18915,23367,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#5cb3c78d","u16":[23642,46069,51124,36160]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#81ba9ea0","u16":[33215,47692,40600,41056]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#afc16cc0","u16":[44937,49680,27712,49216]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#cfc748df","u16":[53325,51103,18490,57375]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#dfc937f2","u16":[57302,51777,14117,62271]}}, +{"bias":1,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":0.1,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#3daee973","u16":[15677,44718,59881,29606]}}, +{"bias":0.25,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#3daee960","u16":[15677,44718,59881,24672]}}, +{"bias":0.5,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#3daee940","u16":[15677,44718,59881,16448]}}, +{"bias":0.75,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#3daee920","u16":[15677,44718,59881,8224]}}, +{"bias":0.9,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#3daee90d","u16":[15677,44718,59881,3290]}}, +{"bias":1,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#3daee980","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#0000001a","u16":[0,0,0,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#00000040","u16":[0,0,0,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#00000080","u16":[0,0,0,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#000000bf","u16":[0,0,0,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#000000e6","u16":[0,0,0,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffff1a","u16":[65535,65535,65535,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffff40","u16":[65535,65535,65535,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffffbf","u16":[65535,65535,65535,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffffe6","u16":[65535,65535,65535,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#7f7f7f1a","u16":[32639,32639,32639,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#7f7f7f40","u16":[32639,32639,32639,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#7f7f7f80","u16":[32639,32639,32639,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#7f7f7fbf","u16":[32639,32639,32639,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#7f7f7fe6","u16":[32639,32639,32639,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#7f7f7fff","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#8080801a","u16":[32896,32896,32896,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#80808040","u16":[32896,32896,32896,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#80808080","u16":[32896,32896,32896,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#808080bf","u16":[32896,32896,32896,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#808080e6","u16":[32896,32896,32896,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#808080ff","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ff00001a","u16":[65535,0,0,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ff000040","u16":[65535,0,0,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ff000080","u16":[65535,0,0,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ff0000bf","u16":[65535,0,0,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ff0000e6","u16":[65535,0,0,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#ff0000ff","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#00ff001a","u16":[0,65535,0,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#00ff0040","u16":[0,65535,0,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#00ff0080","u16":[0,65535,0,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#00ff00bf","u16":[0,65535,0,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#00ff00e6","u16":[0,65535,0,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#00ff00ff","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#0000ff1a","u16":[0,0,65535,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#0000ff40","u16":[0,0,65535,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#0000ff80","u16":[0,0,65535,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#0000ffbf","u16":[0,0,65535,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#0000ffe6","u16":[0,0,65535,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#0000ffff","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffff001a","u16":[65535,65535,0,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffff0040","u16":[65535,65535,0,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffff0080","u16":[65535,65535,0,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffff00bf","u16":[65535,65535,0,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffff00e6","u16":[65535,65535,0,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#ffff00ff","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#0302011a","u16":[771,514,257,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#03020140","u16":[771,514,257,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#03020180","u16":[771,514,257,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#030201bf","u16":[771,514,257,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#030201e6","u16":[771,514,257,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#030201ff","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#3daee91a","u16":[15677,44718,59881,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#3daee940","u16":[15677,44718,59881,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#3daee980","u16":[15677,44718,59881,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#3daee9bf","u16":[15677,44718,59881,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#3daee9e6","u16":[15677,44718,59881,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#3daee9ff","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#2326291a","u16":[8995,9766,10537,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#23262940","u16":[8995,9766,10537,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#23262980","u16":[8995,9766,10537,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#232629bf","u16":[8995,9766,10537,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#232629e6","u16":[8995,9766,10537,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#232629ff","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#eff0f11a","u16":[61423,61680,61937,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#eff0f140","u16":[61423,61680,61937,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#eff0f180","u16":[61423,61680,61937,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#eff0f1bf","u16":[61423,61680,61937,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#eff0f1e6","u16":[61423,61680,61937,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#eff0f1ff","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#fcfcfc1a","u16":[64764,64764,64764,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#fcfcfc40","u16":[64764,64764,64764,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#fcfcfc80","u16":[64764,64764,64764,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#fcfcfcbf","u16":[64764,64764,64764,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#fcfcfce6","u16":[64764,64764,64764,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#fcfcfcff","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#da44531a","u16":[56026,17476,21331,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#da445340","u16":[56026,17476,21331,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#da445380","u16":[56026,17476,21331,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#da4453bf","u16":[56026,17476,21331,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#da4453e6","u16":[56026,17476,21331,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#da4453ff","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#e8cb2d1a","u16":[59624,52171,11565,6554]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#e8cb2d40","u16":[59624,52171,11565,16384]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#e8cb2d80","u16":[59624,52171,11565,32768]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#e8cb2dbf","u16":[59624,52171,11565,49151]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#e8cb2de6","u16":[59624,52171,11565,58982]}}, +{"bias":1,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#e8cb2dff","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#3daee90d","u16":[15677,44718,59881,3290]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#3daee920","u16":[15677,44718,59881,8224]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#3daee940","u16":[15677,44718,59881,16448]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#3daee960","u16":[15677,44718,59881,24672]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#3daee973","u16":[15677,44718,59881,29606]}}, +{"bias":1,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#3daee980","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"bias":-0.5,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":0.1,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"bias":0.25,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"bias":0.5,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"bias":0.75,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"bias":0.9,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"bias":1,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"bias":1.5,"c1":"#ffffff00","c2":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}} +], +"overlay": [ +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ffffffff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#7f7f7fff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#808080ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#00ff00ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#0000ffff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ffff00ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#030201ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#3daee9ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#232629ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#eff0f1ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#fcfcfcff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#da4453ff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#e8cb2dff"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#1f5775ff","u16":[7967,22359,30069,65535]},"paint":"#3daee980"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#3daee980"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#1f5775ff","u16":[7967,22359,30069,65535]},"paint":"#3daee980"}, +{"base":"#000000ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ffffff00"}, +{"base":"#000000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ffffff00"}, +{"base":"#000000ff","mode":"Screen","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ffffff00"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#000000ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#808080ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ff0000ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#00ff00ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffff00ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#030201ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#3daee9ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#232629ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#da4453ff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#9ed6f4ff","u16":[40606,54998,62708,65535]},"paint":"#3daee980"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#9ed6f4ff","u16":[40606,54998,62708,65535]},"paint":"#3daee980"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#3daee980"}, +{"base":"#ffffffff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffff00"}, +{"base":"#ffffffff","mode":"Multiply","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffff00"}, +{"base":"#ffffffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffff00"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#000000ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#ffffffff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#3f3f3fff","u16":[16191,16191,16191,65535]},"paint":"#7f7f7fff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]},"paint":"#7f7f7fff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]},"paint":"#808080ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]},"paint":"#808080ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#7f0000ff","u16":[32639,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#ff7f7fff","u16":[65535,32639,32639,65535]},"paint":"#ff0000ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#007f00ff","u16":[0,32639,0,65535]},"paint":"#00ff00ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#7fff7fff","u16":[32639,65535,32639,65535]},"paint":"#00ff00ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#00007fff","u16":[0,0,32639,65535]},"paint":"#0000ffff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#7f7fffff","u16":[32639,32639,65535,65535]},"paint":"#0000ffff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#7f7f00ff","u16":[32639,32639,0,65535]},"paint":"#ffff00ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#ffff7fff","u16":[65535,65535,32639,65535]},"paint":"#ffff00ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#010100ff","u16":[257,257,0,65535]},"paint":"#030201ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#818080ff","u16":[33153,32896,32896,65535]},"paint":"#030201ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#1e5774ff","u16":[7710,22359,29812,65535]},"paint":"#3daee9ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#9ed6f4ff","u16":[40606,54998,62708,65535]},"paint":"#3daee9ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#111314ff","u16":[4369,4883,5140,65535]},"paint":"#232629ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#919294ff","u16":[37265,37522,38036,65535]},"paint":"#232629ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#777878ff","u16":[30583,30840,30840,65535]},"paint":"#eff0f1ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#f7f7f8ff","u16":[63479,63479,63736,65535]},"paint":"#eff0f1ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#7e7e7eff","u16":[32382,32382,32382,65535]},"paint":"#fcfcfcff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]},"paint":"#fcfcfcff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#6d2229ff","u16":[28013,8738,10537,65535]},"paint":"#da4453ff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#eca1a9ff","u16":[60652,41377,43433,65535]},"paint":"#da4453ff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#746516ff","u16":[29812,25957,5654,65535]},"paint":"#e8cb2dff"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#f3e596ff","u16":[62451,58853,38550,65535]},"paint":"#e8cb2dff"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#5e96b4ff","u16":[24158,38550,46260,65535]},"paint":"#3daee980"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#4f6b7aff","u16":[20303,27499,31354,65535]},"paint":"#3daee980"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#8fabbaff","u16":[36751,43947,47802,65535]},"paint":"#3daee980"}, +{"base":"#7f7f7fff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#ffffff00"}, +{"base":"#7f7f7fff","mode":"Multiply","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#ffffff00"}, +{"base":"#7f7f7fff","mode":"Screen","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#ffffff00"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#000000ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#ffffffff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]},"paint":"#7f7f7fff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]},"paint":"#7f7f7fff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]},"paint":"#808080ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]},"paint":"#808080ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#800000ff","u16":[32896,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]},"paint":"#ff0000ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#008000ff","u16":[0,32896,0,65535]},"paint":"#00ff00ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]},"paint":"#00ff00ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#000080ff","u16":[0,0,32896,65535]},"paint":"#0000ffff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]},"paint":"#0000ffff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#808000ff","u16":[32896,32896,0,65535]},"paint":"#ffff00ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#ffff80ff","u16":[65535,65535,32896,65535]},"paint":"#ffff00ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#020101ff","u16":[514,257,257,65535]},"paint":"#030201ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#818180ff","u16":[33153,33153,32896,65535]},"paint":"#030201ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#1f5775ff","u16":[7967,22359,30069,65535]},"paint":"#3daee9ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#9ed7f4ff","u16":[40606,55255,62708,65535]},"paint":"#3daee9ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#121315ff","u16":[4626,4883,5397,65535]},"paint":"#232629ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#919394ff","u16":[37265,37779,38036,65535]},"paint":"#232629ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#787879ff","u16":[30840,30840,31097,65535]},"paint":"#eff0f1ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#f7f8f8ff","u16":[63479,63736,63736,65535]},"paint":"#eff0f1ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#7e7e7eff","u16":[32382,32382,32382,65535]},"paint":"#fcfcfcff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]},"paint":"#fcfcfcff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#6d222aff","u16":[28013,8738,10794,65535]},"paint":"#da4453ff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#eda2a9ff","u16":[60909,41634,43433,65535]},"paint":"#da4453ff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#746617ff","u16":[29812,26214,5911,65535]},"paint":"#e8cb2dff"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#f4e596ff","u16":[62708,58853,38550,65535]},"paint":"#e8cb2dff"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#5f97b5ff","u16":[24415,38807,46517,65535]},"paint":"#3daee980"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#4f6b7aff","u16":[20303,27499,31354,65535]},"paint":"#3daee980"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#8fabbaff","u16":[36751,43947,47802,65535]},"paint":"#3daee980"}, +{"base":"#808080ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#ffffff00"}, +{"base":"#808080ff","mode":"Multiply","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#ffffff00"}, +{"base":"#808080ff","mode":"Screen","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#ffffff00"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ffffffff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#7f0000ff","u16":[32639,0,0,65535]},"paint":"#7f7f7fff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff7f7fff","u16":[65535,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#800000ff","u16":[32896,0,0,65535]},"paint":"#808080ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#0000ffff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#030000ff","u16":[771,0,0,65535]},"paint":"#030201ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff0201ff","u16":[65535,514,257,65535]},"paint":"#030201ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#3d0000ff","u16":[15677,0,0,65535]},"paint":"#3daee9ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ffaee9ff","u16":[65535,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#230000ff","u16":[8995,0,0,65535]},"paint":"#232629ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff2629ff","u16":[65535,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#ef0000ff","u16":[61423,0,0,65535]},"paint":"#eff0f1ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#fff0f1ff","u16":[65535,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#fc0000ff","u16":[64764,0,0,65535]},"paint":"#fcfcfcff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#fffcfcff","u16":[65535,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#da0000ff","u16":[56026,0,0,65535]},"paint":"#da4453ff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff4453ff","u16":[65535,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#e80000ff","u16":[59624,0,0,65535]},"paint":"#e8cb2dff"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ffcb2dff","u16":[65535,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#9e5775ff","u16":[40606,22359,30069,65535]},"paint":"#3daee980"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#9e0000ff","u16":[40606,0,0,65535]},"paint":"#3daee980"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff5775ff","u16":[65535,22359,30069,65535]},"paint":"#3daee980"}, +{"base":"#ff0000ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ffffff00"}, +{"base":"#ff0000ff","mode":"Multiply","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ffffff00"}, +{"base":"#ff0000ff","mode":"Screen","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ffffff00"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#000000ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#ffffffff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#007f00ff","u16":[0,32639,0,65535]},"paint":"#7f7f7fff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#7fff7fff","u16":[32639,65535,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#008000ff","u16":[0,32896,0,65535]},"paint":"#808080ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]},"paint":"#808080ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ff0000ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#0000ffff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]},"paint":"#0000ffff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#000200ff","u16":[0,514,0,65535]},"paint":"#030201ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#03ff01ff","u16":[771,65535,257,65535]},"paint":"#030201ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00ae00ff","u16":[0,44718,0,65535]},"paint":"#3daee9ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#3dffe9ff","u16":[15677,65535,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#002600ff","u16":[0,9766,0,65535]},"paint":"#232629ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#23ff29ff","u16":[8995,65535,10537,65535]},"paint":"#232629ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00f000ff","u16":[0,61680,0,65535]},"paint":"#eff0f1ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#effff1ff","u16":[61423,65535,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00fc00ff","u16":[0,64764,0,65535]},"paint":"#fcfcfcff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#fcfffcff","u16":[64764,65535,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#004400ff","u16":[0,17476,0,65535]},"paint":"#da4453ff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#daff53ff","u16":[56026,65535,21331,65535]},"paint":"#da4453ff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00cb00ff","u16":[0,52171,0,65535]},"paint":"#e8cb2dff"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#e8ff2dff","u16":[59624,65535,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#1fd675ff","u16":[7967,54998,30069,65535]},"paint":"#3daee980"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00d600ff","u16":[0,54998,0,65535]},"paint":"#3daee980"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#1fff75ff","u16":[7967,65535,30069,65535]},"paint":"#3daee980"}, +{"base":"#00ff00ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#ffffff00"}, +{"base":"#00ff00ff","mode":"Multiply","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#ffffff00"}, +{"base":"#00ff00ff","mode":"Screen","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#ffffff00"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#000000ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#ffffffff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#00007fff","u16":[0,0,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#7f7fffff","u16":[32639,32639,65535,65535]},"paint":"#7f7f7fff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#000080ff","u16":[0,0,32896,65535]},"paint":"#808080ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]},"paint":"#808080ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]},"paint":"#ff0000ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#00ff00ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]},"paint":"#00ff00ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#ffff00ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffff00ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#000001ff","u16":[0,0,257,65535]},"paint":"#030201ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#0302ffff","u16":[771,514,65535,65535]},"paint":"#030201ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#0000e9ff","u16":[0,0,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#3daeffff","u16":[15677,44718,65535,65535]},"paint":"#3daee9ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#000029ff","u16":[0,0,10537,65535]},"paint":"#232629ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#2326ffff","u16":[8995,9766,65535,65535]},"paint":"#232629ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#0000f1ff","u16":[0,0,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#eff0ffff","u16":[61423,61680,65535,65535]},"paint":"#eff0f1ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#0000fcff","u16":[0,0,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#fcfcffff","u16":[64764,64764,65535,65535]},"paint":"#fcfcfcff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#000053ff","u16":[0,0,21331,65535]},"paint":"#da4453ff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#da44ffff","u16":[56026,17476,65535,65535]},"paint":"#da4453ff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#00002dff","u16":[0,0,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#e8cbffff","u16":[59624,52171,65535,65535]},"paint":"#e8cb2dff"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#1f57f4ff","u16":[7967,22359,62708,65535]},"paint":"#3daee980"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#0000f4ff","u16":[0,0,62708,65535]},"paint":"#3daee980"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#1f57ffff","u16":[7967,22359,65535,65535]},"paint":"#3daee980"}, +{"base":"#0000ffff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#ffffff00"}, +{"base":"#0000ffff","mode":"Multiply","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#ffffff00"}, +{"base":"#0000ffff","mode":"Screen","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#ffffff00"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#000000ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffffffff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#7f7f00ff","u16":[32639,32639,0,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff7fff","u16":[65535,65535,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#808000ff","u16":[32896,32896,0,65535]},"paint":"#808080ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff80ff","u16":[65535,65535,32896,65535]},"paint":"#808080ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#0000ffff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#030200ff","u16":[771,514,0,65535]},"paint":"#030201ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff01ff","u16":[65535,65535,257,65535]},"paint":"#030201ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#3dae00ff","u16":[15677,44718,0,65535]},"paint":"#3daee9ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffffe9ff","u16":[65535,65535,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#232600ff","u16":[8995,9766,0,65535]},"paint":"#232629ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff29ff","u16":[65535,65535,10537,65535]},"paint":"#232629ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#eff000ff","u16":[61423,61680,0,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#fffff1ff","u16":[65535,65535,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#fcfc00ff","u16":[64764,64764,0,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#fffffcff","u16":[65535,65535,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#da4400ff","u16":[56026,17476,0,65535]},"paint":"#da4453ff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff53ff","u16":[65535,65535,21331,65535]},"paint":"#da4453ff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#e8cb00ff","u16":[59624,52171,0,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff2dff","u16":[65535,65535,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#9ed675ff","u16":[40606,54998,30069,65535]},"paint":"#3daee980"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#9ed600ff","u16":[40606,54998,0,65535]},"paint":"#3daee980"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff75ff","u16":[65535,65535,30069,65535]},"paint":"#3daee980"}, +{"base":"#ffff00ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffffff00"}, +{"base":"#ffff00ff","mode":"Multiply","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffffff00"}, +{"base":"#ffff00ff","mode":"Screen","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffffff00"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#000000ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#ffffffff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#010100ff","u16":[257,257,0,65535]},"paint":"#7f7f7fff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#818080ff","u16":[33153,32896,32896,65535]},"paint":"#7f7f7fff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#020101ff","u16":[514,257,257,65535]},"paint":"#808080ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#818180ff","u16":[33153,33153,32896,65535]},"paint":"#808080ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#030000ff","u16":[771,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#ff0201ff","u16":[65535,514,257,65535]},"paint":"#ff0000ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#000200ff","u16":[0,514,0,65535]},"paint":"#00ff00ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#03ff01ff","u16":[771,65535,257,65535]},"paint":"#00ff00ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#000001ff","u16":[0,0,257,65535]},"paint":"#0000ffff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#0302ffff","u16":[771,514,65535,65535]},"paint":"#0000ffff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#030200ff","u16":[771,514,0,65535]},"paint":"#ffff00ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#ffff01ff","u16":[65535,65535,257,65535]},"paint":"#ffff00ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#030201ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#060402ff","u16":[1542,1028,514,65535]},"paint":"#030201ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#010101ff","u16":[257,257,257,65535]},"paint":"#3daee9ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#3fafe9ff","u16":[16191,44975,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#232629ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#26282aff","u16":[9766,10280,10794,65535]},"paint":"#232629ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#eff0f1ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#fcfcfcff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#030100ff","u16":[771,257,0,65535]},"paint":"#da4453ff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#da4554ff","u16":[56026,17733,21588,65535]},"paint":"#da4453ff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#030200ff","u16":[771,514,0,65535]},"paint":"#e8cb2dff"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#e8cb2eff","u16":[59624,52171,11822,65535]},"paint":"#e8cb2dff"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#205875ff","u16":[8224,22616,30069,65535]},"paint":"#3daee980"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#020201ff","u16":[514,514,257,65535]},"paint":"#3daee980"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#225876ff","u16":[8738,22616,30326,65535]},"paint":"#3daee980"}, +{"base":"#030201ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#ffffff00"}, +{"base":"#030201ff","mode":"Multiply","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#ffffff00"}, +{"base":"#030201ff","mode":"Screen","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#ffffff00"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#000000ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#ffffffff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#1e5774ff","u16":[7710,22359,29812,65535]},"paint":"#7f7f7fff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#9ed6f4ff","u16":[40606,54998,62708,65535]},"paint":"#7f7f7fff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#1f5775ff","u16":[7967,22359,30069,65535]},"paint":"#808080ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#9ed7f4ff","u16":[40606,55255,62708,65535]},"paint":"#808080ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#3d0000ff","u16":[15677,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#ffaee9ff","u16":[65535,44718,59881,65535]},"paint":"#ff0000ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#00ae00ff","u16":[0,44718,0,65535]},"paint":"#00ff00ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#3dffe9ff","u16":[15677,65535,59881,65535]},"paint":"#00ff00ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#0000e9ff","u16":[0,0,59881,65535]},"paint":"#0000ffff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#3daeffff","u16":[15677,44718,65535,65535]},"paint":"#0000ffff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#3dae00ff","u16":[15677,44718,0,65535]},"paint":"#ffff00ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#ffffe9ff","u16":[65535,65535,59881,65535]},"paint":"#ffff00ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#010101ff","u16":[257,257,257,65535]},"paint":"#030201ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#3fafe9ff","u16":[16191,44975,59881,65535]},"paint":"#030201ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#0f77d5ff","u16":[3855,30583,54741,65535]},"paint":"#3daee9ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#6be5fdff","u16":[27499,58853,65021,65535]},"paint":"#3daee9ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#081a25ff","u16":[2056,6682,9509,65535]},"paint":"#232629ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#58baedff","u16":[22616,47802,60909,65535]},"paint":"#232629ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#39a4dcff","u16":[14649,42148,56540,65535]},"paint":"#eff0f1ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#f3fafeff","u16":[62451,64250,65278,65535]},"paint":"#eff0f1ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#3cace6ff","u16":[15420,44204,59110,65535]},"paint":"#fcfcfcff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#fdfeffff","u16":[65021,65278,65535,65535]},"paint":"#fcfcfcff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#342e4cff","u16":[13364,11822,19532,65535]},"paint":"#da4453ff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#e3c4f0ff","u16":[58339,50372,61680,65535]},"paint":"#da4453ff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#378b29ff","u16":[14135,35723,10537,65535]},"paint":"#e8cb2dff"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#eeeeedff","u16":[61166,61166,60909,65535]},"paint":"#e8cb2dff"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee980"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#2692dfff","u16":[9766,37522,57311,65535]},"paint":"#3daee980"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#55caf3ff","u16":[21845,51914,62451,65535]},"paint":"#3daee980"}, +{"base":"#3daee9ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#ffffff00"}, +{"base":"#3daee9ff","mode":"Multiply","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#ffffff00"}, +{"base":"#3daee9ff","mode":"Screen","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#ffffff00"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#000000ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#ffffffff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#111314ff","u16":[4369,4883,5140,65535]},"paint":"#7f7f7fff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#919294ff","u16":[37265,37522,38036,65535]},"paint":"#7f7f7fff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#121315ff","u16":[4626,4883,5397,65535]},"paint":"#808080ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#919394ff","u16":[37265,37779,38036,65535]},"paint":"#808080ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#230000ff","u16":[8995,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#ff2629ff","u16":[65535,9766,10537,65535]},"paint":"#ff0000ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#002600ff","u16":[0,9766,0,65535]},"paint":"#00ff00ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#23ff29ff","u16":[8995,65535,10537,65535]},"paint":"#00ff00ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#000029ff","u16":[0,0,10537,65535]},"paint":"#0000ffff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#2326ffff","u16":[8995,9766,65535,65535]},"paint":"#0000ffff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#232600ff","u16":[8995,9766,0,65535]},"paint":"#ffff00ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#ffff29ff","u16":[65535,65535,10537,65535]},"paint":"#ffff00ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#030201ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#26282aff","u16":[9766,10280,10794,65535]},"paint":"#030201ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#081a25ff","u16":[2056,6682,9509,65535]},"paint":"#3daee9ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#58baedff","u16":[22616,47802,60909,65535]},"paint":"#3daee9ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#050607ff","u16":[1285,1542,1799,65535]},"paint":"#232629ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#41464bff","u16":[16705,17990,19275,65535]},"paint":"#232629ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#212427ff","u16":[8481,9252,10023,65535]},"paint":"#eff0f1ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#f1f2f3ff","u16":[61937,62194,62451,65535]},"paint":"#eff0f1ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#fcfcfcff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#1e0a0dff","u16":[7710,2570,3341,65535]},"paint":"#da4453ff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#df606fff","u16":[57311,24672,28527,65535]},"paint":"#da4453ff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#201e07ff","u16":[8224,7710,1799,65535]},"paint":"#e8cb2dff"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#ebd34fff","u16":[60395,54227,20303,65535]},"paint":"#e8cb2dff"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#306a89ff","u16":[12336,27242,35209,65535]},"paint":"#3daee980"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#162027ff","u16":[5654,8224,10023,65535]},"paint":"#3daee980"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#3e708bff","u16":[15934,28784,35723,65535]},"paint":"#3daee980"}, +{"base":"#232629ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#ffffff00"}, +{"base":"#232629ff","mode":"Multiply","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#ffffff00"}, +{"base":"#232629ff","mode":"Screen","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#ffffff00"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#000000ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#ffffffff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#777878ff","u16":[30583,30840,30840,65535]},"paint":"#7f7f7fff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#f7f7f8ff","u16":[63479,63479,63736,65535]},"paint":"#7f7f7fff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#787879ff","u16":[30840,30840,31097,65535]},"paint":"#808080ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#f7f8f8ff","u16":[63479,63736,63736,65535]},"paint":"#808080ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#ef0000ff","u16":[61423,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#fff0f1ff","u16":[65535,61680,61937,65535]},"paint":"#ff0000ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#00f000ff","u16":[0,61680,0,65535]},"paint":"#00ff00ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#effff1ff","u16":[61423,65535,61937,65535]},"paint":"#00ff00ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#0000f1ff","u16":[0,0,61937,65535]},"paint":"#0000ffff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#eff0ffff","u16":[61423,61680,65535,65535]},"paint":"#0000ffff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#eff000ff","u16":[61423,61680,0,65535]},"paint":"#ffff00ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#fffff1ff","u16":[65535,65535,61937,65535]},"paint":"#ffff00ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#030201ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#39a4dcff","u16":[14649,42148,56540,65535]},"paint":"#3daee9ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#f3fafeff","u16":[62451,64250,65278,65535]},"paint":"#3daee9ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#212427ff","u16":[8481,9252,10023,65535]},"paint":"#232629ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#f1f2f3ff","u16":[61937,62194,62451,65535]},"paint":"#232629ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#e0e2e4ff","u16":[57568,58082,58596,65535]},"paint":"#eff0f1ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]},"paint":"#eff0f1ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#ecedeeff","u16":[60652,60909,61166,65535]},"paint":"#fcfcfcff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#fcfcfcff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#cc404eff","u16":[52428,16448,20046,65535]},"paint":"#da4453ff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#fdf4f6ff","u16":[65021,62708,63222,65535]},"paint":"#da4453ff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#d9bf2bff","u16":[55769,49087,11051,65535]},"paint":"#e8cb2dff"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#fefcf3ff","u16":[65278,64764,62451,65535]},"paint":"#e8cb2dff"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#96cfedff","u16":[38550,53199,60909,65535]},"paint":"#3daee980"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#94c9e7ff","u16":[38036,51657,59367,65535]},"paint":"#3daee980"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#f1f5f7ff","u16":[61937,62965,63479,65535]},"paint":"#3daee980"}, +{"base":"#eff0f1ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#ffffff00"}, +{"base":"#eff0f1ff","mode":"Multiply","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#ffffff00"}, +{"base":"#eff0f1ff","mode":"Screen","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#ffffff00"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#000000ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#ffffffff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#7e7e7eff","u16":[32382,32382,32382,65535]},"paint":"#7f7f7fff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]},"paint":"#7f7f7fff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#7e7e7eff","u16":[32382,32382,32382,65535]},"paint":"#808080ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]},"paint":"#808080ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#fc0000ff","u16":[64764,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fffcfcff","u16":[65535,64764,64764,65535]},"paint":"#ff0000ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#00fc00ff","u16":[0,64764,0,65535]},"paint":"#00ff00ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fcfffcff","u16":[64764,65535,64764,65535]},"paint":"#00ff00ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#0000fcff","u16":[0,0,64764,65535]},"paint":"#0000ffff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fcfcffff","u16":[64764,64764,65535,65535]},"paint":"#0000ffff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#fcfc00ff","u16":[64764,64764,0,65535]},"paint":"#ffff00ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fffffcff","u16":[65535,65535,64764,65535]},"paint":"#ffff00ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#030201ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#3cace6ff","u16":[15420,44204,59110,65535]},"paint":"#3daee9ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fdfeffff","u16":[65021,65278,65535,65535]},"paint":"#3daee9ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#232629ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#ecedeeff","u16":[60652,60909,61166,65535]},"paint":"#eff0f1ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#eff0f1ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#f9f9f9ff","u16":[63993,63993,63993,65535]},"paint":"#fcfcfcff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#fcfcfcff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#d74352ff","u16":[55255,17219,21074,65535]},"paint":"#da4453ff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fffdfdff","u16":[65535,65021,65021,65535]},"paint":"#da4453ff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#e5c92cff","u16":[58853,51657,11308,65535]},"paint":"#e8cb2dff"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]},"paint":"#e8cb2dff"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#9dd5f3ff","u16":[40349,54741,62451,65535]},"paint":"#3daee980"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#9cd3f1ff","u16":[40092,54227,61937,65535]},"paint":"#3daee980"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fcfdfdff","u16":[64764,65021,65021,65535]},"paint":"#3daee980"}, +{"base":"#fcfcfcff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#ffffff00"}, +{"base":"#fcfcfcff","mode":"Multiply","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#ffffff00"}, +{"base":"#fcfcfcff","mode":"Screen","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#ffffff00"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#000000ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#ffffffff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#6d2229ff","u16":[28013,8738,10537,65535]},"paint":"#7f7f7fff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#eca1a9ff","u16":[60652,41377,43433,65535]},"paint":"#7f7f7fff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#6d222aff","u16":[28013,8738,10794,65535]},"paint":"#808080ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#eda2a9ff","u16":[60909,41634,43433,65535]},"paint":"#808080ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#da0000ff","u16":[56026,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#ff4453ff","u16":[65535,17476,21331,65535]},"paint":"#ff0000ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#004400ff","u16":[0,17476,0,65535]},"paint":"#00ff00ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#daff53ff","u16":[56026,65535,21331,65535]},"paint":"#00ff00ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#000053ff","u16":[0,0,21331,65535]},"paint":"#0000ffff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#da44ffff","u16":[56026,17476,65535,65535]},"paint":"#0000ffff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#da4400ff","u16":[56026,17476,0,65535]},"paint":"#ffff00ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#ffff53ff","u16":[65535,65535,21331,65535]},"paint":"#ffff00ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#030100ff","u16":[771,257,0,65535]},"paint":"#030201ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#da4554ff","u16":[56026,17733,21588,65535]},"paint":"#030201ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#342e4cff","u16":[13364,11822,19532,65535]},"paint":"#3daee9ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#e3c4f0ff","u16":[58339,50372,61680,65535]},"paint":"#3daee9ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#1e0a0dff","u16":[7710,2570,3341,65535]},"paint":"#232629ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#df606fff","u16":[57311,24672,28527,65535]},"paint":"#232629ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#cc404eff","u16":[52428,16448,20046,65535]},"paint":"#eff0f1ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#fdf4f6ff","u16":[65021,62708,63222,65535]},"paint":"#eff0f1ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#d74352ff","u16":[55255,17219,21074,65535]},"paint":"#fcfcfcff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#fffdfdff","u16":[65535,65021,65021,65535]},"paint":"#fcfcfcff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#ba121bff","u16":[47802,4626,6939,65535]},"paint":"#da4453ff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#fa768bff","u16":[64250,30326,35723,65535]},"paint":"#da4453ff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#c6360fff","u16":[50886,13878,3855,65535]},"paint":"#e8cb2dff"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#fcd971ff","u16":[64764,55769,29041,65535]},"paint":"#e8cb2dff"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#8c799eff","u16":[35980,31097,40606,65535]},"paint":"#3daee980"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#87394fff","u16":[34695,14649,20303,65535]},"paint":"#3daee980"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#de84a2ff","u16":[57054,33924,41634,65535]},"paint":"#3daee980"}, +{"base":"#da4453ff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#ffffff00"}, +{"base":"#da4453ff","mode":"Multiply","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#ffffff00"}, +{"base":"#da4453ff","mode":"Screen","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#ffffff00"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#000000ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#ffffffff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#746516ff","u16":[29812,25957,5654,65535]},"paint":"#7f7f7fff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#f3e596ff","u16":[62451,58853,38550,65535]},"paint":"#7f7f7fff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#746617ff","u16":[29812,26214,5911,65535]},"paint":"#808080ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#f4e596ff","u16":[62708,58853,38550,65535]},"paint":"#808080ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#e80000ff","u16":[59624,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#ffcb2dff","u16":[65535,52171,11565,65535]},"paint":"#ff0000ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#00cb00ff","u16":[0,52171,0,65535]},"paint":"#00ff00ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#e8ff2dff","u16":[59624,65535,11565,65535]},"paint":"#00ff00ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#00002dff","u16":[0,0,11565,65535]},"paint":"#0000ffff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#e8cbffff","u16":[59624,52171,65535,65535]},"paint":"#0000ffff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#e8cb00ff","u16":[59624,52171,0,65535]},"paint":"#ffff00ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#ffff2dff","u16":[65535,65535,11565,65535]},"paint":"#ffff00ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#030200ff","u16":[771,514,0,65535]},"paint":"#030201ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#e8cb2eff","u16":[59624,52171,11822,65535]},"paint":"#030201ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#378b29ff","u16":[14135,35723,10537,65535]},"paint":"#3daee9ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#eeeeedff","u16":[61166,61166,60909,65535]},"paint":"#3daee9ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#201e07ff","u16":[8224,7710,1799,65535]},"paint":"#232629ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#ebd34fff","u16":[60395,54227,20303,65535]},"paint":"#232629ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#d9bf2bff","u16":[55769,49087,11051,65535]},"paint":"#eff0f1ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#fefcf3ff","u16":[65278,64764,62451,65535]},"paint":"#eff0f1ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#e5c92cff","u16":[58853,51657,11308,65535]},"paint":"#fcfcfcff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]},"paint":"#fcfcfcff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#c6360fff","u16":[50886,13878,3855,65535]},"paint":"#da4453ff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#fcd971ff","u16":[64764,55769,29041,65535]},"paint":"#da4453ff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#d3a208ff","u16":[54227,41634,2056,65535]},"paint":"#e8cb2dff"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#fdf452ff","u16":[65021,62708,21074,65535]},"paint":"#e8cb2dff"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#93bc8bff","u16":[37779,48316,35723,65535]},"paint":"#3daee980"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#90aa2bff","u16":[37008,43690,11051,65535]},"paint":"#3daee980"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#ebdd8dff","u16":[60395,56797,36237,65535]},"paint":"#3daee980"}, +{"base":"#e8cb2dff","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#ffffff00"}, +{"base":"#e8cb2dff","mode":"Multiply","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#ffffff00"}, +{"base":"#e8cb2dff","mode":"Screen","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#ffffff00"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#000000ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#ffffffff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#1e5774ff","u16":[7710,22359,29812,65535]},"paint":"#7f7f7fff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#9ed6f4ff","u16":[40606,54998,62708,65535]},"paint":"#7f7f7fff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#1f5775ff","u16":[7967,22359,30069,65535]},"paint":"#808080ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#9ed7f4ff","u16":[40606,55255,62708,65535]},"paint":"#808080ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#3d0000ff","u16":[15677,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#ffaee9ff","u16":[65535,44718,59881,65535]},"paint":"#ff0000ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#00ae00ff","u16":[0,44718,0,65535]},"paint":"#00ff00ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#3dffe9ff","u16":[15677,65535,59881,65535]},"paint":"#00ff00ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#0000e9ff","u16":[0,0,59881,65535]},"paint":"#0000ffff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#3daeffff","u16":[15677,44718,65535,65535]},"paint":"#0000ffff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#3dae00ff","u16":[15677,44718,0,65535]},"paint":"#ffff00ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#ffffe9ff","u16":[65535,65535,59881,65535]},"paint":"#ffff00ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#010101ff","u16":[257,257,257,65535]},"paint":"#030201ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#3fafe9ff","u16":[16191,44975,59881,65535]},"paint":"#030201ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#0f77d5ff","u16":[3855,30583,54741,65535]},"paint":"#3daee9ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#6be5fdff","u16":[27499,58853,65021,65535]},"paint":"#3daee9ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#081a25ff","u16":[2056,6682,9509,65535]},"paint":"#232629ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#58baedff","u16":[22616,47802,60909,65535]},"paint":"#232629ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#39a4dcff","u16":[14649,42148,56540,65535]},"paint":"#eff0f1ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#f3fafeff","u16":[62451,64250,65278,65535]},"paint":"#eff0f1ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#3cace6ff","u16":[15420,44204,59110,65535]},"paint":"#fcfcfcff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#fdfeffff","u16":[65021,65278,65535,65535]},"paint":"#fcfcfcff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#342e4cff","u16":[13364,11822,19532,65535]},"paint":"#da4453ff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#e3c4f0ff","u16":[58339,50372,61680,65535]},"paint":"#da4453ff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#378b29ff","u16":[14135,35723,10537,65535]},"paint":"#e8cb2dff"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#eeeeedff","u16":[61166,61166,60909,65535]},"paint":"#e8cb2dff"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee980"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#2692dfff","u16":[9766,37522,57311,65535]},"paint":"#3daee980"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#55caf3ff","u16":[21845,51914,62451,65535]},"paint":"#3daee980"}, +{"base":"#3daee980","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#ffffff00"}, +{"base":"#3daee980","mode":"Multiply","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#ffffff00"}, +{"base":"#3daee980","mode":"Screen","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#ffffff00"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"paint":"#000000ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#000000ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffffff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#7f7f7fff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"paint":"#808080ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#808080ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"paint":"#ff0000ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ff0000ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"paint":"#00ff00ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#00ff00ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#0000ffff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"paint":"#ffff00ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffff00ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#030201ff","u16":[771,514,257,65535]},"paint":"#030201ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#030201ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"paint":"#3daee9ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#3daee9ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"paint":"#232629ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#232629ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#eff0f1ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#fcfcfcff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"paint":"#da4453ff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#da4453ff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#e8cb2dff"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#9ed6f4ff","u16":[40606,54998,62708,65535]},"paint":"#3daee980"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#9ed6f4ff","u16":[40606,54998,62708,65535]},"paint":"#3daee980"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#3daee980"}, +{"base":"#ffffff00","mode":"SourceOver","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffff00"}, +{"base":"#ffffff00","mode":"Multiply","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffff00"}, +{"base":"#ffffff00","mode":"Screen","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"paint":"#ffffff00"} +], +"hcy_color": [ +{"a":1,"c":-0.5,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":-0.5,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":-0.5,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":-0.5,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":-0.5,"h":-0.25,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":-0.5,"h":-0.25,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":-0.5,"h":-0.25,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":-0.5,"h":-0.25,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":-0.5,"h":-0.25,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":-0.5,"h":-0.25,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":-0.5,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":-0.5,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":-0.5,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":-0.5,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0,"h":-0.25,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":0,"h":-0.25,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":0,"h":-0.25,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":0,"h":-0.25,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":0,"h":-0.25,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":0,"h":-0.25,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":0,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.25,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.25,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.25,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.25,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.25,"h":-0.25,"out":{"hex":"#6a4f7fff","u16":[27247,20190,32590,65535]},"y":0.1}, +{"a":0.5,"c":0.25,"h":-0.25,"out":{"hex":"#6a4f7f80","u16":[27247,20190,32590,32768]},"y":0.1}, +{"a":1,"c":0.25,"h":-0.25,"out":{"hex":"#c2b5ceff","u16":[49896,46625,52929,65535]},"y":0.5}, +{"a":0.5,"c":0.25,"h":-0.25,"out":{"hex":"#c2b5ce80","u16":[49896,46625,52929,32768]},"y":0.5}, +{"a":1,"c":0.25,"h":-0.25,"out":{"hex":"#f4f2f6ff","u16":[62778,62299,63253,65535]},"y":0.9}, +{"a":0.5,"c":0.25,"h":-0.25,"out":{"hex":"#f4f2f680","u16":[62778,62299,63253,32768]},"y":0.9}, +{"a":1,"c":0.25,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.25,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.25,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.25,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.5,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.5,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.5,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.5,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.5,"h":-0.25,"out":{"hex":"#78419aff","u16":[30810,16792,39599,65535]},"y":0.1}, +{"a":0.5,"c":0.5,"h":-0.25,"out":{"hex":"#78419a80","u16":[30810,16792,39599,32768]},"y":0.1}, +{"a":1,"c":0.5,"h":-0.25,"out":{"hex":"#cab1e0ff","u16":[51871,45388,57502,65535]},"y":0.5}, +{"a":0.5,"c":0.5,"h":-0.25,"out":{"hex":"#cab1e080","u16":[51871,45388,57502,32768]},"y":0.5}, +{"a":1,"c":0.5,"h":-0.25,"out":{"hex":"#f5f2f9ff","u16":[63084,62127,64025,65535]},"y":0.9}, +{"a":0.5,"c":0.5,"h":-0.25,"out":{"hex":"#f5f2f980","u16":[63084,62127,64025,32768]},"y":0.9}, +{"a":1,"c":0.5,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.5,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.5,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.5,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1,"h":-0.25,"out":{"hex":"#8f00c4ff","u16":[36750,0,50361,65535]},"y":0.1}, +{"a":0.5,"c":1,"h":-0.25,"out":{"hex":"#8f00c480","u16":[36750,0,50361,32768]},"y":0.1}, +{"a":1,"c":1,"h":-0.25,"out":{"hex":"#d8a6ffff","u16":[55570,42784,65535,65535]},"y":0.5}, +{"a":0.5,"c":1,"h":-0.25,"out":{"hex":"#d8a6ff80","u16":[55570,42784,65535,32768]},"y":0.5}, +{"a":1,"c":1,"h":-0.25,"out":{"hex":"#f8f0ffff","u16":[63691,61780,65535,65535]},"y":0.9}, +{"a":0.5,"c":1,"h":-0.25,"out":{"hex":"#f8f0ff80","u16":[63691,61780,65535,32768]},"y":0.9}, +{"a":1,"c":1,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1.5,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1.5,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1.5,"h":-0.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1.5,"h":-0.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1.5,"h":-0.25,"out":{"hex":"#8f00c4ff","u16":[36750,0,50361,65535]},"y":0.1}, +{"a":0.5,"c":1.5,"h":-0.25,"out":{"hex":"#8f00c480","u16":[36750,0,50361,32768]},"y":0.1}, +{"a":1,"c":1.5,"h":-0.25,"out":{"hex":"#d8a6ffff","u16":[55570,42784,65535,65535]},"y":0.5}, +{"a":0.5,"c":1.5,"h":-0.25,"out":{"hex":"#d8a6ff80","u16":[55570,42784,65535,32768]},"y":0.5}, +{"a":1,"c":1.5,"h":-0.25,"out":{"hex":"#f8f0ffff","u16":[63691,61780,65535,65535]},"y":0.9}, +{"a":0.5,"c":1.5,"h":-0.25,"out":{"hex":"#f8f0ff80","u16":[63691,61780,65535,32768]},"y":0.9}, +{"a":1,"c":1.5,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1.5,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1.5,"h":-0.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1.5,"h":-0.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":-0.5,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":-0.5,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":-0.5,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":-0.5,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":-0.5,"h":0,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":-0.5,"h":0,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":-0.5,"h":0,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":-0.5,"h":0,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":-0.5,"h":0,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":-0.5,"h":0,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":-0.5,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":-0.5,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":-0.5,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":-0.5,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0,"h":0,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":0,"h":0,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":0,"h":0,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":0,"h":0,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":0,"h":0,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":0,"h":0,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":0,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.25,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.25,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.25,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.25,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.25,"h":0,"out":{"hex":"#794f4fff","u16":[30996,20190,20190,65535]},"y":0.1}, +{"a":0.5,"c":0.25,"h":0,"out":{"hex":"#794f4f80","u16":[30996,20190,20190,32768]},"y":0.1}, +{"a":1,"c":0.25,"h":0,"out":{"hex":"#ceb4b4ff","u16":[52929,46328,46328,65535]},"y":0.5}, +{"a":0.5,"c":0.25,"h":0,"out":{"hex":"#ceb4b480","u16":[52929,46328,46328,32768]},"y":0.5}, +{"a":1,"c":0.25,"h":0,"out":{"hex":"#f6f2f2ff","u16":[63253,62257,62257,65535]},"y":0.9}, +{"a":0.5,"c":0.25,"h":0,"out":{"hex":"#f6f2f280","u16":[63253,62257,62257,32768]},"y":0.9}, +{"a":1,"c":0.25,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.25,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.25,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.25,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.5,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.5,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.5,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.5,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.5,"h":0,"out":{"hex":"#904141ff","u16":[37051,16792,16792,65535]},"y":0.1}, +{"a":0.5,"c":0.5,"h":0,"out":{"hex":"#90414180","u16":[37051,16792,16792,32768]},"y":0.1}, +{"a":1,"c":0.5,"h":0,"out":{"hex":"#e0aeaeff","u16":[57502,44773,44773,65535]},"y":0.5}, +{"a":0.5,"c":0.5,"h":0,"out":{"hex":"#e0aeae80","u16":[57502,44773,44773,32768]},"y":0.5}, +{"a":1,"c":0.5,"h":0,"out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]},"y":0.9}, +{"a":0.5,"c":0.5,"h":0,"out":{"hex":"#f9f1f180","u16":[64025,62043,62043,32768]},"y":0.9}, +{"a":1,"c":0.5,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.5,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.5,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.5,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1,"h":0,"out":{"hex":"#b50000ff","u16":[46514,0,0,65535]},"y":0.1}, +{"a":0.5,"c":1,"h":0,"out":{"hex":"#b5000080","u16":[46514,0,0,32768]},"y":0.1}, +{"a":1,"c":1,"h":0,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]},"y":0.5}, +{"a":0.5,"c":1,"h":0,"out":{"hex":"#ffa1a180","u16":[65535,41449,41449,32768]},"y":0.5}, +{"a":1,"c":1,"h":0,"out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]},"y":0.9}, +{"a":0.5,"c":1,"h":0,"out":{"hex":"#fff0f080","u16":[65535,61611,61611,32768]},"y":0.9}, +{"a":1,"c":1,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1.5,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1.5,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1.5,"h":0,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1.5,"h":0,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1.5,"h":0,"out":{"hex":"#b50000ff","u16":[46514,0,0,65535]},"y":0.1}, +{"a":0.5,"c":1.5,"h":0,"out":{"hex":"#b5000080","u16":[46514,0,0,32768]},"y":0.1}, +{"a":1,"c":1.5,"h":0,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]},"y":0.5}, +{"a":0.5,"c":1.5,"h":0,"out":{"hex":"#ffa1a180","u16":[65535,41449,41449,32768]},"y":0.5}, +{"a":1,"c":1.5,"h":0,"out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]},"y":0.9}, +{"a":0.5,"c":1.5,"h":0,"out":{"hex":"#fff0f080","u16":[65535,61611,61611,32768]},"y":0.9}, +{"a":1,"c":1.5,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1.5,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1.5,"h":0,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1.5,"h":0,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":-0.5,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":-0.5,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":-0.5,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":-0.5,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":-0.5,"h":0.1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":-0.5,"h":0.1,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":-0.5,"h":0.1,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":-0.5,"h":0.1,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":-0.5,"h":0.1,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":-0.5,"h":0.1,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":-0.5,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":-0.5,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":-0.5,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":-0.5,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0,"h":0.1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":0,"h":0.1,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":0,"h":0.1,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":0,"h":0.1,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":0,"h":0.1,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":0,"h":0.1,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":0,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.25,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.25,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.25,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.25,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.25,"h":0.1,"out":{"hex":"#5f594fff","u16":[24419,22840,20190,65535]},"y":0.1}, +{"a":0.5,"c":0.25,"h":0.1,"out":{"hex":"#5f594f80","u16":[24419,22840,20190,32768]},"y":0.1}, +{"a":1,"c":0.25,"h":0.1,"out":{"hex":"#c5b9a3ff","u16":[50750,47469,41962,65535]},"y":0.5}, +{"a":0.5,"c":0.25,"h":0.1,"out":{"hex":"#c5b9a380","u16":[50750,47469,41962,32768]},"y":0.5}, +{"a":1,"c":0.25,"h":0.1,"out":{"hex":"#f6f3eeff","u16":[63253,62378,61038,65535]},"y":0.9}, +{"a":0.5,"c":0.25,"h":0.1,"out":{"hex":"#f6f3ee80","u16":[63253,62378,61038,32768]},"y":0.9}, +{"a":1,"c":0.25,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.25,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.25,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.25,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.5,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.5,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.5,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.5,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.5,"h":0.1,"out":{"hex":"#645841ff","u16":[25735,22668,16792,65535]},"y":0.1}, +{"a":0.5,"c":0.5,"h":0.1,"out":{"hex":"#64584180","u16":[25735,22668,16792,32768]},"y":0.1}, +{"a":1,"c":0.5,"h":0.1,"out":{"hex":"#d0b788ff","u16":[53486,47111,34899,65535]},"y":0.5}, +{"a":0.5,"c":0.5,"h":0.1,"out":{"hex":"#d0b78880","u16":[53486,47111,34899,32768]},"y":0.5}, +{"a":1,"c":0.5,"h":0.1,"out":{"hex":"#f9f2e8ff","u16":[64025,62286,59564,65535]},"y":0.9}, +{"a":0.5,"c":0.5,"h":0.1,"out":{"hex":"#f9f2e880","u16":[64025,62286,59564,32768]},"y":0.9}, +{"a":1,"c":0.5,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.5,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.5,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.5,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1,"h":0.1,"out":{"hex":"#6e5700ff","u16":[28151,22318,0,65535]},"y":0.1}, +{"a":0.5,"c":1,"h":0.1,"out":{"hex":"#6e570080","u16":[28151,22318,0,32768]},"y":0.1}, +{"a":1,"c":1,"h":0.1,"out":{"hex":"#e4b400ff","u16":[58508,46384,0,65535]},"y":0.5}, +{"a":0.5,"c":1,"h":0.1,"out":{"hex":"#e4b40080","u16":[58508,46384,0,32768]},"y":0.5}, +{"a":1,"c":1,"h":0.1,"out":{"hex":"#fff2dcff","u16":[65535,62102,56477,65535]},"y":0.9}, +{"a":0.5,"c":1,"h":0.1,"out":{"hex":"#fff2dc80","u16":[65535,62102,56477,32768]},"y":0.9}, +{"a":1,"c":1,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1.5,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1.5,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1.5,"h":0.1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1.5,"h":0.1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1.5,"h":0.1,"out":{"hex":"#6e5700ff","u16":[28151,22318,0,65535]},"y":0.1}, +{"a":0.5,"c":1.5,"h":0.1,"out":{"hex":"#6e570080","u16":[28151,22318,0,32768]},"y":0.1}, +{"a":1,"c":1.5,"h":0.1,"out":{"hex":"#e4b400ff","u16":[58508,46384,0,65535]},"y":0.5}, +{"a":0.5,"c":1.5,"h":0.1,"out":{"hex":"#e4b40080","u16":[58508,46384,0,32768]},"y":0.5}, +{"a":1,"c":1.5,"h":0.1,"out":{"hex":"#fff2dcff","u16":[65535,62102,56477,65535]},"y":0.9}, +{"a":0.5,"c":1.5,"h":0.1,"out":{"hex":"#fff2dc80","u16":[65535,62102,56477,32768]},"y":0.9}, +{"a":1,"c":1.5,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1.5,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1.5,"h":0.1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1.5,"h":0.1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":-0.5,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0,"h":0.16666666666666666,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":0,"h":0.16666666666666666,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":0,"h":0.16666666666666666,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":0,"h":0.16666666666666666,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":0,"h":0.16666666666666666,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":0,"h":0.16666666666666666,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":0,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#5a5a4fff","u16":[23213,23213,20190,65535]},"y":0.1}, +{"a":0.5,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#5a5a4f80","u16":[23213,23213,20190,32768]},"y":0.1}, +{"a":1,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#bcbca3ff","u16":[48244,48244,41962,65535]},"y":0.5}, +{"a":0.5,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#bcbca380","u16":[48244,48244,41962,32768]},"y":0.5}, +{"a":1,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#f5f5d5ff","u16":[63020,63020,54813,65535]},"y":0.9}, +{"a":0.5,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#f5f5d580","u16":[63020,63020,54813,32768]},"y":0.9}, +{"a":1,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.25,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#5b5b41ff","u16":[23413,23413,16792,65535]},"y":0.1}, +{"a":0.5,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#5b5b4180","u16":[23413,23413,16792,32768]},"y":0.1}, +{"a":1,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#bdbd88ff","u16":[48661,48661,34899,65535]},"y":0.5}, +{"a":0.5,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#bdbd8880","u16":[48661,48661,34899,32768]},"y":0.5}, +{"a":1,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#f7f7b1ff","u16":[63564,63564,45587,65535]},"y":0.9}, +{"a":0.5,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#f7f7b180","u16":[63564,63564,45587,32768]},"y":0.9}, +{"a":1,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.5,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1,"h":0.16666666666666666,"out":{"hex":"#5d5d00ff","u16":[23808,23808,0,65535]},"y":0.1}, +{"a":0.5,"c":1,"h":0.16666666666666666,"out":{"hex":"#5d5d0080","u16":[23808,23808,0,32768]},"y":0.1}, +{"a":1,"c":1,"h":0.16666666666666666,"out":{"hex":"#c1c100ff","u16":[49481,49481,0,65535]},"y":0.5}, +{"a":0.5,"c":1,"h":0.16666666666666666,"out":{"hex":"#c1c10080","u16":[49481,49481,0,32768]},"y":0.5}, +{"a":1,"c":1,"h":0.16666666666666666,"out":{"hex":"#fbfb00ff","u16":[64635,64635,0,65535]},"y":0.9}, +{"a":0.5,"c":1,"h":0.16666666666666666,"out":{"hex":"#fbfb0080","u16":[64635,64635,0,32768]},"y":0.9}, +{"a":1,"c":1,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#5d5d00ff","u16":[23808,23808,0,65535]},"y":0.1}, +{"a":0.5,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#5d5d0080","u16":[23808,23808,0,32768]},"y":0.1}, +{"a":1,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#c1c100ff","u16":[49481,49481,0,65535]},"y":0.5}, +{"a":0.5,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#c1c10080","u16":[49481,49481,0,32768]},"y":0.5}, +{"a":1,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#fbfb00ff","u16":[64635,64635,0,65535]},"y":0.9}, +{"a":0.5,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#fbfb0080","u16":[64635,64635,0,32768]},"y":0.9}, +{"a":1,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1.5,"h":0.16666666666666666,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":-0.5,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":-0.5,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":-0.5,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":-0.5,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":-0.5,"h":0.5,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":-0.5,"h":0.5,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":-0.5,"h":0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":-0.5,"h":0.5,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":-0.5,"h":0.5,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":-0.5,"h":0.5,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":-0.5,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":-0.5,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":-0.5,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":-0.5,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0,"h":0.5,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":0,"h":0.5,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":0,"h":0.5,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":0,"h":0.5,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":0,"h":0.5,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":0,"h":0.5,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":0,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.25,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.25,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.25,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.25,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.25,"h":0.5,"out":{"hex":"#4f5c5cff","u16":[20190,23704,23704,65535]},"y":0.1}, +{"a":0.5,"c":0.25,"h":0.5,"out":{"hex":"#4f5c5c80","u16":[20190,23704,23704,32768]},"y":0.1}, +{"a":1,"c":0.25,"h":0.5,"out":{"hex":"#a3c0c0ff","u16":[41962,49265,49265,65535]},"y":0.5}, +{"a":0.5,"c":0.25,"h":0.5,"out":{"hex":"#a3c0c080","u16":[41962,49265,49265,32768]},"y":0.5}, +{"a":1,"c":0.25,"h":0.5,"out":{"hex":"#e7f6f6ff","u16":[59462,63253,63253,65535]},"y":0.9}, +{"a":0.5,"c":0.25,"h":0.5,"out":{"hex":"#e7f6f680","u16":[59462,63253,63253,32768]},"y":0.9}, +{"a":1,"c":0.25,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.25,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.25,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.25,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.5,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.5,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.5,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.5,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.5,"h":0.5,"out":{"hex":"#415f5fff","u16":[16792,24374,24374,65535]},"y":0.1}, +{"a":0.5,"c":0.5,"h":0.5,"out":{"hex":"#415f5f80","u16":[16792,24374,24374,32768]},"y":0.1}, +{"a":1,"c":0.5,"h":0.5,"out":{"hex":"#88c5c5ff","u16":[34899,50657,50657,65535]},"y":0.5}, +{"a":0.5,"c":0.5,"h":0.5,"out":{"hex":"#88c5c580","u16":[34899,50657,50657,32768]},"y":0.5}, +{"a":1,"c":0.5,"h":0.5,"out":{"hex":"#dbf9f9ff","u16":[56260,64025,64025,65535]},"y":0.9}, +{"a":0.5,"c":0.5,"h":0.5,"out":{"hex":"#dbf9f980","u16":[56260,64025,64025,32768]},"y":0.9}, +{"a":1,"c":0.5,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.5,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.5,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.5,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1,"h":0.5,"out":{"hex":"#006464ff","u16":[0,25651,25651,65535]},"y":0.1}, +{"a":0.5,"c":1,"h":0.5,"out":{"hex":"#00646480","u16":[0,25651,25651,32768]},"y":0.1}, +{"a":1,"c":1,"h":0.5,"out":{"hex":"#00cfcfff","u16":[0,53312,53312,65535]},"y":0.5}, +{"a":0.5,"c":1,"h":0.5,"out":{"hex":"#00cfcf80","u16":[0,53312,53312,32768]},"y":0.5}, +{"a":1,"c":1,"h":0.5,"out":{"hex":"#bfffffff","u16":[49092,65535,65535,65535]},"y":0.9}, +{"a":0.5,"c":1,"h":0.5,"out":{"hex":"#bfffff80","u16":[49092,65535,65535,32768]},"y":0.9}, +{"a":1,"c":1,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1.5,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1.5,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1.5,"h":0.5,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1.5,"h":0.5,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1.5,"h":0.5,"out":{"hex":"#006464ff","u16":[0,25651,25651,65535]},"y":0.1}, +{"a":0.5,"c":1.5,"h":0.5,"out":{"hex":"#00646480","u16":[0,25651,25651,32768]},"y":0.1}, +{"a":1,"c":1.5,"h":0.5,"out":{"hex":"#00cfcfff","u16":[0,53312,53312,65535]},"y":0.5}, +{"a":0.5,"c":1.5,"h":0.5,"out":{"hex":"#00cfcf80","u16":[0,53312,53312,32768]},"y":0.5}, +{"a":1,"c":1.5,"h":0.5,"out":{"hex":"#bfffffff","u16":[49092,65535,65535,65535]},"y":0.9}, +{"a":0.5,"c":1.5,"h":0.5,"out":{"hex":"#bfffff80","u16":[49092,65535,65535,32768]},"y":0.9}, +{"a":1,"c":1.5,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1.5,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1.5,"h":0.5,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1.5,"h":0.5,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":-0.5,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":-0.5,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":-0.5,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":-0.5,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":-0.5,"h":0.9,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":-0.5,"h":0.9,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":-0.5,"h":0.9,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":-0.5,"h":0.9,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":-0.5,"h":0.9,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":-0.5,"h":0.9,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":-0.5,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":-0.5,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":-0.5,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":-0.5,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0,"h":0.9,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":0,"h":0.9,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":0,"h":0.9,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":0,"h":0.9,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":0,"h":0.9,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":0,"h":0.9,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":0,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.25,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.25,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.25,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.25,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.25,"h":0.9,"out":{"hex":"#734f66ff","u16":[29497,20190,26250,65535]},"y":0.1}, +{"a":0.5,"c":0.25,"h":0.9,"out":{"hex":"#734f6680","u16":[29497,20190,26250,32768]},"y":0.1}, +{"a":1,"c":0.25,"h":0.9,"out":{"hex":"#ceb3c4ff","u16":[52929,45908,50262,65535]},"y":0.5}, +{"a":0.5,"c":0.25,"h":0.9,"out":{"hex":"#ceb3c480","u16":[52929,45908,50262,32768]},"y":0.5}, +{"a":1,"c":0.25,"h":0.9,"out":{"hex":"#f6f2f4ff","u16":[63253,62198,62834,65535]},"y":0.9}, +{"a":0.5,"c":0.25,"h":0.9,"out":{"hex":"#f6f2f480","u16":[63253,62198,62834,32768]},"y":0.9}, +{"a":1,"c":0.25,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.25,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.25,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.25,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.5,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.5,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.5,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.5,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.5,"h":0.9,"out":{"hex":"#874171ff","u16":[34604,16792,29069,65535]},"y":0.1}, +{"a":0.5,"c":0.5,"h":0.9,"out":{"hex":"#87417180","u16":[34604,16792,29069,32768]},"y":0.1}, +{"a":1,"c":0.5,"h":0.9,"out":{"hex":"#e0abcdff","u16":[57502,43892,52566,65535]},"y":0.5}, +{"a":0.5,"c":0.5,"h":0.9,"out":{"hex":"#e0abcd80","u16":[57502,43892,52566,32768]},"y":0.5}, +{"a":1,"c":0.5,"h":0.9,"out":{"hex":"#f9f1f6ff","u16":[64025,61925,63195,65535]},"y":0.9}, +{"a":0.5,"c":0.5,"h":0.9,"out":{"hex":"#f9f1f680","u16":[64025,61925,63195,32768]},"y":0.9}, +{"a":1,"c":0.5,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.5,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.5,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.5,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1,"h":0.9,"out":{"hex":"#a60084ff","u16":[42753,0,33895,65535]},"y":0.1}, +{"a":0.5,"c":1,"h":0.9,"out":{"hex":"#a6008480","u16":[42753,0,33895,32768]},"y":0.1}, +{"a":1,"c":1,"h":0.9,"out":{"hex":"#ff9addff","u16":[65535,39485,56843,65535]},"y":0.5}, +{"a":0.5,"c":1,"h":0.9,"out":{"hex":"#ff9add80","u16":[65535,39485,56843,32768]},"y":0.5}, +{"a":1,"c":1,"h":0.9,"out":{"hex":"#ffeff9ff","u16":[65535,61374,63909,65535]},"y":0.9}, +{"a":0.5,"c":1,"h":0.9,"out":{"hex":"#ffeff980","u16":[65535,61374,63909,32768]},"y":0.9}, +{"a":1,"c":1,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1.5,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1.5,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1.5,"h":0.9,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1.5,"h":0.9,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1.5,"h":0.9,"out":{"hex":"#a60084ff","u16":[42753,0,33895,65535]},"y":0.1}, +{"a":0.5,"c":1.5,"h":0.9,"out":{"hex":"#a6008480","u16":[42753,0,33895,32768]},"y":0.1}, +{"a":1,"c":1.5,"h":0.9,"out":{"hex":"#ff9addff","u16":[65535,39485,56843,65535]},"y":0.5}, +{"a":0.5,"c":1.5,"h":0.9,"out":{"hex":"#ff9add80","u16":[65535,39485,56843,32768]},"y":0.5}, +{"a":1,"c":1.5,"h":0.9,"out":{"hex":"#ffeff9ff","u16":[65535,61374,63909,65535]},"y":0.9}, +{"a":0.5,"c":1.5,"h":0.9,"out":{"hex":"#ffeff980","u16":[65535,61374,63909,32768]},"y":0.9}, +{"a":1,"c":1.5,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1.5,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1.5,"h":0.9,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1.5,"h":0.9,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":-0.5,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":-0.5,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":-0.5,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":-0.5,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":-0.5,"h":1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":-0.5,"h":1,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":-0.5,"h":1,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":-0.5,"h":1,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":-0.5,"h":1,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":-0.5,"h":1,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":-0.5,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":-0.5,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":-0.5,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":-0.5,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0,"h":1,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":0,"h":1,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":0,"h":1,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":0,"h":1,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":0,"h":1,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":0,"h":1,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":0,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.25,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.25,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.25,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.25,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.25,"h":1,"out":{"hex":"#794f4fff","u16":[30996,20190,20190,65535]},"y":0.1}, +{"a":0.5,"c":0.25,"h":1,"out":{"hex":"#794f4f80","u16":[30996,20190,20190,32768]},"y":0.1}, +{"a":1,"c":0.25,"h":1,"out":{"hex":"#ceb4b4ff","u16":[52929,46328,46328,65535]},"y":0.5}, +{"a":0.5,"c":0.25,"h":1,"out":{"hex":"#ceb4b480","u16":[52929,46328,46328,32768]},"y":0.5}, +{"a":1,"c":0.25,"h":1,"out":{"hex":"#f6f2f2ff","u16":[63253,62257,62257,65535]},"y":0.9}, +{"a":0.5,"c":0.25,"h":1,"out":{"hex":"#f6f2f280","u16":[63253,62257,62257,32768]},"y":0.9}, +{"a":1,"c":0.25,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.25,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.25,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.25,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.5,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.5,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.5,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.5,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.5,"h":1,"out":{"hex":"#904141ff","u16":[37051,16792,16792,65535]},"y":0.1}, +{"a":0.5,"c":0.5,"h":1,"out":{"hex":"#90414180","u16":[37051,16792,16792,32768]},"y":0.1}, +{"a":1,"c":0.5,"h":1,"out":{"hex":"#e0aeaeff","u16":[57502,44773,44773,65535]},"y":0.5}, +{"a":0.5,"c":0.5,"h":1,"out":{"hex":"#e0aeae80","u16":[57502,44773,44773,32768]},"y":0.5}, +{"a":1,"c":0.5,"h":1,"out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]},"y":0.9}, +{"a":0.5,"c":0.5,"h":1,"out":{"hex":"#f9f1f180","u16":[64025,62043,62043,32768]},"y":0.9}, +{"a":1,"c":0.5,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.5,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.5,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.5,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1,"h":1,"out":{"hex":"#b50000ff","u16":[46514,0,0,65535]},"y":0.1}, +{"a":0.5,"c":1,"h":1,"out":{"hex":"#b5000080","u16":[46514,0,0,32768]},"y":0.1}, +{"a":1,"c":1,"h":1,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]},"y":0.5}, +{"a":0.5,"c":1,"h":1,"out":{"hex":"#ffa1a180","u16":[65535,41449,41449,32768]},"y":0.5}, +{"a":1,"c":1,"h":1,"out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]},"y":0.9}, +{"a":0.5,"c":1,"h":1,"out":{"hex":"#fff0f080","u16":[65535,61611,61611,32768]},"y":0.9}, +{"a":1,"c":1,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1.5,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1.5,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1.5,"h":1,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1.5,"h":1,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1.5,"h":1,"out":{"hex":"#b50000ff","u16":[46514,0,0,65535]},"y":0.1}, +{"a":0.5,"c":1.5,"h":1,"out":{"hex":"#b5000080","u16":[46514,0,0,32768]},"y":0.1}, +{"a":1,"c":1.5,"h":1,"out":{"hex":"#ffa1a1ff","u16":[65535,41449,41449,65535]},"y":0.5}, +{"a":0.5,"c":1.5,"h":1,"out":{"hex":"#ffa1a180","u16":[65535,41449,41449,32768]},"y":0.5}, +{"a":1,"c":1.5,"h":1,"out":{"hex":"#fff0f0ff","u16":[65535,61611,61611,65535]},"y":0.9}, +{"a":0.5,"c":1.5,"h":1,"out":{"hex":"#fff0f080","u16":[65535,61611,61611,32768]},"y":0.9}, +{"a":1,"c":1.5,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1.5,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1.5,"h":1,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1.5,"h":1,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":-0.5,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":-0.5,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":-0.5,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":-0.5,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":-0.5,"h":1.25,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":-0.5,"h":1.25,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":-0.5,"h":1.25,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":-0.5,"h":1.25,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":-0.5,"h":1.25,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":-0.5,"h":1.25,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":-0.5,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":-0.5,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":-0.5,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":-0.5,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0,"h":1.25,"out":{"hex":"#5a5a5aff","u16":[23011,23011,23011,65535]},"y":0.1}, +{"a":0.5,"c":0,"h":1.25,"out":{"hex":"#5a5a5a80","u16":[23011,23011,23011,32768]},"y":0.1}, +{"a":1,"c":0,"h":1.25,"out":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"y":0.5}, +{"a":0.5,"c":0,"h":1.25,"out":{"hex":"#bababa80","u16":[47824,47824,47824,32768]},"y":0.5}, +{"a":1,"c":0,"h":1.25,"out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"y":0.9}, +{"a":0.5,"c":0,"h":1.25,"out":{"hex":"#f3f3f380","u16":[62470,62470,62470,32768]},"y":0.9}, +{"a":1,"c":0,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.25,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.25,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.25,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.25,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.25,"h":1.25,"out":{"hex":"#555c4fff","u16":[21958,23571,20190,65535]},"y":0.1}, +{"a":0.5,"c":0.25,"h":1.25,"out":{"hex":"#555c4f80","u16":[21958,23571,20190,32768]},"y":0.1}, +{"a":1,"c":0.25,"h":1.25,"out":{"hex":"#b2bfa3ff","u16":[45637,48987,41962,65535]},"y":0.5}, +{"a":0.5,"c":0.25,"h":1.25,"out":{"hex":"#b2bfa380","u16":[45637,48987,41962,32768]},"y":0.5}, +{"a":1,"c":0.25,"h":1.25,"out":{"hex":"#edf6e4ff","u16":[61030,63253,58705,65535]},"y":0.9}, +{"a":0.5,"c":0.25,"h":1.25,"out":{"hex":"#edf6e480","u16":[61030,63253,58705,32768]},"y":0.9}, +{"a":1,"c":0.25,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.25,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.25,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.25,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":0.5,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":0.5,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":0.5,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":0.5,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":0.5,"h":1.25,"out":{"hex":"#515e41ff","u16":[20842,24115,16792,65535]},"y":0.1}, +{"a":0.5,"c":0.5,"h":1.25,"out":{"hex":"#515e4180","u16":[20842,24115,16792,32768]},"y":0.1}, +{"a":1,"c":0.5,"h":1.25,"out":{"hex":"#a9c388ff","u16":[43317,50119,34899,65535]},"y":0.5}, +{"a":0.5,"c":0.5,"h":1.25,"out":{"hex":"#a9c38880","u16":[43317,50119,34899,32768]},"y":0.5}, +{"a":1,"c":0.5,"h":1.25,"out":{"hex":"#e8f9d5ff","u16":[59547,64025,54624,65535]},"y":0.9}, +{"a":0.5,"c":0.5,"h":1.25,"out":{"hex":"#e8f9d580","u16":[59547,64025,54624,32768]},"y":0.9}, +{"a":1,"c":0.5,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":0.5,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":0.5,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":0.5,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1,"h":1.25,"out":{"hex":"#476200ff","u16":[18362,25162,0,65535]},"y":0.1}, +{"a":0.5,"c":1,"h":1.25,"out":{"hex":"#47620080","u16":[18362,25162,0,32768]},"y":0.1}, +{"a":1,"c":1,"h":1.25,"out":{"hex":"#94cb00ff","u16":[38161,52295,0,65535]},"y":0.5}, +{"a":0.5,"c":1,"h":1.25,"out":{"hex":"#94cb0080","u16":[38161,52295,0,32768]},"y":0.5}, +{"a":1,"c":1,"h":1.25,"out":{"hex":"#dcffb0ff","u16":[56441,65535,45113,65535]},"y":0.9}, +{"a":0.5,"c":1,"h":1.25,"out":{"hex":"#dcffb080","u16":[56441,65535,45113,32768]},"y":0.9}, +{"a":1,"c":1,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1}, +{"a":1,"c":1.5,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":-0.1}, +{"a":0.5,"c":1.5,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":-0.1}, +{"a":1,"c":1.5,"h":1.25,"out":{"hex":"#000000ff","u16":[0,0,0,65535]},"y":0}, +{"a":0.5,"c":1.5,"h":1.25,"out":{"hex":"#00000080","u16":[0,0,0,32768]},"y":0}, +{"a":1,"c":1.5,"h":1.25,"out":{"hex":"#476200ff","u16":[18362,25162,0,65535]},"y":0.1}, +{"a":0.5,"c":1.5,"h":1.25,"out":{"hex":"#47620080","u16":[18362,25162,0,32768]},"y":0.1}, +{"a":1,"c":1.5,"h":1.25,"out":{"hex":"#94cb00ff","u16":[38161,52295,0,65535]},"y":0.5}, +{"a":0.5,"c":1.5,"h":1.25,"out":{"hex":"#94cb0080","u16":[38161,52295,0,32768]},"y":0.5}, +{"a":1,"c":1.5,"h":1.25,"out":{"hex":"#dcffb0ff","u16":[56441,65535,45113,65535]},"y":0.9}, +{"a":0.5,"c":1.5,"h":1.25,"out":{"hex":"#dcffb080","u16":[56441,65535,45113,32768]},"y":0.9}, +{"a":1,"c":1.5,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1}, +{"a":0.5,"c":1.5,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1}, +{"a":1,"c":1.5,"h":1.25,"out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"y":1.1}, +{"a":0.5,"c":1.5,"h":1.25,"out":{"hex":"#ffffff80","u16":[65535,65535,65535,32768]},"y":1.1} +], +"scheme_shade": [ +{"chroma":0,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#000000ff","out":{"hex":"#414141ff","u16":[16792,16792,16792,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#000000ff","out":{"hex":"#693030ff","u16":[27038,12254,12254,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#000000ff","out":{"hex":"#aeaeaeff","u16":[44655,44655,44655,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#000000ff","out":{"hex":"#db9f9fff","u16":[56266,40827,40827,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#000000ff","out":{"hex":"#dbdbdbff","u16":[56266,56266,56266,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#000000ff","out":{"hex":"#eed5d5ff","u16":[61112,54869,54869,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#000000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#000000ff","out":{"hex":"#343434ff","u16":[13312,13312,13312,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#000000ff","out":{"hex":"#532626ff","u16":[21435,9715,9715,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#000000ff","out":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#000000ff","out":{"hex":"#cf7272ff","u16":[53312,29400,29400,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#000000ff","out":{"hex":"#b1b1b1ff","u16":[45587,45587,45587,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#000000ff","out":{"hex":"#dca3a3ff","u16":[56623,42000,42000,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#000000ff","out":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#000000ff","out":{"hex":"#e8c7c7ff","u16":[59716,51163,51163,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#000000ff","out":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#000000ff","out":{"hex":"#e8c7c7ff","u16":[59716,51163,51163,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#000000ff","out":{"hex":"#1f1f1fff","u16":[8079,8079,8079,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#000000ff","out":{"hex":"#331717ff","u16":[13009,5896,5896,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#000000ff","out":{"hex":"#555555ff","u16":[21935,21935,21935,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#000000ff","out":{"hex":"#893e3eff","u16":[35319,16007,16007,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#000000ff","out":{"hex":"#6c6c6cff","u16":[27667,27667,27667,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#000000ff","out":{"hex":"#ad4f4fff","u16":[44550,20190,20190,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#000000ff","out":{"hex":"#7d7d7dff","u16":[32240,32240,32240,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#000000ff","out":{"hex":"#ca5c5cff","u16":[51912,23527,23527,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#000000ff","out":{"hex":"#7d7d7dff","u16":[32240,32240,32240,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#000000ff","out":{"hex":"#ca5c5cff","u16":[51912,23527,23527,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#000000ff","out":{"hex":"#2b2b2bff","u16":[11072,11072,11072,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#000000ff","out":{"hex":"#451f1fff","u16":[17827,8079,8079,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#000000ff","out":{"hex":"#757575ff","u16":[30058,30058,30058,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#000000ff","out":{"hex":"#bc5555ff","u16":[48399,21935,21935,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#000000ff","out":{"hex":"#949494ff","u16":[37914,37914,37914,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#000000ff","out":{"hex":"#d27c7cff","u16":[53881,31924,31924,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#000000ff","out":{"hex":"#acacacff","u16":[44180,44180,44180,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#000000ff","out":{"hex":"#da9d9dff","u16":[56087,40225,40225,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#000000ff","out":{"hex":"#acacacff","u16":[44180,44180,44180,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#000000ff","out":{"hex":"#da9d9dff","u16":[56087,40225,40225,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#000000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#000000ff","out":{"hex":"#343434ff","u16":[13312,13312,13312,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#000000ff","out":{"hex":"#532626ff","u16":[21435,9715,9715,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#000000ff","out":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#000000ff","out":{"hex":"#cf7272ff","u16":[53312,29400,29400,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#000000ff","out":{"hex":"#b1b1b1ff","u16":[45587,45587,45587,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#000000ff","out":{"hex":"#dca3a3ff","u16":[56623,42000,42000,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#000000ff","out":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#000000ff","out":{"hex":"#e8c7c7ff","u16":[59716,51163,51163,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#000000ff","out":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#000000ff","out":{"hex":"#e8c7c7ff","u16":[59716,51163,51163,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#ffffffff","out":{"hex":"#fafafaff","u16":[64330,64330,64330,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#ffffffff","out":{"hex":"#fdfafaff","u16":[64936,64165,64165,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#e6e6e6ff","u16":[59214,59214,59214,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#f3e3e3ff","u16":[62470,58297,58297,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#ecd0d0ff","u16":[60542,53381,53381,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#ffffffff","out":{"hex":"#c4c4c4ff","u16":[50352,50352,50352,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#ffffffff","out":{"hex":"#e4babaff","u16":[58536,47850,47850,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#c4c4c4ff","u16":[50352,50352,50352,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#e4babaff","u16":[58536,47850,47850,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#ffffffff","out":{"hex":"#fdfdfdff","u16":[64936,64936,64936,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#ffffffff","out":{"hex":"#fefcfcff","u16":[65236,64855,64855,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#ecececff","u16":[60542,60542,60542,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#f6e9e9ff","u16":[63098,59829,59829,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#ffffffff","out":{"hex":"#e4e4e4ff","u16":[58536,58536,58536,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#ffffffff","out":{"hex":"#f2e0e0ff","u16":[62154,57512,57512,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#e4e4e4ff","u16":[58536,58536,58536,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#f2e0e0ff","u16":[62154,57512,57512,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#ffffffff","out":{"hex":"#fafafaff","u16":[64330,64330,64330,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#ffffffff","out":{"hex":"#fdfafaff","u16":[64936,64165,64165,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#e6e6e6ff","u16":[59214,59214,59214,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#f3e3e3ff","u16":[62470,58297,58297,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#ecd0d0ff","u16":[60542,53381,53381,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#ffffffff","out":{"hex":"#c4c4c4ff","u16":[50352,50352,50352,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#ffffffff","out":{"hex":"#e4babaff","u16":[58536,47850,47850,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#c4c4c4ff","u16":[50352,50352,50352,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#e4babaff","u16":[58536,47850,47850,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#ffffffff","out":{"hex":"#f8f8f8ff","u16":[63717,63717,63717,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#ffffffff","out":{"hex":"#fbf7f7ff","u16":[64634,63467,63467,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#d9d9d9ff","u16":[55727,55727,55727,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#edd3d3ff","u16":[60868,54237,54237,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#bdbdbdff","u16":[48684,48684,48684,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#e1b2b2ff","u16":[57849,45826,45826,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#ffffffff","out":{"hex":"#9c9c9cff","u16":[40134,40134,40134,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#ffffffff","out":{"hex":"#d58888ff","u16":[54628,34956,34956,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#9c9c9cff","u16":[40134,40134,40134,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#d58888ff","u16":[54628,34956,34956,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#ffffffff","out":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#ffffffff","out":{"hex":"#f9f1f1ff","u16":[64025,62043,62043,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#c1c1c1ff","u16":[49526,49526,49526,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffffff","out":{"hex":"#e2b6b6ff","u16":[58194,46851,46851,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffffff","out":{"hex":"#cf7272ff","u16":[53312,29400,29400,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffffff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#010101ff","out":{"hex":"#414141ff","u16":[16793,16793,16793,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#010101ff","out":{"hex":"#693030ff","u16":[27039,12254,12254,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#010101ff","out":{"hex":"#aeaeaeff","u16":[44655,44655,44655,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#010101ff","out":{"hex":"#db9f9fff","u16":[56266,40827,40827,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#010101ff","out":{"hex":"#dbdbdbff","u16":[56267,56267,56267,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#010101ff","out":{"hex":"#eed6d6ff","u16":[61112,54870,54870,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#010101ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#010101ff","out":{"hex":"#343434ff","u16":[13313,13313,13313,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#010101ff","out":{"hex":"#532626ff","u16":[21437,9715,9715,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#010101ff","out":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#010101ff","out":{"hex":"#cf7272ff","u16":[53312,29401,29401,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#010101ff","out":{"hex":"#b1b1b1ff","u16":[45587,45587,45587,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#010101ff","out":{"hex":"#dca3a3ff","u16":[56623,42000,42000,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#010101ff","out":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#010101ff","out":{"hex":"#e8c7c7ff","u16":[59716,51163,51163,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#010101ff","out":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#010101ff","out":{"hex":"#e8c7c7ff","u16":[59716,51163,51163,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#010101ff","out":{"hex":"#1f1f1fff","u16":[8081,8081,8081,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#010101ff","out":{"hex":"#331717ff","u16":[13012,5897,5897,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#010101ff","out":{"hex":"#555555ff","u16":[21935,21935,21935,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#010101ff","out":{"hex":"#893e3eff","u16":[35320,16007,16007,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#010101ff","out":{"hex":"#6c6c6cff","u16":[27668,27668,27668,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#010101ff","out":{"hex":"#ad4f4fff","u16":[44550,20190,20190,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#010101ff","out":{"hex":"#7d7d7dff","u16":[32240,32240,32240,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#010101ff","out":{"hex":"#ca5c5cff","u16":[51912,23527,23527,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#010101ff","out":{"hex":"#7d7d7dff","u16":[32240,32240,32240,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#010101ff","out":{"hex":"#ca5c5cff","u16":[51912,23527,23527,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#010101ff","out":{"hex":"#2b2b2bff","u16":[11073,11073,11073,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#010101ff","out":{"hex":"#451f1fff","u16":[17829,8080,8080,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#010101ff","out":{"hex":"#757575ff","u16":[30058,30058,30058,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#010101ff","out":{"hex":"#bc5555ff","u16":[48400,21935,21935,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#010101ff","out":{"hex":"#949494ff","u16":[37914,37914,37914,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#010101ff","out":{"hex":"#d27c7cff","u16":[53881,31924,31924,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#010101ff","out":{"hex":"#acacacff","u16":[44180,44180,44180,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#010101ff","out":{"hex":"#da9d9dff","u16":[56087,40225,40225,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#010101ff","out":{"hex":"#acacacff","u16":[44180,44180,44180,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#010101ff","out":{"hex":"#da9d9dff","u16":[56087,40225,40225,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#010101ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#010101ff","out":{"hex":"#343434ff","u16":[13313,13313,13313,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#010101ff","out":{"hex":"#532626ff","u16":[21437,9715,9715,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#010101ff","out":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#010101ff","out":{"hex":"#cf7272ff","u16":[53312,29401,29401,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#010101ff","out":{"hex":"#b1b1b1ff","u16":[45587,45587,45587,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#010101ff","out":{"hex":"#dca3a3ff","u16":[56623,42000,42000,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#010101ff","out":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#010101ff","out":{"hex":"#e8c7c7ff","u16":[59716,51163,51163,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#010101ff","out":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#010101ff","out":{"hex":"#e8c7c7ff","u16":[59716,51163,51163,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#020202ff","out":{"hex":"#414141ff","u16":[16795,16795,16795,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#020202ff","out":{"hex":"#693030ff","u16":[27043,12256,12256,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#020202ff","out":{"hex":"#aeaeaeff","u16":[44656,44656,44656,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#020202ff","out":{"hex":"#db9f9fff","u16":[56267,40828,40828,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#020202ff","out":{"hex":"#dbdbdbff","u16":[56267,56267,56267,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#020202ff","out":{"hex":"#eed6d6ff","u16":[61112,54870,54870,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#020202ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#020202ff","out":{"hex":"#343434ff","u16":[13317,13317,13317,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#020202ff","out":{"hex":"#532626ff","u16":[21443,9718,9718,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#020202ff","out":{"hex":"#8d8d8dff","u16":[36143,36143,36143,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#020202ff","out":{"hex":"#cf7272ff","u16":[53312,29402,29402,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#020202ff","out":{"hex":"#b1b1b1ff","u16":[45588,45588,45588,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#020202ff","out":{"hex":"#dca3a3ff","u16":[56623,42001,42001,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#020202ff","out":{"hex":"#cfcfcfff","u16":[53122,53122,53122,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#020202ff","out":{"hex":"#e8c7c7ff","u16":[59716,51164,51164,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#020202ff","out":{"hex":"#cfcfcfff","u16":[53122,53122,53122,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#020202ff","out":{"hex":"#e8c7c7ff","u16":[59716,51164,51164,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#020202ff","out":{"hex":"#1f1f1fff","u16":[8088,8088,8088,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#020202ff","out":{"hex":"#331717ff","u16":[13023,5902,5902,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#020202ff","out":{"hex":"#555555ff","u16":[21937,21937,21937,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#020202ff","out":{"hex":"#893e3eff","u16":[35323,16008,16008,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#020202ff","out":{"hex":"#6c6c6cff","u16":[27669,27669,27669,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#020202ff","out":{"hex":"#ad4f4fff","u16":[44553,20191,20191,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#020202ff","out":{"hex":"#7d7d7dff","u16":[32241,32241,32241,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#020202ff","out":{"hex":"#ca5c5cff","u16":[51914,23528,23528,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#020202ff","out":{"hex":"#7d7d7dff","u16":[32241,32241,32241,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#020202ff","out":{"hex":"#ca5c5cff","u16":[51914,23528,23528,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#020202ff","out":{"hex":"#2b2b2bff","u16":[11078,11078,11078,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#020202ff","out":{"hex":"#451f1fff","u16":[17837,8084,8084,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#020202ff","out":{"hex":"#757575ff","u16":[30060,30060,30060,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#020202ff","out":{"hex":"#bc5555ff","u16":[48402,21936,21936,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#020202ff","out":{"hex":"#949494ff","u16":[37915,37915,37915,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#020202ff","out":{"hex":"#d27c7cff","u16":[53881,31926,31926,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#020202ff","out":{"hex":"#acacacff","u16":[44181,44181,44181,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#020202ff","out":{"hex":"#da9d9dff","u16":[56088,40226,40226,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#020202ff","out":{"hex":"#acacacff","u16":[44181,44181,44181,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#020202ff","out":{"hex":"#da9d9dff","u16":[56088,40226,40226,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#020202ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#020202ff","out":{"hex":"#343434ff","u16":[13317,13317,13317,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#020202ff","out":{"hex":"#532626ff","u16":[21443,9718,9718,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#020202ff","out":{"hex":"#8d8d8dff","u16":[36143,36143,36143,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#020202ff","out":{"hex":"#cf7272ff","u16":[53312,29402,29402,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#020202ff","out":{"hex":"#b1b1b1ff","u16":[45588,45588,45588,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#020202ff","out":{"hex":"#dca3a3ff","u16":[56623,42001,42001,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#020202ff","out":{"hex":"#cfcfcfff","u16":[53122,53122,53122,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#020202ff","out":{"hex":"#e8c7c7ff","u16":[59716,51164,51164,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#020202ff","out":{"hex":"#cfcfcfff","u16":[53122,53122,53122,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#020202ff","out":{"hex":"#e8c7c7ff","u16":[59716,51164,51164,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#040404ff","out":{"hex":"#414141ff","u16":[16808,16808,16808,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#040404ff","out":{"hex":"#693030ff","u16":[27064,12266,12266,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#040404ff","out":{"hex":"#aeaeaeff","u16":[44660,44660,44660,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#040404ff","out":{"hex":"#db9f9fff","u16":[56268,40833,40833,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#040404ff","out":{"hex":"#dbdbdbff","u16":[56270,56270,56270,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#040404ff","out":{"hex":"#eed6d6ff","u16":[61114,54874,54874,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#040404ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#040404ff","out":{"hex":"#343434ff","u16":[13334,13334,13334,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#040404ff","out":{"hex":"#542626ff","u16":[21470,9730,9730,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#040404ff","out":{"hex":"#8d8d8dff","u16":[36148,36148,36148,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#040404ff","out":{"hex":"#cf7272ff","u16":[53314,29410,29410,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#040404ff","out":{"hex":"#b1b1b1ff","u16":[45592,45592,45592,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#040404ff","out":{"hex":"#dca3a3ff","u16":[56625,42006,42006,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#040404ff","out":{"hex":"#cfcfcfff","u16":[53125,53125,53125,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#040404ff","out":{"hex":"#e8c7c7ff","u16":[59718,51168,51168,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#040404ff","out":{"hex":"#cfcfcfff","u16":[53125,53125,53125,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#040404ff","out":{"hex":"#e8c7c7ff","u16":[59718,51168,51168,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#040404ff","out":{"hex":"#202020ff","u16":[8119,8119,8119,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#040404ff","out":{"hex":"#331717ff","u16":[13073,5925,5925,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#040404ff","out":{"hex":"#555555ff","u16":[21946,21946,21946,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#040404ff","out":{"hex":"#8a3e3eff","u16":[35338,16015,16015,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#040404ff","out":{"hex":"#6c6c6cff","u16":[27676,27676,27676,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#040404ff","out":{"hex":"#ad4f4fff","u16":[44564,20197,20197,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#040404ff","out":{"hex":"#7d7d7dff","u16":[32247,32247,32247,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#040404ff","out":{"hex":"#ca5c5cff","u16":[51924,23532,23532,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#040404ff","out":{"hex":"#7d7d7dff","u16":[32247,32247,32247,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#040404ff","out":{"hex":"#ca5c5cff","u16":[51924,23532,23532,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#040404ff","out":{"hex":"#2b2b2bff","u16":[11099,11099,11099,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#040404ff","out":{"hex":"#462020ff","u16":[17871,8099,8099,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#040404ff","out":{"hex":"#757575ff","u16":[30066,30066,30066,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#040404ff","out":{"hex":"#bc5555ff","u16":[48412,21941,21941,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#040404ff","out":{"hex":"#949494ff","u16":[37920,37920,37920,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#040404ff","out":{"hex":"#d27c7cff","u16":[53883,31932,31932,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#040404ff","out":{"hex":"#acacacff","u16":[44185,44185,44185,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#040404ff","out":{"hex":"#da9d9dff","u16":[56089,40231,40231,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#040404ff","out":{"hex":"#acacacff","u16":[44185,44185,44185,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#040404ff","out":{"hex":"#da9d9dff","u16":[56089,40231,40231,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#040404ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#040404ff","out":{"hex":"#343434ff","u16":[13334,13334,13334,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#040404ff","out":{"hex":"#542626ff","u16":[21470,9730,9730,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#040404ff","out":{"hex":"#8d8d8dff","u16":[36148,36148,36148,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#040404ff","out":{"hex":"#cf7272ff","u16":[53314,29410,29410,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#040404ff","out":{"hex":"#b1b1b1ff","u16":[45592,45592,45592,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#040404ff","out":{"hex":"#dca3a3ff","u16":[56625,42006,42006,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#040404ff","out":{"hex":"#cfcfcfff","u16":[53125,53125,53125,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#040404ff","out":{"hex":"#e8c7c7ff","u16":[59718,51168,51168,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#040404ff","out":{"hex":"#cfcfcfff","u16":[53125,53125,53125,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#040404ff","out":{"hex":"#e8c7c7ff","u16":[59718,51168,51168,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#080808ff","out":{"hex":"#424242ff","u16":[16867,16867,16867,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#080808ff","out":{"hex":"#6a3030ff","u16":[27158,12308,12308,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#080808ff","out":{"hex":"#aeaeaeff","u16":[44678,44678,44678,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#080808ff","out":{"hex":"#db9f9fff","u16":[56275,40856,40856,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#080808ff","out":{"hex":"#dbdbdbff","u16":[56284,56284,56284,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#080808ff","out":{"hex":"#eed6d6ff","u16":[61120,54890,54890,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#080808ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#080808ff","out":{"hex":"#343434ff","u16":[13411,13411,13411,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#080808ff","out":{"hex":"#542626ff","u16":[21595,9787,9787,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#080808ff","out":{"hex":"#8d8d8dff","u16":[36171,36171,36171,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#080808ff","out":{"hex":"#cf7373ff","u16":[53321,29444,29444,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#080808ff","out":{"hex":"#b1b1b1ff","u16":[45610,45610,45610,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#080808ff","out":{"hex":"#dca4a4ff","u16":[56631,42028,42028,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#080808ff","out":{"hex":"#cfcfcfff","u16":[53140,53140,53140,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#080808ff","out":{"hex":"#e8c7c7ff","u16":[59724,51186,51186,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#080808ff","out":{"hex":"#cfcfcfff","u16":[53140,53140,53140,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#080808ff","out":{"hex":"#e8c7c7ff","u16":[59724,51186,51186,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#080808ff","out":{"hex":"#202020ff","u16":[8258,8258,8258,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#080808ff","out":{"hex":"#341717ff","u16":[13297,6026,6026,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#080808ff","out":{"hex":"#565656ff","u16":[21989,21989,21989,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#080808ff","out":{"hex":"#8a3e3eff","u16":[35406,16046,16046,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#080808ff","out":{"hex":"#6c6c6cff","u16":[27709,27709,27709,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#080808ff","out":{"hex":"#ae4f4fff","u16":[44616,20220,20220,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#080808ff","out":{"hex":"#7e7e7eff","u16":[32274,32274,32274,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#080808ff","out":{"hex":"#ca5c5cff","u16":[51967,23552,23552,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#080808ff","out":{"hex":"#7e7e7eff","u16":[32274,32274,32274,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#080808ff","out":{"hex":"#ca5c5cff","u16":[51967,23552,23552,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#080808ff","out":{"hex":"#2c2c2cff","u16":[11195,11195,11195,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#080808ff","out":{"hex":"#462020ff","u16":[18026,8169,8169,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#080808ff","out":{"hex":"#757575ff","u16":[30095,30095,30095,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#080808ff","out":{"hex":"#bd5555ff","u16":[48459,21962,21962,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#080808ff","out":{"hex":"#949494ff","u16":[37942,37942,37942,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#080808ff","out":{"hex":"#d27c7cff","u16":[53890,31963,31963,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#080808ff","out":{"hex":"#acacacff","u16":[44203,44203,44203,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#080808ff","out":{"hex":"#da9d9dff","u16":[56096,40255,40255,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#080808ff","out":{"hex":"#acacacff","u16":[44203,44203,44203,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#080808ff","out":{"hex":"#da9d9dff","u16":[56096,40255,40255,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#080808ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#080808ff","out":{"hex":"#343434ff","u16":[13411,13411,13411,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#080808ff","out":{"hex":"#542626ff","u16":[21595,9787,9787,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#080808ff","out":{"hex":"#8d8d8dff","u16":[36171,36171,36171,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#080808ff","out":{"hex":"#cf7373ff","u16":[53321,29444,29444,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#080808ff","out":{"hex":"#b1b1b1ff","u16":[45610,45610,45610,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#080808ff","out":{"hex":"#dca4a4ff","u16":[56631,42028,42028,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#080808ff","out":{"hex":"#cfcfcfff","u16":[53140,53140,53140,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#080808ff","out":{"hex":"#e8c7c7ff","u16":[59724,51186,51186,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#080808ff","out":{"hex":"#cfcfcfff","u16":[53140,53140,53140,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#080808ff","out":{"hex":"#e8c7c7ff","u16":[59724,51186,51186,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#101010ff","out":{"hex":"#434343ff","u16":[17133,17133,17133,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#101010ff","out":{"hex":"#6b3131ff","u16":[27587,12503,12503,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#101010ff","out":{"hex":"#aeaeaeff","u16":[44761,44761,44761,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#101010ff","out":{"hex":"#db9f9fff","u16":[56307,40961,40961,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#101010ff","out":{"hex":"#dbdbdbff","u16":[56347,56347,56347,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#101010ff","out":{"hex":"#eed6d6ff","u16":[61148,54964,54964,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#101010ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#101010ff","out":{"hex":"#363636ff","u16":[13760,13760,13760,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#101010ff","out":{"hex":"#562727ff","u16":[22156,10041,10041,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#101010ff","out":{"hex":"#8d8d8dff","u16":[36279,36279,36279,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#101010ff","out":{"hex":"#d07373ff","u16":[53355,29600,29600,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#101010ff","out":{"hex":"#b2b2b2ff","u16":[45691,45691,45691,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#101010ff","out":{"hex":"#dca4a4ff","u16":[56663,42130,42130,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#101010ff","out":{"hex":"#cfcfcfff","u16":[53207,53207,53207,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#101010ff","out":{"hex":"#e9c7c7ff","u16":[59754,51266,51266,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#101010ff","out":{"hex":"#cfcfcfff","u16":[53207,53207,53207,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#101010ff","out":{"hex":"#e9c7c7ff","u16":[59754,51266,51266,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#101010ff","out":{"hex":"#222222ff","u16":[8864,8864,8864,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#101010ff","out":{"hex":"#381919ff","u16":[14273,6469,6469,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#101010ff","out":{"hex":"#565656ff","u16":[22184,22184,22184,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#101010ff","out":{"hex":"#8b3f3fff","u16":[35720,16188,16188,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#101010ff","out":{"hex":"#6c6c6cff","u16":[27856,27856,27856,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#101010ff","out":{"hex":"#af4f4fff","u16":[44854,20328,20328,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#101010ff","out":{"hex":"#7e7e7eff","u16":[32397,32397,32397,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#101010ff","out":{"hex":"#cb5c5cff","u16":[52165,23642,23642,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#101010ff","out":{"hex":"#7e7e7eff","u16":[32397,32397,32397,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#101010ff","out":{"hex":"#cb5c5cff","u16":[52165,23642,23642,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#101010ff","out":{"hex":"#2d2d2dff","u16":[11625,11625,11625,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#101010ff","out":{"hex":"#492121ff","u16":[18718,8483,8483,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#101010ff","out":{"hex":"#767676ff","u16":[30229,30229,30229,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#101010ff","out":{"hex":"#bd5656ff","u16":[48675,22059,22059,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#101010ff","out":{"hex":"#949494ff","u16":[38044,38044,38044,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#101010ff","out":{"hex":"#d27d7dff","u16":[53923,32105,32105,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#101010ff","out":{"hex":"#acacacff","u16":[44288,44288,44288,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#101010ff","out":{"hex":"#da9d9dff","u16":[56128,40362,40362,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#101010ff","out":{"hex":"#acacacff","u16":[44288,44288,44288,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#101010ff","out":{"hex":"#da9d9dff","u16":[56128,40362,40362,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#101010ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#101010ff","out":{"hex":"#363636ff","u16":[13760,13760,13760,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#101010ff","out":{"hex":"#562727ff","u16":[22156,10041,10041,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#101010ff","out":{"hex":"#8d8d8dff","u16":[36279,36279,36279,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#101010ff","out":{"hex":"#d07373ff","u16":[53355,29600,29600,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#101010ff","out":{"hex":"#b2b2b2ff","u16":[45691,45691,45691,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#101010ff","out":{"hex":"#dca4a4ff","u16":[56663,42130,42130,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#101010ff","out":{"hex":"#cfcfcfff","u16":[53207,53207,53207,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#101010ff","out":{"hex":"#e9c7c7ff","u16":[59754,51266,51266,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#101010ff","out":{"hex":"#cfcfcfff","u16":[53207,53207,53207,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#101010ff","out":{"hex":"#e9c7c7ff","u16":[59754,51266,51266,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#202020ff","out":{"hex":"#2f2f2fff","u16":[12103,12103,12103,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#202020ff","out":{"hex":"#4c2222ff","u16":[19487,8832,8832,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#202020ff","out":{"hex":"#3c3c3cff","u16":[15351,15351,15351,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#202020ff","out":{"hex":"#602c2cff","u16":[24717,11202,11202,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#202020ff","out":{"hex":"#434343ff","u16":[17328,17328,17328,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#202020ff","out":{"hex":"#6d3131ff","u16":[27901,12645,12645,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#202020ff","out":{"hex":"#4a4a4aff","u16":[19066,19066,19066,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#202020ff","out":{"hex":"#773636ff","u16":[30699,13913,13913,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#202020ff","out":{"hex":"#4a4a4aff","u16":[19066,19066,19066,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#202020ff","out":{"hex":"#773636ff","u16":[30699,13913,13913,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#202020ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#202020ff","out":{"hex":"#282828ff","u16":[10369,10369,10369,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#202020ff","out":{"hex":"#411d1dff","u16":[16695,7566,7566,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#202020ff","out":{"hex":"#303030ff","u16":[12388,12388,12388,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#202020ff","out":{"hex":"#4e2323ff","u16":[19948,9040,9040,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#202020ff","out":{"hex":"#353535ff","u16":[13676,13676,13676,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#202020ff","out":{"hex":"#562727ff","u16":[22021,9980,9980,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#202020ff","out":{"hex":"#3a3a3aff","u16":[14832,14832,14832,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#202020ff","out":{"hex":"#5d2a2aff","u16":[23882,10824,10824,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#202020ff","out":{"hex":"#3a3a3aff","u16":[14832,14832,14832,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#202020ff","out":{"hex":"#5d2a2aff","u16":[23882,10824,10824,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#202020ff","out":{"hex":"#1f1f1fff","u16":[7956,7956,7956,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#202020ff","out":{"hex":"#321717ff","u16":[12810,5806,5806,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#202020ff","out":{"hex":"#1d1d1dff","u16":[7459,7459,7459,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#202020ff","out":{"hex":"#2f1515ff","u16":[12010,5443,5443,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#202020ff","out":{"hex":"#1c1c1cff","u16":[7248,7248,7248,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#202020ff","out":{"hex":"#2d1515ff","u16":[11671,5289,5289,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#202020ff","out":{"hex":"#1c1c1cff","u16":[7086,7086,7086,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#202020ff","out":{"hex":"#2c1414ff","u16":[11409,5171,5171,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#202020ff","out":{"hex":"#1b1b1bff","u16":[6918,6918,6918,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#202020ff","out":{"hex":"#2b1414ff","u16":[11140,5048,5048,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#202020ff","out":{"hex":"#1b1b1bff","u16":[6918,6918,6918,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#202020ff","out":{"hex":"#2b1414ff","u16":[11140,5048,5048,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#202020ff","out":{"hex":"#1d1d1dff","u16":[7431,7431,7431,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#202020ff","out":{"hex":"#2f1515ff","u16":[11965,5423,5423,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#202020ff","out":{"hex":"#161616ff","u16":[5721,5721,5721,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#202020ff","out":{"hex":"#241010ff","u16":[9211,4175,4175,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#202020ff","out":{"hex":"#131313ff","u16":[4829,4829,4829,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#202020ff","out":{"hex":"#1e0e0eff","u16":[7776,3524,3524,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#202020ff","out":{"hex":"#101010ff","u16":[4002,4002,4002,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#202020ff","out":{"hex":"#190b0bff","u16":[6443,2920,2920,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#202020ff","out":{"hex":"#0b0b0bff","u16":[2888,2888,2888,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#202020ff","out":{"hex":"#120808ff","u16":[4650,2107,2107,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#202020ff","out":{"hex":"#0b0b0bff","u16":[2888,2888,2888,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#202020ff","out":{"hex":"#120808ff","u16":[4650,2107,2107,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#202020ff","out":{"hex":"#151515ff","u16":[5407,5407,5407,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#202020ff","out":{"hex":"#220f0fff","u16":[8707,3946,3946,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#202020ff","out":{"hex":"#101010ff","u16":[4163,4163,4163,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#202020ff","out":{"hex":"#1a0c0cff","u16":[6703,3038,3038,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#202020ff","out":{"hex":"#0e0e0eff","u16":[3514,3514,3514,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#202020ff","out":{"hex":"#160a0aff","u16":[5658,2564,2564,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#202020ff","out":{"hex":"#0b0b0bff","u16":[2912,2912,2912,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#202020ff","out":{"hex":"#120808ff","u16":[4688,2125,2125,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#202020ff","out":{"hex":"#080808ff","u16":[2102,2102,2102,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#202020ff","out":{"hex":"#0d0606ff","u16":[3384,1533,1533,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#202020ff","out":{"hex":"#080808ff","u16":[2102,2102,2102,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#202020ff","out":{"hex":"#0d0606ff","u16":[3384,1533,1533,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#404040ff","out":{"hex":"#1f1f1fff","u16":[7945,7945,7945,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#404040ff","out":{"hex":"#321717ff","u16":[12793,5798,5798,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#404040ff","out":{"hex":"#4b4b4bff","u16":[19161,19161,19161,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#404040ff","out":{"hex":"#783636ff","u16":[30852,13982,13982,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#404040ff","out":{"hex":"#555555ff","u16":[21904,21904,21904,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#404040ff","out":{"hex":"#893e3eff","u16":[35270,15985,15985,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#404040ff","out":{"hex":"#5c5c5cff","u16":[23718,23718,23718,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#404040ff","out":{"hex":"#954343ff","u16":[38191,17308,17308,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#404040ff","out":{"hex":"#636363ff","u16":[25379,25379,25379,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#404040ff","out":{"hex":"#9f4848ff","u16":[40865,18520,18520,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#404040ff","out":{"hex":"#636363ff","u16":[25379,25379,25379,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#404040ff","out":{"hex":"#9f4848ff","u16":[40865,18520,18520,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#404040ff","out":{"hex":"#333333ff","u16":[13179,13179,13179,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#404040ff","out":{"hex":"#532525ff","u16":[21221,9617,9617,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#404040ff","out":{"hex":"#454545ff","u16":[17821,17821,17821,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#404040ff","out":{"hex":"#703333ff","u16":[28695,13005,13005,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#404040ff","out":{"hex":"#4b4b4bff","u16":[19317,19317,19317,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#404040ff","out":{"hex":"#793737ff","u16":[31104,14096,14096,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#404040ff","out":{"hex":"#4f4f4fff","u16":[20354,20354,20354,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#404040ff","out":{"hex":"#803a3aff","u16":[32773,14853,14853,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#404040ff","out":{"hex":"#535353ff","u16":[21331,21331,21331,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#404040ff","out":{"hex":"#863d3dff","u16":[34346,15566,15566,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#404040ff","out":{"hex":"#535353ff","u16":[21331,21331,21331,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#404040ff","out":{"hex":"#863d3dff","u16":[34346,15566,15566,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#404040ff","out":{"hex":"#3e3e3eff","u16":[15903,15903,15903,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#404040ff","out":{"hex":"#642d2dff","u16":[25607,11605,11605,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#404040ff","out":{"hex":"#3a3a3aff","u16":[14892,14892,14892,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#404040ff","out":{"hex":"#5d2a2aff","u16":[23978,10867,10867,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#404040ff","out":{"hex":"#383838ff","u16":[14463,14463,14463,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#404040ff","out":{"hex":"#5b2929ff","u16":[23288,10554,10554,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#404040ff","out":{"hex":"#373737ff","u16":[14131,14131,14131,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#404040ff","out":{"hex":"#592828ff","u16":[22754,10312,10312,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#404040ff","out":{"hex":"#363636ff","u16":[13790,13790,13790,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#404040ff","out":{"hex":"#562727ff","u16":[22204,10063,10063,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#404040ff","out":{"hex":"#363636ff","u16":[13790,13790,13790,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#404040ff","out":{"hex":"#562727ff","u16":[22204,10063,10063,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#404040ff","out":{"hex":"#3a3a3aff","u16":[14862,14862,14862,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#404040ff","out":{"hex":"#5d2a2aff","u16":[23930,10845,10845,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#404040ff","out":{"hex":"#2d2d2dff","u16":[11441,11441,11441,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#404040ff","out":{"hex":"#482020ff","u16":[18423,8349,8349,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#404040ff","out":{"hex":"#262626ff","u16":[9659,9659,9659,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#404040ff","out":{"hex":"#3d1b1bff","u16":[15552,7048,7048,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#404040ff","out":{"hex":"#1f1f1fff","u16":[8003,8003,8003,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#404040ff","out":{"hex":"#321717ff","u16":[12887,5840,5840,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#404040ff","out":{"hex":"#161616ff","u16":[5775,5775,5775,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#404040ff","out":{"hex":"#241010ff","u16":[9299,4214,4214,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#404040ff","out":{"hex":"#161616ff","u16":[5775,5775,5775,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#404040ff","out":{"hex":"#241010ff","u16":[9299,4214,4214,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#404040ff","out":{"hex":"#2a2a2aff","u16":[10703,10703,10703,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#404040ff","out":{"hex":"#431e1eff","u16":[17233,7810,7810,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#404040ff","out":{"hex":"#202020ff","u16":[8239,8239,8239,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#404040ff","out":{"hex":"#341717ff","u16":[13267,6013,6013,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#404040ff","out":{"hex":"#1b1b1bff","u16":[6956,6956,6956,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#404040ff","out":{"hex":"#2c1414ff","u16":[11200,5076,5076,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#404040ff","out":{"hex":"#161616ff","u16":[5763,5763,5763,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#404040ff","out":{"hex":"#241010ff","u16":[9281,4206,4206,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#404040ff","out":{"hex":"#101010ff","u16":[4159,4159,4159,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#404040ff","out":{"hex":"#1a0c0cff","u16":[6697,3035,3035,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#404040ff","out":{"hex":"#101010ff","u16":[4159,4159,4159,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#404040ff","out":{"hex":"#1a0c0cff","u16":[6697,3035,3035,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#656565ff","u16":[26054,26054,26054,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#a34a4aff","u16":[41952,19013,19013,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#8a8a8aff","u16":[35398,35398,35398,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#cf6e6eff","u16":[53081,28308,28308,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#959595ff","u16":[38401,38401,38401,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#d27f7fff","u16":[54041,32599,32599,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#9e9e9eff","u16":[40480,40480,40480,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#d58a8aff","u16":[54748,35418,35418,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#a5a5a5ff","u16":[42438,42438,42438,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#d89494ff","u16":[55444,37991,37991,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#a5a5a5ff","u16":[42438,42438,42438,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#d89494ff","u16":[55444,37991,37991,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#757575ff","u16":[30055,30055,30055,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#bc5555ff","u16":[48395,21933,21933,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#848484ff","u16":[33843,33843,33843,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#cd6565ff","u16":[52613,25944,25944,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#898989ff","u16":[35223,35223,35223,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#ce6d6dff","u16":[53028,28049,28049,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#8d8d8dff","u16":[36217,36217,36217,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#d07373ff","u16":[53336,29511,29511,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#919191ff","u16":[37180,37180,37180,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#d17878ff","u16":[53642,30891,30891,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#919191ff","u16":[37180,37180,37180,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#d17878ff","u16":[53642,30891,30891,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#7a7a7aff","u16":[31480,31480,31480,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#c55959ff","u16":[50688,22972,22972,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#727272ff","u16":[29318,29318,29318,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#b85353ff","u16":[47207,21394,21394,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#6e6e6eff","u16":[28397,28397,28397,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#b25151ff","u16":[45724,20722,20722,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#6c6c6cff","u16":[27682,27682,27682,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#ad4f4fff","u16":[44573,20201,20201,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#696969ff","u16":[26945,26945,26945,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#a94d4dff","u16":[43386,19663,19663,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#696969ff","u16":[26945,26945,26945,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#a94d4dff","u16":[43386,19663,19663,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#737373ff","u16":[29491,29491,29491,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#b95454ff","u16":[47486,21521,21521,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#585858ff","u16":[22704,22704,22704,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#8e4040ff","u16":[36558,16568,16568,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#4b4b4bff","u16":[19166,19166,19166,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#783636ff","u16":[30861,13986,13986,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#3e3e3eff","u16":[15882,15882,15882,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#642d2dff","u16":[25572,11590,11590,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#2d2d2dff","u16":[11460,11460,11460,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#482121ff","u16":[18453,8363,8363,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#2d2d2dff","u16":[11460,11460,11460,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#482121ff","u16":[18453,8363,8363,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#4f4f4fff","u16":[20206,20206,20206,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#7f7f7fff","out":{"hex":"#7f3939ff","u16":[32536,14746,14746,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#3d3d3dff","u16":[15556,15556,15556,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#7f7f7fff","out":{"hex":"#612c2cff","u16":[25049,11352,11352,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#333333ff","u16":[13132,13132,13132,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f7f7fff","out":{"hex":"#522525ff","u16":[21145,9583,9583,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#2a2a2aff","u16":[10882,10882,10882,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f7f7fff","out":{"hex":"#441f1fff","u16":[17521,7941,7941,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#1f1f1fff","u16":[7852,7852,7852,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#7f7f7fff","out":{"hex":"#311616ff","u16":[12643,5730,5730,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#1f1f1fff","u16":[7852,7852,7852,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f7f7fff","out":{"hex":"#311616ff","u16":[12643,5730,5730,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#808080ff","out":{"hex":"#666666ff","u16":[26298,26298,26298,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#808080ff","out":{"hex":"#a54b4bff","u16":[42345,19191,19191,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#808080ff","out":{"hex":"#8b8b8bff","u16":[35663,35663,35663,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#808080ff","out":{"hex":"#cf7070ff","u16":[53163,28701,28701,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#808080ff","out":{"hex":"#969696ff","u16":[38676,38676,38676,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#808080ff","out":{"hex":"#d38080ff","u16":[54133,32979,32979,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#808080ff","out":{"hex":"#9f9f9fff","u16":[40763,40763,40763,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#808080ff","out":{"hex":"#d58b8bff","u16":[54847,35795,35795,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#808080ff","out":{"hex":"#a6a6a6ff","u16":[42729,42729,42729,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#808080ff","out":{"hex":"#d89595ff","u16":[55550,38367,38367,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#808080ff","out":{"hex":"#a6a6a6ff","u16":[42729,42729,42729,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#808080ff","out":{"hex":"#d89595ff","u16":[55550,38367,38367,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#808080ff","out":{"hex":"#767676ff","u16":[30314,30314,30314,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#808080ff","out":{"hex":"#be5656ff","u16":[48812,22122,22122,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#808080ff","out":{"hex":"#858585ff","u16":[34100,34100,34100,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#808080ff","out":{"hex":"#cd6767ff","u16":[52689,26343,26343,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#808080ff","out":{"hex":"#8a8a8aff","u16":[35480,35480,35480,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#808080ff","out":{"hex":"#cf6f6fff","u16":[53107,28431,28431,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#808080ff","out":{"hex":"#8e8e8eff","u16":[36475,36475,36475,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#808080ff","out":{"hex":"#d07474ff","u16":[53417,29883,29883,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#808080ff","out":{"hex":"#929292ff","u16":[37438,37438,37438,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#808080ff","out":{"hex":"#d17a7aff","u16":[53725,31256,31256,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#808080ff","out":{"hex":"#929292ff","u16":[37438,37438,37438,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#808080ff","out":{"hex":"#d17a7aff","u16":[53725,31256,31256,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#808080ff","out":{"hex":"#7b7b7bff","u16":[31726,31726,31726,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#808080ff","out":{"hex":"#c75a5aff","u16":[51084,23152,23152,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#808080ff","out":{"hex":"#737373ff","u16":[29543,29543,29543,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#808080ff","out":{"hex":"#b95454ff","u16":[47570,21559,21559,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#808080ff","out":{"hex":"#6f6f6fff","u16":[28614,28614,28614,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#808080ff","out":{"hex":"#b35151ff","u16":[46073,20881,20881,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#808080ff","out":{"hex":"#6d6d6dff","u16":[27892,27892,27892,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#808080ff","out":{"hex":"#af4f4fff","u16":[44911,20354,20354,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#808080ff","out":{"hex":"#6a6a6aff","u16":[27147,27147,27147,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#808080ff","out":{"hex":"#aa4d4dff","u16":[43712,19810,19810,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#808080ff","out":{"hex":"#6a6a6aff","u16":[27147,27147,27147,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#808080ff","out":{"hex":"#aa4d4dff","u16":[43712,19810,19810,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#808080ff","out":{"hex":"#747474ff","u16":[29723,29723,29723,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#808080ff","out":{"hex":"#ba5454ff","u16":[47859,21690,21690,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#808080ff","out":{"hex":"#595959ff","u16":[22883,22883,22883,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#808080ff","out":{"hex":"#8f4141ff","u16":[36846,16699,16699,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#808080ff","out":{"hex":"#4b4b4bff","u16":[19317,19317,19317,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#808080ff","out":{"hex":"#793737ff","u16":[31104,14097,14097,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#808080ff","out":{"hex":"#3e3e3eff","u16":[16007,16007,16007,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#808080ff","out":{"hex":"#642d2dff","u16":[25774,11681,11681,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#808080ff","out":{"hex":"#2d2d2dff","u16":[11550,11550,11550,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#808080ff","out":{"hex":"#482121ff","u16":[18598,8429,8429,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#808080ff","out":{"hex":"#2d2d2dff","u16":[11550,11550,11550,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#808080ff","out":{"hex":"#482121ff","u16":[18598,8429,8429,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#808080ff","out":{"hex":"#4f4f4fff","u16":[20341,20341,20341,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#808080ff","out":{"hex":"#7f3a3aff","u16":[32753,14844,14844,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#808080ff","out":{"hex":"#3d3d3dff","u16":[15660,15660,15660,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#808080ff","out":{"hex":"#622c2cff","u16":[25216,11428,11428,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#808080ff","out":{"hex":"#333333ff","u16":[13220,13220,13220,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#808080ff","out":{"hex":"#532626ff","u16":[21287,9648,9648,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#808080ff","out":{"hex":"#2b2b2bff","u16":[10955,10955,10955,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#808080ff","out":{"hex":"#451f1fff","u16":[17639,7994,7994,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#808080ff","out":{"hex":"#1f1f1fff","u16":[7904,7904,7904,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#808080ff","out":{"hex":"#321616ff","u16":[12728,5769,5769,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#808080ff","out":{"hex":"#1f1f1fff","u16":[7904,7904,7904,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#808080ff","out":{"hex":"#321616ff","u16":[12728,5769,5769,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#818181ff","out":{"hex":"#676767ff","u16":[26542,26542,26542,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#818181ff","out":{"hex":"#a64b4bff","u16":[42737,19369,19369,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#818181ff","out":{"hex":"#8c8c8cff","u16":[35928,35928,35928,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#818181ff","out":{"hex":"#cf7171ff","u16":[53245,29090,29090,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#818181ff","out":{"hex":"#989898ff","u16":[38952,38952,38952,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#818181ff","out":{"hex":"#d38282ff","u16":[54225,33357,33357,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#818181ff","out":{"hex":"#a0a0a0ff","u16":[41047,41047,41047,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#818181ff","out":{"hex":"#d68d8dff","u16":[54947,36170,36170,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#818181ff","out":{"hex":"#a7a7a7ff","u16":[43020,43020,43020,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#818181ff","out":{"hex":"#d99797ff","u16":[55657,38742,38742,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#818181ff","out":{"hex":"#a7a7a7ff","u16":[43020,43020,43020,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#818181ff","out":{"hex":"#d99797ff","u16":[55657,38742,38742,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#818181ff","out":{"hex":"#777777ff","u16":[30573,30573,30573,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#818181ff","out":{"hex":"#c05757ff","u16":[49229,22311,22311,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#818181ff","out":{"hex":"#868686ff","u16":[34357,34357,34357,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#818181ff","out":{"hex":"#cd6868ff","u16":[52766,26738,26738,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#818181ff","out":{"hex":"#8b8b8bff","u16":[35737,35737,35737,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#818181ff","out":{"hex":"#cf7070ff","u16":[53186,28810,28810,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#818181ff","out":{"hex":"#8f8f8fff","u16":[36732,36732,36732,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#818181ff","out":{"hex":"#d07676ff","u16":[53499,30253,30253,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#818181ff","out":{"hex":"#939393ff","u16":[37696,37696,37696,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#818181ff","out":{"hex":"#d17b7bff","u16":[53809,31619,31619,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#818181ff","out":{"hex":"#939393ff","u16":[37696,37696,37696,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#818181ff","out":{"hex":"#d17b7bff","u16":[53809,31619,31619,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#818181ff","out":{"hex":"#7c7c7cff","u16":[31972,31972,31972,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#818181ff","out":{"hex":"#c85b5bff","u16":[51481,23331,23331,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#818181ff","out":{"hex":"#747474ff","u16":[29769,29769,29769,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#818181ff","out":{"hex":"#bb5555ff","u16":[47933,21723,21723,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#818181ff","out":{"hex":"#707070ff","u16":[28830,28830,28830,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#818181ff","out":{"hex":"#b55252ff","u16":[46422,21039,21039,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#818181ff","out":{"hex":"#6d6d6dff","u16":[28102,28102,28102,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#818181ff","out":{"hex":"#b05050ff","u16":[45249,20507,20507,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#818181ff","out":{"hex":"#6a6a6aff","u16":[27350,27350,27350,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#818181ff","out":{"hex":"#ab4e4eff","u16":[44038,19958,19958,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#818181ff","out":{"hex":"#6a6a6aff","u16":[27350,27350,27350,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#818181ff","out":{"hex":"#ab4e4eff","u16":[44038,19958,19958,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#818181ff","out":{"hex":"#757575ff","u16":[29955,29955,29955,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#818181ff","out":{"hex":"#bc5555ff","u16":[48233,21860,21860,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#818181ff","out":{"hex":"#5a5a5aff","u16":[23062,23062,23062,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#818181ff","out":{"hex":"#904141ff","u16":[37134,16829,16829,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#818181ff","out":{"hex":"#4c4c4cff","u16":[19468,19468,19468,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#818181ff","out":{"hex":"#7a3737ff","u16":[31347,14207,14207,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#818181ff","out":{"hex":"#3f3f3fff","u16":[16132,16132,16132,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#818181ff","out":{"hex":"#652e2eff","u16":[25975,11772,11772,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#818181ff","out":{"hex":"#2d2d2dff","u16":[11641,11641,11641,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#818181ff","out":{"hex":"#492121ff","u16":[18744,8495,8495,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#818181ff","out":{"hex":"#2d2d2dff","u16":[11641,11641,11641,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#818181ff","out":{"hex":"#492121ff","u16":[18744,8495,8495,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#818181ff","out":{"hex":"#505050ff","u16":[20476,20476,20476,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#818181ff","out":{"hex":"#803a3aff","u16":[32970,14942,14942,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#818181ff","out":{"hex":"#3d3d3dff","u16":[15764,15764,15764,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#818181ff","out":{"hex":"#632d2dff","u16":[25383,11503,11503,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#818181ff","out":{"hex":"#343434ff","u16":[13307,13307,13307,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#818181ff","out":{"hex":"#532626ff","u16":[21427,9711,9711,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#818181ff","out":{"hex":"#2b2b2bff","u16":[11027,11027,11027,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#818181ff","out":{"hex":"#451f1fff","u16":[17755,8047,8047,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#818181ff","out":{"hex":"#1f1f1fff","u16":[7957,7957,7957,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#818181ff","out":{"hex":"#321717ff","u16":[12812,5807,5807,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#818181ff","out":{"hex":"#1f1f1fff","u16":[7957,7957,7957,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#818181ff","out":{"hex":"#321717ff","u16":[12812,5807,5807,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#a0a0a0ff","u16":[41133,41133,41133,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#d68d8dff","u16":[54977,36284,36284,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#ccccccff","u16":[52536,52536,52536,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#e7c4c4ff","u16":[59463,50467,50467,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#dbdbdbff","u16":[56345,56345,56345,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#eed6d6ff","u16":[61148,54961,54961,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#e6e6e6ff","u16":[59011,59011,59011,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#f3e2e2ff","u16":[62375,58062,58062,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#efefefff","u16":[61540,61540,61540,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#f7ededff","u16":[63575,60976,60976,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#efefefff","u16":[61540,61540,61540,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#f7ededff","u16":[63575,60976,60976,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#b6b6b6ff","u16":[46759,46759,46759,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#dea9a9ff","u16":[57079,43459,43459,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#c3c3c3ff","u16":[50203,50203,50203,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#e4b9b9ff","u16":[58474,47670,47670,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#c8c8c8ff","u16":[51504,51504,51504,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#e6c0c0ff","u16":[59021,49235,49235,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#ccccccff","u16":[52454,52454,52454,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#e7c4c4ff","u16":[59428,50370,50370,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#d0d0d0ff","u16":[53384,53384,53384,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#e9c8c8ff","u16":[59831,51476,51476,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#d0d0d0ff","u16":[53384,53384,53384,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#e9c8c8ff","u16":[59831,51476,51476,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#b7b7b7ff","u16":[47124,47124,47124,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#dfababff","u16":[57223,43911,43911,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#a9a9a9ff","u16":[43429,43429,43429,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#d99999ff","u16":[55807,39267,39267,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#a3a3a3ff","u16":[41840,41840,41840,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#d79191ff","u16":[55229,37213,37213,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#9e9e9eff","u16":[40600,40600,40600,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#d58a8aff","u16":[54790,35578,35578,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#999999ff","u16":[39312,39312,39312,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#d38484ff","u16":[54347,33847,33847,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#999999ff","u16":[39312,39312,39312,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#d38484ff","u16":[54347,33847,33847,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#adadadff","u16":[44352,44352,44352,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#da9d9dff","u16":[56152,40444,40444,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#858585ff","u16":[34146,34146,34146,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#cd6767ff","u16":[52703,26414,26414,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#707070ff","u16":[28825,28825,28825,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#b55252ff","u16":[46413,21035,21035,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#5d5d5dff","u16":[23885,23885,23885,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#964444ff","u16":[38459,17430,17430,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#434343ff","u16":[17235,17235,17235,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#6c3131ff","u16":[27752,12577,12577,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#434343ff","u16":[17235,17235,17235,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#6c3131ff","u16":[27752,12577,12577,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#6a6a6aff","u16":[27203,27203,27203,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#bfbfbfff","out":{"hex":"#aa4d4dff","u16":[43802,19851,19851,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#515151ff","u16":[20943,20943,20943,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#bfbfbfff","out":{"hex":"#833b3bff","u16":[33722,15283,15283,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#454545ff","u16":[17679,17679,17679,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#bfbfbfff","out":{"hex":"#6f3232ff","u16":[28467,12902,12902,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#393939ff","u16":[14650,14650,14650,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#bfbfbfff","out":{"hex":"#5c2a2aff","u16":[23588,10690,10690,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#292929ff","u16":[10571,10571,10571,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#bfbfbfff","out":{"hex":"#421e1eff","u16":[17021,7714,7714,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#292929ff","u16":[10571,10571,10571,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#bfbfbfff","out":{"hex":"#421e1eff","u16":[17021,7714,7714,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#a1a1a1ff","u16":[41363,41363,41363,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#d68e8eff","u16":[55059,36588,36588,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#cdcdcdff","u16":[52805,52805,52805,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#e8c6c6ff","u16":[59579,50788,50788,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#dcdcdcff","u16":[56629,56629,56629,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#eed7d7ff","u16":[61276,55293,55293,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#e7e7e7ff","u16":[59305,59305,59305,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#f3e3e3ff","u16":[62513,58402,58402,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#f1f1f1ff","u16":[61843,61843,61843,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#f8efefff","u16":[63721,61324,61324,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#f1f1f1ff","u16":[61843,61843,61843,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#f8efefff","u16":[63721,61324,61324,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#b7b7b7ff","u16":[47024,47024,47024,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#dfaaaaff","u16":[57184,43787,43787,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#c4c4c4ff","u16":[50456,50456,50456,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#e4bbbbff","u16":[58580,47976,47976,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#c9c9c9ff","u16":[51753,51753,51753,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#e6c1c1ff","u16":[59128,49534,49534,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#cdcdcdff","u16":[52701,52701,52701,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#e8c5c5ff","u16":[59534,50665,50665,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#d1d1d1ff","u16":[53629,53629,53629,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#e9c9c9ff","u16":[59938,51766,51766,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#d1d1d1ff","u16":[53629,53629,53629,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#e9c9c9ff","u16":[59938,51766,51766,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#b8b8b8ff","u16":[47366,47366,47366,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#dfacacff","u16":[57319,44210,44210,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#aaaaaaff","u16":[43643,43643,43643,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#d99a9aff","u16":[55887,39541,39541,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#a4a4a4ff","u16":[42042,42042,42042,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#d79292ff","u16":[55301,37476,37476,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#9f9f9fff","u16":[40792,40792,40792,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#d58b8bff","u16":[54857,35833,35833,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#9a9a9aff","u16":[39493,39493,39493,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#d48585ff","u16":[54409,34094,34094,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#9a9a9aff","u16":[39493,39493,39493,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#d48585ff","u16":[54409,34094,34094,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#adadadff","u16":[44585,44585,44585,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#db9f9fff","u16":[56240,40738,40738,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#868686ff","u16":[34324,34324,34324,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#cd6868ff","u16":[52756,26689,26689,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#717171ff","u16":[28976,28976,28976,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#b65252ff","u16":[46656,21145,21145,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#5d5d5dff","u16":[24010,24010,24010,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#964444ff","u16":[38661,17521,17521,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#434343ff","u16":[17326,17326,17326,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#6d3131ff","u16":[27897,12643,12643,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#434343ff","u16":[17326,17326,17326,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#6d3131ff","u16":[27897,12643,12643,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#6a6a6aff","u16":[27279,27279,27279,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#c0c0c0ff","out":{"hex":"#ab4d4dff","u16":[43923,19906,19906,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#525252ff","u16":[21001,21001,21001,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#c0c0c0ff","out":{"hex":"#843c3cff","u16":[33815,15325,15325,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#454545ff","u16":[17728,17728,17728,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#c0c0c0ff","out":{"hex":"#6f3232ff","u16":[28546,12937,12937,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#393939ff","u16":[14690,14690,14690,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#c0c0c0ff","out":{"hex":"#5c2a2aff","u16":[23654,10720,10720,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#292929ff","u16":[10601,10601,10601,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#c0c0c0ff","out":{"hex":"#421e1eff","u16":[17068,7735,7735,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#292929ff","u16":[10601,10601,10601,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#c0c0c0ff","out":{"hex":"#421e1eff","u16":[17068,7735,7735,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#f8f8f8ff","u16":[63805,63805,63805,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#fcf7f7ff","u16":[64677,63567,63567,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#e4e4e4ff","u16":[58633,58633,58633,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#f2e0e0ff","u16":[62199,57624,57624,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#d4d4d4ff","u16":[54361,54361,54361,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#eacdcdff","u16":[60260,52632,52632,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#c1c1c1ff","u16":[49644,49644,49644,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#e3b7b7ff","u16":[58243,46994,46994,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#c1c1c1ff","u16":[49644,49644,49644,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#e3b7b7ff","u16":[58243,46994,46994,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#fbfbfbff","u16":[64416,64416,64416,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#fdfafaff","u16":[64978,64263,64263,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#f1f1f1ff","u16":[61926,61926,61926,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#f8efefff","u16":[63761,61419,61419,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#e9e9e9ff","u16":[59976,59976,59976,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#f4e6e6ff","u16":[62829,59177,59177,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#e1e1e1ff","u16":[57947,57947,57947,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#f1ddddff","u16":[61881,56828,56828,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#e1e1e1ff","u16":[57947,57947,57947,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#f1ddddff","u16":[61881,56828,56828,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#f8f8f8ff","u16":[63805,63805,63805,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#fcf7f7ff","u16":[64677,63567,63567,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#e4e4e4ff","u16":[58633,58633,58633,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#f2e0e0ff","u16":[62199,57624,57624,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#d4d4d4ff","u16":[54361,54361,54361,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#eacdcdff","u16":[60260,52632,52632,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#c1c1c1ff","u16":[49644,49644,49644,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#e3b7b7ff","u16":[58243,46994,46994,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#c1c1c1ff","u16":[49644,49644,49644,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#e3b7b7ff","u16":[58243,46994,46994,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#f6f6f6ff","u16":[63186,63186,63186,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#faf5f5ff","u16":[64373,62860,62860,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#d6d6d6ff","u16":[55101,55101,55101,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#ecd0d0ff","u16":[60588,53503,53503,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#bbbbbbff","u16":[47946,47946,47946,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#e0afafff","u16":[57551,44923,44923,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#999999ff","u16":[39199,39199,39199,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#d38383ff","u16":[54309,33694,33694,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#999999ff","u16":[39199,39199,39199,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#d38383ff","u16":[54309,33694,33694,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#fdfdfdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#f1f1f1ff","u16":[61926,61926,61926,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#fdfdfdff","out":{"hex":"#f8efefff","u16":[63761,61419,61419,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#bebebeff","u16":[48804,48804,48804,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#fdfdfdff","out":{"hex":"#e1b3b3ff","u16":[57898,45972,45972,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#888888ff","u16":[35078,35078,35078,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#fdfdfdff","out":{"hex":"#ce6c6cff","u16":[52983,27831,27831,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#fdfdfdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#fefefeff","out":{"hex":"#f9f9f9ff","u16":[64067,64067,64067,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#fefefeff","out":{"hex":"#fcf9f9ff","u16":[64806,63866,63866,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#e5e5e5ff","u16":[58923,58923,58923,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#f3e2e2ff","u16":[62334,57961,57961,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#d5d5d5ff","u16":[54680,54680,54680,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#ebceceff","u16":[60401,53007,53007,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#fefefeff","out":{"hex":"#c3c3c3ff","u16":[49998,49998,49998,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#fefefeff","out":{"hex":"#e3b9b9ff","u16":[58389,47423,47423,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#c3c3c3ff","u16":[49998,49998,49998,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#e3b9b9ff","u16":[58389,47423,47423,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#fefefeff","out":{"hex":"#fcfcfcff","u16":[64676,64676,64676,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#fefefeff","out":{"hex":"#fdfbfbff","u16":[65107,64559,64559,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#f2f2f2ff","u16":[62198,62198,62198,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#f9f0f0ff","u16":[63893,61731,61731,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#eaeaeaff","u16":[60259,60259,60259,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#f5e8e8ff","u16":[62963,59503,59503,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#fefefeff","out":{"hex":"#e3e3e3ff","u16":[58242,58242,58242,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#fefefeff","out":{"hex":"#f1dedeff","u16":[62017,57171,57171,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#e3e3e3ff","u16":[58242,58242,58242,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#f1dedeff","u16":[62017,57171,57171,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#fefefeff","out":{"hex":"#f9f9f9ff","u16":[64067,64067,64067,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#fefefeff","out":{"hex":"#fcf9f9ff","u16":[64806,63866,63866,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#e5e5e5ff","u16":[58923,58923,58923,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#f3e2e2ff","u16":[62334,57961,57961,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#d5d5d5ff","u16":[54680,54680,54680,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#ebceceff","u16":[60401,53007,53007,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#fefefeff","out":{"hex":"#c3c3c3ff","u16":[49998,49998,49998,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#fefefeff","out":{"hex":"#e3b9b9ff","u16":[58389,47423,47423,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#c3c3c3ff","u16":[49998,49998,49998,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#e3b9b9ff","u16":[58389,47423,47423,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#fefefeff","out":{"hex":"#f7f7f7ff","u16":[63452,63452,63452,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#fefefeff","out":{"hex":"#fbf6f6ff","u16":[64503,63164,63164,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#d8d8d8ff","u16":[55414,55414,55414,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#ecd2d2ff","u16":[60728,53871,53871,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#bcbcbcff","u16":[48316,48316,48316,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#e1b1b1ff","u16":[57700,45376,45376,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#fefefeff","out":{"hex":"#9a9a9aff","u16":[39669,39669,39669,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#fefefeff","out":{"hex":"#d48686ff","u16":[54468,34330,34330,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#9a9a9aff","u16":[39669,39669,39669,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#d48686ff","u16":[54468,34330,34330,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#fefefeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#fefefeff","out":{"hex":"#f2f2f2ff","u16":[62198,62198,62198,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#fefefeff","out":{"hex":"#f9f0f0ff","u16":[63893,61731,61731,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#bfbfbfff","u16":[49166,49166,49166,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefefeff","out":{"hex":"#e2b5b5ff","u16":[58046,46413,46413,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#8b8b8bff","u16":[35613,35613,35613,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefefeff","out":{"hex":"#cf6f6fff","u16":[53147,28627,28627,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefefeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#cb0000ff","u16":[52246,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#cb0000ff","u16":[52246,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#ff0000ff","out":{"hex":"#ff4343ff","u16":[65535,17242,17242,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#ff0000ff","out":{"hex":"#ff4343ff","u16":[65535,17242,17242,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#ff6060ff","u16":[65535,24674,24674,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#ff6060ff","u16":[65535,24674,24674,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#ff7070ff","u16":[65535,28837,28837,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#ff7070ff","u16":[65535,28837,28837,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#ff0000ff","out":{"hex":"#ff7e7eff","u16":[65535,32379,32379,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#ff0000ff","out":{"hex":"#ff7e7eff","u16":[65535,32379,32379,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#ff7e7eff","u16":[65535,32379,32379,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#ff7e7eff","u16":[65535,32379,32379,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#eb0000ff","u16":[60309,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#eb0000ff","u16":[60309,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#ff0000ff","out":{"hex":"#ff2e2eff","u16":[65535,11694,11694,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#ff0000ff","out":{"hex":"#ff2e2eff","u16":[65535,11694,11694,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#ff4141ff","u16":[65535,16734,16734,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#ff4141ff","u16":[65535,16734,16734,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#ff4c4cff","u16":[65535,19557,19557,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#ff4c4cff","u16":[65535,19557,19557,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#ff0000ff","out":{"hex":"#ff5555ff","u16":[65535,21960,21960,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#ff0000ff","out":{"hex":"#ff5555ff","u16":[65535,21960,21960,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#ff5555ff","u16":[65535,21960,21960,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#ff5555ff","u16":[65535,21960,21960,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#f60000ff","u16":[63210,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#f60000ff","u16":[63210,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#ff0000ff","out":{"hex":"#e50000ff","u16":[58875,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#ff0000ff","out":{"hex":"#e50000ff","u16":[58875,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#de0000ff","u16":[57029,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#de0000ff","u16":[57029,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#d80000ff","u16":[55596,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#d80000ff","u16":[55596,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#ff0000ff","out":{"hex":"#d30000ff","u16":[54118,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#ff0000ff","out":{"hex":"#d30000ff","u16":[54118,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#d30000ff","u16":[54118,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#d30000ff","u16":[54118,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#e60000ff","u16":[59214,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#e60000ff","u16":[59214,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#ff0000ff","out":{"hex":"#b10000ff","u16":[45587,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#ff0000ff","out":{"hex":"#b10000ff","u16":[45587,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#960000ff","u16":[38483,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#960000ff","u16":[38483,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#7c0000ff","u16":[31889,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#7c0000ff","u16":[31889,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#ff0000ff","out":{"hex":"#5a0000ff","u16":[23011,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#ff0000ff","out":{"hex":"#5a0000ff","u16":[23011,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#5a0000ff","u16":[23011,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#5a0000ff","u16":[23011,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#9e0000ff","u16":[40612,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#ff0000ff","out":{"hex":"#9e0000ff","u16":[40612,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#ff0000ff","out":{"hex":"#7a0000ff","u16":[31266,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#ff0000ff","out":{"hex":"#7a0000ff","u16":[31266,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#670000ff","u16":[26394,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff0000ff","out":{"hex":"#670000ff","u16":[26394,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#550000ff","u16":[21871,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff0000ff","out":{"hex":"#550000ff","u16":[21871,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#ff0000ff","out":{"hex":"#3d0000ff","u16":[15782,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#ff0000ff","out":{"hex":"#3d0000ff","u16":[15782,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#3d0000ff","u16":[15782,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff0000ff","out":{"hex":"#3d0000ff","u16":[15782,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#00d700ff","u16":[0,55365,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#00d700ff","u16":[0,55365,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#00ff00ff","out":{"hex":"#a6ffa6ff","u16":[42676,65535,42676,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#00ff00ff","out":{"hex":"#a6ffa6ff","u16":[42676,65535,42676,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#eeffeeff","u16":[61070,65535,61070,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#eeffeeff","u16":[61070,65535,61070,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#00f600ff","u16":[0,63179,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#00f600ff","u16":[0,63179,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#00ff00ff","out":{"hex":"#58ff58ff","u16":[22712,65535,22712,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#00ff00ff","out":{"hex":"#58ff58ff","u16":[22712,65535,22712,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#7eff7eff","u16":[32502,65535,32502,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#7eff7eff","u16":[32502,65535,32502,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#94ff94ff","u16":[37985,65535,37985,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#94ff94ff","u16":[37985,65535,37985,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#00ff00ff","out":{"hex":"#a6ffa6ff","u16":[42651,65535,42651,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#00ff00ff","out":{"hex":"#a6ffa6ff","u16":[42651,65535,42651,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#a6ffa6ff","u16":[42651,65535,42651,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#a6ffa6ff","u16":[42651,65535,42651,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#00f400ff","u16":[0,62739,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#00f400ff","u16":[0,62739,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#00ff00ff","out":{"hex":"#00e000ff","u16":[0,57450,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#00ff00ff","out":{"hex":"#00e000ff","u16":[0,57450,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#00d700ff","u16":[0,55162,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#00d700ff","u16":[0,55162,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#00d000ff","u16":[0,53368,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#00d000ff","u16":[0,53368,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#00ff00ff","out":{"hex":"#00c800ff","u16":[0,51499,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#00ff00ff","out":{"hex":"#00c800ff","u16":[0,51499,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#00c800ff","u16":[0,51499,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#00c800ff","u16":[0,51499,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#00e600ff","u16":[0,59214,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#00e600ff","u16":[0,59214,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#00ff00ff","out":{"hex":"#00b100ff","u16":[0,45587,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#00ff00ff","out":{"hex":"#00b100ff","u16":[0,45587,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#009600ff","u16":[0,38483,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#009600ff","u16":[0,38483,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#007c00ff","u16":[0,31889,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#007c00ff","u16":[0,31889,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#00ff00ff","out":{"hex":"#005a00ff","u16":[0,23011,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#00ff00ff","out":{"hex":"#005a00ff","u16":[0,23011,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#005a00ff","u16":[0,23011,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#005a00ff","u16":[0,23011,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#008200ff","u16":[0,33491,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#00ff00ff","out":{"hex":"#008200ff","u16":[0,33491,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#00ff00ff","out":{"hex":"#006400ff","u16":[0,25784,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#00ff00ff","out":{"hex":"#006400ff","u16":[0,25784,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#005500ff","u16":[0,21766,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ff00ff","out":{"hex":"#005500ff","u16":[0,21766,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#004600ff","u16":[0,18036,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ff00ff","out":{"hex":"#004600ff","u16":[0,18036,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#00ff00ff","out":{"hex":"#003300ff","u16":[0,13015,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#00ff00ff","out":{"hex":"#003300ff","u16":[0,13015,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#003300ff","u16":[0,13015,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ff00ff","out":{"hex":"#003300ff","u16":[0,13015,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000a4ff","u16":[0,0,42151,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000a4ff","u16":[0,0,42151,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#0000ffff","out":{"hex":"#2f2fffff","u16":[12068,12068,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#0000ffff","out":{"hex":"#2f2fffff","u16":[12068,12068,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#4343ffff","u16":[17269,17269,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#4343ffff","u16":[17269,17269,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#4f4fffff","u16":[20182,20182,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#4f4fffff","u16":[20182,20182,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#0000ffff","out":{"hex":"#5858ffff","u16":[22661,22661,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#0000ffff","out":{"hex":"#5858ffff","u16":[22661,22661,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#5858ffff","u16":[22661,22661,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#5858ffff","u16":[22661,22661,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000daff","u16":[0,0,55910,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000daff","u16":[0,0,55910,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#0000ffff","out":{"hex":"#2121ffff","u16":[8601,8601,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#0000ffff","out":{"hex":"#2121ffff","u16":[8601,8601,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#3030ffff","u16":[12308,12308,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#3030ffff","u16":[12308,12308,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#3838ffff","u16":[14385,14385,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#3838ffff","u16":[14385,14385,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#0000ffff","out":{"hex":"#3f3fffff","u16":[16152,16152,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#0000ffff","out":{"hex":"#3f3fffff","u16":[16152,16152,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#3f3fffff","u16":[16152,16152,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#3f3fffff","u16":[16152,16152,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000f6ff","u16":[0,0,63341,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000f6ff","u16":[0,0,63341,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#0000ffff","out":{"hex":"#0000e7ff","u16":[0,0,59266,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#0000ffff","out":{"hex":"#0000e7ff","u16":[0,0,59266,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#0000e0ff","u16":[0,0,57538,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#0000e0ff","u16":[0,0,57538,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#0000dbff","u16":[0,0,56200,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#0000dbff","u16":[0,0,56200,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#0000ffff","out":{"hex":"#0000d5ff","u16":[0,0,54822,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#0000ffff","out":{"hex":"#0000d5ff","u16":[0,0,54822,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#0000d5ff","u16":[0,0,54822,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#0000d5ff","u16":[0,0,54822,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000e6ff","u16":[0,0,59214,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000e6ff","u16":[0,0,59214,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#0000ffff","out":{"hex":"#0000b1ff","u16":[0,0,45587,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#0000ffff","out":{"hex":"#0000b1ff","u16":[0,0,45587,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#000096ff","u16":[0,0,38483,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#000096ff","u16":[0,0,38483,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#00007cff","u16":[0,0,31889,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#00007cff","u16":[0,0,31889,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#0000ffff","out":{"hex":"#00005aff","u16":[0,0,23011,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#0000ffff","out":{"hex":"#00005aff","u16":[0,0,23011,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#00005aff","u16":[0,0,23011,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#00005aff","u16":[0,0,23011,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000a5ff","u16":[0,0,42350,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#0000ffff","out":{"hex":"#0000a5ff","u16":[0,0,42350,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#0000ffff","out":{"hex":"#00007fff","u16":[0,0,32604,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#0000ffff","out":{"hex":"#00007fff","u16":[0,0,32604,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#00006bff","u16":[0,0,27523,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#0000ffff","out":{"hex":"#00006bff","u16":[0,0,27523,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#000059ff","u16":[0,0,22807,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#0000ffff","out":{"hex":"#000059ff","u16":[0,0,22807,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#0000ffff","out":{"hex":"#000040ff","u16":[0,0,16457,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#0000ffff","out":{"hex":"#000040ff","u16":[0,0,16457,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#000040ff","u16":[0,0,16457,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#0000ffff","out":{"hex":"#000040ff","u16":[0,0,16457,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#d9d900ff","u16":[55656,55656,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#d9d900ff","u16":[55656,55656,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#f9f900ff","u16":[63935,63935,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#f9f900ff","u16":[63935,63935,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#ffff00ff","out":{"hex":"#ffff9cff","u16":[65535,65535,40136,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#ffff00ff","out":{"hex":"#ffff9cff","u16":[65535,65535,40136,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#ffffdfff","u16":[65535,65535,57435,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#ffffdfff","u16":[65535,65535,57435,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#f3f300ff","u16":[62539,62539,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#f3f300ff","u16":[62539,62539,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#ffff00ff","out":{"hex":"#dddd00ff","u16":[56834,56834,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#ffff00ff","out":{"hex":"#dddd00ff","u16":[56834,56834,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#d3d300ff","u16":[54348,54348,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#d3d300ff","u16":[54348,54348,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#cccc00ff","u16":[52391,52391,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#cccc00ff","u16":[52391,52391,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#ffff00ff","out":{"hex":"#c4c400ff","u16":[50341,50341,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#ffff00ff","out":{"hex":"#c4c400ff","u16":[50341,50341,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#c4c400ff","u16":[50341,50341,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#c4c400ff","u16":[50341,50341,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#e6e600ff","u16":[59214,59214,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#e6e600ff","u16":[59214,59214,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#ffff00ff","out":{"hex":"#b1b100ff","u16":[45587,45587,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#ffff00ff","out":{"hex":"#b1b100ff","u16":[45587,45587,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#969600ff","u16":[38483,38483,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#969600ff","u16":[38483,38483,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#7c7c00ff","u16":[31889,31889,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#7c7c00ff","u16":[31889,31889,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#ffff00ff","out":{"hex":"#5a5a00ff","u16":[23011,23011,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#ffff00ff","out":{"hex":"#5a5a00ff","u16":[23011,23011,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#5a5a00ff","u16":[23011,23011,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#5a5a00ff","u16":[23011,23011,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#747400ff","u16":[29854,29854,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#ffff00ff","out":{"hex":"#747400ff","u16":[29854,29854,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#ffff00ff","out":{"hex":"#595900ff","u16":[22984,22984,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#ffff00ff","out":{"hex":"#595900ff","u16":[22984,22984,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#4b4b00ff","u16":[19402,19402,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffff00ff","out":{"hex":"#4b4b00ff","u16":[19402,19402,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#3f3f00ff","u16":[16078,16078,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffff00ff","out":{"hex":"#3f3f00ff","u16":[16078,16078,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#ffff00ff","out":{"hex":"#2d2d00ff","u16":[11602,11602,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#ffff00ff","out":{"hex":"#2d2d00ff","u16":[11602,11602,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#2d2d00ff","u16":[11602,11602,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffff00ff","out":{"hex":"#2d2d00ff","u16":[11602,11602,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#00ffffff","out":{"hex":"#00d8d8ff","u16":[0,55482,55482,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#00ffffff","out":{"hex":"#00d8d8ff","u16":[0,55482,55482,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#00ffffff","out":{"hex":"#c5ffffff","u16":[50679,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#00ffffff","out":{"hex":"#c5ffffff","u16":[50679,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#00ffffff","out":{"hex":"#00f7f7ff","u16":[0,63444,63444,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#00ffffff","out":{"hex":"#00f7f7ff","u16":[0,63444,63444,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#00ffffff","out":{"hex":"#64ffffff","u16":[25695,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#00ffffff","out":{"hex":"#64ffffff","u16":[25695,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#8fffffff","u16":[36770,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#8fffffff","u16":[36770,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#a7ffffff","u16":[42973,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#a7ffffff","u16":[42973,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#00ffffff","out":{"hex":"#bcffffff","u16":[48252,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#00ffffff","out":{"hex":"#bcffffff","u16":[48252,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#bcffffff","u16":[48252,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#bcffffff","u16":[48252,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#00ffffff","out":{"hex":"#00f4f4ff","u16":[0,62671,62671,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#00ffffff","out":{"hex":"#00f4f4ff","u16":[0,62671,62671,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#00ffffff","out":{"hex":"#00dfdfff","u16":[0,57241,57241,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#00ffffff","out":{"hex":"#00dfdfff","u16":[0,57241,57241,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#00d6d6ff","u16":[0,54887,54887,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#00d6d6ff","u16":[0,54887,54887,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#00ceceff","u16":[0,53038,53038,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#00ceceff","u16":[0,53038,53038,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#00ffffff","out":{"hex":"#00c7c7ff","u16":[0,51109,51109,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#00ffffff","out":{"hex":"#00c7c7ff","u16":[0,51109,51109,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#00c7c7ff","u16":[0,51109,51109,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#00c7c7ff","u16":[0,51109,51109,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#00ffffff","out":{"hex":"#00e6e6ff","u16":[0,59214,59214,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#00ffffff","out":{"hex":"#00e6e6ff","u16":[0,59214,59214,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#00ffffff","out":{"hex":"#00b1b1ff","u16":[0,45587,45587,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#00ffffff","out":{"hex":"#00b1b1ff","u16":[0,45587,45587,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#009696ff","u16":[0,38483,38483,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#009696ff","u16":[0,38483,38483,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#007c7cff","u16":[0,31889,31889,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#007c7cff","u16":[0,31889,31889,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#00ffffff","out":{"hex":"#005a5aff","u16":[0,23011,23011,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#00ffffff","out":{"hex":"#005a5aff","u16":[0,23011,23011,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#005a5aff","u16":[0,23011,23011,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#005a5aff","u16":[0,23011,23011,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#00ffffff","out":{"hex":"#007e7eff","u16":[0,32311,32311,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#00ffffff","out":{"hex":"#007e7eff","u16":[0,32311,32311,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#00ffffff","out":{"hex":"#006161ff","u16":[0,24875,24875,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#00ffffff","out":{"hex":"#006161ff","u16":[0,24875,24875,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#005252ff","u16":[0,20999,20999,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#00ffffff","out":{"hex":"#005252ff","u16":[0,20999,20999,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#004444ff","u16":[0,17401,17401,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#00ffffff","out":{"hex":"#004444ff","u16":[0,17401,17401,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#00ffffff","out":{"hex":"#003131ff","u16":[0,12556,12556,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#00ffffff","out":{"hex":"#003131ff","u16":[0,12556,12556,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#003131ff","u16":[0,12556,12556,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#00ffffff","out":{"hex":"#003131ff","u16":[0,12556,12556,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#d000d0ff","u16":[53397,0,53397,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#d000d0ff","u16":[53397,0,53397,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#ff00ffff","out":{"hex":"#ff4dffff","u16":[65535,19848,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#ff00ffff","out":{"hex":"#ff4dffff","u16":[65535,19848,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#ff6fffff","u16":[65535,28403,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#ff6fffff","u16":[65535,28403,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#ff81ffff","u16":[65535,33194,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#ff81ffff","u16":[65535,33194,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#ff00ffff","out":{"hex":"#ff91ffff","u16":[65535,37271,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#ff00ffff","out":{"hex":"#ff91ffff","u16":[65535,37271,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#ff91ffff","u16":[65535,37271,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#ff91ffff","u16":[65535,37271,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#ed00edff","u16":[61023,0,61023,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#ed00edff","u16":[61023,0,61023,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#ff00ffff","out":{"hex":"#ff33ffff","u16":[65535,13091,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#ff00ffff","out":{"hex":"#ff33ffff","u16":[65535,13091,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#ff49ffff","u16":[65535,18734,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#ff49ffff","u16":[65535,18734,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#ff55ffff","u16":[65535,21895,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#ff55ffff","u16":[65535,21895,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#ff00ffff","out":{"hex":"#ff60ffff","u16":[65535,24584,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#ff00ffff","out":{"hex":"#ff60ffff","u16":[65535,24584,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#ff60ffff","u16":[65535,24584,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#ff60ffff","u16":[65535,24584,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#f600f6ff","u16":[63143,0,63143,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#f600f6ff","u16":[63143,0,63143,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#ff00ffff","out":{"hex":"#e400e4ff","u16":[58673,0,58673,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#ff00ffff","out":{"hex":"#e400e4ff","u16":[58673,0,58673,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#dd00ddff","u16":[56765,0,56765,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#dd00ddff","u16":[56765,0,56765,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#d700d7ff","u16":[55283,0,55283,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#d700d7ff","u16":[55283,0,55283,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#ff00ffff","out":{"hex":"#d100d1ff","u16":[53751,0,53751,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#ff00ffff","out":{"hex":"#d100d1ff","u16":[53751,0,53751,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#d100d1ff","u16":[53751,0,53751,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#d100d1ff","u16":[53751,0,53751,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#e600e6ff","u16":[59214,0,59214,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#e600e6ff","u16":[59214,0,59214,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#ff00ffff","out":{"hex":"#b100b1ff","u16":[45587,0,45587,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#ff00ffff","out":{"hex":"#b100b1ff","u16":[45587,0,45587,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#960096ff","u16":[38483,0,38483,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#960096ff","u16":[38483,0,38483,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#7c007cff","u16":[31889,0,31889,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#7c007cff","u16":[31889,0,31889,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#ff00ffff","out":{"hex":"#5a005aff","u16":[23011,0,23011,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#ff00ffff","out":{"hex":"#5a005aff","u16":[23011,0,23011,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#5a005aff","u16":[23011,0,23011,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#5a005aff","u16":[23011,0,23011,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#9a009aff","u16":[39683,0,39683,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#ff00ffff","out":{"hex":"#9a009aff","u16":[39683,0,39683,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#ff00ffff","out":{"hex":"#770077ff","u16":[30550,0,30550,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#ff00ffff","out":{"hex":"#770077ff","u16":[30550,0,30550,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#640064ff","u16":[25790,0,25790,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff00ffff","out":{"hex":"#640064ff","u16":[25790,0,25790,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#530053ff","u16":[21371,0,21371,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff00ffff","out":{"hex":"#530053ff","u16":[21371,0,21371,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#ff00ffff","out":{"hex":"#3c003cff","u16":[15421,0,15421,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#ff00ffff","out":{"hex":"#3c003cff","u16":[15421,0,15421,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#3c003cff","u16":[15421,0,15421,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff00ffff","out":{"hex":"#3c003cff","u16":[15421,0,15421,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#800000ff","out":{"hex":"#3c0000ff","u16":[15438,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#800000ff","out":{"hex":"#3c0000ff","u16":[15438,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#800000ff","out":{"hex":"#950000ff","u16":[38399,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#800000ff","out":{"hex":"#950000ff","u16":[38399,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#800000ff","out":{"hex":"#ab0000ff","u16":[43953,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#800000ff","out":{"hex":"#ab0000ff","u16":[43953,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#800000ff","out":{"hex":"#b90000ff","u16":[47621,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#800000ff","out":{"hex":"#b90000ff","u16":[47621,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#800000ff","out":{"hex":"#c60000ff","u16":[50978,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#800000ff","out":{"hex":"#c60000ff","u16":[50978,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#800000ff","out":{"hex":"#c60000ff","u16":[50978,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#800000ff","out":{"hex":"#c60000ff","u16":[50978,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#800000ff","out":{"hex":"#660000ff","u16":[26235,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#800000ff","out":{"hex":"#660000ff","u16":[26235,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#800000ff","out":{"hex":"#8b0000ff","u16":[35685,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#800000ff","out":{"hex":"#8b0000ff","u16":[35685,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#800000ff","out":{"hex":"#970000ff","u16":[38720,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#800000ff","out":{"hex":"#970000ff","u16":[38720,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#800000ff","out":{"hex":"#9f0000ff","u16":[40821,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#800000ff","out":{"hex":"#9f0000ff","u16":[40821,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#800000ff","out":{"hex":"#a70000ff","u16":[42799,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#800000ff","out":{"hex":"#a70000ff","u16":[42799,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#800000ff","out":{"hex":"#a70000ff","u16":[42799,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#800000ff","out":{"hex":"#a70000ff","u16":[42799,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#800000ff","out":{"hex":"#7c0000ff","u16":[31807,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#800000ff","out":{"hex":"#7c0000ff","u16":[31807,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#800000ff","out":{"hex":"#740000ff","u16":[29785,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#800000ff","out":{"hex":"#740000ff","u16":[29785,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#800000ff","out":{"hex":"#710000ff","u16":[28928,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#800000ff","out":{"hex":"#710000ff","u16":[28928,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#800000ff","out":{"hex":"#6e0000ff","u16":[28265,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#800000ff","out":{"hex":"#6e0000ff","u16":[28265,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#800000ff","out":{"hex":"#6b0000ff","u16":[27582,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#800000ff","out":{"hex":"#6b0000ff","u16":[27582,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#800000ff","out":{"hex":"#6b0000ff","u16":[27582,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#800000ff","out":{"hex":"#6b0000ff","u16":[27582,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#800000ff","out":{"hex":"#740000ff","u16":[29723,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#800000ff","out":{"hex":"#740000ff","u16":[29723,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#800000ff","out":{"hex":"#590000ff","u16":[22883,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#800000ff","out":{"hex":"#590000ff","u16":[22883,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#800000ff","out":{"hex":"#4b0000ff","u16":[19317,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#800000ff","out":{"hex":"#4b0000ff","u16":[19317,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#800000ff","out":{"hex":"#3e0000ff","u16":[16007,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#800000ff","out":{"hex":"#3e0000ff","u16":[16007,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#800000ff","out":{"hex":"#2d0000ff","u16":[11550,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#800000ff","out":{"hex":"#2d0000ff","u16":[11550,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#800000ff","out":{"hex":"#2d0000ff","u16":[11550,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#800000ff","out":{"hex":"#2d0000ff","u16":[11550,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#800000ff","out":{"hex":"#530000ff","u16":[21412,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#800000ff","out":{"hex":"#530000ff","u16":[21412,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#800000ff","out":{"hex":"#400000ff","u16":[16484,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#800000ff","out":{"hex":"#400000ff","u16":[16484,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#800000ff","out":{"hex":"#360000ff","u16":[13916,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#800000ff","out":{"hex":"#360000ff","u16":[13916,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#800000ff","out":{"hex":"#2d0000ff","u16":[11531,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#800000ff","out":{"hex":"#2d0000ff","u16":[11531,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#800000ff","out":{"hex":"#200000ff","u16":[8320,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#800000ff","out":{"hex":"#200000ff","u16":[8320,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#800000ff","out":{"hex":"#200000ff","u16":[8320,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#800000ff","out":{"hex":"#200000ff","u16":[8320,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#008000ff","out":{"hex":"#006300ff","u16":[0,25392,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#008000ff","out":{"hex":"#006300ff","u16":[0,25392,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#008000ff","out":{"hex":"#008c00ff","u16":[0,35969,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#008000ff","out":{"hex":"#008c00ff","u16":[0,35969,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#008000ff","out":{"hex":"#009900ff","u16":[0,39285,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#008000ff","out":{"hex":"#009900ff","u16":[0,39285,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#008000ff","out":{"hex":"#00a200ff","u16":[0,41566,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#008000ff","out":{"hex":"#00a200ff","u16":[0,41566,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#008000ff","out":{"hex":"#00aa00ff","u16":[0,43706,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#008000ff","out":{"hex":"#00aa00ff","u16":[0,43706,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#008000ff","out":{"hex":"#00aa00ff","u16":[0,43706,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#008000ff","out":{"hex":"#00aa00ff","u16":[0,43706,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#008000ff","out":{"hex":"#007400ff","u16":[0,29837,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#008000ff","out":{"hex":"#007400ff","u16":[0,29837,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#008000ff","out":{"hex":"#008500ff","u16":[0,34305,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#008000ff","out":{"hex":"#008500ff","u16":[0,34305,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#008000ff","out":{"hex":"#008c00ff","u16":[0,35908,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#008000ff","out":{"hex":"#008c00ff","u16":[0,35908,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#008000ff","out":{"hex":"#009000ff","u16":[0,37057,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#008000ff","out":{"hex":"#009000ff","u16":[0,37057,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#008000ff","out":{"hex":"#009400ff","u16":[0,38164,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#008000ff","out":{"hex":"#009400ff","u16":[0,38164,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#008000ff","out":{"hex":"#009400ff","u16":[0,38164,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#008000ff","out":{"hex":"#009400ff","u16":[0,38164,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#008000ff","out":{"hex":"#007c00ff","u16":[0,31755,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#008000ff","out":{"hex":"#007c00ff","u16":[0,31755,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#008000ff","out":{"hex":"#007300ff","u16":[0,29631,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#008000ff","out":{"hex":"#007300ff","u16":[0,29631,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#008000ff","out":{"hex":"#007000ff","u16":[0,28728,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#008000ff","out":{"hex":"#007000ff","u16":[0,28728,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#008000ff","out":{"hex":"#006d00ff","u16":[0,28028,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#008000ff","out":{"hex":"#006d00ff","u16":[0,28028,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#008000ff","out":{"hex":"#006a00ff","u16":[0,27306,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#008000ff","out":{"hex":"#006a00ff","u16":[0,27306,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#008000ff","out":{"hex":"#006a00ff","u16":[0,27306,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#008000ff","out":{"hex":"#006a00ff","u16":[0,27306,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#008000ff","out":{"hex":"#007400ff","u16":[0,29723,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#008000ff","out":{"hex":"#007400ff","u16":[0,29723,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#008000ff","out":{"hex":"#005900ff","u16":[0,22883,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#008000ff","out":{"hex":"#005900ff","u16":[0,22883,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#008000ff","out":{"hex":"#004b00ff","u16":[0,19317,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#008000ff","out":{"hex":"#004b00ff","u16":[0,19317,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#008000ff","out":{"hex":"#003e00ff","u16":[0,16007,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#008000ff","out":{"hex":"#003e00ff","u16":[0,16007,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#008000ff","out":{"hex":"#002d00ff","u16":[0,11550,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#008000ff","out":{"hex":"#002d00ff","u16":[0,11550,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#008000ff","out":{"hex":"#002d00ff","u16":[0,11550,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#008000ff","out":{"hex":"#002d00ff","u16":[0,11550,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#008000ff","out":{"hex":"#005100ff","u16":[0,20736,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#008000ff","out":{"hex":"#005100ff","u16":[0,20736,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#008000ff","out":{"hex":"#003e00ff","u16":[0,15964,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#008000ff","out":{"hex":"#003e00ff","u16":[0,15964,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#008000ff","out":{"hex":"#003400ff","u16":[0,13476,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#008000ff","out":{"hex":"#003400ff","u16":[0,13476,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#008000ff","out":{"hex":"#002b00ff","u16":[0,11167,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#008000ff","out":{"hex":"#002b00ff","u16":[0,11167,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#008000ff","out":{"hex":"#001f00ff","u16":[0,8058,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#008000ff","out":{"hex":"#001f00ff","u16":[0,8058,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#008000ff","out":{"hex":"#001f00ff","u16":[0,8058,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#008000ff","out":{"hex":"#001f00ff","u16":[0,8058,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#000080ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#000080ff","out":{"hex":"#0000acff","u16":[0,0,44315,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#000080ff","out":{"hex":"#0000acff","u16":[0,0,44315,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#000080ff","out":{"hex":"#0000d4ff","u16":[0,0,54510,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#000080ff","out":{"hex":"#0000d4ff","u16":[0,0,54510,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#000080ff","out":{"hex":"#0000edff","u16":[0,0,60862,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#000080ff","out":{"hex":"#0000edff","u16":[0,0,60862,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#000080ff","out":{"hex":"#1111ffff","u16":[4342,4342,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#000080ff","out":{"hex":"#1111ffff","u16":[4342,4342,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#000080ff","out":{"hex":"#1111ffff","u16":[4342,4342,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#000080ff","out":{"hex":"#1111ffff","u16":[4342,4342,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#000080ff","out":{"hex":"#00002aff","u16":[0,0,10676,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#000080ff","out":{"hex":"#00002aff","u16":[0,0,10676,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#000080ff","out":{"hex":"#000098ff","u16":[0,0,39046,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#000080ff","out":{"hex":"#000098ff","u16":[0,0,39046,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#000080ff","out":{"hex":"#0000b0ff","u16":[0,0,45156,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#000080ff","out":{"hex":"#0000b0ff","u16":[0,0,45156,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#000080ff","out":{"hex":"#0000bfff","u16":[0,0,49156,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#000080ff","out":{"hex":"#0000bfff","u16":[0,0,49156,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#000080ff","out":{"hex":"#0000cdff","u16":[0,0,52799,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#000080ff","out":{"hex":"#0000cdff","u16":[0,0,52799,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#000080ff","out":{"hex":"#0000cdff","u16":[0,0,52799,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#000080ff","out":{"hex":"#0000cdff","u16":[0,0,52799,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#000080ff","out":{"hex":"#00007cff","u16":[0,0,31821,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#000080ff","out":{"hex":"#00007cff","u16":[0,0,31821,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#000080ff","out":{"hex":"#000074ff","u16":[0,0,29827,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#000080ff","out":{"hex":"#000074ff","u16":[0,0,29827,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#000080ff","out":{"hex":"#000071ff","u16":[0,0,28983,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#000080ff","out":{"hex":"#000071ff","u16":[0,0,28983,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#000080ff","out":{"hex":"#00006eff","u16":[0,0,28331,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#000080ff","out":{"hex":"#00006eff","u16":[0,0,28331,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#000080ff","out":{"hex":"#00006cff","u16":[0,0,27659,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#000080ff","out":{"hex":"#00006cff","u16":[0,0,27659,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#000080ff","out":{"hex":"#00006cff","u16":[0,0,27659,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#000080ff","out":{"hex":"#00006cff","u16":[0,0,27659,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#000080ff","out":{"hex":"#000074ff","u16":[0,0,29723,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#000080ff","out":{"hex":"#000074ff","u16":[0,0,29723,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#000080ff","out":{"hex":"#000059ff","u16":[0,0,22883,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#000080ff","out":{"hex":"#000059ff","u16":[0,0,22883,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#000080ff","out":{"hex":"#00004bff","u16":[0,0,19317,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#000080ff","out":{"hex":"#00004bff","u16":[0,0,19317,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#000080ff","out":{"hex":"#00003eff","u16":[0,0,16007,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#000080ff","out":{"hex":"#00003eff","u16":[0,0,16007,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#000080ff","out":{"hex":"#00002dff","u16":[0,0,11550,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#000080ff","out":{"hex":"#00002dff","u16":[0,0,11550,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#000080ff","out":{"hex":"#00002dff","u16":[0,0,11550,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#000080ff","out":{"hex":"#00002dff","u16":[0,0,11550,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#000080ff","out":{"hex":"#000054ff","u16":[0,0,21596,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#000080ff","out":{"hex":"#000054ff","u16":[0,0,21596,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#000080ff","out":{"hex":"#000041ff","u16":[0,0,16626,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#000080ff","out":{"hex":"#000041ff","u16":[0,0,16626,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#000080ff","out":{"hex":"#000037ff","u16":[0,0,14035,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#000080ff","out":{"hex":"#000037ff","u16":[0,0,14035,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#000080ff","out":{"hex":"#00002dff","u16":[0,0,11630,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#000080ff","out":{"hex":"#00002dff","u16":[0,0,11630,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#000080ff","out":{"hex":"#000021ff","u16":[0,0,8392,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#000080ff","out":{"hex":"#000021ff","u16":[0,0,8392,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#000080ff","out":{"hex":"#000021ff","u16":[0,0,8392,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#000080ff","out":{"hex":"#000021ff","u16":[0,0,8392,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#ff4343ff","u16":[65535,17265,17265,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#ff4343ff","u16":[65535,17265,17265,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#ff8080ff","out":{"hex":"#ff9494ff","u16":[65535,38069,38069,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#ff8080ff","out":{"hex":"#ff9494ff","u16":[65535,38069,38069,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#ffa9a9ff","u16":[65535,43335,43335,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#ffa9a9ff","u16":[65535,43335,43335,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#ffb6b6ff","u16":[65535,46828,46828,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#ffb6b6ff","u16":[65535,46828,46828,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#ff8080ff","out":{"hex":"#ffc3c3ff","u16":[65535,50034,50034,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#ff8080ff","out":{"hex":"#ffc3c3ff","u16":[65535,50034,50034,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#ffc3c3ff","u16":[65535,50034,50034,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#ffc3c3ff","u16":[65535,50034,50034,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#ff6e6eff","u16":[65535,28392,28392,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#ff6e6eff","u16":[65535,28392,28392,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#ff8080ff","out":{"hex":"#ff8888ff","u16":[65535,34892,34892,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#ff8080ff","out":{"hex":"#ff8888ff","u16":[65535,34892,34892,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#ff9090ff","u16":[65535,37119,37119,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#ff9090ff","u16":[65535,37119,37119,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#ff9797ff","u16":[65535,38690,38690,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#ff9797ff","u16":[65535,38690,38690,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#ff8080ff","out":{"hex":"#ff9c9cff","u16":[65535,40187,40187,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#ff8080ff","out":{"hex":"#ff9c9cff","u16":[65535,40187,40187,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#ff9c9cff","u16":[65535,40187,40187,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#ff9c9cff","u16":[65535,40187,40187,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#ff7575ff","u16":[65535,30027,30027,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#ff7575ff","u16":[65535,30027,30027,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#ff8080ff","out":{"hex":"#ff5d5dff","u16":[65535,24002,24002,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#ff8080ff","out":{"hex":"#ff5d5dff","u16":[65535,24002,24002,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#ff5252ff","u16":[65535,20999,20999,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#ff5252ff","u16":[65535,20999,20999,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#ff4747ff","u16":[65535,18354,18354,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#ff4747ff","u16":[65535,18354,18354,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#ff8080ff","out":{"hex":"#ff3b3bff","u16":[65535,15146,15146,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#ff8080ff","out":{"hex":"#ff3b3bff","u16":[65535,15146,15146,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#ff3b3bff","u16":[65535,15146,15146,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#ff3b3bff","u16":[65535,15146,15146,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#ff6262ff","u16":[65535,25151,25151,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#ff6262ff","u16":[65535,25151,25151,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#ff8080ff","out":{"hex":"#e80000ff","u16":[59745,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#ff8080ff","out":{"hex":"#e80000ff","u16":[59745,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#c40000ff","u16":[50435,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#c40000ff","u16":[50435,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#a30000ff","u16":[41792,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#a30000ff","u16":[41792,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#ff8080ff","out":{"hex":"#750000ff","u16":[30157,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#ff8080ff","out":{"hex":"#750000ff","u16":[30157,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#750000ff","u16":[30157,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#750000ff","u16":[30157,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#c40000ff","u16":[50249,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#ff8080ff","out":{"hex":"#c40000ff","u16":[50249,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#ff8080ff","out":{"hex":"#970000ff","u16":[38686,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#ff8080ff","out":{"hex":"#970000ff","u16":[38686,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#7f0000ff","u16":[32657,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff8080ff","out":{"hex":"#7f0000ff","u16":[32657,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#690000ff","u16":[27061,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff8080ff","out":{"hex":"#690000ff","u16":[27061,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#ff8080ff","out":{"hex":"#4c0000ff","u16":[19527,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#ff8080ff","out":{"hex":"#4c0000ff","u16":[19527,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#4c0000ff","u16":[19527,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff8080ff","out":{"hex":"#4c0000ff","u16":[19527,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#00e000ff","u16":[0,57621,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#00e000ff","u16":[0,57621,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#80ff80ff","out":{"hex":"#d0ffd0ff","u16":[53460,65535,53460,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#80ff80ff","out":{"hex":"#d0ffd0ff","u16":[53460,65535,53460,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#33ff33ff","u16":[12997,65535,12997,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#33ff33ff","u16":[12997,65535,12997,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#80ff80ff","out":{"hex":"#97ff97ff","u16":[38767,65535,38767,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#80ff80ff","out":{"hex":"#97ff97ff","u16":[38767,65535,38767,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#aeffaeff","u16":[44640,65535,44640,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#aeffaeff","u16":[44640,65535,44640,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#bdffbdff","u16":[48499,65535,48499,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#bdffbdff","u16":[48499,65535,48499,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#80ff80ff","out":{"hex":"#caffcaff","u16":[52020,65535,52020,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#80ff80ff","out":{"hex":"#caffcaff","u16":[52020,65535,52020,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#caffcaff","u16":[52020,65535,52020,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#caffcaff","u16":[52020,65535,52020,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#00fd00ff","u16":[0,65114,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#00fd00ff","u16":[0,65114,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#80ff80ff","out":{"hex":"#00e700ff","u16":[0,59493,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#80ff80ff","out":{"hex":"#00e700ff","u16":[0,59493,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#00de00ff","u16":[0,57056,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#00de00ff","u16":[0,57056,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#00d700ff","u16":[0,55144,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#00d700ff","u16":[0,55144,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#80ff80ff","out":{"hex":"#00cf00ff","u16":[0,53148,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#80ff80ff","out":{"hex":"#00cf00ff","u16":[0,53148,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#00cf00ff","u16":[0,53148,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#00cf00ff","u16":[0,53148,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#00ef00ff","u16":[0,61513,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#00ef00ff","u16":[0,61513,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#80ff80ff","out":{"hex":"#00b800ff","u16":[0,47357,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#80ff80ff","out":{"hex":"#00b800ff","u16":[0,47357,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#009c00ff","u16":[0,39978,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#009c00ff","u16":[0,39978,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#008100ff","u16":[0,33127,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#008100ff","u16":[0,33127,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#80ff80ff","out":{"hex":"#005d00ff","u16":[0,23904,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#80ff80ff","out":{"hex":"#005d00ff","u16":[0,23904,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#005d00ff","u16":[0,23904,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#005d00ff","u16":[0,23904,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#008300ff","u16":[0,33733,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#80ff80ff","out":{"hex":"#008300ff","u16":[0,33733,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#80ff80ff","out":{"hex":"#006500ff","u16":[0,25970,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#80ff80ff","out":{"hex":"#006500ff","u16":[0,25970,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#005500ff","u16":[0,21924,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#80ff80ff","out":{"hex":"#005500ff","u16":[0,21924,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#004700ff","u16":[0,18167,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#80ff80ff","out":{"hex":"#004700ff","u16":[0,18167,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#80ff80ff","out":{"hex":"#003300ff","u16":[0,13109,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#80ff80ff","out":{"hex":"#003300ff","u16":[0,13109,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#003300ff","u16":[0,13109,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#80ff80ff","out":{"hex":"#003300ff","u16":[0,13109,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#8080ffff","out":{"hex":"#5e5effff","u16":[24109,24109,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#8080ffff","out":{"hex":"#5e5effff","u16":[24109,24109,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#8080ffff","out":{"hex":"#8e8effff","u16":[36376,36376,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#8080ffff","out":{"hex":"#8e8effff","u16":[36376,36376,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#9c9cffff","u16":[40086,40086,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#9c9cffff","u16":[40086,40086,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#a6a6ffff","u16":[42618,42618,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#a6a6ffff","u16":[42618,42618,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#8080ffff","out":{"hex":"#afafffff","u16":[44981,44981,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#8080ffff","out":{"hex":"#afafffff","u16":[44981,44981,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#afafffff","u16":[44981,44981,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#afafffff","u16":[44981,44981,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#8080ffff","out":{"hex":"#7474ffff","u16":[29726,29726,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#8080ffff","out":{"hex":"#7474ffff","u16":[29726,29726,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#8080ffff","out":{"hex":"#8686ffff","u16":[34351,34351,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#8080ffff","out":{"hex":"#8686ffff","u16":[34351,34351,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#8c8cffff","u16":[36005,36005,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#8c8cffff","u16":[36005,36005,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#9191ffff","u16":[37188,37188,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#9191ffff","u16":[37188,37188,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#8080ffff","out":{"hex":"#9595ffff","u16":[38327,38327,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#8080ffff","out":{"hex":"#9595ffff","u16":[38327,38327,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#9595ffff","u16":[38327,38327,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#9595ffff","u16":[38327,38327,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#8080ffff","out":{"hex":"#7a7affff","u16":[31262,31262,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#8080ffff","out":{"hex":"#7a7affff","u16":[31262,31262,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#8080ffff","out":{"hex":"#6d6dffff","u16":[28125,28125,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#8080ffff","out":{"hex":"#6d6dffff","u16":[28125,28125,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#6868ffff","u16":[26746,26746,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#6868ffff","u16":[26746,26746,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#6464ffff","u16":[25652,25652,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#6464ffff","u16":[25652,25652,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#8080ffff","out":{"hex":"#5f5fffff","u16":[24499,24499,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#8080ffff","out":{"hex":"#5f5fffff","u16":[24499,24499,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#5f5fffff","u16":[24499,24499,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#5f5fffff","u16":[24499,24499,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#8080ffff","out":{"hex":"#6f6fffff","u16":[28495,28495,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#8080ffff","out":{"hex":"#6f6fffff","u16":[28495,28495,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#8080ffff","out":{"hex":"#4545ffff","u16":[17677,17677,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#8080ffff","out":{"hex":"#4545ffff","u16":[17677,17677,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#2525ffff","u16":[9523,9523,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#2525ffff","u16":[9523,9523,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#0000e4ff","u16":[0,0,58648,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#0000e4ff","u16":[0,0,58648,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#8080ffff","out":{"hex":"#0000a5ff","u16":[0,0,42320,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#8080ffff","out":{"hex":"#0000a5ff","u16":[0,0,42320,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#0000a5ff","u16":[0,0,42320,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#0000a5ff","u16":[0,0,42320,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#8080ffff","out":{"hex":"#2c2cffff","u16":[11424,11424,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#8080ffff","out":{"hex":"#2c2cffff","u16":[11424,11424,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#8080ffff","out":{"hex":"#0000dbff","u16":[0,0,56352,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#8080ffff","out":{"hex":"#0000dbff","u16":[0,0,56352,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#0000b9ff","u16":[0,0,47571,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#8080ffff","out":{"hex":"#0000b9ff","u16":[0,0,47571,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#000099ff","u16":[0,0,39419,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#8080ffff","out":{"hex":"#000099ff","u16":[0,0,39419,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#8080ffff","out":{"hex":"#00006fff","u16":[0,0,28444,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#8080ffff","out":{"hex":"#00006fff","u16":[0,0,28444,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#00006fff","u16":[0,0,28444,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#8080ffff","out":{"hex":"#00006fff","u16":[0,0,28444,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#030201ff","out":{"hex":"#593b1eff","u16":[22767,15178,7589,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#030201ff","out":{"hex":"#5d3a00ff","u16":[23803,14809,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#030201ff","out":{"hex":"#ec9d4fff","u16":[60531,40354,20177,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#030201ff","out":{"hex":"#f69900ff","u16":[63286,39374,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#030201ff","out":{"hex":"#f9d4bcff","u16":[64036,54467,48299,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#030201ff","out":{"hex":"#ffd2b5ff","u16":[65535,54079,46406,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#030201ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#030201ff","out":{"hex":"#462f17ff","u16":[18053,12035,6018,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#030201ff","out":{"hex":"#492e00ff","u16":[18874,11743,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#030201ff","out":{"hex":"#bf7f40ff","u16":[48991,32661,16330,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#030201ff","out":{"hex":"#c77c00ff","u16":[51221,31868,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#030201ff","out":{"hex":"#f0a050ff","u16":[61794,41196,20598,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#030201ff","out":{"hex":"#fb9c00ff","u16":[64607,40196,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#030201ff","out":{"hex":"#f7c5a1ff","u16":[63581,50594,41456,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#030201ff","out":{"hex":"#ffc396ff","u16":[65535,50041,38443,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#030201ff","out":{"hex":"#f7c5a1ff","u16":[63581,50594,41456,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#030201ff","out":{"hex":"#ffc396ff","u16":[65535,50041,38443,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#030201ff","out":{"hex":"#2b1c0eff","u16":[10966,7311,3655,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#030201ff","out":{"hex":"#2d1c00ff","u16":[11465,7133,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#030201ff","out":{"hex":"#744d27ff","u16":[29736,19824,9912,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#030201ff","out":{"hex":"#794b00ff","u16":[31090,19343,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#030201ff","out":{"hex":"#926131ff","u16":[37506,25004,12502,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#030201ff","out":{"hex":"#995f00ff","u16":[39213,24397,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#030201ff","out":{"hex":"#aa7139ff","u16":[43703,29135,14568,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#030201ff","out":{"hex":"#b26f00ff","u16":[45692,28428,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#030201ff","out":{"hex":"#aa7139ff","u16":[43703,29135,14568,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#030201ff","out":{"hex":"#b26f00ff","u16":[45692,28428,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#030201ff","out":{"hex":"#3a2713ff","u16":[15017,10012,5006,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#030201ff","out":{"hex":"#3d2600ff","u16":[15701,9768,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#030201ff","out":{"hex":"#9f6a35ff","u16":[40746,27164,13582,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#030201ff","out":{"hex":"#a66700ff","u16":[42601,26504,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#030201ff","out":{"hex":"#c88543ff","u16":[51394,34263,17131,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#030201ff","out":{"hex":"#d18200ff","u16":[53733,33431,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#030201ff","out":{"hex":"#e99b4eff","u16":[59887,39924,19962,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#030201ff","out":{"hex":"#f49800ff","u16":[62612,38955,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#030201ff","out":{"hex":"#e99b4eff","u16":[59887,39924,19962,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#030201ff","out":{"hex":"#f49800ff","u16":[62612,38955,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#030201ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#030201ff","out":{"hex":"#462f17ff","u16":[18053,12035,6018,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#030201ff","out":{"hex":"#492e00ff","u16":[18874,11743,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#030201ff","out":{"hex":"#bf7f40ff","u16":[48991,32661,16330,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#030201ff","out":{"hex":"#c77c00ff","u16":[51221,31868,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#030201ff","out":{"hex":"#f0a050ff","u16":[61794,41196,20598,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#030201ff","out":{"hex":"#fb9c00ff","u16":[64607,40196,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#030201ff","out":{"hex":"#f7c5a1ff","u16":[63581,50594,41456,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#030201ff","out":{"hex":"#ffc396ff","u16":[65535,50041,38443,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#030201ff","out":{"hex":"#f7c5a1ff","u16":[63581,50594,41456,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#030201ff","out":{"hex":"#ffc396ff","u16":[65535,50041,38443,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#fffefdff","out":{"hex":"#fff9f2ff","u16":[65535,63866,62152,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#fffefdff","out":{"hex":"#fff9f2ff","u16":[65535,63866,62152,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#ffe1bdff","u16":[65535,57802,48616,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#ffe1bdff","u16":[65535,57802,48616,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#ffcd85ff","u16":[65535,52696,34241,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#ffcd85ff","u16":[65535,52696,34241,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#fffefdff","out":{"hex":"#fcb700ff","u16":[64651,47128,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#fffefdff","out":{"hex":"#fcb700ff","u16":[64651,47128,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#fcb700ff","u16":[64651,47128,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#fcb700ff","u16":[64651,47128,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#fffefdff","out":{"hex":"#fffbf7ff","u16":[65535,64577,63606,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#fffefdff","out":{"hex":"#fffbf7ff","u16":[65535,64577,63606,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#fff0e0ff","u16":[65535,61676,57523,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#fff0e0ff","u16":[65535,61676,57523,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#ffe7ccff","u16":[65535,59388,52398,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#ffe7ccff","u16":[65535,59388,52398,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#fffefdff","out":{"hex":"#ffdeb5ff","u16":[65535,56989,46585,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#fffefdff","out":{"hex":"#ffdeb5ff","u16":[65535,56989,46585,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#ffdeb5ff","u16":[65535,56989,46585,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#ffdeb5ff","u16":[65535,56989,46585,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#fffefdff","out":{"hex":"#fff9f2ff","u16":[65535,63866,62152,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#fffefdff","out":{"hex":"#fff9f2ff","u16":[65535,63866,62152,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#ffe1bdff","u16":[65535,57802,48616,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#ffe1bdff","u16":[65535,57802,48616,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#ffcd85ff","u16":[65535,52696,34241,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#ffcd85ff","u16":[65535,52696,34241,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#fffefdff","out":{"hex":"#fcb700ff","u16":[64651,47128,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#fffefdff","out":{"hex":"#fcb700ff","u16":[64651,47128,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#fcb700ff","u16":[64651,47128,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#fcb700ff","u16":[64651,47128,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#fffefdff","out":{"hex":"#fff6ecff","u16":[65535,63146,60656,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#fffefdff","out":{"hex":"#fff6ecff","u16":[65535,63146,60656,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#ffd190ff","u16":[65535,53588,37111,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#ffd190ff","u16":[65535,53588,37111,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#f3b100ff","u16":[62481,45546,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#f3b100ff","u16":[62481,45546,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#fffefdff","out":{"hex":"#c89200ff","u16":[51328,37416,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#fffefdff","out":{"hex":"#c89200ff","u16":[51328,37416,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#c89200ff","u16":[51328,37416,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#c89200ff","u16":[51328,37416,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#fffefdff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#fffefdff","out":{"hex":"#fff0e0ff","u16":[65535,61676,57523,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#fffefdff","out":{"hex":"#fff0e0ff","u16":[65535,61676,57523,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#f7b400ff","u16":[63577,46345,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#fffefdff","out":{"hex":"#f7b400ff","u16":[63577,46345,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#b38300ff","u16":[46101,33605,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#fffefdff","out":{"hex":"#b38300ff","u16":[46101,33605,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#fffefdff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#666667ff","u16":[26087,26293,26498,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#4a6a82ff","u16":[18907,27241,33317,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#7f8081ff","out":{"hex":"#8a8b8cff","u16":[35386,35665,35944,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#7f8081ff","out":{"hex":"#6490b0ff","u16":[25646,36950,45193,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#959798ff","u16":[38378,38680,38982,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#6c9cbfff","u16":[27814,40074,49013,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#9d9fa0ff","u16":[40449,40768,41086,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#72a4c9ff","u16":[29316,42237,51659,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#7f8081ff","out":{"hex":"#a5a6a8ff","u16":[42401,42735,43069,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#7f8081ff","out":{"hex":"#78acd3ff","u16":[30730,44275,54152,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#a5a6a8ff","u16":[42401,42735,43069,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#78acd3ff","u16":[30730,44275,54152,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#757677ff","u16":[30075,30311,30548,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#557a95ff","u16":[21797,31404,38409,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#7f8081ff","out":{"hex":"#848586ff","u16":[33835,34101,34368,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#7f8081ff","out":{"hex":"#5f89a8ff","u16":[24522,35330,43212,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#898a8bff","u16":[35206,35483,35760,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#638fafff","u16":[25516,36762,44963,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#8d8e8fff","u16":[36194,36479,36764,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#6693b4ff","u16":[26231,37793,46224,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#7f8081ff","out":{"hex":"#919293ff","u16":[37150,37443,37735,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#7f8081ff","out":{"hex":"#6997b9ff","u16":[26925,38792,47446,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#919293ff","u16":[37150,37443,37735,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#6997b9ff","u16":[26925,38792,47446,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#7a7b7cff","u16":[31478,31726,31974,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#59809cff","u16":[22814,32870,40202,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#7f8081ff","out":{"hex":"#727374ff","u16":[29313,29544,29775,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#7f8081ff","out":{"hex":"#537792ff","u16":[21245,30609,37437,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#6e6f70ff","u16":[28391,28615,28838,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#50738dff","u16":[20577,29646,36259,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#6c6d6dff","u16":[27675,27893,28111,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#4e708aff","u16":[20058,28899,35345,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#7f8081ff","out":{"hex":"#696a6aff","u16":[26937,27149,27361,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#7f8081ff","out":{"hex":"#4c6d86ff","u16":[19522,28127,34402,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#696a6aff","u16":[26937,27149,27361,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#4c6d86ff","u16":[19522,28127,34402,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#737475ff","u16":[29491,29723,29955,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#537893ff","u16":[21374,30794,37664,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#7f8081ff","out":{"hex":"#58595aff","u16":[22704,22883,23062,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#7f8081ff","out":{"hex":"#405c71ff","u16":[16455,23708,28996,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#4b4b4cff","u16":[19166,19317,19468,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#364e5fff","u16":[13891,20013,24478,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#3e3e3fff","u16":[15882,16007,16132,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#2d414fff","u16":[11510,16584,20283,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#7f8081ff","out":{"hex":"#2d2d2dff","u16":[11460,11550,11641,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#7f8081ff","out":{"hex":"#202f39ff","u16":[8306,11967,14636,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#2d2d2dff","u16":[11460,11550,11641,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#202f39ff","u16":[8306,11967,14636,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#4f4f50ff","u16":[20186,20345,20504,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8081ff","out":{"hex":"#395264ff","u16":[14630,21078,25780,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#7f8081ff","out":{"hex":"#3c3d3dff","u16":[15540,15663,15785,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#7f8081ff","out":{"hex":"#2c3f4dff","u16":[11263,16228,19847,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#333334ff","u16":[13119,13222,13325,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8081ff","out":{"hex":"#253541ff","u16":[9508,13698,16755,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#2a2b2bff","u16":[10871,10956,11042,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8081ff","out":{"hex":"#1f2c36ff","u16":[7878,11351,13883,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#7f8081ff","out":{"hex":"#1f1f1fff","u16":[7844,7906,7968,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#7f8081ff","out":{"hex":"#162027ff","u16":[5685,8191,10018,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#1f1f1fff","u16":[7844,7906,7968,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8081ff","out":{"hex":"#162027ff","u16":[5685,8191,10018,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#010000ff","out":{"hex":"#840000ff","u16":[33943,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#010000ff","out":{"hex":"#840000ff","u16":[33943,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ff8e8eff","u16":[65535,36510,36510,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ff8e8eff","u16":[65535,36510,36510,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#010000ff","out":{"hex":"#ffd0d0ff","u16":[65535,53428,53428,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#010000ff","out":{"hex":"#ffd0d0ff","u16":[65535,53428,53428,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#010000ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#010000ff","out":{"hex":"#690000ff","u16":[26910,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#010000ff","out":{"hex":"#690000ff","u16":[26910,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ff4e4eff","u16":[65535,19931,19931,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ff4e4eff","u16":[65535,19931,19931,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#010000ff","out":{"hex":"#ff9494ff","u16":[65535,38000,38000,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#010000ff","out":{"hex":"#ff9494ff","u16":[65535,38000,38000,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#010000ff","out":{"hex":"#ffbfbfff","u16":[65535,49111,49111,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#010000ff","out":{"hex":"#ffbfbfff","u16":[65535,49111,49111,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#010000ff","out":{"hex":"#ffbfbfff","u16":[65535,49111,49111,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#010000ff","out":{"hex":"#ffbfbfff","u16":[65535,49111,49111,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#010000ff","out":{"hex":"#400000ff","u16":[16333,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#010000ff","out":{"hex":"#400000ff","u16":[16333,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ad0000ff","u16":[44339,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ad0000ff","u16":[44339,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#010000ff","out":{"hex":"#da0000ff","u16":[55927,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#010000ff","out":{"hex":"#da0000ff","u16":[55927,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#010000ff","out":{"hex":"#fe0000ff","u16":[65170,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#010000ff","out":{"hex":"#fe0000ff","u16":[65170,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#010000ff","out":{"hex":"#fe0000ff","u16":[65170,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#010000ff","out":{"hex":"#fe0000ff","u16":[65170,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#010000ff","out":{"hex":"#570000ff","u16":[22381,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#010000ff","out":{"hex":"#570000ff","u16":[22381,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ec0000ff","u16":[60760,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ec0000ff","u16":[60760,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#010000ff","out":{"hex":"#ff5e5eff","u16":[65535,24128,24128,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#010000ff","out":{"hex":"#ff5e5eff","u16":[65535,24128,24128,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#010000ff","out":{"hex":"#ff8b8bff","u16":[65535,35737,35737,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#010000ff","out":{"hex":"#ff8b8bff","u16":[65535,35737,35737,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#010000ff","out":{"hex":"#ff8b8bff","u16":[65535,35737,35737,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#010000ff","out":{"hex":"#ff8b8bff","u16":[65535,35737,35737,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#010000ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#010000ff","out":{"hex":"#690000ff","u16":[26910,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#010000ff","out":{"hex":"#690000ff","u16":[26910,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ff4e4eff","u16":[65535,19931,19931,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#010000ff","out":{"hex":"#ff4e4eff","u16":[65535,19931,19931,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#010000ff","out":{"hex":"#ff9494ff","u16":[65535,38000,38000,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#010000ff","out":{"hex":"#ff9494ff","u16":[65535,38000,38000,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#010000ff","out":{"hex":"#ffbfbfff","u16":[65535,49111,49111,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#010000ff","out":{"hex":"#ffbfbfff","u16":[65535,49111,49111,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#010000ff","out":{"hex":"#ffbfbfff","u16":[65535,49111,49111,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#010000ff","out":{"hex":"#ffbfbfff","u16":[65535,49111,49111,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#000001ff","out":{"hex":"#0000d8ff","u16":[0,0,55455,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#000001ff","out":{"hex":"#0000d8ff","u16":[0,0,55455,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#000001ff","out":{"hex":"#a5a5ffff","u16":[42499,42499,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#000001ff","out":{"hex":"#a5a5ffff","u16":[42499,42499,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#000001ff","out":{"hex":"#d8d8ffff","u16":[55466,55466,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#000001ff","out":{"hex":"#d8d8ffff","u16":[55466,55466,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#000001ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#000001ff","out":{"hex":"#0000abff","u16":[0,0,43965,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#000001ff","out":{"hex":"#0000abff","u16":[0,0,43965,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#000001ff","out":{"hex":"#7e7effff","u16":[32462,32462,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#000001ff","out":{"hex":"#7e7effff","u16":[32462,32462,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#000001ff","out":{"hex":"#aaaaffff","u16":[43563,43563,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#000001ff","out":{"hex":"#aaaaffff","u16":[43563,43563,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#000001ff","out":{"hex":"#cacaffff","u16":[52003,52003,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#000001ff","out":{"hex":"#cacaffff","u16":[52003,52003,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#000001ff","out":{"hex":"#cacaffff","u16":[52003,52003,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#000001ff","out":{"hex":"#cacaffff","u16":[52003,52003,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#000001ff","out":{"hex":"#000068ff","u16":[0,0,26683,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#000001ff","out":{"hex":"#000068ff","u16":[0,0,26683,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#000001ff","out":{"hex":"#2a2affff","u16":[10864,10864,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#000001ff","out":{"hex":"#2a2affff","u16":[10864,10864,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#000001ff","out":{"hex":"#5353ffff","u16":[21241,21241,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#000001ff","out":{"hex":"#5353ffff","u16":[21241,21241,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#000001ff","out":{"hex":"#6b6bffff","u16":[27543,27543,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#000001ff","out":{"hex":"#6b6bffff","u16":[27543,27543,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#000001ff","out":{"hex":"#6b6bffff","u16":[27543,27543,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#000001ff","out":{"hex":"#6b6bffff","u16":[27543,27543,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#000001ff","out":{"hex":"#00008eff","u16":[0,0,36565,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#000001ff","out":{"hex":"#00008eff","u16":[0,0,36565,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#000001ff","out":{"hex":"#6060ffff","u16":[24635,24635,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#000001ff","out":{"hex":"#6060ffff","u16":[24635,24635,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#000001ff","out":{"hex":"#8787ffff","u16":[34614,34614,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#000001ff","out":{"hex":"#8787ffff","u16":[34614,34614,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#000001ff","out":{"hex":"#a3a3ffff","u16":[41955,41955,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#000001ff","out":{"hex":"#a3a3ffff","u16":[41955,41955,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#000001ff","out":{"hex":"#a3a3ffff","u16":[41955,41955,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#000001ff","out":{"hex":"#a3a3ffff","u16":[41955,41955,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#000001ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#000001ff","out":{"hex":"#0000abff","u16":[0,0,43965,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#000001ff","out":{"hex":"#0000abff","u16":[0,0,43965,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#000001ff","out":{"hex":"#7e7effff","u16":[32462,32462,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#000001ff","out":{"hex":"#7e7effff","u16":[32462,32462,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#000001ff","out":{"hex":"#aaaaffff","u16":[43563,43563,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#000001ff","out":{"hex":"#aaaaffff","u16":[43563,43563,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#000001ff","out":{"hex":"#cacaffff","u16":[52003,52003,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#000001ff","out":{"hex":"#cacaffff","u16":[52003,52003,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#000001ff","out":{"hex":"#cacaffff","u16":[52003,52003,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#000001ff","out":{"hex":"#cacaffff","u16":[52003,52003,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#fefffeff","out":{"hex":"#edffedff","u16":[60899,65535,60899,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#fefffeff","out":{"hex":"#edffedff","u16":[60899,65535,60899,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#91ff91ff","u16":[37284,65535,37284,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#91ff91ff","u16":[37284,65535,37284,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#00f900ff","u16":[0,63944,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#00f900ff","u16":[0,63944,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#fefffeff","out":{"hex":"#00e400ff","u16":[0,58522,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#fefffeff","out":{"hex":"#00e400ff","u16":[0,58522,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#00e400ff","u16":[0,58522,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#00e400ff","u16":[0,58522,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#fefffeff","out":{"hex":"#f6fff6ff","u16":[63134,65535,63134,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#fefffeff","out":{"hex":"#f6fff6ff","u16":[63134,65535,63134,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#d0ffd0ff","u16":[53513,65535,53513,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#d0ffd0ff","u16":[53513,65535,53513,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#aeffaeff","u16":[44636,65535,44636,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#aeffaeff","u16":[44636,65535,44636,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#fefffeff","out":{"hex":"#80ff80ff","u16":[32855,65535,32855,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#fefffeff","out":{"hex":"#80ff80ff","u16":[32855,65535,32855,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#80ff80ff","u16":[32855,65535,32855,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#80ff80ff","u16":[32855,65535,32855,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#fefffeff","out":{"hex":"#edffedff","u16":[60899,65535,60899,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#fefffeff","out":{"hex":"#edffedff","u16":[60899,65535,60899,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#91ff91ff","u16":[37284,65535,37284,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#91ff91ff","u16":[37284,65535,37284,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#00f900ff","u16":[0,63944,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#00f900ff","u16":[0,63944,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#fefffeff","out":{"hex":"#00e400ff","u16":[0,58522,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#fefffeff","out":{"hex":"#00e400ff","u16":[0,58522,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#00e400ff","u16":[0,58522,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#00e400ff","u16":[0,58522,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#fefffeff","out":{"hex":"#e4ffe4ff","u16":[58561,65535,58561,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#fefffeff","out":{"hex":"#e4ffe4ff","u16":[58561,65535,58561,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#00fc00ff","u16":[0,64795,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#00fc00ff","u16":[0,64795,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#00dc00ff","u16":[0,56574,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#00dc00ff","u16":[0,56574,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#fefffeff","out":{"hex":"#00b500ff","u16":[0,46585,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#fefffeff","out":{"hex":"#00b500ff","u16":[0,46585,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#00b500ff","u16":[0,46585,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#00b500ff","u16":[0,46585,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#fefffeff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#fefffeff","out":{"hex":"#d0ffd0ff","u16":[53513,65535,53513,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#fefffeff","out":{"hex":"#d0ffd0ff","u16":[53513,65535,53513,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#00e000ff","u16":[0,57558,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#fefffeff","out":{"hex":"#00e000ff","u16":[0,57558,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#00a300ff","u16":[0,41915,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#fefffeff","out":{"hex":"#00a300ff","u16":[0,41915,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#fefffeff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#3290c1ff","u16":[12967,36988,49529,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#0091c7ff","u16":[0,37260,51067,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#3daee9ff","out":{"hex":"#4bbaf7ff","u16":[19330,47912,63602,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#3daee9ff","out":{"hex":"#2bbcffff","u16":[11102,48251,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#7ac6f9ff","u16":[31305,50791,63873,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#6dc7ffff","u16":[28119,51064,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#92cef9ff","u16":[37530,52828,64075,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#89ceffff","u16":[35335,53057,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#3daee9ff","out":{"hex":"#a6d5faff","u16":[42710,54775,64277,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#3daee9ff","out":{"hex":"#a0d6ffff","u16":[41108,54964,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#a6d5faff","u16":[42710,54775,64277,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#a0d6ffff","u16":[41108,54964,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#39a4dbff","u16":[14749,42070,56336,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#00a5e2ff","u16":[0,42380,58085,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#3daee9ff","out":{"hex":"#3fb3f0ff","u16":[16117,45974,61563,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#3daee9ff","out":{"hex":"#00b4f7ff","u16":[0,46312,63474,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#41b9f7ff","u16":[16627,47428,63511,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#00baffff","u16":[0,47778,65483,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#52bcf8ff","u16":[21175,48262,63634,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#38bdffff","u16":[14469,48592,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#3daee9ff","out":{"hex":"#61bff8ff","u16":[24886,49070,63709,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#3daee9ff","out":{"hex":"#4ec0ffff","u16":[19992,49381,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#61bff8ff","u16":[24886,49070,63709,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#4ec0ffff","u16":[19992,49381,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#3ba7e0ff","u16":[15084,43027,57617,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#00a9e7ff","u16":[0,43344,59406,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#3daee9ff","out":{"hex":"#369bd0ff","u16":[13973,39859,53374,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#3daee9ff","out":{"hex":"#009cd6ff","u16":[0,40152,55031,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#3596c9ff","u16":[13498,38503,51559,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#0097cfff","u16":[0,38787,53160,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#3392c3ff","u16":[13128,37447,50145,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#0093c9ff","u16":[0,37723,51702,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#3daee9ff","out":{"hex":"#328dbdff","u16":[12745,36355,48682,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#3daee9ff","out":{"hex":"#008fc3ff","u16":[0,36623,50194,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#328dbdff","u16":[12745,36355,48682,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#008fc3ff","u16":[0,36623,50194,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#379dd3ff","u16":[14165,40405,54105,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#009ed9ff","u16":[0,40702,55785,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#3daee9ff","out":{"hex":"#2a79a2ff","u16":[10905,31107,41654,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#3daee9ff","out":{"hex":"#007aa7ff","u16":[0,31336,42948,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#246689ff","u16":[9206,26259,35163,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#00678dff","u16":[0,26453,36255,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#1e5571ff","u16":[7628,21759,29137,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#005575ff","u16":[0,21919,30042,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#3daee9ff","out":{"hex":"#153d52ff","u16":[5504,15701,21025,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#3daee9ff","out":{"hex":"#003e54ff","u16":[0,15817,21678,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#153d52ff","u16":[5504,15701,21025,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#003e54ff","u16":[0,15817,21678,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#246689ff","u16":[9200,26243,35140,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee9ff","out":{"hex":"#00678dff","u16":[0,26435,36232,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#3daee9ff","out":{"hex":"#1c4f69ff","u16":[7083,20204,27054,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#3daee9ff","out":{"hex":"#004f6dff","u16":[0,20352,27894,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#174259ff","u16":[5979,17055,22838,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee9ff","out":{"hex":"#00435cff","u16":[0,17181,23547,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#13374aff","u16":[4954,14132,18924,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee9ff","out":{"hex":"#00374cff","u16":[0,14236,19512,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#3daee9ff","out":{"hex":"#0e2835ff","u16":[3575,10198,13655,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#3daee9ff","out":{"hex":"#002837ff","u16":[0,10273,14080,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#0e2835ff","u16":[3575,10198,13655,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee9ff","out":{"hex":"#002837ff","u16":[0,10273,14080,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#232629ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#232629ff","out":{"hex":"#303438ff","u16":[12270,13321,14373,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#232629ff","out":{"hex":"#203645ff","u16":[8218,13767,17737,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#232629ff","out":{"hex":"#3b4045ff","u16":[15165,16465,17765,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#232629ff","out":{"hex":"#284255ff","u16":[10157,17017,21923,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#232629ff","out":{"hex":"#42484dff","u16":[16963,18417,19871,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#232629ff","out":{"hex":"#2c4a5fff","u16":[11361,19034,24522,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#232629ff","out":{"hex":"#484e55ff","u16":[18556,20147,21738,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#232629ff","out":{"hex":"#305168ff","u16":[12429,20822,26825,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#232629ff","out":{"hex":"#484e55ff","u16":[18556,20147,21738,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#232629ff","out":{"hex":"#305168ff","u16":[12429,20822,26825,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#232629ff","out":{"hex":"#070808ff","u16":[1838,1996,2153,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#232629ff","out":{"hex":"#05080aff","u16":[1231,2063,2657,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#232629ff","out":{"hex":"#2a2d31ff","u16":[10765,11688,12611,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#232629ff","out":{"hex":"#1c2f3dff","u16":[7210,12080,15563,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#232629ff","out":{"hex":"#313539ff","u16":[12511,13583,14656,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#232629ff","out":{"hex":"#213746ff","u16":[8380,14038,18086,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#232629ff","out":{"hex":"#353a3eff","u16":[13649,14819,15989,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#232629ff","out":{"hex":"#243c4dff","u16":[9142,15316,19732,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#232629ff","out":{"hex":"#393e43ff","u16":[14684,15942,17201,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#232629ff","out":{"hex":"#264053ff","u16":[9835,16476,21227,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#232629ff","out":{"hex":"#393e43ff","u16":[14684,15942,17201,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#232629ff","out":{"hex":"#264053ff","u16":[9835,16476,21227,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#232629ff","out":{"hex":"#222528ff","u16":[8701,9447,10193,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#232629ff","out":{"hex":"#172631ff","u16":[5828,9763,12579,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#232629ff","out":{"hex":"#202225ff","u16":[8156,8855,9555,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#232629ff","out":{"hex":"#15242eff","u16":[5463,9152,11791,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#232629ff","out":{"hex":"#1f2124ff","u16":[7926,8605,9284,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#232629ff","out":{"hex":"#15232dff","u16":[5308,8893,11457,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#232629ff","out":{"hex":"#1e2123ff","u16":[7747,8411,9075,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#232629ff","out":{"hex":"#14222cff","u16":[5189,8693,11199,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#232629ff","out":{"hex":"#1d2022ff","u16":[7564,8212,8860,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#232629ff","out":{"hex":"#14212bff","u16":[5066,8487,10934,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#232629ff","out":{"hex":"#1d2022ff","u16":[7564,8212,8860,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#232629ff","out":{"hex":"#14212bff","u16":[5066,8487,10934,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#232629ff","out":{"hex":"#202225ff","u16":[8127,8824,9521,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#232629ff","out":{"hex":"#15232eff","u16":[5444,9120,11749,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#232629ff","out":{"hex":"#181a1dff","u16":[6257,6793,7330,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#232629ff","out":{"hex":"#101b23ff","u16":[4191,7021,9045,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#232629ff","out":{"hex":"#151618ff","u16":[5282,5735,6188,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#232629ff","out":{"hex":"#0e171eff","u16":[3538,5927,7636,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#232629ff","out":{"hex":"#111214ff","u16":[4377,4752,5127,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#232629ff","out":{"hex":"#0b1319ff","u16":[2932,4911,6327,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#232629ff","out":{"hex":"#0c0d0eff","u16":[3158,3429,3700,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#232629ff","out":{"hex":"#080e12ff","u16":[2115,3544,4566,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#232629ff","out":{"hex":"#0c0d0eff","u16":[3158,3429,3700,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#232629ff","out":{"hex":"#080e12ff","u16":[2115,3544,4566,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#232629ff","out":{"hex":"#17191bff","u16":[5907,6413,6920,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#232629ff","out":{"hex":"#0f1a21ff","u16":[3957,6628,8539,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#232629ff","out":{"hex":"#121315ff","u16":[4547,4937,5327,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#232629ff","out":{"hex":"#0c141aff","u16":[3046,5103,6574,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#232629ff","out":{"hex":"#0f1011ff","u16":[3839,4168,4497,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#232629ff","out":{"hex":"#0a1116ff","u16":[2571,4308,5550,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#232629ff","out":{"hex":"#0c0d0eff","u16":[3181,3454,3726,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#232629ff","out":{"hex":"#080e12ff","u16":[2131,3569,4598,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#232629ff","out":{"hex":"#090a0aff","u16":[2295,2492,2689,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#232629ff","out":{"hex":"#060a0dff","u16":[1537,2576,3318,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#232629ff","out":{"hex":"#090a0aff","u16":[2295,2492,2689,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#232629ff","out":{"hex":"#060a0dff","u16":[1537,2576,3318,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#c8ccd0ff","u16":[51452,52431,53393,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#b0d0ebff","u16":[45139,53353,60305,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#e8eaebff","u16":[59643,60018,60392,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#dfebf6ff","u16":[57368,60376,63227,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#f2f3f4ff","u16":[62290,62491,62693,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#eef4faff","u16":[61084,62684,64244,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#f3f7fcff","u16":[62497,63580,64645,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#f9fafaff","u16":[64065,64155,64245,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#f7fafdff","u16":[63532,64241,64944,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#fcfcfdff","u16":[64808,64852,64897,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#fbfdfeff","u16":[64547,64895,65242,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#fcfcfdff","u16":[64808,64852,64897,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#fbfdfeff","u16":[64547,64895,65242,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#e4e5e7ff","u16":[58483,58937,59388,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#d9e7f4ff","u16":[55711,59369,62792,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#ced1d4ff","u16":[52861,53727,54581,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#b8d4edff","u16":[47346,54545,60782,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#a9cce9ff","u16":[43450,52465,59955,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#bdc1c6ff","u16":[48453,49685,50888,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#9dc6e7ff","u16":[40227,50838,59326,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#b5babfff","u16":[46408,47826,49202,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#8fbfe4ff","u16":[36660,49145,58689,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#b5babfff","u16":[46408,47826,49202,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#8fbfe4ff","u16":[36660,49145,58689,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#d6d9dcff","u16":[55099,55795,56484,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#c5dcf0ff","u16":[50752,56455,61563,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#a1a8aeff","u16":[41434,43111,44721,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#71aed8ff","u16":[29134,44654,55482,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#6093b6ff","u16":[24594,37696,46836,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#71757aff","u16":[28983,30156,31282,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#4f7a97ff","u16":[20379,31236,38810,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#515558ff","u16":[20914,21761,22573,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#39586dff","u16":[14706,22540,28005,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#515558ff","u16":[20914,21761,22573,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#39586dff","u16":[14706,22540,28005,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#6d7176ff","u16":[28014,29149,30238,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#eff0f1ff","out":{"hex":"#4d7592ff","u16":[19698,30193,37514,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#54575bff","u16":[21568,22441,23279,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#eff0f1ff","out":{"hex":"#3b5a70ff","u16":[15165,23244,28881,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#eff0f1ff","out":{"hex":"#324c5fff","u16":[12802,19622,24380,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#3b3d3fff","u16":[15087,15697,16284,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#eff0f1ff","out":{"hex":"#293f4fff","u16":[10608,16260,20202,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#2a2c2eff","u16":[10887,11328,11750,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#eff0f1ff","out":{"hex":"#1e2e39ff","u16":[7655,11733,14578,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#2a2c2eff","u16":[10887,11328,11750,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#eff0f1ff","out":{"hex":"#1e2e39ff","u16":[7655,11733,14578,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#f7f7f7ff","u16":[63542,63542,63542,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#fbf6f6ff","u16":[64548,63267,63267,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#f1dfdfff","u16":[62063,57287,57287,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#eacbcbff","u16":[60119,52255,52255,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#e2b5b5ff","u16":[58096,46561,46561,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#e2b5b5ff","u16":[58096,46561,46561,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#fafafaff","u16":[64156,64156,64156,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#fcf9f9ff","u16":[64850,63967,63967,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#f8eeeeff","u16":[63630,61106,61106,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#e8e8e8ff","u16":[59693,59693,59693,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#f4e5e5ff","u16":[62696,58850,58850,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#e0e0e0ff","u16":[57652,57652,57652,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#f0dcdcff","u16":[61744,56485,56485,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#e0e0e0ff","u16":[57652,57652,57652,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#f0dcdcff","u16":[61744,56485,56485,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#f7f7f7ff","u16":[63542,63542,63542,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#fbf6f6ff","u16":[64548,63267,63267,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#f1dfdfff","u16":[62063,57287,57287,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#eacbcbff","u16":[60119,52255,52255,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#e2b5b5ff","u16":[58096,46561,46561,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#e2b5b5ff","u16":[58096,46561,46561,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#f5f5f5ff","u16":[62920,62920,62920,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#faf3f3ff","u16":[64243,62557,62557,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#ebcfcfff","u16":[60448,53133,53133,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#b9b9b9ff","u16":[47575,47575,47575,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#dfadadff","u16":[57403,44467,44467,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#979797ff","u16":[38725,38725,38725,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#d38181ff","u16":[54149,33046,33046,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#979797ff","u16":[38725,38725,38725,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#d38181ff","u16":[54149,33046,33046,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#fcfcfcff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#fcfcfcff","out":{"hex":"#f8eeeeff","u16":[63630,61106,61106,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#fcfcfcff","out":{"hex":"#e1b1b1ff","u16":[57751,45529,45529,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#868686ff","u16":[34535,34535,34535,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#fcfcfcff","out":{"hex":"#ce6969ff","u16":[52819,27011,27011,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#fcfcfcff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#31363bff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#31363bff","out":{"hex":"#3b4148ff","u16":[15268,16826,18384,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#31363bff","out":{"hex":"#274458ff","u16":[10027,17381,22614,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#31363bff","out":{"hex":"#464d54ff","u16":[17874,19698,21522,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#31363bff","out":{"hex":"#2e4f67ff","u16":[11739,20348,26475,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#31363bff","out":{"hex":"#4c545cff","u16":[19563,21559,23556,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#31363bff","out":{"hex":"#325771ff","u16":[12848,22271,28977,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#31363bff","out":{"hex":"#525a63ff","u16":[21093,23246,25398,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#31363bff","out":{"hex":"#365d7aff","u16":[13853,24013,31243,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#31363bff","out":{"hex":"#525a63ff","u16":[21093,23246,25398,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#31363bff","out":{"hex":"#365d7aff","u16":[13853,24013,31243,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#31363bff","out":{"hex":"#23272aff","u16":[9057,9981,10905,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#31363bff","out":{"hex":"#172834ff","u16":[5948,10310,13415,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#31363bff","out":{"hex":"#363c41ff","u16":[13977,15404,16830,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#31363bff","out":{"hex":"#243e51ff","u16":[9180,15912,20703,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#31363bff","out":{"hex":"#3c4248ff","u16":[15447,17023,18599,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#31363bff","out":{"hex":"#274459ff","u16":[10145,17585,22880,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#31363bff","out":{"hex":"#40474dff","u16":[16448,18126,19804,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#31363bff","out":{"hex":"#2a495fff","u16":[10802,18724,24362,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#31363bff","out":{"hex":"#444b51ff","u16":[17380,19154,20927,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#31363bff","out":{"hex":"#2c4d64ff","u16":[11415,19786,25743,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#31363bff","out":{"hex":"#444b51ff","u16":[17380,19154,20927,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#31363bff","out":{"hex":"#2c4d64ff","u16":[11415,19786,25743,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#31363bff","out":{"hex":"#2f3439ff","u16":[12179,13421,14664,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#31363bff","out":{"hex":"#1f3646ff","u16":[7999,13864,18039,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#31363bff","out":{"hex":"#2c3135ff","u16":[11410,12574,13738,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#31363bff","out":{"hex":"#1d3342ff","u16":[7494,12989,16900,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#31363bff","out":{"hex":"#2b3034ff","u16":[11084,12215,13346,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#31363bff","out":{"hex":"#1c3140ff","u16":[7280,12618,16418,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#31363bff","out":{"hex":"#2a2e33ff","u16":[10832,11937,13043,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#31363bff","out":{"hex":"#1c303eff","u16":[7114,12331,16044,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#31363bff","out":{"hex":"#292d32ff","u16":[10573,11652,12731,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#31363bff","out":{"hex":"#1b2f3dff","u16":[6944,12036,15660,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#31363bff","out":{"hex":"#292d32ff","u16":[10573,11652,12731,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#31363bff","out":{"hex":"#1b2f3dff","u16":[6944,12036,15660,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#31363bff","out":{"hex":"#2c3135ff","u16":[11378,12539,13700,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#31363bff","out":{"hex":"#1d3242ff","u16":[7473,12953,16854,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#31363bff","out":{"hex":"#222629ff","u16":[8760,9654,10548,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#31363bff","out":{"hex":"#162732ff","u16":[5753,9972,12975,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#31363bff","out":{"hex":"#1d2023ff","u16":[7395,8149,8904,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#31363bff","out":{"hex":"#13212bff","u16":[4857,8418,10953,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#31363bff","out":{"hex":"#181a1dff","u16":[6128,6753,7378,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#31363bff","out":{"hex":"#101b23ff","u16":[4024,6976,9076,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#31363bff","out":{"hex":"#111315ff","u16":[4422,4873,5324,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#31363bff","out":{"hex":"#0b1419ff","u16":[2904,5034,6549,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#31363bff","out":{"hex":"#111315ff","u16":[4422,4873,5324,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#31363bff","out":{"hex":"#0b1419ff","u16":[2904,5034,6549,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#31363bff","out":{"hex":"#202327ff","u16":[8230,9070,9910,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#31363bff","out":{"hex":"#15242fff","u16":[5405,9369,12191,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#31363bff","out":{"hex":"#191b1eff","u16":[6336,6983,7630,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#31363bff","out":{"hex":"#101c25ff","u16":[4161,7213,9385,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#31363bff","out":{"hex":"#151719ff","u16":[5349,5894,6441,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#31363bff","out":{"hex":"#0e181fff","u16":[3513,6089,7923,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#31363bff","out":{"hex":"#111315ff","u16":[4433,4885,5337,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#31363bff","out":{"hex":"#0b141aff","u16":[2911,5046,6565,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#31363bff","out":{"hex":"#0c0e0fff","u16":[3199,3525,3851,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#31363bff","out":{"hex":"#080e12ff","u16":[2101,3641,4737,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#31363bff","out":{"hex":"#0c0e0fff","u16":[3199,3525,3851,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#31363bff","out":{"hex":"#080e12ff","u16":[2101,3641,4737,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#292e31ff","u16":[10657,11842,12631,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#1b303aff","u16":[6950,12323,14943,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#353b3fff","u16":[13684,15204,16218,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#233e4bff","u16":[8923,15823,19187,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#3c4348ff","u16":[15510,17233,18382,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#274655ff","u16":[10114,17934,21747,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#434a4fff","u16":[17108,19009,20277,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#2b4d5dff","u16":[11156,19782,23989,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#434a4fff","u16":[17108,19009,20277,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#2b4d5dff","u16":[11156,19782,23989,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#23272aff","u16":[9019,10021,10689,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#172931ff","u16":[5881,10429,12646,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#2b2f32ff","u16":[10929,12144,12953,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#1c313cff","u16":[7127,12638,15325,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#2f3438ff","u16":[12133,13482,14380,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#1f3742ff","u16":[7912,14030,17013,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#33393dff","u16":[13209,14677,15655,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#223b48ff","u16":[8613,15273,18521,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#33393dff","u16":[13209,14677,15655,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#223b48ff","u16":[8613,15273,18521,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#1a1d1fff","u16":[6713,7459,7956,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#111e25ff","u16":[4377,7762,9413,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#181b1dff","u16":[6294,6993,7459,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#101c22ff","u16":[4104,7278,8825,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#181a1cff","u16":[6116,6796,7249,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#101c21ff","u16":[3988,7072,8576,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#171a1cff","u16":[5979,6644,7086,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#0f1b21ff","u16":[3899,6914,8384,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#17191bff","u16":[5838,6487,6919,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#0f1a20ff","u16":[3807,6751,8186,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#17191bff","u16":[5838,6487,6919,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#0f1a20ff","u16":[3807,6751,8186,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#181b1dff","u16":[6270,6966,7431,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#101c22ff","u16":[4088,7250,8791,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#131516ff","u16":[4827,5363,5721,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#0c161aff","u16":[3148,5581,6768,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#101213ff","u16":[4075,4527,4829,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#0a1216ff","u16":[2657,4712,5713,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#0d0f10ff","u16":[3376,3752,4002,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#090f12ff","u16":[2202,3904,4734,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#090b0bff","u16":[2436,2707,2888,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#060b0dff","u16":[1589,2817,3416,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#090b0bff","u16":[2436,2707,2888,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#060b0dff","u16":[1589,2817,3416,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#121415ff","u16":[4565,5071,5410,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#1b1e20ff","out":{"hex":"#0c1519ff","u16":[2976,5278,6400,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#0e0f10ff","u16":[3514,3904,4165,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#1b1e20ff","out":{"hex":"#091013ff","u16":[2292,4063,4927,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#0c0d0eff","u16":[2967,3296,3516,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#1b1e20ff","out":{"hex":"#080d10ff","u16":[1934,3430,4159,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#0a0b0bff","u16":[2458,2731,2913,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#1b1e20ff","out":{"hex":"#060b0dff","u16":[1603,2842,3446,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#070808ff","u16":[1773,1971,2102,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#1b1e20ff","out":{"hex":"#05080aff","u16":[1157,2051,2487,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#070808ff","u16":[1773,1971,2102,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#1b1e20ff","out":{"hex":"#05080aff","u16":[1157,2051,2487,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#da4453ff","out":{"hex":"#ac3642ff","u16":[44325,13826,16876,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#da4453ff","out":{"hex":"#c10030ff","u16":[49587,0,12229,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#da4453ff","out":{"hex":"#e55260ff","u16":[58764,21174,24579,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#da4453ff","out":{"hex":"#ff2f4cff","u16":[65535,12149,19487,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#e66872ff","u16":[59210,26810,29311,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#ff5364ff","u16":[65535,21355,25644,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#e8767eff","u16":[59541,30246,32331,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#ff6572ff","u16":[65535,25934,29294,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#da4453ff","out":{"hex":"#e98188ff","u16":[59871,33267,35046,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#da4453ff","out":{"hex":"#ff747eff","u16":[65535,29701,32465,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#e98188ff","u16":[59871,33267,35046,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#ff747eff","u16":[65535,29701,32465,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#da4453ff","out":{"hex":"#c83e4cff","u16":[51370,16024,19558,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#da4453ff","out":{"hex":"#e00037ff","u16":[57468,0,14172,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#da4453ff","out":{"hex":"#e24756ff","u16":[58188,18151,22154,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#da4453ff","out":{"hex":"#fd003eff","u16":[65096,0,16054,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#e5515fff","u16":[58745,20895,24354,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#ff2d4bff","u16":[65535,11558,19173,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#e55965ff","u16":[58890,22929,26015,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#ff3c53ff","u16":[65535,15429,21436,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#da4453ff","out":{"hex":"#e6606bff","u16":[59034,24767,27558,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#da4453ff","out":{"hex":"#ff485bff","u16":[65535,18384,23444,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#e6606bff","u16":[59034,24767,27558,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#ff485bff","u16":[65535,18384,23444,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#da4453ff","out":{"hex":"#d24250ff","u16":[54052,16860,20580,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#da4453ff","out":{"hex":"#eb003aff","u16":[60468,0,14913,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#da4453ff","out":{"hex":"#c43d4bff","u16":[50373,15713,19179,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#da4453ff","out":{"hex":"#db0036ff","u16":[56352,0,13897,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#be3b48ff","u16":[48807,15224,18582,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#d40034ff","u16":[54600,0,13465,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#b93a47ff","u16":[47592,14845,18120,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#cf0033ff","u16":[53241,0,13130,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#da4453ff","out":{"hex":"#b43845ff","u16":[46338,14454,17642,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#da4453ff","out":{"hex":"#ca0032ff","u16":[51838,0,12784,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#b43845ff","u16":[46338,14454,17642,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#ca0032ff","u16":[51838,0,12784,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#da4453ff","out":{"hex":"#c53d4bff","u16":[50622,15790,19274,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#da4453ff","out":{"hex":"#dc0036ff","u16":[56631,0,13966,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#da4453ff","out":{"hex":"#982f3aff","u16":[38973,12157,14838,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#da4453ff","out":{"hex":"#aa002aff","u16":[43599,0,10752,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#802831ff","u16":[32900,10262,12526,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#8f0023ff","u16":[36805,0,9077,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#6a2128ff","u16":[27262,8504,10379,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#77001dff","u16":[30498,0,7521,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#da4453ff","out":{"hex":"#4d181dff","u16":[19672,6136,7490,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#da4453ff","out":{"hex":"#560015ff","u16":[22007,0,5427,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#4d181dff","u16":[19672,6136,7490,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#560015ff","u16":[22007,0,5427,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#da4453ff","out":{"hex":"#882a34ff","u16":[34902,10886,13289,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#da4453ff","out":{"hex":"#980025ff","u16":[39044,0,9629,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#da4453ff","out":{"hex":"#692128ff","u16":[26870,8382,10230,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#da4453ff","out":{"hex":"#75001dff","u16":[30060,0,7413,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#581c22ff","u16":[22683,7075,8636,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#da4453ff","out":{"hex":"#630018ff","u16":[25375,0,6258,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#49171cff","u16":[18796,5863,7156,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#da4453ff","out":{"hex":"#520014ff","u16":[21027,0,5185,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#da4453ff","out":{"hex":"#351014ff","u16":[13563,4230,5164,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#da4453ff","out":{"hex":"#3b000fff","u16":[15173,0,3742,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#351014ff","u16":[13563,4230,5164,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#da4453ff","out":{"hex":"#3b000fff","u16":[15173,0,3742,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#208f4fff","u16":[8223,36687,20241,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#00904bff","u16":[0,36913,19370,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#27ae60ff","out":{"hex":"#2abb67ff","u16":[10792,48147,26564,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#27ae60ff","out":{"hex":"#00bc63ff","u16":[0,48444,25420,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#2dca6fff","u16":[11635,51910,28640,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#00cb6bff","u16":[0,52230,27407,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#30d475ff","u16":[12222,54531,30086,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#00d570ff","u16":[0,54867,28790,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#27ae60ff","out":{"hex":"#32de7aff","u16":[12778,57008,31453,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#27ae60ff","out":{"hex":"#00df75ff","u16":[0,57359,30098,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#32de7aff","u16":[12778,57008,31453,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#00df75ff","u16":[0,57359,30098,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#24a35aff","u16":[9374,41822,23074,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#00a456ff","u16":[0,42080,22081,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#27ae60ff","out":{"hex":"#28b363ff","u16":[10329,46085,25426,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#27ae60ff","out":{"hex":"#00b45fff","u16":[0,46368,24331,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#2ab966ff","u16":[10683,47663,26297,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#00bb62ff","u16":[0,47956,25164,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#2bbe69ff","u16":[10939,48807,26928,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#00bf64ff","u16":[0,49107,25768,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#27ae60ff","out":{"hex":"#2cc26bff","u16":[11189,49919,27542,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#27ae60ff","out":{"hex":"#00c367ff","u16":[0,50227,26356,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#2cc26bff","u16":[11189,49919,27542,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#00c367ff","u16":[0,50227,26356,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#26a85cff","u16":[9652,43063,23759,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#00a958ff","u16":[0,43328,22736,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#27ae60ff","out":{"hex":"#239c56ff","u16":[8958,39967,22051,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#27ae60ff","out":{"hex":"#009c52ff","u16":[0,40213,21101,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#229653ff","u16":[8662,38645,21321,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#00974fff","u16":[0,38883,20403,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#219251ff","u16":[8431,37616,20754,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#00934dff","u16":[0,37848,19860,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#27ae60ff","out":{"hex":"#208e4eff","u16":[8193,36553,20167,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#27ae60ff","out":{"hex":"#008f4bff","u16":[0,36778,19299,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#208e4eff","u16":[8193,36553,20167,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#008f4bff","u16":[0,36778,19299,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#239d57ff","u16":[9056,40405,22292,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#009e53ff","u16":[0,40653,21332,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#27ae60ff","out":{"hex":"#1b7943ff","u16":[6972,31107,17162,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#27ae60ff","out":{"hex":"#007a40ff","u16":[0,31298,16423,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#176638ff","u16":[5886,26259,14488,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#006736ff","u16":[0,26421,13864,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#13552fff","u16":[4877,21759,12005,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#00552dff","u16":[0,21893,11488,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#27ae60ff","out":{"hex":"#0e3d22ff","u16":[3519,15701,8663,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#27ae60ff","out":{"hex":"#003d20ff","u16":[0,15798,8290,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#0e3d22ff","u16":[3519,15701,8663,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#003d20ff","u16":[0,15798,8290,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#176839ff","u16":[5998,26759,14763,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#27ae60ff","out":{"hex":"#006937ff","u16":[0,26923,14128,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#27ae60ff","out":{"hex":"#12502cff","u16":[4617,20601,11366,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#27ae60ff","out":{"hex":"#00512aff","u16":[0,20728,10876,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#0f4425ff","u16":[3898,17391,9595,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#27ae60ff","out":{"hex":"#004424ff","u16":[0,17498,9182,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#0d381fff","u16":[3230,14410,7951,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#27ae60ff","out":{"hex":"#00381eff","u16":[0,14499,7608,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#27ae60ff","out":{"hex":"#092816ff","u16":[2331,10398,5737,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#27ae60ff","out":{"hex":"#002915ff","u16":[0,10463,5490,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#092816ff","u16":[2331,10398,5737,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#27ae60ff","out":{"hex":"#002915ff","u16":[0,10463,5490,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#f67400ff","out":{"hex":"#ca5f00ff","u16":[51891,24469,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#f67400ff","out":{"hex":"#ca5f00ff","u16":[51891,24469,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#f67400ff","out":{"hex":"#ff823fff","u16":[65535,33536,16267,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#f67400ff","out":{"hex":"#ff823fff","u16":[65535,33536,16267,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#ff976cff","u16":[65535,38702,27799,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#ff976cff","u16":[65535,38702,27799,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#ffa483ff","u16":[65535,42090,33619,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#ffa483ff","u16":[65535,42090,33619,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#f67400ff","out":{"hex":"#ffb096ff","u16":[65535,45179,38426,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#f67400ff","out":{"hex":"#ffb096ff","u16":[65535,45179,38426,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#ffb096ff","u16":[65535,45179,38426,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#ffb096ff","u16":[65535,45179,38426,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#f67400ff","out":{"hex":"#e66d00ff","u16":[59146,27890,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#f67400ff","out":{"hex":"#e66d00ff","u16":[59146,27890,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#f67400ff","out":{"hex":"#fd7800ff","u16":[65147,30719,0,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#f67400ff","out":{"hex":"#fd7800ff","u16":[65147,30719,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#ff8037ff","u16":[65535,32827,14022,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#ff8037ff","u16":[65535,32827,14022,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#ff8649ff","u16":[65535,34461,18798,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#ff8649ff","u16":[65535,34461,18798,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#f67400ff","out":{"hex":"#ff8c57ff","u16":[65535,36006,22432,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#f67400ff","out":{"hex":"#ff8c57ff","u16":[65535,36006,22432,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#ff8c57ff","u16":[65535,36006,22432,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#ff8c57ff","u16":[65535,36006,22432,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#f67400ff","out":{"hex":"#ed7000ff","u16":[60880,28708,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#f67400ff","out":{"hex":"#ed7000ff","u16":[60880,28708,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#f67400ff","out":{"hex":"#dc6800ff","u16":[56499,26642,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#f67400ff","out":{"hex":"#dc6800ff","u16":[56499,26642,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#d56400ff","u16":[54627,25759,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#d56400ff","u16":[54627,25759,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#cf6200ff","u16":[53171,25073,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#cf6200ff","u16":[53171,25073,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#f67400ff","out":{"hex":"#c95f00ff","u16":[51666,24363,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#f67400ff","out":{"hex":"#c95f00ff","u16":[51666,24363,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#c95f00ff","u16":[51666,24363,0,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#c95f00ff","u16":[51666,24363,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#f67400ff","out":{"hex":"#de6900ff","u16":[57124,26936,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#f67400ff","out":{"hex":"#de6900ff","u16":[57124,26936,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#f67400ff","out":{"hex":"#ab5100ff","u16":[43978,20738,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#f67400ff","out":{"hex":"#ab5100ff","u16":[43978,20738,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#904400ff","u16":[37125,17506,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#904400ff","u16":[37125,17506,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#783800ff","u16":[30763,14506,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#783800ff","u16":[30763,14506,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#f67400ff","out":{"hex":"#562900ff","u16":[22198,10468,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#f67400ff","out":{"hex":"#562900ff","u16":[22198,10468,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#562900ff","u16":[22198,10468,0,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#562900ff","u16":[22198,10468,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#f67400ff","out":{"hex":"#934500ff","u16":[37799,17824,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#f67400ff","out":{"hex":"#934500ff","u16":[37799,17824,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#f67400ff","out":{"hex":"#713500ff","u16":[29100,13722,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#f67400ff","out":{"hex":"#713500ff","u16":[29100,13722,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#602d00ff","u16":[24566,11584,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#f67400ff","out":{"hex":"#602d00ff","u16":[24566,11584,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#4f2500ff","u16":[20356,9599,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#f67400ff","out":{"hex":"#4f2500ff","u16":[20356,9599,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#f67400ff","out":{"hex":"#391b00ff","u16":[14688,6927,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#f67400ff","out":{"hex":"#391b00ff","u16":[14688,6927,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#391b00ff","u16":[14688,6927,0,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#f67400ff","out":{"hex":"#391b00ff","u16":[14688,6927,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#206592ff","u16":[8339,26035,37629,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#006696ff","u16":[0,26160,38647,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#2980b9ff","out":{"hex":"#2d8bc9ff","u16":[11452,35754,51675,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#2980b9ff","out":{"hex":"#008ccfff","u16":[0,35925,53073,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#3097dbff","u16":[12446,38857,56160,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#0098e0ff","u16":[0,39043,57679,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#33a0e7ff","u16":[13133,41002,59260,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#00a0edff","u16":[0,41198,60864,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#2980b9ff","out":{"hex":"#36a7f2ff","u16":[13780,43020,62177,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#2980b9ff","out":{"hex":"#00a8f8ff","u16":[0,43226,63860,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#36a7f2ff","u16":[13780,43020,62177,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#00a8f8ff","u16":[0,43226,63860,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#2675aaff","u16":[9663,30167,43601,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#0076aeff","u16":[0,30311,44780,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#2980b9ff","out":{"hex":"#2b85c0ff","u16":[10943,34164,49377,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#2980b9ff","out":{"hex":"#0086c5ff","u16":[0,34327,50713,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#2c8bc8ff","u16":[11408,35614,51473,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#008bceff","u16":[0,35784,52866,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#2e8fceff","u16":[11742,36657,52980,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#008fd4ff","u16":[0,36832,54414,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#2980b9ff","out":{"hex":"#2f93d4ff","u16":[12065,37665,54438,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#2980b9ff","out":{"hex":"#0093daff","u16":[0,37845,55910,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#2f93d4ff","u16":[12065,37665,54438,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#0093daff","u16":[0,37845,55910,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#287bb2ff","u16":[10166,31737,45869,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#007cb7ff","u16":[0,31889,47110,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#2980b9ff","out":{"hex":"#2573a6ff","u16":[9473,29576,42746,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#2980b9ff","out":{"hex":"#0074abff","u16":[0,29717,43902,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#2470a1ff","u16":[9179,28656,41417,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#0070a6ff","u16":[0,28793,42537,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#236d9dff","u16":[8950,27942,40385,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#006da1ff","u16":[0,28076,41478,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#2980b9ff","out":{"hex":"#226a99ff","u16":[8714,27206,39321,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#2980b9ff","out":{"hex":"#006a9dff","u16":[0,27336,40385,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#226a99ff","u16":[8714,27206,39321,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#006a9dff","u16":[0,27336,40385,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#2574a7ff","u16":[9521,29723,42959,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#0074acff","u16":[0,29865,44121,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#2980b9ff","out":{"hex":"#1d5981ff","u16":[7330,22883,33073,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#2980b9ff","out":{"hex":"#005984ff","u16":[0,22992,33968,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#184b6dff","u16":[6188,19317,27919,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#004c70ff","u16":[0,19410,28675,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#143e5aff","u16":[5127,16007,23135,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#003f5cff","u16":[0,16083,23761,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#2980b9ff","out":{"hex":"#0e2d41ff","u16":[3700,11550,16694,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#2980b9ff","out":{"hex":"#002d43ff","u16":[0,11606,17146,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#0e2d41ff","u16":[3700,11550,16694,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#002d43ff","u16":[0,11606,17146,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#1a5073ff","u16":[6563,20488,29612,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#2980b9ff","out":{"hex":"#005076ff","u16":[0,20586,30413,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#2980b9ff","out":{"hex":"#143d59ff","u16":[5053,15773,22797,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#2980b9ff","out":{"hex":"#003e5bff","u16":[0,15848,23414,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#11344bff","u16":[4265,13315,19245,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#2980b9ff","out":{"hex":"#00344dff","u16":[0,13379,19766,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#0e2b3eff","u16":[3534,11034,15947,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#2980b9ff","out":{"hex":"#002b40ff","u16":[0,11086,16379,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#2980b9ff","out":{"hex":"#0a1f2dff","u16":[2550,7961,11507,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#2980b9ff","out":{"hex":"#001f2eff","u16":[0,8000,11819,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#0a1f2dff","u16":[2550,7961,11507,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#2980b9ff","out":{"hex":"#001f2eff","u16":[0,8000,11819,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#677172ff","u16":[26411,29114,29322,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#44787bff","u16":[17515,30716,31514,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#899798ff","u16":[35273,38884,39162,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#5ba0a4ff","u16":[23393,41024,42089,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#94a4a5ff","u16":[38152,42057,42357,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#62adb1ff","u16":[25302,44371,45523,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#9cacadff","u16":[40150,44260,44576,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#68b6baff","u16":[26628,46696,47908,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#a4b4b6ff","u16":[42036,46339,46670,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#6cbec3ff","u16":[27878,48889,50158,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#a4b4b6ff","u16":[42036,46339,46670,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#6cbec3ff","u16":[27878,48889,50158,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#768283ff","u16":[30270,33369,33607,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#4e898dff","u16":[20075,35205,36119,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#839192ff","u16":[33749,37204,37470,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#57999dff","u16":[22383,39251,40270,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#889697ff","u16":[35026,38612,38888,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#5a9fa3ff","u16":[23229,40737,41794,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#8c9a9bff","u16":[35949,39628,39911,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#5da3a7ff","u16":[23841,41809,42895,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#8f9e9fff","u16":[36843,40615,40905,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#5fa7abff","u16":[24435,42850,43962,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#8f9e9fff","u16":[36843,40615,40905,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#5fa7abff","u16":[24435,42850,43962,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#7a8788ff","u16":[31461,34681,34929,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#518e92ff","u16":[20865,36590,37540,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#727e7eff","u16":[29261,32256,32486,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#4c8488ff","u16":[19406,34031,34914,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#6e797aff","u16":[28323,31222,31445,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#498083ff","u16":[18784,32940,33795,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#6b7677ff","u16":[27594,30418,30636,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#477d80ff","u16":[18300,32092,32926,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#687374ff","u16":[26841,29589,29800,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#45797dff","u16":[17801,31217,32028,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#687374ff","u16":[26841,29589,29800,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#45797dff","u16":[17801,31217,32028,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#737e7fff","u16":[29491,32510,32742,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#4c8589ff","u16":[19558,34299,35189,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#586162ff","u16":[22704,25028,25207,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#3b6769ff","u16":[15057,26406,27091,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#4b5253ff","u16":[19166,21128,21279,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#315759ff","u16":[12711,22291,22870,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#3e4445ff","u16":[15882,17507,17632,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#29484aff","u16":[10533,18471,18950,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#2d3132ff","u16":[11460,12633,12724,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#1e3435ff","u16":[7600,13328,13675,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#2d3132ff","u16":[11460,12633,12724,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#1e3435ff","u16":[7600,13328,13675,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#4e5656ff","u16":[19945,21987,22144,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#7f8c8dff","out":{"hex":"#335a5dff","u16":[13227,23197,23799,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#3c4242ff","u16":[15355,16927,17048,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#7f8c8dff","out":{"hex":"#284547ff","u16":[10183,17859,18322,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#323838ff","u16":[12962,14289,14391,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#7f8c8dff","out":{"hex":"#213b3cff","u16":[8597,15076,15467,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#2a2e2eff","u16":[10741,11840,11925,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#7f8c8dff","out":{"hex":"#1c3132ff","u16":[7124,12492,12816,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#1e2121ff","u16":[7751,8544,8605,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#7f8c8dff","out":{"hex":"#142324ff","u16":[5140,9014,9249,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#1e2121ff","u16":[7751,8544,8605,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#7f8c8dff","out":{"hex":"#142324ff","u16":[5140,9014,9249,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#79468eff","u16":[31171,17898,36601,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#9419b6ff","u16":[37917,6507,46796,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#a763c4ff","u16":[42983,25335,50300,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#cb2df9ff","u16":[52078,11583,64097,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#ae73c8ff","u16":[44617,29491,51299,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#ce4ffaff","u16":[52941,20410,64178,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#b27dcaff","u16":[45798,32198,52033,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#d060faff","u16":[53577,24794,64239,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#b787cdff","u16":[46942,34655,52754,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#d36ffaff","u16":[54205,28401,64300,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#b787cdff","u16":[46942,34655,52754,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#d36ffaff","u16":[54205,28401,64300,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#8d51a6ff","u16":[36342,20868,42673,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#ac1ed4ff","u16":[44207,7587,54560,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#a15dbdff","u16":[41451,23801,48671,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#c422f2ff","u16":[50421,8653,62228,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#a762c4ff","u16":[42933,25199,50270,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#cb2cf9ff","u16":[52052,11188,64095,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#a968c5ff","u16":[43479,26660,50602,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#cc3af9ff","u16":[52338,14903,64121,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#ab6dc6ff","u16":[44018,28031,50930,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#cd45faff","u16":[52622,17743,64148,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#ab6dc6ff","u16":[44018,28031,50930,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#cd45faff","u16":[52622,17743,64148,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#9656b0ff","u16":[38443,22074,45139,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#b61fe1ff","u16":[46762,8025,57713,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#8b50a4ff","u16":[35849,20584,42093,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#aa1dd1ff","u16":[43607,7484,53818,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#874e9fff","u16":[34746,19951,40798,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#a41ccbff","u16":[42265,7253,52162,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#844c9bff","u16":[33890,19459,39793,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#a01cc6ff","u16":[41224,7075,50877,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#804a97ff","u16":[33007,18952,38757,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#9c1bc1ff","u16":[40150,6890,49552,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#804a97ff","u16":[33007,18952,38757,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#9c1bc1ff","u16":[40150,6890,49552,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#8c50a4ff","u16":[35993,20667,42262,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#aa1dd2ff","u16":[43782,7514,54035,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#6c3e7fff","u16":[27710,15911,32537,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#8317a2ff","u16":[33707,5785,41600,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#5b346bff","u16":[23392,13431,27467,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#6f1389ff","u16":[28454,4883,35117,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#4b2b59ff","u16":[19383,11130,22760,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#5c1071ff","u16":[23578,4046,29099,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#361f40ff","u16":[13987,8031,16423,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#420b52ff","u16":[17014,2920,20998,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#361f40ff","u16":[13987,8031,16423,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#420b52ff","u16":[17014,2920,20998,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#613872ff","u16":[24966,14335,29314,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#9b59b6ff","out":{"hex":"#761492ff","u16":[30369,5212,37480,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#4b2b58ff","u16":[19220,11036,22569,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#9b59b6ff","out":{"hex":"#5b1070ff","u16":[23380,4013,28855,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#3f244aff","u16":[16225,9316,19052,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#9b59b6ff","out":{"hex":"#4d0d5fff","u16":[19737,3387,24358,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#341e3dff","u16":[13445,7720,15787,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#9b59b6ff","out":{"hex":"#400b4fff","u16":[16354,2806,20184,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#26162cff","u16":[9702,5571,11391,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#9b59b6ff","out":{"hex":"#2e0839ff","u16":[11801,2025,14565,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#26162cff","u16":[9702,5571,11391,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#9b59b6ff","out":{"hex":"#2e0839ff","u16":[11801,2025,14565,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#187dc7ff","u16":[6091,32134,51037,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#007dc8ff","u16":[0,32168,51480,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#40a4fdff","u16":[16487,42211,64993,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#3ba4ffff","u16":[15042,42250,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#69b0fdff","u16":[27047,45198,65047,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#67b0ffff","u16":[26356,45230,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#7eb8fdff","u16":[32497,47291,65088,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#7cb8ffff","u16":[31992,47320,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#90c0fdff","u16":[37024,49279,65128,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#8fc0ffff","u16":[36632,49304,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#90c0fdff","u16":[37024,49279,65128,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#8fc0ffff","u16":[36632,49304,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#1b8fe3ff","u16":[6953,36682,58260,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#008fe5ff","u16":[0,36720,58766,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#1e9efbff","u16":[7689,40564,64425,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#009efdff","u16":[0,40606,64985,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#39a3fdff","u16":[14776,41871,64987,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#33a3ffff","u16":[13082,41911,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#4aa7fdff","u16":[19025,42791,65003,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#45a7ffff","u16":[17850,42829,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#57aafdff","u16":[22359,43688,65019,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#53aaffff","u16":[21432,43724,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#57aafdff","u16":[22359,43688,65019,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#53aaffff","u16":[21432,43724,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#1c93eaff","u16":[7179,37878,60159,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#0094ecff","u16":[0,37917,60681,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#1a89d9ff","u16":[6668,35179,55873,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#0089dbff","u16":[0,35216,56359,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#1984d2ff","u16":[6450,34028,54044,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#0085d4ff","u16":[0,34063,54513,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#1881cdff","u16":[6280,33132,52621,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#0081cfff","u16":[0,33166,53079,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#187dc7ff","u16":[6104,32206,51151,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#007dc9ff","u16":[0,32240,51596,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#187dc7ff","u16":[6104,32206,51151,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#007dc9ff","u16":[0,32240,51596,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#1a8adcff","u16":[6734,35528,56427,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#008addff","u16":[0,35565,56918,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#146aa9ff","u16":[5184,27352,43442,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#006babff","u16":[0,27381,43819,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#115a8fff","u16":[4377,23090,36672,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#005a90ff","u16":[0,23114,36991,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#0e4a76ff","u16":[3627,19133,30388,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#004b77ff","u16":[0,19153,30652,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#0a3655ff","u16":[2617,13806,21928,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#003656ff","u16":[0,13821,22118,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#0a3655ff","u16":[2617,13806,21928,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#003656ff","u16":[0,13821,22118,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#115c92ff","u16":[4491,23696,37636,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#1d99f3ff","out":{"hex":"#005c94ff","u16":[0,23721,37963,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#0d4771ff","u16":[3458,18243,28975,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#1d99f3ff","out":{"hex":"#004772ff","u16":[0,18262,29226,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#0b3c5fff","u16":[2919,15400,24459,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#1d99f3ff","out":{"hex":"#003c60ff","u16":[0,15417,24672,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#09324fff","u16":[2419,12761,20268,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#1d99f3ff","out":{"hex":"#003250ff","u16":[0,12775,20444,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#072439ff","u16":[1745,9208,14625,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#1d99f3ff","out":{"hex":"#002439ff","u16":[0,9218,14752,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#072439ff","u16":[1745,9208,14625,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#1d99f3ff","out":{"hex":"#002439ff","u16":[0,9218,14752,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#b9bec3ff","u16":[47576,48794,49989,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#97c3e5ff","u16":[38726,50015,58938,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#eeeff0ff","u16":[61149,61404,61661,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#e7f0f8ff","u16":[59493,61666,63773,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#d6d8dbff","u16":[54979,55633,56285,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#c5dbefff","u16":[50573,56300,61453,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#e2e4e5ff","u16":[58058,58507,58956,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#d6e5f4ff","u16":[55099,58966,62587,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#e6e8e9ff","u16":[59237,59610,59984,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#dde9f5ff","u16":[56790,59993,63034,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#eaebecff","u16":[60102,60421,60742,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#e2ecf7ff","u16":[58019,60749,63366,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#edeeefff","u16":[60953,61220,61489,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#e6eff8ff","u16":[59218,61494,63697,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#edeeefff","u16":[60953,61220,61489,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#e6eff8ff","u16":[59218,61494,63697,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#d4d6d9ff","u16":[54418,55111,55802,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#c1d9eeff","u16":[49728,55817,61252,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#c0c4c8ff","u16":[49394,50463,51516,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#a3c9e8ff","u16":[41807,51540,59527,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#b8bdc1ff","u16":[47203,48452,49677,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#94c1e5ff","u16":[38075,49704,58820,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#b1b6bcff","u16":[45476,46876,48242,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#88bce3ff","u16":[34958,48272,58283,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#aab0b6ff","u16":[43666,45233,46754,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#7ab6e1ff","u16":[31466,46787,57740,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#aab0b6ff","u16":[43666,45233,46754,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#7ab6e1ff","u16":[31466,46787,57740,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#c7cbceff","u16":[51148,52080,53002,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#aeceeaff","u16":[44656,53022,60113,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#979ca2ff","u16":[38744,40194,41599,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#6aa2c9ff","u16":[27323,41630,51699,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#7f8489ff","u16":[32707,33931,35117,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#5a89aaff","u16":[23065,35143,43643,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#696d71ff","u16":[27102,28116,29099,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#4a718dff","u16":[19112,29120,36164,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#4c4f52ff","u16":[19556,20288,20998,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#365266ff","u16":[13791,21013,26096,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#4c4f52ff","u16":[19556,20288,20998,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#365266ff","u16":[13791,21013,26096,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#6d7175ff","u16":[27988,29036,30050,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#dee0e2ff","out":{"hex":"#4d7591ff","u16":[19737,30072,37347,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#54575aff","u16":[21547,22354,23135,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#dee0e2ff","out":{"hex":"#3b5a70ff","u16":[15195,23152,28752,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#47494cff","u16":[18190,18870,19530,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#dee0e2ff","out":{"hex":"#324c5eff","u16":[12827,19544,24272,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#3b3d3fff","u16":[15073,15636,16183,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#dee0e2ff","out":{"hex":"#293f4eff","u16":[10629,16195,20112,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#2a2c2dff","u16":[10876,11283,11678,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#dee0e2ff","out":{"hex":"#1e2d38ff","u16":[7670,11686,14513,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#2a2c2dff","u16":[10876,11283,11678,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#dee0e2ff","out":{"hex":"#1e2d38ff","u16":[7670,11686,14513,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#292c30ff","out":{"hex":"#000000ff","u16":[0,0,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#292c30ff","out":{"hex":"#35393eff","u16":[13566,14558,15882,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#292c30ff","out":{"hex":"#243a4fff","u16":[9194,14942,20300,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#40444bff","u16":[16369,17567,19164,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#2b465fff","u16":[11094,18029,24494,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#474c53ff","u16":[18142,19469,21239,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#304e6aff","u16":[12295,19982,27147,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#292c30ff","out":{"hex":"#4d525aff","u16":[19728,21171,23096,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#292c30ff","out":{"hex":"#345573ff","u16":[13370,21729,29520,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#4d525aff","u16":[19728,21171,23096,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#345573ff","u16":[13370,21729,29520,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#292c30ff","out":{"hex":"#17181aff","u16":[5784,6208,6772,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#292c30ff","out":{"hex":"#0f1922ff","u16":[3920,6371,8656,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#292c30ff","out":{"hex":"#2f3337ff","u16":[12143,13032,14217,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#292c30ff","out":{"hex":"#203447ff","u16":[8230,13375,18171,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#363a3fff","u16":[13785,14794,16138,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#243b50ff","u16":[9342,15183,20628,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#3a3e44ff","u16":[14877,15965,17417,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#274057ff","u16":[10082,16386,22261,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#292c30ff","out":{"hex":"#3e4248ff","u16":[15880,17042,18591,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#292c30ff","out":{"hex":"#2a445cff","u16":[10762,17490,23762,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#3e4248ff","u16":[15880,17042,18591,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#2a445cff","u16":[10762,17490,23762,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#292c30ff","out":{"hex":"#282b2eff","u16":[10192,10938,11932,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#292c30ff","out":{"hex":"#1b2c3bff","u16":[6907,11226,15251,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#292c30ff","out":{"hex":"#25282cff","u16":[9552,10251,11183,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#292c30ff","out":{"hex":"#192938ff","u16":[6474,10521,14293,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#24272aff","u16":[9281,9960,10866,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#182836ff","u16":[6290,10222,13888,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#232629ff","u16":[9071,9735,10620,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#182735ff","u16":[6148,9991,13574,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#292c30ff","out":{"hex":"#222528ff","u16":[8856,9504,10368,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#292c30ff","out":{"hex":"#172634ff","u16":[6002,9754,13252,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#222528ff","u16":[8856,9504,10368,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#172634ff","u16":[6002,9754,13252,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#292c30ff","out":{"hex":"#25282bff","u16":[9521,10217,11146,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#292c30ff","out":{"hex":"#192937ff","u16":[6452,10486,14247,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#292c30ff","out":{"hex":"#1d1f21ff","u16":[7330,7866,8581,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#292c30ff","out":{"hex":"#131f2bff","u16":[4968,8073,10968,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#181a1cff","u16":[6188,6640,7244,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#101b24ff","u16":[4193,6815,9259,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#141517ff","u16":[5127,5502,6003,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#0e161eff","u16":[3475,5647,7672,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#292c30ff","out":{"hex":"#0e0f11ff","u16":[3700,3970,4331,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#292c30ff","out":{"hex":"#0a1016ff","u16":[2507,4075,5536,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#0e0f11ff","u16":[3700,3970,4331,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#0a1016ff","u16":[2507,4075,5536,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#292c30ff","out":{"hex":"#1b1d1fff","u16":[6909,7414,8088,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#292c30ff","out":{"hex":"#121e28ff","u16":[4682,7609,10338,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#292c30ff","out":{"hex":"#151618ff","u16":[5319,5708,6227,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#292c30ff","out":{"hex":"#0e171fff","u16":[3605,5858,7959,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#111314ff","u16":[4490,4818,5256,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#292c30ff","out":{"hex":"#0c131aff","u16":[3043,4945,6719,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#0e1011ff","u16":[3720,3992,4356,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#292c30ff","out":{"hex":"#0a1016ff","u16":[2522,4098,5567,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#292c30ff","out":{"hex":"#0a0b0cff","u16":[2685,2881,3143,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#292c30ff","out":{"hex":"#070c10ff","u16":[1819,2957,4017,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#0a0b0cff","u16":[2685,2881,3143,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#292c30ff","out":{"hex":"#070c10ff","u16":[1819,2957,4017,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#bb2e7bff","u16":[47981,11944,31713,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#c7007fff","u16":[51107,0,32632,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#e93a9aff","out":{"hex":"#f050a3ff","u16":[61688,20583,41979,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#e93a9aff","out":{"hex":"#ff3aa8ff","u16":[65535,15000,43071,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#f16aacff","u16":[61973,27170,44179,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#ff5db0ff","u16":[65535,23799,45135,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#f279b2ff","u16":[62185,31065,45746,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#ff6fb5ff","u16":[65535,28426,46612,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#e93a9aff","out":{"hex":"#f386b8ff","u16":[62396,34447,47252,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#e93a9aff","out":{"hex":"#ff7ebbff","u16":[65535,32289,48035,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#f386b8ff","u16":[62396,34447,47252,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#ff7ebbff","u16":[65535,32289,48035,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#d7368eff","u16":[55246,13752,36514,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#e50092ff","u16":[58846,0,37573,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#e93a9aff","out":{"hex":"#ef3f9fff","u16":[61550,16241,40866,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#e93a9aff","out":{"hex":"#ff19a4ff","u16":[65535,6342,42031,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#f04ea3ff","u16":[61671,20092,41839,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#ff37a7ff","u16":[65535,14239,42940,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#f057a6ff","u16":[61761,22481,42551,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#ff45aaff","u16":[65535,17747,43607,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#e93a9aff","out":{"hex":"#f160a8ff","u16":[61851,24598,43250,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#e93a9aff","out":{"hex":"#ff50acff","u16":[65535,20572,44262,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#f160a8ff","u16":[61851,24598,43250,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#ff50acff","u16":[65535,20572,44262,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#e13895ff","u16":[57746,14375,38167,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#ef0099ff","u16":[61509,0,39273,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#e93a9aff","out":{"hex":"#d1348aff","u16":[53762,13383,35534,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#e93a9aff","out":{"hex":"#df008eff","u16":[57265,0,36564,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#cb3286ff","u16":[52065,12960,34412,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#d8008aff","u16":[55458,0,35410,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#c53183ff","u16":[50748,12633,33542,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#d20086ff","u16":[54055,0,34514,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#e93a9aff","out":{"hex":"#c0307fff","u16":[49388,12294,32643,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#e93a9aff","out":{"hex":"#cd0083ff","u16":[52606,0,33589,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#c0307fff","u16":[49388,12294,32643,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#cd0083ff","u16":[52606,0,33589,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#d3348bff","u16":[54105,13468,35761,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#e0008fff","u16":[57631,0,36797,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#e93a9aff","out":{"hex":"#a2286bff","u16":[41654,10369,27531,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#e93a9aff","out":{"hex":"#ad006eff","u16":[44368,0,28329,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#89225aff","u16":[35163,8753,23241,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#92005dff","u16":[37455,0,23915,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#711c4bff","u16":[29137,7253,19258,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#79004dff","u16":[31036,0,19816,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#e93a9aff","out":{"hex":"#521436ff","u16":[21025,5234,13897,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#e93a9aff","out":{"hex":"#570038ff","u16":[22395,0,14299,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#521436ff","u16":[21025,5234,13897,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#570038ff","u16":[22395,0,14299,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#90245fff","u16":[36956,9199,24426,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#e93a9aff","out":{"hex":"#990062ff","u16":[39365,0,25134,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#e93a9aff","out":{"hex":"#6f1c49ff","u16":[28452,7083,18805,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#e93a9aff","out":{"hex":"#76004bff","u16":[30305,0,19350,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#5d173eff","u16":[24018,5979,15875,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93a9aff","out":{"hex":"#640040ff","u16":[25584,0,16335,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#4d1333ff","u16":[19902,4954,13154,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93a9aff","out":{"hex":"#520035ff","u16":[21199,0,13535,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#e93a9aff","out":{"hex":"#380e25ff","u16":[14361,3575,9492,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#e93a9aff","out":{"hex":"#3c0026ff","u16":[15297,0,9767,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#380e25ff","u16":[14361,3575,9492,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93a9aff","out":{"hex":"#3c0026ff","u16":[15297,0,9767,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#ba3146ff","u16":[47727,12495,18026,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#c9003bff","u16":[51626,0,15270,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#e93d58ff","out":{"hex":"#ed5568ff","u16":[60825,21738,26846,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#e93d58ff","out":{"hex":"#ff3f5eff","u16":[65535,16193,24185,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#ee6b7aff","u16":[61154,27609,31385,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#ff5e72ff","u16":[65535,24034,29371,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#ef7986ff","u16":[61400,31179,34330,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#ff6e7fff","u16":[65535,28325,32634,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#e93d58ff","out":{"hex":"#f08690ff","u16":[61644,34315,36999,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#e93d58ff","out":{"hex":"#ff7c8aff","u16":[65535,31950,35546,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#f08690ff","u16":[61644,34315,36999,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#ff7c8aff","u16":[65535,31950,35546,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#d63851ff","u16":[55099,14425,20810,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#e80045ff","u16":[59600,0,17629,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#e93d58ff","out":{"hex":"#ec465fff","u16":[60667,18106,24309,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#e93d58ff","out":{"hex":"#ff2652ff","u16":[65535,9888,21153,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#ed5367ff","u16":[60808,21374,26581,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#ff3d5dff","u16":[65535,15645,23873,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#ed5b6eff","u16":[60913,23477,28143,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#ff4964ff","u16":[65535,18678,25691,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#e93d58ff","out":{"hex":"#ed6373ff","u16":[61018,25376,29607,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#e93d58ff","out":{"hex":"#ff536aff","u16":[65535,21211,27367,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#ed6373ff","u16":[61018,25376,29607,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#ff536aff","u16":[65535,21211,27367,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#e13b55ff","u16":[57757,15121,21814,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#f30048ff","u16":[62475,0,18479,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#e93d58ff","out":{"hex":"#d1374fff","u16":[53797,14084,20318,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#e93d58ff","out":{"hex":"#e20043ff","u16":[58191,0,17212,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#cb354dff","u16":[52111,13643,19681,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#db0041ff","u16":[56367,0,16673,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#c6344bff","u16":[50802,13300,19187,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#d6003fff","u16":[54952,0,16254,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#e93d58ff","out":{"hex":"#c03249ff","u16":[49451,12946,18677,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#e93d58ff","out":{"hex":"#d0003eff","u16":[53491,0,15822,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#c03249ff","u16":[49451,12946,18677,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#d0003eff","u16":[53491,0,15822,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#d33750ff","u16":[54105,14165,20435,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#e40043ff","u16":[58525,0,17311,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#e93d58ff","out":{"hex":"#a22a3dff","u16":[41654,10905,15732,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#e93d58ff","out":{"hex":"#af0034ff","u16":[45057,0,13327,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#892434ff","u16":[35163,9206,13281,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#94002cff","u16":[38036,0,11250,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#711e2bff","u16":[29137,7628,11005,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#7b0024ff","u16":[31517,0,9322,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#e93d58ff","out":{"hex":"#52151fff","u16":[21025,5504,7941,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#e93d58ff","out":{"hex":"#58001aff","u16":[22743,0,6727,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#52151fff","u16":[21025,5504,7941,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#58001aff","u16":[22743,0,6727,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#902637ff","u16":[37115,9717,14018,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#e93d58ff","out":{"hex":"#9c002eff","u16":[40147,0,11875,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#e93d58ff","out":{"hex":"#6f1d2aff","u16":[28574,7481,10792,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#e93d58ff","out":{"hex":"#780024ff","u16":[30908,0,9142,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#5e1923ff","u16":[24121,6315,9110,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#e93d58ff","out":{"hex":"#66001eff","u16":[26092,0,7717,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#4e141dff","u16":[19987,5233,7549,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#e93d58ff","out":{"hex":"#540019ff","u16":[21620,0,6395,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#e93d58ff","out":{"hex":"#380f15ff","u16":[14423,3776,5447,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#e93d58ff","out":{"hex":"#3d0012ff","u16":[15601,0,4615,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#380f15ff","u16":[14423,3776,5447,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#e93d58ff","out":{"hex":"#3d0012ff","u16":[15601,0,4615,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#e9643aff","out":{"hex":"#bd512fff","u16":[48602,20859,12098,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#e9643aff","out":{"hex":"#c74a00ff","u16":[51017,19009,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#e9643aff","out":{"hex":"#f3724eff","u16":[62539,29173,20091,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#e9643aff","out":{"hex":"#ff6a36ff","u16":[65535,27123,13985,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#f4856dff","u16":[62806,34308,28052,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#ff8060ff","u16":[65535,32791,24751,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#f5927fff","u16":[63005,37623,32582,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#ff8e75ff","u16":[65535,36370,30087,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#e9643aff","out":{"hex":"#f69e8eff","u16":[63204,40620,36459,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#e9643aff","out":{"hex":"#ff9a86ff","u16":[65535,39570,34474,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#f69e8eff","u16":[63204,40620,36459,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#ff9a86ff","u16":[65535,39570,34474,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#e9643aff","out":{"hex":"#d85d36ff","u16":[55631,23876,13848,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#e9643aff","out":{"hex":"#e35500ff","u16":[58396,21759,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#e9643aff","out":{"hex":"#f1673cff","u16":[61876,26556,15403,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#e9643aff","out":{"hex":"#fd5e00ff","u16":[64951,24201,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#f3704bff","u16":[62515,28663,19188,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#ff6731ff","u16":[65535,26548,12469,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#f47656ff","u16":[62596,30370,22105,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#ff6f42ff","u16":[65535,28461,17026,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#e9643aff","out":{"hex":"#f47c60ff","u16":[62678,31968,24621,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#e9643aff","out":{"hex":"#ff7650ff","u16":[65535,30231,20448,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#f47c60ff","u16":[62678,31968,24621,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#ff7650ff","u16":[65535,30231,20448,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#e9643aff","out":{"hex":"#e16038ff","u16":[57709,24768,14365,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#e9643aff","out":{"hex":"#ec5800ff","u16":[60578,22572,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#e9643aff","out":{"hex":"#d15a34ff","u16":[53653,23027,13356,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#e9643aff","out":{"hex":"#db5200ff","u16":[56320,20985,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#ca5732ff","u16":[51923,22285,12925,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#d44f00ff","u16":[54504,20308,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#c55431ff","u16":[50579,21708,12590,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#cf4d00ff","u16":[53093,19783,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#e9643aff","out":{"hex":"#bf5230ff","u16":[49190,21112,12245,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#e9643aff","out":{"hex":"#c94b00ff","u16":[51635,19239,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#bf5230ff","u16":[49190,21112,12245,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#c94b00ff","u16":[51635,19239,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#e9643aff","out":{"hex":"#d35a34ff","u16":[54105,23221,13468,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#e9643aff","out":{"hex":"#dd5200ff","u16":[56794,21162,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#e9643aff","out":{"hex":"#a24628ff","u16":[41654,17877,10369,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#e9643aff","out":{"hex":"#aa3f00ff","u16":[43724,16292,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#893b22ff","u16":[35163,15092,8753,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#903600ff","u16":[36911,13753,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#71311cff","u16":[29137,12505,7253,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#772c00ff","u16":[30586,11396,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#e9643aff","out":{"hex":"#522314ff","u16":[21025,9024,5234,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#e9643aff","out":{"hex":"#562000ff","u16":[22070,8224,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#522314ff","u16":[21025,9024,5234,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#562000ff","u16":[22070,8224,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#e9643aff","out":{"hex":"#8e3d23ff","u16":[36455,15646,9074,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#e9643aff","out":{"hex":"#953700ff","u16":[38267,14259,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#e9643aff","out":{"hex":"#6d2f1bff","u16":[28066,12045,6986,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#e9643aff","out":{"hex":"#732b00ff","u16":[29460,10977,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#5c2817ff","u16":[23692,10169,5898,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#e9643aff","out":{"hex":"#612400ff","u16":[24870,9266,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#4c2113ff","u16":[19632,8426,4887,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#e9643aff","out":{"hex":"#501e00ff","u16":[20608,7678,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#e9643aff","out":{"hex":"#37180eff","u16":[14166,6080,3527,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#e9643aff","out":{"hex":"#3a1600ff","u16":[14870,5541,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#37180eff","u16":[14166,6080,3527,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#e9643aff","out":{"hex":"#3a1600ff","u16":[14870,5541,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#c3ab26ff","u16":[50165,43894,9730,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#c4ab00ff","u16":[50381,43892,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#f8d930ff","u16":[63737,55770,12363,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#f9d900ff","u16":[64011,55767,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#fee998ff","u16":[65339,59781,38987,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#ffe994ff","u16":[65535,59779,37926,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#fff4d1ff","u16":[65432,62600,53626,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#fff4cfff","u16":[65535,62599,53251,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#fffefbff","u16":[65526,65274,64558,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#fffefbff","u16":[65535,65274,64531,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#fffefbff","u16":[65526,65274,64558,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#fffefbff","u16":[65535,65274,64531,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#dec22bff","u16":[57097,49960,11075,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#dfc200ff","u16":[57343,49957,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#edcf2eff","u16":[60841,53236,11801,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#eecf00ff","u16":[61102,53233,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#f2d42fff","u16":[62263,54480,12077,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#f3d400ff","u16":[62531,54477,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#f6d830ff","u16":[63305,55392,12279,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#f7d800ff","u16":[63577,55389,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#fadb31ff","u16":[64326,56285,12477,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#fbdb00ff","u16":[64603,56283,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#fadb31ff","u16":[64326,56285,12477,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#fbdb00ff","u16":[64603,56283,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#dec32bff","u16":[57173,50026,11090,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#dfc300ff","u16":[57419,50024,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#ccb328ff","u16":[52549,45981,10193,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#cdb300ff","u16":[52776,45978,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#c5ac26ff","u16":[50557,44237,9806,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#c6ac00ff","u16":[50774,44235,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#bfa725ff","u16":[48998,42873,9504,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#bfa700ff","u16":[49209,42871,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#b8a124ff","u16":[47377,41455,9190,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#b9a100ff","u16":[47581,41453,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#b8a124ff","u16":[47377,41455,9190,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#b9a100ff","u16":[47581,41453,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#d2b729ff","u16":[53873,47139,10450,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#d3b700ff","u16":[54105,47136,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#a18d1fff","u16":[41475,36291,8045,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#a28d00ff","u16":[41654,36289,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#88771aff","u16":[35012,30636,6791,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#897700ff","u16":[35163,30634,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#716316ff","u16":[29012,25386,5627,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#716300ff","u16":[29137,25384,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#514710ff","u16":[20935,18318,4061,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#524700ff","u16":[21025,18317,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#514710ff","u16":[20935,18318,4061,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#524700ff","u16":[21025,18317,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#7c6d18ff","u16":[31995,27996,6206,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#e8cb2dff","out":{"hex":"#7d6d00ff","u16":[32133,27994,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#605413ff","u16":[24632,21553,4778,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#e8cb2dff","out":{"hex":"#605400ff","u16":[24738,21552,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#514710ff","u16":[20793,18195,4033,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#e8cb2dff","out":{"hex":"#514700ff","u16":[20883,18193,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#433b0dff","u16":[17230,15077,3342,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#e8cb2dff","out":{"hex":"#433b00ff","u16":[17304,15075,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#302a09ff","u16":[12433,10879,2412,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#e8cb2dff","out":{"hex":"#312a00ff","u16":[12487,10878,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#302a09ff","u16":[12433,10879,2412,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#e8cb2dff","out":{"hex":"#312a00ff","u16":[12487,10878,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#33b11fff","u16":[13100,45528,7946,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#2bb200ff","u16":[11047,45696,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#3dd425ff","out":{"hex":"#41e328ff","u16":[16792,58359,10185,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#3dd425ff","out":{"hex":"#37e400ff","u16":[14161,58575,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#46f42bff","u16":[18023,62636,10932,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#3bf500ff","u16":[15198,62868,0,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#56fe42ff","u16":[22134,65300,16849,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#4dff34ff","u16":[19867,65535,13251,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#3dd425ff","out":{"hex":"#9bfe94ff","u16":[39845,65363,38160,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#3dd425ff","out":{"hex":"#98ff91ff","u16":[39066,65535,37284,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#9bfe94ff","u16":[39845,65363,38160,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#98ff91ff","u16":[39066,65535,37284,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#3ac923ff","u16":[14886,51736,9029,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#31ca00ff","u16":[12554,51928,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#3dd425ff","out":{"hex":"#3ed926ff","u16":[16055,55797,9738,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#3dd425ff","out":{"hex":"#35da00ff","u16":[13539,56004,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#40df27ff","u16":[16495,57326,10005,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#36e000ff","u16":[13910,57538,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#41e328ff","u16":[16816,58441,10200,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#37e400ff","u16":[14181,58658,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#3dd425ff","out":{"hex":"#43e828ff","u16":[17129,59532,10390,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#3dd425ff","out":{"hex":"#38e800ff","u16":[14445,59752,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#43e828ff","u16":[17129,59532,10390,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#38e800ff","u16":[14445,59752,0,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#3bcc24ff","u16":[15060,52338,9135,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#31cc00ff","u16":[12700,52532,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#3dd425ff","out":{"hex":"#36bc21ff","u16":[13899,48305,8431,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#3dd425ff","out":{"hex":"#2ebd00ff","u16":[11721,48484,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#34b520ff","u16":[13401,46574,8128,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#2cb600ff","u16":[11301,46746,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#33b01fff","u16":[13012,45223,7893,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#2bb100ff","u16":[10973,45390,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#3dd425ff","out":{"hex":"#31ab1eff","u16":[12609,43821,7648,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#3dd425ff","out":{"hex":"#29ab00ff","u16":[10633,43983,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#31ab1eff","u16":[12609,43821,7648,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#29ab00ff","u16":[10633,43983,0,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#37c021ff","u16":[14165,49229,8592,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#2ec000ff","u16":[11945,49411,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#3dd425ff","out":{"hex":"#2a931aff","u16":[10905,37900,6615,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#3dd425ff","out":{"hex":"#249400ff","u16":[9196,38040,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#247c16ff","u16":[9206,31994,5584,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#1e7d00ff","u16":[7763,32113,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#1e6712ff","u16":[7628,26511,4627,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#196800ff","u16":[6433,26609,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#3dd425ff","out":{"hex":"#154a0dff","u16":[5504,19130,3339,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#3dd425ff","out":{"hex":"#124b00ff","u16":[4642,19201,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#154a0dff","u16":[5504,19130,3339,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#124b00ff","u16":[4642,19201,0,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#227715ff","u16":[8836,30707,5359,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#3dd425ff","out":{"hex":"#1d7800ff","u16":[7451,30821,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#3dd425ff","out":{"hex":"#1a5c10ff","u16":[6802,23640,4126,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#3dd425ff","out":{"hex":"#165c00ff","u16":[5736,23728,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#164e0eff","u16":[5742,19957,3483,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#3dd425ff","out":{"hex":"#134e00ff","u16":[4842,20031,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#13400bff","u16":[4758,16536,2886,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#3dd425ff","out":{"hex":"#104100ff","u16":[4013,16598,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#3dd425ff","out":{"hex":"#0d2e08ff","u16":[3433,11932,2083,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#3dd425ff","out":{"hex":"#0b2f00ff","u16":[2895,11977,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#0d2e08ff","u16":[3433,11932,2083,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#3dd425ff","out":{"hex":"#0b2f00ff","u16":[2895,11977,0,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#00b19aff","u16":[0,45375,39569,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#00b19aff","u16":[0,45375,39569,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#00e2c5ff","u16":[0,58061,50631,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#00e2c5ff","u16":[0,58061,50631,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#00f2d3ff","u16":[0,62294,54323,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#00f2d3ff","u16":[0,62294,54323,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#00feddff","u16":[0,65255,56905,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#00feddff","u16":[0,65255,56905,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#91ffe8ff","u16":[37247,65535,59714,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#91ffe8ff","u16":[37247,65535,59714,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#91ffe8ff","u16":[37247,65535,59714,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#91ffe8ff","u16":[37247,65535,59714,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#00c9afff","u16":[0,51570,44971,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#00c9afff","u16":[0,51570,44971,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#00d8bcff","u16":[0,55499,48397,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#00d8bcff","u16":[0,55499,48397,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#00dec1ff","u16":[0,56980,49689,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#00dec1ff","u16":[0,56980,49689,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#00e2c5ff","u16":[0,58062,50632,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#00e2c5ff","u16":[0,58062,50632,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#00e6c9ff","u16":[0,59120,51555,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#00e6c9ff","u16":[0,59120,51555,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#00e6c9ff","u16":[0,59120,51555,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#00e6c9ff","u16":[0,59120,51555,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#00cbb1ff","u16":[0,52076,45412,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#00cbb1ff","u16":[0,52076,45412,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#00bba3ff","u16":[0,48030,41884,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#00bba3ff","u16":[0,48030,41884,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#00b49dff","u16":[0,46292,40368,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#00b49dff","u16":[0,46292,40368,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#00af98ff","u16":[0,44935,39185,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#00af98ff","u16":[0,44935,39185,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#00a994ff","u16":[0,43527,37957,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#00a994ff","u16":[0,43527,37957,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#00a994ff","u16":[0,43527,37957,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#00a994ff","u16":[0,43527,37957,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#00bfa6ff","u16":[0,48997,42727,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#00bfa6ff","u16":[0,48997,42727,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#009380ff","u16":[0,37721,32894,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#009380ff","u16":[0,37721,32894,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#007c6cff","u16":[0,31843,27768,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#007c6cff","u16":[0,31843,27768,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#00675aff","u16":[0,26386,23010,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#00675aff","u16":[0,26386,23010,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#004a41ff","u16":[0,19040,16604,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#004a41ff","u16":[0,19040,16604,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#004a41ff","u16":[0,19040,16604,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#004a41ff","u16":[0,19040,16604,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#007667ff","u16":[0,30324,26444,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#00d3b8ff","out":{"hex":"#007667ff","u16":[0,30324,26444,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#005b4fff","u16":[0,23346,20358,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#00d3b8ff","out":{"hex":"#005b4fff","u16":[0,23346,20358,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#004d43ff","u16":[0,19708,17186,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#00d3b8ff","out":{"hex":"#004d43ff","u16":[0,19708,17186,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#004037ff","u16":[0,16330,14241,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#00d3b8ff","out":{"hex":"#004037ff","u16":[0,16330,14241,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#002e28ff","u16":[0,11784,10276,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#00d3b8ff","out":{"hex":"#002e28ff","u16":[0,11784,10276,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#002e28ff","u16":[0,11784,10276,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#00d3b8ff","out":{"hex":"#002e28ff","u16":[0,11784,10276,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#b875dcff","out":{"hex":"#a84cd1ff","u16":[43068,19610,53754,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#b875dcff","out":{"hex":"#c000f8ff","u16":[49262,0,63755,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#b875dcff","out":{"hex":"#be85dfff","u16":[48885,34215,57247,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#b875dcff","out":{"hex":"#d174ffff","u16":[53656,29899,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#c596e2ff","u16":[50723,38505,58081,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#d689ffff","u16":[54897,35194,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#cba1e4ff","u16":[52051,41380,58698,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#d996ffff","u16":[55807,38611,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#b875dcff","out":{"hex":"#d0abe7ff","u16":[53339,44033,59307,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#b875dcff","out":{"hex":"#dda2ffff","u16":[56699,41699,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#d0abe7ff","u16":[53339,44033,59307,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#dda2ffff","u16":[56699,41699,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#b875dcff","out":{"hex":"#b366daff","u16":[45961,26172,55966,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#b875dcff","out":{"hex":"#c948ffff","u16":[51725,18611,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#b875dcff","out":{"hex":"#bb7cddff","u16":[47935,31807,56824,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#b875dcff","out":{"hex":"#ce68ffff","u16":[53022,26773,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#bd83deff","u16":[48698,33752,57163,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#d072ffff","u16":[53531,29309,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#c089dfff","u16":[49261,35127,57416,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#d279ffff","u16":[53908,31050,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#b875dcff","out":{"hex":"#c28ee0ff","u16":[49817,36441,57667,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#b875dcff","out":{"hex":"#d37fffff","u16":[54282,32682,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#c28ee0ff","u16":[49817,36441,57667,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#d37fffff","u16":[54282,32682,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#b875dcff","out":{"hex":"#b56ddbff","u16":[46576,28043,56231,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#b875dcff","out":{"hex":"#cb54ffff","u16":[52126,21512,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#b875dcff","out":{"hex":"#b05dd9ff","u16":[45297,23995,55684,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#b875dcff","out":{"hex":"#c83affff","u16":[51296,14778,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#ae56d8ff","u16":[44773,22124,55464,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#c629ffff","u16":[50959,10649,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#ad50d7ff","u16":[44376,20584,55298,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#c514ffff","u16":[50704,5213,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#b875dcff","out":{"hex":"#a94dd3ff","u16":[43355,19740,54112,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#b875dcff","out":{"hex":"#c100faff","u16":[49590,0,64180,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#a94dd3ff","u16":[43355,19740,54112,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#c100faff","u16":[49590,0,64180,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#b875dcff","out":{"hex":"#b15fd9ff","u16":[45448,24508,55748,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#b875dcff","out":{"hex":"#c83dffff","u16":[51393,15743,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#b875dcff","out":{"hex":"#8f41b3ff","u16":[36770,16742,45893,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#b875dcff","out":{"hex":"#a400d4ff","u16":[42058,0,54432,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#793797ff","u16":[31040,14133,38742,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#8a00b3ff","u16":[35504,0,45950,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#642e7dff","u16":[25721,11711,32102,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#720094ff","u16":[29420,0,38075,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#b875dcff","out":{"hex":"#48215aff","u16":[18560,8451,23165,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#b875dcff","out":{"hex":"#53006bff","u16":[21229,0,27475,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#48215aff","u16":[18560,8451,23165,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#53006bff","u16":[21229,0,27475,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#b875dcff","out":{"hex":"#7d399bff","u16":[32008,14574,39950,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#b875dcff","out":{"hex":"#8e00b8ff","u16":[36611,0,47383,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#b875dcff","out":{"hex":"#602c78ff","u16":[24642,11220,30756,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#b875dcff","out":{"hex":"#6e008eff","u16":[28186,0,36479,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#512565ff","u16":[20802,9472,25964,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#b875dcff","out":{"hex":"#5d0078ff","u16":[23794,0,30795,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#431f54ff","u16":[17238,7848,21514,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#b875dcff","out":{"hex":"#4d0063ff","u16":[19717,0,25517,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#b875dcff","out":{"hex":"#30163cff","u16":[12438,5664,15525,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#b875dcff","out":{"hex":"#370048ff","u16":[14227,0,18413,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#30163cff","u16":[12438,5664,15525,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#b875dcff","out":{"hex":"#370048ff","u16":[14227,0,18413,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#7e47e1ff","u16":[32362,18264,57731,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#8532ffff","u16":[34202,12784,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#926ee4ff","out":{"hex":"#9b7ce6ff","u16":[39807,31948,59023,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#926ee4ff","out":{"hex":"#a074ffff","u16":[41038,29921,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#a58be8ff","u16":[42357,35781,59530,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#a985ffff","u16":[43420,34160,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#ac95e9ff","u16":[44155,38361,59907,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#b090ffff","u16":[45108,36965,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#926ee4ff","out":{"hex":"#b29febff","u16":[45869,40747,60281,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#926ee4ff","out":{"hex":"#b69affff","u16":[46723,39536,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#b29febff","u16":[45869,40747,60281,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#b69affff","u16":[46723,39536,65535,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#8a60e3ff","u16":[35469,24672,58236,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#9054ffff","u16":[37030,21467,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#926ee4ff","out":{"hex":"#9674e5ff","u16":[38499,29878,58775,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#926ee4ff","out":{"hex":"#9b6bffff","u16":[39824,27584,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#9a7be6ff","u16":[39635,31679,58989,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#9f73ffff","u16":[40878,29620,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#9d80e6ff","u16":[40461,32954,59150,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#a279ffff","u16":[41647,31043,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#926ee4ff","out":{"hex":"#a185e7ff","u16":[41267,34172,59309,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#926ee4ff","out":{"hex":"#a57effff","u16":[42400,32392,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#a185e7ff","u16":[41267,34172,59309,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#a57effff","u16":[42400,32392,65535,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#8e68e3ff","u16":[36556,26619,58424,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#945dffff","u16":[38028,23806,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#926ee4ff","out":{"hex":"#875be2ff","u16":[34786,23388,58120,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#926ee4ff","out":{"hex":"#8e4dffff","u16":[36405,19874,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#8455e2ff","u16":[34047,21933,57999,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#8b46ffff","u16":[35731,18007,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#8251e1ff","u16":[33480,20761,57907,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#8940ffff","u16":[35215,16438,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#926ee4ff","out":{"hex":"#804ce1ff","u16":[32901,19504,57815,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#926ee4ff","out":{"hex":"#8739ffff","u16":[34690,14665,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#804ce1ff","u16":[32901,19504,57815,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#8739ffff","u16":[34690,14665,65535,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#885ce2ff","u16":[34942,23686,58147,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#8e4fffff","u16":[36548,20248,65535,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#926ee4ff","out":{"hex":"#7133d3ff","u16":[29009,13221,54202,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#926ee4ff","out":{"hex":"#7800f0ff","u16":[30895,0,61784,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#5f2bb2ff","u16":[24489,11160,45756,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#6500cbff","u16":[26081,0,52156,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#4f2494ff","u16":[20292,9248,37915,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#5400a8ff","u16":[21611,0,43218,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#926ee4ff","out":{"hex":"#391a6aff","u16":[14643,6673,27359,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#926ee4ff","out":{"hex":"#3d0079ff","u16":[15594,0,31186,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#391a6aff","u16":[14643,6673,27359,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#3d0079ff","u16":[15594,0,31186,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#642ebbff","u16":[25691,11708,48004,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#926ee4ff","out":{"hex":"#6a00d5ff","u16":[27362,0,54718,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#926ee4ff","out":{"hex":"#4d2390ff","u16":[19779,9015,36957,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#926ee4ff","out":{"hex":"#5200a4ff","u16":[21065,0,42126,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#411e79ff","u16":[16697,7609,31198,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#926ee4ff","out":{"hex":"#45008aff","u16":[17783,0,35562,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#361965ff","u16":[13836,6306,25852,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#926ee4ff","out":{"hex":"#390073ff","u16":[14735,0,29468,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#926ee4ff","out":{"hex":"#271249ff","u16":[9984,4550,18654,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#926ee4ff","out":{"hex":"#290053ff","u16":[10633,0,21264,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#271249ff","u16":[9984,4550,18654,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#926ee4ff","out":{"hex":"#290053ff","u16":[10633,0,21264,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#686b6fff","out":{"hex":"#505255ff","u16":[20451,21041,21827,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#686b6fff","out":{"hex":"#39546eff","u16":[14529,21633,28322,65535]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#686b6fff","out":{"hex":"#72757aff","u16":[29284,30129,31255,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#686b6fff","out":{"hex":"#51799eff","u16":[20804,30976,40556,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#7d8085ff","u16":[32036,32960,34192,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#5984adff","u16":[22759,33887,44366,65535]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#84888dff","u16":[33926,34905,36210,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#5e8cb7ff","u16":[24102,35887,46985,65535]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#686b6fff","out":{"hex":"#8b8f94ff","u16":[35698,36728,38101,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#686b6fff","out":{"hex":"#6393c0ff","u16":[25361,37761,49439,65535]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#8b8f94ff","u16":[35698,36728,38101,65535]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#6393c0ff","u16":[25361,37761,49439,65535]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#686b6fff","out":{"hex":"#5e6164ff","u16":[24155,24852,25781,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#686b6fff","out":{"hex":"#436382ff","u16":[17160,25551,33452,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#686b6fff","out":{"hex":"#6d7074ff","u16":[27909,28714,29788,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#686b6fff","out":{"hex":"#4d7396ff","u16":[19828,29522,38652,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#727579ff","u16":[29252,30096,31221,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#51789eff","u16":[20782,30943,40511,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#76797dff","u16":[30213,31084,32246,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#547ca3ff","u16":[21464,31959,41841,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#686b6fff","out":{"hex":"#797d81ff","u16":[31138,32036,33234,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#686b6fff","out":{"hex":"#5680a8ff","u16":[22121,32937,43123,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#797d81ff","u16":[31138,32036,33234,65535]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#5680a8ff","u16":[22121,32937,43123,65535]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#686b6fff","out":{"hex":"#64676bff","u16":[25805,26549,27542,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#686b6fff","out":{"hex":"#476a8bff","u16":[18333,27296,35737,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#686b6fff","out":{"hex":"#5e6064ff","u16":[24086,24781,25708,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#686b6fff","out":{"hex":"#436382ff","u16":[17112,25478,33357,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#5b5e61ff","u16":[23356,24030,24928,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#41607eff","u16":[16593,24706,32346,65535]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#595b5fff","u16":[22790,23447,24324,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#3f5e7bff","u16":[16191,24107,31562,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#686b6fff","out":{"hex":"#56595cff","u16":[22206,22847,23701,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#686b6fff","out":{"hex":"#3d5b78ff","u16":[15776,23490,30754,65535]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#56595cff","u16":[22206,22847,23701,65535]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#3d5b78ff","u16":[15776,23490,30754,65535]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#686b6fff","out":{"hex":"#5e6164ff","u16":[24150,24847,25775,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#686b6fff","out":{"hex":"#436382ff","u16":[17157,25546,33445,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#686b6fff","out":{"hex":"#484a4dff","u16":[18592,19129,19844,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#686b6fff","out":{"hex":"#334d64ff","u16":[13209,19667,25749,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#3d3f41ff","u16":[15695,16148,16752,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#2b4155ff","u16":[11150,16602,21736,65535]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#333436ff","u16":[13006,13381,13881,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#243646ff","u16":[9239,13757,18011,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#686b6fff","out":{"hex":"#252627ff","u16":[9385,9655,10016,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#686b6fff","out":{"hex":"#1a2733ff","u16":[6667,9927,12997,65535]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#252627ff","u16":[9385,9655,10016,65535]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#1a2733ff","u16":[6667,9927,12997,65535]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#686b6fff","out":{"hex":"#424446ff","u16":[16899,17387,18036,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#686b6fff","out":{"hex":"#2f465bff","u16":[12006,17876,23403,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#686b6fff","out":{"hex":"#333436ff","u16":[13010,13385,13886,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#686b6fff","out":{"hex":"#243646ff","u16":[9243,13762,18018,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#2b2c2eff","u16":[10983,11300,11722,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#686b6fff","out":{"hex":"#1e2d3bff","u16":[7802,11617,15210,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#232426ff","u16":[9101,9363,9713,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#686b6fff","out":{"hex":"#192531ff","u16":[6465,9626,12603,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#686b6fff","out":{"hex":"#1a1a1bff","u16":[6567,6756,7009,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#686b6fff","out":{"hex":"#121b23ff","u16":[4665,6946,9095,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#1a1a1bff","u16":[6567,6756,7009,65535]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#686b6fff","out":{"hex":"#121b23ff","u16":[4665,6946,9095,65535]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#3daee980","out":{"hex":"#3290c180","u16":[12967,36988,49529,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee980","out":{"hex":"#0091c780","u16":[0,37260,51067,32896]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#3daee980","out":{"hex":"#4bbaf780","u16":[19330,47912,63602,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#3daee980","out":{"hex":"#2bbcff80","u16":[11102,48251,65535,32896]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#3daee980","out":{"hex":"#7ac6f980","u16":[31305,50791,63873,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee980","out":{"hex":"#6dc7ff80","u16":[28119,51064,65535,32896]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#3daee980","out":{"hex":"#92cef980","u16":[37530,52828,64075,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee980","out":{"hex":"#89ceff80","u16":[35335,53057,65535,32896]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#3daee980","out":{"hex":"#a6d5fa80","u16":[42710,54775,64277,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#3daee980","out":{"hex":"#a0d6ff80","u16":[41108,54964,65535,32896]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#3daee980","out":{"hex":"#a6d5fa80","u16":[42710,54775,64277,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee980","out":{"hex":"#a0d6ff80","u16":[41108,54964,65535,32896]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#3daee980","out":{"hex":"#39a4db80","u16":[14749,42070,56336,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee980","out":{"hex":"#00a5e280","u16":[0,42380,58085,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#3daee980","out":{"hex":"#3fb3f080","u16":[16117,45974,61563,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#3daee980","out":{"hex":"#00b4f780","u16":[0,46312,63474,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#3daee980","out":{"hex":"#41b9f780","u16":[16627,47428,63511,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee980","out":{"hex":"#00baff80","u16":[0,47778,65483,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#3daee980","out":{"hex":"#52bcf880","u16":[21175,48262,63634,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee980","out":{"hex":"#38bdff80","u16":[14469,48592,65535,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#3daee980","out":{"hex":"#61bff880","u16":[24886,49070,63709,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#3daee980","out":{"hex":"#4ec0ff80","u16":[19992,49381,65535,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#3daee980","out":{"hex":"#61bff880","u16":[24886,49070,63709,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee980","out":{"hex":"#4ec0ff80","u16":[19992,49381,65535,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#3daee980","out":{"hex":"#3ba7e080","u16":[15084,43027,57617,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee980","out":{"hex":"#00a9e780","u16":[0,43344,59406,32896]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#3daee980","out":{"hex":"#369bd080","u16":[13973,39859,53374,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#3daee980","out":{"hex":"#009cd680","u16":[0,40152,55031,32896]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#3daee980","out":{"hex":"#3596c980","u16":[13498,38503,51559,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee980","out":{"hex":"#0097cf80","u16":[0,38787,53160,32896]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#3daee980","out":{"hex":"#3392c380","u16":[13128,37447,50145,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee980","out":{"hex":"#0093c980","u16":[0,37723,51702,32896]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#3daee980","out":{"hex":"#328dbd80","u16":[12745,36355,48682,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#3daee980","out":{"hex":"#008fc380","u16":[0,36623,50194,32896]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#3daee980","out":{"hex":"#328dbd80","u16":[12745,36355,48682,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee980","out":{"hex":"#008fc380","u16":[0,36623,50194,32896]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#3daee980","out":{"hex":"#379dd380","u16":[14165,40405,54105,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee980","out":{"hex":"#009ed980","u16":[0,40702,55785,32896]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#3daee980","out":{"hex":"#2a79a280","u16":[10905,31107,41654,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#3daee980","out":{"hex":"#007aa780","u16":[0,31336,42948,32896]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#3daee980","out":{"hex":"#24668980","u16":[9206,26259,35163,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee980","out":{"hex":"#00678d80","u16":[0,26453,36255,32896]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#3daee980","out":{"hex":"#1e557180","u16":[7628,21759,29137,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee980","out":{"hex":"#00557580","u16":[0,21919,30042,32896]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#3daee980","out":{"hex":"#153d5280","u16":[5504,15701,21025,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#3daee980","out":{"hex":"#003e5480","u16":[0,15817,21678,32896]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#3daee980","out":{"hex":"#153d5280","u16":[5504,15701,21025,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee980","out":{"hex":"#003e5480","u16":[0,15817,21678,32896]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#3daee980","out":{"hex":"#24668980","u16":[9200,26243,35140,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#3daee980","out":{"hex":"#00678d80","u16":[0,26435,36232,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#3daee980","out":{"hex":"#1c4f6980","u16":[7083,20204,27054,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#3daee980","out":{"hex":"#004f6d80","u16":[0,20352,27894,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#3daee980","out":{"hex":"#17425980","u16":[5979,17055,22838,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#3daee980","out":{"hex":"#00435c80","u16":[0,17181,23547,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#3daee980","out":{"hex":"#13374a80","u16":[4954,14132,18924,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#3daee980","out":{"hex":"#00374c80","u16":[0,14236,19512,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#3daee980","out":{"hex":"#0e283580","u16":[3575,10198,13655,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#3daee980","out":{"hex":"#00283780","u16":[0,10273,14080,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#3daee980","out":{"hex":"#0e283580","u16":[3575,10198,13655,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#3daee980","out":{"hex":"#00283780","u16":[0,10273,14080,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#ffffff00","out":{"hex":"#fafafa00","u16":[64330,64330,64330,0]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#ffffff00","out":{"hex":"#fdfafa00","u16":[64936,64165,64165,0]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#e6e6e600","u16":[59214,59214,59214,0]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#f3e3e300","u16":[62470,58297,58297,0]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#d6d6d600","u16":[54997,54997,54997,0]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#ecd0d000","u16":[60542,53381,53381,0]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#ffffff00","out":{"hex":"#c4c4c400","u16":[50352,50352,50352,0]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#ffffff00","out":{"hex":"#e4baba00","u16":[58536,47850,47850,0]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#c4c4c400","u16":[50352,50352,50352,0]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#e4baba00","u16":[58536,47850,47850,0]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#ffffff00","out":{"hex":"#fdfdfd00","u16":[64936,64936,64936,0]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#ffffff00","out":{"hex":"#fefcfc00","u16":[65236,64855,64855,0]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#f3f3f300","u16":[62470,62470,62470,0]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#f9f1f100","u16":[64025,62043,62043,0]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#ececec00","u16":[60542,60542,60542,0]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#f6e9e900","u16":[63098,59829,59829,0]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#ffffff00","out":{"hex":"#e4e4e400","u16":[58536,58536,58536,0]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#ffffff00","out":{"hex":"#f2e0e000","u16":[62154,57512,57512,0]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#e4e4e400","u16":[58536,58536,58536,0]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#f2e0e000","u16":[62154,57512,57512,0]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#ffffff00","out":{"hex":"#fafafa00","u16":[64330,64330,64330,0]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#ffffff00","out":{"hex":"#fdfafa00","u16":[64936,64165,64165,0]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#e6e6e600","u16":[59214,59214,59214,0]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#f3e3e300","u16":[62470,58297,58297,0]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#d6d6d600","u16":[54997,54997,54997,0]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#ecd0d000","u16":[60542,53381,53381,0]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#ffffff00","out":{"hex":"#c4c4c400","u16":[50352,50352,50352,0]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#ffffff00","out":{"hex":"#e4baba00","u16":[58536,47850,47850,0]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#c4c4c400","u16":[50352,50352,50352,0]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#e4baba00","u16":[58536,47850,47850,0]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#ffffff00","out":{"hex":"#f8f8f800","u16":[63717,63717,63717,0]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#ffffff00","out":{"hex":"#fbf7f700","u16":[64634,63467,63467,0]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#d9d9d900","u16":[55727,55727,55727,0]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#edd3d300","u16":[60868,54237,54237,0]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#bdbdbd00","u16":[48684,48684,48684,0]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#e1b2b200","u16":[57849,45826,45826,0]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#ffffff00","out":{"hex":"#9c9c9c00","u16":[40134,40134,40134,0]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#ffffff00","out":{"hex":"#d5888800","u16":[54628,34956,34956,0]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#9c9c9c00","u16":[40134,40134,40134,0]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#d5888800","u16":[54628,34956,34956,0]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#ffffff00","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#ffffff00","out":{"hex":"#f3f3f300","u16":[62470,62470,62470,0]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#ffffff00","out":{"hex":"#f9f1f100","u16":[64025,62043,62043,0]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#c1c1c100","u16":[49526,49526,49526,0]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#ffffff00","out":{"hex":"#e2b6b600","u16":[58194,46851,46851,0]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#8d8d8d00","u16":[36141,36141,36141,0]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#ffffff00","out":{"hex":"#cf727200","u16":[53312,29400,29400,0]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#ffffff00","out":{"hex":"#00000000","u16":[0,0,0,0]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#00000080","out":{"hex":"#41414180","u16":[16792,16792,16792,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#00000080","out":{"hex":"#69303080","u16":[27038,12254,12254,32896]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#00000080","out":{"hex":"#aeaeae80","u16":[44655,44655,44655,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#00000080","out":{"hex":"#db9f9f80","u16":[56266,40827,40827,32896]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#00000080","out":{"hex":"#dbdbdb80","u16":[56266,56266,56266,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#00000080","out":{"hex":"#eed5d580","u16":[61112,54869,54869,32896]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#00000080","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#00000080","out":{"hex":"#34343480","u16":[13312,13312,13312,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#00000080","out":{"hex":"#53262680","u16":[21435,9715,9715,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#00000080","out":{"hex":"#8d8d8d80","u16":[36141,36141,36141,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#00000080","out":{"hex":"#cf727280","u16":[53312,29400,29400,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#00000080","out":{"hex":"#b1b1b180","u16":[45587,45587,45587,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#00000080","out":{"hex":"#dca3a380","u16":[56623,42000,42000,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#00000080","out":{"hex":"#cfcfcf80","u16":[53121,53121,53121,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#00000080","out":{"hex":"#e8c7c780","u16":[59716,51163,51163,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#00000080","out":{"hex":"#cfcfcf80","u16":[53121,53121,53121,32896]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#00000080","out":{"hex":"#e8c7c780","u16":[59716,51163,51163,32896]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#00000080","out":{"hex":"#1f1f1f80","u16":[8079,8079,8079,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#00000080","out":{"hex":"#33171780","u16":[13009,5896,5896,32896]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#00000080","out":{"hex":"#55555580","u16":[21935,21935,21935,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#00000080","out":{"hex":"#893e3e80","u16":[35319,16007,16007,32896]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#00000080","out":{"hex":"#6c6c6c80","u16":[27667,27667,27667,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#00000080","out":{"hex":"#ad4f4f80","u16":[44550,20190,20190,32896]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#00000080","out":{"hex":"#7d7d7d80","u16":[32240,32240,32240,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#00000080","out":{"hex":"#ca5c5c80","u16":[51912,23527,23527,32896]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#00000080","out":{"hex":"#7d7d7d80","u16":[32240,32240,32240,32896]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#00000080","out":{"hex":"#ca5c5c80","u16":[51912,23527,23527,32896]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#00000080","out":{"hex":"#2b2b2b80","u16":[11072,11072,11072,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#00000080","out":{"hex":"#451f1f80","u16":[17827,8079,8079,32896]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#00000080","out":{"hex":"#75757580","u16":[30058,30058,30058,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#00000080","out":{"hex":"#bc555580","u16":[48399,21935,21935,32896]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#00000080","out":{"hex":"#94949480","u16":[37914,37914,37914,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#00000080","out":{"hex":"#d27c7c80","u16":[53881,31924,31924,32896]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#00000080","out":{"hex":"#acacac80","u16":[44180,44180,44180,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#00000080","out":{"hex":"#da9d9d80","u16":[56087,40225,40225,32896]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#00000080","out":{"hex":"#acacac80","u16":[44180,44180,44180,32896]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#00000080","out":{"hex":"#da9d9d80","u16":[56087,40225,40225,32896]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#00000080","out":{"hex":"#00000080","u16":[0,0,0,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#00000080","out":{"hex":"#34343480","u16":[13312,13312,13312,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#00000080","out":{"hex":"#53262680","u16":[21435,9715,9715,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#00000080","out":{"hex":"#8d8d8d80","u16":[36141,36141,36141,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#00000080","out":{"hex":"#cf727280","u16":[53312,29400,29400,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#00000080","out":{"hex":"#b1b1b180","u16":[45587,45587,45587,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#00000080","out":{"hex":"#dca3a380","u16":[56623,42000,42000,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#00000080","out":{"hex":"#cfcfcf80","u16":[53121,53121,53121,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#00000080","out":{"hex":"#e8c7c780","u16":[59716,51163,51163,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#00000080","out":{"hex":"#cfcfcf80","u16":[53121,53121,53121,32896]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#00000080","out":{"hex":"#e8c7c780","u16":[59716,51163,51163,32896]},"role":"Shadow"}, +{"chroma":0,"contrast":-1,"in":"#ff000033","out":{"hex":"#cb000033","u16":[52246,0,0,13107]},"role":"Light"}, +{"chroma":0.5,"contrast":-1,"in":"#ff000033","out":{"hex":"#cb000033","u16":[52246,0,0,13107]},"role":"Light"}, +{"chroma":0,"contrast":0,"in":"#ff000033","out":{"hex":"#ff434333","u16":[65535,17242,17242,13107]},"role":"Light"}, +{"chroma":0.5,"contrast":0,"in":"#ff000033","out":{"hex":"#ff434333","u16":[65535,17242,17242,13107]},"role":"Light"}, +{"chroma":0,"contrast":0.4,"in":"#ff000033","out":{"hex":"#ff606033","u16":[65535,24674,24674,13107]},"role":"Light"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff000033","out":{"hex":"#ff606033","u16":[65535,24674,24674,13107]},"role":"Light"}, +{"chroma":0,"contrast":0.7,"in":"#ff000033","out":{"hex":"#ff707033","u16":[65535,28837,28837,13107]},"role":"Light"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff000033","out":{"hex":"#ff707033","u16":[65535,28837,28837,13107]},"role":"Light"}, +{"chroma":0,"contrast":1,"in":"#ff000033","out":{"hex":"#ff7e7e33","u16":[65535,32379,32379,13107]},"role":"Light"}, +{"chroma":0.5,"contrast":1,"in":"#ff000033","out":{"hex":"#ff7e7e33","u16":[65535,32379,32379,13107]},"role":"Light"}, +{"chroma":0,"contrast":1.5,"in":"#ff000033","out":{"hex":"#ff7e7e33","u16":[65535,32379,32379,13107]},"role":"Light"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff000033","out":{"hex":"#ff7e7e33","u16":[65535,32379,32379,13107]},"role":"Light"}, +{"chroma":0,"contrast":-1,"in":"#ff000033","out":{"hex":"#eb000033","u16":[60309,0,0,13107]},"role":"Midlight"}, +{"chroma":0.5,"contrast":-1,"in":"#ff000033","out":{"hex":"#eb000033","u16":[60309,0,0,13107]},"role":"Midlight"}, +{"chroma":0,"contrast":0,"in":"#ff000033","out":{"hex":"#ff2e2e33","u16":[65535,11694,11694,13107]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0,"in":"#ff000033","out":{"hex":"#ff2e2e33","u16":[65535,11694,11694,13107]},"role":"Midlight"}, +{"chroma":0,"contrast":0.4,"in":"#ff000033","out":{"hex":"#ff414133","u16":[65535,16734,16734,13107]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff000033","out":{"hex":"#ff414133","u16":[65535,16734,16734,13107]},"role":"Midlight"}, +{"chroma":0,"contrast":0.7,"in":"#ff000033","out":{"hex":"#ff4c4c33","u16":[65535,19557,19557,13107]},"role":"Midlight"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff000033","out":{"hex":"#ff4c4c33","u16":[65535,19557,19557,13107]},"role":"Midlight"}, +{"chroma":0,"contrast":1,"in":"#ff000033","out":{"hex":"#ff555533","u16":[65535,21960,21960,13107]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1,"in":"#ff000033","out":{"hex":"#ff555533","u16":[65535,21960,21960,13107]},"role":"Midlight"}, +{"chroma":0,"contrast":1.5,"in":"#ff000033","out":{"hex":"#ff555533","u16":[65535,21960,21960,13107]},"role":"Midlight"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff000033","out":{"hex":"#ff555533","u16":[65535,21960,21960,13107]},"role":"Midlight"}, +{"chroma":0,"contrast":-1,"in":"#ff000033","out":{"hex":"#f6000033","u16":[63210,0,0,13107]},"role":"Mid"}, +{"chroma":0.5,"contrast":-1,"in":"#ff000033","out":{"hex":"#f6000033","u16":[63210,0,0,13107]},"role":"Mid"}, +{"chroma":0,"contrast":0,"in":"#ff000033","out":{"hex":"#e5000033","u16":[58875,0,0,13107]},"role":"Mid"}, +{"chroma":0.5,"contrast":0,"in":"#ff000033","out":{"hex":"#e5000033","u16":[58875,0,0,13107]},"role":"Mid"}, +{"chroma":0,"contrast":0.4,"in":"#ff000033","out":{"hex":"#de000033","u16":[57029,0,0,13107]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff000033","out":{"hex":"#de000033","u16":[57029,0,0,13107]},"role":"Mid"}, +{"chroma":0,"contrast":0.7,"in":"#ff000033","out":{"hex":"#d8000033","u16":[55596,0,0,13107]},"role":"Mid"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff000033","out":{"hex":"#d8000033","u16":[55596,0,0,13107]},"role":"Mid"}, +{"chroma":0,"contrast":1,"in":"#ff000033","out":{"hex":"#d3000033","u16":[54118,0,0,13107]},"role":"Mid"}, +{"chroma":0.5,"contrast":1,"in":"#ff000033","out":{"hex":"#d3000033","u16":[54118,0,0,13107]},"role":"Mid"}, +{"chroma":0,"contrast":1.5,"in":"#ff000033","out":{"hex":"#d3000033","u16":[54118,0,0,13107]},"role":"Mid"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff000033","out":{"hex":"#d3000033","u16":[54118,0,0,13107]},"role":"Mid"}, +{"chroma":0,"contrast":-1,"in":"#ff000033","out":{"hex":"#e6000033","u16":[59214,0,0,13107]},"role":"Dark"}, +{"chroma":0.5,"contrast":-1,"in":"#ff000033","out":{"hex":"#e6000033","u16":[59214,0,0,13107]},"role":"Dark"}, +{"chroma":0,"contrast":0,"in":"#ff000033","out":{"hex":"#b1000033","u16":[45587,0,0,13107]},"role":"Dark"}, +{"chroma":0.5,"contrast":0,"in":"#ff000033","out":{"hex":"#b1000033","u16":[45587,0,0,13107]},"role":"Dark"}, +{"chroma":0,"contrast":0.4,"in":"#ff000033","out":{"hex":"#96000033","u16":[38483,0,0,13107]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff000033","out":{"hex":"#96000033","u16":[38483,0,0,13107]},"role":"Dark"}, +{"chroma":0,"contrast":0.7,"in":"#ff000033","out":{"hex":"#7c000033","u16":[31889,0,0,13107]},"role":"Dark"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff000033","out":{"hex":"#7c000033","u16":[31889,0,0,13107]},"role":"Dark"}, +{"chroma":0,"contrast":1,"in":"#ff000033","out":{"hex":"#5a000033","u16":[23011,0,0,13107]},"role":"Dark"}, +{"chroma":0.5,"contrast":1,"in":"#ff000033","out":{"hex":"#5a000033","u16":[23011,0,0,13107]},"role":"Dark"}, +{"chroma":0,"contrast":1.5,"in":"#ff000033","out":{"hex":"#5a000033","u16":[23011,0,0,13107]},"role":"Dark"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff000033","out":{"hex":"#5a000033","u16":[23011,0,0,13107]},"role":"Dark"}, +{"chroma":0,"contrast":-1,"in":"#ff000033","out":{"hex":"#9e000033","u16":[40612,0,0,13107]},"role":"Shadow"}, +{"chroma":0.5,"contrast":-1,"in":"#ff000033","out":{"hex":"#9e000033","u16":[40612,0,0,13107]},"role":"Shadow"}, +{"chroma":0,"contrast":0,"in":"#ff000033","out":{"hex":"#7a000033","u16":[31266,0,0,13107]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0,"in":"#ff000033","out":{"hex":"#7a000033","u16":[31266,0,0,13107]},"role":"Shadow"}, +{"chroma":0,"contrast":0.4,"in":"#ff000033","out":{"hex":"#67000033","u16":[26394,0,0,13107]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.4,"in":"#ff000033","out":{"hex":"#67000033","u16":[26394,0,0,13107]},"role":"Shadow"}, +{"chroma":0,"contrast":0.7,"in":"#ff000033","out":{"hex":"#55000033","u16":[21871,0,0,13107]},"role":"Shadow"}, +{"chroma":0.5,"contrast":0.7,"in":"#ff000033","out":{"hex":"#55000033","u16":[21871,0,0,13107]},"role":"Shadow"}, +{"chroma":0,"contrast":1,"in":"#ff000033","out":{"hex":"#3d000033","u16":[15782,0,0,13107]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1,"in":"#ff000033","out":{"hex":"#3d000033","u16":[15782,0,0,13107]},"role":"Shadow"}, +{"chroma":0,"contrast":1.5,"in":"#ff000033","out":{"hex":"#3d000033","u16":[15782,0,0,13107]},"role":"Shadow"}, +{"chroma":0.5,"contrast":1.5,"in":"#ff000033","out":{"hex":"#3d000033","u16":[15782,0,0,13107]},"role":"Shadow"} +], +"qt_lighter_darker": [ +{"factor":-10,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":-10,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":0,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":0,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":1,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":1,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":50,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":50,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":99,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":99,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":100,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":100,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":101,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":101,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":102,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":102,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":110,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":110,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":120,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":120,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":150,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":150,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":200,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":200,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":300,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":300,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":1000,"in":"#000000ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":1000,"in":"#000000ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"factor":-10,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":-10,"in":"#ffffffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":0,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":0,"in":"#ffffffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1,"in":"#ffffffff","op":"lighter","out":{"hex":"#030303ff","u16":[655,655,655,65535]}}, +{"factor":1,"in":"#ffffffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#ffffffff","op":"lighter","out":{"hex":"#7f7f7fff","u16":[32767,32767,32767,65535]}}, +{"factor":50,"in":"#ffffffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#ffffffff","op":"lighter","out":{"hex":"#fcfcfcff","u16":[64886,64886,64886,65535]}}, +{"factor":99,"in":"#ffffffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":100,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":100,"in":"#ffffffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":101,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":101,"in":"#ffffffff","op":"darker","out":{"hex":"#fcfcfcff","u16":[64886,64886,64886,65535]}}, +{"factor":102,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":102,"in":"#ffffffff","op":"darker","out":{"hex":"#fafafaff","u16":[64250,64250,64250,65535]}}, +{"factor":110,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":110,"in":"#ffffffff","op":"darker","out":{"hex":"#e8e8e8ff","u16":[59577,59577,59577,65535]}}, +{"factor":120,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":120,"in":"#ffffffff","op":"darker","out":{"hex":"#d4d4d4ff","u16":[54612,54612,54612,65535]}}, +{"factor":150,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#ffffffff","op":"darker","out":{"hex":"#aaaaaaff","u16":[43690,43690,43690,65535]}}, +{"factor":200,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#ffffffff","op":"darker","out":{"hex":"#7f7f7fff","u16":[32767,32767,32767,65535]}}, +{"factor":300,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#ffffffff","op":"darker","out":{"hex":"#555555ff","u16":[21845,21845,21845,65535]}}, +{"factor":1000,"in":"#ffffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#ffffffff","op":"darker","out":{"hex":"#191919ff","u16":[6553,6553,6553,65535]}}, +{"factor":-10,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"factor":-10,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"factor":0,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"factor":0,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"factor":1,"in":"#010101ff","op":"lighter","out":{"hex":"#000000ff","u16":[2,2,2,65535]}}, +{"factor":1,"in":"#010101ff","op":"darker","out":{"hex":"#646464ff","u16":[25700,25700,25700,65535]}}, +{"factor":50,"in":"#010101ff","op":"lighter","out":{"hex":"#000000ff","u16":[128,128,128,65535]}}, +{"factor":50,"in":"#010101ff","op":"darker","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":99,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[254,254,254,65535]}}, +{"factor":99,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[259,259,259,65535]}}, +{"factor":100,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"factor":100,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"factor":101,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[259,259,259,65535]}}, +{"factor":101,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[254,254,254,65535]}}, +{"factor":102,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[262,262,262,65535]}}, +{"factor":102,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[251,251,251,65535]}}, +{"factor":110,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[282,282,282,65535]}}, +{"factor":110,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[233,233,233,65535]}}, +{"factor":120,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[308,308,308,65535]}}, +{"factor":120,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[214,214,214,65535]}}, +{"factor":150,"in":"#010101ff","op":"lighter","out":{"hex":"#010101ff","u16":[385,385,385,65535]}}, +{"factor":150,"in":"#010101ff","op":"darker","out":{"hex":"#010101ff","u16":[171,171,171,65535]}}, +{"factor":200,"in":"#010101ff","op":"lighter","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":200,"in":"#010101ff","op":"darker","out":{"hex":"#000000ff","u16":[128,128,128,65535]}}, +{"factor":300,"in":"#010101ff","op":"lighter","out":{"hex":"#030303ff","u16":[771,771,771,65535]}}, +{"factor":300,"in":"#010101ff","op":"darker","out":{"hex":"#000000ff","u16":[85,85,85,65535]}}, +{"factor":1000,"in":"#010101ff","op":"lighter","out":{"hex":"#0a0a0aff","u16":[2570,2570,2570,65535]}}, +{"factor":1000,"in":"#010101ff","op":"darker","out":{"hex":"#000000ff","u16":[25,25,25,65535]}}, +{"factor":-10,"in":"#020202ff","op":"lighter","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":-10,"in":"#020202ff","op":"darker","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":0,"in":"#020202ff","op":"lighter","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":0,"in":"#020202ff","op":"darker","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":1,"in":"#020202ff","op":"lighter","out":{"hex":"#000000ff","u16":[5,5,5,65535]}}, +{"factor":1,"in":"#020202ff","op":"darker","out":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}}, +{"factor":50,"in":"#020202ff","op":"lighter","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"factor":50,"in":"#020202ff","op":"darker","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":99,"in":"#020202ff","op":"lighter","out":{"hex":"#020202ff","u16":[508,508,508,65535]}}, +{"factor":99,"in":"#020202ff","op":"darker","out":{"hex":"#020202ff","u16":[519,519,519,65535]}}, +{"factor":100,"in":"#020202ff","op":"lighter","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":100,"in":"#020202ff","op":"darker","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":101,"in":"#020202ff","op":"lighter","out":{"hex":"#020202ff","u16":[519,519,519,65535]}}, +{"factor":101,"in":"#020202ff","op":"darker","out":{"hex":"#020202ff","u16":[508,508,508,65535]}}, +{"factor":102,"in":"#020202ff","op":"lighter","out":{"hex":"#020202ff","u16":[524,524,524,65535]}}, +{"factor":102,"in":"#020202ff","op":"darker","out":{"hex":"#020202ff","u16":[503,503,503,65535]}}, +{"factor":110,"in":"#020202ff","op":"lighter","out":{"hex":"#020202ff","u16":[565,565,565,65535]}}, +{"factor":110,"in":"#020202ff","op":"darker","out":{"hex":"#020202ff","u16":[467,467,467,65535]}}, +{"factor":120,"in":"#020202ff","op":"lighter","out":{"hex":"#020202ff","u16":[616,616,616,65535]}}, +{"factor":120,"in":"#020202ff","op":"darker","out":{"hex":"#020202ff","u16":[428,428,428,65535]}}, +{"factor":150,"in":"#020202ff","op":"lighter","out":{"hex":"#030303ff","u16":[771,771,771,65535]}}, +{"factor":150,"in":"#020202ff","op":"darker","out":{"hex":"#010101ff","u16":[342,342,342,65535]}}, +{"factor":200,"in":"#020202ff","op":"lighter","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":200,"in":"#020202ff","op":"darker","out":{"hex":"#010101ff","u16":[257,257,257,65535]}}, +{"factor":300,"in":"#020202ff","op":"lighter","out":{"hex":"#060606ff","u16":[1542,1542,1542,65535]}}, +{"factor":300,"in":"#020202ff","op":"darker","out":{"hex":"#010101ff","u16":[171,171,171,65535]}}, +{"factor":1000,"in":"#020202ff","op":"lighter","out":{"hex":"#141414ff","u16":[5140,5140,5140,65535]}}, +{"factor":1000,"in":"#020202ff","op":"darker","out":{"hex":"#000000ff","u16":[51,51,51,65535]}}, +{"factor":-10,"in":"#040404ff","op":"lighter","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":-10,"in":"#040404ff","op":"darker","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":0,"in":"#040404ff","op":"lighter","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":0,"in":"#040404ff","op":"darker","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":1,"in":"#040404ff","op":"lighter","out":{"hex":"#000000ff","u16":[10,10,10,65535]}}, +{"factor":1,"in":"#040404ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#040404ff","op":"lighter","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":50,"in":"#040404ff","op":"darker","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":99,"in":"#040404ff","op":"lighter","out":{"hex":"#040404ff","u16":[1017,1017,1017,65535]}}, +{"factor":99,"in":"#040404ff","op":"darker","out":{"hex":"#040404ff","u16":[1038,1038,1038,65535]}}, +{"factor":100,"in":"#040404ff","op":"lighter","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":100,"in":"#040404ff","op":"darker","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":101,"in":"#040404ff","op":"lighter","out":{"hex":"#040404ff","u16":[1038,1038,1038,65535]}}, +{"factor":101,"in":"#040404ff","op":"darker","out":{"hex":"#040404ff","u16":[1017,1017,1017,65535]}}, +{"factor":102,"in":"#040404ff","op":"lighter","out":{"hex":"#040404ff","u16":[1048,1048,1048,65535]}}, +{"factor":102,"in":"#040404ff","op":"darker","out":{"hex":"#040404ff","u16":[1007,1007,1007,65535]}}, +{"factor":110,"in":"#040404ff","op":"lighter","out":{"hex":"#040404ff","u16":[1130,1130,1130,65535]}}, +{"factor":110,"in":"#040404ff","op":"darker","out":{"hex":"#040404ff","u16":[934,934,934,65535]}}, +{"factor":120,"in":"#040404ff","op":"lighter","out":{"hex":"#050505ff","u16":[1233,1233,1233,65535]}}, +{"factor":120,"in":"#040404ff","op":"darker","out":{"hex":"#030303ff","u16":[856,856,856,65535]}}, +{"factor":150,"in":"#040404ff","op":"lighter","out":{"hex":"#060606ff","u16":[1542,1542,1542,65535]}}, +{"factor":150,"in":"#040404ff","op":"darker","out":{"hex":"#030303ff","u16":[685,685,685,65535]}}, +{"factor":200,"in":"#040404ff","op":"lighter","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":200,"in":"#040404ff","op":"darker","out":{"hex":"#020202ff","u16":[514,514,514,65535]}}, +{"factor":300,"in":"#040404ff","op":"lighter","out":{"hex":"#0c0c0cff","u16":[3084,3084,3084,65535]}}, +{"factor":300,"in":"#040404ff","op":"darker","out":{"hex":"#010101ff","u16":[342,342,342,65535]}}, +{"factor":1000,"in":"#040404ff","op":"lighter","out":{"hex":"#282828ff","u16":[10280,10280,10280,65535]}}, +{"factor":1000,"in":"#040404ff","op":"darker","out":{"hex":"#000000ff","u16":[102,102,102,65535]}}, +{"factor":-10,"in":"#080808ff","op":"lighter","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":-10,"in":"#080808ff","op":"darker","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":0,"in":"#080808ff","op":"lighter","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":0,"in":"#080808ff","op":"darker","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":1,"in":"#080808ff","op":"lighter","out":{"hex":"#000000ff","u16":[20,20,20,65535]}}, +{"factor":1,"in":"#080808ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#080808ff","op":"lighter","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":50,"in":"#080808ff","op":"darker","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":99,"in":"#080808ff","op":"lighter","out":{"hex":"#080808ff","u16":[2035,2035,2035,65535]}}, +{"factor":99,"in":"#080808ff","op":"darker","out":{"hex":"#080808ff","u16":[2076,2076,2076,65535]}}, +{"factor":100,"in":"#080808ff","op":"lighter","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":100,"in":"#080808ff","op":"darker","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":101,"in":"#080808ff","op":"lighter","out":{"hex":"#080808ff","u16":[2076,2076,2076,65535]}}, +{"factor":101,"in":"#080808ff","op":"darker","out":{"hex":"#080808ff","u16":[2035,2035,2035,65535]}}, +{"factor":102,"in":"#080808ff","op":"lighter","out":{"hex":"#080808ff","u16":[2097,2097,2097,65535]}}, +{"factor":102,"in":"#080808ff","op":"darker","out":{"hex":"#080808ff","u16":[2015,2015,2015,65535]}}, +{"factor":110,"in":"#080808ff","op":"lighter","out":{"hex":"#090909ff","u16":[2261,2261,2261,65535]}}, +{"factor":110,"in":"#080808ff","op":"darker","out":{"hex":"#070707ff","u16":[1869,1869,1869,65535]}}, +{"factor":120,"in":"#080808ff","op":"lighter","out":{"hex":"#0a0a0aff","u16":[2467,2467,2467,65535]}}, +{"factor":120,"in":"#080808ff","op":"darker","out":{"hex":"#070707ff","u16":[1713,1713,1713,65535]}}, +{"factor":150,"in":"#080808ff","op":"lighter","out":{"hex":"#0c0c0cff","u16":[3084,3084,3084,65535]}}, +{"factor":150,"in":"#080808ff","op":"darker","out":{"hex":"#050505ff","u16":[1370,1370,1370,65535]}}, +{"factor":200,"in":"#080808ff","op":"lighter","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":200,"in":"#080808ff","op":"darker","out":{"hex":"#040404ff","u16":[1028,1028,1028,65535]}}, +{"factor":300,"in":"#080808ff","op":"lighter","out":{"hex":"#181818ff","u16":[6168,6168,6168,65535]}}, +{"factor":300,"in":"#080808ff","op":"darker","out":{"hex":"#030303ff","u16":[685,685,685,65535]}}, +{"factor":1000,"in":"#080808ff","op":"lighter","out":{"hex":"#505050ff","u16":[20560,20560,20560,65535]}}, +{"factor":1000,"in":"#080808ff","op":"darker","out":{"hex":"#010101ff","u16":[205,205,205,65535]}}, +{"factor":-10,"in":"#101010ff","op":"lighter","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":-10,"in":"#101010ff","op":"darker","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":0,"in":"#101010ff","op":"lighter","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":0,"in":"#101010ff","op":"darker","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":1,"in":"#101010ff","op":"lighter","out":{"hex":"#000000ff","u16":[41,41,41,65535]}}, +{"factor":1,"in":"#101010ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#101010ff","op":"lighter","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":50,"in":"#101010ff","op":"darker","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":99,"in":"#101010ff","op":"lighter","out":{"hex":"#101010ff","u16":[4071,4071,4071,65535]}}, +{"factor":99,"in":"#101010ff","op":"darker","out":{"hex":"#101010ff","u16":[4153,4153,4153,65535]}}, +{"factor":100,"in":"#101010ff","op":"lighter","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":100,"in":"#101010ff","op":"darker","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":101,"in":"#101010ff","op":"lighter","out":{"hex":"#101010ff","u16":[4153,4153,4153,65535]}}, +{"factor":101,"in":"#101010ff","op":"darker","out":{"hex":"#101010ff","u16":[4071,4071,4071,65535]}}, +{"factor":102,"in":"#101010ff","op":"lighter","out":{"hex":"#101010ff","u16":[4194,4194,4194,65535]}}, +{"factor":102,"in":"#101010ff","op":"darker","out":{"hex":"#101010ff","u16":[4031,4031,4031,65535]}}, +{"factor":110,"in":"#101010ff","op":"lighter","out":{"hex":"#121212ff","u16":[4523,4523,4523,65535]}}, +{"factor":110,"in":"#101010ff","op":"darker","out":{"hex":"#0f0f0fff","u16":[3738,3738,3738,65535]}}, +{"factor":120,"in":"#101010ff","op":"lighter","out":{"hex":"#131313ff","u16":[4934,4934,4934,65535]}}, +{"factor":120,"in":"#101010ff","op":"darker","out":{"hex":"#0d0d0dff","u16":[3426,3426,3426,65535]}}, +{"factor":150,"in":"#101010ff","op":"lighter","out":{"hex":"#181818ff","u16":[6168,6168,6168,65535]}}, +{"factor":150,"in":"#101010ff","op":"darker","out":{"hex":"#0b0b0bff","u16":[2741,2741,2741,65535]}}, +{"factor":200,"in":"#101010ff","op":"lighter","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":200,"in":"#101010ff","op":"darker","out":{"hex":"#080808ff","u16":[2056,2056,2056,65535]}}, +{"factor":300,"in":"#101010ff","op":"lighter","out":{"hex":"#303030ff","u16":[12336,12336,12336,65535]}}, +{"factor":300,"in":"#101010ff","op":"darker","out":{"hex":"#050505ff","u16":[1370,1370,1370,65535]}}, +{"factor":1000,"in":"#101010ff","op":"lighter","out":{"hex":"#a0a0a0ff","u16":[41120,41120,41120,65535]}}, +{"factor":1000,"in":"#101010ff","op":"darker","out":{"hex":"#020202ff","u16":[411,411,411,65535]}}, +{"factor":-10,"in":"#202020ff","op":"lighter","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":-10,"in":"#202020ff","op":"darker","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":0,"in":"#202020ff","op":"lighter","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":0,"in":"#202020ff","op":"darker","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":1,"in":"#202020ff","op":"lighter","out":{"hex":"#000000ff","u16":[82,82,82,65535]}}, +{"factor":1,"in":"#202020ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#202020ff","op":"lighter","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":50,"in":"#202020ff","op":"darker","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":99,"in":"#202020ff","op":"lighter","out":{"hex":"#202020ff","u16":[8142,8142,8142,65535]}}, +{"factor":99,"in":"#202020ff","op":"darker","out":{"hex":"#202020ff","u16":[8306,8306,8306,65535]}}, +{"factor":100,"in":"#202020ff","op":"lighter","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":100,"in":"#202020ff","op":"darker","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":101,"in":"#202020ff","op":"lighter","out":{"hex":"#202020ff","u16":[8306,8306,8306,65535]}}, +{"factor":101,"in":"#202020ff","op":"darker","out":{"hex":"#202020ff","u16":[8142,8142,8142,65535]}}, +{"factor":102,"in":"#202020ff","op":"lighter","out":{"hex":"#212121ff","u16":[8388,8388,8388,65535]}}, +{"factor":102,"in":"#202020ff","op":"darker","out":{"hex":"#1f1f1fff","u16":[8062,8062,8062,65535]}}, +{"factor":110,"in":"#202020ff","op":"lighter","out":{"hex":"#232323ff","u16":[9046,9046,9046,65535]}}, +{"factor":110,"in":"#202020ff","op":"darker","out":{"hex":"#1d1d1dff","u16":[7476,7476,7476,65535]}}, +{"factor":120,"in":"#202020ff","op":"lighter","out":{"hex":"#262626ff","u16":[9868,9868,9868,65535]}}, +{"factor":120,"in":"#202020ff","op":"darker","out":{"hex":"#1b1b1bff","u16":[6853,6853,6853,65535]}}, +{"factor":150,"in":"#202020ff","op":"lighter","out":{"hex":"#303030ff","u16":[12336,12336,12336,65535]}}, +{"factor":150,"in":"#202020ff","op":"darker","out":{"hex":"#151515ff","u16":[5482,5482,5482,65535]}}, +{"factor":200,"in":"#202020ff","op":"lighter","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":200,"in":"#202020ff","op":"darker","out":{"hex":"#101010ff","u16":[4112,4112,4112,65535]}}, +{"factor":300,"in":"#202020ff","op":"lighter","out":{"hex":"#606060ff","u16":[24672,24672,24672,65535]}}, +{"factor":300,"in":"#202020ff","op":"darker","out":{"hex":"#0b0b0bff","u16":[2741,2741,2741,65535]}}, +{"factor":1000,"in":"#202020ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#202020ff","op":"darker","out":{"hex":"#030303ff","u16":[822,822,822,65535]}}, +{"factor":-10,"in":"#404040ff","op":"lighter","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":-10,"in":"#404040ff","op":"darker","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":0,"in":"#404040ff","op":"lighter","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":0,"in":"#404040ff","op":"darker","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":1,"in":"#404040ff","op":"lighter","out":{"hex":"#010101ff","u16":[164,164,164,65535]}}, +{"factor":1,"in":"#404040ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#404040ff","op":"lighter","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":50,"in":"#404040ff","op":"darker","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":99,"in":"#404040ff","op":"lighter","out":{"hex":"#3f3f3fff","u16":[16285,16285,16285,65535]}}, +{"factor":99,"in":"#404040ff","op":"darker","out":{"hex":"#414141ff","u16":[16612,16612,16612,65535]}}, +{"factor":100,"in":"#404040ff","op":"lighter","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":100,"in":"#404040ff","op":"darker","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":101,"in":"#404040ff","op":"lighter","out":{"hex":"#414141ff","u16":[16612,16612,16612,65535]}}, +{"factor":101,"in":"#404040ff","op":"darker","out":{"hex":"#3f3f3fff","u16":[16285,16285,16285,65535]}}, +{"factor":102,"in":"#404040ff","op":"lighter","out":{"hex":"#414141ff","u16":[16776,16776,16776,65535]}}, +{"factor":102,"in":"#404040ff","op":"darker","out":{"hex":"#3f3f3fff","u16":[16125,16125,16125,65535]}}, +{"factor":110,"in":"#404040ff","op":"lighter","out":{"hex":"#464646ff","u16":[18092,18092,18092,65535]}}, +{"factor":110,"in":"#404040ff","op":"darker","out":{"hex":"#3a3a3aff","u16":[14952,14952,14952,65535]}}, +{"factor":120,"in":"#404040ff","op":"lighter","out":{"hex":"#4d4d4dff","u16":[19737,19737,19737,65535]}}, +{"factor":120,"in":"#404040ff","op":"darker","out":{"hex":"#353535ff","u16":[13706,13706,13706,65535]}}, +{"factor":150,"in":"#404040ff","op":"lighter","out":{"hex":"#606060ff","u16":[24672,24672,24672,65535]}}, +{"factor":150,"in":"#404040ff","op":"darker","out":{"hex":"#2b2b2bff","u16":[10965,10965,10965,65535]}}, +{"factor":200,"in":"#404040ff","op":"lighter","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":200,"in":"#404040ff","op":"darker","out":{"hex":"#202020ff","u16":[8224,8224,8224,65535]}}, +{"factor":300,"in":"#404040ff","op":"lighter","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"factor":300,"in":"#404040ff","op":"darker","out":{"hex":"#151515ff","u16":[5482,5482,5482,65535]}}, +{"factor":1000,"in":"#404040ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#404040ff","op":"darker","out":{"hex":"#060606ff","u16":[1644,1644,1644,65535]}}, +{"factor":-10,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"factor":-10,"in":"#7f7f7fff","op":"darker","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"factor":0,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"factor":0,"in":"#7f7f7fff","op":"darker","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"factor":1,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#010101ff","u16":[326,326,326,65535]}}, +{"factor":1,"in":"#7f7f7fff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#3f3f3fff","u16":[16319,16319,16319,65535]}}, +{"factor":50,"in":"#7f7f7fff","op":"darker","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"factor":99,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#7e7e7eff","u16":[32315,32315,32315,65535]}}, +{"factor":99,"in":"#7f7f7fff","op":"darker","out":{"hex":"#808080ff","u16":[32965,32965,32965,65535]}}, +{"factor":100,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"factor":100,"in":"#7f7f7fff","op":"darker","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"factor":101,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#808080ff","u16":[32965,32965,32965,65535]}}, +{"factor":101,"in":"#7f7f7fff","op":"darker","out":{"hex":"#7e7e7eff","u16":[32315,32315,32315,65535]}}, +{"factor":102,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#828282ff","u16":[33291,33291,33291,65535]}}, +{"factor":102,"in":"#7f7f7fff","op":"darker","out":{"hex":"#7d7d7dff","u16":[31999,31999,31999,65535]}}, +{"factor":110,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#8c8c8cff","u16":[35902,35902,35902,65535]}}, +{"factor":110,"in":"#7f7f7fff","op":"darker","out":{"hex":"#737373ff","u16":[29671,29671,29671,65535]}}, +{"factor":120,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#989898ff","u16":[39166,39166,39166,65535]}}, +{"factor":120,"in":"#7f7f7fff","op":"darker","out":{"hex":"#6a6a6aff","u16":[27199,27199,27199,65535]}}, +{"factor":150,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#bebebeff","u16":[48958,48958,48958,65535]}}, +{"factor":150,"in":"#7f7f7fff","op":"darker","out":{"hex":"#555555ff","u16":[21759,21759,21759,65535]}}, +{"factor":200,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"factor":200,"in":"#7f7f7fff","op":"darker","out":{"hex":"#3f3f3fff","u16":[16319,16319,16319,65535]}}, +{"factor":300,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#7f7f7fff","op":"darker","out":{"hex":"#2a2a2aff","u16":[10879,10879,10879,65535]}}, +{"factor":1000,"in":"#7f7f7fff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#7f7f7fff","op":"darker","out":{"hex":"#0d0d0dff","u16":[3263,3263,3263,65535]}}, +{"factor":-10,"in":"#808080ff","op":"lighter","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":-10,"in":"#808080ff","op":"darker","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":0,"in":"#808080ff","op":"lighter","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":0,"in":"#808080ff","op":"darker","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":1,"in":"#808080ff","op":"lighter","out":{"hex":"#010101ff","u16":[328,328,328,65535]}}, +{"factor":1,"in":"#808080ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#808080ff","op":"lighter","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":50,"in":"#808080ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#808080ff","op":"lighter","out":{"hex":"#7f7f7fff","u16":[32570,32570,32570,65535]}}, +{"factor":99,"in":"#808080ff","op":"darker","out":{"hex":"#818181ff","u16":[33224,33224,33224,65535]}}, +{"factor":100,"in":"#808080ff","op":"lighter","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":100,"in":"#808080ff","op":"darker","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":101,"in":"#808080ff","op":"lighter","out":{"hex":"#818181ff","u16":[33224,33224,33224,65535]}}, +{"factor":101,"in":"#808080ff","op":"darker","out":{"hex":"#7f7f7fff","u16":[32570,32570,32570,65535]}}, +{"factor":102,"in":"#808080ff","op":"lighter","out":{"hex":"#838383ff","u16":[33553,33553,33553,65535]}}, +{"factor":102,"in":"#808080ff","op":"darker","out":{"hex":"#7d7d7dff","u16":[32250,32250,32250,65535]}}, +{"factor":110,"in":"#808080ff","op":"lighter","out":{"hex":"#8d8d8dff","u16":[36185,36185,36185,65535]}}, +{"factor":110,"in":"#808080ff","op":"darker","out":{"hex":"#747474ff","u16":[29905,29905,29905,65535]}}, +{"factor":120,"in":"#808080ff","op":"lighter","out":{"hex":"#9a9a9aff","u16":[39475,39475,39475,65535]}}, +{"factor":120,"in":"#808080ff","op":"darker","out":{"hex":"#6b6b6bff","u16":[27413,27413,27413,65535]}}, +{"factor":150,"in":"#808080ff","op":"lighter","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"factor":150,"in":"#808080ff","op":"darker","out":{"hex":"#555555ff","u16":[21930,21930,21930,65535]}}, +{"factor":200,"in":"#808080ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#808080ff","op":"darker","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":300,"in":"#808080ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#808080ff","op":"darker","out":{"hex":"#2b2b2bff","u16":[10965,10965,10965,65535]}}, +{"factor":1000,"in":"#808080ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#808080ff","op":"darker","out":{"hex":"#0d0d0dff","u16":[3289,3289,3289,65535]}}, +{"factor":-10,"in":"#818181ff","op":"lighter","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"factor":-10,"in":"#818181ff","op":"darker","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"factor":0,"in":"#818181ff","op":"lighter","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"factor":0,"in":"#818181ff","op":"darker","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"factor":1,"in":"#818181ff","op":"lighter","out":{"hex":"#010101ff","u16":[331,331,331,65535]}}, +{"factor":1,"in":"#818181ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#818181ff","op":"lighter","out":{"hex":"#404040ff","u16":[16576,16576,16576,65535]}}, +{"factor":50,"in":"#818181ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#818181ff","op":"lighter","out":{"hex":"#808080ff","u16":[32824,32824,32824,65535]}}, +{"factor":99,"in":"#818181ff","op":"darker","out":{"hex":"#828282ff","u16":[33484,33484,33484,65535]}}, +{"factor":100,"in":"#818181ff","op":"lighter","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"factor":100,"in":"#818181ff","op":"darker","out":{"hex":"#818181ff","u16":[33153,33153,33153,65535]}}, +{"factor":101,"in":"#818181ff","op":"lighter","out":{"hex":"#828282ff","u16":[33484,33484,33484,65535]}}, +{"factor":101,"in":"#818181ff","op":"darker","out":{"hex":"#808080ff","u16":[32824,32824,32824,65535]}}, +{"factor":102,"in":"#818181ff","op":"lighter","out":{"hex":"#848484ff","u16":[33816,33816,33816,65535]}}, +{"factor":102,"in":"#818181ff","op":"darker","out":{"hex":"#7e7e7eff","u16":[32502,32502,32502,65535]}}, +{"factor":110,"in":"#818181ff","op":"lighter","out":{"hex":"#8e8e8eff","u16":[36468,36468,36468,65535]}}, +{"factor":110,"in":"#818181ff","op":"darker","out":{"hex":"#757575ff","u16":[30139,30139,30139,65535]}}, +{"factor":120,"in":"#818181ff","op":"lighter","out":{"hex":"#9b9b9bff","u16":[39783,39783,39783,65535]}}, +{"factor":120,"in":"#818181ff","op":"darker","out":{"hex":"#6b6b6bff","u16":[27627,27627,27627,65535]}}, +{"factor":150,"in":"#818181ff","op":"lighter","out":{"hex":"#c1c1c1ff","u16":[49729,49729,49729,65535]}}, +{"factor":150,"in":"#818181ff","op":"darker","out":{"hex":"#565656ff","u16":[22102,22102,22102,65535]}}, +{"factor":200,"in":"#818181ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#818181ff","op":"darker","out":{"hex":"#404040ff","u16":[16576,16576,16576,65535]}}, +{"factor":300,"in":"#818181ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#818181ff","op":"darker","out":{"hex":"#2b2b2bff","u16":[11051,11051,11051,65535]}}, +{"factor":1000,"in":"#818181ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#818181ff","op":"darker","out":{"hex":"#0d0d0dff","u16":[3315,3315,3315,65535]}}, +{"factor":-10,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"factor":-10,"in":"#bfbfbfff","op":"darker","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"factor":0,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"factor":0,"in":"#bfbfbfff","op":"darker","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"factor":1,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#020202ff","u16":[490,490,490,65535]}}, +{"factor":1,"in":"#bfbfbfff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#5f5f5fff","u16":[24543,24543,24543,65535]}}, +{"factor":50,"in":"#bfbfbfff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#bdbdbdff","u16":[48600,48600,48600,65535]}}, +{"factor":99,"in":"#bfbfbfff","op":"darker","out":{"hex":"#c1c1c1ff","u16":[49577,49577,49577,65535]}}, +{"factor":100,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"factor":100,"in":"#bfbfbfff","op":"darker","out":{"hex":"#bfbfbfff","u16":[49087,49087,49087,65535]}}, +{"factor":101,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#c1c1c1ff","u16":[49577,49577,49577,65535]}}, +{"factor":101,"in":"#bfbfbfff","op":"darker","out":{"hex":"#bdbdbdff","u16":[48600,48600,48600,65535]}}, +{"factor":102,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#c3c3c3ff","u16":[50068,50068,50068,65535]}}, +{"factor":102,"in":"#bfbfbfff","op":"darker","out":{"hex":"#bbbbbbff","u16":[48124,48124,48124,65535]}}, +{"factor":110,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#d2d2d2ff","u16":[53995,53995,53995,65535]}}, +{"factor":110,"in":"#bfbfbfff","op":"darker","out":{"hex":"#aeaeaeff","u16":[44624,44624,44624,65535]}}, +{"factor":120,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#e5e5e5ff","u16":[58904,58904,58904,65535]}}, +{"factor":120,"in":"#bfbfbfff","op":"darker","out":{"hex":"#9f9f9fff","u16":[40905,40905,40905,65535]}}, +{"factor":150,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#bfbfbfff","op":"darker","out":{"hex":"#7f7f7fff","u16":[32724,32724,32724,65535]}}, +{"factor":200,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#bfbfbfff","op":"darker","out":{"hex":"#5f5f5fff","u16":[24543,24543,24543,65535]}}, +{"factor":300,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#bfbfbfff","op":"darker","out":{"hex":"#404040ff","u16":[16362,16362,16362,65535]}}, +{"factor":1000,"in":"#bfbfbfff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#bfbfbfff","op":"darker","out":{"hex":"#131313ff","u16":[4908,4908,4908,65535]}}, +{"factor":-10,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"factor":-10,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"factor":0,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"factor":0,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"factor":1,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#020202ff","u16":[493,493,493,65535]}}, +{"factor":1,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#606060ff","u16":[24672,24672,24672,65535]}}, +{"factor":50,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#bebebeff","u16":[48855,48855,48855,65535]}}, +{"factor":99,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#c2c2c2ff","u16":[49837,49837,49837,65535]}}, +{"factor":100,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"factor":100,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"factor":101,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#c2c2c2ff","u16":[49837,49837,49837,65535]}}, +{"factor":101,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#bebebeff","u16":[48855,48855,48855,65535]}}, +{"factor":102,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#c4c4c4ff","u16":[50330,50330,50330,65535]}}, +{"factor":102,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#bcbcbcff","u16":[48376,48376,48376,65535]}}, +{"factor":110,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#d3d3d3ff","u16":[54278,54278,54278,65535]}}, +{"factor":110,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#afafafff","u16":[44858,44858,44858,65535]}}, +{"factor":120,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#e6e6e6ff","u16":[59212,59212,59212,65535]}}, +{"factor":120,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#a0a0a0ff","u16":[41120,41120,41120,65535]}}, +{"factor":150,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"factor":200,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#606060ff","u16":[24672,24672,24672,65535]}}, +{"factor":300,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#404040ff","u16":[16448,16448,16448,65535]}}, +{"factor":1000,"in":"#c0c0c0ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#c0c0c0ff","op":"darker","out":{"hex":"#131313ff","u16":[4934,4934,4934,65535]}}, +{"factor":-10,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"factor":-10,"in":"#fdfdfdff","op":"darker","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"factor":0,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"factor":0,"in":"#fdfdfdff","op":"darker","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"factor":1,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#030303ff","u16":[650,650,650,65535]}}, +{"factor":1,"in":"#fdfdfdff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#7e7e7eff","u16":[32510,32510,32510,65535]}}, +{"factor":50,"in":"#fdfdfdff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#fafafaff","u16":[64377,64377,64377,65535]}}, +{"factor":99,"in":"#fdfdfdff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":100,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"factor":100,"in":"#fdfdfdff","op":"darker","out":{"hex":"#fdfdfdff","u16":[65021,65021,65021,65535]}}, +{"factor":101,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":101,"in":"#fdfdfdff","op":"darker","out":{"hex":"#fafafaff","u16":[64377,64377,64377,65535]}}, +{"factor":102,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":102,"in":"#fdfdfdff","op":"darker","out":{"hex":"#f8f8f8ff","u16":[63746,63746,63746,65535]}}, +{"factor":110,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":110,"in":"#fdfdfdff","op":"darker","out":{"hex":"#e6e6e6ff","u16":[59110,59110,59110,65535]}}, +{"factor":120,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":120,"in":"#fdfdfdff","op":"darker","out":{"hex":"#d3d3d3ff","u16":[54184,54184,54184,65535]}}, +{"factor":150,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#fdfdfdff","op":"darker","out":{"hex":"#a9a9a9ff","u16":[43347,43347,43347,65535]}}, +{"factor":200,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#fdfdfdff","op":"darker","out":{"hex":"#7e7e7eff","u16":[32510,32510,32510,65535]}}, +{"factor":300,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#fdfdfdff","op":"darker","out":{"hex":"#545454ff","u16":[21673,21673,21673,65535]}}, +{"factor":1000,"in":"#fdfdfdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#fdfdfdff","op":"darker","out":{"hex":"#191919ff","u16":[6502,6502,6502,65535]}}, +{"factor":-10,"in":"#fefefeff","op":"lighter","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"factor":-10,"in":"#fefefeff","op":"darker","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"factor":0,"in":"#fefefeff","op":"lighter","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"factor":0,"in":"#fefefeff","op":"darker","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"factor":1,"in":"#fefefeff","op":"lighter","out":{"hex":"#030303ff","u16":[652,652,652,65535]}}, +{"factor":1,"in":"#fefefeff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#fefefeff","op":"lighter","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"factor":50,"in":"#fefefeff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#fefefeff","op":"lighter","out":{"hex":"#fbfbfbff","u16":[64631,64631,64631,65535]}}, +{"factor":99,"in":"#fefefeff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":100,"in":"#fefefeff","op":"lighter","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"factor":100,"in":"#fefefeff","op":"darker","out":{"hex":"#fefefeff","u16":[65278,65278,65278,65535]}}, +{"factor":101,"in":"#fefefeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":101,"in":"#fefefeff","op":"darker","out":{"hex":"#fbfbfbff","u16":[64631,64631,64631,65535]}}, +{"factor":102,"in":"#fefefeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":102,"in":"#fefefeff","op":"darker","out":{"hex":"#f9f9f9ff","u16":[63998,63998,63998,65535]}}, +{"factor":110,"in":"#fefefeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":110,"in":"#fefefeff","op":"darker","out":{"hex":"#e7e7e7ff","u16":[59343,59343,59343,65535]}}, +{"factor":120,"in":"#fefefeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":120,"in":"#fefefeff","op":"darker","out":{"hex":"#d4d4d4ff","u16":[54398,54398,54398,65535]}}, +{"factor":150,"in":"#fefefeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#fefefeff","op":"darker","out":{"hex":"#a9a9a9ff","u16":[43518,43518,43518,65535]}}, +{"factor":200,"in":"#fefefeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#fefefeff","op":"darker","out":{"hex":"#7f7f7fff","u16":[32639,32639,32639,65535]}}, +{"factor":300,"in":"#fefefeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#fefefeff","op":"darker","out":{"hex":"#555555ff","u16":[21759,21759,21759,65535]}}, +{"factor":1000,"in":"#fefefeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#fefefeff","op":"darker","out":{"hex":"#191919ff","u16":[6527,6527,6527,65535]}}, +{"factor":-10,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"factor":-10,"in":"#ff0000ff","op":"darker","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"factor":0,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"factor":0,"in":"#ff0000ff","op":"darker","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"factor":1,"in":"#ff0000ff","op":"lighter","out":{"hex":"#030000ff","u16":[655,0,0,65535]}}, +{"factor":1,"in":"#ff0000ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#ff0000ff","op":"lighter","out":{"hex":"#7f0000ff","u16":[32767,0,0,65535]}}, +{"factor":50,"in":"#ff0000ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#ff0000ff","op":"lighter","out":{"hex":"#fc0000ff","u16":[64886,0,0,65535]}}, +{"factor":99,"in":"#ff0000ff","op":"darker","out":{"hex":"#ff0303ff","u16":[65535,655,655,65535]}}, +{"factor":100,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"factor":100,"in":"#ff0000ff","op":"darker","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"factor":101,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ff0303ff","u16":[65535,655,655,65535]}}, +{"factor":101,"in":"#ff0000ff","op":"darker","out":{"hex":"#fc0000ff","u16":[64886,0,0,65535]}}, +{"factor":102,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ff0505ff","u16":[65535,1310,1310,65535]}}, +{"factor":102,"in":"#ff0000ff","op":"darker","out":{"hex":"#fa0000ff","u16":[64250,0,0,65535]}}, +{"factor":110,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ff1919ff","u16":[65535,6553,6553,65535]}}, +{"factor":110,"in":"#ff0000ff","op":"darker","out":{"hex":"#e80000ff","u16":[59577,0,0,65535]}}, +{"factor":120,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ff3333ff","u16":[65535,13107,13107,65535]}}, +{"factor":120,"in":"#ff0000ff","op":"darker","out":{"hex":"#d40000ff","u16":[54612,0,0,65535]}}, +{"factor":150,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ff7f7fff","u16":[65535,32767,32767,65535]}}, +{"factor":150,"in":"#ff0000ff","op":"darker","out":{"hex":"#aa0000ff","u16":[43690,0,0,65535]}}, +{"factor":200,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#ff0000ff","op":"darker","out":{"hex":"#7f0000ff","u16":[32767,0,0,65535]}}, +{"factor":300,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#ff0000ff","op":"darker","out":{"hex":"#550000ff","u16":[21845,0,0,65535]}}, +{"factor":1000,"in":"#ff0000ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#ff0000ff","op":"darker","out":{"hex":"#190000ff","u16":[6553,0,0,65535]}}, +{"factor":-10,"in":"#00ff00ff","op":"lighter","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"factor":-10,"in":"#00ff00ff","op":"darker","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"factor":0,"in":"#00ff00ff","op":"lighter","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"factor":0,"in":"#00ff00ff","op":"darker","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"factor":1,"in":"#00ff00ff","op":"lighter","out":{"hex":"#000300ff","u16":[0,655,0,65535]}}, +{"factor":1,"in":"#00ff00ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#00ff00ff","op":"lighter","out":{"hex":"#007f00ff","u16":[0,32767,0,65535]}}, +{"factor":50,"in":"#00ff00ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#00ff00ff","op":"lighter","out":{"hex":"#00fc00ff","u16":[0,64886,0,65535]}}, +{"factor":99,"in":"#00ff00ff","op":"darker","out":{"hex":"#03ff03ff","u16":[655,65535,655,65535]}}, +{"factor":100,"in":"#00ff00ff","op":"lighter","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"factor":100,"in":"#00ff00ff","op":"darker","out":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"factor":101,"in":"#00ff00ff","op":"lighter","out":{"hex":"#03ff03ff","u16":[655,65535,655,65535]}}, +{"factor":101,"in":"#00ff00ff","op":"darker","out":{"hex":"#00fc00ff","u16":[0,64886,0,65535]}}, +{"factor":102,"in":"#00ff00ff","op":"lighter","out":{"hex":"#05ff05ff","u16":[1310,65535,1310,65535]}}, +{"factor":102,"in":"#00ff00ff","op":"darker","out":{"hex":"#00fa00ff","u16":[0,64250,0,65535]}}, +{"factor":110,"in":"#00ff00ff","op":"lighter","out":{"hex":"#19ff19ff","u16":[6553,65535,6553,65535]}}, +{"factor":110,"in":"#00ff00ff","op":"darker","out":{"hex":"#00e800ff","u16":[0,59577,0,65535]}}, +{"factor":120,"in":"#00ff00ff","op":"lighter","out":{"hex":"#33ff33ff","u16":[13107,65535,13107,65535]}}, +{"factor":120,"in":"#00ff00ff","op":"darker","out":{"hex":"#00d400ff","u16":[0,54612,0,65535]}}, +{"factor":150,"in":"#00ff00ff","op":"lighter","out":{"hex":"#7fff7fff","u16":[32767,65535,32767,65535]}}, +{"factor":150,"in":"#00ff00ff","op":"darker","out":{"hex":"#00aa00ff","u16":[0,43690,0,65535]}}, +{"factor":200,"in":"#00ff00ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#00ff00ff","op":"darker","out":{"hex":"#007f00ff","u16":[0,32767,0,65535]}}, +{"factor":300,"in":"#00ff00ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#00ff00ff","op":"darker","out":{"hex":"#005500ff","u16":[0,21845,0,65535]}}, +{"factor":1000,"in":"#00ff00ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#00ff00ff","op":"darker","out":{"hex":"#001900ff","u16":[0,6553,0,65535]}}, +{"factor":-10,"in":"#0000ffff","op":"lighter","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"factor":-10,"in":"#0000ffff","op":"darker","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"factor":0,"in":"#0000ffff","op":"lighter","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"factor":0,"in":"#0000ffff","op":"darker","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"factor":1,"in":"#0000ffff","op":"lighter","out":{"hex":"#000003ff","u16":[0,0,655,65535]}}, +{"factor":1,"in":"#0000ffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#0000ffff","op":"lighter","out":{"hex":"#00007fff","u16":[0,0,32767,65535]}}, +{"factor":50,"in":"#0000ffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#0000ffff","op":"lighter","out":{"hex":"#0000fcff","u16":[0,0,64886,65535]}}, +{"factor":99,"in":"#0000ffff","op":"darker","out":{"hex":"#0303ffff","u16":[655,655,65535,65535]}}, +{"factor":100,"in":"#0000ffff","op":"lighter","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"factor":100,"in":"#0000ffff","op":"darker","out":{"hex":"#0000ffff","u16":[0,0,65535,65535]}}, +{"factor":101,"in":"#0000ffff","op":"lighter","out":{"hex":"#0303ffff","u16":[655,655,65535,65535]}}, +{"factor":101,"in":"#0000ffff","op":"darker","out":{"hex":"#0000fcff","u16":[0,0,64886,65535]}}, +{"factor":102,"in":"#0000ffff","op":"lighter","out":{"hex":"#0505ffff","u16":[1310,1310,65535,65535]}}, +{"factor":102,"in":"#0000ffff","op":"darker","out":{"hex":"#0000faff","u16":[0,0,64250,65535]}}, +{"factor":110,"in":"#0000ffff","op":"lighter","out":{"hex":"#1919ffff","u16":[6553,6553,65535,65535]}}, +{"factor":110,"in":"#0000ffff","op":"darker","out":{"hex":"#0000e8ff","u16":[0,0,59577,65535]}}, +{"factor":120,"in":"#0000ffff","op":"lighter","out":{"hex":"#3333ffff","u16":[13107,13107,65535,65535]}}, +{"factor":120,"in":"#0000ffff","op":"darker","out":{"hex":"#0000d4ff","u16":[0,0,54612,65535]}}, +{"factor":150,"in":"#0000ffff","op":"lighter","out":{"hex":"#7f7fffff","u16":[32767,32767,65535,65535]}}, +{"factor":150,"in":"#0000ffff","op":"darker","out":{"hex":"#0000aaff","u16":[0,0,43690,65535]}}, +{"factor":200,"in":"#0000ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#0000ffff","op":"darker","out":{"hex":"#00007fff","u16":[0,0,32767,65535]}}, +{"factor":300,"in":"#0000ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#0000ffff","op":"darker","out":{"hex":"#000055ff","u16":[0,0,21845,65535]}}, +{"factor":1000,"in":"#0000ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#0000ffff","op":"darker","out":{"hex":"#000019ff","u16":[0,0,6553,65535]}}, +{"factor":-10,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"factor":-10,"in":"#ffff00ff","op":"darker","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"factor":0,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"factor":0,"in":"#ffff00ff","op":"darker","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"factor":1,"in":"#ffff00ff","op":"lighter","out":{"hex":"#030300ff","u16":[655,655,0,65535]}}, +{"factor":1,"in":"#ffff00ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#ffff00ff","op":"lighter","out":{"hex":"#7f7f00ff","u16":[32767,32767,0,65535]}}, +{"factor":50,"in":"#ffff00ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#ffff00ff","op":"lighter","out":{"hex":"#fcfc00ff","u16":[64886,64886,0,65535]}}, +{"factor":99,"in":"#ffff00ff","op":"darker","out":{"hex":"#ffff03ff","u16":[65535,65535,655,65535]}}, +{"factor":100,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"factor":100,"in":"#ffff00ff","op":"darker","out":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"factor":101,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffff03ff","u16":[65535,65535,655,65535]}}, +{"factor":101,"in":"#ffff00ff","op":"darker","out":{"hex":"#fcfc00ff","u16":[64886,64886,0,65535]}}, +{"factor":102,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffff05ff","u16":[65535,65535,1310,65535]}}, +{"factor":102,"in":"#ffff00ff","op":"darker","out":{"hex":"#fafa00ff","u16":[64250,64250,0,65535]}}, +{"factor":110,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffff19ff","u16":[65535,65535,6553,65535]}}, +{"factor":110,"in":"#ffff00ff","op":"darker","out":{"hex":"#e8e800ff","u16":[59577,59577,0,65535]}}, +{"factor":120,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffff33ff","u16":[65535,65535,13107,65535]}}, +{"factor":120,"in":"#ffff00ff","op":"darker","out":{"hex":"#d4d400ff","u16":[54612,54612,0,65535]}}, +{"factor":150,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffff7fff","u16":[65535,65535,32767,65535]}}, +{"factor":150,"in":"#ffff00ff","op":"darker","out":{"hex":"#aaaa00ff","u16":[43690,43690,0,65535]}}, +{"factor":200,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#ffff00ff","op":"darker","out":{"hex":"#7f7f00ff","u16":[32767,32767,0,65535]}}, +{"factor":300,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#ffff00ff","op":"darker","out":{"hex":"#555500ff","u16":[21845,21845,0,65535]}}, +{"factor":1000,"in":"#ffff00ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#ffff00ff","op":"darker","out":{"hex":"#191900ff","u16":[6553,6553,0,65535]}}, +{"factor":-10,"in":"#00ffffff","op":"lighter","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"factor":-10,"in":"#00ffffff","op":"darker","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"factor":0,"in":"#00ffffff","op":"lighter","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"factor":0,"in":"#00ffffff","op":"darker","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"factor":1,"in":"#00ffffff","op":"lighter","out":{"hex":"#000303ff","u16":[0,655,655,65535]}}, +{"factor":1,"in":"#00ffffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#00ffffff","op":"lighter","out":{"hex":"#007f7fff","u16":[0,32767,32767,65535]}}, +{"factor":50,"in":"#00ffffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#00ffffff","op":"lighter","out":{"hex":"#00fcfcff","u16":[0,64886,64886,65535]}}, +{"factor":99,"in":"#00ffffff","op":"darker","out":{"hex":"#03ffffff","u16":[655,65535,65535,65535]}}, +{"factor":100,"in":"#00ffffff","op":"lighter","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"factor":100,"in":"#00ffffff","op":"darker","out":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"factor":101,"in":"#00ffffff","op":"lighter","out":{"hex":"#03ffffff","u16":[655,65535,65535,65535]}}, +{"factor":101,"in":"#00ffffff","op":"darker","out":{"hex":"#00fcfcff","u16":[0,64886,64886,65535]}}, +{"factor":102,"in":"#00ffffff","op":"lighter","out":{"hex":"#05ffffff","u16":[1310,65535,65535,65535]}}, +{"factor":102,"in":"#00ffffff","op":"darker","out":{"hex":"#00fafaff","u16":[0,64250,64250,65535]}}, +{"factor":110,"in":"#00ffffff","op":"lighter","out":{"hex":"#19ffffff","u16":[6553,65535,65535,65535]}}, +{"factor":110,"in":"#00ffffff","op":"darker","out":{"hex":"#00e8e8ff","u16":[0,59577,59577,65535]}}, +{"factor":120,"in":"#00ffffff","op":"lighter","out":{"hex":"#33ffffff","u16":[13107,65535,65535,65535]}}, +{"factor":120,"in":"#00ffffff","op":"darker","out":{"hex":"#00d4d4ff","u16":[0,54612,54612,65535]}}, +{"factor":150,"in":"#00ffffff","op":"lighter","out":{"hex":"#7fffffff","u16":[32767,65535,65535,65535]}}, +{"factor":150,"in":"#00ffffff","op":"darker","out":{"hex":"#00aaaaff","u16":[0,43690,43690,65535]}}, +{"factor":200,"in":"#00ffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#00ffffff","op":"darker","out":{"hex":"#007f7fff","u16":[0,32767,32767,65535]}}, +{"factor":300,"in":"#00ffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#00ffffff","op":"darker","out":{"hex":"#005555ff","u16":[0,21845,21845,65535]}}, +{"factor":1000,"in":"#00ffffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#00ffffff","op":"darker","out":{"hex":"#001919ff","u16":[0,6553,6553,65535]}}, +{"factor":-10,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"factor":-10,"in":"#ff00ffff","op":"darker","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"factor":0,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"factor":0,"in":"#ff00ffff","op":"darker","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"factor":1,"in":"#ff00ffff","op":"lighter","out":{"hex":"#030003ff","u16":[655,0,655,65535]}}, +{"factor":1,"in":"#ff00ffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#ff00ffff","op":"lighter","out":{"hex":"#7f007fff","u16":[32767,0,32767,65535]}}, +{"factor":50,"in":"#ff00ffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#ff00ffff","op":"lighter","out":{"hex":"#fc00fcff","u16":[64886,0,64886,65535]}}, +{"factor":99,"in":"#ff00ffff","op":"darker","out":{"hex":"#ff03ffff","u16":[65535,655,65535,65535]}}, +{"factor":100,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"factor":100,"in":"#ff00ffff","op":"darker","out":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"factor":101,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ff03ffff","u16":[65535,655,65535,65535]}}, +{"factor":101,"in":"#ff00ffff","op":"darker","out":{"hex":"#fc00fcff","u16":[64886,0,64886,65535]}}, +{"factor":102,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ff05ffff","u16":[65535,1310,65535,65535]}}, +{"factor":102,"in":"#ff00ffff","op":"darker","out":{"hex":"#fa00faff","u16":[64250,0,64250,65535]}}, +{"factor":110,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ff19ffff","u16":[65535,6553,65535,65535]}}, +{"factor":110,"in":"#ff00ffff","op":"darker","out":{"hex":"#e800e8ff","u16":[59577,0,59577,65535]}}, +{"factor":120,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ff33ffff","u16":[65535,13107,65535,65535]}}, +{"factor":120,"in":"#ff00ffff","op":"darker","out":{"hex":"#d400d4ff","u16":[54612,0,54612,65535]}}, +{"factor":150,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ff7fffff","u16":[65535,32767,65535,65535]}}, +{"factor":150,"in":"#ff00ffff","op":"darker","out":{"hex":"#aa00aaff","u16":[43690,0,43690,65535]}}, +{"factor":200,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#ff00ffff","op":"darker","out":{"hex":"#7f007fff","u16":[32767,0,32767,65535]}}, +{"factor":300,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#ff00ffff","op":"darker","out":{"hex":"#550055ff","u16":[21845,0,21845,65535]}}, +{"factor":1000,"in":"#ff00ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#ff00ffff","op":"darker","out":{"hex":"#190019ff","u16":[6553,0,6553,65535]}}, +{"factor":-10,"in":"#800000ff","op":"lighter","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"factor":-10,"in":"#800000ff","op":"darker","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"factor":0,"in":"#800000ff","op":"lighter","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"factor":0,"in":"#800000ff","op":"darker","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"factor":1,"in":"#800000ff","op":"lighter","out":{"hex":"#010000ff","u16":[328,0,0,65535]}}, +{"factor":1,"in":"#800000ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#800000ff","op":"lighter","out":{"hex":"#400000ff","u16":[16448,0,0,65535]}}, +{"factor":50,"in":"#800000ff","op":"darker","out":{"hex":"#ff0101ff","u16":[65535,257,257,65535]}}, +{"factor":99,"in":"#800000ff","op":"lighter","out":{"hex":"#7f0000ff","u16":[32570,0,0,65535]}}, +{"factor":99,"in":"#800000ff","op":"darker","out":{"hex":"#810000ff","u16":[33224,0,0,65535]}}, +{"factor":100,"in":"#800000ff","op":"lighter","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"factor":100,"in":"#800000ff","op":"darker","out":{"hex":"#800000ff","u16":[32896,0,0,65535]}}, +{"factor":101,"in":"#800000ff","op":"lighter","out":{"hex":"#810000ff","u16":[33224,0,0,65535]}}, +{"factor":101,"in":"#800000ff","op":"darker","out":{"hex":"#7f0000ff","u16":[32570,0,0,65535]}}, +{"factor":102,"in":"#800000ff","op":"lighter","out":{"hex":"#830000ff","u16":[33553,0,0,65535]}}, +{"factor":102,"in":"#800000ff","op":"darker","out":{"hex":"#7d0000ff","u16":[32250,0,0,65535]}}, +{"factor":110,"in":"#800000ff","op":"lighter","out":{"hex":"#8d0000ff","u16":[36185,0,0,65535]}}, +{"factor":110,"in":"#800000ff","op":"darker","out":{"hex":"#740000ff","u16":[29905,0,0,65535]}}, +{"factor":120,"in":"#800000ff","op":"lighter","out":{"hex":"#9a0000ff","u16":[39475,0,0,65535]}}, +{"factor":120,"in":"#800000ff","op":"darker","out":{"hex":"#6b0000ff","u16":[27413,0,0,65535]}}, +{"factor":150,"in":"#800000ff","op":"lighter","out":{"hex":"#c00000ff","u16":[49344,0,0,65535]}}, +{"factor":150,"in":"#800000ff","op":"darker","out":{"hex":"#550000ff","u16":[21930,0,0,65535]}}, +{"factor":200,"in":"#800000ff","op":"lighter","out":{"hex":"#ff0101ff","u16":[65535,257,257,65535]}}, +{"factor":200,"in":"#800000ff","op":"darker","out":{"hex":"#400000ff","u16":[16448,0,0,65535]}}, +{"factor":300,"in":"#800000ff","op":"lighter","out":{"hex":"#ff8181ff","u16":[65535,33153,33153,65535]}}, +{"factor":300,"in":"#800000ff","op":"darker","out":{"hex":"#2b0000ff","u16":[10965,0,0,65535]}}, +{"factor":1000,"in":"#800000ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#800000ff","op":"darker","out":{"hex":"#0d0000ff","u16":[3289,0,0,65535]}}, +{"factor":-10,"in":"#008000ff","op":"lighter","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"factor":-10,"in":"#008000ff","op":"darker","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"factor":0,"in":"#008000ff","op":"lighter","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"factor":0,"in":"#008000ff","op":"darker","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"factor":1,"in":"#008000ff","op":"lighter","out":{"hex":"#000100ff","u16":[0,328,0,65535]}}, +{"factor":1,"in":"#008000ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#008000ff","op":"lighter","out":{"hex":"#004000ff","u16":[0,16448,0,65535]}}, +{"factor":50,"in":"#008000ff","op":"darker","out":{"hex":"#01ff01ff","u16":[257,65535,257,65535]}}, +{"factor":99,"in":"#008000ff","op":"lighter","out":{"hex":"#007f00ff","u16":[0,32570,0,65535]}}, +{"factor":99,"in":"#008000ff","op":"darker","out":{"hex":"#008100ff","u16":[0,33224,0,65535]}}, +{"factor":100,"in":"#008000ff","op":"lighter","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"factor":100,"in":"#008000ff","op":"darker","out":{"hex":"#008000ff","u16":[0,32896,0,65535]}}, +{"factor":101,"in":"#008000ff","op":"lighter","out":{"hex":"#008100ff","u16":[0,33224,0,65535]}}, +{"factor":101,"in":"#008000ff","op":"darker","out":{"hex":"#007f00ff","u16":[0,32570,0,65535]}}, +{"factor":102,"in":"#008000ff","op":"lighter","out":{"hex":"#008300ff","u16":[0,33553,0,65535]}}, +{"factor":102,"in":"#008000ff","op":"darker","out":{"hex":"#007d00ff","u16":[0,32250,0,65535]}}, +{"factor":110,"in":"#008000ff","op":"lighter","out":{"hex":"#008d00ff","u16":[0,36185,0,65535]}}, +{"factor":110,"in":"#008000ff","op":"darker","out":{"hex":"#007400ff","u16":[0,29905,0,65535]}}, +{"factor":120,"in":"#008000ff","op":"lighter","out":{"hex":"#009a00ff","u16":[0,39475,0,65535]}}, +{"factor":120,"in":"#008000ff","op":"darker","out":{"hex":"#006b00ff","u16":[0,27413,0,65535]}}, +{"factor":150,"in":"#008000ff","op":"lighter","out":{"hex":"#00c000ff","u16":[0,49344,0,65535]}}, +{"factor":150,"in":"#008000ff","op":"darker","out":{"hex":"#005500ff","u16":[0,21930,0,65535]}}, +{"factor":200,"in":"#008000ff","op":"lighter","out":{"hex":"#01ff01ff","u16":[257,65535,257,65535]}}, +{"factor":200,"in":"#008000ff","op":"darker","out":{"hex":"#004000ff","u16":[0,16448,0,65535]}}, +{"factor":300,"in":"#008000ff","op":"lighter","out":{"hex":"#81ff81ff","u16":[33153,65535,33153,65535]}}, +{"factor":300,"in":"#008000ff","op":"darker","out":{"hex":"#002b00ff","u16":[0,10965,0,65535]}}, +{"factor":1000,"in":"#008000ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#008000ff","op":"darker","out":{"hex":"#000d00ff","u16":[0,3289,0,65535]}}, +{"factor":-10,"in":"#000080ff","op":"lighter","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"factor":-10,"in":"#000080ff","op":"darker","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"factor":0,"in":"#000080ff","op":"lighter","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"factor":0,"in":"#000080ff","op":"darker","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"factor":1,"in":"#000080ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,328,65535]}}, +{"factor":1,"in":"#000080ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#000080ff","op":"lighter","out":{"hex":"#000040ff","u16":[0,0,16448,65535]}}, +{"factor":50,"in":"#000080ff","op":"darker","out":{"hex":"#0101ffff","u16":[257,257,65535,65535]}}, +{"factor":99,"in":"#000080ff","op":"lighter","out":{"hex":"#00007fff","u16":[0,0,32570,65535]}}, +{"factor":99,"in":"#000080ff","op":"darker","out":{"hex":"#000081ff","u16":[0,0,33224,65535]}}, +{"factor":100,"in":"#000080ff","op":"lighter","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"factor":100,"in":"#000080ff","op":"darker","out":{"hex":"#000080ff","u16":[0,0,32896,65535]}}, +{"factor":101,"in":"#000080ff","op":"lighter","out":{"hex":"#000081ff","u16":[0,0,33224,65535]}}, +{"factor":101,"in":"#000080ff","op":"darker","out":{"hex":"#00007fff","u16":[0,0,32570,65535]}}, +{"factor":102,"in":"#000080ff","op":"lighter","out":{"hex":"#000083ff","u16":[0,0,33553,65535]}}, +{"factor":102,"in":"#000080ff","op":"darker","out":{"hex":"#00007dff","u16":[0,0,32250,65535]}}, +{"factor":110,"in":"#000080ff","op":"lighter","out":{"hex":"#00008dff","u16":[0,0,36185,65535]}}, +{"factor":110,"in":"#000080ff","op":"darker","out":{"hex":"#000074ff","u16":[0,0,29905,65535]}}, +{"factor":120,"in":"#000080ff","op":"lighter","out":{"hex":"#00009aff","u16":[0,0,39475,65535]}}, +{"factor":120,"in":"#000080ff","op":"darker","out":{"hex":"#00006bff","u16":[0,0,27413,65535]}}, +{"factor":150,"in":"#000080ff","op":"lighter","out":{"hex":"#0000c0ff","u16":[0,0,49344,65535]}}, +{"factor":150,"in":"#000080ff","op":"darker","out":{"hex":"#000055ff","u16":[0,0,21930,65535]}}, +{"factor":200,"in":"#000080ff","op":"lighter","out":{"hex":"#0101ffff","u16":[257,257,65535,65535]}}, +{"factor":200,"in":"#000080ff","op":"darker","out":{"hex":"#000040ff","u16":[0,0,16448,65535]}}, +{"factor":300,"in":"#000080ff","op":"lighter","out":{"hex":"#8181ffff","u16":[33153,33153,65535,65535]}}, +{"factor":300,"in":"#000080ff","op":"darker","out":{"hex":"#00002bff","u16":[0,0,10965,65535]}}, +{"factor":1000,"in":"#000080ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#000080ff","op":"darker","out":{"hex":"#00000dff","u16":[0,0,3289,65535]}}, +{"factor":-10,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"factor":-10,"in":"#ff8080ff","op":"darker","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"factor":0,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"factor":0,"in":"#ff8080ff","op":"darker","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"factor":1,"in":"#ff8080ff","op":"lighter","out":{"hex":"#030101ff","u16":[655,329,329,65535]}}, +{"factor":1,"in":"#ff8080ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#ff8080ff","op":"lighter","out":{"hex":"#7f4040ff","u16":[32767,16448,16448,65535]}}, +{"factor":50,"in":"#ff8080ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#ff8080ff","op":"lighter","out":{"hex":"#fc7f7fff","u16":[64886,32570,32570,65535]}}, +{"factor":99,"in":"#ff8080ff","op":"darker","out":{"hex":"#ff8383ff","u16":[65535,33551,33551,65535]}}, +{"factor":100,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"factor":100,"in":"#ff8080ff","op":"darker","out":{"hex":"#ff8080ff","u16":[65535,32896,32896,65535]}}, +{"factor":101,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ff8383ff","u16":[65535,33551,33551,65535]}}, +{"factor":101,"in":"#ff8080ff","op":"darker","out":{"hex":"#fc7f7fff","u16":[64886,32570,32570,65535]}}, +{"factor":102,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ff8585ff","u16":[65535,34206,34206,65535]}}, +{"factor":102,"in":"#ff8080ff","op":"darker","out":{"hex":"#fa7d7dff","u16":[64250,32251,32251,65535]}}, +{"factor":110,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ff9999ff","u16":[65535,39449,39449,65535]}}, +{"factor":110,"in":"#ff8080ff","op":"darker","out":{"hex":"#e87474ff","u16":[59577,29905,29905,65535]}}, +{"factor":120,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ffb3b3ff","u16":[65535,46003,46003,65535]}}, +{"factor":120,"in":"#ff8080ff","op":"darker","out":{"hex":"#d46b6bff","u16":[54612,27413,27413,65535]}}, +{"factor":150,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#ff8080ff","op":"darker","out":{"hex":"#aa5555ff","u16":[43690,21931,21931,65535]}}, +{"factor":200,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#ff8080ff","op":"darker","out":{"hex":"#7f4040ff","u16":[32767,16448,16448,65535]}}, +{"factor":300,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#ff8080ff","op":"darker","out":{"hex":"#552b2bff","u16":[21845,10965,10965,65535]}}, +{"factor":1000,"in":"#ff8080ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#ff8080ff","op":"darker","out":{"hex":"#190d0dff","u16":[6553,3289,3289,65535]}}, +{"factor":-10,"in":"#80ff80ff","op":"lighter","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"factor":-10,"in":"#80ff80ff","op":"darker","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"factor":0,"in":"#80ff80ff","op":"lighter","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"factor":0,"in":"#80ff80ff","op":"darker","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"factor":1,"in":"#80ff80ff","op":"lighter","out":{"hex":"#010301ff","u16":[329,655,329,65535]}}, +{"factor":1,"in":"#80ff80ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#80ff80ff","op":"lighter","out":{"hex":"#407f40ff","u16":[16448,32767,16448,65535]}}, +{"factor":50,"in":"#80ff80ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#80ff80ff","op":"lighter","out":{"hex":"#7ffc7fff","u16":[32570,64886,32570,65535]}}, +{"factor":99,"in":"#80ff80ff","op":"darker","out":{"hex":"#83ff83ff","u16":[33551,65535,33551,65535]}}, +{"factor":100,"in":"#80ff80ff","op":"lighter","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"factor":100,"in":"#80ff80ff","op":"darker","out":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]}}, +{"factor":101,"in":"#80ff80ff","op":"lighter","out":{"hex":"#83ff83ff","u16":[33551,65535,33551,65535]}}, +{"factor":101,"in":"#80ff80ff","op":"darker","out":{"hex":"#7ffc7fff","u16":[32570,64886,32570,65535]}}, +{"factor":102,"in":"#80ff80ff","op":"lighter","out":{"hex":"#85ff85ff","u16":[34206,65535,34206,65535]}}, +{"factor":102,"in":"#80ff80ff","op":"darker","out":{"hex":"#7dfa7dff","u16":[32251,64250,32251,65535]}}, +{"factor":110,"in":"#80ff80ff","op":"lighter","out":{"hex":"#99ff99ff","u16":[39449,65535,39449,65535]}}, +{"factor":110,"in":"#80ff80ff","op":"darker","out":{"hex":"#74e874ff","u16":[29905,59577,29905,65535]}}, +{"factor":120,"in":"#80ff80ff","op":"lighter","out":{"hex":"#b3ffb3ff","u16":[46003,65535,46003,65535]}}, +{"factor":120,"in":"#80ff80ff","op":"darker","out":{"hex":"#6bd46bff","u16":[27413,54612,27413,65535]}}, +{"factor":150,"in":"#80ff80ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#80ff80ff","op":"darker","out":{"hex":"#55aa55ff","u16":[21931,43690,21931,65535]}}, +{"factor":200,"in":"#80ff80ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#80ff80ff","op":"darker","out":{"hex":"#407f40ff","u16":[16448,32767,16448,65535]}}, +{"factor":300,"in":"#80ff80ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#80ff80ff","op":"darker","out":{"hex":"#2b552bff","u16":[10965,21845,10965,65535]}}, +{"factor":1000,"in":"#80ff80ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#80ff80ff","op":"darker","out":{"hex":"#0d190dff","u16":[3289,6553,3289,65535]}}, +{"factor":-10,"in":"#8080ffff","op":"lighter","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"factor":-10,"in":"#8080ffff","op":"darker","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"factor":0,"in":"#8080ffff","op":"lighter","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"factor":0,"in":"#8080ffff","op":"darker","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"factor":1,"in":"#8080ffff","op":"lighter","out":{"hex":"#010103ff","u16":[329,329,655,65535]}}, +{"factor":1,"in":"#8080ffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#8080ffff","op":"lighter","out":{"hex":"#40407fff","u16":[16448,16448,32767,65535]}}, +{"factor":50,"in":"#8080ffff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#8080ffff","op":"lighter","out":{"hex":"#7f7ffcff","u16":[32570,32570,64886,65535]}}, +{"factor":99,"in":"#8080ffff","op":"darker","out":{"hex":"#8383ffff","u16":[33551,33551,65535,65535]}}, +{"factor":100,"in":"#8080ffff","op":"lighter","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"factor":100,"in":"#8080ffff","op":"darker","out":{"hex":"#8080ffff","u16":[32896,32896,65535,65535]}}, +{"factor":101,"in":"#8080ffff","op":"lighter","out":{"hex":"#8383ffff","u16":[33551,33551,65535,65535]}}, +{"factor":101,"in":"#8080ffff","op":"darker","out":{"hex":"#7f7ffcff","u16":[32570,32570,64886,65535]}}, +{"factor":102,"in":"#8080ffff","op":"lighter","out":{"hex":"#8585ffff","u16":[34206,34206,65535,65535]}}, +{"factor":102,"in":"#8080ffff","op":"darker","out":{"hex":"#7d7dfaff","u16":[32251,32251,64250,65535]}}, +{"factor":110,"in":"#8080ffff","op":"lighter","out":{"hex":"#9999ffff","u16":[39449,39449,65535,65535]}}, +{"factor":110,"in":"#8080ffff","op":"darker","out":{"hex":"#7474e8ff","u16":[29905,29905,59577,65535]}}, +{"factor":120,"in":"#8080ffff","op":"lighter","out":{"hex":"#b3b3ffff","u16":[46003,46003,65535,65535]}}, +{"factor":120,"in":"#8080ffff","op":"darker","out":{"hex":"#6b6bd4ff","u16":[27413,27413,54612,65535]}}, +{"factor":150,"in":"#8080ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#8080ffff","op":"darker","out":{"hex":"#5555aaff","u16":[21931,21931,43690,65535]}}, +{"factor":200,"in":"#8080ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#8080ffff","op":"darker","out":{"hex":"#40407fff","u16":[16448,16448,32767,65535]}}, +{"factor":300,"in":"#8080ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#8080ffff","op":"darker","out":{"hex":"#2b2b55ff","u16":[10965,10965,21845,65535]}}, +{"factor":1000,"in":"#8080ffff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#8080ffff","op":"darker","out":{"hex":"#0d0d19ff","u16":[3289,3289,6553,65535]}}, +{"factor":-10,"in":"#030201ff","op":"lighter","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"factor":-10,"in":"#030201ff","op":"darker","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"factor":0,"in":"#030201ff","op":"lighter","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"factor":0,"in":"#030201ff","op":"darker","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"factor":1,"in":"#030201ff","op":"lighter","out":{"hex":"#000000ff","u16":[7,5,2,65535]}}, +{"factor":1,"in":"#030201ff","op":"darker","out":{"hex":"#ffc182ff","u16":[65535,49473,33410,65535]}}, +{"factor":50,"in":"#030201ff","op":"lighter","out":{"hex":"#010100ff","u16":[385,257,128,65535]}}, +{"factor":50,"in":"#030201ff","op":"darker","out":{"hex":"#060402ff","u16":[1542,1028,514,65535]}}, +{"factor":99,"in":"#030201ff","op":"lighter","out":{"hex":"#030201ff","u16":[763,509,254,65535]}}, +{"factor":99,"in":"#030201ff","op":"darker","out":{"hex":"#030201ff","u16":[778,519,259,65535]}}, +{"factor":100,"in":"#030201ff","op":"lighter","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"factor":100,"in":"#030201ff","op":"darker","out":{"hex":"#030201ff","u16":[771,514,257,65535]}}, +{"factor":101,"in":"#030201ff","op":"lighter","out":{"hex":"#030201ff","u16":[778,519,259,65535]}}, +{"factor":101,"in":"#030201ff","op":"darker","out":{"hex":"#030201ff","u16":[763,509,254,65535]}}, +{"factor":102,"in":"#030201ff","op":"lighter","out":{"hex":"#030201ff","u16":[786,524,262,65535]}}, +{"factor":102,"in":"#030201ff","op":"darker","out":{"hex":"#030201ff","u16":[755,503,252,65535]}}, +{"factor":110,"in":"#030201ff","op":"lighter","out":{"hex":"#030201ff","u16":[848,565,283,65535]}}, +{"factor":110,"in":"#030201ff","op":"darker","out":{"hex":"#030201ff","u16":[700,467,233,65535]}}, +{"factor":120,"in":"#030201ff","op":"lighter","out":{"hex":"#040201ff","u16":[925,617,308,65535]}}, +{"factor":120,"in":"#030201ff","op":"darker","out":{"hex":"#020201ff","u16":[642,428,214,65535]}}, +{"factor":150,"in":"#030201ff","op":"lighter","out":{"hex":"#040301ff","u16":[1156,771,385,65535]}}, +{"factor":150,"in":"#030201ff","op":"darker","out":{"hex":"#020101ff","u16":[514,343,171,65535]}}, +{"factor":200,"in":"#030201ff","op":"lighter","out":{"hex":"#060402ff","u16":[1542,1028,514,65535]}}, +{"factor":200,"in":"#030201ff","op":"darker","out":{"hex":"#010100ff","u16":[385,257,128,65535]}}, +{"factor":300,"in":"#030201ff","op":"lighter","out":{"hex":"#090603ff","u16":[2313,1542,771,65535]}}, +{"factor":300,"in":"#030201ff","op":"darker","out":{"hex":"#010100ff","u16":[257,171,86,65535]}}, +{"factor":1000,"in":"#030201ff","op":"lighter","out":{"hex":"#1e140aff","u16":[7710,5140,2570,65535]}}, +{"factor":1000,"in":"#030201ff","op":"darker","out":{"hex":"#000000ff","u16":[77,51,26,65535]}}, +{"factor":-10,"in":"#fffefdff","op":"lighter","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"factor":-10,"in":"#fffefdff","op":"darker","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"factor":0,"in":"#fffefdff","op":"lighter","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"factor":0,"in":"#fffefdff","op":"darker","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"factor":1,"in":"#fffefdff","op":"lighter","out":{"hex":"#030303ff","u16":[655,652,650,65535]}}, +{"factor":1,"in":"#fffefdff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#fffefdff","op":"lighter","out":{"hex":"#7f7f7eff","u16":[32767,32639,32510,65535]}}, +{"factor":50,"in":"#fffefdff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#fffefdff","op":"lighter","out":{"hex":"#fcfbfaff","u16":[64886,64632,64377,65535]}}, +{"factor":99,"in":"#fffefdff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":100,"in":"#fffefdff","op":"lighter","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"factor":100,"in":"#fffefdff","op":"darker","out":{"hex":"#fffefdff","u16":[65535,65278,65021,65535]}}, +{"factor":101,"in":"#fffefdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":101,"in":"#fffefdff","op":"darker","out":{"hex":"#fcfbfaff","u16":[64886,64632,64377,65535]}}, +{"factor":102,"in":"#fffefdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":102,"in":"#fffefdff","op":"darker","out":{"hex":"#faf9f8ff","u16":[64250,63998,63746,65535]}}, +{"factor":110,"in":"#fffefdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":110,"in":"#fffefdff","op":"darker","out":{"hex":"#e8e7e6ff","u16":[59577,59343,59110,65535]}}, +{"factor":120,"in":"#fffefdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":120,"in":"#fffefdff","op":"darker","out":{"hex":"#d4d4d3ff","u16":[54612,54398,54184,65535]}}, +{"factor":150,"in":"#fffefdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#fffefdff","op":"darker","out":{"hex":"#aaa9a9ff","u16":[43690,43519,43347,65535]}}, +{"factor":200,"in":"#fffefdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#fffefdff","op":"darker","out":{"hex":"#7f7f7eff","u16":[32767,32639,32510,65535]}}, +{"factor":300,"in":"#fffefdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#fffefdff","op":"darker","out":{"hex":"#555554ff","u16":[21845,21759,21674,65535]}}, +{"factor":1000,"in":"#fffefdff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#fffefdff","op":"darker","out":{"hex":"#191919ff","u16":[6553,6527,6502,65535]}}, +{"factor":-10,"in":"#7f8081ff","op":"lighter","out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"factor":-10,"in":"#7f8081ff","op":"darker","out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"factor":0,"in":"#7f8081ff","op":"lighter","out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"factor":0,"in":"#7f8081ff","op":"darker","out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"factor":1,"in":"#7f8081ff","op":"lighter","out":{"hex":"#010101ff","u16":[326,328,331,65535]}}, +{"factor":1,"in":"#7f8081ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#7f8081ff","op":"lighter","out":{"hex":"#3f4040ff","u16":[16319,16448,16576,65535]}}, +{"factor":50,"in":"#7f8081ff","op":"darker","out":{"hex":"#feffffff","u16":[65290,65413,65535,65535]}}, +{"factor":99,"in":"#7f8081ff","op":"lighter","out":{"hex":"#7e7f80ff","u16":[32315,32570,32824,65535]}}, +{"factor":99,"in":"#7f8081ff","op":"darker","out":{"hex":"#808182ff","u16":[32965,33224,33484,65535]}}, +{"factor":100,"in":"#7f8081ff","op":"lighter","out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"factor":100,"in":"#7f8081ff","op":"darker","out":{"hex":"#7f8081ff","u16":[32639,32896,33153,65535]}}, +{"factor":101,"in":"#7f8081ff","op":"lighter","out":{"hex":"#808182ff","u16":[32965,33224,33484,65535]}}, +{"factor":101,"in":"#7f8081ff","op":"darker","out":{"hex":"#7e7f80ff","u16":[32315,32570,32824,65535]}}, +{"factor":102,"in":"#7f8081ff","op":"lighter","out":{"hex":"#828384ff","u16":[33292,33554,33816,65535]}}, +{"factor":102,"in":"#7f8081ff","op":"darker","out":{"hex":"#7d7d7eff","u16":[31998,32250,32502,65535]}}, +{"factor":110,"in":"#7f8081ff","op":"lighter","out":{"hex":"#8c8d8eff","u16":[35903,36185,36468,65535]}}, +{"factor":110,"in":"#7f8081ff","op":"darker","out":{"hex":"#737475ff","u16":[29672,29905,30139,65535]}}, +{"factor":120,"in":"#7f8081ff","op":"lighter","out":{"hex":"#989a9bff","u16":[39166,39475,39783,65535]}}, +{"factor":120,"in":"#7f8081ff","op":"darker","out":{"hex":"#6a6b6bff","u16":[27199,27413,27627,65535]}}, +{"factor":150,"in":"#7f8081ff","op":"lighter","out":{"hex":"#bec0c1ff","u16":[48958,49344,49729,65535]}}, +{"factor":150,"in":"#7f8081ff","op":"darker","out":{"hex":"#555556ff","u16":[21759,21931,22102,65535]}}, +{"factor":200,"in":"#7f8081ff","op":"lighter","out":{"hex":"#feffffff","u16":[65290,65413,65535,65535]}}, +{"factor":200,"in":"#7f8081ff","op":"darker","out":{"hex":"#3f4040ff","u16":[16319,16448,16576,65535]}}, +{"factor":300,"in":"#7f8081ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#7f8081ff","op":"darker","out":{"hex":"#2a2b2bff","u16":[10880,10965,11051,65535]}}, +{"factor":1000,"in":"#7f8081ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#7f8081ff","op":"darker","out":{"hex":"#0d0d0dff","u16":[3264,3289,3315,65535]}}, +{"factor":-10,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"factor":-10,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"factor":0,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"factor":0,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"factor":1,"in":"#010000ff","op":"lighter","out":{"hex":"#000000ff","u16":[2,0,0,65535]}}, +{"factor":1,"in":"#010000ff","op":"darker","out":{"hex":"#640000ff","u16":[25700,0,0,65535]}}, +{"factor":50,"in":"#010000ff","op":"lighter","out":{"hex":"#000000ff","u16":[128,0,0,65535]}}, +{"factor":50,"in":"#010000ff","op":"darker","out":{"hex":"#020000ff","u16":[514,0,0,65535]}}, +{"factor":99,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[254,0,0,65535]}}, +{"factor":99,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[259,0,0,65535]}}, +{"factor":100,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"factor":100,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[257,0,0,65535]}}, +{"factor":101,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[259,0,0,65535]}}, +{"factor":101,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[254,0,0,65535]}}, +{"factor":102,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[262,0,0,65535]}}, +{"factor":102,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[251,0,0,65535]}}, +{"factor":110,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[282,0,0,65535]}}, +{"factor":110,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[233,0,0,65535]}}, +{"factor":120,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[308,0,0,65535]}}, +{"factor":120,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[214,0,0,65535]}}, +{"factor":150,"in":"#010000ff","op":"lighter","out":{"hex":"#010000ff","u16":[385,0,0,65535]}}, +{"factor":150,"in":"#010000ff","op":"darker","out":{"hex":"#010000ff","u16":[171,0,0,65535]}}, +{"factor":200,"in":"#010000ff","op":"lighter","out":{"hex":"#020000ff","u16":[514,0,0,65535]}}, +{"factor":200,"in":"#010000ff","op":"darker","out":{"hex":"#000000ff","u16":[128,0,0,65535]}}, +{"factor":300,"in":"#010000ff","op":"lighter","out":{"hex":"#030000ff","u16":[771,0,0,65535]}}, +{"factor":300,"in":"#010000ff","op":"darker","out":{"hex":"#000000ff","u16":[85,0,0,65535]}}, +{"factor":1000,"in":"#010000ff","op":"lighter","out":{"hex":"#0a0000ff","u16":[2570,0,0,65535]}}, +{"factor":1000,"in":"#010000ff","op":"darker","out":{"hex":"#000000ff","u16":[25,0,0,65535]}}, +{"factor":-10,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"factor":-10,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"factor":0,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"factor":0,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"factor":1,"in":"#000001ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,2,65535]}}, +{"factor":1,"in":"#000001ff","op":"darker","out":{"hex":"#000064ff","u16":[0,0,25700,65535]}}, +{"factor":50,"in":"#000001ff","op":"lighter","out":{"hex":"#000000ff","u16":[0,0,128,65535]}}, +{"factor":50,"in":"#000001ff","op":"darker","out":{"hex":"#000002ff","u16":[0,0,514,65535]}}, +{"factor":99,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,254,65535]}}, +{"factor":99,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,259,65535]}}, +{"factor":100,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"factor":100,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,257,65535]}}, +{"factor":101,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,259,65535]}}, +{"factor":101,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,254,65535]}}, +{"factor":102,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,262,65535]}}, +{"factor":102,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,251,65535]}}, +{"factor":110,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,282,65535]}}, +{"factor":110,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,233,65535]}}, +{"factor":120,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,308,65535]}}, +{"factor":120,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,214,65535]}}, +{"factor":150,"in":"#000001ff","op":"lighter","out":{"hex":"#000001ff","u16":[0,0,385,65535]}}, +{"factor":150,"in":"#000001ff","op":"darker","out":{"hex":"#000001ff","u16":[0,0,171,65535]}}, +{"factor":200,"in":"#000001ff","op":"lighter","out":{"hex":"#000002ff","u16":[0,0,514,65535]}}, +{"factor":200,"in":"#000001ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,128,65535]}}, +{"factor":300,"in":"#000001ff","op":"lighter","out":{"hex":"#000003ff","u16":[0,0,771,65535]}}, +{"factor":300,"in":"#000001ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,85,65535]}}, +{"factor":1000,"in":"#000001ff","op":"lighter","out":{"hex":"#00000aff","u16":[0,0,2570,65535]}}, +{"factor":1000,"in":"#000001ff","op":"darker","out":{"hex":"#000000ff","u16":[0,0,25,65535]}}, +{"factor":-10,"in":"#fefffeff","op":"lighter","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"factor":-10,"in":"#fefffeff","op":"darker","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"factor":0,"in":"#fefffeff","op":"lighter","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"factor":0,"in":"#fefffeff","op":"darker","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"factor":1,"in":"#fefffeff","op":"lighter","out":{"hex":"#030303ff","u16":[652,655,652,65535]}}, +{"factor":1,"in":"#fefffeff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#fefffeff","op":"lighter","out":{"hex":"#7f7f7fff","u16":[32639,32767,32639,65535]}}, +{"factor":50,"in":"#fefffeff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#fefffeff","op":"lighter","out":{"hex":"#fbfcfbff","u16":[64632,64886,64632,65535]}}, +{"factor":99,"in":"#fefffeff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":100,"in":"#fefffeff","op":"lighter","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"factor":100,"in":"#fefffeff","op":"darker","out":{"hex":"#fefffeff","u16":[65278,65535,65278,65535]}}, +{"factor":101,"in":"#fefffeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":101,"in":"#fefffeff","op":"darker","out":{"hex":"#fbfcfbff","u16":[64632,64886,64632,65535]}}, +{"factor":102,"in":"#fefffeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":102,"in":"#fefffeff","op":"darker","out":{"hex":"#f9faf9ff","u16":[63998,64250,63998,65535]}}, +{"factor":110,"in":"#fefffeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":110,"in":"#fefffeff","op":"darker","out":{"hex":"#e7e8e7ff","u16":[59343,59577,59343,65535]}}, +{"factor":120,"in":"#fefffeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":120,"in":"#fefffeff","op":"darker","out":{"hex":"#d4d4d4ff","u16":[54398,54612,54398,65535]}}, +{"factor":150,"in":"#fefffeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#fefffeff","op":"darker","out":{"hex":"#a9aaa9ff","u16":[43519,43690,43519,65535]}}, +{"factor":200,"in":"#fefffeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#fefffeff","op":"darker","out":{"hex":"#7f7f7fff","u16":[32639,32767,32639,65535]}}, +{"factor":300,"in":"#fefffeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#fefffeff","op":"darker","out":{"hex":"#555555ff","u16":[21759,21845,21759,65535]}}, +{"factor":1000,"in":"#fefffeff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#fefffeff","op":"darker","out":{"hex":"#191919ff","u16":[6527,6553,6527,65535]}}, +{"factor":-10,"in":"#3daee9ff","op":"lighter","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"factor":-10,"in":"#3daee9ff","op":"darker","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"factor":0,"in":"#3daee9ff","op":"lighter","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"factor":0,"in":"#3daee9ff","op":"darker","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"factor":1,"in":"#3daee9ff","op":"lighter","out":{"hex":"#010202ff","u16":[157,447,598,65535]}}, +{"factor":1,"in":"#3daee9ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#3daee9ff","op":"lighter","out":{"hex":"#1e5774ff","u16":[7838,22359,29940,65535]}}, +{"factor":50,"in":"#3daee9ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#3daee9ff","op":"lighter","out":{"hex":"#3cace7ff","u16":[15522,44276,59288,65535]}}, +{"factor":99,"in":"#3daee9ff","op":"darker","out":{"hex":"#3eb0ebff","u16":[15833,45166,60479,65535]}}, +{"factor":100,"in":"#3daee9ff","op":"lighter","out":{"hex":"#3daee9ff","u16":[15677,44719,59881,65535]}}, +{"factor":100,"in":"#3daee9ff","op":"darker","out":{"hex":"#3daee9ff","u16":[15677,44719,59881,65535]}}, +{"factor":101,"in":"#3daee9ff","op":"lighter","out":{"hex":"#3eb0ebff","u16":[15833,45166,60479,65535]}}, +{"factor":101,"in":"#3daee9ff","op":"darker","out":{"hex":"#3cace7ff","u16":[15522,44276,59288,65535]}}, +{"factor":102,"in":"#3daee9ff","op":"lighter","out":{"hex":"#3eb1eeff","u16":[15990,45613,61078,65535]}}, +{"factor":102,"in":"#3daee9ff","op":"darker","out":{"hex":"#3cabe4ff","u16":[15369,43841,58706,65535]}}, +{"factor":110,"in":"#3daee9ff","op":"lighter","out":{"hex":"#44bfffff","u16":[17491,49056,65535,65535]}}, +{"factor":110,"in":"#3daee9ff","op":"darker","out":{"hex":"#379ed4ff","u16":[14252,40653,54437,65535]}}, +{"factor":120,"in":"#3daee9ff","op":"lighter","out":{"hex":"#5bc7ffff","u16":[23479,51110,65535,65535]}}, +{"factor":120,"in":"#3daee9ff","op":"darker","out":{"hex":"#3391c2ff","u16":[13064,37265,49900,65535]}}, +{"factor":150,"in":"#3daee9ff","op":"lighter","out":{"hex":"#a1dfffff","u16":[41443,57271,65535,65535]}}, +{"factor":150,"in":"#3daee9ff","op":"darker","out":{"hex":"#29749bff","u16":[10451,29812,39920,65535]}}, +{"factor":200,"in":"#3daee9ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#3daee9ff","op":"darker","out":{"hex":"#1e5774ff","u16":[7838,22359,29940,65535]}}, +{"factor":300,"in":"#3daee9ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#3daee9ff","op":"darker","out":{"hex":"#143a4eff","u16":[5226,14906,19960,65535]}}, +{"factor":1000,"in":"#3daee9ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#3daee9ff","op":"darker","out":{"hex":"#061117ff","u16":[1568,4472,5988,65535]}}, +{"factor":-10,"in":"#232629ff","op":"lighter","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"factor":-10,"in":"#232629ff","op":"darker","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"factor":0,"in":"#232629ff","op":"lighter","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"factor":0,"in":"#232629ff","op":"darker","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"factor":1,"in":"#232629ff","op":"lighter","out":{"hex":"#000000ff","u16":[90,97,105,65535]}}, +{"factor":1,"in":"#232629ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#232629ff","op":"lighter","out":{"hex":"#111314ff","u16":[4497,4883,5268,65535]}}, +{"factor":50,"in":"#232629ff","op":"darker","out":{"hex":"#464c52ff","u16":[17990,19532,21074,65535]}}, +{"factor":99,"in":"#232629ff","op":"lighter","out":{"hex":"#232629ff","u16":[8905,9669,10432,65535]}}, +{"factor":99,"in":"#232629ff","op":"darker","out":{"hex":"#232629ff","u16":[9085,9863,10642,65535]}}, +{"factor":100,"in":"#232629ff","op":"lighter","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"factor":100,"in":"#232629ff","op":"darker","out":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}, +{"factor":101,"in":"#232629ff","op":"lighter","out":{"hex":"#232629ff","u16":[9085,9863,10642,65535]}}, +{"factor":101,"in":"#232629ff","op":"darker","out":{"hex":"#232629ff","u16":[8905,9669,10432,65535]}}, +{"factor":102,"in":"#232629ff","op":"lighter","out":{"hex":"#24272aff","u16":[9174,9961,10747,65535]}}, +{"factor":102,"in":"#232629ff","op":"darker","out":{"hex":"#222528ff","u16":[8818,9574,10330,65535]}}, +{"factor":110,"in":"#232629ff","op":"lighter","out":{"hex":"#262a2dff","u16":[9894,10742,11590,65535]}}, +{"factor":110,"in":"#232629ff","op":"darker","out":{"hex":"#202325ff","u16":[8177,8878,9579,65535]}}, +{"factor":120,"in":"#232629ff","op":"lighter","out":{"hex":"#2a2e31ff","u16":[10794,11719,12644,65535]}}, +{"factor":120,"in":"#232629ff","op":"darker","out":{"hex":"#1d2022ff","u16":[7495,8138,8780,65535]}}, +{"factor":150,"in":"#232629ff","op":"lighter","out":{"hex":"#34393dff","u16":[13492,14649,15805,65535]}}, +{"factor":150,"in":"#232629ff","op":"darker","out":{"hex":"#17191bff","u16":[5996,6510,7024,65535]}}, +{"factor":200,"in":"#232629ff","op":"lighter","out":{"hex":"#464c52ff","u16":[17990,19532,21074,65535]}}, +{"factor":200,"in":"#232629ff","op":"darker","out":{"hex":"#111314ff","u16":[4497,4883,5268,65535]}}, +{"factor":300,"in":"#232629ff","op":"lighter","out":{"hex":"#69727bff","u16":[26985,29298,31611,65535]}}, +{"factor":300,"in":"#232629ff","op":"darker","out":{"hex":"#0c0d0eff","u16":[2998,3255,3512,65535]}}, +{"factor":1000,"in":"#232629ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#232629ff","op":"darker","out":{"hex":"#030404ff","u16":[899,976,1053,65535]}}, +{"factor":-10,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"factor":-10,"in":"#eff0f1ff","op":"darker","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"factor":0,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"factor":0,"in":"#eff0f1ff","op":"darker","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"factor":1,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#020202ff","u16":[614,616,619,65535]}}, +{"factor":1,"in":"#eff0f1ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#777878ff","u16":[30711,30839,30968,65535]}}, +{"factor":50,"in":"#eff0f1ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#edeeefff","u16":[60814,61068,61323,65535]}}, +{"factor":99,"in":"#eff0f1ff","op":"darker","out":{"hex":"#f1f2f3ff","u16":[62037,62296,62556,65535]}}, +{"factor":100,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"factor":100,"in":"#eff0f1ff","op":"darker","out":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]}}, +{"factor":101,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#f1f2f3ff","u16":[62037,62296,62556,65535]}}, +{"factor":101,"in":"#eff0f1ff","op":"darker","out":{"hex":"#edeeefff","u16":[60814,61068,61323,65535]}}, +{"factor":102,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#f4f5f6ff","u16":[62651,62913,63175,65535]}}, +{"factor":102,"in":"#eff0f1ff","op":"darker","out":{"hex":"#eaebecff","u16":[60218,60470,60722,65535]}}, +{"factor":110,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":110,"in":"#eff0f1ff","op":"darker","out":{"hex":"#d9dadbff","u16":[55839,56072,56306,65535]}}, +{"factor":120,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":120,"in":"#eff0f1ff","op":"darker","out":{"hex":"#c7c8c9ff","u16":[51186,51400,51614,65535]}}, +{"factor":150,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#eff0f1ff","op":"darker","out":{"hex":"#9fa0a1ff","u16":[40948,41120,41291,65535]}}, +{"factor":200,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#eff0f1ff","op":"darker","out":{"hex":"#777878ff","u16":[30711,30839,30968,65535]}}, +{"factor":300,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#eff0f1ff","op":"darker","out":{"hex":"#505050ff","u16":[20474,20559,20645,65535]}}, +{"factor":1000,"in":"#eff0f1ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#eff0f1ff","op":"darker","out":{"hex":"#181818ff","u16":[6142,6167,6193,65535]}}, +{"factor":-10,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"factor":-10,"in":"#fcfcfcff","op":"darker","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"factor":0,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"factor":0,"in":"#fcfcfcff","op":"darker","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"factor":1,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#030303ff","u16":[647,647,647,65535]}}, +{"factor":1,"in":"#fcfcfcff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#7e7e7eff","u16":[32382,32382,32382,65535]}}, +{"factor":50,"in":"#fcfcfcff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#fafafaff","u16":[64122,64122,64122,65535]}}, +{"factor":99,"in":"#fcfcfcff","op":"darker","out":{"hex":"#ffffffff","u16":[65411,65411,65411,65535]}}, +{"factor":100,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"factor":100,"in":"#fcfcfcff","op":"darker","out":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}, +{"factor":101,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65411,65411,65411,65535]}}, +{"factor":101,"in":"#fcfcfcff","op":"darker","out":{"hex":"#fafafaff","u16":[64122,64122,64122,65535]}}, +{"factor":102,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":102,"in":"#fcfcfcff","op":"darker","out":{"hex":"#f7f7f7ff","u16":[63494,63494,63494,65535]}}, +{"factor":110,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":110,"in":"#fcfcfcff","op":"darker","out":{"hex":"#e5e5e5ff","u16":[58876,58876,58876,65535]}}, +{"factor":120,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":120,"in":"#fcfcfcff","op":"darker","out":{"hex":"#d2d2d2ff","u16":[53970,53970,53970,65535]}}, +{"factor":150,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#fcfcfcff","op":"darker","out":{"hex":"#a8a8a8ff","u16":[43176,43176,43176,65535]}}, +{"factor":200,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#fcfcfcff","op":"darker","out":{"hex":"#7e7e7eff","u16":[32382,32382,32382,65535]}}, +{"factor":300,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#fcfcfcff","op":"darker","out":{"hex":"#545454ff","u16":[21588,21588,21588,65535]}}, +{"factor":1000,"in":"#fcfcfcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#fcfcfcff","op":"darker","out":{"hex":"#191919ff","u16":[6476,6476,6476,65535]}}, +{"factor":-10,"in":"#31363bff","op":"lighter","out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"factor":-10,"in":"#31363bff","op":"darker","out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"factor":0,"in":"#31363bff","op":"lighter","out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"factor":0,"in":"#31363bff","op":"darker","out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"factor":1,"in":"#31363bff","op":"lighter","out":{"hex":"#000101ff","u16":[125,138,151,65535]}}, +{"factor":1,"in":"#31363bff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#31363bff","op":"lighter","out":{"hex":"#181b1dff","u16":[6296,6939,7581,65535]}}, +{"factor":50,"in":"#31363bff","op":"darker","out":{"hex":"#626c76ff","u16":[25186,27756,30326,65535]}}, +{"factor":99,"in":"#31363bff","op":"lighter","out":{"hex":"#31353aff","u16":[12468,13740,15012,65535]}}, +{"factor":99,"in":"#31363bff","op":"darker","out":{"hex":"#31373cff","u16":[12718,14016,15314,65535]}}, +{"factor":100,"in":"#31363bff","op":"lighter","out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"factor":100,"in":"#31363bff","op":"darker","out":{"hex":"#31363bff","u16":[12593,13878,15163,65535]}}, +{"factor":101,"in":"#31363bff","op":"lighter","out":{"hex":"#31373cff","u16":[12718,14016,15314,65535]}}, +{"factor":101,"in":"#31363bff","op":"darker","out":{"hex":"#31353aff","u16":[12468,13740,15012,65535]}}, +{"factor":102,"in":"#31363bff","op":"lighter","out":{"hex":"#32373cff","u16":[12845,14155,15466,65535]}}, +{"factor":102,"in":"#31363bff","op":"darker","out":{"hex":"#30353aff","u16":[12345,13605,14865,65535]}}, +{"factor":110,"in":"#31363bff","op":"lighter","out":{"hex":"#363b41ff","u16":[13852,15265,16679,65535]}}, +{"factor":110,"in":"#31363bff","op":"darker","out":{"hex":"#2d3136ff","u16":[11448,12616,13784,65535]}}, +{"factor":120,"in":"#31363bff","op":"lighter","out":{"hex":"#3b4147ff","u16":[15111,16653,18195,65535]}}, +{"factor":120,"in":"#31363bff","op":"darker","out":{"hex":"#292d31ff","u16":[10493,11564,12635,65535]}}, +{"factor":150,"in":"#31363bff","op":"lighter","out":{"hex":"#495158ff","u16":[18889,20816,22744,65535]}}, +{"factor":150,"in":"#31363bff","op":"darker","out":{"hex":"#212427ff","u16":[8395,9251,10108,65535]}}, +{"factor":200,"in":"#31363bff","op":"lighter","out":{"hex":"#626c76ff","u16":[25186,27756,30326,65535]}}, +{"factor":200,"in":"#31363bff","op":"darker","out":{"hex":"#181b1dff","u16":[6296,6939,7581,65535]}}, +{"factor":300,"in":"#31363bff","op":"lighter","out":{"hex":"#93a2b1ff","u16":[37779,41634,45489,65535]}}, +{"factor":300,"in":"#31363bff","op":"darker","out":{"hex":"#101214ff","u16":[4197,4626,5054,65535]}}, +{"factor":1000,"in":"#31363bff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#31363bff","op":"darker","out":{"hex":"#050506ff","u16":[1259,1388,1516,65535]}}, +{"factor":-10,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"factor":-10,"in":"#1b1e20ff","op":"darker","out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"factor":0,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"factor":0,"in":"#1b1e20ff","op":"darker","out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"factor":1,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#000000ff","u16":[69,77,82,65535]}}, +{"factor":1,"in":"#1b1e20ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#0d0f10ff","u16":[3469,3855,4112,65535]}}, +{"factor":50,"in":"#1b1e20ff","op":"darker","out":{"hex":"#363c40ff","u16":[13878,15420,16448,65535]}}, +{"factor":99,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#1b1e20ff","u16":[6870,7633,8142,65535]}}, +{"factor":99,"in":"#1b1e20ff","op":"darker","out":{"hex":"#1b1e20ff","u16":[7008,7787,8306,65535]}}, +{"factor":100,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"factor":100,"in":"#1b1e20ff","op":"darker","out":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]}}, +{"factor":101,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#1b1e20ff","u16":[7008,7787,8306,65535]}}, +{"factor":101,"in":"#1b1e20ff","op":"darker","out":{"hex":"#1b1e20ff","u16":[6870,7633,8142,65535]}}, +{"factor":102,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#1c1f21ff","u16":[7077,7864,8388,65535]}}, +{"factor":102,"in":"#1b1e20ff","op":"darker","out":{"hex":"#1a1d1fff","u16":[6802,7558,8062,65535]}}, +{"factor":110,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#1e2123ff","u16":[7633,8481,9046,65535]}}, +{"factor":110,"in":"#1b1e20ff","op":"darker","out":{"hex":"#191b1dff","u16":[6308,7009,7476,65535]}}, +{"factor":120,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#202426ff","u16":[8326,9251,9868,65535]}}, +{"factor":120,"in":"#1b1e20ff","op":"darker","out":{"hex":"#16191bff","u16":[5782,6425,6853,65535]}}, +{"factor":150,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#282d30ff","u16":[10408,11565,12336,65535]}}, +{"factor":150,"in":"#1b1e20ff","op":"darker","out":{"hex":"#121415ff","u16":[4625,5139,5482,65535]}}, +{"factor":200,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#363c40ff","u16":[13878,15420,16448,65535]}}, +{"factor":200,"in":"#1b1e20ff","op":"darker","out":{"hex":"#0d0f10ff","u16":[3469,3855,4112,65535]}}, +{"factor":300,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#515a60ff","u16":[20817,23130,24672,65535]}}, +{"factor":300,"in":"#1b1e20ff","op":"darker","out":{"hex":"#090a0bff","u16":[2313,2570,2741,65535]}}, +{"factor":1000,"in":"#1b1e20ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#1b1e20ff","op":"darker","out":{"hex":"#030303ff","u16":[694,771,822,65535]}}, +{"factor":-10,"in":"#da4453ff","op":"lighter","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"factor":-10,"in":"#da4453ff","op":"darker","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"factor":0,"in":"#da4453ff","op":"lighter","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"factor":0,"in":"#da4453ff","op":"darker","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"factor":1,"in":"#da4453ff","op":"lighter","out":{"hex":"#020101ff","u16":[560,175,213,65535]}}, +{"factor":1,"in":"#da4453ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#da4453ff","op":"lighter","out":{"hex":"#6d2229ff","u16":[28013,8738,10665,65535]}}, +{"factor":50,"in":"#da4453ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#da4453ff","op":"lighter","out":{"hex":"#d84352ff","u16":[55471,17303,21120,65535]}}, +{"factor":99,"in":"#da4453ff","op":"darker","out":{"hex":"#dc4554ff","u16":[56586,17651,21544,65535]}}, +{"factor":100,"in":"#da4453ff","op":"lighter","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"factor":100,"in":"#da4453ff","op":"darker","out":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]}}, +{"factor":101,"in":"#da4453ff","op":"lighter","out":{"hex":"#dc4554ff","u16":[56586,17651,21544,65535]}}, +{"factor":101,"in":"#da4453ff","op":"darker","out":{"hex":"#d84352ff","u16":[55471,17303,21120,65535]}}, +{"factor":102,"in":"#da4453ff","op":"lighter","out":{"hex":"#de4555ff","u16":[57146,17825,21757,65535]}}, +{"factor":102,"in":"#da4453ff","op":"darker","out":{"hex":"#d64351ff","u16":[54927,17133,20912,65535]}}, +{"factor":110,"in":"#da4453ff","op":"lighter","out":{"hex":"#f04b5bff","u16":[61628,19223,23464,65535]}}, +{"factor":110,"in":"#da4453ff","op":"darker","out":{"hex":"#c63e4bff","u16":[50932,15887,19391,65535]}}, +{"factor":120,"in":"#da4453ff","op":"lighter","out":{"hex":"#ff5667ff","u16":[65535,22138,26478,65535]}}, +{"factor":120,"in":"#da4453ff","op":"darker","out":{"hex":"#b63945ff","u16":[46688,14563,17776,65535]}}, +{"factor":150,"in":"#da4453ff","op":"lighter","out":{"hex":"#ff98a2ff","u16":[65535,38946,41605,65535]}}, +{"factor":150,"in":"#da4453ff","op":"darker","out":{"hex":"#912d37ff","u16":[37350,11650,14220,65535]}}, +{"factor":200,"in":"#da4453ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#da4453ff","op":"darker","out":{"hex":"#6d2229ff","u16":[28013,8738,10665,65535]}}, +{"factor":300,"in":"#da4453ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#da4453ff","op":"darker","out":{"hex":"#49171cff","u16":[18675,5825,7110,65535]}}, +{"factor":1000,"in":"#da4453ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#da4453ff","op":"darker","out":{"hex":"#160708ff","u16":[5602,1747,2133,65535]}}, +{"factor":-10,"in":"#27ae60ff","op":"lighter","out":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]}}, +{"factor":-10,"in":"#27ae60ff","op":"darker","out":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]}}, +{"factor":0,"in":"#27ae60ff","op":"lighter","out":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]}}, +{"factor":0,"in":"#27ae60ff","op":"darker","out":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]}}, +{"factor":1,"in":"#27ae60ff","op":"lighter","out":{"hex":"#000201ff","u16":[100,447,247,65535]}}, +{"factor":1,"in":"#27ae60ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#27ae60ff","op":"lighter","out":{"hex":"#145730ff","u16":[5012,22359,12335,65535]}}, +{"factor":50,"in":"#27ae60ff","op":"darker","out":{"hex":"#96ffc2ff","u16":[38590,65535,49965,65535]}}, +{"factor":99,"in":"#27ae60ff","op":"lighter","out":{"hex":"#27ac5fff","u16":[9924,44275,24426,65535]}}, +{"factor":99,"in":"#27ae60ff","op":"darker","out":{"hex":"#27b061ff","u16":[10123,45165,24917,65535]}}, +{"factor":100,"in":"#27ae60ff","op":"lighter","out":{"hex":"#27ae60ff","u16":[10023,44718,24670,65535]}}, +{"factor":100,"in":"#27ae60ff","op":"darker","out":{"hex":"#27ae60ff","u16":[10023,44718,24670,65535]}}, +{"factor":101,"in":"#27ae60ff","op":"lighter","out":{"hex":"#27b061ff","u16":[10123,45165,24917,65535]}}, +{"factor":101,"in":"#27ae60ff","op":"darker","out":{"hex":"#27ac5fff","u16":[9924,44275,24426,65535]}}, +{"factor":102,"in":"#27ae60ff","op":"lighter","out":{"hex":"#28b162ff","u16":[10223,45612,25163,65535]}}, +{"factor":102,"in":"#27ae60ff","op":"darker","out":{"hex":"#26ab5eff","u16":[9827,43841,24186,65535]}}, +{"factor":110,"in":"#27ae60ff","op":"lighter","out":{"hex":"#2bbf6aff","u16":[11025,49189,27137,65535]}}, +{"factor":110,"in":"#27ae60ff","op":"darker","out":{"hex":"#239e57ff","u16":[9112,40652,22427,65535]}}, +{"factor":120,"in":"#27ae60ff","op":"lighter","out":{"hex":"#2fd173ff","u16":[12028,53661,29604,65535]}}, +{"factor":120,"in":"#27ae60ff","op":"darker","out":{"hex":"#219150ff","u16":[8353,37265,20558,65535]}}, +{"factor":150,"in":"#27ae60ff","op":"lighter","out":{"hex":"#3fff90ff","u16":[16231,65535,37046,65535]}}, +{"factor":150,"in":"#27ae60ff","op":"darker","out":{"hex":"#1a7440ff","u16":[6682,29812,16447,65535]}}, +{"factor":200,"in":"#27ae60ff","op":"lighter","out":{"hex":"#96ffc2ff","u16":[38590,65535,49965,65535]}}, +{"factor":200,"in":"#27ae60ff","op":"darker","out":{"hex":"#145730ff","u16":[5012,22359,12335,65535]}}, +{"factor":300,"in":"#27ae60ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#27ae60ff","op":"darker","out":{"hex":"#0d3a20ff","u16":[3341,14906,8223,65535]}}, +{"factor":1000,"in":"#27ae60ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#27ae60ff","op":"darker","out":{"hex":"#04110aff","u16":[1002,4471,2467,65535]}}, +{"factor":-10,"in":"#f67400ff","op":"lighter","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"factor":-10,"in":"#f67400ff","op":"darker","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"factor":0,"in":"#f67400ff","op":"lighter","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"factor":0,"in":"#f67400ff","op":"darker","out":{"hex":"#f67400ff","u16":[63222,29812,0,65535]}}, +{"factor":1,"in":"#f67400ff","op":"lighter","out":{"hex":"#020100ff","u16":[632,298,0,65535]}}, +{"factor":1,"in":"#f67400ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#f67400ff","op":"lighter","out":{"hex":"#7b3a00ff","u16":[31611,14905,0,65535]}}, +{"factor":50,"in":"#f67400ff","op":"darker","out":{"hex":"#fff5edff","u16":[65535,63090,60909,65535]}}, +{"factor":99,"in":"#f67400ff","op":"lighter","out":{"hex":"#f47300ff","u16":[62596,29514,0,65535]}}, +{"factor":99,"in":"#f67400ff","op":"darker","out":{"hex":"#f87500ff","u16":[63854,30107,0,65535]}}, +{"factor":100,"in":"#f67400ff","op":"lighter","out":{"hex":"#f67400ff","u16":[63222,29809,0,65535]}}, +{"factor":100,"in":"#f67400ff","op":"darker","out":{"hex":"#f67400ff","u16":[63222,29809,0,65535]}}, +{"factor":101,"in":"#f67400ff","op":"lighter","out":{"hex":"#f87500ff","u16":[63854,30107,0,65535]}}, +{"factor":101,"in":"#f67400ff","op":"darker","out":{"hex":"#f47300ff","u16":[62596,29514,0,65535]}}, +{"factor":102,"in":"#f67400ff","op":"lighter","out":{"hex":"#fb7600ff","u16":[64486,30405,0,65535]}}, +{"factor":102,"in":"#f67400ff","op":"darker","out":{"hex":"#f17200ff","u16":[61982,29225,0,65535]}}, +{"factor":110,"in":"#f67400ff","op":"lighter","out":{"hex":"#ff8010ff","u16":[65535,33019,4009,65535]}}, +{"factor":110,"in":"#f67400ff","op":"darker","out":{"hex":"#e06900ff","u16":[57474,27099,0,65535]}}, +{"factor":120,"in":"#f67400ff","op":"lighter","out":{"hex":"#ff8d28ff","u16":[65535,36360,10331,65535]}}, +{"factor":120,"in":"#f67400ff","op":"darker","out":{"hex":"#cd6100ff","u16":[52685,24841,0,65535]}}, +{"factor":150,"in":"#f67400ff","op":"lighter","out":{"hex":"#ffb472ff","u16":[65535,46384,29298,65535]}}, +{"factor":150,"in":"#f67400ff","op":"darker","out":{"hex":"#a44d00ff","u16":[42148,19873,0,65535]}}, +{"factor":200,"in":"#f67400ff","op":"lighter","out":{"hex":"#fff5edff","u16":[65535,63090,60909,65535]}}, +{"factor":200,"in":"#f67400ff","op":"darker","out":{"hex":"#7b3a00ff","u16":[31611,14905,0,65535]}}, +{"factor":300,"in":"#f67400ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#f67400ff","op":"darker","out":{"hex":"#522700ff","u16":[21074,9936,0,65535]}}, +{"factor":1000,"in":"#f67400ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#f67400ff","op":"darker","out":{"hex":"#190c00ff","u16":[6322,2981,0,65535]}}, +{"factor":-10,"in":"#2980b9ff","op":"lighter","out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"factor":-10,"in":"#2980b9ff","op":"darker","out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"factor":0,"in":"#2980b9ff","op":"lighter","out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"factor":0,"in":"#2980b9ff","op":"darker","out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"factor":1,"in":"#2980b9ff","op":"lighter","out":{"hex":"#000102ff","u16":[105,329,475,65535]}}, +{"factor":1,"in":"#2980b9ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#2980b9ff","op":"lighter","out":{"hex":"#14405cff","u16":[5268,16448,23772,65535]}}, +{"factor":50,"in":"#2980b9ff","op":"darker","out":{"hex":"#acdeffff","u16":[44079,57042,65535,65535]}}, +{"factor":99,"in":"#2980b9ff","op":"lighter","out":{"hex":"#297fb7ff","u16":[10433,32570,47074,65535]}}, +{"factor":99,"in":"#2980b9ff","op":"darker","out":{"hex":"#2981bbff","u16":[10642,33225,48020,65535]}}, +{"factor":100,"in":"#2980b9ff","op":"lighter","out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"factor":100,"in":"#2980b9ff","op":"darker","out":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]}}, +{"factor":101,"in":"#2980b9ff","op":"lighter","out":{"hex":"#2981bbff","u16":[10642,33225,48020,65535]}}, +{"factor":101,"in":"#2980b9ff","op":"darker","out":{"hex":"#297fb7ff","u16":[10433,32570,47074,65535]}}, +{"factor":102,"in":"#2980b9ff","op":"lighter","out":{"hex":"#2a83bdff","u16":[10748,33553,48495,65535]}}, +{"factor":102,"in":"#2980b9ff","op":"darker","out":{"hex":"#287db5ff","u16":[10330,32250,46612,65535]}}, +{"factor":110,"in":"#2980b9ff","op":"lighter","out":{"hex":"#2d8dcbff","u16":[11591,36185,52299,65535]}}, +{"factor":110,"in":"#2980b9ff","op":"darker","out":{"hex":"#2574a8ff","u16":[9579,29905,43222,65535]}}, +{"factor":120,"in":"#2980b9ff","op":"lighter","out":{"hex":"#319adeff","u16":[12644,39475,57054,65535]}}, +{"factor":120,"in":"#2980b9ff","op":"darker","out":{"hex":"#226b9aff","u16":[8781,27413,39620,65535]}}, +{"factor":150,"in":"#2980b9ff","op":"lighter","out":{"hex":"#4fb9ffff","u16":[20306,47632,65535,65535]}}, +{"factor":150,"in":"#2980b9ff","op":"darker","out":{"hex":"#1b557bff","u16":[7025,21930,31696,65535]}}, +{"factor":200,"in":"#2980b9ff","op":"lighter","out":{"hex":"#acdeffff","u16":[44079,57042,65535,65535]}}, +{"factor":200,"in":"#2980b9ff","op":"darker","out":{"hex":"#14405cff","u16":[5268,16448,23772,65535]}}, +{"factor":300,"in":"#2980b9ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#2980b9ff","op":"darker","out":{"hex":"#0e2b3eff","u16":[3512,10965,15848,65535]}}, +{"factor":1000,"in":"#2980b9ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#2980b9ff","op":"darker","out":{"hex":"#040d12ff","u16":[1054,3289,4754,65535]}}, +{"factor":-10,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"factor":-10,"in":"#7f8c8dff","op":"darker","out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"factor":0,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"factor":0,"in":"#7f8c8dff","op":"darker","out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"factor":1,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#010101ff","u16":[326,359,362,65535]}}, +{"factor":1,"in":"#7f8c8dff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#3f4646ff","u16":[16319,17989,18118,65535]}}, +{"factor":50,"in":"#7f8c8dff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#7e8b8cff","u16":[32316,35623,35878,65535]}}, +{"factor":99,"in":"#7f8c8dff","op":"darker","out":{"hex":"#808d8eff","u16":[32965,36339,36599,65535]}}, +{"factor":100,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"factor":100,"in":"#7f8c8dff","op":"darker","out":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}}, +{"factor":101,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#808d8eff","u16":[32965,36339,36599,65535]}}, +{"factor":101,"in":"#7f8c8dff","op":"darker","out":{"hex":"#7e8b8cff","u16":[32316,35623,35878,65535]}}, +{"factor":102,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#828f90ff","u16":[33291,36699,36961,65535]}}, +{"factor":102,"in":"#7f8c8dff","op":"darker","out":{"hex":"#7d898aff","u16":[31999,35274,35526,65535]}}, +{"factor":110,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#8c9a9bff","u16":[35902,39577,39860,65535]}}, +{"factor":110,"in":"#7f8c8dff","op":"darker","out":{"hex":"#737f80ff","u16":[29671,32708,32942,65535]}}, +{"factor":120,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#98a8a9ff","u16":[39166,43175,43484,65535]}}, +{"factor":120,"in":"#7f8c8dff","op":"darker","out":{"hex":"#6a7575ff","u16":[27199,29983,30197,65535]}}, +{"factor":150,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#bed2d3ff","u16":[48958,53969,54355,65535]}}, +{"factor":150,"in":"#7f8c8dff","op":"darker","out":{"hex":"#555d5eff","u16":[21759,23986,24158,65535]}}, +{"factor":200,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#7f8c8dff","op":"darker","out":{"hex":"#3f4646ff","u16":[16319,17989,18118,65535]}}, +{"factor":300,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#7f8c8dff","op":"darker","out":{"hex":"#2a2f2fff","u16":[10880,11993,12079,65535]}}, +{"factor":1000,"in":"#7f8c8dff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#7f8c8dff","op":"darker","out":{"hex":"#0d0e0eff","u16":[3263,3597,3623,65535]}}, +{"factor":-10,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"factor":-10,"in":"#9b59b6ff","op":"darker","out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"factor":0,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"factor":0,"in":"#9b59b6ff","op":"darker","out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"factor":1,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#020102ff","u16":[398,228,467,65535]}}, +{"factor":1,"in":"#9b59b6ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#4d2c5bff","u16":[19917,11436,23387,65535]}}, +{"factor":50,"in":"#9b59b6ff","op":"darker","out":{"hex":"#f9eaffff","u16":[63945,60060,65535,65535]}}, +{"factor":99,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#9958b4ff","u16":[39440,22646,46310,65535]}}, +{"factor":99,"in":"#9b59b6ff","op":"darker","out":{"hex":"#9d5ab8ff","u16":[40232,23101,47241,65535]}}, +{"factor":100,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"factor":100,"in":"#9b59b6ff","op":"darker","out":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}}, +{"factor":101,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#9d5ab8ff","u16":[40232,23101,47241,65535]}}, +{"factor":101,"in":"#9b59b6ff","op":"darker","out":{"hex":"#9958b4ff","u16":[39440,22646,46310,65535]}}, +{"factor":102,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#9e5bbaff","u16":[40631,23330,47709,65535]}}, +{"factor":102,"in":"#9b59b6ff","op":"darker","out":{"hex":"#9857b2ff","u16":[39053,22424,45856,65535]}}, +{"factor":110,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#aa62c8ff","u16":[43818,25160,51451,65535]}}, +{"factor":110,"in":"#9b59b6ff","op":"darker","out":{"hex":"#8d51a5ff","u16":[36213,20793,42521,65535]}}, +{"factor":120,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#ba6bdaff","u16":[47801,27447,56128,65535]}}, +{"factor":120,"in":"#9b59b6ff","op":"darker","out":{"hex":"#814a98ff","u16":[33195,19060,38978,65535]}}, +{"factor":150,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#de8fffff","u16":[57155,36673,65535,65535]}}, +{"factor":150,"in":"#9b59b6ff","op":"darker","out":{"hex":"#673b79ff","u16":[26556,15248,31182,65535]}}, +{"factor":200,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#f9eaffff","u16":[63945,60060,65535,65535]}}, +{"factor":200,"in":"#9b59b6ff","op":"darker","out":{"hex":"#4d2c5bff","u16":[19917,11436,23387,65535]}}, +{"factor":300,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#9b59b6ff","op":"darker","out":{"hex":"#341e3dff","u16":[13278,7624,15591,65535]}}, +{"factor":1000,"in":"#9b59b6ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#9b59b6ff","op":"darker","out":{"hex":"#0f0912ff","u16":[3983,2287,4677,65535]}}, +{"factor":-10,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]}}, +{"factor":-10,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]}}, +{"factor":0,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]}}, +{"factor":0,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]}}, +{"factor":1,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#000202ff","u16":[74,393,624,65535]}}, +{"factor":1,"in":"#1d99f3ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#0e4d79ff","u16":[3726,19662,31225,65535]}}, +{"factor":50,"in":"#1d99f3ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#1d97f1ff","u16":[7379,38935,61832,65535]}}, +{"factor":99,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1d9bf5ff","u16":[7527,39717,63075,65535]}}, +{"factor":100,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#1d99f3ff","u16":[7453,39324,62451,65535]}}, +{"factor":100,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1d99f3ff","u16":[7453,39324,62451,65535]}}, +{"factor":101,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#1d9bf5ff","u16":[7527,39717,63075,65535]}}, +{"factor":101,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1d97f1ff","u16":[7379,38935,61832,65535]}}, +{"factor":102,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#1e9cf8ff","u16":[7602,40111,63700,65535]}}, +{"factor":102,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1c96eeff","u16":[7307,38553,61226,65535]}}, +{"factor":110,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#2ba6ffff","u16":[10982,42595,65535,65535]}}, +{"factor":110,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1a8bddff","u16":[6775,35749,56773,65535]}}, +{"factor":120,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#43b0ffff","u16":[17227,45221,65535,65535]}}, +{"factor":120,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1880caff","u16":[6211,32770,52042,65535]}}, +{"factor":150,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#8ccfffff","u16":[35962,53100,65535,65535]}}, +{"factor":150,"in":"#1d99f3ff","op":"darker","out":{"hex":"#1366a2ff","u16":[4969,26216,41634,65535]}}, +{"factor":200,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#1d99f3ff","op":"darker","out":{"hex":"#0e4d79ff","u16":[3726,19662,31225,65535]}}, +{"factor":300,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#1d99f3ff","op":"darker","out":{"hex":"#0a3351ff","u16":[2484,13108,20817,65535]}}, +{"factor":1000,"in":"#1d99f3ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#1d99f3ff","op":"darker","out":{"hex":"#030f18ff","u16":[745,3932,6245,65535]}}, +{"factor":-10,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"factor":-10,"in":"#dee0e2ff","op":"darker","out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"factor":0,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"factor":0,"in":"#dee0e2ff","op":"darker","out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"factor":1,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#020202ff","u16":[570,575,580,65535]}}, +{"factor":1,"in":"#dee0e2ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#6f7071ff","u16":[28527,28784,29041,65535]}}, +{"factor":50,"in":"#dee0e2ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#dcdee0ff","u16":[56488,56997,57506,65535]}}, +{"factor":99,"in":"#dee0e2ff","op":"darker","out":{"hex":"#e0e2e4ff","u16":[57624,58143,58662,65535]}}, +{"factor":100,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"factor":100,"in":"#dee0e2ff","op":"darker","out":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]}}, +{"factor":101,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#e0e2e4ff","u16":[57624,58143,58662,65535]}}, +{"factor":101,"in":"#dee0e2ff","op":"darker","out":{"hex":"#dcdee0ff","u16":[56488,56997,57506,65535]}}, +{"factor":102,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#e2e4e7ff","u16":[58194,58719,59243,65535]}}, +{"factor":102,"in":"#dee0e2ff","op":"darker","out":{"hex":"#dadcdeff","u16":[55935,56439,56943,65535]}}, +{"factor":110,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#f4f6f9ff","u16":[62759,63325,63890,65535]}}, +{"factor":110,"in":"#dee0e2ff","op":"darker","out":{"hex":"#cacccdff","u16":[51866,52334,52801,65535]}}, +{"factor":120,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":120,"in":"#dee0e2ff","op":"darker","out":{"hex":"#b9bbbcff","u16":[47544,47973,48401,65535]}}, +{"factor":150,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":150,"in":"#dee0e2ff","op":"darker","out":{"hex":"#949597ff","u16":[38036,38378,38721,65535]}}, +{"factor":200,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#dee0e2ff","op":"darker","out":{"hex":"#6f7071ff","u16":[28527,28784,29041,65535]}}, +{"factor":300,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#dee0e2ff","op":"darker","out":{"hex":"#4a4b4bff","u16":[19017,19189,19360,65535]}}, +{"factor":1000,"in":"#dee0e2ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#dee0e2ff","op":"darker","out":{"hex":"#161617ff","u16":[5705,5757,5808,65535]}}, +{"factor":-10,"in":"#292c30ff","op":"lighter","out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"factor":-10,"in":"#292c30ff","op":"darker","out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"factor":0,"in":"#292c30ff","op":"lighter","out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"factor":0,"in":"#292c30ff","op":"darker","out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"factor":1,"in":"#292c30ff","op":"lighter","out":{"hex":"#000000ff","u16":[105,113,123,65535]}}, +{"factor":1,"in":"#292c30ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#292c30ff","op":"lighter","out":{"hex":"#151618ff","u16":[5269,5654,6168,65535]}}, +{"factor":50,"in":"#292c30ff","op":"darker","out":{"hex":"#525860ff","u16":[21074,22616,24672,65535]}}, +{"factor":99,"in":"#292c30ff","op":"lighter","out":{"hex":"#292c30ff","u16":[10432,11195,12213,65535]}}, +{"factor":99,"in":"#292c30ff","op":"darker","out":{"hex":"#292c30ff","u16":[10642,11421,12459,65535]}}, +{"factor":100,"in":"#292c30ff","op":"lighter","out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"factor":100,"in":"#292c30ff","op":"darker","out":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]}}, +{"factor":101,"in":"#292c30ff","op":"lighter","out":{"hex":"#292c30ff","u16":[10642,11421,12459,65535]}}, +{"factor":101,"in":"#292c30ff","op":"darker","out":{"hex":"#292c30ff","u16":[10432,11195,12213,65535]}}, +{"factor":102,"in":"#292c30ff","op":"lighter","out":{"hex":"#2a2d31ff","u16":[10747,11533,12582,65535]}}, +{"factor":102,"in":"#292c30ff","op":"darker","out":{"hex":"#282b2fff","u16":[10330,11086,12094,65535]}}, +{"factor":110,"in":"#292c30ff","op":"lighter","out":{"hex":"#2d3035ff","u16":[11590,12438,13569,65535]}}, +{"factor":110,"in":"#292c30ff","op":"darker","out":{"hex":"#25282cff","u16":[9579,10279,11214,65535]}}, +{"factor":120,"in":"#292c30ff","op":"lighter","out":{"hex":"#31353aff","u16":[12644,13569,14803,65535]}}, +{"factor":120,"in":"#292c30ff","op":"darker","out":{"hex":"#222528ff","u16":[8781,9423,10280,65535]}}, +{"factor":150,"in":"#292c30ff","op":"lighter","out":{"hex":"#3e4248ff","u16":[15806,16962,18504,65535]}}, +{"factor":150,"in":"#292c30ff","op":"darker","out":{"hex":"#1b1d20ff","u16":[7025,7539,8224,65535]}}, +{"factor":200,"in":"#292c30ff","op":"lighter","out":{"hex":"#525860ff","u16":[21074,22616,24672,65535]}}, +{"factor":200,"in":"#292c30ff","op":"darker","out":{"hex":"#151618ff","u16":[5269,5654,6168,65535]}}, +{"factor":300,"in":"#292c30ff","op":"lighter","out":{"hex":"#7b8490ff","u16":[31611,33924,37008,65535]}}, +{"factor":300,"in":"#292c30ff","op":"darker","out":{"hex":"#0e0f10ff","u16":[3512,3769,4112,65535]}}, +{"factor":1000,"in":"#292c30ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#292c30ff","op":"darker","out":{"hex":"#040405ff","u16":[1053,1130,1233,65535]}}, +{"factor":-10,"in":"#e93a9aff","op":"lighter","out":{"hex":"#e93a9aff","u16":[59881,14906,39578,65535]}}, +{"factor":-10,"in":"#e93a9aff","op":"darker","out":{"hex":"#e93a9aff","u16":[59881,14906,39578,65535]}}, +{"factor":0,"in":"#e93a9aff","op":"lighter","out":{"hex":"#e93a9aff","u16":[59881,14906,39578,65535]}}, +{"factor":0,"in":"#e93a9aff","op":"darker","out":{"hex":"#e93a9aff","u16":[59881,14906,39578,65535]}}, +{"factor":1,"in":"#e93a9aff","op":"lighter","out":{"hex":"#020102ff","u16":[598,149,395,65535]}}, +{"factor":1,"in":"#e93a9aff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#e93a9aff","op":"lighter","out":{"hex":"#741d4dff","u16":[29940,7453,19787,65535]}}, +{"factor":50,"in":"#e93a9aff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#e93a9aff","op":"lighter","out":{"hex":"#e73998ff","u16":[59288,14758,39183,65535]}}, +{"factor":99,"in":"#e93a9aff","op":"darker","out":{"hex":"#eb3b9cff","u16":[60479,15054,39970,65535]}}, +{"factor":100,"in":"#e93a9aff","op":"lighter","out":{"hex":"#e93a9aff","u16":[59881,14906,39575,65535]}}, +{"factor":100,"in":"#e93a9aff","op":"darker","out":{"hex":"#e93a9aff","u16":[59881,14906,39575,65535]}}, +{"factor":101,"in":"#e93a9aff","op":"lighter","out":{"hex":"#eb3b9cff","u16":[60479,15054,39970,65535]}}, +{"factor":101,"in":"#e93a9aff","op":"darker","out":{"hex":"#e73998ff","u16":[59288,14758,39183,65535]}}, +{"factor":102,"in":"#e93a9aff","op":"lighter","out":{"hex":"#ee3b9dff","u16":[61078,15204,40366,65535]}}, +{"factor":102,"in":"#e93a9aff","op":"darker","out":{"hex":"#e43997ff","u16":[58706,14613,38798,65535]}}, +{"factor":110,"in":"#e93a9aff","op":"lighter","out":{"hex":"#ff41a9ff","u16":[65535,16647,43462,65535]}}, +{"factor":110,"in":"#e93a9aff","op":"darker","out":{"hex":"#d4358cff","u16":[54437,13550,35977,65535]}}, +{"factor":120,"in":"#e93a9aff","op":"lighter","out":{"hex":"#ff58b4ff","u16":[65535,22635,46166,65535]}}, +{"factor":120,"in":"#e93a9aff","op":"darker","out":{"hex":"#c23080ff","u16":[49900,12421,32978,65535]}}, +{"factor":150,"in":"#e93a9aff","op":"lighter","out":{"hex":"#ff9ed3ff","u16":[65535,40599,54276,65535]}}, +{"factor":150,"in":"#e93a9aff","op":"darker","out":{"hex":"#9b2767ff","u16":[39920,9937,26383,65535]}}, +{"factor":200,"in":"#e93a9aff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#e93a9aff","op":"darker","out":{"hex":"#741d4dff","u16":[29940,7453,19787,65535]}}, +{"factor":300,"in":"#e93a9aff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#e93a9aff","op":"darker","out":{"hex":"#4e1333ff","u16":[19960,4968,13191,65535]}}, +{"factor":1000,"in":"#e93a9aff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#e93a9aff","op":"darker","out":{"hex":"#17060fff","u16":[5988,1491,3957,65535]}}, +{"factor":-10,"in":"#e93d58ff","op":"lighter","out":{"hex":"#e93d58ff","u16":[59881,15677,22616,65535]}}, +{"factor":-10,"in":"#e93d58ff","op":"darker","out":{"hex":"#e93d58ff","u16":[59881,15677,22616,65535]}}, +{"factor":0,"in":"#e93d58ff","op":"lighter","out":{"hex":"#e93d58ff","u16":[59881,15677,22616,65535]}}, +{"factor":0,"in":"#e93d58ff","op":"darker","out":{"hex":"#e93d58ff","u16":[59881,15677,22616,65535]}}, +{"factor":1,"in":"#e93d58ff","op":"lighter","out":{"hex":"#020101ff","u16":[598,157,226,65535]}}, +{"factor":1,"in":"#e93d58ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#e93d58ff","op":"lighter","out":{"hex":"#741e2cff","u16":[29940,7838,11308,65535]}}, +{"factor":50,"in":"#e93d58ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#e93d58ff","op":"lighter","out":{"hex":"#e73c57ff","u16":[59288,15522,22393,65535]}}, +{"factor":99,"in":"#e93d58ff","op":"darker","out":{"hex":"#eb3e59ff","u16":[60479,15833,22843,65535]}}, +{"factor":100,"in":"#e93d58ff","op":"lighter","out":{"hex":"#e93d58ff","u16":[59881,15677,22617,65535]}}, +{"factor":100,"in":"#e93d58ff","op":"darker","out":{"hex":"#e93d58ff","u16":[59881,15677,22617,65535]}}, +{"factor":101,"in":"#e93d58ff","op":"lighter","out":{"hex":"#eb3e59ff","u16":[60479,15833,22843,65535]}}, +{"factor":101,"in":"#e93d58ff","op":"darker","out":{"hex":"#e73c57ff","u16":[59288,15522,22393,65535]}}, +{"factor":102,"in":"#e93d58ff","op":"lighter","out":{"hex":"#ee3e5aff","u16":[61078,15990,23069,65535]}}, +{"factor":102,"in":"#e93d58ff","op":"darker","out":{"hex":"#e43c56ff","u16":[58706,15369,22173,65535]}}, +{"factor":110,"in":"#e93d58ff","op":"lighter","out":{"hex":"#ff4461ff","u16":[65535,17491,25034,65535]}}, +{"factor":110,"in":"#e93d58ff","op":"darker","out":{"hex":"#d43750ff","u16":[54437,14252,20561,65535]}}, +{"factor":120,"in":"#e93d58ff","op":"lighter","out":{"hex":"#ff5b75ff","u16":[65535,23479,30082,65535]}}, +{"factor":120,"in":"#e93d58ff","op":"darker","out":{"hex":"#c23349ff","u16":[49900,13064,18847,65535]}}, +{"factor":150,"in":"#e93d58ff","op":"lighter","out":{"hex":"#ffa1b0ff","u16":[65535,41443,45225,65535]}}, +{"factor":150,"in":"#e93d58ff","op":"darker","out":{"hex":"#9b293bff","u16":[39920,10451,15078,65535]}}, +{"factor":200,"in":"#e93d58ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#e93d58ff","op":"darker","out":{"hex":"#741e2cff","u16":[29940,7838,11308,65535]}}, +{"factor":300,"in":"#e93d58ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#e93d58ff","op":"darker","out":{"hex":"#4e141dff","u16":[19960,5226,7539,65535]}}, +{"factor":1000,"in":"#e93d58ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#e93d58ff","op":"darker","out":{"hex":"#170609ff","u16":[5988,1568,2262,65535]}}, +{"factor":-10,"in":"#e9643aff","op":"lighter","out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"factor":-10,"in":"#e9643aff","op":"darker","out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"factor":0,"in":"#e9643aff","op":"lighter","out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"factor":0,"in":"#e9643aff","op":"darker","out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"factor":1,"in":"#e9643aff","op":"lighter","out":{"hex":"#020101ff","u16":[598,257,149,65535]}}, +{"factor":1,"in":"#e9643aff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#e9643aff","op":"lighter","out":{"hex":"#74321dff","u16":[29940,12850,7453,65535]}}, +{"factor":50,"in":"#e9643aff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#e9643aff","op":"lighter","out":{"hex":"#e76339ff","u16":[59288,25445,14758,65535]}}, +{"factor":99,"in":"#e9643aff","op":"darker","out":{"hex":"#eb653bff","u16":[60479,25956,15054,65535]}}, +{"factor":100,"in":"#e9643aff","op":"lighter","out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"factor":100,"in":"#e9643aff","op":"darker","out":{"hex":"#e9643aff","u16":[59881,25700,14906,65535]}}, +{"factor":101,"in":"#e9643aff","op":"lighter","out":{"hex":"#eb653bff","u16":[60479,25956,15054,65535]}}, +{"factor":101,"in":"#e9643aff","op":"darker","out":{"hex":"#e76339ff","u16":[59288,25445,14758,65535]}}, +{"factor":102,"in":"#e9643aff","op":"lighter","out":{"hex":"#ee663bff","u16":[61078,26213,15204,65535]}}, +{"factor":102,"in":"#e9643aff","op":"darker","out":{"hex":"#e46239ff","u16":[58706,25195,14613,65535]}}, +{"factor":110,"in":"#e9643aff","op":"lighter","out":{"hex":"#ff6e41ff","u16":[65535,28380,16647,65535]}}, +{"factor":110,"in":"#e9643aff","op":"darker","out":{"hex":"#d45b35ff","u16":[54437,23363,13550,65535]}}, +{"factor":120,"in":"#e9643aff","op":"lighter","out":{"hex":"#ff8058ff","u16":[65535,32931,22635,65535]}}, +{"factor":120,"in":"#e9643aff","op":"darker","out":{"hex":"#c25330ff","u16":[49900,21416,12421,65535]}}, +{"factor":150,"in":"#e9643aff","op":"lighter","out":{"hex":"#ffb59eff","u16":[65535,46584,40599,65535]}}, +{"factor":150,"in":"#e9643aff","op":"darker","out":{"hex":"#9b4327ff","u16":[39920,17133,9937,65535]}}, +{"factor":200,"in":"#e9643aff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#e9643aff","op":"darker","out":{"hex":"#74321dff","u16":[29940,12850,7453,65535]}}, +{"factor":300,"in":"#e9643aff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#e9643aff","op":"darker","out":{"hex":"#4e2113ff","u16":[19960,8566,4968,65535]}}, +{"factor":1000,"in":"#e9643aff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#e9643aff","op":"darker","out":{"hex":"#170a06ff","u16":[5988,2570,1491,65535]}}, +{"factor":-10,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"factor":-10,"in":"#e8cb2dff","op":"darker","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"factor":0,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"factor":0,"in":"#e8cb2dff","op":"darker","out":{"hex":"#e8cb2dff","u16":[59624,52171,11565,65535]}}, +{"factor":1,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#020200ff","u16":[596,522,116,65535]}}, +{"factor":1,"in":"#e8cb2dff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#746617ff","u16":[29812,26087,5783,65535]}}, +{"factor":50,"in":"#e8cb2dff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#e6c92dff","u16":[59033,51658,11451,65535]}}, +{"factor":99,"in":"#e8cb2dff","op":"darker","out":{"hex":"#eacd2dff","u16":[60220,52696,11681,65535]}}, +{"factor":100,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#e8cb2dff","u16":[59624,52175,11565,65535]}}, +{"factor":100,"in":"#e8cb2dff","op":"darker","out":{"hex":"#e8cb2dff","u16":[59624,52175,11565,65535]}}, +{"factor":101,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#eacd2dff","u16":[60220,52696,11681,65535]}}, +{"factor":101,"in":"#e8cb2dff","op":"darker","out":{"hex":"#e6c92dff","u16":[59033,51658,11451,65535]}}, +{"factor":102,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#edcf2eff","u16":[60816,53218,11797,65535]}}, +{"factor":102,"in":"#e8cb2dff","op":"darker","out":{"hex":"#e3c72cff","u16":[58454,51151,11338,65535]}}, +{"factor":110,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#ffdf32ff","u16":[65535,57355,12763,65535]}}, +{"factor":110,"in":"#e8cb2dff","op":"darker","out":{"hex":"#d3b929ff","u16":[54203,47431,10514,65535]}}, +{"factor":120,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#ffe349ff","u16":[65535,58279,18725,65535]}}, +{"factor":120,"in":"#e8cb2dff","op":"darker","out":{"hex":"#c1a926ff","u16":[49686,43479,9638,65535]}}, +{"factor":150,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#ffee8eff","u16":[65535,61052,36613,65535]}}, +{"factor":150,"in":"#e8cb2dff","op":"darker","out":{"hex":"#9b871eff","u16":[39749,34783,7710,65535]}}, +{"factor":200,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#e8cb2dff","op":"darker","out":{"hex":"#746617ff","u16":[29812,26087,5783,65535]}}, +{"factor":300,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#e8cb2dff","op":"darker","out":{"hex":"#4d440fff","u16":[19874,17391,3855,65535]}}, +{"factor":1000,"in":"#e8cb2dff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#e8cb2dff","op":"darker","out":{"hex":"#171404ff","u16":[5962,5217,1156,65535]}}, +{"factor":-10,"in":"#3dd425ff","op":"lighter","out":{"hex":"#3dd425ff","u16":[15677,54484,9509,65535]}}, +{"factor":-10,"in":"#3dd425ff","op":"darker","out":{"hex":"#3dd425ff","u16":[15677,54484,9509,65535]}}, +{"factor":0,"in":"#3dd425ff","op":"lighter","out":{"hex":"#3dd425ff","u16":[15677,54484,9509,65535]}}, +{"factor":0,"in":"#3dd425ff","op":"darker","out":{"hex":"#3dd425ff","u16":[15677,54484,9509,65535]}}, +{"factor":1,"in":"#3dd425ff","op":"lighter","out":{"hex":"#010200ff","u16":[157,544,95,65535]}}, +{"factor":1,"in":"#3dd425ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#3dd425ff","op":"lighter","out":{"hex":"#1f6a13ff","u16":[7839,27242,4755,65535]}}, +{"factor":50,"in":"#3dd425ff","op":"darker","out":{"hex":"#dbffd6ff","u16":[56334,65535,54871,65535]}}, +{"factor":99,"in":"#3dd425ff","op":"lighter","out":{"hex":"#3cd225ff","u16":[15523,53944,9415,65535]}}, +{"factor":99,"in":"#3dd425ff","op":"darker","out":{"hex":"#3ed625ff","u16":[15835,55028,9604,65535]}}, +{"factor":100,"in":"#3dd425ff","op":"lighter","out":{"hex":"#3dd425ff","u16":[15678,54484,9509,65535]}}, +{"factor":100,"in":"#3dd425ff","op":"darker","out":{"hex":"#3dd425ff","u16":[15678,54484,9509,65535]}}, +{"factor":101,"in":"#3dd425ff","op":"lighter","out":{"hex":"#3ed625ff","u16":[15835,55028,9604,65535]}}, +{"factor":101,"in":"#3dd425ff","op":"darker","out":{"hex":"#3cd225ff","u16":[15523,53944,9415,65535]}}, +{"factor":102,"in":"#3dd425ff","op":"lighter","out":{"hex":"#3ed826ff","u16":[15992,55573,9699,65535]}}, +{"factor":102,"in":"#3dd425ff","op":"darker","out":{"hex":"#3cd024ff","u16":[15371,53415,9323,65535]}}, +{"factor":110,"in":"#3dd425ff","op":"lighter","out":{"hex":"#43e929ff","u16":[17246,59932,10460,65535]}}, +{"factor":110,"in":"#3dd425ff","op":"darker","out":{"hex":"#37c122ff","u16":[14253,49530,8645,65535]}}, +{"factor":120,"in":"#3dd425ff","op":"lighter","out":{"hex":"#49fe2cff","u16":[18814,65380,11411,65535]}}, +{"factor":120,"in":"#3dd425ff","op":"darker","out":{"hex":"#33b11fff","u16":[13065,45403,7924,65535]}}, +{"factor":150,"in":"#3dd425ff","op":"lighter","out":{"hex":"#80ff6cff","u16":[32828,65535,27629,65535]}}, +{"factor":150,"in":"#3dd425ff","op":"darker","out":{"hex":"#298d19ff","u16":[10452,36322,6339,65535]}}, +{"factor":200,"in":"#3dd425ff","op":"lighter","out":{"hex":"#dbffd6ff","u16":[56334,65535,54871,65535]}}, +{"factor":200,"in":"#3dd425ff","op":"darker","out":{"hex":"#1f6a13ff","u16":[7839,27242,4755,65535]}}, +{"factor":300,"in":"#3dd425ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#3dd425ff","op":"darker","out":{"hex":"#14470cff","u16":[5226,18161,3170,65535]}}, +{"factor":1000,"in":"#3dd425ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#3dd425ff","op":"darker","out":{"hex":"#061504ff","u16":[1568,5448,951,65535]}}, +{"factor":-10,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"factor":-10,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"factor":0,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"factor":0,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00d3b8ff","u16":[0,54227,47288,65535]}}, +{"factor":1,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#000202ff","u16":[0,542,473,65535]}}, +{"factor":1,"in":"#00d3b8ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00695cff","u16":[0,27113,23643,65535]}}, +{"factor":50,"in":"#00d3b8ff","op":"darker","out":{"hex":"#a7fff4ff","u16":[42919,65535,62640,65535]}}, +{"factor":99,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00d1b6ff","u16":[0,53690,46818,65535]}}, +{"factor":99,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00d5baff","u16":[0,54769,47759,65535]}}, +{"factor":100,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00d3b8ff","u16":[0,54227,47286,65535]}}, +{"factor":100,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00d3b8ff","u16":[0,54227,47286,65535]}}, +{"factor":101,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00d5baff","u16":[0,54769,47759,65535]}}, +{"factor":101,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00d1b6ff","u16":[0,53690,46818,65535]}}, +{"factor":102,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00d7bcff","u16":[0,55311,48231,65535]}}, +{"factor":102,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00cfb4ff","u16":[0,53163,46358,65535]}}, +{"factor":110,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00e8caff","u16":[0,59649,52014,65535]}}, +{"factor":110,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00c0a7ff","u16":[0,49297,42987,65535]}}, +{"factor":120,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#00fdddff","u16":[0,65072,56743,65535]}}, +{"factor":120,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00b099ff","u16":[0,45189,39405,65535]}}, +{"factor":150,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#3dffe6ff","u16":[15805,65535,59170,65535]}}, +{"factor":150,"in":"#00d3b8ff","op":"darker","out":{"hex":"#008d7bff","u16":[0,36151,31524,65535]}}, +{"factor":200,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#a7fff4ff","u16":[42919,65535,62640,65535]}}, +{"factor":200,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00695cff","u16":[0,27113,23643,65535]}}, +{"factor":300,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#00d3b8ff","op":"darker","out":{"hex":"#00463dff","u16":[0,18075,15761,65535]}}, +{"factor":1000,"in":"#00d3b8ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#00d3b8ff","op":"darker","out":{"hex":"#001512ff","u16":[0,5422,4728,65535]}}, +{"factor":-10,"in":"#b875dcff","op":"lighter","out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"factor":-10,"in":"#b875dcff","op":"darker","out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"factor":0,"in":"#b875dcff","op":"lighter","out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"factor":0,"in":"#b875dcff","op":"darker","out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"factor":1,"in":"#b875dcff","op":"lighter","out":{"hex":"#020102ff","u16":[473,300,565,65535]}}, +{"factor":1,"in":"#b875dcff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#b875dcff","op":"lighter","out":{"hex":"#5c3b6eff","u16":[23644,15035,28270,65535]}}, +{"factor":50,"in":"#b875dcff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#b875dcff","op":"lighter","out":{"hex":"#b674daff","u16":[46820,29771,55980,65535]}}, +{"factor":99,"in":"#b875dcff","op":"darker","out":{"hex":"#ba76deff","u16":[47761,30370,57105,65535]}}, +{"factor":100,"in":"#b875dcff","op":"lighter","out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"factor":100,"in":"#b875dcff","op":"darker","out":{"hex":"#b875dcff","u16":[47288,30069,56540,65535]}}, +{"factor":101,"in":"#b875dcff","op":"lighter","out":{"hex":"#ba76deff","u16":[47761,30370,57105,65535]}}, +{"factor":101,"in":"#b875dcff","op":"darker","out":{"hex":"#b674daff","u16":[46820,29771,55980,65535]}}, +{"factor":102,"in":"#b875dcff","op":"lighter","out":{"hex":"#bc77e0ff","u16":[48234,30670,57670,65535]}}, +{"factor":102,"in":"#b875dcff","op":"darker","out":{"hex":"#b473d8ff","u16":[46361,29479,55431,65535]}}, +{"factor":110,"in":"#b875dcff","op":"lighter","out":{"hex":"#ca81f2ff","u16":[52017,33076,62194,65535]}}, +{"factor":110,"in":"#b875dcff","op":"darker","out":{"hex":"#a76ac8ff","u16":[42990,27336,51400,65535]}}, +{"factor":120,"in":"#b875dcff","op":"lighter","out":{"hex":"#d891ffff","u16":[55620,37166,65535,65535]}}, +{"factor":120,"in":"#b875dcff","op":"darker","out":{"hex":"#9961b7ff","u16":[39407,25057,47116,65535]}}, +{"factor":150,"in":"#b875dcff","op":"lighter","out":{"hex":"#efd3ffff","u16":[61548,54128,65535,65535]}}, +{"factor":150,"in":"#b875dcff","op":"darker","out":{"hex":"#7b4e93ff","u16":[31525,20046,37693,65535]}}, +{"factor":200,"in":"#b875dcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#b875dcff","op":"darker","out":{"hex":"#5c3b6eff","u16":[23644,15035,28270,65535]}}, +{"factor":300,"in":"#b875dcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#b875dcff","op":"darker","out":{"hex":"#3d2749ff","u16":[15762,10023,18846,65535]}}, +{"factor":1000,"in":"#b875dcff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#b875dcff","op":"darker","out":{"hex":"#120c16ff","u16":[4729,3007,5654,65535]}}, +{"factor":-10,"in":"#926ee4ff","op":"lighter","out":{"hex":"#926ee4ff","u16":[37522,28270,58596,65535]}}, +{"factor":-10,"in":"#926ee4ff","op":"darker","out":{"hex":"#926ee4ff","u16":[37522,28270,58596,65535]}}, +{"factor":0,"in":"#926ee4ff","op":"lighter","out":{"hex":"#926ee4ff","u16":[37522,28270,58596,65535]}}, +{"factor":0,"in":"#926ee4ff","op":"darker","out":{"hex":"#926ee4ff","u16":[37522,28270,58596,65535]}}, +{"factor":1,"in":"#926ee4ff","op":"lighter","out":{"hex":"#010102ff","u16":[375,282,585,65535]}}, +{"factor":1,"in":"#926ee4ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#926ee4ff","op":"lighter","out":{"hex":"#493772ff","u16":[18762,14135,29298,65535]}}, +{"factor":50,"in":"#926ee4ff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":99,"in":"#926ee4ff","op":"lighter","out":{"hex":"#916de2ff","u16":[37153,27990,58015,65535]}}, +{"factor":99,"in":"#926ee4ff","op":"darker","out":{"hex":"#936fe6ff","u16":[37899,28552,59181,65535]}}, +{"factor":100,"in":"#926ee4ff","op":"lighter","out":{"hex":"#926ee4ff","u16":[37525,28270,58596,65535]}}, +{"factor":100,"in":"#926ee4ff","op":"darker","out":{"hex":"#926ee4ff","u16":[37525,28270,58596,65535]}}, +{"factor":101,"in":"#926ee4ff","op":"lighter","out":{"hex":"#936fe6ff","u16":[37899,28552,59181,65535]}}, +{"factor":101,"in":"#926ee4ff","op":"darker","out":{"hex":"#916de2ff","u16":[37153,27990,58015,65535]}}, +{"factor":102,"in":"#926ee4ff","op":"lighter","out":{"hex":"#9570e9ff","u16":[38275,28835,59767,65535]}}, +{"factor":102,"in":"#926ee4ff","op":"darker","out":{"hex":"#8f6ce0ff","u16":[36789,27716,57447,65535]}}, +{"factor":110,"in":"#926ee4ff","op":"lighter","out":{"hex":"#a179fbff","u16":[41277,31097,64455,65535]}}, +{"factor":110,"in":"#926ee4ff","op":"darker","out":{"hex":"#8564cfff","u16":[34113,25700,53269,65535]}}, +{"factor":120,"in":"#926ee4ff","op":"lighter","out":{"hex":"#b08effff","u16":[45290,36398,65535,65535]}}, +{"factor":120,"in":"#926ee4ff","op":"darker","out":{"hex":"#7a5cbeff","u16":[31271,23559,48830,65535]}}, +{"factor":150,"in":"#926ee4ff","op":"lighter","out":{"hex":"#e0d2ffff","u16":[57504,53977,65535,65535]}}, +{"factor":150,"in":"#926ee4ff","op":"darker","out":{"hex":"#614998ff","u16":[25016,18847,39064,65535]}}, +{"factor":200,"in":"#926ee4ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":200,"in":"#926ee4ff","op":"darker","out":{"hex":"#493772ff","u16":[18762,14135,29298,65535]}}, +{"factor":300,"in":"#926ee4ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#926ee4ff","op":"darker","out":{"hex":"#31254cff","u16":[12508,9423,19532,65535]}}, +{"factor":1000,"in":"#926ee4ff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#926ee4ff","op":"darker","out":{"hex":"#0f0b17ff","u16":[3752,2827,5859,65535]}}, +{"factor":-10,"in":"#686b6fff","op":"lighter","out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"factor":-10,"in":"#686b6fff","op":"darker","out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"factor":0,"in":"#686b6fff","op":"lighter","out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"factor":0,"in":"#686b6fff","op":"darker","out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"factor":1,"in":"#686b6fff","op":"lighter","out":{"hex":"#010101ff","u16":[267,275,285,65535]}}, +{"factor":1,"in":"#686b6fff","op":"darker","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":50,"in":"#686b6fff","op":"lighter","out":{"hex":"#343537ff","u16":[13363,13749,14263,65535]}}, +{"factor":50,"in":"#686b6fff","op":"darker","out":{"hex":"#d0d6deff","u16":[53456,54998,57054,65535]}}, +{"factor":99,"in":"#686b6fff","op":"lighter","out":{"hex":"#676a6eff","u16":[26463,27226,28244,65535]}}, +{"factor":99,"in":"#686b6fff","op":"darker","out":{"hex":"#696c70ff","u16":[26995,27774,28812,65535]}}, +{"factor":100,"in":"#686b6fff","op":"lighter","out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"factor":100,"in":"#686b6fff","op":"darker","out":{"hex":"#686b6fff","u16":[26728,27499,28527,65535]}}, +{"factor":101,"in":"#686b6fff","op":"lighter","out":{"hex":"#696c70ff","u16":[26995,27774,28812,65535]}}, +{"factor":101,"in":"#686b6fff","op":"darker","out":{"hex":"#676a6eff","u16":[26463,27226,28244,65535]}}, +{"factor":102,"in":"#686b6fff","op":"lighter","out":{"hex":"#6a6d71ff","u16":[27262,28048,29097,65535]}}, +{"factor":102,"in":"#686b6fff","op":"darker","out":{"hex":"#66696dff","u16":[26203,26959,27967,65535]}}, +{"factor":110,"in":"#686b6fff","op":"lighter","out":{"hex":"#72767aff","u16":[29400,30248,31379,65535]}}, +{"factor":110,"in":"#686b6fff","op":"darker","out":{"hex":"#5f6165ff","u16":[24298,24998,25933,65535]}}, +{"factor":120,"in":"#686b6fff","op":"lighter","out":{"hex":"#7d8085ff","u16":[32073,32998,34232,65535]}}, +{"factor":120,"in":"#686b6fff","op":"darker","out":{"hex":"#57595cff","u16":[22273,22915,23772,65535]}}, +{"factor":150,"in":"#686b6fff","op":"lighter","out":{"hex":"#9ca0a6ff","u16":[40091,41248,42790,65535]}}, +{"factor":150,"in":"#686b6fff","op":"darker","out":{"hex":"#45474aff","u16":[17819,18333,19018,65535]}}, +{"factor":200,"in":"#686b6fff","op":"lighter","out":{"hex":"#d0d6deff","u16":[53456,54998,57054,65535]}}, +{"factor":200,"in":"#686b6fff","op":"darker","out":{"hex":"#343537ff","u16":[13363,13749,14263,65535]}}, +{"factor":300,"in":"#686b6fff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":300,"in":"#686b6fff","op":"darker","out":{"hex":"#232425ff","u16":[8909,9166,9509,65535]}}, +{"factor":1000,"in":"#686b6fff","op":"lighter","out":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"factor":1000,"in":"#686b6fff","op":"darker","out":{"hex":"#0a0b0bff","u16":[2672,2749,2852,65535]}}, +{"factor":-10,"in":"#3daee980","op":"lighter","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"factor":-10,"in":"#3daee980","op":"darker","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"factor":0,"in":"#3daee980","op":"lighter","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"factor":0,"in":"#3daee980","op":"darker","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"factor":1,"in":"#3daee980","op":"lighter","out":{"hex":"#01020280","u16":[157,447,598,32896]}}, +{"factor":1,"in":"#3daee980","op":"darker","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"factor":50,"in":"#3daee980","op":"lighter","out":{"hex":"#1e577480","u16":[7838,22359,29940,32896]}}, +{"factor":50,"in":"#3daee980","op":"darker","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"factor":99,"in":"#3daee980","op":"lighter","out":{"hex":"#3cace780","u16":[15522,44276,59288,32896]}}, +{"factor":99,"in":"#3daee980","op":"darker","out":{"hex":"#3eb0eb80","u16":[15833,45166,60479,32896]}}, +{"factor":100,"in":"#3daee980","op":"lighter","out":{"hex":"#3daee980","u16":[15677,44719,59881,32896]}}, +{"factor":100,"in":"#3daee980","op":"darker","out":{"hex":"#3daee980","u16":[15677,44719,59881,32896]}}, +{"factor":101,"in":"#3daee980","op":"lighter","out":{"hex":"#3eb0eb80","u16":[15833,45166,60479,32896]}}, +{"factor":101,"in":"#3daee980","op":"darker","out":{"hex":"#3cace780","u16":[15522,44276,59288,32896]}}, +{"factor":102,"in":"#3daee980","op":"lighter","out":{"hex":"#3eb1ee80","u16":[15990,45613,61078,32896]}}, +{"factor":102,"in":"#3daee980","op":"darker","out":{"hex":"#3cabe480","u16":[15369,43841,58706,32896]}}, +{"factor":110,"in":"#3daee980","op":"lighter","out":{"hex":"#44bfff80","u16":[17491,49056,65535,32896]}}, +{"factor":110,"in":"#3daee980","op":"darker","out":{"hex":"#379ed480","u16":[14252,40653,54437,32896]}}, +{"factor":120,"in":"#3daee980","op":"lighter","out":{"hex":"#5bc7ff80","u16":[23479,51110,65535,32896]}}, +{"factor":120,"in":"#3daee980","op":"darker","out":{"hex":"#3391c280","u16":[13064,37265,49900,32896]}}, +{"factor":150,"in":"#3daee980","op":"lighter","out":{"hex":"#a1dfff80","u16":[41443,57271,65535,32896]}}, +{"factor":150,"in":"#3daee980","op":"darker","out":{"hex":"#29749b80","u16":[10451,29812,39920,32896]}}, +{"factor":200,"in":"#3daee980","op":"lighter","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"factor":200,"in":"#3daee980","op":"darker","out":{"hex":"#1e577480","u16":[7838,22359,29940,32896]}}, +{"factor":300,"in":"#3daee980","op":"lighter","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"factor":300,"in":"#3daee980","op":"darker","out":{"hex":"#143a4e80","u16":[5226,14906,19960,32896]}}, +{"factor":1000,"in":"#3daee980","op":"lighter","out":{"hex":"#ffffff80","u16":[65535,65535,65535,32896]}}, +{"factor":1000,"in":"#3daee980","op":"darker","out":{"hex":"#06111780","u16":[1568,4472,5988,32896]}}, +{"factor":-10,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":-10,"in":"#ffffff00","op":"darker","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":0,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":0,"in":"#ffffff00","op":"darker","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":1,"in":"#ffffff00","op":"lighter","out":{"hex":"#03030300","u16":[655,655,655,0]}}, +{"factor":1,"in":"#ffffff00","op":"darker","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":50,"in":"#ffffff00","op":"lighter","out":{"hex":"#7f7f7f00","u16":[32767,32767,32767,0]}}, +{"factor":50,"in":"#ffffff00","op":"darker","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":99,"in":"#ffffff00","op":"lighter","out":{"hex":"#fcfcfc00","u16":[64886,64886,64886,0]}}, +{"factor":99,"in":"#ffffff00","op":"darker","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":100,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":100,"in":"#ffffff00","op":"darker","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":101,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":101,"in":"#ffffff00","op":"darker","out":{"hex":"#fcfcfc00","u16":[64886,64886,64886,0]}}, +{"factor":102,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":102,"in":"#ffffff00","op":"darker","out":{"hex":"#fafafa00","u16":[64250,64250,64250,0]}}, +{"factor":110,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":110,"in":"#ffffff00","op":"darker","out":{"hex":"#e8e8e800","u16":[59577,59577,59577,0]}}, +{"factor":120,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":120,"in":"#ffffff00","op":"darker","out":{"hex":"#d4d4d400","u16":[54612,54612,54612,0]}}, +{"factor":150,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":150,"in":"#ffffff00","op":"darker","out":{"hex":"#aaaaaa00","u16":[43690,43690,43690,0]}}, +{"factor":200,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":200,"in":"#ffffff00","op":"darker","out":{"hex":"#7f7f7f00","u16":[32767,32767,32767,0]}}, +{"factor":300,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":300,"in":"#ffffff00","op":"darker","out":{"hex":"#55555500","u16":[21845,21845,21845,0]}}, +{"factor":1000,"in":"#ffffff00","op":"lighter","out":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"factor":1000,"in":"#ffffff00","op":"darker","out":{"hex":"#19191900","u16":[6553,6553,6553,0]}}, +{"factor":-10,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":-10,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":0,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":0,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":1,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":1,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":50,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":50,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":99,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":99,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":100,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":100,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":101,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":101,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":102,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":102,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":110,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":110,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":120,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":120,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":150,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":150,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":200,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":200,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":300,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":300,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":1000,"in":"#00000080","op":"lighter","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":1000,"in":"#00000080","op":"darker","out":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"factor":-10,"in":"#ff000033","op":"lighter","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"factor":-10,"in":"#ff000033","op":"darker","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"factor":0,"in":"#ff000033","op":"lighter","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"factor":0,"in":"#ff000033","op":"darker","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"factor":1,"in":"#ff000033","op":"lighter","out":{"hex":"#03000033","u16":[655,0,0,13107]}}, +{"factor":1,"in":"#ff000033","op":"darker","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"factor":50,"in":"#ff000033","op":"lighter","out":{"hex":"#7f000033","u16":[32767,0,0,13107]}}, +{"factor":50,"in":"#ff000033","op":"darker","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"factor":99,"in":"#ff000033","op":"lighter","out":{"hex":"#fc000033","u16":[64886,0,0,13107]}}, +{"factor":99,"in":"#ff000033","op":"darker","out":{"hex":"#ff030333","u16":[65535,655,655,13107]}}, +{"factor":100,"in":"#ff000033","op":"lighter","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"factor":100,"in":"#ff000033","op":"darker","out":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"factor":101,"in":"#ff000033","op":"lighter","out":{"hex":"#ff030333","u16":[65535,655,655,13107]}}, +{"factor":101,"in":"#ff000033","op":"darker","out":{"hex":"#fc000033","u16":[64886,0,0,13107]}}, +{"factor":102,"in":"#ff000033","op":"lighter","out":{"hex":"#ff050533","u16":[65535,1310,1310,13107]}}, +{"factor":102,"in":"#ff000033","op":"darker","out":{"hex":"#fa000033","u16":[64250,0,0,13107]}}, +{"factor":110,"in":"#ff000033","op":"lighter","out":{"hex":"#ff191933","u16":[65535,6553,6553,13107]}}, +{"factor":110,"in":"#ff000033","op":"darker","out":{"hex":"#e8000033","u16":[59577,0,0,13107]}}, +{"factor":120,"in":"#ff000033","op":"lighter","out":{"hex":"#ff333333","u16":[65535,13107,13107,13107]}}, +{"factor":120,"in":"#ff000033","op":"darker","out":{"hex":"#d4000033","u16":[54612,0,0,13107]}}, +{"factor":150,"in":"#ff000033","op":"lighter","out":{"hex":"#ff7f7f33","u16":[65535,32767,32767,13107]}}, +{"factor":150,"in":"#ff000033","op":"darker","out":{"hex":"#aa000033","u16":[43690,0,0,13107]}}, +{"factor":200,"in":"#ff000033","op":"lighter","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"factor":200,"in":"#ff000033","op":"darker","out":{"hex":"#7f000033","u16":[32767,0,0,13107]}}, +{"factor":300,"in":"#ff000033","op":"lighter","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"factor":300,"in":"#ff000033","op":"darker","out":{"hex":"#55000033","u16":[21845,0,0,13107]}}, +{"factor":1000,"in":"#ff000033","op":"lighter","out":{"hex":"#ffffff33","u16":[65535,65535,65535,13107]}}, +{"factor":1000,"in":"#ff000033","op":"darker","out":{"hex":"#19000033","u16":[6553,0,0,13107]}} +], +"qt_colorspace": [ +{"from_linear":{"hex":"#000000ff","u16":[0,0,0,65535]},"in":"#000000ff","to_linear":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"from_linear":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"in":"#ffffffff","to_linear":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"from_linear":{"hex":"#0d0d0dff","u16":[3266,3266,3266,65535]},"in":"#010101ff","to_linear":{"hex":"#000000ff","u16":[20,20,20,65535]}}, +{"from_linear":{"hex":"#161616ff","u16":[5567,5567,5567,65535]},"in":"#020202ff","to_linear":{"hex":"#000000ff","u16":[40,40,40,65535]}}, +{"from_linear":{"hex":"#222222ff","u16":[8638,8638,8638,65535]},"in":"#040404ff","to_linear":{"hex":"#000000ff","u16":[80,80,80,65535]}}, +{"from_linear":{"hex":"#323232ff","u16":[12737,12737,12737,65535]},"in":"#080808ff","to_linear":{"hex":"#010101ff","u16":[159,159,159,65535]}}, +{"from_linear":{"hex":"#474747ff","u16":[18209,18209,18209,65535]},"in":"#101010ff","to_linear":{"hex":"#010101ff","u16":[340,340,340,65535]}}, +{"from_linear":{"hex":"#636363ff","u16":[25513,25513,25513,65535]},"in":"#202020ff","to_linear":{"hex":"#040404ff","u16":[947,947,947,65535]}}, +{"from_linear":{"hex":"#898989ff","u16":[35262,35262,35262,65535]},"in":"#404040ff","to_linear":{"hex":"#0d0d0dff","u16":[3360,3360,3360,65535]}}, +{"from_linear":{"hex":"#bbbbbbff","u16":[48107,48107,48107,65535]},"in":"#7f7f7fff","to_linear":{"hex":"#363636ff","u16":[13909,13909,13909,65535]}}, +{"from_linear":{"hex":"#bcbcbcff","u16":[48276,48276,48276,65535]},"in":"#808080ff","to_linear":{"hex":"#373737ff","u16":[14146,14146,14146,65535]}}, +{"from_linear":{"hex":"#bdbdbdff","u16":[48445,48445,48445,65535]},"in":"#818181ff","to_linear":{"hex":"#383838ff","u16":[14387,14387,14387,65535]}}, +{"from_linear":{"hex":"#e0e0e0ff","u16":[57691,57691,57691,65535]},"in":"#bfbfbfff","to_linear":{"hex":"#858585ff","u16":[34143,34143,34143,65535]}}, +{"from_linear":{"hex":"#e1e1e1ff","u16":[57825,57825,57825,65535]},"in":"#c0c0c0ff","to_linear":{"hex":"#868686ff","u16":[34544,34544,34544,65535]}}, +{"from_linear":{"hex":"#fefefeff","u16":[65309,65309,65309,65535]},"in":"#fdfdfdff","to_linear":{"hex":"#fafafaff","u16":[64372,64372,64372,65535]}}, +{"from_linear":{"hex":"#ffffffff","u16":[65422,65422,65422,65535]},"in":"#fefefeff","to_linear":{"hex":"#fdfdfdff","u16":[64952,64952,64952,65535]}}, +{"from_linear":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"in":"#ff0000ff","to_linear":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"from_linear":{"hex":"#00ff00ff","u16":[0,65535,0,65535]},"in":"#00ff00ff","to_linear":{"hex":"#00ff00ff","u16":[0,65535,0,65535]}}, +{"from_linear":{"f":[-3.5762786865234375e-07,0,1,1],"hex":"#0000ffff","spec":"ExtendedRgb","u16":[0,0,65535,65535]},"in":"#0000ffff","to_linear":{"f":[0,0,1,1],"hex":"#0000ffff","spec":"ExtendedRgb","u16":[0,0,65535,65535]}}, +{"from_linear":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]},"in":"#ffff00ff","to_linear":{"hex":"#ffff00ff","u16":[65535,65535,0,65535]}}, +{"from_linear":{"hex":"#00ffffff","u16":[0,65535,65535,65535]},"in":"#00ffffff","to_linear":{"hex":"#00ffffff","u16":[0,65535,65535,65535]}}, +{"from_linear":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]},"in":"#ff00ffff","to_linear":{"hex":"#ff00ffff","u16":[65535,0,65535,65535]}}, +{"from_linear":{"hex":"#bc0000ff","u16":[48276,0,0,65535]},"in":"#800000ff","to_linear":{"hex":"#370000ff","u16":[14146,0,0,65535]}}, +{"from_linear":{"hex":"#00bc00ff","u16":[0,48276,0,65535]},"in":"#008000ff","to_linear":{"hex":"#003700ff","u16":[0,14146,0,65535]}}, +{"from_linear":{"f":[-1.7881393432617188e-07,0,0.73681640625,1],"hex":"#0000bcff","spec":"ExtendedRgb","u16":[0,0,48287,65535]},"in":"#000080ff","to_linear":{"f":[0,0,0.2158203125,1],"hex":"#000037ff","spec":"ExtendedRgb","u16":[0,0,14144,65535]}}, +{"from_linear":{"hex":"#ffbcbcff","u16":[65535,48276,48276,65535]},"in":"#ff8080ff","to_linear":{"hex":"#ff3737ff","u16":[65535,14146,14146,65535]}}, +{"from_linear":{"hex":"#bcffbcff","u16":[48276,65535,48276,65535]},"in":"#80ff80ff","to_linear":{"hex":"#37ff37ff","u16":[14146,65535,14146,65535]}}, +{"from_linear":{"hex":"#bcbcffff","u16":[48276,48276,65535,65535]},"in":"#8080ffff","to_linear":{"hex":"#3737ffff","u16":[14146,14146,65535,65535]}}, +{"from_linear":{"hex":"#1c160dff","u16":[7255,5567,3266,65535]},"in":"#030201ff","to_linear":{"hex":"#000000ff","u16":[60,40,20,65535]}}, +{"from_linear":{"hex":"#fffffeff","u16":[65535,65422,65309,65535]},"in":"#fffefdff","to_linear":{"hex":"#fffdfaff","u16":[65535,64952,64372,65535]}}, +{"from_linear":{"hex":"#bbbcbdff","u16":[48107,48276,48445,65535]},"in":"#7f8081ff","to_linear":{"hex":"#363738ff","u16":[13909,14146,14387,65535]}}, +{"from_linear":{"hex":"#0d0000ff","u16":[3266,0,0,65535]},"in":"#010000ff","to_linear":{"hex":"#000000ff","u16":[20,0,0,65535]}}, +{"from_linear":{"f":[0,0,0.049835205078125,1],"hex":"#00000dff","spec":"ExtendedRgb","u16":[0,0,3266,65535]},"in":"#000001ff","to_linear":{"f":[0,0,0.00030350685119628906,1],"hex":"#000000ff","spec":"ExtendedRgb","u16":[0,0,20,65535]}}, +{"from_linear":{"hex":"#ffffffff","u16":[65422,65535,65422,65535]},"in":"#fefffeff","to_linear":{"hex":"#fdfffdff","u16":[64952,65535,64952,65535]}}, +{"from_linear":{"hex":"#86d7f5ff","u16":[34492,55356,62984,65535]},"in":"#3daee9ff","to_linear":{"hex":"#0c6cd0ff","u16":[3058,27739,53401,65535]}}, +{"from_linear":{"hex":"#686c70ff","u16":[26620,27674,28680,65535]},"in":"#232629ff","to_linear":{"hex":"#040506ff","u16":[1101,1270,1453,65535]}}, +{"from_linear":{"hex":"#f8f8f9ff","u16":[63693,63810,63927,65535]},"in":"#eff0f1ff","to_linear":{"hex":"#dcdee0ff","u16":[56567,57105,57646,65535]}}, +{"from_linear":{"hex":"#fefefeff","u16":[65195,65195,65195,65535]},"in":"#fcfcfcff","to_linear":{"hex":"#f8f8f8ff","u16":[63795,63795,63795,65535]}}, +{"from_linear":{"hex":"#797f84ff","u16":[31169,32606,33967,65535]},"in":"#31363bff","to_linear":{"hex":"#08090bff","u16":[2013,2418,2866,65535]}}, +{"from_linear":{"hex":"#5c6063ff","u16":[23523,24740,25513,65535]},"in":"#1b1e20ff","to_linear":{"hex":"#030304ff","u16":[718,851,947,65535]}}, +{"from_linear":{"hex":"#ee8d9bff","u16":[61163,36256,39708,65535]},"in":"#da4453ff","to_linear":{"hex":"#b30f16ff","u16":[45947,3788,5669,65535]}}, +{"from_linear":{"hex":"#6dd7a5ff","u16":[28014,55356,42416,65535]},"in":"#27ae60ff","to_linear":{"hex":"#056c1eff","u16":[1330,27739,7666,65535]}}, +{"from_linear":{"hex":"#fbb400ff","u16":[64508,46191,0,65535]},"in":"#f67400ff","to_linear":{"hex":"#eb2d00ff","u16":[60396,11446,0,65535]}}, +{"from_linear":{"hex":"#70bcddff","u16":[28680,48276,56882,65535]},"in":"#2980b9ff","to_linear":{"hex":"#06377cff","u16":[1453,14146,31794,65535]}}, +{"from_linear":{"hex":"#bbc4c4ff","u16":[48107,50250,50410,65535]},"in":"#7f8c8dff","to_linear":{"hex":"#364344ff","u16":[13909,17187,17456,65535]}}, +{"from_linear":{"hex":"#cd9fdcff","u16":[52583,40987,56471,65535]},"in":"#9b59b6ff","to_linear":{"hex":"#541977ff","u16":[21481,6547,30656,65535]}}, +{"from_linear":{"hex":"#5fcbfaff","u16":[24342,52280,64160,65535]},"in":"#1d99f3ff","to_linear":{"hex":"#0351e5ff","u16":[805,20876,58737,65535]}}, +{"from_linear":{"hex":"#f0f1f2ff","u16":[61656,61900,62143,65535]},"in":"#dee0e2ff","to_linear":{"hex":"#babec2ff","u16":[47871,48850,49841,65535]}}, +{"from_linear":{"hex":"#707378ff","u16":[28680,29644,30872,65535]},"in":"#292c30ff","to_linear":{"hex":"#060608ff","u16":[1453,1651,1937,65535]}}, +{"from_linear":{"hex":"#f583ccff","u16":[62984,33700,52432,65535]},"in":"#e93a9aff","to_linear":{"hex":"#d00b52ff","u16":[53401,2773,21177,65535]}}, +{"from_linear":{"hex":"#f5869fff","u16":[62984,34492,40777,65535]},"in":"#e93d58ff","to_linear":{"hex":"#d00c19ff","u16":[53401,3058,6395,65535]}}, +{"from_linear":{"hex":"#f5a883ff","u16":[62984,43205,33700,65535]},"in":"#e9643aff","to_linear":{"hex":"#d0200bff","u16":[53401,8352,2773,65535]}}, +{"from_linear":{"hex":"#f5e775ff","u16":[62865,59268,29957,65535]},"in":"#e8cb2dff","to_linear":{"hex":"#ce9807ff","u16":[52884,39138,1720,65535]}}, +{"from_linear":{"hex":"#86eb6aff","u16":[34492,60414,27328,65535]},"in":"#3dd425ff","to_linear":{"hex":"#0ca805ff","u16":[3058,43147,1212,65535]}}, +{"from_linear":{"hex":"#00ebddff","u16":[0,60288,56745,65535]},"in":"#00d3b8ff","to_linear":{"hex":"#00a67aff","u16":[0,42690,31412,65535]}}, +{"from_linear":{"hex":"#ddb4efff","u16":[56745,46370,61410,65535]},"in":"#b875dcff","to_linear":{"hex":"#7a2db7ff","u16":[31412,11658,46903,65535]}}, +{"from_linear":{"hex":"#c7aff3ff","u16":[51200,45101,62385,65535]},"in":"#926ee4ff","to_linear":{"hex":"#4928c6ff","u16":[18837,10219,50844,65535]}}, +{"from_linear":{"hex":"#abadb0ff","u16":[43976,44543,45285,65535]},"in":"#686b6fff","to_linear":{"hex":"#232529ff","u16":[9072,9635,10417,65535]}}, +{"from_linear":{"hex":"#86d7f580","u16":[34492,55356,62984,32896]},"in":"#3daee980","to_linear":{"hex":"#0c6cd080","u16":[3058,27739,53401,32896]}}, +{"from_linear":{"hex":"#ffffff00","u16":[65535,65535,65535,0]},"in":"#ffffff00","to_linear":{"hex":"#ffffff00","u16":[65535,65535,65535,0]}}, +{"from_linear":{"hex":"#00000080","u16":[0,0,0,32896]},"in":"#00000080","to_linear":{"hex":"#00000080","u16":[0,0,0,32896]}}, +{"from_linear":{"hex":"#ff000033","u16":[65535,0,0,13107]},"in":"#ff000033","to_linear":{"hex":"#ff000033","u16":[65535,0,0,13107]}}, +{"from_linear":{"hex":"#000000ff","u16":[0,0,0,65535]},"in":"#000000ff","to_linear":{"hex":"#000000ff","u16":[0,0,0,65535]}}, +{"from_linear":{"hex":"#0d0d0dff","u16":[3266,3266,3266,65535]},"in":"#010101ff","to_linear":{"hex":"#000000ff","u16":[20,20,20,65535]}}, +{"from_linear":{"hex":"#161616ff","u16":[5567,5567,5567,65535]},"in":"#020202ff","to_linear":{"hex":"#000000ff","u16":[40,40,40,65535]}}, +{"from_linear":{"hex":"#1c1c1cff","u16":[7255,7255,7255,65535]},"in":"#030303ff","to_linear":{"hex":"#000000ff","u16":[60,60,60,65535]}}, +{"from_linear":{"hex":"#222222ff","u16":[8638,8638,8638,65535]},"in":"#040404ff","to_linear":{"hex":"#000000ff","u16":[80,80,80,65535]}}, +{"from_linear":{"hex":"#262626ff","u16":[9831,9831,9831,65535]},"in":"#050505ff","to_linear":{"hex":"#000000ff","u16":[99,99,99,65535]}}, +{"from_linear":{"hex":"#2a2a2aff","u16":[10891,10891,10891,65535]},"in":"#060606ff","to_linear":{"hex":"#000000ff","u16":[119,119,119,65535]}}, +{"from_linear":{"hex":"#2e2e2eff","u16":[11853,11853,11853,65535]},"in":"#070707ff","to_linear":{"hex":"#010101ff","u16":[139,139,139,65535]}}, +{"from_linear":{"hex":"#323232ff","u16":[12737,12737,12737,65535]},"in":"#080808ff","to_linear":{"hex":"#010101ff","u16":[159,159,159,65535]}}, +{"from_linear":{"hex":"#353535ff","u16":[13559,13559,13559,65535]},"in":"#090909ff","to_linear":{"hex":"#010101ff","u16":[179,179,179,65535]}}, +{"from_linear":{"hex":"#383838ff","u16":[14329,14329,14329,65535]},"in":"#0a0a0aff","to_linear":{"hex":"#010101ff","u16":[199,199,199,65535]}}, +{"from_linear":{"hex":"#3b3b3bff","u16":[15056,15056,15056,65535]},"in":"#0b0b0bff","to_linear":{"hex":"#010101ff","u16":[219,219,219,65535]}}, +{"from_linear":{"hex":"#3d3d3dff","u16":[15745,15745,15745,65535]},"in":"#0c0c0cff","to_linear":{"hex":"#010101ff","u16":[241,241,241,65535]}}, +{"from_linear":{"hex":"#404040ff","u16":[16401,16401,16401,65535]},"in":"#0d0d0dff","to_linear":{"hex":"#010101ff","u16":[264,264,264,65535]}}, +{"from_linear":{"hex":"#424242ff","u16":[17028,17028,17028,65535]},"in":"#0e0e0eff","to_linear":{"hex":"#010101ff","u16":[288,288,288,65535]}}, +{"from_linear":{"hex":"#454545ff","u16":[17630,17630,17630,65535]},"in":"#0f0f0fff","to_linear":{"hex":"#010101ff","u16":[313,313,313,65535]}}, +{"from_linear":{"hex":"#474747ff","u16":[18209,18209,18209,65535]},"in":"#101010ff","to_linear":{"hex":"#010101ff","u16":[340,340,340,65535]}}, +{"from_linear":{"hex":"#494949ff","u16":[18767,18767,18767,65535]},"in":"#111111ff","to_linear":{"hex":"#010101ff","u16":[367,367,367,65535]}}, +{"from_linear":{"hex":"#4b4b4bff","u16":[19306,19306,19306,65535]},"in":"#121212ff","to_linear":{"hex":"#020202ff","u16":[396,396,396,65535]}}, +{"from_linear":{"hex":"#4d4d4dff","u16":[19828,19828,19828,65535]},"in":"#131313ff","to_linear":{"hex":"#020202ff","u16":[427,427,427,65535]}}, +{"from_linear":{"hex":"#4f4f4fff","u16":[20334,20334,20334,65535]},"in":"#141414ff","to_linear":{"hex":"#020202ff","u16":[458,458,458,65535]}}, +{"from_linear":{"hex":"#515151ff","u16":[20826,20826,20826,65535]},"in":"#151515ff","to_linear":{"hex":"#020202ff","u16":[491,491,491,65535]}}, +{"from_linear":{"hex":"#535353ff","u16":[21304,21304,21304,65535]},"in":"#161616ff","to_linear":{"hex":"#020202ff","u16":[526,526,526,65535]}}, +{"from_linear":{"hex":"#555555ff","u16":[21769,21769,21769,65535]},"in":"#171717ff","to_linear":{"hex":"#020202ff","u16":[562,562,562,65535]}}, +{"from_linear":{"hex":"#565656ff","u16":[22223,22223,22223,65535]},"in":"#181818ff","to_linear":{"hex":"#020202ff","u16":[599,599,599,65535]}}, +{"from_linear":{"hex":"#585858ff","u16":[22667,22667,22667,65535]},"in":"#191919ff","to_linear":{"hex":"#020202ff","u16":[637,637,637,65535]}}, +{"from_linear":{"hex":"#5a5a5aff","u16":[23099,23099,23099,65535]},"in":"#1a1a1aff","to_linear":{"hex":"#030303ff","u16":[677,677,677,65535]}}, +{"from_linear":{"hex":"#5c5c5cff","u16":[23523,23523,23523,65535]},"in":"#1b1b1bff","to_linear":{"hex":"#030303ff","u16":[718,718,718,65535]}}, +{"from_linear":{"hex":"#5d5d5dff","u16":[23937,23937,23937,65535]},"in":"#1c1c1cff","to_linear":{"hex":"#030303ff","u16":[761,761,761,65535]}}, +{"from_linear":{"hex":"#5f5f5fff","u16":[24342,24342,24342,65535]},"in":"#1d1d1dff","to_linear":{"hex":"#030303ff","u16":[805,805,805,65535]}}, +{"from_linear":{"hex":"#606060ff","u16":[24740,24740,24740,65535]},"in":"#1e1e1eff","to_linear":{"hex":"#030303ff","u16":[851,851,851,65535]}}, +{"from_linear":{"hex":"#626262ff","u16":[25130,25130,25130,65535]},"in":"#1f1f1fff","to_linear":{"hex":"#030303ff","u16":[898,898,898,65535]}}, +{"from_linear":{"hex":"#636363ff","u16":[25513,25513,25513,65535]},"in":"#202020ff","to_linear":{"hex":"#040404ff","u16":[947,947,947,65535]}}, +{"from_linear":{"hex":"#656565ff","u16":[25888,25888,25888,65535]},"in":"#212121ff","to_linear":{"hex":"#040404ff","u16":[997,997,997,65535]}}, +{"from_linear":{"hex":"#666666ff","u16":[26257,26257,26257,65535]},"in":"#222222ff","to_linear":{"hex":"#040404ff","u16":[1048,1048,1048,65535]}}, +{"from_linear":{"hex":"#686868ff","u16":[26620,26620,26620,65535]},"in":"#232323ff","to_linear":{"hex":"#040404ff","u16":[1101,1101,1101,65535]}}, +{"from_linear":{"hex":"#696969ff","u16":[26977,26977,26977,65535]},"in":"#242424ff","to_linear":{"hex":"#040404ff","u16":[1156,1156,1156,65535]}}, +{"from_linear":{"hex":"#6a6a6aff","u16":[27328,27328,27328,65535]},"in":"#252525ff","to_linear":{"hex":"#050505ff","u16":[1212,1212,1212,65535]}}, +{"from_linear":{"hex":"#6c6c6cff","u16":[27674,27674,27674,65535]},"in":"#262626ff","to_linear":{"hex":"#050505ff","u16":[1270,1270,1270,65535]}}, +{"from_linear":{"hex":"#6d6d6dff","u16":[28014,28014,28014,65535]},"in":"#272727ff","to_linear":{"hex":"#050505ff","u16":[1330,1330,1330,65535]}}, +{"from_linear":{"hex":"#6e6e6eff","u16":[28350,28350,28350,65535]},"in":"#282828ff","to_linear":{"hex":"#050505ff","u16":[1391,1391,1391,65535]}}, +{"from_linear":{"hex":"#707070ff","u16":[28680,28680,28680,65535]},"in":"#292929ff","to_linear":{"hex":"#060606ff","u16":[1453,1453,1453,65535]}}, +{"from_linear":{"hex":"#717171ff","u16":[29006,29006,29006,65535]},"in":"#2a2a2aff","to_linear":{"hex":"#060606ff","u16":[1517,1517,1517,65535]}}, +{"from_linear":{"hex":"#727272ff","u16":[29327,29327,29327,65535]},"in":"#2b2b2bff","to_linear":{"hex":"#060606ff","u16":[1583,1583,1583,65535]}}, +{"from_linear":{"hex":"#737373ff","u16":[29644,29644,29644,65535]},"in":"#2c2c2cff","to_linear":{"hex":"#060606ff","u16":[1651,1651,1651,65535]}}, +{"from_linear":{"hex":"#757575ff","u16":[29957,29957,29957,65535]},"in":"#2d2d2dff","to_linear":{"hex":"#070707ff","u16":[1720,1720,1720,65535]}}, +{"from_linear":{"hex":"#767676ff","u16":[30266,30266,30266,65535]},"in":"#2e2e2eff","to_linear":{"hex":"#070707ff","u16":[1790,1790,1790,65535]}}, +{"from_linear":{"hex":"#777777ff","u16":[30571,30571,30571,65535]},"in":"#2f2f2fff","to_linear":{"hex":"#070707ff","u16":[1863,1863,1863,65535]}}, +{"from_linear":{"hex":"#787878ff","u16":[30872,30872,30872,65535]},"in":"#303030ff","to_linear":{"hex":"#080808ff","u16":[1937,1937,1937,65535]}}, +{"from_linear":{"hex":"#797979ff","u16":[31169,31169,31169,65535]},"in":"#313131ff","to_linear":{"hex":"#080808ff","u16":[2013,2013,2013,65535]}}, +{"from_linear":{"hex":"#7a7a7aff","u16":[31463,31463,31463,65535]},"in":"#323232ff","to_linear":{"hex":"#080808ff","u16":[2090,2090,2090,65535]}}, +{"from_linear":{"hex":"#7c7c7cff","u16":[31754,31754,31754,65535]},"in":"#333333ff","to_linear":{"hex":"#080808ff","u16":[2170,2170,2170,65535]}}, +{"from_linear":{"hex":"#7d7d7dff","u16":[32041,32041,32041,65535]},"in":"#343434ff","to_linear":{"hex":"#090909ff","u16":[2250,2250,2250,65535]}}, +{"from_linear":{"hex":"#7e7e7eff","u16":[32325,32325,32325,65535]},"in":"#353535ff","to_linear":{"hex":"#090909ff","u16":[2333,2333,2333,65535]}}, +{"from_linear":{"hex":"#7f7f7fff","u16":[32606,32606,32606,65535]},"in":"#363636ff","to_linear":{"hex":"#090909ff","u16":[2418,2418,2418,65535]}}, +{"from_linear":{"hex":"#808080ff","u16":[32884,32884,32884,65535]},"in":"#373737ff","to_linear":{"hex":"#0a0a0aff","u16":[2504,2504,2504,65535]}}, +{"from_linear":{"hex":"#818181ff","u16":[33159,33159,33159,65535]},"in":"#383838ff","to_linear":{"hex":"#0a0a0aff","u16":[2592,2592,2592,65535]}}, +{"from_linear":{"hex":"#828282ff","u16":[33431,33431,33431,65535]},"in":"#393939ff","to_linear":{"hex":"#0a0a0aff","u16":[2681,2681,2681,65535]}}, +{"from_linear":{"hex":"#838383ff","u16":[33700,33700,33700,65535]},"in":"#3a3a3aff","to_linear":{"hex":"#0b0b0bff","u16":[2773,2773,2773,65535]}}, +{"from_linear":{"hex":"#848484ff","u16":[33967,33967,33967,65535]},"in":"#3b3b3bff","to_linear":{"hex":"#0b0b0bff","u16":[2866,2866,2866,65535]}}, +{"from_linear":{"hex":"#858585ff","u16":[34231,34231,34231,65535]},"in":"#3c3c3cff","to_linear":{"hex":"#0c0c0cff","u16":[2961,2961,2961,65535]}}, +{"from_linear":{"hex":"#868686ff","u16":[34492,34492,34492,65535]},"in":"#3d3d3dff","to_linear":{"hex":"#0c0c0cff","u16":[3058,3058,3058,65535]}}, +{"from_linear":{"hex":"#878787ff","u16":[34751,34751,34751,65535]},"in":"#3e3e3eff","to_linear":{"hex":"#0c0c0cff","u16":[3157,3157,3157,65535]}}, +{"from_linear":{"hex":"#888888ff","u16":[35008,35008,35008,65535]},"in":"#3f3f3fff","to_linear":{"hex":"#0d0d0dff","u16":[3258,3258,3258,65535]}}, +{"from_linear":{"hex":"#898989ff","u16":[35262,35262,35262,65535]},"in":"#404040ff","to_linear":{"hex":"#0d0d0dff","u16":[3360,3360,3360,65535]}}, +{"from_linear":{"hex":"#8a8a8aff","u16":[35514,35514,35514,65535]},"in":"#414141ff","to_linear":{"hex":"#0d0d0dff","u16":[3464,3464,3464,65535]}}, +{"from_linear":{"hex":"#8b8b8bff","u16":[35764,35764,35764,65535]},"in":"#424242ff","to_linear":{"hex":"#0e0e0eff","u16":[3570,3570,3570,65535]}}, +{"from_linear":{"hex":"#8c8c8cff","u16":[36011,36011,36011,65535]},"in":"#434343ff","to_linear":{"hex":"#0e0e0eff","u16":[3678,3678,3678,65535]}}, +{"from_linear":{"hex":"#8d8d8dff","u16":[36256,36256,36256,65535]},"in":"#444444ff","to_linear":{"hex":"#0f0f0fff","u16":[3788,3788,3788,65535]}}, +{"from_linear":{"hex":"#8e8e8eff","u16":[36500,36500,36500,65535]},"in":"#454545ff","to_linear":{"hex":"#0f0f0fff","u16":[3900,3900,3900,65535]}}, +{"from_linear":{"hex":"#8f8f8fff","u16":[36741,36741,36741,65535]},"in":"#464646ff","to_linear":{"hex":"#101010ff","u16":[4014,4014,4014,65535]}}, +{"from_linear":{"hex":"#909090ff","u16":[36980,36980,36980,65535]},"in":"#474747ff","to_linear":{"hex":"#101010ff","u16":[4129,4129,4129,65535]}}, +{"from_linear":{"hex":"#919191ff","u16":[37217,37217,37217,65535]},"in":"#484848ff","to_linear":{"hex":"#111111ff","u16":[4247,4247,4247,65535]}}, +{"from_linear":{"hex":"#929292ff","u16":[37452,37452,37452,65535]},"in":"#494949ff","to_linear":{"hex":"#111111ff","u16":[4366,4366,4366,65535]}}, +{"from_linear":{"hex":"#939393ff","u16":[37686,37686,37686,65535]},"in":"#4a4a4aff","to_linear":{"hex":"#111111ff","u16":[4488,4488,4488,65535]}}, +{"from_linear":{"hex":"#949494ff","u16":[37917,37917,37917,65535]},"in":"#4b4b4bff","to_linear":{"hex":"#121212ff","u16":[4611,4611,4611,65535]}}, +{"from_linear":{"hex":"#949494ff","u16":[38147,38147,38147,65535]},"in":"#4c4c4cff","to_linear":{"hex":"#121212ff","u16":[4736,4736,4736,65535]}}, +{"from_linear":{"hex":"#959595ff","u16":[38375,38375,38375,65535]},"in":"#4d4d4dff","to_linear":{"hex":"#131313ff","u16":[4864,4864,4864,65535]}}, +{"from_linear":{"hex":"#969696ff","u16":[38601,38602,38601,65535]},"in":"#4e4e4eff","to_linear":{"hex":"#131313ff","u16":[4993,4993,4993,65535]}}, +{"from_linear":{"hex":"#979797ff","u16":[38826,38826,38826,65535]},"in":"#4f4f4fff","to_linear":{"hex":"#141414ff","u16":[5124,5124,5124,65535]}}, +{"from_linear":{"hex":"#989898ff","u16":[39049,39049,39049,65535]},"in":"#505050ff","to_linear":{"hex":"#141414ff","u16":[5257,5257,5257,65535]}}, +{"from_linear":{"hex":"#999999ff","u16":[39270,39270,39270,65535]},"in":"#515151ff","to_linear":{"hex":"#151515ff","u16":[5392,5392,5392,65535]}}, +{"from_linear":{"hex":"#9a9a9aff","u16":[39490,39490,39490,65535]},"in":"#525252ff","to_linear":{"hex":"#161616ff","u16":[5530,5530,5530,65535]}}, +{"from_linear":{"hex":"#9b9b9bff","u16":[39708,39708,39708,65535]},"in":"#535353ff","to_linear":{"hex":"#161616ff","u16":[5669,5669,5669,65535]}}, +{"from_linear":{"hex":"#9b9b9bff","u16":[39925,39925,39925,65535]},"in":"#545454ff","to_linear":{"hex":"#171717ff","u16":[5810,5810,5810,65535]}}, +{"from_linear":{"hex":"#9c9c9cff","u16":[40140,40140,40140,65535]},"in":"#555555ff","to_linear":{"hex":"#171717ff","u16":[5953,5953,5953,65535]}}, +{"from_linear":{"hex":"#9d9d9dff","u16":[40354,40354,40354,65535]},"in":"#565656ff","to_linear":{"hex":"#181818ff","u16":[6099,6099,6099,65535]}}, +{"from_linear":{"hex":"#9e9e9eff","u16":[40566,40566,40566,65535]},"in":"#575757ff","to_linear":{"hex":"#181818ff","u16":[6246,6246,6246,65535]}}, +{"from_linear":{"hex":"#9f9f9fff","u16":[40777,40777,40777,65535]},"in":"#585858ff","to_linear":{"hex":"#191919ff","u16":[6395,6395,6395,65535]}}, +{"from_linear":{"hex":"#9f9f9fff","u16":[40987,40987,40987,65535]},"in":"#595959ff","to_linear":{"hex":"#191919ff","u16":[6547,6547,6547,65535]}}, +{"from_linear":{"hex":"#a0a0a0ff","u16":[41195,41195,41195,65535]},"in":"#5a5a5aff","to_linear":{"hex":"#1a1a1aff","u16":[6700,6700,6700,65535]}}, +{"from_linear":{"hex":"#a1a1a1ff","u16":[41401,41401,41401,65535]},"in":"#5b5b5bff","to_linear":{"hex":"#1b1b1bff","u16":[6856,6856,6856,65535]}}, +{"from_linear":{"hex":"#a2a2a2ff","u16":[41607,41607,41607,65535]},"in":"#5c5c5cff","to_linear":{"hex":"#1b1b1bff","u16":[7014,7014,7014,65535]}}, +{"from_linear":{"hex":"#a3a3a3ff","u16":[41811,41811,41811,65535]},"in":"#5d5d5dff","to_linear":{"hex":"#1c1c1cff","u16":[7174,7174,7174,65535]}}, +{"from_linear":{"hex":"#a3a3a3ff","u16":[42014,42014,42014,65535]},"in":"#5e5e5eff","to_linear":{"hex":"#1d1d1dff","u16":[7335,7335,7335,65535]}}, +{"from_linear":{"hex":"#a4a4a4ff","u16":[42215,42215,42215,65535]},"in":"#5f5f5fff","to_linear":{"hex":"#1d1d1dff","u16":[7500,7500,7500,65535]}}, +{"from_linear":{"hex":"#a5a5a5ff","u16":[42416,42416,42416,65535]},"in":"#606060ff","to_linear":{"hex":"#1e1e1eff","u16":[7666,7666,7666,65535]}}, +{"from_linear":{"hex":"#a6a6a6ff","u16":[42615,42615,42615,65535]},"in":"#616161ff","to_linear":{"hex":"#1e1e1eff","u16":[7834,7834,7834,65535]}}, +{"from_linear":{"hex":"#a7a7a7ff","u16":[42813,42813,42813,65535]},"in":"#626262ff","to_linear":{"hex":"#1f1f1fff","u16":[8004,8004,8004,65535]}}, +{"from_linear":{"hex":"#a7a7a7ff","u16":[43009,43009,43009,65535]},"in":"#636363ff","to_linear":{"hex":"#202020ff","u16":[8177,8177,8177,65535]}}, +{"from_linear":{"hex":"#a8a8a8ff","u16":[43205,43205,43205,65535]},"in":"#646464ff","to_linear":{"hex":"#202020ff","u16":[8352,8352,8352,65535]}}, +{"from_linear":{"hex":"#a9a9a9ff","u16":[43400,43400,43400,65535]},"in":"#656565ff","to_linear":{"hex":"#212121ff","u16":[8528,8528,8528,65535]}}, +{"from_linear":{"hex":"#aaaaaaff","u16":[43593,43593,43593,65535]},"in":"#666666ff","to_linear":{"hex":"#222222ff","u16":[8708,8708,8708,65535]}}, +{"from_linear":{"hex":"#aaaaaaff","u16":[43785,43785,43785,65535]},"in":"#676767ff","to_linear":{"hex":"#232323ff","u16":[8889,8889,8889,65535]}}, +{"from_linear":{"hex":"#abababff","u16":[43976,43976,43976,65535]},"in":"#686868ff","to_linear":{"hex":"#232323ff","u16":[9072,9072,9072,65535]}}, +{"from_linear":{"hex":"#acacacff","u16":[44166,44166,44166,65535]},"in":"#696969ff","to_linear":{"hex":"#242424ff","u16":[9258,9258,9258,65535]}}, +{"from_linear":{"hex":"#adadadff","u16":[44355,44355,44355,65535]},"in":"#6a6a6aff","to_linear":{"hex":"#252525ff","u16":[9445,9445,9445,65535]}}, +{"from_linear":{"hex":"#adadadff","u16":[44543,44543,44543,65535]},"in":"#6b6b6bff","to_linear":{"hex":"#252525ff","u16":[9635,9635,9635,65535]}}, +{"from_linear":{"hex":"#aeaeaeff","u16":[44730,44730,44730,65535]},"in":"#6c6c6cff","to_linear":{"hex":"#262626ff","u16":[9828,9828,9828,65535]}}, +{"from_linear":{"hex":"#afafafff","u16":[44916,44916,44916,65535]},"in":"#6d6d6dff","to_linear":{"hex":"#272727ff","u16":[10022,10022,10022,65535]}}, +{"from_linear":{"hex":"#afafafff","u16":[45101,45101,45101,65535]},"in":"#6e6e6eff","to_linear":{"hex":"#282828ff","u16":[10219,10219,10219,65535]}}, +{"from_linear":{"hex":"#b0b0b0ff","u16":[45285,45285,45285,65535]},"in":"#6f6f6fff","to_linear":{"hex":"#292929ff","u16":[10417,10417,10417,65535]}}, +{"from_linear":{"hex":"#b1b1b1ff","u16":[45468,45468,45468,65535]},"in":"#707070ff","to_linear":{"hex":"#292929ff","u16":[10619,10619,10619,65535]}}, +{"from_linear":{"hex":"#b2b2b2ff","u16":[45651,45651,45651,65535]},"in":"#717171ff","to_linear":{"hex":"#2a2a2aff","u16":[10822,10822,10822,65535]}}, +{"from_linear":{"hex":"#b2b2b2ff","u16":[45832,45832,45832,65535]},"in":"#727272ff","to_linear":{"hex":"#2b2b2bff","u16":[11028,11028,11028,65535]}}, +{"from_linear":{"hex":"#b3b3b3ff","u16":[46012,46012,46012,65535]},"in":"#737373ff","to_linear":{"hex":"#2c2c2cff","u16":[11235,11235,11235,65535]}}, +{"from_linear":{"hex":"#b4b4b4ff","u16":[46191,46191,46191,65535]},"in":"#747474ff","to_linear":{"hex":"#2d2d2dff","u16":[11446,11446,11446,65535]}}, +{"from_linear":{"hex":"#b4b4b4ff","u16":[46370,46370,46370,65535]},"in":"#757575ff","to_linear":{"hex":"#2d2d2dff","u16":[11658,11658,11658,65535]}}, +{"from_linear":{"hex":"#b5b5b5ff","u16":[46547,46547,46547,65535]},"in":"#767676ff","to_linear":{"hex":"#2e2e2eff","u16":[11873,11873,11873,65535]}}, +{"from_linear":{"hex":"#b6b6b6ff","u16":[46724,46724,46724,65535]},"in":"#777777ff","to_linear":{"hex":"#2f2f2fff","u16":[12090,12090,12090,65535]}}, +{"from_linear":{"hex":"#b6b6b6ff","u16":[46900,46900,46900,65535]},"in":"#787878ff","to_linear":{"hex":"#303030ff","u16":[12309,12309,12309,65535]}}, +{"from_linear":{"hex":"#b7b7b7ff","u16":[47075,47075,47075,65535]},"in":"#797979ff","to_linear":{"hex":"#313131ff","u16":[12530,12530,12530,65535]}}, +{"from_linear":{"hex":"#b8b8b8ff","u16":[47249,47249,47249,65535]},"in":"#7a7a7aff","to_linear":{"hex":"#323232ff","u16":[12754,12754,12754,65535]}}, +{"from_linear":{"hex":"#b9b9b9ff","u16":[47422,47422,47422,65535]},"in":"#7b7b7bff","to_linear":{"hex":"#333333ff","u16":[12980,12980,12980,65535]}}, +{"from_linear":{"hex":"#b9b9b9ff","u16":[47594,47594,47594,65535]},"in":"#7c7c7cff","to_linear":{"hex":"#333333ff","u16":[13209,13209,13209,65535]}}, +{"from_linear":{"hex":"#bababaff","u16":[47766,47766,47766,65535]},"in":"#7d7d7dff","to_linear":{"hex":"#343434ff","u16":[13440,13440,13440,65535]}}, +{"from_linear":{"hex":"#bbbbbbff","u16":[47937,47937,47937,65535]},"in":"#7e7e7eff","to_linear":{"hex":"#353535ff","u16":[13673,13673,13673,65535]}}, +{"from_linear":{"hex":"#bbbbbbff","u16":[48107,48107,48107,65535]},"in":"#7f7f7fff","to_linear":{"hex":"#363636ff","u16":[13909,13909,13909,65535]}}, +{"from_linear":{"hex":"#bcbcbcff","u16":[48276,48276,48276,65535]},"in":"#808080ff","to_linear":{"hex":"#373737ff","u16":[14146,14146,14146,65535]}}, +{"from_linear":{"hex":"#bdbdbdff","u16":[48445,48445,48445,65535]},"in":"#818181ff","to_linear":{"hex":"#383838ff","u16":[14387,14387,14387,65535]}}, +{"from_linear":{"hex":"#bdbdbdff","u16":[48612,48612,48612,65535]},"in":"#828282ff","to_linear":{"hex":"#393939ff","u16":[14629,14629,14629,65535]}}, +{"from_linear":{"hex":"#bebebeff","u16":[48779,48779,48779,65535]},"in":"#838383ff","to_linear":{"hex":"#3a3a3aff","u16":[14874,14874,14874,65535]}}, +{"from_linear":{"hex":"#bebebeff","u16":[48946,48946,48946,65535]},"in":"#848484ff","to_linear":{"hex":"#3b3b3bff","u16":[15122,15122,15122,65535]}}, +{"from_linear":{"hex":"#bfbfbfff","u16":[49111,49111,49111,65535]},"in":"#858585ff","to_linear":{"hex":"#3c3c3cff","u16":[15371,15371,15371,65535]}}, +{"from_linear":{"hex":"#c0c0c0ff","u16":[49276,49276,49276,65535]},"in":"#868686ff","to_linear":{"hex":"#3d3d3dff","u16":[15623,15623,15623,65535]}}, +{"from_linear":{"hex":"#c0c0c0ff","u16":[49440,49440,49440,65535]},"in":"#878787ff","to_linear":{"hex":"#3e3e3eff","u16":[15878,15878,15878,65535]}}, +{"from_linear":{"hex":"#c1c1c1ff","u16":[49603,49603,49603,65535]},"in":"#888888ff","to_linear":{"hex":"#3f3f3fff","u16":[16135,16135,16135,65535]}}, +{"from_linear":{"hex":"#c2c2c2ff","u16":[49766,49766,49766,65535]},"in":"#898989ff","to_linear":{"hex":"#404040ff","u16":[16394,16394,16394,65535]}}, +{"from_linear":{"hex":"#c2c2c2ff","u16":[49928,49928,49928,65535]},"in":"#8a8a8aff","to_linear":{"hex":"#414141ff","u16":[16656,16656,16656,65535]}}, +{"from_linear":{"hex":"#c3c3c3ff","u16":[50089,50089,50089,65535]},"in":"#8b8b8bff","to_linear":{"hex":"#424242ff","u16":[16920,16920,16920,65535]}}, +{"from_linear":{"hex":"#c4c4c4ff","u16":[50250,50250,50250,65535]},"in":"#8c8c8cff","to_linear":{"hex":"#434343ff","u16":[17187,17187,17187,65535]}}, +{"from_linear":{"hex":"#c4c4c4ff","u16":[50410,50410,50410,65535]},"in":"#8d8d8dff","to_linear":{"hex":"#444444ff","u16":[17456,17456,17456,65535]}}, +{"from_linear":{"hex":"#c5c5c5ff","u16":[50569,50569,50569,65535]},"in":"#8e8e8eff","to_linear":{"hex":"#454545ff","u16":[17727,17727,17727,65535]}}, +{"from_linear":{"hex":"#c5c5c5ff","u16":[50728,50728,50728,65535]},"in":"#8f8f8fff","to_linear":{"hex":"#464646ff","u16":[18001,18001,18001,65535]}}, +{"from_linear":{"hex":"#c6c6c6ff","u16":[50886,50886,50886,65535]},"in":"#909090ff","to_linear":{"hex":"#474747ff","u16":[18277,18277,18277,65535]}}, +{"from_linear":{"hex":"#c7c7c7ff","u16":[51043,51043,51043,65535]},"in":"#919191ff","to_linear":{"hex":"#484848ff","u16":[18556,18556,18556,65535]}}, +{"from_linear":{"hex":"#c7c7c7ff","u16":[51200,51200,51200,65535]},"in":"#929292ff","to_linear":{"hex":"#494949ff","u16":[18837,18837,18837,65535]}}, +{"from_linear":{"hex":"#c8c8c8ff","u16":[51356,51356,51356,65535]},"in":"#939393ff","to_linear":{"hex":"#4a4a4aff","u16":[19121,19121,19121,65535]}}, +{"from_linear":{"hex":"#c8c8c8ff","u16":[51511,51511,51511,65535]},"in":"#949494ff","to_linear":{"hex":"#4c4c4cff","u16":[19407,19407,19407,65535]}}, +{"from_linear":{"hex":"#c9c9c9ff","u16":[51666,51666,51666,65535]},"in":"#959595ff","to_linear":{"hex":"#4d4d4dff","u16":[19696,19696,19696,65535]}}, +{"from_linear":{"hex":"#cacacaff","u16":[51821,51821,51821,65535]},"in":"#969696ff","to_linear":{"hex":"#4e4e4eff","u16":[19987,19987,19987,65535]}}, +{"from_linear":{"hex":"#cacacaff","u16":[51974,51974,51974,65535]},"in":"#979797ff","to_linear":{"hex":"#4f4f4fff","u16":[20281,20281,20281,65535]}}, +{"from_linear":{"hex":"#cbcbcbff","u16":[52127,52127,52127,65535]},"in":"#989898ff","to_linear":{"hex":"#505050ff","u16":[20577,20577,20577,65535]}}, +{"from_linear":{"hex":"#cbcbcbff","u16":[52280,52280,52280,65535]},"in":"#999999ff","to_linear":{"hex":"#515151ff","u16":[20876,20876,20876,65535]}}, +{"from_linear":{"hex":"#ccccccff","u16":[52432,52432,52432,65535]},"in":"#9a9a9aff","to_linear":{"hex":"#525252ff","u16":[21177,21177,21177,65535]}}, +{"from_linear":{"hex":"#cdcdcdff","u16":[52583,52583,52583,65535]},"in":"#9b9b9bff","to_linear":{"hex":"#545454ff","u16":[21481,21481,21481,65535]}}, +{"from_linear":{"hex":"#cdcdcdff","u16":[52734,52734,52734,65535]},"in":"#9c9c9cff","to_linear":{"hex":"#555555ff","u16":[21787,21787,21787,65535]}}, +{"from_linear":{"hex":"#cececeff","u16":[52884,52884,52884,65535]},"in":"#9d9d9dff","to_linear":{"hex":"#565656ff","u16":[22096,22096,22096,65535]}}, +{"from_linear":{"hex":"#cececeff","u16":[53034,53034,53034,65535]},"in":"#9e9e9eff","to_linear":{"hex":"#575757ff","u16":[22407,22407,22407,65535]}}, +{"from_linear":{"hex":"#cfcfcfff","u16":[53183,53183,53183,65535]},"in":"#9f9f9fff","to_linear":{"hex":"#585858ff","u16":[22721,22721,22721,65535]}}, +{"from_linear":{"hex":"#d0d0d0ff","u16":[53331,53331,53331,65535]},"in":"#a0a0a0ff","to_linear":{"hex":"#5a5a5aff","u16":[23038,23038,23038,65535]}}, +{"from_linear":{"hex":"#d0d0d0ff","u16":[53479,53479,53479,65535]},"in":"#a1a1a1ff","to_linear":{"hex":"#5b5b5bff","u16":[23357,23357,23357,65535]}}, +{"from_linear":{"hex":"#d1d1d1ff","u16":[53627,53627,53627,65535]},"in":"#a2a2a2ff","to_linear":{"hex":"#5c5c5cff","u16":[23678,23678,23678,65535]}}, +{"from_linear":{"hex":"#d1d1d1ff","u16":[53774,53774,53774,65535]},"in":"#a3a3a3ff","to_linear":{"hex":"#5d5d5dff","u16":[24002,24002,24002,65535]}}, +{"from_linear":{"hex":"#d2d2d2ff","u16":[53920,53920,53920,65535]},"in":"#a4a4a4ff","to_linear":{"hex":"#5f5f5fff","u16":[24329,24329,24329,65535]}}, +{"from_linear":{"hex":"#d2d2d2ff","u16":[54066,54066,54066,65535]},"in":"#a5a5a5ff","to_linear":{"hex":"#606060ff","u16":[24658,24658,24658,65535]}}, +{"from_linear":{"hex":"#d3d3d3ff","u16":[54211,54211,54211,65535]},"in":"#a6a6a6ff","to_linear":{"hex":"#616161ff","u16":[24990,24990,24990,65535]}}, +{"from_linear":{"hex":"#d4d4d4ff","u16":[54356,54356,54356,65535]},"in":"#a7a7a7ff","to_linear":{"hex":"#636363ff","u16":[25325,25325,25325,65535]}}, +{"from_linear":{"hex":"#d4d4d4ff","u16":[54500,54500,54500,65535]},"in":"#a8a8a8ff","to_linear":{"hex":"#646464ff","u16":[25662,25662,25662,65535]}}, +{"from_linear":{"hex":"#d5d5d5ff","u16":[54644,54644,54644,65535]},"in":"#a9a9a9ff","to_linear":{"hex":"#656565ff","u16":[26001,26001,26001,65535]}}, +{"from_linear":{"hex":"#d5d5d5ff","u16":[54788,54788,54788,65535]},"in":"#aaaaaaff","to_linear":{"hex":"#676767ff","u16":[26344,26344,26344,65535]}}, +{"from_linear":{"hex":"#d6d6d6ff","u16":[54931,54931,54931,65535]},"in":"#abababff","to_linear":{"hex":"#686868ff","u16":[26688,26688,26688,65535]}}, +{"from_linear":{"hex":"#d6d6d6ff","u16":[55073,55073,55073,65535]},"in":"#acacacff","to_linear":{"hex":"#696969ff","u16":[27036,27036,27036,65535]}}, +{"from_linear":{"hex":"#d7d7d7ff","u16":[55215,55215,55215,65535]},"in":"#adadadff","to_linear":{"hex":"#6b6b6bff","u16":[27386,27386,27386,65535]}}, +{"from_linear":{"hex":"#d7d7d7ff","u16":[55356,55356,55356,65535]},"in":"#aeaeaeff","to_linear":{"hex":"#6c6c6cff","u16":[27739,27739,27739,65535]}}, +{"from_linear":{"hex":"#d8d8d8ff","u16":[55497,55497,55497,65535]},"in":"#afafafff","to_linear":{"hex":"#6d6d6dff","u16":[28094,28094,28094,65535]}}, +{"from_linear":{"hex":"#d8d8d8ff","u16":[55638,55638,55638,65535]},"in":"#b0b0b0ff","to_linear":{"hex":"#6f6f6fff","u16":[28452,28452,28452,65535]}}, +{"from_linear":{"hex":"#d9d9d9ff","u16":[55778,55778,55778,65535]},"in":"#b1b1b1ff","to_linear":{"hex":"#707070ff","u16":[28813,28813,28813,65535]}}, +{"from_linear":{"hex":"#dadadaff","u16":[55917,55917,55917,65535]},"in":"#b2b2b2ff","to_linear":{"hex":"#727272ff","u16":[29176,29176,29176,65535]}}, +{"from_linear":{"hex":"#dadadaff","u16":[56056,56056,56056,65535]},"in":"#b3b3b3ff","to_linear":{"hex":"#737373ff","u16":[29542,29542,29542,65535]}}, +{"from_linear":{"hex":"#dbdbdbff","u16":[56195,56195,56195,65535]},"in":"#b4b4b4ff","to_linear":{"hex":"#747474ff","u16":[29911,29911,29911,65535]}}, +{"from_linear":{"hex":"#dbdbdbff","u16":[56333,56333,56333,65535]},"in":"#b5b5b5ff","to_linear":{"hex":"#767676ff","u16":[30282,30282,30282,65535]}}, +{"from_linear":{"hex":"#dcdcdcff","u16":[56471,56471,56471,65535]},"in":"#b6b6b6ff","to_linear":{"hex":"#777777ff","u16":[30656,30656,30656,65535]}}, +{"from_linear":{"hex":"#dcdcdcff","u16":[56608,56608,56608,65535]},"in":"#b7b7b7ff","to_linear":{"hex":"#797979ff","u16":[31033,31033,31033,65535]}}, +{"from_linear":{"hex":"#ddddddff","u16":[56745,56745,56745,65535]},"in":"#b8b8b8ff","to_linear":{"hex":"#7a7a7aff","u16":[31412,31412,31412,65535]}}, +{"from_linear":{"hex":"#ddddddff","u16":[56882,56882,56882,65535]},"in":"#b9b9b9ff","to_linear":{"hex":"#7c7c7cff","u16":[31794,31794,31794,65535]}}, +{"from_linear":{"hex":"#dededeff","u16":[57018,57018,57018,65535]},"in":"#bababaff","to_linear":{"hex":"#7d7d7dff","u16":[32179,32179,32179,65535]}}, +{"from_linear":{"hex":"#dededeff","u16":[57153,57153,57153,65535]},"in":"#bbbbbbff","to_linear":{"hex":"#7f7f7fff","u16":[32567,32567,32567,65535]}}, +{"from_linear":{"hex":"#dfdfdfff","u16":[57288,57288,57288,65535]},"in":"#bcbcbcff","to_linear":{"hex":"#808080ff","u16":[32957,32957,32957,65535]}}, +{"from_linear":{"hex":"#dfdfdfff","u16":[57423,57423,57423,65535]},"in":"#bdbdbdff","to_linear":{"hex":"#828282ff","u16":[33350,33350,33350,65535]}}, +{"from_linear":{"hex":"#e0e0e0ff","u16":[57558,57558,57558,65535]},"in":"#bebebeff","to_linear":{"hex":"#838383ff","u16":[33745,33745,33745,65535]}}, +{"from_linear":{"hex":"#e0e0e0ff","u16":[57691,57691,57691,65535]},"in":"#bfbfbfff","to_linear":{"hex":"#858585ff","u16":[34143,34143,34143,65535]}}, +{"from_linear":{"hex":"#e1e1e1ff","u16":[57825,57825,57825,65535]},"in":"#c0c0c0ff","to_linear":{"hex":"#868686ff","u16":[34544,34544,34544,65535]}}, +{"from_linear":{"hex":"#e2e2e2ff","u16":[57958,57958,57958,65535]},"in":"#c1c1c1ff","to_linear":{"hex":"#888888ff","u16":[34948,34948,34948,65535]}}, +{"from_linear":{"hex":"#e2e2e2ff","u16":[58091,58091,58091,65535]},"in":"#c2c2c2ff","to_linear":{"hex":"#8a8a8aff","u16":[35355,35355,35355,65535]}}, +{"from_linear":{"hex":"#e3e3e3ff","u16":[58223,58223,58223,65535]},"in":"#c3c3c3ff","to_linear":{"hex":"#8b8b8bff","u16":[35764,35764,35764,65535]}}, +{"from_linear":{"hex":"#e3e3e3ff","u16":[58355,58355,58355,65535]},"in":"#c4c4c4ff","to_linear":{"hex":"#8d8d8dff","u16":[36176,36176,36176,65535]}}, +{"from_linear":{"hex":"#e4e4e4ff","u16":[58487,58487,58487,65535]},"in":"#c5c5c5ff","to_linear":{"hex":"#8e8e8eff","u16":[36591,36591,36591,65535]}}, +{"from_linear":{"hex":"#e4e4e4ff","u16":[58618,58618,58618,65535]},"in":"#c6c6c6ff","to_linear":{"hex":"#909090ff","u16":[37008,37008,37008,65535]}}, +{"from_linear":{"hex":"#e5e5e5ff","u16":[58748,58748,58748,65535]},"in":"#c7c7c7ff","to_linear":{"hex":"#929292ff","u16":[37429,37429,37429,65535]}}, +{"from_linear":{"hex":"#e5e5e5ff","u16":[58879,58879,58879,65535]},"in":"#c8c8c8ff","to_linear":{"hex":"#939393ff","u16":[37852,37852,37852,65535]}}, +{"from_linear":{"hex":"#e6e6e6ff","u16":[59009,59009,59009,65535]},"in":"#c9c9c9ff","to_linear":{"hex":"#959595ff","u16":[38278,38278,38278,65535]}}, +{"from_linear":{"hex":"#e6e6e6ff","u16":[59138,59138,59138,65535]},"in":"#cacacaff","to_linear":{"hex":"#979797ff","u16":[38706,38706,38706,65535]}}, +{"from_linear":{"hex":"#e7e7e7ff","u16":[59268,59268,59268,65535]},"in":"#cbcbcbff","to_linear":{"hex":"#989898ff","u16":[39138,39138,39138,65535]}}, +{"from_linear":{"hex":"#e7e7e7ff","u16":[59396,59396,59396,65535]},"in":"#ccccccff","to_linear":{"hex":"#9a9a9aff","u16":[39572,39572,39572,65535]}}, +{"from_linear":{"hex":"#e8e8e8ff","u16":[59525,59525,59525,65535]},"in":"#cdcdcdff","to_linear":{"hex":"#9c9c9cff","u16":[40009,40009,40009,65535]}}, +{"from_linear":{"hex":"#e8e8e8ff","u16":[59653,59653,59653,65535]},"in":"#cececeff","to_linear":{"hex":"#9d9d9dff","u16":[40449,40449,40449,65535]}}, +{"from_linear":{"hex":"#e9e9e9ff","u16":[59781,59781,59781,65535]},"in":"#cfcfcfff","to_linear":{"hex":"#9f9f9fff","u16":[40891,40891,40891,65535]}}, +{"from_linear":{"hex":"#e9e9e9ff","u16":[59908,59908,59908,65535]},"in":"#d0d0d0ff","to_linear":{"hex":"#a1a1a1ff","u16":[41337,41337,41337,65535]}}, +{"from_linear":{"hex":"#eaeaeaff","u16":[60035,60035,60035,65535]},"in":"#d1d1d1ff","to_linear":{"hex":"#a3a3a3ff","u16":[41785,41785,41785,65535]}}, +{"from_linear":{"hex":"#eaeaeaff","u16":[60162,60162,60162,65535]},"in":"#d2d2d2ff","to_linear":{"hex":"#a4a4a4ff","u16":[42236,42236,42236,65535]}}, +{"from_linear":{"hex":"#ebebebff","u16":[60288,60288,60288,65535]},"in":"#d3d3d3ff","to_linear":{"hex":"#a6a6a6ff","u16":[42690,42690,42690,65535]}}, +{"from_linear":{"hex":"#ebebebff","u16":[60414,60414,60414,65535]},"in":"#d4d4d4ff","to_linear":{"hex":"#a8a8a8ff","u16":[43147,43147,43147,65535]}}, +{"from_linear":{"hex":"#ecececff","u16":[60540,60540,60540,65535]},"in":"#d5d5d5ff","to_linear":{"hex":"#aaaaaaff","u16":[43606,43606,43606,65535]}}, +{"from_linear":{"hex":"#ecececff","u16":[60665,60665,60665,65535]},"in":"#d6d6d6ff","to_linear":{"hex":"#abababff","u16":[44069,44069,44069,65535]}}, +{"from_linear":{"hex":"#edededff","u16":[60790,60790,60790,65535]},"in":"#d7d7d7ff","to_linear":{"hex":"#adadadff","u16":[44534,44534,44534,65535]}}, +{"from_linear":{"hex":"#edededff","u16":[60915,60915,60915,65535]},"in":"#d8d8d8ff","to_linear":{"hex":"#afafafff","u16":[45002,45002,45002,65535]}}, +{"from_linear":{"hex":"#eeeeeeff","u16":[61039,61039,61039,65535]},"in":"#d9d9d9ff","to_linear":{"hex":"#b1b1b1ff","u16":[45473,45473,45473,65535]}}, +{"from_linear":{"hex":"#eeeeeeff","u16":[61163,61163,61163,65535]},"in":"#dadadaff","to_linear":{"hex":"#b3b3b3ff","u16":[45947,45947,45947,65535]}}, +{"from_linear":{"hex":"#eeeeeeff","u16":[61287,61287,61287,65535]},"in":"#dbdbdbff","to_linear":{"hex":"#b5b5b5ff","u16":[46423,46423,46423,65535]}}, +{"from_linear":{"hex":"#efefefff","u16":[61410,61410,61410,65535]},"in":"#dcdcdcff","to_linear":{"hex":"#b7b7b7ff","u16":[46903,46903,46903,65535]}}, +{"from_linear":{"hex":"#efefefff","u16":[61533,61533,61533,65535]},"in":"#ddddddff","to_linear":{"hex":"#b8b8b8ff","u16":[47385,47385,47385,65535]}}, +{"from_linear":{"hex":"#f0f0f0ff","u16":[61656,61656,61656,65535]},"in":"#dededeff","to_linear":{"hex":"#bababaff","u16":[47871,47871,47871,65535]}}, +{"from_linear":{"hex":"#f0f0f0ff","u16":[61778,61778,61778,65535]},"in":"#dfdfdfff","to_linear":{"hex":"#bcbcbcff","u16":[48359,48359,48359,65535]}}, +{"from_linear":{"hex":"#f1f1f1ff","u16":[61900,61900,61900,65535]},"in":"#e0e0e0ff","to_linear":{"hex":"#bebebeff","u16":[48850,48850,48850,65535]}}, +{"from_linear":{"hex":"#f1f1f1ff","u16":[62022,62022,62022,65535]},"in":"#e1e1e1ff","to_linear":{"hex":"#c0c0c0ff","u16":[49344,49344,49344,65535]}}, +{"from_linear":{"hex":"#f2f2f2ff","u16":[62143,62143,62143,65535]},"in":"#e2e2e2ff","to_linear":{"hex":"#c2c2c2ff","u16":[49841,49841,49841,65535]}}, +{"from_linear":{"hex":"#f2f2f2ff","u16":[62264,62264,62264,65535]},"in":"#e3e3e3ff","to_linear":{"hex":"#c4c4c4ff","u16":[50341,50341,50341,65535]}}, +{"from_linear":{"hex":"#f3f3f3ff","u16":[62385,62385,62385,65535]},"in":"#e4e4e4ff","to_linear":{"hex":"#c6c6c6ff","u16":[50844,50844,50844,65535]}}, +{"from_linear":{"hex":"#f3f3f3ff","u16":[62505,62505,62505,65535]},"in":"#e5e5e5ff","to_linear":{"hex":"#c8c8c8ff","u16":[51349,51349,51349,65535]}}, +{"from_linear":{"hex":"#f4f4f4ff","u16":[62625,62625,62625,65535]},"in":"#e6e6e6ff","to_linear":{"hex":"#cacacaff","u16":[51858,51858,51858,65535]}}, +{"from_linear":{"hex":"#f4f4f4ff","u16":[62745,62745,62745,65535]},"in":"#e7e7e7ff","to_linear":{"hex":"#ccccccff","u16":[52369,52369,52369,65535]}}, +{"from_linear":{"hex":"#f5f5f5ff","u16":[62865,62865,62865,65535]},"in":"#e8e8e8ff","to_linear":{"hex":"#cececeff","u16":[52884,52884,52884,65535]}}, +{"from_linear":{"hex":"#f5f5f5ff","u16":[62984,62984,62984,65535]},"in":"#e9e9e9ff","to_linear":{"hex":"#d0d0d0ff","u16":[53401,53401,53401,65535]}}, +{"from_linear":{"hex":"#f6f6f6ff","u16":[63103,63103,63103,65535]},"in":"#eaeaeaff","to_linear":{"hex":"#d2d2d2ff","u16":[53921,53921,53921,65535]}}, +{"from_linear":{"hex":"#f6f6f6ff","u16":[63222,63222,63222,65535]},"in":"#ebebebff","to_linear":{"hex":"#d4d4d4ff","u16":[54445,54445,54445,65535]}}, +{"from_linear":{"hex":"#f6f6f6ff","u16":[63340,63340,63340,65535]},"in":"#ecececff","to_linear":{"hex":"#d6d6d6ff","u16":[54971,54971,54971,65535]}}, +{"from_linear":{"hex":"#f7f7f7ff","u16":[63458,63458,63458,65535]},"in":"#edededff","to_linear":{"hex":"#d8d8d8ff","u16":[55500,55500,55500,65535]}}, +{"from_linear":{"hex":"#f7f7f7ff","u16":[63576,63576,63576,65535]},"in":"#eeeeeeff","to_linear":{"hex":"#dadadaff","u16":[56032,56032,56032,65535]}}, +{"from_linear":{"hex":"#f8f8f8ff","u16":[63693,63693,63693,65535]},"in":"#efefefff","to_linear":{"hex":"#dcdcdcff","u16":[56567,56567,56567,65535]}}, +{"from_linear":{"hex":"#f8f8f8ff","u16":[63810,63810,63810,65535]},"in":"#f0f0f0ff","to_linear":{"hex":"#dededeff","u16":[57105,57105,57105,65535]}}, +{"from_linear":{"hex":"#f9f9f9ff","u16":[63927,63927,63927,65535]},"in":"#f1f1f1ff","to_linear":{"hex":"#e0e0e0ff","u16":[57646,57646,57646,65535]}}, +{"from_linear":{"hex":"#f9f9f9ff","u16":[64044,64044,64044,65535]},"in":"#f2f2f2ff","to_linear":{"hex":"#e2e2e2ff","u16":[58190,58190,58190,65535]}}, +{"from_linear":{"hex":"#fafafaff","u16":[64160,64160,64160,65535]},"in":"#f3f3f3ff","to_linear":{"hex":"#e5e5e5ff","u16":[58737,58737,58737,65535]}}, +{"from_linear":{"hex":"#fafafaff","u16":[64276,64276,64276,65535]},"in":"#f4f4f4ff","to_linear":{"hex":"#e7e7e7ff","u16":[59287,59287,59287,65535]}}, +{"from_linear":{"hex":"#fbfbfbff","u16":[64392,64392,64392,65535]},"in":"#f5f5f5ff","to_linear":{"hex":"#e9e9e9ff","u16":[59840,59840,59840,65535]}}, +{"from_linear":{"hex":"#fbfbfbff","u16":[64508,64508,64508,65535]},"in":"#f6f6f6ff","to_linear":{"hex":"#ebebebff","u16":[60396,60396,60396,65535]}}, +{"from_linear":{"hex":"#fbfbfbff","u16":[64623,64623,64623,65535]},"in":"#f7f7f7ff","to_linear":{"hex":"#edededff","u16":[60955,60955,60955,65535]}}, +{"from_linear":{"hex":"#fcfcfcff","u16":[64738,64738,64738,65535]},"in":"#f8f8f8ff","to_linear":{"hex":"#efefefff","u16":[61517,61517,61517,65535]}}, +{"from_linear":{"hex":"#fcfcfcff","u16":[64852,64852,64852,65535]},"in":"#f9f9f9ff","to_linear":{"hex":"#f2f2f2ff","u16":[62082,62082,62082,65535]}}, +{"from_linear":{"hex":"#fdfdfdff","u16":[64967,64967,64967,65535]},"in":"#fafafaff","to_linear":{"hex":"#f4f4f4ff","u16":[62650,62650,62650,65535]}}, +{"from_linear":{"hex":"#fdfdfdff","u16":[65081,65081,65081,65535]},"in":"#fbfbfbff","to_linear":{"hex":"#f6f6f6ff","u16":[63221,63221,63221,65535]}}, +{"from_linear":{"hex":"#fefefeff","u16":[65195,65195,65195,65535]},"in":"#fcfcfcff","to_linear":{"hex":"#f8f8f8ff","u16":[63795,63795,63795,65535]}}, +{"from_linear":{"hex":"#fefefeff","u16":[65309,65309,65309,65535]},"in":"#fdfdfdff","to_linear":{"hex":"#fafafaff","u16":[64372,64372,64372,65535]}}, +{"from_linear":{"hex":"#ffffffff","u16":[65422,65422,65422,65535]},"in":"#fefefeff","to_linear":{"hex":"#fdfdfdff","u16":[64952,64952,64952,65535]}}, +{"from_linear":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"in":"#ffffffff","to_linear":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]}}, +{"from_linear":{"hex":"#0d0000ff","u16":[3266,0,0,65535]},"in":"#010000ff","to_linear":{"hex":"#000000ff","u16":[20,0,0,65535]}}, +{"from_linear":{"hex":"#160000ff","u16":[5567,0,0,65535]},"in":"#020000ff","to_linear":{"hex":"#000000ff","u16":[40,0,0,65535]}}, +{"from_linear":{"hex":"#1c0000ff","u16":[7255,0,0,65535]},"in":"#030000ff","to_linear":{"hex":"#000000ff","u16":[60,0,0,65535]}}, +{"from_linear":{"hex":"#220000ff","u16":[8638,0,0,65535]},"in":"#040000ff","to_linear":{"hex":"#000000ff","u16":[80,0,0,65535]}}, +{"from_linear":{"hex":"#260000ff","u16":[9831,0,0,65535]},"in":"#050000ff","to_linear":{"hex":"#000000ff","u16":[99,0,0,65535]}}, +{"from_linear":{"hex":"#2a0000ff","u16":[10891,0,0,65535]},"in":"#060000ff","to_linear":{"hex":"#000000ff","u16":[119,0,0,65535]}}, +{"from_linear":{"hex":"#2e0000ff","u16":[11853,0,0,65535]},"in":"#070000ff","to_linear":{"hex":"#010000ff","u16":[139,0,0,65535]}}, +{"from_linear":{"hex":"#320000ff","u16":[12737,0,0,65535]},"in":"#080000ff","to_linear":{"hex":"#010000ff","u16":[159,0,0,65535]}}, +{"from_linear":{"hex":"#350000ff","u16":[13559,0,0,65535]},"in":"#090000ff","to_linear":{"hex":"#010000ff","u16":[179,0,0,65535]}}, +{"from_linear":{"hex":"#380000ff","u16":[14329,0,0,65535]},"in":"#0a0000ff","to_linear":{"hex":"#010000ff","u16":[199,0,0,65535]}}, +{"from_linear":{"hex":"#3b0000ff","u16":[15056,0,0,65535]},"in":"#0b0000ff","to_linear":{"hex":"#010000ff","u16":[219,0,0,65535]}}, +{"from_linear":{"hex":"#3d0000ff","u16":[15745,0,0,65535]},"in":"#0c0000ff","to_linear":{"hex":"#010000ff","u16":[241,0,0,65535]}}, +{"from_linear":{"hex":"#400000ff","u16":[16401,0,0,65535]},"in":"#0d0000ff","to_linear":{"hex":"#010000ff","u16":[264,0,0,65535]}}, +{"from_linear":{"hex":"#420000ff","u16":[17028,0,0,65535]},"in":"#0e0000ff","to_linear":{"hex":"#010000ff","u16":[288,0,0,65535]}}, +{"from_linear":{"hex":"#450000ff","u16":[17630,0,0,65535]},"in":"#0f0000ff","to_linear":{"hex":"#010000ff","u16":[313,0,0,65535]}}, +{"from_linear":{"hex":"#470000ff","u16":[18209,0,0,65535]},"in":"#100000ff","to_linear":{"hex":"#010000ff","u16":[340,0,0,65535]}}, +{"from_linear":{"hex":"#490000ff","u16":[18767,0,0,65535]},"in":"#110000ff","to_linear":{"hex":"#010000ff","u16":[367,0,0,65535]}}, +{"from_linear":{"hex":"#4b0000ff","u16":[19306,0,0,65535]},"in":"#120000ff","to_linear":{"hex":"#020000ff","u16":[396,0,0,65535]}}, +{"from_linear":{"hex":"#4d0000ff","u16":[19828,0,0,65535]},"in":"#130000ff","to_linear":{"hex":"#020000ff","u16":[427,0,0,65535]}}, +{"from_linear":{"hex":"#4f0000ff","u16":[20334,0,0,65535]},"in":"#140000ff","to_linear":{"hex":"#020000ff","u16":[458,0,0,65535]}}, +{"from_linear":{"hex":"#510000ff","u16":[20826,0,0,65535]},"in":"#150000ff","to_linear":{"hex":"#020000ff","u16":[491,0,0,65535]}}, +{"from_linear":{"hex":"#530000ff","u16":[21304,0,0,65535]},"in":"#160000ff","to_linear":{"hex":"#020000ff","u16":[526,0,0,65535]}}, +{"from_linear":{"hex":"#550000ff","u16":[21769,0,0,65535]},"in":"#170000ff","to_linear":{"hex":"#020000ff","u16":[562,0,0,65535]}}, +{"from_linear":{"hex":"#560000ff","u16":[22223,0,0,65535]},"in":"#180000ff","to_linear":{"hex":"#020000ff","u16":[599,0,0,65535]}}, +{"from_linear":{"hex":"#580000ff","u16":[22667,0,0,65535]},"in":"#190000ff","to_linear":{"hex":"#020000ff","u16":[637,0,0,65535]}}, +{"from_linear":{"hex":"#5a0000ff","u16":[23099,0,0,65535]},"in":"#1a0000ff","to_linear":{"hex":"#030000ff","u16":[677,0,0,65535]}}, +{"from_linear":{"hex":"#5c0000ff","u16":[23523,0,0,65535]},"in":"#1b0000ff","to_linear":{"hex":"#030000ff","u16":[718,0,0,65535]}}, +{"from_linear":{"hex":"#5d0000ff","u16":[23937,0,0,65535]},"in":"#1c0000ff","to_linear":{"hex":"#030000ff","u16":[761,0,0,65535]}}, +{"from_linear":{"hex":"#5f0000ff","u16":[24342,0,0,65535]},"in":"#1d0000ff","to_linear":{"hex":"#030000ff","u16":[805,0,0,65535]}}, +{"from_linear":{"hex":"#600000ff","u16":[24740,0,0,65535]},"in":"#1e0000ff","to_linear":{"hex":"#030000ff","u16":[851,0,0,65535]}}, +{"from_linear":{"hex":"#620000ff","u16":[25130,0,0,65535]},"in":"#1f0000ff","to_linear":{"hex":"#030000ff","u16":[898,0,0,65535]}}, +{"from_linear":{"hex":"#630000ff","u16":[25513,0,0,65535]},"in":"#200000ff","to_linear":{"hex":"#040000ff","u16":[947,0,0,65535]}}, +{"from_linear":{"hex":"#650000ff","u16":[25888,0,0,65535]},"in":"#210000ff","to_linear":{"hex":"#040000ff","u16":[997,0,0,65535]}}, +{"from_linear":{"hex":"#660000ff","u16":[26257,0,0,65535]},"in":"#220000ff","to_linear":{"hex":"#040000ff","u16":[1048,0,0,65535]}}, +{"from_linear":{"hex":"#680000ff","u16":[26620,0,0,65535]},"in":"#230000ff","to_linear":{"hex":"#040000ff","u16":[1101,0,0,65535]}}, +{"from_linear":{"hex":"#690000ff","u16":[26977,0,0,65535]},"in":"#240000ff","to_linear":{"hex":"#040000ff","u16":[1156,0,0,65535]}}, +{"from_linear":{"hex":"#6a0000ff","u16":[27328,0,0,65535]},"in":"#250000ff","to_linear":{"hex":"#050000ff","u16":[1212,0,0,65535]}}, +{"from_linear":{"hex":"#6c0000ff","u16":[27674,0,0,65535]},"in":"#260000ff","to_linear":{"hex":"#050000ff","u16":[1270,0,0,65535]}}, +{"from_linear":{"hex":"#6d0000ff","u16":[28014,0,0,65535]},"in":"#270000ff","to_linear":{"hex":"#050000ff","u16":[1330,0,0,65535]}}, +{"from_linear":{"hex":"#6e0000ff","u16":[28350,0,0,65535]},"in":"#280000ff","to_linear":{"hex":"#050000ff","u16":[1391,0,0,65535]}}, +{"from_linear":{"hex":"#700000ff","u16":[28680,0,0,65535]},"in":"#290000ff","to_linear":{"hex":"#060000ff","u16":[1453,0,0,65535]}}, +{"from_linear":{"hex":"#710000ff","u16":[29006,0,0,65535]},"in":"#2a0000ff","to_linear":{"hex":"#060000ff","u16":[1517,0,0,65535]}}, +{"from_linear":{"hex":"#720000ff","u16":[29327,0,0,65535]},"in":"#2b0000ff","to_linear":{"hex":"#060000ff","u16":[1583,0,0,65535]}}, +{"from_linear":{"hex":"#730000ff","u16":[29644,0,0,65535]},"in":"#2c0000ff","to_linear":{"hex":"#060000ff","u16":[1651,0,0,65535]}}, +{"from_linear":{"hex":"#750000ff","u16":[29957,0,0,65535]},"in":"#2d0000ff","to_linear":{"hex":"#070000ff","u16":[1720,0,0,65535]}}, +{"from_linear":{"hex":"#760000ff","u16":[30266,0,0,65535]},"in":"#2e0000ff","to_linear":{"hex":"#070000ff","u16":[1790,0,0,65535]}}, +{"from_linear":{"hex":"#770000ff","u16":[30571,0,0,65535]},"in":"#2f0000ff","to_linear":{"hex":"#070000ff","u16":[1863,0,0,65535]}}, +{"from_linear":{"hex":"#780000ff","u16":[30872,0,0,65535]},"in":"#300000ff","to_linear":{"hex":"#080000ff","u16":[1937,0,0,65535]}}, +{"from_linear":{"hex":"#790000ff","u16":[31169,0,0,65535]},"in":"#310000ff","to_linear":{"hex":"#080000ff","u16":[2013,0,0,65535]}}, +{"from_linear":{"hex":"#7a0000ff","u16":[31463,0,0,65535]},"in":"#320000ff","to_linear":{"hex":"#080000ff","u16":[2090,0,0,65535]}}, +{"from_linear":{"hex":"#7c0000ff","u16":[31754,0,0,65535]},"in":"#330000ff","to_linear":{"hex":"#080000ff","u16":[2170,0,0,65535]}}, +{"from_linear":{"hex":"#7d0000ff","u16":[32041,0,0,65535]},"in":"#340000ff","to_linear":{"hex":"#090000ff","u16":[2250,0,0,65535]}}, +{"from_linear":{"hex":"#7e0000ff","u16":[32325,0,0,65535]},"in":"#350000ff","to_linear":{"hex":"#090000ff","u16":[2333,0,0,65535]}}, +{"from_linear":{"hex":"#7f0000ff","u16":[32606,0,0,65535]},"in":"#360000ff","to_linear":{"hex":"#090000ff","u16":[2418,0,0,65535]}}, +{"from_linear":{"hex":"#800000ff","u16":[32884,0,0,65535]},"in":"#370000ff","to_linear":{"hex":"#0a0000ff","u16":[2504,0,0,65535]}}, +{"from_linear":{"hex":"#810000ff","u16":[33159,0,0,65535]},"in":"#380000ff","to_linear":{"hex":"#0a0000ff","u16":[2592,0,0,65535]}}, +{"from_linear":{"hex":"#820000ff","u16":[33431,0,0,65535]},"in":"#390000ff","to_linear":{"hex":"#0a0000ff","u16":[2681,0,0,65535]}}, +{"from_linear":{"hex":"#830000ff","u16":[33700,0,0,65535]},"in":"#3a0000ff","to_linear":{"hex":"#0b0000ff","u16":[2773,0,0,65535]}}, +{"from_linear":{"hex":"#840000ff","u16":[33967,0,0,65535]},"in":"#3b0000ff","to_linear":{"hex":"#0b0000ff","u16":[2866,0,0,65535]}}, +{"from_linear":{"hex":"#850000ff","u16":[34231,0,0,65535]},"in":"#3c0000ff","to_linear":{"hex":"#0c0000ff","u16":[2961,0,0,65535]}}, +{"from_linear":{"hex":"#860000ff","u16":[34492,0,0,65535]},"in":"#3d0000ff","to_linear":{"hex":"#0c0000ff","u16":[3058,0,0,65535]}}, +{"from_linear":{"hex":"#870000ff","u16":[34751,0,0,65535]},"in":"#3e0000ff","to_linear":{"hex":"#0c0000ff","u16":[3157,0,0,65535]}}, +{"from_linear":{"hex":"#880000ff","u16":[35008,0,0,65535]},"in":"#3f0000ff","to_linear":{"hex":"#0d0000ff","u16":[3258,0,0,65535]}}, +{"from_linear":{"hex":"#890000ff","u16":[35262,0,0,65535]},"in":"#400000ff","to_linear":{"hex":"#0d0000ff","u16":[3360,0,0,65535]}}, +{"from_linear":{"hex":"#8a0000ff","u16":[35514,0,0,65535]},"in":"#410000ff","to_linear":{"hex":"#0d0000ff","u16":[3464,0,0,65535]}}, +{"from_linear":{"hex":"#8b0000ff","u16":[35764,0,0,65535]},"in":"#420000ff","to_linear":{"hex":"#0e0000ff","u16":[3570,0,0,65535]}}, +{"from_linear":{"hex":"#8c0000ff","u16":[36011,0,0,65535]},"in":"#430000ff","to_linear":{"hex":"#0e0000ff","u16":[3678,0,0,65535]}}, +{"from_linear":{"hex":"#8d0000ff","u16":[36256,0,0,65535]},"in":"#440000ff","to_linear":{"hex":"#0f0000ff","u16":[3788,0,0,65535]}}, +{"from_linear":{"hex":"#8e0000ff","u16":[36500,0,0,65535]},"in":"#450000ff","to_linear":{"hex":"#0f0000ff","u16":[3900,0,0,65535]}}, +{"from_linear":{"hex":"#8f0000ff","u16":[36741,0,0,65535]},"in":"#460000ff","to_linear":{"hex":"#100000ff","u16":[4014,0,0,65535]}}, +{"from_linear":{"hex":"#900000ff","u16":[36980,0,0,65535]},"in":"#470000ff","to_linear":{"hex":"#100000ff","u16":[4129,0,0,65535]}}, +{"from_linear":{"hex":"#910000ff","u16":[37217,0,0,65535]},"in":"#480000ff","to_linear":{"hex":"#110000ff","u16":[4247,0,0,65535]}}, +{"from_linear":{"hex":"#920000ff","u16":[37452,0,0,65535]},"in":"#490000ff","to_linear":{"hex":"#110000ff","u16":[4366,0,0,65535]}}, +{"from_linear":{"hex":"#930000ff","u16":[37686,0,0,65535]},"in":"#4a0000ff","to_linear":{"hex":"#110000ff","u16":[4488,0,0,65535]}}, +{"from_linear":{"hex":"#940000ff","u16":[37917,0,0,65535]},"in":"#4b0000ff","to_linear":{"hex":"#120000ff","u16":[4611,0,0,65535]}}, +{"from_linear":{"hex":"#940000ff","u16":[38147,0,0,65535]},"in":"#4c0000ff","to_linear":{"hex":"#120000ff","u16":[4736,0,0,65535]}}, +{"from_linear":{"hex":"#950000ff","u16":[38375,0,0,65535]},"in":"#4d0000ff","to_linear":{"hex":"#130000ff","u16":[4864,0,0,65535]}}, +{"from_linear":{"hex":"#960000ff","u16":[38601,0,0,65535]},"in":"#4e0000ff","to_linear":{"hex":"#130000ff","u16":[4993,0,0,65535]}}, +{"from_linear":{"hex":"#970000ff","u16":[38826,0,0,65535]},"in":"#4f0000ff","to_linear":{"hex":"#140000ff","u16":[5124,0,0,65535]}}, +{"from_linear":{"hex":"#980000ff","u16":[39049,0,0,65535]},"in":"#500000ff","to_linear":{"hex":"#140000ff","u16":[5257,0,0,65535]}}, +{"from_linear":{"hex":"#990000ff","u16":[39270,0,0,65535]},"in":"#510000ff","to_linear":{"hex":"#150000ff","u16":[5392,0,0,65535]}}, +{"from_linear":{"hex":"#9a0000ff","u16":[39490,0,0,65535]},"in":"#520000ff","to_linear":{"hex":"#160000ff","u16":[5530,0,0,65535]}}, +{"from_linear":{"hex":"#9b0000ff","u16":[39708,0,0,65535]},"in":"#530000ff","to_linear":{"hex":"#160000ff","u16":[5669,0,0,65535]}}, +{"from_linear":{"hex":"#9b0000ff","u16":[39925,0,0,65535]},"in":"#540000ff","to_linear":{"hex":"#170000ff","u16":[5810,0,0,65535]}}, +{"from_linear":{"hex":"#9c0000ff","u16":[40140,0,0,65535]},"in":"#550000ff","to_linear":{"hex":"#170000ff","u16":[5953,0,0,65535]}}, +{"from_linear":{"hex":"#9d0000ff","u16":[40354,0,0,65535]},"in":"#560000ff","to_linear":{"hex":"#180000ff","u16":[6099,0,0,65535]}}, +{"from_linear":{"hex":"#9e0000ff","u16":[40566,0,0,65535]},"in":"#570000ff","to_linear":{"hex":"#180000ff","u16":[6246,0,0,65535]}}, +{"from_linear":{"hex":"#9f0000ff","u16":[40777,0,0,65535]},"in":"#580000ff","to_linear":{"hex":"#190000ff","u16":[6395,0,0,65535]}}, +{"from_linear":{"hex":"#9f0000ff","u16":[40987,0,0,65535]},"in":"#590000ff","to_linear":{"hex":"#190000ff","u16":[6547,0,0,65535]}}, +{"from_linear":{"hex":"#a00000ff","u16":[41195,0,0,65535]},"in":"#5a0000ff","to_linear":{"hex":"#1a0000ff","u16":[6700,0,0,65535]}}, +{"from_linear":{"hex":"#a10000ff","u16":[41401,0,0,65535]},"in":"#5b0000ff","to_linear":{"hex":"#1b0000ff","u16":[6856,0,0,65535]}}, +{"from_linear":{"hex":"#a20000ff","u16":[41607,0,0,65535]},"in":"#5c0000ff","to_linear":{"hex":"#1b0000ff","u16":[7014,0,0,65535]}}, +{"from_linear":{"hex":"#a30000ff","u16":[41811,0,0,65535]},"in":"#5d0000ff","to_linear":{"hex":"#1c0000ff","u16":[7174,0,0,65535]}}, +{"from_linear":{"hex":"#a30000ff","u16":[42014,0,0,65535]},"in":"#5e0000ff","to_linear":{"hex":"#1d0000ff","u16":[7335,0,0,65535]}}, +{"from_linear":{"hex":"#a40000ff","u16":[42215,0,0,65535]},"in":"#5f0000ff","to_linear":{"hex":"#1d0000ff","u16":[7500,0,0,65535]}}, +{"from_linear":{"hex":"#a50000ff","u16":[42416,0,0,65535]},"in":"#600000ff","to_linear":{"hex":"#1e0000ff","u16":[7666,0,0,65535]}}, +{"from_linear":{"hex":"#a60000ff","u16":[42615,0,0,65535]},"in":"#610000ff","to_linear":{"hex":"#1e0000ff","u16":[7834,0,0,65535]}}, +{"from_linear":{"hex":"#a70000ff","u16":[42813,0,0,65535]},"in":"#620000ff","to_linear":{"hex":"#1f0000ff","u16":[8004,0,0,65535]}}, +{"from_linear":{"hex":"#a70000ff","u16":[43009,0,0,65535]},"in":"#630000ff","to_linear":{"hex":"#200000ff","u16":[8177,0,0,65535]}}, +{"from_linear":{"hex":"#a80000ff","u16":[43205,0,0,65535]},"in":"#640000ff","to_linear":{"hex":"#200000ff","u16":[8352,0,0,65535]}}, +{"from_linear":{"hex":"#a90000ff","u16":[43400,0,0,65535]},"in":"#650000ff","to_linear":{"hex":"#210000ff","u16":[8528,0,0,65535]}}, +{"from_linear":{"hex":"#aa0000ff","u16":[43593,0,0,65535]},"in":"#660000ff","to_linear":{"hex":"#220000ff","u16":[8708,0,0,65535]}}, +{"from_linear":{"hex":"#aa0000ff","u16":[43785,0,0,65535]},"in":"#670000ff","to_linear":{"hex":"#230000ff","u16":[8889,0,0,65535]}}, +{"from_linear":{"hex":"#ab0000ff","u16":[43976,0,0,65535]},"in":"#680000ff","to_linear":{"hex":"#230000ff","u16":[9072,0,0,65535]}}, +{"from_linear":{"hex":"#ac0000ff","u16":[44166,0,0,65535]},"in":"#690000ff","to_linear":{"hex":"#240000ff","u16":[9258,0,0,65535]}}, +{"from_linear":{"hex":"#ad0000ff","u16":[44355,0,0,65535]},"in":"#6a0000ff","to_linear":{"hex":"#250000ff","u16":[9445,0,0,65535]}}, +{"from_linear":{"hex":"#ad0000ff","u16":[44543,0,0,65535]},"in":"#6b0000ff","to_linear":{"hex":"#250000ff","u16":[9635,0,0,65535]}}, +{"from_linear":{"hex":"#ae0000ff","u16":[44730,0,0,65535]},"in":"#6c0000ff","to_linear":{"hex":"#260000ff","u16":[9828,0,0,65535]}}, +{"from_linear":{"hex":"#af0000ff","u16":[44916,0,0,65535]},"in":"#6d0000ff","to_linear":{"hex":"#270000ff","u16":[10022,0,0,65535]}}, +{"from_linear":{"hex":"#af0000ff","u16":[45101,0,0,65535]},"in":"#6e0000ff","to_linear":{"hex":"#280000ff","u16":[10219,0,0,65535]}}, +{"from_linear":{"hex":"#b00000ff","u16":[45285,0,0,65535]},"in":"#6f0000ff","to_linear":{"hex":"#290000ff","u16":[10417,0,0,65535]}}, +{"from_linear":{"hex":"#b10000ff","u16":[45468,0,0,65535]},"in":"#700000ff","to_linear":{"hex":"#290000ff","u16":[10619,0,0,65535]}}, +{"from_linear":{"hex":"#b20000ff","u16":[45651,0,0,65535]},"in":"#710000ff","to_linear":{"hex":"#2a0000ff","u16":[10822,0,0,65535]}}, +{"from_linear":{"hex":"#b20000ff","u16":[45832,0,0,65535]},"in":"#720000ff","to_linear":{"hex":"#2b0000ff","u16":[11028,0,0,65535]}}, +{"from_linear":{"hex":"#b30000ff","u16":[46012,0,0,65535]},"in":"#730000ff","to_linear":{"hex":"#2c0000ff","u16":[11235,0,0,65535]}}, +{"from_linear":{"hex":"#b40000ff","u16":[46191,0,0,65535]},"in":"#740000ff","to_linear":{"hex":"#2d0000ff","u16":[11446,0,0,65535]}}, +{"from_linear":{"hex":"#b40000ff","u16":[46370,0,0,65535]},"in":"#750000ff","to_linear":{"hex":"#2d0000ff","u16":[11658,0,0,65535]}}, +{"from_linear":{"hex":"#b50000ff","u16":[46547,0,0,65535]},"in":"#760000ff","to_linear":{"hex":"#2e0000ff","u16":[11873,0,0,65535]}}, +{"from_linear":{"hex":"#b60000ff","u16":[46724,0,0,65535]},"in":"#770000ff","to_linear":{"hex":"#2f0000ff","u16":[12090,0,0,65535]}}, +{"from_linear":{"hex":"#b60000ff","u16":[46900,0,0,65535]},"in":"#780000ff","to_linear":{"hex":"#300000ff","u16":[12309,0,0,65535]}}, +{"from_linear":{"hex":"#b70000ff","u16":[47075,0,0,65535]},"in":"#790000ff","to_linear":{"hex":"#310000ff","u16":[12530,0,0,65535]}}, +{"from_linear":{"hex":"#b80000ff","u16":[47249,0,0,65535]},"in":"#7a0000ff","to_linear":{"hex":"#320000ff","u16":[12754,0,0,65535]}}, +{"from_linear":{"hex":"#b90000ff","u16":[47422,0,0,65535]},"in":"#7b0000ff","to_linear":{"hex":"#330000ff","u16":[12980,0,0,65535]}}, +{"from_linear":{"hex":"#b90000ff","u16":[47594,0,0,65535]},"in":"#7c0000ff","to_linear":{"hex":"#330000ff","u16":[13209,0,0,65535]}}, +{"from_linear":{"hex":"#ba0000ff","u16":[47766,0,0,65535]},"in":"#7d0000ff","to_linear":{"hex":"#340000ff","u16":[13440,0,0,65535]}}, +{"from_linear":{"hex":"#bb0000ff","u16":[47937,0,0,65535]},"in":"#7e0000ff","to_linear":{"hex":"#350000ff","u16":[13673,0,0,65535]}}, +{"from_linear":{"hex":"#bb0000ff","u16":[48107,0,0,65535]},"in":"#7f0000ff","to_linear":{"hex":"#360000ff","u16":[13909,0,0,65535]}}, +{"from_linear":{"hex":"#bc0000ff","u16":[48276,0,0,65535]},"in":"#800000ff","to_linear":{"hex":"#370000ff","u16":[14146,0,0,65535]}}, +{"from_linear":{"hex":"#bd0000ff","u16":[48445,0,0,65535]},"in":"#810000ff","to_linear":{"hex":"#380000ff","u16":[14387,0,0,65535]}}, +{"from_linear":{"hex":"#bd0000ff","u16":[48612,0,0,65535]},"in":"#820000ff","to_linear":{"hex":"#390000ff","u16":[14629,0,0,65535]}}, +{"from_linear":{"hex":"#be0000ff","u16":[48779,0,0,65535]},"in":"#830000ff","to_linear":{"hex":"#3a0000ff","u16":[14874,0,0,65535]}}, +{"from_linear":{"hex":"#be0000ff","u16":[48946,0,0,65535]},"in":"#840000ff","to_linear":{"hex":"#3b0000ff","u16":[15122,0,0,65535]}}, +{"from_linear":{"hex":"#bf0000ff","u16":[49111,0,0,65535]},"in":"#850000ff","to_linear":{"hex":"#3c0000ff","u16":[15371,0,0,65535]}}, +{"from_linear":{"hex":"#c00000ff","u16":[49276,0,0,65535]},"in":"#860000ff","to_linear":{"hex":"#3d0000ff","u16":[15623,0,0,65535]}}, +{"from_linear":{"hex":"#c00000ff","u16":[49440,0,0,65535]},"in":"#870000ff","to_linear":{"hex":"#3e0000ff","u16":[15878,0,0,65535]}}, +{"from_linear":{"hex":"#c10000ff","u16":[49603,0,0,65535]},"in":"#880000ff","to_linear":{"hex":"#3f0000ff","u16":[16135,0,0,65535]}}, +{"from_linear":{"hex":"#c20000ff","u16":[49766,0,0,65535]},"in":"#890000ff","to_linear":{"hex":"#400000ff","u16":[16394,0,0,65535]}}, +{"from_linear":{"hex":"#c20000ff","u16":[49928,0,0,65535]},"in":"#8a0000ff","to_linear":{"hex":"#410000ff","u16":[16656,0,0,65535]}}, +{"from_linear":{"hex":"#c30000ff","u16":[50089,0,0,65535]},"in":"#8b0000ff","to_linear":{"hex":"#420000ff","u16":[16920,0,0,65535]}}, +{"from_linear":{"hex":"#c40000ff","u16":[50250,0,0,65535]},"in":"#8c0000ff","to_linear":{"hex":"#430000ff","u16":[17187,0,0,65535]}}, +{"from_linear":{"hex":"#c40000ff","u16":[50410,0,0,65535]},"in":"#8d0000ff","to_linear":{"hex":"#440000ff","u16":[17456,0,0,65535]}}, +{"from_linear":{"hex":"#c50000ff","u16":[50569,0,0,65535]},"in":"#8e0000ff","to_linear":{"hex":"#450000ff","u16":[17727,0,0,65535]}}, +{"from_linear":{"hex":"#c50000ff","u16":[50728,0,0,65535]},"in":"#8f0000ff","to_linear":{"hex":"#460000ff","u16":[18001,0,0,65535]}}, +{"from_linear":{"hex":"#c60000ff","u16":[50886,0,0,65535]},"in":"#900000ff","to_linear":{"hex":"#470000ff","u16":[18277,0,0,65535]}}, +{"from_linear":{"hex":"#c70000ff","u16":[51043,0,0,65535]},"in":"#910000ff","to_linear":{"hex":"#480000ff","u16":[18556,0,0,65535]}}, +{"from_linear":{"hex":"#c70000ff","u16":[51200,0,0,65535]},"in":"#920000ff","to_linear":{"hex":"#490000ff","u16":[18837,0,0,65535]}}, +{"from_linear":{"hex":"#c80000ff","u16":[51356,0,0,65535]},"in":"#930000ff","to_linear":{"hex":"#4a0000ff","u16":[19121,0,0,65535]}}, +{"from_linear":{"hex":"#c80000ff","u16":[51511,0,0,65535]},"in":"#940000ff","to_linear":{"hex":"#4c0000ff","u16":[19407,0,0,65535]}}, +{"from_linear":{"hex":"#c90000ff","u16":[51666,0,0,65535]},"in":"#950000ff","to_linear":{"hex":"#4d0000ff","u16":[19696,0,0,65535]}}, +{"from_linear":{"hex":"#ca0000ff","u16":[51821,0,0,65535]},"in":"#960000ff","to_linear":{"hex":"#4e0000ff","u16":[19987,0,0,65535]}}, +{"from_linear":{"hex":"#ca0000ff","u16":[51974,0,0,65535]},"in":"#970000ff","to_linear":{"hex":"#4f0000ff","u16":[20281,0,0,65535]}}, +{"from_linear":{"hex":"#cb0000ff","u16":[52127,0,0,65535]},"in":"#980000ff","to_linear":{"hex":"#500000ff","u16":[20577,0,0,65535]}}, +{"from_linear":{"hex":"#cb0000ff","u16":[52280,0,0,65535]},"in":"#990000ff","to_linear":{"hex":"#510000ff","u16":[20876,0,0,65535]}}, +{"from_linear":{"hex":"#cc0000ff","u16":[52432,0,0,65535]},"in":"#9a0000ff","to_linear":{"hex":"#520000ff","u16":[21177,0,0,65535]}}, +{"from_linear":{"hex":"#cd0000ff","u16":[52583,0,0,65535]},"in":"#9b0000ff","to_linear":{"hex":"#540000ff","u16":[21481,0,0,65535]}}, +{"from_linear":{"hex":"#cd0000ff","u16":[52734,0,0,65535]},"in":"#9c0000ff","to_linear":{"hex":"#550000ff","u16":[21787,0,0,65535]}}, +{"from_linear":{"hex":"#ce0000ff","u16":[52884,0,0,65535]},"in":"#9d0000ff","to_linear":{"hex":"#560000ff","u16":[22096,0,0,65535]}}, +{"from_linear":{"hex":"#ce0000ff","u16":[53034,0,0,65535]},"in":"#9e0000ff","to_linear":{"hex":"#570000ff","u16":[22407,0,0,65535]}}, +{"from_linear":{"hex":"#cf0000ff","u16":[53183,0,0,65535]},"in":"#9f0000ff","to_linear":{"hex":"#580000ff","u16":[22721,0,0,65535]}}, +{"from_linear":{"hex":"#d00000ff","u16":[53331,0,0,65535]},"in":"#a00000ff","to_linear":{"hex":"#5a0000ff","u16":[23038,0,0,65535]}}, +{"from_linear":{"hex":"#d00000ff","u16":[53479,0,0,65535]},"in":"#a10000ff","to_linear":{"hex":"#5b0000ff","u16":[23357,0,0,65535]}}, +{"from_linear":{"hex":"#d10000ff","u16":[53627,0,0,65535]},"in":"#a20000ff","to_linear":{"hex":"#5c0000ff","u16":[23678,0,0,65535]}}, +{"from_linear":{"hex":"#d10000ff","u16":[53774,0,0,65535]},"in":"#a30000ff","to_linear":{"hex":"#5d0000ff","u16":[24002,0,0,65535]}}, +{"from_linear":{"hex":"#d20000ff","u16":[53920,0,0,65535]},"in":"#a40000ff","to_linear":{"hex":"#5f0000ff","u16":[24329,0,0,65535]}}, +{"from_linear":{"hex":"#d20000ff","u16":[54066,0,0,65535]},"in":"#a50000ff","to_linear":{"hex":"#600000ff","u16":[24658,0,0,65535]}}, +{"from_linear":{"hex":"#d30000ff","u16":[54211,0,0,65535]},"in":"#a60000ff","to_linear":{"hex":"#610000ff","u16":[24990,0,0,65535]}}, +{"from_linear":{"hex":"#d40000ff","u16":[54356,0,0,65535]},"in":"#a70000ff","to_linear":{"hex":"#630000ff","u16":[25325,0,0,65535]}}, +{"from_linear":{"hex":"#d40000ff","u16":[54500,0,0,65535]},"in":"#a80000ff","to_linear":{"hex":"#640000ff","u16":[25662,0,0,65535]}}, +{"from_linear":{"hex":"#d50000ff","u16":[54644,0,0,65535]},"in":"#a90000ff","to_linear":{"hex":"#650000ff","u16":[26001,0,0,65535]}}, +{"from_linear":{"hex":"#d50000ff","u16":[54788,0,0,65535]},"in":"#aa0000ff","to_linear":{"hex":"#670000ff","u16":[26344,0,0,65535]}}, +{"from_linear":{"hex":"#d60000ff","u16":[54931,0,0,65535]},"in":"#ab0000ff","to_linear":{"hex":"#680000ff","u16":[26688,0,0,65535]}}, +{"from_linear":{"hex":"#d60000ff","u16":[55073,0,0,65535]},"in":"#ac0000ff","to_linear":{"hex":"#690000ff","u16":[27036,0,0,65535]}}, +{"from_linear":{"hex":"#d70000ff","u16":[55215,0,0,65535]},"in":"#ad0000ff","to_linear":{"hex":"#6b0000ff","u16":[27386,0,0,65535]}}, +{"from_linear":{"hex":"#d70000ff","u16":[55356,0,0,65535]},"in":"#ae0000ff","to_linear":{"hex":"#6c0000ff","u16":[27739,0,0,65535]}}, +{"from_linear":{"hex":"#d80000ff","u16":[55497,0,0,65535]},"in":"#af0000ff","to_linear":{"hex":"#6d0000ff","u16":[28094,0,0,65535]}}, +{"from_linear":{"hex":"#d80000ff","u16":[55638,0,0,65535]},"in":"#b00000ff","to_linear":{"hex":"#6f0000ff","u16":[28452,0,0,65535]}}, +{"from_linear":{"hex":"#d90000ff","u16":[55778,0,0,65535]},"in":"#b10000ff","to_linear":{"hex":"#700000ff","u16":[28813,0,0,65535]}}, +{"from_linear":{"hex":"#da0000ff","u16":[55917,0,0,65535]},"in":"#b20000ff","to_linear":{"hex":"#720000ff","u16":[29176,0,0,65535]}}, +{"from_linear":{"hex":"#da0000ff","u16":[56056,0,0,65535]},"in":"#b30000ff","to_linear":{"hex":"#730000ff","u16":[29542,0,0,65535]}}, +{"from_linear":{"hex":"#db0000ff","u16":[56195,0,0,65535]},"in":"#b40000ff","to_linear":{"hex":"#740000ff","u16":[29911,0,0,65535]}}, +{"from_linear":{"hex":"#db0000ff","u16":[56333,0,0,65535]},"in":"#b50000ff","to_linear":{"hex":"#760000ff","u16":[30282,0,0,65535]}}, +{"from_linear":{"hex":"#dc0000ff","u16":[56471,0,0,65535]},"in":"#b60000ff","to_linear":{"hex":"#770000ff","u16":[30656,0,0,65535]}}, +{"from_linear":{"hex":"#dc0000ff","u16":[56608,0,0,65535]},"in":"#b70000ff","to_linear":{"hex":"#790000ff","u16":[31033,0,0,65535]}}, +{"from_linear":{"hex":"#dd0000ff","u16":[56745,0,0,65535]},"in":"#b80000ff","to_linear":{"hex":"#7a0000ff","u16":[31412,0,0,65535]}}, +{"from_linear":{"hex":"#dd0000ff","u16":[56882,0,0,65535]},"in":"#b90000ff","to_linear":{"hex":"#7c0000ff","u16":[31794,0,0,65535]}}, +{"from_linear":{"hex":"#de0000ff","u16":[57018,0,0,65535]},"in":"#ba0000ff","to_linear":{"hex":"#7d0000ff","u16":[32179,0,0,65535]}}, +{"from_linear":{"hex":"#de0000ff","u16":[57153,0,0,65535]},"in":"#bb0000ff","to_linear":{"hex":"#7f0000ff","u16":[32567,0,0,65535]}}, +{"from_linear":{"hex":"#df0000ff","u16":[57288,0,0,65535]},"in":"#bc0000ff","to_linear":{"hex":"#800000ff","u16":[32957,0,0,65535]}}, +{"from_linear":{"hex":"#df0000ff","u16":[57423,0,0,65535]},"in":"#bd0000ff","to_linear":{"hex":"#820000ff","u16":[33350,0,0,65535]}}, +{"from_linear":{"hex":"#e00000ff","u16":[57558,0,0,65535]},"in":"#be0000ff","to_linear":{"hex":"#830000ff","u16":[33745,0,0,65535]}}, +{"from_linear":{"hex":"#e00000ff","u16":[57691,0,0,65535]},"in":"#bf0000ff","to_linear":{"hex":"#850000ff","u16":[34143,0,0,65535]}}, +{"from_linear":{"hex":"#e10000ff","u16":[57825,0,0,65535]},"in":"#c00000ff","to_linear":{"hex":"#860000ff","u16":[34544,0,0,65535]}}, +{"from_linear":{"hex":"#e20000ff","u16":[57958,0,0,65535]},"in":"#c10000ff","to_linear":{"hex":"#880000ff","u16":[34948,0,0,65535]}}, +{"from_linear":{"hex":"#e20000ff","u16":[58091,0,0,65535]},"in":"#c20000ff","to_linear":{"hex":"#8a0000ff","u16":[35355,0,0,65535]}}, +{"from_linear":{"hex":"#e30000ff","u16":[58223,0,0,65535]},"in":"#c30000ff","to_linear":{"hex":"#8b0000ff","u16":[35764,0,0,65535]}}, +{"from_linear":{"hex":"#e30000ff","u16":[58355,0,0,65535]},"in":"#c40000ff","to_linear":{"hex":"#8d0000ff","u16":[36176,0,0,65535]}}, +{"from_linear":{"hex":"#e40000ff","u16":[58487,0,0,65535]},"in":"#c50000ff","to_linear":{"hex":"#8e0000ff","u16":[36591,0,0,65535]}}, +{"from_linear":{"hex":"#e40000ff","u16":[58618,0,0,65535]},"in":"#c60000ff","to_linear":{"hex":"#900000ff","u16":[37008,0,0,65535]}}, +{"from_linear":{"hex":"#e50000ff","u16":[58748,0,0,65535]},"in":"#c70000ff","to_linear":{"hex":"#920000ff","u16":[37429,0,0,65535]}}, +{"from_linear":{"hex":"#e50000ff","u16":[58879,0,0,65535]},"in":"#c80000ff","to_linear":{"hex":"#930000ff","u16":[37852,0,0,65535]}}, +{"from_linear":{"hex":"#e60000ff","u16":[59009,0,0,65535]},"in":"#c90000ff","to_linear":{"hex":"#950000ff","u16":[38278,0,0,65535]}}, +{"from_linear":{"hex":"#e60000ff","u16":[59138,0,0,65535]},"in":"#ca0000ff","to_linear":{"hex":"#970000ff","u16":[38706,0,0,65535]}}, +{"from_linear":{"hex":"#e70000ff","u16":[59268,0,0,65535]},"in":"#cb0000ff","to_linear":{"hex":"#980000ff","u16":[39138,0,0,65535]}}, +{"from_linear":{"hex":"#e70000ff","u16":[59396,0,0,65535]},"in":"#cc0000ff","to_linear":{"hex":"#9a0000ff","u16":[39572,0,0,65535]}}, +{"from_linear":{"hex":"#e80000ff","u16":[59525,0,0,65535]},"in":"#cd0000ff","to_linear":{"hex":"#9c0000ff","u16":[40009,0,0,65535]}}, +{"from_linear":{"hex":"#e80000ff","u16":[59653,0,0,65535]},"in":"#ce0000ff","to_linear":{"hex":"#9d0000ff","u16":[40449,0,0,65535]}}, +{"from_linear":{"hex":"#e90000ff","u16":[59781,0,0,65535]},"in":"#cf0000ff","to_linear":{"hex":"#9f0000ff","u16":[40891,0,0,65535]}}, +{"from_linear":{"hex":"#e90000ff","u16":[59908,0,0,65535]},"in":"#d00000ff","to_linear":{"hex":"#a10000ff","u16":[41337,0,0,65535]}}, +{"from_linear":{"hex":"#ea0000ff","u16":[60035,0,0,65535]},"in":"#d10000ff","to_linear":{"hex":"#a30000ff","u16":[41785,0,0,65535]}}, +{"from_linear":{"hex":"#ea0000ff","u16":[60162,0,0,65535]},"in":"#d20000ff","to_linear":{"hex":"#a40000ff","u16":[42236,0,0,65535]}}, +{"from_linear":{"hex":"#eb0000ff","u16":[60288,0,0,65535]},"in":"#d30000ff","to_linear":{"hex":"#a60000ff","u16":[42690,0,0,65535]}}, +{"from_linear":{"hex":"#eb0000ff","u16":[60414,0,0,65535]},"in":"#d40000ff","to_linear":{"hex":"#a80000ff","u16":[43147,0,0,65535]}}, +{"from_linear":{"hex":"#ec0000ff","u16":[60540,0,0,65535]},"in":"#d50000ff","to_linear":{"hex":"#aa0000ff","u16":[43606,0,0,65535]}}, +{"from_linear":{"hex":"#ec0000ff","u16":[60665,0,0,65535]},"in":"#d60000ff","to_linear":{"hex":"#ab0000ff","u16":[44069,0,0,65535]}}, +{"from_linear":{"hex":"#ed0000ff","u16":[60790,0,0,65535]},"in":"#d70000ff","to_linear":{"hex":"#ad0000ff","u16":[44534,0,0,65535]}}, +{"from_linear":{"hex":"#ed0000ff","u16":[60915,0,0,65535]},"in":"#d80000ff","to_linear":{"hex":"#af0000ff","u16":[45002,0,0,65535]}}, +{"from_linear":{"hex":"#ee0000ff","u16":[61039,0,0,65535]},"in":"#d90000ff","to_linear":{"hex":"#b10000ff","u16":[45473,0,0,65535]}}, +{"from_linear":{"hex":"#ee0000ff","u16":[61163,0,0,65535]},"in":"#da0000ff","to_linear":{"hex":"#b30000ff","u16":[45947,0,0,65535]}}, +{"from_linear":{"hex":"#ee0000ff","u16":[61287,0,0,65535]},"in":"#db0000ff","to_linear":{"hex":"#b50000ff","u16":[46423,0,0,65535]}}, +{"from_linear":{"hex":"#ef0000ff","u16":[61410,0,0,65535]},"in":"#dc0000ff","to_linear":{"hex":"#b70000ff","u16":[46903,0,0,65535]}}, +{"from_linear":{"hex":"#ef0000ff","u16":[61533,0,0,65535]},"in":"#dd0000ff","to_linear":{"hex":"#b80000ff","u16":[47385,0,0,65535]}}, +{"from_linear":{"hex":"#f00000ff","u16":[61656,0,0,65535]},"in":"#de0000ff","to_linear":{"hex":"#ba0000ff","u16":[47871,0,0,65535]}}, +{"from_linear":{"hex":"#f00000ff","u16":[61778,0,0,65535]},"in":"#df0000ff","to_linear":{"hex":"#bc0000ff","u16":[48359,0,0,65535]}}, +{"from_linear":{"hex":"#f10000ff","u16":[61900,0,0,65535]},"in":"#e00000ff","to_linear":{"hex":"#be0000ff","u16":[48850,0,0,65535]}}, +{"from_linear":{"hex":"#f10000ff","u16":[62022,0,0,65535]},"in":"#e10000ff","to_linear":{"hex":"#c00000ff","u16":[49344,0,0,65535]}}, +{"from_linear":{"hex":"#f20000ff","u16":[62143,0,0,65535]},"in":"#e20000ff","to_linear":{"hex":"#c20000ff","u16":[49841,0,0,65535]}}, +{"from_linear":{"hex":"#f20000ff","u16":[62264,0,0,65535]},"in":"#e30000ff","to_linear":{"hex":"#c40000ff","u16":[50341,0,0,65535]}}, +{"from_linear":{"hex":"#f30000ff","u16":[62385,0,0,65535]},"in":"#e40000ff","to_linear":{"hex":"#c60000ff","u16":[50844,0,0,65535]}}, +{"from_linear":{"hex":"#f30000ff","u16":[62505,0,0,65535]},"in":"#e50000ff","to_linear":{"hex":"#c80000ff","u16":[51349,0,0,65535]}}, +{"from_linear":{"hex":"#f40000ff","u16":[62625,0,0,65535]},"in":"#e60000ff","to_linear":{"hex":"#ca0000ff","u16":[51858,0,0,65535]}}, +{"from_linear":{"hex":"#f40000ff","u16":[62745,0,0,65535]},"in":"#e70000ff","to_linear":{"hex":"#cc0000ff","u16":[52369,0,0,65535]}}, +{"from_linear":{"hex":"#f50000ff","u16":[62865,0,0,65535]},"in":"#e80000ff","to_linear":{"hex":"#ce0000ff","u16":[52884,0,0,65535]}}, +{"from_linear":{"hex":"#f50000ff","u16":[62984,0,0,65535]},"in":"#e90000ff","to_linear":{"hex":"#d00000ff","u16":[53401,0,0,65535]}}, +{"from_linear":{"hex":"#f60000ff","u16":[63103,0,0,65535]},"in":"#ea0000ff","to_linear":{"hex":"#d20000ff","u16":[53921,0,0,65535]}}, +{"from_linear":{"hex":"#f60000ff","u16":[63222,0,0,65535]},"in":"#eb0000ff","to_linear":{"hex":"#d40000ff","u16":[54445,0,0,65535]}}, +{"from_linear":{"hex":"#f60000ff","u16":[63340,0,0,65535]},"in":"#ec0000ff","to_linear":{"hex":"#d60000ff","u16":[54971,0,0,65535]}}, +{"from_linear":{"hex":"#f70000ff","u16":[63458,0,0,65535]},"in":"#ed0000ff","to_linear":{"hex":"#d80000ff","u16":[55500,0,0,65535]}}, +{"from_linear":{"hex":"#f70000ff","u16":[63576,0,0,65535]},"in":"#ee0000ff","to_linear":{"hex":"#da0000ff","u16":[56032,0,0,65535]}}, +{"from_linear":{"hex":"#f80000ff","u16":[63693,0,0,65535]},"in":"#ef0000ff","to_linear":{"hex":"#dc0000ff","u16":[56567,0,0,65535]}}, +{"from_linear":{"hex":"#f80000ff","u16":[63810,0,0,65535]},"in":"#f00000ff","to_linear":{"hex":"#de0000ff","u16":[57105,0,0,65535]}}, +{"from_linear":{"hex":"#f90000ff","u16":[63927,0,0,65535]},"in":"#f10000ff","to_linear":{"hex":"#e00000ff","u16":[57646,0,0,65535]}}, +{"from_linear":{"hex":"#f90000ff","u16":[64044,0,0,65535]},"in":"#f20000ff","to_linear":{"hex":"#e20000ff","u16":[58190,0,0,65535]}}, +{"from_linear":{"hex":"#fa0000ff","u16":[64160,0,0,65535]},"in":"#f30000ff","to_linear":{"hex":"#e50000ff","u16":[58737,0,0,65535]}}, +{"from_linear":{"hex":"#fa0000ff","u16":[64276,0,0,65535]},"in":"#f40000ff","to_linear":{"hex":"#e70000ff","u16":[59287,0,0,65535]}}, +{"from_linear":{"hex":"#fb0000ff","u16":[64392,0,0,65535]},"in":"#f50000ff","to_linear":{"hex":"#e90000ff","u16":[59840,0,0,65535]}}, +{"from_linear":{"hex":"#fb0000ff","u16":[64508,0,0,65535]},"in":"#f60000ff","to_linear":{"hex":"#eb0000ff","u16":[60396,0,0,65535]}}, +{"from_linear":{"hex":"#fb0000ff","u16":[64623,0,0,65535]},"in":"#f70000ff","to_linear":{"hex":"#ed0000ff","u16":[60955,0,0,65535]}}, +{"from_linear":{"hex":"#fc0000ff","u16":[64738,0,0,65535]},"in":"#f80000ff","to_linear":{"hex":"#ef0000ff","u16":[61517,0,0,65535]}}, +{"from_linear":{"hex":"#fc0000ff","u16":[64852,0,0,65535]},"in":"#f90000ff","to_linear":{"hex":"#f20000ff","u16":[62082,0,0,65535]}}, +{"from_linear":{"hex":"#fd0000ff","u16":[64967,0,0,65535]},"in":"#fa0000ff","to_linear":{"hex":"#f40000ff","u16":[62650,0,0,65535]}}, +{"from_linear":{"hex":"#fd0000ff","u16":[65081,0,0,65535]},"in":"#fb0000ff","to_linear":{"hex":"#f60000ff","u16":[63221,0,0,65535]}}, +{"from_linear":{"hex":"#fe0000ff","u16":[65195,0,0,65535]},"in":"#fc0000ff","to_linear":{"hex":"#f80000ff","u16":[63795,0,0,65535]}}, +{"from_linear":{"hex":"#fe0000ff","u16":[65309,0,0,65535]},"in":"#fd0000ff","to_linear":{"hex":"#fa0000ff","u16":[64372,0,0,65535]}}, +{"from_linear":{"hex":"#ff0000ff","u16":[65422,0,0,65535]},"in":"#fe0000ff","to_linear":{"hex":"#fd0000ff","u16":[64952,0,0,65535]}}, +{"from_linear":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"in":"#ff0000ff","to_linear":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}} +], +"qt_extended": [ +{"in":[1.0499999523162842,0.12345679104328156,-0.009999999776482582],"stored":{"f":[1.0498046875,0.12347412109375,-0.01000213623046875,1],"hex":"#ff1f00ff","spec":"ExtendedRgb","u16":[65535,8092,0,65535]},"to_rgb":{"hex":"#ff1f00ff","u16":[65535,8092,0,65535]}}, +{"in":[-1.0000000116860974e-07,0.5,0.75],"stored":{"f":[-1.1920928955078125e-07,0.5,0.75,1],"hex":"#0080bfff","spec":"ExtendedRgb","u16":[0,32768,49151,65535]},"to_rgb":{"hex":"#0080bfff","u16":[0,32768,49151,65535]}}, +{"in":[1.0000001192092896,0.3333333432674408,0.6666666865348816],"stored":{"f":[1,0.333251953125,0.66650390625,1],"hex":"#ff55aaff","spec":"ExtendedRgb","u16":[65535,21840,43679,65535]},"to_rgb":{"hex":"#ff55aaff","u16":[65535,21840,43679,65535]}}, +{"in":[2,-1,9.999999747378752e-05],"stored":{"f":[2,-1,0.00010001659393310547,1],"hex":"#ff0000ff","spec":"ExtendedRgb","u16":[65535,0,7,65535]},"to_rgb":{"hex":"#ff0000ff","u16":[65535,0,7,65535]}}, +{"in":[0.9990000128746033,1.0010000467300415,0.20000000298023224],"stored":{"f":[0.9990234375,1.0009765625,0.199951171875,1],"hex":"#ffff33ff","spec":"ExtendedRgb","u16":[65471,65535,13104,65535]},"to_rgb":{"hex":"#ffff33ff","u16":[65471,65535,13104,65535]}}, +{"in":[0.10000000149011612,0.20000000298023224,1.5],"stored":{"f":[0.0999755859375,0.199951171875,1.5,1],"hex":"#1933ffff","spec":"ExtendedRgb","u16":[6552,13104,65535,65535]},"to_rgb":{"hex":"#1933ffff","u16":[6552,13104,65535,65535]}} +], +"qt_color_text": [ +{"in":"#abc","out":{"hex":"#aabbccff","u16":[43690,48059,52428,65535]}}, +{"in":"#3daee9","out":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}}, +{"in":"#803daee9","out":{"hex":"#3daee980","u16":[15677,44718,59881,32896]}}, +{"in":"#123456789","out":{"hex":"#124578ff","u16":[4657,17764,30871,65535]}}, +{"in":"#123456789abc","out":{"hex":"#12569aff","u16":[4660,22136,39612,65535]}}, +{"in":"#12","out":{"valid":false}}, +{"in":"#12345","out":{"valid":false}}, +{"in":"#1234567","out":{"valid":false}}, +{"in":"#xyzxyz","out":{"valid":false}}, +{"in":"red","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"in":"Red","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"in":"RED","out":{"hex":"#ff0000ff","u16":[65535,0,0,65535]}}, +{"in":"transparent","out":{"hex":"#00000000","u16":[0,0,0,0]}}, +{"in":"rebeccapurple","out":{"valid":false}}, +{"in":"grey","out":{"hex":"#808080ff","u16":[32896,32896,32896,65535]}}, +{"in":"notacolour","out":{"valid":false}}, +{"in":" #3daee9","out":{"valid":false}}, +{"in":"","out":{"valid":false}} +] +} diff --git a/tools/kde_golden/vectors/pixels.json b/tools/kde_golden/vectors/pixels.json new file mode 100644 index 000000000..6f0fb8bb6 --- /dev/null +++ b/tools/kde_golden/vectors/pixels.json @@ -0,0 +1,147 @@ +{ +"meta": {"generator": "tools/kde_golden/driver (kde_golden)", "image": "invent-registry.kde.org/neon/docker-images/plasma@sha256:f58f9b97f218ee976cf928f6fe00abb929a644aded8c9dfb02f77a9b2a6e1732", "kcolorscheme_build": "6.31.0", "kguiaddons_build": "6.31.0", "kind": "pixels", "packages": {"breeze": "4:6.8.0-0zneon+24.04+noble+release+build80", "fonts-hack": "3.003-3", "fonts-noto-core": "20201225-2", "kde-style-breeze": "4:6.8.0-0zneon+24.04+noble+release+build80", "kde-style-oxygen": "4:6.8.0-0zneon+24.04+noble+release+build59", "kf6-kcolorscheme": "6.31.0-0zneon+24.04+noble+release+build47", "kf6-kconfig": "6.31.0-0zneon+24.04+noble+release+build36", "kf6-kguiaddons": "6.31.0-0zneon+24.04+noble+release+build59", "libkf6colorscheme-dev": "6.31.0-0zneon+24.04+noble+release+build47", "plasma-workspace": "4:6.8.0-0zneon+24.04+noble+release+build99", "qt6-base": "6.11.1-0zneon+24.04+noble+release+build162"}, "qt_build": "6.11.1", "qt_runtime": "6.11.1", "readme": "tools/kde_golden/README.md documents every field"}, +"schemes": { +"BreezeLight": { +"environment": {"image_format": "ARGB32, filled opaque with the palette background, then painted", "kdeglobals_color_scheme": "BreezeLight", "platform": "offscreen", "scheme": "BreezeLight", "style": "breeze"}, +"records": [ +{"background": "#eff0f1ff", "cols": {"0": [[0, 34, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e3e4e5ff"], [4, 1, "#d5d5d7ff"], [5, 1, "#d3d3d4ff"], [6, 22, "#d1d1d2ff"], [28, 1, "#d3d3d4ff"], [29, 1, "#d4d4d5ff"], [30, 1, "#d8d9daff"], [31, 1, "#e8e9eaff"], [32, 2, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#dbdcddff"], [3, 1, "#d5d5d6ff"], [4, 1, "#ededeeff"], [5, 1, "#f7f7f8ff"], [6, 22, "#fcfcfcff"], [28, 1, "#f7f7f8ff"], [29, 1, "#ededeeff"], [30, 1, "#d5d5d7ff"], [31, 1, "#d5d5d6ff"], [32, 1, "#e2e3e4ff"], [33, 1, "#eff0f1ff"]], "42": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 30, "#fcfcfcff"], [32, 1, "#d1d1d2ff"], [33, 1, "#d1d2d3ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.normal", "rows": {"0": [[0, 84, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e3e4e5ff"], [4, 1, "#d5d5d7ff"], [5, 1, "#d3d3d4ff"], [6, 72, "#d1d1d2ff"], [78, 1, "#d3d3d4ff"], [79, 1, "#d5d5d7ff"], [80, 1, "#e3e4e5ff"], [81, 3, "#eff0f1ff"]], "17": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 80, "#fcfcfcff"], [82, 1, "#d1d1d2ff"], [83, 1, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#dbdcddff"], [3, 1, "#d5d5d6ff"], [4, 1, "#ededeeff"], [5, 1, "#f7f7f8ff"], [6, 72, "#fcfcfcff"], [78, 1, "#f7f7f8ff"], [79, 1, "#ededeeff"], [80, 1, "#d5d5d6ff"], [81, 1, "#dbdcddff"], [82, 2, "#eff0f1ff"]], "32": [[0, 2, "#eff0f1ff"], [2, 1, "#e2e3e4ff"], [3, 1, "#d5d6d6ff"], [4, 1, "#d8d8d9ff"], [5, 1, "#d5d5d6ff"], [6, 72, "#d1d1d2ff"], [78, 1, "#d5d5d6ff"], [79, 1, "#d8d8d9ff"], [80, 1, "#d5d6d6ff"], [81, 1, "#e2e3e4ff"], [82, 2, "#eff0f1ff"]], "33": [[0, 3, "#eff0f1ff"], [3, 1, "#e8e9eaff"], [4, 1, "#dadbdcff"], [5, 1, "#d4d5d6ff"], [6, 72, "#d1d2d3ff"], [78, 1, "#d4d5d6ff"], [79, 1, "#dadbdcff"], [80, 1, "#e8e9eaff"], [81, 3, "#eff0f1ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 2, "group": "Active", "name": "normal"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 34, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c2dceaff"], [4, 1, "#70bde7ff"], [5, 1, "#4eb3e8ff"], [6, 22, "#3daee9ff"], [28, 1, "#4eb3e8ff"], [29, 1, "#6fbce5ff"], [30, 1, "#b7d1dfff"], [31, 1, "#e8e9eaff"], [32, 2, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#99cce7ff"], [3, 1, "#56b7eaff"], [4, 1, "#bce2f6ff"], [5, 1, "#e9f4faff"], [6, 22, "#fcfcfcff"], [28, 1, "#e9f4faff"], [29, 1, "#bce2f6ff"], [30, 1, "#56b7ebff"], [31, 1, "#93c5e0ff"], [32, 1, "#e2e3e4ff"], [33, 1, "#eff0f1ff"]], "42": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 30, "#fcfcfcff"], [32, 1, "#3daee9ff"], [33, 1, "#d1d2d3ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.hover", "rows": {"0": [[0, 84, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c2dceaff"], [4, 1, "#70bde7ff"], [5, 1, "#4eb3e8ff"], [6, 72, "#3daee9ff"], [78, 1, "#4eb3e8ff"], [79, 1, "#70bde7ff"], [80, 1, "#c2dceaff"], [81, 3, "#eff0f1ff"]], "17": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 80, "#fcfcfcff"], [82, 1, "#3daee9ff"], [83, 1, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#99cce7ff"], [3, 1, "#56b7eaff"], [4, 1, "#bce2f6ff"], [5, 1, "#e9f4faff"], [6, 72, "#fcfcfcff"], [78, 1, "#e9f4faff"], [79, 1, "#bce2f6ff"], [80, 1, "#56b7eaff"], [81, 1, "#99cde7ff"], [82, 2, "#eff0f1ff"]], "32": [[0, 2, "#eff0f1ff"], [2, 1, "#e2e3e4ff"], [3, 1, "#b4cedbff"], [4, 1, "#73c0e9ff"], [5, 1, "#50b5eaff"], [6, 72, "#3daee9ff"], [78, 1, "#50b5eaff"], [79, 1, "#73c0e9ff"], [80, 1, "#b5cfdbff"], [81, 1, "#e2e3e4ff"], [82, 2, "#eff0f1ff"]], "33": [[0, 3, "#eff0f1ff"], [3, 1, "#e8e9eaff"], [4, 1, "#dadbdcff"], [5, 1, "#d4d5d6ff"], [6, 72, "#d1d2d3ff"], [78, 1, "#d4d5d6ff"], [79, 1, "#dadbdcff"], [80, 1, "#e8e9eaff"], [81, 3, "#eff0f1ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 8194, "group": "Active", "name": "hover"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 34, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c2dceaff"], [4, 1, "#70bde7ff"], [5, 1, "#4eb3e8ff"], [6, 22, "#3daee9ff"], [28, 1, "#4eb3e8ff"], [29, 1, "#6fbce5ff"], [30, 1, "#b7d1dfff"], [31, 1, "#e8e9eaff"], [32, 2, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#99cce7ff"], [3, 1, "#56b7eaff"], [4, 1, "#bce2f6ff"], [5, 1, "#e9f4faff"], [6, 22, "#fcfcfcff"], [28, 1, "#e9f4faff"], [29, 1, "#bce2f6ff"], [30, 1, "#56b7ebff"], [31, 1, "#93c5e0ff"], [32, 1, "#e2e3e4ff"], [33, 1, "#eff0f1ff"]], "42": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 30, "#fcfcfcff"], [32, 1, "#3daee9ff"], [33, 1, "#d1d2d3ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.focus", "rows": {"0": [[0, 84, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c2dceaff"], [4, 1, "#70bde7ff"], [5, 1, "#4eb3e8ff"], [6, 72, "#3daee9ff"], [78, 1, "#4eb3e8ff"], [79, 1, "#70bde7ff"], [80, 1, "#c2dceaff"], [81, 3, "#eff0f1ff"]], "17": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 80, "#fcfcfcff"], [82, 1, "#3daee9ff"], [83, 1, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#99cce7ff"], [3, 1, "#56b7eaff"], [4, 1, "#bce2f6ff"], [5, 1, "#e9f4faff"], [6, 72, "#fcfcfcff"], [78, 1, "#e9f4faff"], [79, 1, "#bce2f6ff"], [80, 1, "#56b7eaff"], [81, 1, "#99cde7ff"], [82, 2, "#eff0f1ff"]], "32": [[0, 2, "#eff0f1ff"], [2, 1, "#e2e3e4ff"], [3, 1, "#b4cedbff"], [4, 1, "#73c0e9ff"], [5, 1, "#50b5eaff"], [6, 72, "#3daee9ff"], [78, 1, "#50b5eaff"], [79, 1, "#73c0e9ff"], [80, 1, "#b5cfdbff"], [81, 1, "#e2e3e4ff"], [82, 2, "#eff0f1ff"]], "33": [[0, 3, "#eff0f1ff"], [3, 1, "#e8e9eaff"], [4, 1, "#dadbdcff"], [5, 1, "#d4d5d6ff"], [6, 72, "#d1d2d3ff"], [78, 1, "#d4d5d6ff"], [79, 1, "#dadbdcff"], [80, 1, "#e8e9eaff"], [81, 3, "#eff0f1ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 8388866, "group": "Active", "name": "focus"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 34, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#72c2ecff"], [5, 1, "#4eb4eaff"], [6, 22, "#3daee9ff"], [28, 1, "#4eb4eaff"], [29, 1, "#72c2ecff"], [30, 1, "#c8e1efff"], [31, 3, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#9ed1edff"], [3, 1, "#52b6eaff"], [4, 1, "#96d3f2ff"], [5, 1, "#b6e0f5ff"], [6, 22, "#c3e5f6ff"], [28, 1, "#b6e0f5ff"], [29, 1, "#96d3f2ff"], [30, 1, "#52b6eaff"], [31, 1, "#9ed1edff"], [32, 2, "#eff0f1ff"]], "42": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 30, "#c3e5f6ff"], [32, 1, "#3daee9ff"], [33, 1, "#eff0f1ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.pressed", "rows": {"0": [[0, 84, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#72c2ecff"], [5, 1, "#4eb4eaff"], [6, 72, "#3daee9ff"], [78, 1, "#4eb4eaff"], [79, 1, "#72c2ecff"], [80, 1, "#c8e1efff"], [81, 3, "#eff0f1ff"]], "17": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 80, "#c3e5f6ff"], [82, 1, "#3daee9ff"], [83, 1, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#9ed1edff"], [3, 1, "#52b6eaff"], [4, 1, "#96d3f2ff"], [5, 1, "#b6e0f5ff"], [6, 72, "#c3e5f6ff"], [78, 1, "#b6e0f5ff"], [79, 1, "#96d3f2ff"], [80, 1, "#52b6eaff"], [81, 1, "#9dd2edff"], [82, 2, "#eff0f1ff"]], "32": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#72c2ecff"], [5, 1, "#4eb4eaff"], [6, 72, "#3daee9ff"], [78, 1, "#4eb4eaff"], [79, 1, "#72c2ecff"], [80, 1, "#c8e2efff"], [81, 3, "#eff0f1ff"]], "33": [[0, 84, "#eff0f1ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 4, "group": "Active", "name": "pressed"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 34, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#d3e0e8ff"], [4, 1, "#a1cadfff"], [5, 1, "#8fc3ddff"], [6, 22, "#87c0ddff"], [28, 1, "#8fc2ddff"], [29, 1, "#a0c8ddff"], [30, 1, "#c8d5ddff"], [31, 1, "#e8e9eaff"], [32, 2, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#b9d4e2ff"], [3, 1, "#92c5dfff"], [4, 1, "#bcddefff"], [5, 1, "#cee8f6ff"], [6, 22, "#d6ecf8ff"], [28, 1, "#cee8f6ff"], [29, 1, "#bcddefff"], [30, 1, "#93c6e0ff"], [31, 1, "#b2cedbff"], [32, 1, "#e2e3e4ff"], [33, 1, "#eff0f1ff"]], "42": [[0, 1, "#eff0f1ff"], [1, 1, "#87c0ddff"], [2, 30, "#d6ecf8ff"], [32, 1, "#87c0ddff"], [33, 1, "#d1d2d3ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.default", "rows": {"0": [[0, 84, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#d3e0e8ff"], [4, 1, "#a1cadfff"], [5, 1, "#8fc3ddff"], [6, 72, "#87c0ddff"], [78, 1, "#8fc3ddff"], [79, 1, "#a1cadfff"], [80, 1, "#d3e0e8ff"], [81, 3, "#eff0f1ff"]], "17": [[0, 1, "#eff0f1ff"], [1, 1, "#87c0ddff"], [2, 80, "#d6ecf8ff"], [82, 1, "#87c0ddff"], [83, 1, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#b9d4e2ff"], [3, 1, "#92c5dfff"], [4, 1, "#bcddefff"], [5, 1, "#cee8f6ff"], [6, 72, "#d6ecf8ff"], [78, 1, "#cee8f6ff"], [79, 1, "#bcddefff"], [80, 1, "#92c5dfff"], [81, 1, "#b9d4e2ff"], [82, 2, "#eff0f1ff"]], "32": [[0, 2, "#eff0f1ff"], [2, 1, "#e2e3e4ff"], [3, 1, "#c5d2d9ff"], [4, 1, "#a4cde1ff"], [5, 1, "#90c4dfff"], [6, 72, "#87c0ddff"], [78, 1, "#90c4dfff"], [79, 1, "#a4cde1ff"], [80, 1, "#c5d2d9ff"], [81, 1, "#e2e3e4ff"], [82, 2, "#eff0f1ff"]], "33": [[0, 3, "#eff0f1ff"], [3, 1, "#e8e9eaff"], [4, 1, "#dadbdcff"], [5, 1, "#d4d5d6ff"], [6, 72, "#d1d2d3ff"], [78, 1, "#d4d5d6ff"], [79, 1, "#dadbdcff"], [80, 1, "#e8e9eaff"], [81, 3, "#eff0f1ff"]]}, "size": [84, 34], "state": {"features": 4, "flags": 2, "group": "Active", "name": "default"}}, +{"background": "#e3e5e7ff", "cols": {"0": [[0, 34, "#e3e5e7ff"]], "1": [[0, 3, "#e3e5e7ff"], [3, 1, "#e3e5e6ff"], [4, 1, "#e3e3e4ff"], [5, 1, "#e3e3e3ff"], [6, 22, "#e2e2e2ff"], [28, 1, "#e3e3e3ff"], [29, 1, "#e3e3e4ff"], [30, 1, "#e3e5e6ff"], [31, 3, "#e3e5e7ff"]], "2": [[0, 2, "#e3e5e7ff"], [2, 1, "#e3e4e5ff"], [3, 1, "#e3e3e3ff"], [4, 1, "#ebebebff"], [5, 1, "#eeeeeeff"], [6, 22, "#f0f0f0ff"], [28, 1, "#eeeeeeff"], [29, 1, "#ebebebff"], [30, 1, "#e3e3e3ff"], [31, 1, "#e3e4e5ff"], [32, 2, "#e3e5e7ff"]], "42": [[0, 1, "#e3e5e7ff"], [1, 1, "#e2e2e2ff"], [2, 30, "#f0f0f0ff"], [32, 1, "#e2e2e2ff"], [33, 1, "#e3e5e7ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.disabled", "rows": {"0": [[0, 84, "#e3e5e7ff"]], "1": [[0, 3, "#e3e5e7ff"], [3, 1, "#e3e5e6ff"], [4, 1, "#e3e3e4ff"], [5, 1, "#e3e3e3ff"], [6, 72, "#e2e2e2ff"], [78, 1, "#e3e3e3ff"], [79, 1, "#e3e3e4ff"], [80, 1, "#e3e5e6ff"], [81, 3, "#e3e5e7ff"]], "17": [[0, 1, "#e3e5e7ff"], [1, 1, "#e2e2e2ff"], [2, 80, "#f0f0f0ff"], [82, 1, "#e2e2e2ff"], [83, 1, "#e3e5e7ff"]], "2": [[0, 2, "#e3e5e7ff"], [2, 1, "#e3e4e5ff"], [3, 1, "#e3e3e3ff"], [4, 1, "#ebebebff"], [5, 1, "#eeeeeeff"], [6, 72, "#f0f0f0ff"], [78, 1, "#eeeeeeff"], [79, 1, "#ebebebff"], [80, 1, "#e3e3e3ff"], [81, 1, "#e3e4e5ff"], [82, 2, "#e3e5e7ff"]], "32": [[0, 3, "#e3e5e7ff"], [3, 1, "#e3e5e6ff"], [4, 1, "#e3e3e4ff"], [5, 1, "#e3e3e3ff"], [6, 72, "#e2e2e2ff"], [78, 1, "#e3e3e3ff"], [79, 1, "#e3e3e4ff"], [80, 1, "#e3e5e6ff"], [81, 3, "#e3e5e7ff"]], "33": [[0, 84, "#e3e5e7ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 2, "group": "Disabled", "name": "disabled"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 34, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e3e4e5ff"], [4, 1, "#d5d5d7ff"], [5, 1, "#d3d3d4ff"], [6, 22, "#d1d1d2ff"], [28, 1, "#d3d3d4ff"], [29, 1, "#d4d4d5ff"], [30, 1, "#d8d9daff"], [31, 1, "#e8e9eaff"], [32, 2, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#dbdcddff"], [3, 1, "#d5d5d6ff"], [4, 1, "#ededeeff"], [5, 1, "#f7f7f8ff"], [6, 22, "#fcfcfcff"], [28, 1, "#f7f7f8ff"], [29, 1, "#ededeeff"], [30, 1, "#d5d5d7ff"], [31, 1, "#d5d5d6ff"], [32, 1, "#e2e3e4ff"], [33, 1, "#eff0f1ff"]], "42": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 30, "#fcfcfcff"], [32, 1, "#d1d1d2ff"], [33, 1, "#d1d2d3ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.inactive_window", "rows": {"0": [[0, 84, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e3e4e5ff"], [4, 1, "#d5d5d7ff"], [5, 1, "#d3d3d4ff"], [6, 72, "#d1d1d2ff"], [78, 1, "#d3d3d4ff"], [79, 1, "#d5d5d7ff"], [80, 1, "#e3e4e5ff"], [81, 3, "#eff0f1ff"]], "17": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 80, "#fcfcfcff"], [82, 1, "#d1d1d2ff"], [83, 1, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#dbdcddff"], [3, 1, "#d5d5d6ff"], [4, 1, "#ededeeff"], [5, 1, "#f7f7f8ff"], [6, 72, "#fcfcfcff"], [78, 1, "#f7f7f8ff"], [79, 1, "#ededeeff"], [80, 1, "#d5d5d6ff"], [81, 1, "#dbdcddff"], [82, 2, "#eff0f1ff"]], "32": [[0, 2, "#eff0f1ff"], [2, 1, "#e2e3e4ff"], [3, 1, "#d5d6d6ff"], [4, 1, "#d8d8d9ff"], [5, 1, "#d5d5d6ff"], [6, 72, "#d1d1d2ff"], [78, 1, "#d5d5d6ff"], [79, 1, "#d8d8d9ff"], [80, 1, "#d5d6d6ff"], [81, 1, "#e2e3e4ff"], [82, 2, "#eff0f1ff"]], "33": [[0, 3, "#eff0f1ff"], [3, 1, "#e8e9eaff"], [4, 1, "#dadbdcff"], [5, 1, "#d4d5d6ff"], [6, 72, "#d1d2d3ff"], [78, 1, "#d4d5d6ff"], [79, 1, "#dadbdcff"], [80, 1, "#e8e9eaff"], [81, 3, "#eff0f1ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 2, "group": "Inactive", "name": "inactive_window"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 34, "#eff0f1ff"]], "1": [[0, 34, "#eff0f1ff"]], "2": [[0, 34, "#eff0f1ff"]], "42": [[0, 34, "#eff0f1ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.flat", "rows": {"0": [[0, 84, "#eff0f1ff"]], "1": [[0, 84, "#eff0f1ff"]], "17": [[0, 84, "#eff0f1ff"]], "2": [[0, 84, "#eff0f1ff"]], "32": [[0, 84, "#eff0f1ff"]], "33": [[0, 84, "#eff0f1ff"]]}, "size": [84, 34], "state": {"features": 1, "flags": 0, "group": "Active", "name": "flat"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 34, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#75c2ecff"], [5, 1, "#4fb4eaff"], [6, 22, "#3daee9ff"], [28, 1, "#4fb4eaff"], [29, 1, "#75c2ecff"], [30, 1, "#c8e1efff"], [31, 3, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#9fd2edff"], [3, 1, "#56b7eaff"], [4, 1, "#b3daefff"], [5, 1, "#dee9f0ff"], [6, 22, "#eff0f1ff"], [28, 1, "#dee9f0ff"], [29, 1, "#b3daefff"], [30, 1, "#56b7eaff"], [31, 1, "#9fd2edff"], [32, 2, "#eff0f1ff"]], "42": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 30, "#eff0f1ff"], [32, 1, "#3daee9ff"], [33, 1, "#eff0f1ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.flat_hover", "rows": {"0": [[0, 84, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#75c2ecff"], [5, 1, "#4fb4eaff"], [6, 72, "#3daee9ff"], [78, 1, "#4fb4eaff"], [79, 1, "#75c2ecff"], [80, 1, "#c8e1efff"], [81, 3, "#eff0f1ff"]], "17": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 80, "#eff0f1ff"], [82, 1, "#3daee9ff"], [83, 1, "#eff0f1ff"]], "2": [[0, 2, "#eff0f1ff"], [2, 1, "#9fd2edff"], [3, 1, "#56b7eaff"], [4, 1, "#b3daefff"], [5, 1, "#dee9f0ff"], [6, 72, "#eff0f1ff"], [78, 1, "#dee9f0ff"], [79, 1, "#b3daefff"], [80, 1, "#56b7eaff"], [81, 1, "#9fd3edff"], [82, 2, "#eff0f1ff"]], "32": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#75c2ecff"], [5, 1, "#4fb4eaff"], [6, 72, "#3daee9ff"], [78, 1, "#4fb4eaff"], [79, 1, "#75c2ecff"], [80, 1, "#c8e2efff"], [81, 3, "#eff0f1ff"]], "33": [[0, 84, "#eff0f1ff"]]}, "size": [84, 34], "state": {"features": 1, "flags": 8192, "group": "Active", "name": "flat_hover"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 28, "#eff0f1ff"]], "14": [[0, 28, "#eff0f1ff"]]}, "control": "CC_ToolButton", "id": "tool_button.normal", "rows": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 28, "#eff0f1ff"]], "14": [[0, 28, "#eff0f1ff"]]}, "size": [28, 28], "state": {"flags": 4098, "group": "Active", "name": "normal"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#75c2ecff"], [5, 1, "#4fb4eaff"], [6, 16, "#3daee9ff"], [22, 1, "#4fb4eaff"], [23, 1, "#75c2ecff"], [24, 1, "#c8e1efff"], [25, 3, "#eff0f1ff"]], "14": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 24, "#eff0f1ff"], [26, 1, "#3daee9ff"], [27, 1, "#eff0f1ff"]]}, "control": "CC_ToolButton", "id": "tool_button.hover", "rows": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#75c2ecff"], [5, 1, "#4fb4eaff"], [6, 16, "#3daee9ff"], [22, 1, "#4fb4eaff"], [23, 1, "#75c2ecff"], [24, 1, "#c8e1efff"], [25, 3, "#eff0f1ff"]], "14": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 24, "#eff0f1ff"], [26, 1, "#3daee9ff"], [27, 1, "#eff0f1ff"]]}, "size": [28, 28], "state": {"flags": 12290, "group": "Active", "name": "hover"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#72c1ecff"], [5, 1, "#4db4e9ff"], [6, 16, "#3daee9ff"], [22, 1, "#4db4e9ff"], [23, 1, "#72c1ecff"], [24, 1, "#c8e1efff"], [25, 3, "#eff0f1ff"]], "14": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 24, "#b9dceeff"], [26, 1, "#3daee9ff"], [27, 1, "#eff0f1ff"]]}, "control": "CC_ToolButton", "id": "tool_button.pressed", "rows": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c8e1efff"], [4, 1, "#72c1ecff"], [5, 1, "#4db4e9ff"], [6, 16, "#3daee9ff"], [22, 1, "#4db4e9ff"], [23, 1, "#72c1ecff"], [24, 1, "#c8e1efff"], [25, 3, "#eff0f1ff"]], "14": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 24, "#b9dceeff"], [26, 1, "#3daee9ff"], [27, 1, "#eff0f1ff"]]}, "size": [28, 28], "state": {"flags": 12292, "group": "Active", "name": "pressed"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e9e9eaff"], [4, 1, "#d8d9daff"], [5, 1, "#d3d3d5ff"], [6, 16, "#d1d1d2ff"], [22, 1, "#d3d3d5ff"], [23, 1, "#d8d9daff"], [24, 1, "#e9e9eaff"], [25, 3, "#eff0f1ff"]], "14": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 24, "#d5d7d8ff"], [26, 1, "#d1d1d2ff"], [27, 1, "#eff0f1ff"]]}, "control": "CC_ToolButton", "id": "tool_button.checked", "rows": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e9e9eaff"], [4, 1, "#d8d9daff"], [5, 1, "#d3d3d5ff"], [6, 16, "#d1d1d2ff"], [22, 1, "#d3d3d5ff"], [23, 1, "#d8d9daff"], [24, 1, "#e9e9eaff"], [25, 3, "#eff0f1ff"]], "14": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 24, "#d5d7d8ff"], [26, 1, "#d1d1d2ff"], [27, 1, "#eff0f1ff"]]}, "size": [28, 28], "state": {"flags": 4128, "group": "Active", "name": "checked"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e3e4e5ff"], [4, 1, "#d5d5d7ff"], [5, 1, "#d3d3d4ff"], [6, 16, "#d1d1d2ff"], [22, 1, "#d3d3d4ff"], [23, 1, "#d4d4d5ff"], [24, 1, "#d8d9daff"], [25, 1, "#e8e9eaff"], [26, 2, "#eff0f1ff"]], "14": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 24, "#fcfcfcff"], [26, 1, "#d1d1d2ff"], [27, 1, "#d1d2d3ff"]]}, "control": "CC_ToolButton", "id": "tool_button.framed", "rows": {"0": [[0, 28, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e3e4e5ff"], [4, 1, "#d5d5d7ff"], [5, 1, "#d3d3d4ff"], [6, 16, "#d1d1d2ff"], [22, 1, "#d3d3d4ff"], [23, 1, "#d5d5d7ff"], [24, 1, "#e3e4e5ff"], [25, 3, "#eff0f1ff"]], "14": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 24, "#fcfcfcff"], [26, 1, "#d1d1d2ff"], [27, 1, "#eff0f1ff"]]}, "size": [28, 28], "state": {"flags": 2, "group": "Active", "name": "framed"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d6d7d9ff"], [4, 1, "#cacccdff"], [5, 21, "#c6c8c9ff"], [26, 1, "#c7c9caff"], [27, 1, "#cbcccdff"], [28, 1, "#d7d8d9ff"], [29, 1, "#e9eaebff"], [30, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#e9eaeaff"], [2, 1, "#cdcfd0ff"], [3, 1, "#f0f1f1ff"], [4, 1, "#fafafaff"], [5, 22, "#ffffffff"], [27, 1, "#f9fafaff"], [28, 1, "#f0f1f1ff"], [29, 1, "#cccfd0ff"], [30, 1, "#e9eaecff"], [31, 1, "#eff0f1ff"]], "2": [[0, 1, "#e8eaebff"], [1, 1, "#cdcecfff"], [2, 28, "#ffffffff"], [30, 1, "#cdceceff"], [31, 1, "#e9eaecff"]], "75": [[0, 1, "#c6c8c9ff"], [1, 29, "#ffffffff"], [30, 1, "#fefefeff"], [31, 1, "#c7c9caff"]]}, "control": "PE_PanelLineEdit", "id": "line_edit.normal", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d6d7d9ff"], [4, 1, "#cacccdff"], [5, 139, "#c6c8c9ff"], [144, 1, "#c7c9caff"], [145, 1, "#cbcccdff"], [146, 1, "#d6d8d9ff"], [147, 1, "#e9eaebff"], [148, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#e9eaeaff"], [2, 1, "#cdcecfff"], [3, 1, "#f0f1f1ff"], [4, 1, "#fafafaff"], [5, 140, "#ffffffff"], [145, 1, "#f9fafaff"], [146, 1, "#f1f2f2ff"], [147, 1, "#cccfd0ff"], [148, 1, "#e9eaecff"], [149, 1, "#eff0f1ff"]], "16": [[0, 1, "#c6c8c9ff"], [1, 147, "#ffffffff"], [148, 1, "#fefefeff"], [149, 1, "#c7c9caff"]], "2": [[0, 1, "#e8eaebff"], [1, 1, "#cdcfd0ff"], [2, 146, "#ffffffff"], [148, 1, "#cdcfcfff"], [149, 1, "#e9eaecff"]], "30": [[0, 1, "#eff0f1ff"], [1, 1, "#e9eaecff"], [2, 1, "#cdceceff"], [3, 1, "#f1f1f1ff"], [4, 1, "#f9f9f9ff"], [5, 139, "#fefefeff"], [144, 1, "#ffffffff"], [145, 1, "#f9f9f9ff"], [146, 1, "#f0f0f1ff"], [147, 1, "#cccecfff"], [148, 1, "#eaebecff"], [149, 1, "#eff0f1ff"]], "31": [[0, 2, "#eff0f1ff"], [2, 1, "#e9eaecff"], [3, 1, "#d7d8daff"], [4, 1, "#cbcdceff"], [5, 139, "#c7c9caff"], [144, 1, "#c8cacaff"], [145, 1, "#cbcdceff"], [146, 1, "#d7d9d9ff"], [147, 1, "#eaebecff"], [148, 2, "#eff0f1ff"]]}, "size": [150, 32], "state": {"flags": 4, "group": "Active", "line_width": 6, "name": "normal"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#d3e6f0ff"], [3, 1, "#80c7edff"], [4, 1, "#4eb5eaff"], [5, 21, "#3daee9ff"], [26, 1, "#40afe9ff"], [27, 1, "#4fb5eaff"], [28, 1, "#80c7edff"], [29, 1, "#d5e6f0ff"], [30, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#d2e6f0ff"], [2, 1, "#57b8ebff"], [3, 1, "#cfebf9ff"], [4, 1, "#edf7fdff"], [5, 22, "#ffffffff"], [27, 1, "#ecf7fdff"], [28, 1, "#ceebf9ff"], [29, 1, "#56b8ebff"], [30, 1, "#d4e7f0ff"], [31, 1, "#eff0f1ff"]], "2": [[0, 1, "#d3e6f0ff"], [1, 1, "#57b8ebff"], [2, 28, "#ffffffff"], [30, 1, "#55b7ebff"], [31, 1, "#d6e7f0ff"]], "75": [[0, 1, "#3daee9ff"], [1, 29, "#ffffffff"], [30, 1, "#fdfeffff"], [31, 1, "#3fafe9ff"]]}, "control": "PE_PanelLineEdit", "id": "line_edit.hover", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#d3e6f0ff"], [3, 1, "#80c7edff"], [4, 1, "#4eb5eaff"], [5, 139, "#3daee9ff"], [144, 1, "#40afe9ff"], [145, 1, "#4fb5eaff"], [146, 1, "#80c7edff"], [147, 1, "#d5e6f0ff"], [148, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#d2e6f0ff"], [2, 1, "#57b8ebff"], [3, 1, "#cfebf9ff"], [4, 1, "#edf7fdff"], [5, 140, "#ffffffff"], [145, 1, "#ecf7fdff"], [146, 1, "#ceebf9ff"], [147, 1, "#55b8ebff"], [148, 1, "#d4e7f0ff"], [149, 1, "#eff0f1ff"]], "16": [[0, 1, "#3daee9ff"], [1, 147, "#ffffffff"], [148, 1, "#fdfeffff"], [149, 1, "#3fafe9ff"]], "2": [[0, 1, "#d3e6f0ff"], [1, 1, "#57b8ebff"], [2, 146, "#ffffffff"], [148, 1, "#55b7ebff"], [149, 1, "#d6e7f0ff"]], "30": [[0, 1, "#eff0f1ff"], [1, 1, "#d4e7f0ff"], [2, 1, "#55b7ebff"], [3, 1, "#cceaf9ff"], [4, 1, "#eaf6fdff"], [5, 140, "#fdfeffff"], [145, 1, "#e9f6fcff"], [146, 1, "#cbe9f9ff"], [147, 1, "#53b6ebff"], [148, 1, "#d7e7f0ff"], [149, 1, "#eff0f1ff"]], "31": [[0, 2, "#eff0f1ff"], [2, 1, "#d6e7f0ff"], [3, 1, "#82c7edff"], [4, 1, "#51b6eaff"], [5, 139, "#3fafe9ff"], [144, 1, "#42b0eaff"], [145, 1, "#52b6eaff"], [146, 1, "#83c8eeff"], [147, 1, "#d8e8f0ff"], [148, 2, "#eff0f1ff"]]}, "size": [150, 32], "state": {"flags": 8196, "group": "Active", "line_width": 6, "name": "hover"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#d3e6f0ff"], [3, 1, "#80c7edff"], [4, 1, "#4eb5eaff"], [5, 21, "#3daee9ff"], [26, 1, "#40afe9ff"], [27, 1, "#4fb5eaff"], [28, 1, "#80c7edff"], [29, 1, "#d5e6f0ff"], [30, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#d2e6f0ff"], [2, 1, "#57b8ebff"], [3, 1, "#cfebf9ff"], [4, 1, "#edf7fdff"], [5, 22, "#ffffffff"], [27, 1, "#ecf7fdff"], [28, 1, "#ceebf9ff"], [29, 1, "#56b8ebff"], [30, 1, "#d4e7f0ff"], [31, 1, "#eff0f1ff"]], "2": [[0, 1, "#d3e6f0ff"], [1, 1, "#57b8ebff"], [2, 28, "#ffffffff"], [30, 1, "#55b7ebff"], [31, 1, "#d6e7f0ff"]], "75": [[0, 1, "#3daee9ff"], [1, 29, "#ffffffff"], [30, 1, "#fdfeffff"], [31, 1, "#3fafe9ff"]]}, "control": "PE_PanelLineEdit", "id": "line_edit.focus", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#d3e6f0ff"], [3, 1, "#80c7edff"], [4, 1, "#4eb5eaff"], [5, 139, "#3daee9ff"], [144, 1, "#40afe9ff"], [145, 1, "#4fb5eaff"], [146, 1, "#80c7edff"], [147, 1, "#d5e6f0ff"], [148, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#d2e6f0ff"], [2, 1, "#57b8ebff"], [3, 1, "#cfebf9ff"], [4, 1, "#edf7fdff"], [5, 140, "#ffffffff"], [145, 1, "#ecf7fdff"], [146, 1, "#ceebf9ff"], [147, 1, "#55b8ebff"], [148, 1, "#d4e7f0ff"], [149, 1, "#eff0f1ff"]], "16": [[0, 1, "#3daee9ff"], [1, 147, "#ffffffff"], [148, 1, "#fdfeffff"], [149, 1, "#3fafe9ff"]], "2": [[0, 1, "#d3e6f0ff"], [1, 1, "#57b8ebff"], [2, 146, "#ffffffff"], [148, 1, "#55b7ebff"], [149, 1, "#d6e7f0ff"]], "30": [[0, 1, "#eff0f1ff"], [1, 1, "#d4e7f0ff"], [2, 1, "#55b7ebff"], [3, 1, "#cceaf9ff"], [4, 1, "#eaf6fdff"], [5, 140, "#fdfeffff"], [145, 1, "#e9f6fcff"], [146, 1, "#cbe9f9ff"], [147, 1, "#53b6ebff"], [148, 1, "#d7e7f0ff"], [149, 1, "#eff0f1ff"]], "31": [[0, 2, "#eff0f1ff"], [2, 1, "#d6e7f0ff"], [3, 1, "#82c7edff"], [4, 1, "#51b6eaff"], [5, 139, "#3fafe9ff"], [144, 1, "#42b0eaff"], [145, 1, "#52b6eaff"], [146, 1, "#83c8eeff"], [147, 1, "#d8e8f0ff"], [148, 2, "#eff0f1ff"]]}, "size": [150, 32], "state": {"flags": 8388868, "group": "Active", "line_width": 6, "name": "focus"}}, +{"background": "#e3e5e7ff", "cols": {"0": [[0, 2, "#e3e5e7ff"], [2, 1, "#e1e3e5ff"], [3, 1, "#dcdcdfff"], [4, 1, "#d8d9dbff"], [5, 21, "#d6d7d9ff"], [26, 1, "#d7d8daff"], [27, 1, "#d8d9dbff"], [28, 1, "#dcdedfff"], [29, 1, "#e1e3e4ff"], [30, 2, "#e3e5e7ff"]], "1": [[0, 1, "#e3e5e7ff"], [1, 1, "#e0e4e5ff"], [2, 1, "#d9dadcff"], [3, 1, "#ededeeff"], [4, 1, "#f0f0f0ff"], [5, 22, "#f3f3f3ff"], [27, 1, "#f0f0f0ff"], [28, 1, "#ececedff"], [29, 1, "#d9dadcff"], [30, 1, "#e2e4e5ff"], [31, 1, "#e3e5e7ff"]], "2": [[0, 1, "#e1e3e5ff"], [1, 1, "#d9dadcff"], [2, 28, "#f3f3f3ff"], [30, 1, "#d9d9dcff"], [31, 1, "#e1e4e5ff"]], "75": [[0, 1, "#d6d7d9ff"], [1, 30, "#f3f3f3ff"], [31, 1, "#d6d7d9ff"]]}, "control": "PE_PanelLineEdit", "id": "line_edit.disabled", "rows": {"0": [[0, 2, "#e3e5e7ff"], [2, 1, "#e1e3e5ff"], [3, 1, "#dcdddfff"], [4, 1, "#d8d9dbff"], [5, 139, "#d6d7d9ff"], [144, 1, "#d7d8daff"], [145, 1, "#d8d9dbff"], [146, 1, "#dcdedfff"], [147, 1, "#e1e3e4ff"], [148, 2, "#e3e5e7ff"]], "1": [[0, 1, "#e3e5e7ff"], [1, 1, "#e0e4e5ff"], [2, 1, "#d9dadcff"], [3, 1, "#ececedff"], [4, 1, "#f0f0f0ff"], [5, 140, "#f3f3f3ff"], [145, 1, "#f0f0f0ff"], [146, 1, "#ececedff"], [147, 1, "#d9dbdcff"], [148, 1, "#e2e4e5ff"], [149, 1, "#e3e5e7ff"]], "16": [[0, 1, "#d6d7d9ff"], [1, 148, "#f3f3f3ff"], [149, 1, "#d6d7d9ff"]], "2": [[0, 1, "#e1e3e5ff"], [1, 1, "#d9dadcff"], [2, 146, "#f3f3f3ff"], [148, 1, "#d9d9dcff"], [149, 1, "#e1e4e5ff"]], "30": [[0, 1, "#e3e5e7ff"], [1, 1, "#e2e4e5ff"], [2, 1, "#d9d9dcff"], [3, 1, "#ececedff"], [4, 1, "#eff0f0ff"], [5, 140, "#f3f3f3ff"], [145, 1, "#efeff0ff"], [146, 1, "#ecececff"], [147, 1, "#d8dadcff"], [148, 1, "#e2e4e5ff"], [149, 1, "#e3e5e7ff"]], "31": [[0, 2, "#e3e5e7ff"], [2, 1, "#e1e4e5ff"], [3, 1, "#dcdde0ff"], [4, 1, "#d8d9dbff"], [5, 139, "#d6d7d9ff"], [144, 1, "#d6d7daff"], [145, 1, "#d8dadbff"], [146, 1, "#dcdddeff"], [147, 1, "#e2e3e5ff"], [148, 2, "#e3e5e7ff"]]}, "size": [150, 32], "state": {"flags": 4, "group": "Disabled", "line_width": 6, "name": "disabled"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#eeeff0ff"], [4, 1, "#dedfe0ff"], [5, 1, "#d0d1d2ff"], [6, 1, "#c7c9caff"], [7, 6, "#c6c8c9ff"], [13, 1, "#c7c9caff"], [14, 1, "#d0d1d2ff"], [15, 1, "#dedfe0ff"], [16, 1, "#eeeff0ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#eeeff0ff"], [3, 1, "#d2d4d5ff"], [4, 1, "#d7d8d9ff"], [5, 1, "#f2f2f2ff"], [6, 1, "#fbfbfbff"], [7, 6, "#fcfcfcff"], [13, 1, "#fbfbfbff"], [14, 1, "#f2f2f2ff"], [15, 1, "#d7d8d9ff"], [16, 1, "#d2d4d5ff"], [17, 1, "#eeeff0ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.off", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]], "12": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#eeeff0ff"], [4, 1, "#dedfe0ff"], [5, 1, "#d0d1d2ff"], [6, 1, "#c7c9caff"], [7, 6, "#c6c8c9ff"], [13, 1, "#c7c9caff"], [14, 1, "#d0d1d2ff"], [15, 1, "#dedfe0ff"], [16, 1, "#eeeff0ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#eeeff0ff"], [3, 1, "#d2d4d5ff"], [4, 1, "#d7d8d9ff"], [5, 1, "#f2f2f2ff"], [6, 1, "#fbfbfbff"], [7, 6, "#fcfcfcff"], [13, 1, "#fbfbfbff"], [14, 1, "#f2f2f2ff"], [15, 1, "#d7d8d9ff"], [16, 1, "#d2d4d5ff"], [17, 1, "#eeeff0ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 8, "group": "Active", "name": "off"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 6, "#c2e4f6ff"], [9, 1, "#b4d3e5ff"], [10, 1, "#3e464cff"], [11, 1, "#232629ff"], [12, 1, "#3e464cff"], [13, 1, "#b4d3e5ff"], [14, 3, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#ebeef1ff"], [4, 1, "#74c2ecff"], [5, 1, "#45b1e9ff"], [6, 8, "#3daee9ff"], [14, 1, "#45b1e9ff"], [15, 1, "#74c2ecff"], [16, 1, "#ebeef1ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#ebeef1ff"], [3, 1, "#4bb3eaff"], [4, 1, "#4cb4eaff"], [5, 1, "#93d0f2ff"], [6, 1, "#bbe2f4ff"], [7, 6, "#c2e4f6ff"], [13, 1, "#bbe2f4ff"], [14, 1, "#93d0f2ff"], [15, 1, "#4cb4eaff"], [16, 1, "#4bb3eaff"], [17, 1, "#ebeef1ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 7, "#c2e4f6ff"], [10, 1, "#b4d3e5ff"], [11, 1, "#31383bff"], [12, 1, "#232629ff"], [13, 1, "#3e464bff"], [14, 1, "#b3d2e4ff"], [15, 2, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.on", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 1, "#c2e4f6ff"], [4, 1, "#b3d2e4ff"], [5, 1, "#3e464bff"], [6, 1, "#232629ff"], [7, 1, "#3e464cff"], [8, 2, "#b4d3e5ff"], [10, 1, "#3e464cff"], [11, 1, "#232629ff"], [12, 1, "#3e464cff"], [13, 1, "#b4d3e5ff"], [14, 3, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "12": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 3, "#c2e4f6ff"], [6, 1, "#b4d3e5ff"], [7, 1, "#3e464cff"], [8, 2, "#232629ff"], [10, 1, "#3e464cff"], [11, 1, "#b4d3e5ff"], [12, 5, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#ebeef1ff"], [4, 1, "#74c2ecff"], [5, 1, "#45b1e9ff"], [6, 8, "#3daee9ff"], [14, 1, "#45b1e9ff"], [15, 1, "#75c2ebff"], [16, 1, "#ebeef1ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#ebeef1ff"], [3, 1, "#4bb3eaff"], [4, 1, "#4cb4eaff"], [5, 1, "#93d0f2ff"], [6, 1, "#bbe2f4ff"], [7, 6, "#c2e4f6ff"], [13, 1, "#bbe2f4ff"], [14, 1, "#93d0f2ff"], [15, 1, "#4cb4eaff"], [16, 1, "#4bb3eaff"], [17, 1, "#ebeef1ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 2, "#c2e4f6ff"], [5, 2, "#b3d2e4ff"], [7, 4, "#c2e4f6ff"], [11, 1, "#b4d3e5ff"], [12, 1, "#3e464cff"], [13, 1, "#232629ff"], [14, 1, "#3e464cff"], [15, 1, "#b4d3e5ff"], [16, 1, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 32, "group": "Active", "name": "on"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 6, "#c2e4f6ff"], [9, 2, "#232629ff"], [11, 6, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#ebeef1ff"], [4, 1, "#74c2ecff"], [5, 1, "#45b1e9ff"], [6, 8, "#3daee9ff"], [14, 1, "#45b1e9ff"], [15, 1, "#74c2ecff"], [16, 1, "#ebeef1ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#ebeef1ff"], [3, 1, "#4bb3eaff"], [4, 1, "#4cb4eaff"], [5, 1, "#93d0f2ff"], [6, 1, "#bbe2f4ff"], [7, 6, "#c2e4f6ff"], [13, 1, "#bbe2f4ff"], [14, 1, "#93d0f2ff"], [15, 1, "#4cb4eaff"], [16, 1, "#4bb3eaff"], [17, 1, "#ebeef1ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 14, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.partial", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 2, "#c2e4f6ff"], [5, 2, "#232629ff"], [7, 2, "#c2e4f6ff"], [9, 2, "#232629ff"], [11, 2, "#c2e4f6ff"], [13, 2, "#232629ff"], [15, 2, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "12": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 14, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#ebeef1ff"], [4, 1, "#74c2ecff"], [5, 1, "#45b1e9ff"], [6, 8, "#3daee9ff"], [14, 1, "#45b1e9ff"], [15, 1, "#75c2ebff"], [16, 1, "#ebeef1ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#ebeef1ff"], [3, 1, "#4bb3eaff"], [4, 1, "#4cb4eaff"], [5, 1, "#93d0f2ff"], [6, 1, "#bbe2f4ff"], [7, 6, "#c2e4f6ff"], [13, 1, "#bbe2f4ff"], [14, 1, "#93d0f2ff"], [15, 1, "#4cb4eaff"], [16, 1, "#4bb3eaff"], [17, 1, "#ebeef1ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 14, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 16, "group": "Active", "name": "partial"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#eceef0ff"], [4, 1, "#97c9e4ff"], [5, 1, "#5bb6e4ff"], [6, 1, "#40afe8ff"], [7, 6, "#3daee9ff"], [13, 1, "#40afe8ff"], [14, 1, "#5bb6e4ff"], [15, 1, "#97c9e4ff"], [16, 1, "#eceef0ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#eceef0ff"], [3, 1, "#68b9e3ff"], [4, 1, "#70bce3ff"], [5, 1, "#cfe5f1ff"], [6, 1, "#f6f9faff"], [7, 6, "#fcfcfcff"], [13, 1, "#f6f9faff"], [14, 1, "#cfe5f1ff"], [15, 1, "#70bce3ff"], [16, 1, "#68b9e3ff"], [17, 1, "#eceef0ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.hover_off", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "12": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#eceef0ff"], [4, 1, "#97c9e4ff"], [5, 1, "#5bb6e4ff"], [6, 1, "#40afe8ff"], [7, 6, "#3daee9ff"], [13, 1, "#40afe8ff"], [14, 1, "#5bb6e4ff"], [15, 1, "#97c9e4ff"], [16, 1, "#eceef0ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#eceef0ff"], [3, 1, "#68b9e3ff"], [4, 1, "#70bce3ff"], [5, 1, "#cfe5f1ff"], [6, 1, "#f6f9faff"], [7, 6, "#fcfcfcff"], [13, 1, "#f6f9faff"], [14, 1, "#cfe5f1ff"], [15, 1, "#70bce3ff"], [16, 1, "#68b9e3ff"], [17, 1, "#eceef0ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 14, "#fcfcfcff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 8200, "group": "Active", "name": "hover_off"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 6, "#c2e4f6ff"], [9, 1, "#b4d3e5ff"], [10, 1, "#3e464cff"], [11, 1, "#232629ff"], [12, 1, "#3e464cff"], [13, 1, "#b4d3e5ff"], [14, 3, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#ebeef1ff"], [4, 1, "#74c2ecff"], [5, 1, "#45b1e9ff"], [6, 8, "#3daee9ff"], [14, 1, "#45b1e9ff"], [15, 1, "#74c2ecff"], [16, 1, "#ebeef1ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#ebeef1ff"], [3, 1, "#4bb3eaff"], [4, 1, "#4cb4eaff"], [5, 1, "#93d0f2ff"], [6, 1, "#bbe2f4ff"], [7, 6, "#c2e4f6ff"], [13, 1, "#bbe2f4ff"], [14, 1, "#93d0f2ff"], [15, 1, "#4cb4eaff"], [16, 1, "#4bb3eaff"], [17, 1, "#ebeef1ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 7, "#c2e4f6ff"], [10, 1, "#b4d3e5ff"], [11, 1, "#31383bff"], [12, 1, "#232629ff"], [13, 1, "#3e464bff"], [14, 1, "#b3d2e4ff"], [15, 2, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.focus_on", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 1, "#c2e4f6ff"], [4, 1, "#b3d2e4ff"], [5, 1, "#3e464bff"], [6, 1, "#232629ff"], [7, 1, "#3e464cff"], [8, 2, "#b4d3e5ff"], [10, 1, "#3e464cff"], [11, 1, "#232629ff"], [12, 1, "#3e464cff"], [13, 1, "#b4d3e5ff"], [14, 3, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "12": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 3, "#c2e4f6ff"], [6, 1, "#b4d3e5ff"], [7, 1, "#3e464cff"], [8, 2, "#232629ff"], [10, 1, "#3e464cff"], [11, 1, "#b4d3e5ff"], [12, 5, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#ebeef1ff"], [4, 1, "#74c2ecff"], [5, 1, "#45b1e9ff"], [6, 8, "#3daee9ff"], [14, 1, "#45b1e9ff"], [15, 1, "#75c2ebff"], [16, 1, "#ebeef1ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#ebeef1ff"], [3, 1, "#4bb3eaff"], [4, 1, "#4cb4eaff"], [5, 1, "#93d0f2ff"], [6, 1, "#bbe2f4ff"], [7, 6, "#c2e4f6ff"], [13, 1, "#bbe2f4ff"], [14, 1, "#93d0f2ff"], [15, 1, "#4cb4eaff"], [16, 1, "#4bb3eaff"], [17, 1, "#ebeef1ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 2, "#c2e4f6ff"], [5, 2, "#b3d2e4ff"], [7, 4, "#c2e4f6ff"], [11, 1, "#b4d3e5ff"], [12, 1, "#3e464cff"], [13, 1, "#232629ff"], [14, 1, "#3e464cff"], [15, 1, "#b4d3e5ff"], [16, 1, "#c2e4f6ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 8388896, "group": "Active", "name": "focus_on"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#e5e5e5ff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#eeeff0ff"], [4, 1, "#dddfdfff"], [5, 1, "#cecfd0ff"], [6, 1, "#c7c9caff"], [7, 6, "#c6c8c9ff"], [13, 1, "#c7c9caff"], [14, 1, "#cecfd0ff"], [15, 1, "#dddfdfff"], [16, 1, "#eeeff0ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#eeeff0ff"], [3, 1, "#d1d3d3ff"], [4, 1, "#d1d2d3ff"], [5, 1, "#dfdfdfff"], [6, 8, "#e5e5e5ff"], [14, 1, "#dfdfdfff"], [15, 1, "#d1d2d3ff"], [16, 1, "#d1d3d3ff"], [17, 1, "#eeeff0ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#e5e5e5ff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.pressed_off", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#e5e5e5ff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]], "12": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#e5e5e5ff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]], "2": [[0, 3, "#eff0f1ff"], [3, 1, "#eeeff0ff"], [4, 1, "#dddfdfff"], [5, 1, "#cecfd0ff"], [6, 1, "#c7c9caff"], [7, 6, "#c6c8c9ff"], [13, 1, "#c7c9caff"], [14, 1, "#cecfd0ff"], [15, 1, "#dddfdfff"], [16, 1, "#eeeff0ff"], [17, 3, "#eff0f1ff"]], "3": [[0, 2, "#eff0f1ff"], [2, 1, "#eeeff0ff"], [3, 1, "#d1d3d3ff"], [4, 1, "#d1d2d3ff"], [5, 1, "#dfdfdfff"], [6, 8, "#e5e5e5ff"], [14, 1, "#dfdfdfff"], [15, 1, "#d1d2d3ff"], [16, 1, "#d1d3d3ff"], [17, 1, "#eeeff0ff"], [18, 2, "#eff0f1ff"]], "8": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 14, "#e5e5e5ff"], [17, 1, "#c6c8c9ff"], [18, 2, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 12, "group": "Active", "name": "pressed_off"}}, +{"background": "#e3e5e7ff", "cols": {"10": [[0, 3, "#e3e5e7ff"], [3, 6, "#ecededff"], [9, 1, "#e7e8e8ff"], [10, 1, "#b6b6b7ff"], [11, 1, "#aaabacff"], [12, 1, "#b6b6b7ff"], [13, 1, "#e7e8e8ff"], [14, 3, "#ecededff"], [17, 3, "#e3e5e7ff"]], "2": [[0, 5, "#e3e5e7ff"], [5, 1, "#e3e4e7ff"], [6, 1, "#e3e6e7ff"], [7, 6, "#e3e5e7ff"], [13, 1, "#e3e6e7ff"], [14, 1, "#e3e4e7ff"], [15, 5, "#e3e5e7ff"]], "3": [[0, 4, "#e3e5e7ff"], [4, 1, "#e3e6e7ff"], [5, 1, "#e9eaeaff"], [6, 1, "#eaebebff"], [7, 6, "#ecededff"], [13, 1, "#eaebebff"], [14, 1, "#e9eaeaff"], [15, 1, "#e3e6e7ff"], [16, 4, "#e3e5e7ff"]], "8": [[0, 3, "#e3e5e7ff"], [3, 7, "#ecededff"], [10, 1, "#e7e8e8ff"], [11, 1, "#b0b1b1ff"], [12, 1, "#aaabacff"], [13, 1, "#b5b6b7ff"], [14, 1, "#e6e7e8ff"], [15, 2, "#ecededff"], [17, 3, "#e3e5e7ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.disabled_on", "rows": {"10": [[0, 3, "#e3e5e7ff"], [3, 1, "#ecededff"], [4, 1, "#e6e7e8ff"], [5, 1, "#b5b6b7ff"], [6, 1, "#aaabacff"], [7, 1, "#b6b6b7ff"], [8, 2, "#e7e8e8ff"], [10, 1, "#b6b6b7ff"], [11, 1, "#aaabacff"], [12, 1, "#b6b6b7ff"], [13, 1, "#e7e8e8ff"], [14, 3, "#ecededff"], [17, 3, "#e3e5e7ff"]], "12": [[0, 3, "#e3e5e7ff"], [3, 3, "#ecededff"], [6, 1, "#e7e8e8ff"], [7, 1, "#b6b6b7ff"], [8, 2, "#aaabacff"], [10, 1, "#b6b6b7ff"], [11, 1, "#e7e8e8ff"], [12, 5, "#ecededff"], [17, 3, "#e3e5e7ff"]], "2": [[0, 5, "#e3e5e7ff"], [5, 1, "#e3e4e7ff"], [6, 1, "#e3e6e7ff"], [7, 6, "#e3e5e7ff"], [13, 1, "#e3e6e7ff"], [14, 1, "#e3e4e7ff"], [15, 5, "#e3e5e7ff"]], "3": [[0, 4, "#e3e5e7ff"], [4, 1, "#e3e6e7ff"], [5, 1, "#e9eaeaff"], [6, 1, "#eaebebff"], [7, 6, "#ecededff"], [13, 1, "#eaebebff"], [14, 1, "#e9eaeaff"], [15, 1, "#e3e6e7ff"], [16, 4, "#e3e5e7ff"]], "8": [[0, 3, "#e3e5e7ff"], [3, 2, "#ecededff"], [5, 2, "#e6e7e8ff"], [7, 4, "#ecededff"], [11, 1, "#e7e8e8ff"], [12, 1, "#b6b6b7ff"], [13, 1, "#aaabacff"], [14, 1, "#b6b6b7ff"], [15, 1, "#e7e8e8ff"], [16, 1, "#ecededff"], [17, 3, "#e3e5e7ff"]]}, "size": [20, 20], "state": {"flags": 32, "group": "Disabled", "name": "disabled_on"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#c9cbccff"], [3, 1, "#f9f9f9ff"], [4, 12, "#fcfcfcff"], [16, 1, "#f9f9f9ff"], [17, 1, "#c8cbccff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.off", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#c9cbccff"], [3, 1, "#f9f9f9ff"], [4, 12, "#fcfcfcff"], [16, 1, "#f9f9f9ff"], [17, 1, "#c8cbccff"], [18, 2, "#eff0f1ff"]], "3": [[0, 4, "#eff0f1ff"], [4, 1, "#eeeff0ff"], [5, 1, "#d9dbdcff"], [6, 1, "#cbcdceff"], [7, 1, "#e2e3e3ff"], [8, 1, "#f3f3f3ff"], [9, 2, "#f9f9f9ff"], [11, 1, "#f3f3f3ff"], [12, 1, "#e2e3e3ff"], [13, 1, "#cbcdceff"], [14, 1, "#d9dbdcff"], [15, 1, "#eeeff0ff"], [16, 4, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 8, "group": "Active", "name": "off"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 1, "#b4dff5ff"], [4, 3, "#c2e4f6ff"], [7, 1, "#32373cff"], [8, 4, "#232629ff"], [12, 1, "#33383dff"], [13, 3, "#c2e4f6ff"], [16, 1, "#b4dff5ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.on", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 1, "#b4dff5ff"], [4, 3, "#c2e4f6ff"], [7, 1, "#32373cff"], [8, 4, "#232629ff"], [12, 1, "#33383dff"], [13, 3, "#c2e4f6ff"], [16, 1, "#b4dff5ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "3": [[0, 4, "#eff0f1ff"], [4, 1, "#e9eef1ff"], [5, 1, "#63bbebff"], [6, 1, "#3faee9ff"], [7, 1, "#61bcecff"], [8, 1, "#96d2f2ff"], [9, 2, "#b4dff5ff"], [11, 1, "#96d2f2ff"], [12, 1, "#61bcecff"], [13, 1, "#3faee9ff"], [14, 1, "#63bbebff"], [15, 1, "#e9eef1ff"], [16, 4, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 32, "group": "Active", "name": "on"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#43b0e8ff"], [3, 1, "#eff5f8ff"], [4, 12, "#fcfcfcff"], [16, 1, "#eff5f8ff"], [17, 1, "#44afe7ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.hover_off", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#43b0e8ff"], [3, 1, "#eff5f8ff"], [4, 12, "#fcfcfcff"], [16, 1, "#eff5f8ff"], [17, 1, "#44afe7ff"], [18, 2, "#eff0f1ff"]], "3": [[0, 4, "#eff0f1ff"], [4, 1, "#ebeef0ff"], [5, 1, "#85c2e3ff"], [6, 1, "#4db1e5ff"], [7, 1, "#93c9e5ff"], [8, 1, "#d2e6f1ff"], [9, 2, "#eff5f8ff"], [11, 1, "#d2e6f1ff"], [12, 1, "#93c9e5ff"], [13, 1, "#4db1e5ff"], [14, 1, "#85c2e3ff"], [15, 1, "#ebeef0ff"], [16, 4, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 8200, "group": "Active", "name": "hover_off"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 1, "#b4dff5ff"], [4, 3, "#c2e4f6ff"], [7, 1, "#32373cff"], [8, 4, "#232629ff"], [12, 1, "#33383dff"], [13, 3, "#c2e4f6ff"], [16, 1, "#b4dff5ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.focus_on", "rows": {"10": [[0, 2, "#eff0f1ff"], [2, 1, "#3daee9ff"], [3, 1, "#b4dff5ff"], [4, 3, "#c2e4f6ff"], [7, 1, "#32373cff"], [8, 4, "#232629ff"], [12, 1, "#33383dff"], [13, 3, "#c2e4f6ff"], [16, 1, "#b4dff5ff"], [17, 1, "#3daee9ff"], [18, 2, "#eff0f1ff"]], "3": [[0, 4, "#eff0f1ff"], [4, 1, "#e9eef1ff"], [5, 1, "#63bbebff"], [6, 1, "#3faee9ff"], [7, 1, "#61bcecff"], [8, 1, "#96d2f2ff"], [9, 2, "#b4dff5ff"], [11, 1, "#96d2f2ff"], [12, 1, "#61bcecff"], [13, 1, "#3faee9ff"], [14, 1, "#63bbebff"], [15, 1, "#e9eef1ff"], [16, 4, "#eff0f1ff"]]}, "size": [20, 20], "state": {"flags": 8388896, "group": "Active", "name": "focus_on"}}, +{"background": "#e3e5e7ff", "cols": {"10": [[0, 3, "#e3e5e7ff"], [3, 4, "#ecededff"], [7, 1, "#b0b1b2ff"], [8, 4, "#aaabacff"], [12, 1, "#b0b1b2ff"], [13, 4, "#ecededff"], [17, 3, "#e3e5e7ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.disabled_on", "rows": {"10": [[0, 3, "#e3e5e7ff"], [3, 4, "#ecededff"], [7, 1, "#b0b1b2ff"], [8, 4, "#aaabacff"], [12, 1, "#b0b1b2ff"], [13, 4, "#ecededff"], [17, 3, "#e3e5e7ff"]], "3": [[0, 5, "#e3e5e7ff"], [5, 1, "#e4e5e7ff"], [6, 1, "#e3e5e7ff"], [7, 1, "#e5e8e9ff"], [8, 1, "#e9eaebff"], [9, 2, "#ecededff"], [11, 1, "#e9eaebff"], [12, 1, "#e5e8e9ff"], [13, 1, "#e3e5e7ff"], [14, 1, "#e4e5e7ff"], [15, 5, "#e3e5e7ff"]]}, "size": [20, 20], "state": {"flags": 32, "group": "Disabled", "name": "disabled_on"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 32, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e3e4e5ff"], [4, 1, "#d5d5d7ff"], [5, 1, "#d3d3d4ff"], [6, 20, "#d1d1d2ff"], [26, 1, "#d3d3d4ff"], [27, 1, "#d4d4d5ff"], [28, 1, "#d8d9daff"], [29, 1, "#e8e9eaff"], [30, 2, "#eff0f1ff"]], "140": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 13, "#fcfcfcff"], [15, 1, "#cccccdff"], [16, 1, "#525457ff"], [17, 1, "#cccccdff"], [18, 12, "#fcfcfcff"], [30, 1, "#d1d1d2ff"], [31, 1, "#d1d2d3ff"]], "40": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 28, "#fcfcfcff"], [30, 1, "#d1d1d2ff"], [31, 1, "#d1d2d3ff"]]}, "control": "CC_ComboBox", "id": "combo_box.normal", "rows": {"0": [[0, 150, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#e3e4e5ff"], [4, 1, "#d5d5d7ff"], [5, 1, "#d3d3d4ff"], [6, 138, "#d1d1d2ff"], [144, 1, "#d3d3d4ff"], [145, 1, "#d5d5d7ff"], [146, 1, "#e3e4e5ff"], [147, 3, "#eff0f1ff"]], "16": [[0, 1, "#eff0f1ff"], [1, 1, "#d1d1d2ff"], [2, 132, "#fcfcfcff"], [134, 1, "#cccccdff"], [135, 1, "#525457ff"], [136, 1, "#cccccdff"], [137, 2, "#fcfcfcff"], [139, 1, "#cccccdff"], [140, 1, "#525457ff"], [141, 1, "#cccccdff"], [142, 6, "#fcfcfcff"], [148, 1, "#d1d1d2ff"], [149, 1, "#eff0f1ff"]], "31": [[0, 3, "#eff0f1ff"], [3, 1, "#e8e9eaff"], [4, 1, "#dadbdcff"], [5, 1, "#d4d5d6ff"], [6, 138, "#d1d2d3ff"], [144, 1, "#d4d5d6ff"], [145, 1, "#dadbdcff"], [146, 1, "#e8e9eaff"], [147, 3, "#eff0f1ff"]]}, "size": [150, 32], "state": {"editable": false, "flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 32, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c2dceaff"], [4, 1, "#70bde7ff"], [5, 1, "#4eb3e8ff"], [6, 20, "#3daee9ff"], [26, 1, "#4eb3e8ff"], [27, 1, "#6fbce5ff"], [28, 1, "#b7d1dfff"], [29, 1, "#e8e9eaff"], [30, 2, "#eff0f1ff"]], "140": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 13, "#fcfcfcff"], [15, 1, "#cccccdff"], [16, 1, "#525457ff"], [17, 1, "#cccccdff"], [18, 12, "#fcfcfcff"], [30, 1, "#3daee9ff"], [31, 1, "#d1d2d3ff"]], "40": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 28, "#fcfcfcff"], [30, 1, "#3daee9ff"], [31, 1, "#d1d2d3ff"]]}, "control": "CC_ComboBox", "id": "combo_box.hover", "rows": {"0": [[0, 150, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c2dceaff"], [4, 1, "#70bde7ff"], [5, 1, "#4eb3e8ff"], [6, 138, "#3daee9ff"], [144, 1, "#4eb3e8ff"], [145, 1, "#70bde7ff"], [146, 1, "#c2dceaff"], [147, 3, "#eff0f1ff"]], "16": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 132, "#fcfcfcff"], [134, 1, "#cccccdff"], [135, 1, "#525457ff"], [136, 1, "#cccccdff"], [137, 2, "#fcfcfcff"], [139, 1, "#cccccdff"], [140, 1, "#525457ff"], [141, 1, "#cccccdff"], [142, 6, "#fcfcfcff"], [148, 1, "#3daee9ff"], [149, 1, "#eff0f1ff"]], "31": [[0, 3, "#eff0f1ff"], [3, 1, "#e8e9eaff"], [4, 1, "#dadbdcff"], [5, 1, "#d4d5d6ff"], [6, 138, "#d1d2d3ff"], [144, 1, "#d4d5d6ff"], [145, 1, "#dadbdcff"], [146, 1, "#e8e9eaff"], [147, 3, "#eff0f1ff"]]}, "size": [150, 32], "state": {"editable": false, "flags": 8192, "group": "Active", "name": "hover"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 32, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c2dceaff"], [4, 1, "#70bde7ff"], [5, 1, "#4eb3e8ff"], [6, 20, "#3daee9ff"], [26, 1, "#4eb3e8ff"], [27, 1, "#6fbce5ff"], [28, 1, "#b7d1dfff"], [29, 1, "#e8e9eaff"], [30, 2, "#eff0f1ff"]], "140": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 13, "#fcfcfcff"], [15, 1, "#c3c4c5ff"], [16, 1, "#333639ff"], [17, 1, "#c3c4c5ff"], [18, 12, "#fcfcfcff"], [30, 1, "#3daee9ff"], [31, 1, "#d1d2d3ff"]], "40": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 28, "#fcfcfcff"], [30, 1, "#3daee9ff"], [31, 1, "#d1d2d3ff"]]}, "control": "CC_ComboBox", "id": "combo_box.focus", "rows": {"0": [[0, 150, "#eff0f1ff"]], "1": [[0, 3, "#eff0f1ff"], [3, 1, "#c2dceaff"], [4, 1, "#70bde7ff"], [5, 1, "#4eb3e8ff"], [6, 138, "#3daee9ff"], [144, 1, "#4eb3e8ff"], [145, 1, "#70bde7ff"], [146, 1, "#c2dceaff"], [147, 3, "#eff0f1ff"]], "16": [[0, 1, "#eff0f1ff"], [1, 1, "#3daee9ff"], [2, 132, "#fcfcfcff"], [134, 1, "#c3c4c5ff"], [135, 1, "#333639ff"], [136, 1, "#c3c4c5ff"], [137, 2, "#fcfcfcff"], [139, 1, "#c3c4c5ff"], [140, 1, "#333639ff"], [141, 1, "#c3c4c5ff"], [142, 6, "#fcfcfcff"], [148, 1, "#3daee9ff"], [149, 1, "#eff0f1ff"]], "31": [[0, 3, "#eff0f1ff"], [3, 1, "#e8e9eaff"], [4, 1, "#dadbdcff"], [5, 1, "#d4d5d6ff"], [6, 138, "#d1d2d3ff"], [144, 1, "#d4d5d6ff"], [145, 1, "#dadbdcff"], [146, 1, "#e8e9eaff"], [147, 3, "#eff0f1ff"]]}, "size": [150, 32], "state": {"editable": false, "flags": 8388864, "group": "Active", "name": "focus"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d6d7d9ff"], [4, 1, "#cacccdff"], [5, 21, "#c6c8c9ff"], [26, 1, "#c7c9caff"], [27, 1, "#cbcccdff"], [28, 1, "#d7d8d9ff"], [29, 1, "#e9eaebff"], [30, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#e9eaeaff"], [2, 1, "#cdcfd0ff"], [3, 1, "#f0f1f1ff"], [4, 1, "#fafafaff"], [5, 22, "#ffffffff"], [27, 1, "#f9fafaff"], [28, 1, "#f0f1f1ff"], [29, 1, "#cccfd0ff"], [30, 1, "#e9eaecff"], [31, 1, "#eff0f1ff"]], "140": [[0, 1, "#c6c8c9ff"], [1, 14, "#ffffffff"], [15, 1, "#cdcecfff"], [16, 1, "#505255ff"], [17, 1, "#cdcecfff"], [18, 12, "#ffffffff"], [30, 1, "#fefefeff"], [31, 1, "#c7c9caff"]], "40": [[0, 1, "#c6c8c9ff"], [1, 29, "#ffffffff"], [30, 1, "#fefefeff"], [31, 1, "#c7c9caff"]]}, "control": "CC_ComboBox", "id": "combo_box.editable", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d6d7d9ff"], [4, 1, "#cacccdff"], [5, 139, "#c6c8c9ff"], [144, 1, "#c7c9caff"], [145, 1, "#cbcccdff"], [146, 1, "#d6d8d9ff"], [147, 1, "#e9eaebff"], [148, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#e9eaeaff"], [2, 1, "#cdcecfff"], [3, 1, "#f0f1f1ff"], [4, 1, "#fafafaff"], [5, 140, "#ffffffff"], [145, 1, "#f9fafaff"], [146, 1, "#f1f2f2ff"], [147, 1, "#cccfd0ff"], [148, 1, "#e9eaecff"], [149, 1, "#eff0f1ff"]], "16": [[0, 1, "#c6c8c9ff"], [1, 133, "#ffffffff"], [134, 1, "#cdcecfff"], [135, 1, "#505255ff"], [136, 1, "#cdcecfff"], [137, 2, "#ffffffff"], [139, 1, "#cdcecfff"], [140, 1, "#505255ff"], [141, 1, "#cdcecfff"], [142, 6, "#ffffffff"], [148, 1, "#fefefeff"], [149, 1, "#c7c9caff"]], "31": [[0, 2, "#eff0f1ff"], [2, 1, "#e9eaecff"], [3, 1, "#d7d8daff"], [4, 1, "#cbcdceff"], [5, 139, "#c7c9caff"], [144, 1, "#c8cacaff"], [145, 1, "#cbcdceff"], [146, 1, "#d7d9d9ff"], [147, 1, "#eaebecff"], [148, 2, "#eff0f1ff"]]}, "size": [150, 32], "state": {"editable": true, "flags": 0, "group": "Active", "name": "editable"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d6d7d9ff"], [4, 1, "#cacccdff"], [5, 21, "#c6c8c9ff"], [26, 1, "#c7c9caff"], [27, 1, "#cbcccdff"], [28, 1, "#d7d8d9ff"], [29, 1, "#e9eaebff"], [30, 2, "#eff0f1ff"]], "40": [[0, 1, "#c6c8c9ff"], [1, 29, "#ffffffff"], [30, 1, "#fefefeff"], [31, 1, "#c7c9caff"]], "90": [[0, 1, "#c6c8c9ff"], [1, 8, "#ffffffff"], [9, 1, "#cecfcfff"], [10, 1, "#525557ff"], [11, 1, "#cecfcfff"], [12, 8, "#ffffffff"], [20, 1, "#cecfcfff"], [21, 1, "#525557ff"], [22, 1, "#cecfcfff"], [23, 7, "#ffffffff"], [30, 1, "#fefefeff"], [31, 1, "#c7c9caff"]]}, "control": "CC_SpinBox", "id": "spin_box.normal", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d6d7d9ff"], [4, 1, "#cacccdff"], [5, 89, "#c6c8c9ff"], [94, 1, "#c7c9caff"], [95, 1, "#cbcccdff"], [96, 1, "#d6d8d9ff"], [97, 1, "#e9eaebff"], [98, 2, "#eff0f1ff"]], "16": [[0, 1, "#c6c8c9ff"], [1, 97, "#ffffffff"], [98, 1, "#fefefeff"], [99, 1, "#c7c9caff"]], "24": [[0, 1, "#c6c8c9ff"], [1, 86, "#ffffffff"], [87, 2, "#cfcfd0ff"], [89, 9, "#ffffffff"], [98, 1, "#fefefeff"], [99, 1, "#c7c9caff"]], "31": [[0, 2, "#eff0f1ff"], [2, 1, "#e9eaecff"], [3, 1, "#d7d8daff"], [4, 1, "#cbcdceff"], [5, 89, "#c7c9caff"], [94, 1, "#c8cacaff"], [95, 1, "#cbcdceff"], [96, 1, "#d7d9d9ff"], [97, 1, "#eaebecff"], [98, 2, "#eff0f1ff"]], "8": [[0, 1, "#c6c8c9ff"], [1, 85, "#ffffffff"], [86, 1, "#cecfcfff"], [87, 2, "#444749ff"], [89, 1, "#cecfcfff"], [90, 8, "#ffffffff"], [98, 1, "#fefefeff"], [99, 1, "#c7c9caff"]]}, "size": [100, 32], "state": {"flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#d3e6f0ff"], [3, 1, "#80c7edff"], [4, 1, "#4eb5eaff"], [5, 21, "#3daee9ff"], [26, 1, "#40afe9ff"], [27, 1, "#4fb5eaff"], [28, 1, "#80c7edff"], [29, 1, "#d5e6f0ff"], [30, 2, "#eff0f1ff"]], "40": [[0, 1, "#3daee9ff"], [1, 29, "#ffffffff"], [30, 1, "#fdfeffff"], [31, 1, "#3fafe9ff"]], "90": [[0, 1, "#3daee9ff"], [1, 8, "#ffffffff"], [9, 1, "#cecfcfff"], [10, 1, "#525557ff"], [11, 1, "#cecfcfff"], [12, 8, "#ffffffff"], [20, 1, "#cecfcfff"], [21, 1, "#525557ff"], [22, 1, "#cecfcfff"], [23, 7, "#ffffffff"], [30, 1, "#fdfeffff"], [31, 1, "#3fafe9ff"]]}, "control": "CC_SpinBox", "id": "spin_box.hover", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#d3e6f0ff"], [3, 1, "#80c7edff"], [4, 1, "#4eb5eaff"], [5, 89, "#3daee9ff"], [94, 1, "#40afe9ff"], [95, 1, "#4fb5eaff"], [96, 1, "#80c7edff"], [97, 1, "#d5e6f0ff"], [98, 2, "#eff0f1ff"]], "16": [[0, 1, "#3daee9ff"], [1, 97, "#ffffffff"], [98, 1, "#fdfeffff"], [99, 1, "#3fafe9ff"]], "24": [[0, 1, "#3daee9ff"], [1, 86, "#ffffffff"], [87, 2, "#cfcfd0ff"], [89, 9, "#ffffffff"], [98, 1, "#fdfeffff"], [99, 1, "#3fafe9ff"]], "31": [[0, 2, "#eff0f1ff"], [2, 1, "#d6e7f0ff"], [3, 1, "#82c7edff"], [4, 1, "#51b6eaff"], [5, 89, "#3fafe9ff"], [94, 1, "#42b0eaff"], [95, 1, "#52b6eaff"], [96, 1, "#83c8eeff"], [97, 1, "#d8e8f0ff"], [98, 2, "#eff0f1ff"]], "8": [[0, 1, "#3daee9ff"], [1, 85, "#ffffffff"], [86, 1, "#cecfcfff"], [87, 2, "#444749ff"], [89, 1, "#cecfcfff"], [90, 8, "#ffffffff"], [98, 1, "#fdfeffff"], [99, 1, "#3fafe9ff"]]}, "size": [100, 32], "state": {"flags": 8192, "group": "Active", "name": "hover"}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 7, "#eff0f1ff"], [7, 1, "#84bcd9ff"], [8, 4, "#72c2ebff"], [12, 1, "#84bcd9ff"], [13, 7, "#eff0f1ff"]], "190": [[0, 7, "#eff0f1ff"], [7, 1, "#babdbeff"], [8, 4, "#d2d3d5ff"], [12, 1, "#babdbeff"], [13, 7, "#eff0f1ff"]], "63": [[0, 1, "#eff0f1ff"], [1, 1, "#d2d2d3ff"], [2, 1, "#fafafaff"], [3, 14, "#fcfcfcff"], [17, 1, "#fafafaff"], [18, 1, "#d2d2d3ff"], [19, 1, "#e1e2e3ff"]]}, "control": "CC_Slider", "id": "slider.normal", "rows": {"10": [[0, 1, "#91c0d6ff"], [1, 1, "#71c2ebff"], [2, 53, "#72c2ebff"], [55, 1, "#cfd1d3ff"], [56, 1, "#fafafaff"], [57, 14, "#fcfcfcff"], [71, 1, "#fafafaff"], [72, 1, "#d2d2d3ff"], [73, 1, "#c1c2c3ff"], [74, 124, "#d2d3d5ff"], [198, 1, "#cfd0d2ff"], [199, 1, "#c2c4c4ff"]]}, "size": [200, 20], "state": {"flags": 128, "group": "Active", "handle_rect": [54, 0, 20, 20], "name": "normal", "value": 30}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 7, "#eff0f1ff"], [7, 1, "#84bcd9ff"], [8, 4, "#72c2ebff"], [12, 1, "#84bcd9ff"], [13, 7, "#eff0f1ff"]], "190": [[0, 7, "#eff0f1ff"], [7, 1, "#babdbeff"], [8, 4, "#d2d3d5ff"], [12, 1, "#babdbeff"], [13, 7, "#eff0f1ff"]], "63": [[0, 1, "#eff0f1ff"], [1, 1, "#45b1e9ff"], [2, 1, "#f3f8fbff"], [3, 14, "#fcfcfcff"], [17, 1, "#f2f8fbff"], [18, 1, "#45b1e9ff"], [19, 1, "#e1e2e3ff"]]}, "control": "CC_Slider", "id": "slider.handle_hover", "rows": {"10": [[0, 1, "#91c0d6ff"], [1, 1, "#71c2ebff"], [2, 53, "#72c2ebff"], [55, 1, "#42b0e9ff"], [56, 1, "#f3f8fbff"], [57, 14, "#fcfcfcff"], [71, 1, "#f2f8fbff"], [72, 1, "#45b1e9ff"], [73, 1, "#c1c2c3ff"], [74, 124, "#d2d3d5ff"], [198, 1, "#cfd0d2ff"], [199, 1, "#c2c4c4ff"]]}, "size": [200, 20], "state": {"flags": 8320, "group": "Active", "handle_rect": [54, 0, 20, 20], "name": "handle_hover", "value": 30}}, +{"background": "#eff0f1ff", "cols": {"10": [[0, 7, "#eff0f1ff"], [7, 1, "#84bcd9ff"], [8, 4, "#72c2ebff"], [12, 1, "#84bcd9ff"], [13, 7, "#eff0f1ff"]], "190": [[0, 7, "#eff0f1ff"], [7, 1, "#babdbeff"], [8, 4, "#d2d3d5ff"], [12, 1, "#babdbeff"], [13, 7, "#eff0f1ff"]], "63": [[0, 1, "#eff0f1ff"], [1, 1, "#45b1e9ff"], [2, 1, "#f3f8fbff"], [3, 14, "#fcfcfcff"], [17, 1, "#f2f8fbff"], [18, 1, "#45b1e9ff"], [19, 1, "#e1e2e3ff"]]}, "control": "CC_Slider", "id": "slider.focus", "rows": {"10": [[0, 1, "#91c0d6ff"], [1, 1, "#71c2ebff"], [2, 53, "#72c2ebff"], [55, 1, "#42b0e9ff"], [56, 1, "#f3f8fbff"], [57, 14, "#fcfcfcff"], [71, 1, "#f2f8fbff"], [72, 1, "#45b1e9ff"], [73, 1, "#c1c2c3ff"], [74, 124, "#d2d3d5ff"], [198, 1, "#cfd0d2ff"], [199, 1, "#c2c4c4ff"]]}, "size": [200, 20], "state": {"flags": 8388992, "group": "Active", "handle_rect": [54, 0, 20, 20], "name": "focus", "value": 30}}, +{"background": "#e3e5e7ff", "cols": {"10": [[0, 7, "#e3e5e7ff"], [7, 1, "#d2d3d6ff"], [8, 4, "#d9dcddff"], [12, 1, "#d2d3d6ff"], [13, 7, "#e3e5e7ff"]], "190": [[0, 7, "#e3e5e7ff"], [7, 1, "#d2d3d6ff"], [8, 4, "#d9dcddff"], [12, 1, "#d2d3d6ff"], [13, 7, "#e3e5e7ff"]], "63": [[0, 1, "#e3e5e7ff"], [1, 1, "#e1e1e1ff"], [2, 16, "#f0f0f0ff"], [18, 1, "#e2e2e2ff"], [19, 1, "#d6d8d9ff"]]}, "control": "CC_Slider", "id": "slider.disabled", "rows": {"10": [[0, 1, "#d4d6d8ff"], [1, 1, "#d8dbdcff"], [2, 53, "#d9dcddff"], [55, 1, "#e1e1e1ff"], [56, 16, "#f0f0f0ff"], [72, 1, "#e1e2e2ff"], [73, 1, "#c6c9caff"], [74, 124, "#d9dcddff"], [198, 1, "#d8dbdcff"], [199, 1, "#d4d6d8ff"]]}, "size": [200, 20], "state": {"flags": 128, "group": "Disabled", "handle_rect": [54, 0, 20, 20], "name": "disabled", "value": 30}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 200, "#c6c8c9ff"]], "10": [[0, 3, "#eff0f1ff"], [3, 1, "#bfc2c2ff"], [4, 1, "#c7c9c9ff"], [5, 28, "#cbcdcdff"], [33, 1, "#c7c9c9ff"], [34, 1, "#bfc2c2ff"], [35, 165, "#eff0f1ff"]]}, "control": "CC_ScrollBar", "id": "scroll_bar.normal", "rows": {"150": [[0, 1, "#c6c8c9ff"], [1, 20, "#eff0f1ff"]], "18": [[0, 1, "#c6c8c9ff"], [1, 6, "#eff0f1ff"], [7, 1, "#b8babaff"], [8, 6, "#cbcdcdff"], [14, 1, "#b8babaff"], [15, 6, "#eff0f1ff"]]}, "size": [21, 200], "state": {"active_sub_control": "none", "flags": 0, "group": "Active", "name": "normal", "slider_rect": [0, 3, 21, 32]}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 200, "#c6c8c9ff"]], "10": [[0, 3, "#eff0f1ff"], [3, 1, "#bfc2c2ff"], [4, 1, "#c7c9c9ff"], [5, 28, "#cbcdcdff"], [33, 1, "#c7c9c9ff"], [34, 1, "#bfc2c2ff"], [35, 165, "#eff0f1ff"]]}, "control": "CC_ScrollBar", "id": "scroll_bar.hover", "rows": {"150": [[0, 1, "#c6c8c9ff"], [1, 20, "#eff0f1ff"]], "18": [[0, 1, "#c6c8c9ff"], [1, 6, "#eff0f1ff"], [7, 1, "#b8babaff"], [8, 6, "#cbcdcdff"], [14, 1, "#b8babaff"], [15, 6, "#eff0f1ff"]]}, "size": [21, 200], "state": {"active_sub_control": "none", "flags": 8192, "group": "Active", "name": "hover", "slider_rect": [0, 3, 21, 32]}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 200, "#c6c8c9ff"]], "10": [[0, 3, "#eff0f1ff"], [3, 1, "#b3daefff"], [4, 1, "#93ceecff"], [5, 28, "#96cfedff"], [33, 1, "#93ceecff"], [34, 1, "#b3daefff"], [35, 165, "#eff0f1ff"]]}, "control": "CC_ScrollBar", "id": "scroll_bar.slider_hover", "rows": {"150": [[0, 1, "#c6c8c9ff"], [1, 20, "#eff0f1ff"]], "18": [[0, 1, "#c6c8c9ff"], [1, 6, "#eff0f1ff"], [7, 1, "#a7d6eeff"], [8, 6, "#96cfedff"], [14, 1, "#a7d6eeff"], [15, 6, "#eff0f1ff"]]}, "size": [21, 200], "state": {"active_sub_control": "SC_ScrollBarSlider", "flags": 8192, "group": "Active", "name": "slider_hover", "slider_rect": [0, 3, 21, 32]}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 200, "#c6c8c9ff"]], "10": [[0, 3, "#eff0f1ff"], [3, 1, "#b3daefff"], [4, 1, "#93ceecff"], [5, 28, "#96cfedff"], [33, 1, "#93ceecff"], [34, 1, "#b3daefff"], [35, 165, "#eff0f1ff"]]}, "control": "CC_ScrollBar", "id": "scroll_bar.slider_pressed", "rows": {"150": [[0, 1, "#c6c8c9ff"], [1, 20, "#eff0f1ff"]], "18": [[0, 1, "#c6c8c9ff"], [1, 6, "#eff0f1ff"], [7, 1, "#a7d6eeff"], [8, 6, "#96cfedff"], [14, 1, "#a7d6eeff"], [15, 6, "#eff0f1ff"]]}, "size": [21, 200], "state": {"active_sub_control": "SC_ScrollBarSlider", "flags": 8196, "group": "Active", "name": "slider_pressed", "slider_rect": [0, 3, 21, 32]}}, +{"background": "#eff0f1ff", "cols": {"150": [[0, 6, "#eff0f1ff"], [6, 1, "#babdbeff"], [7, 4, "#d2d3d5ff"], [11, 1, "#babdbeff"], [12, 6, "#eff0f1ff"]], "50": [[0, 6, "#eff0f1ff"], [6, 1, "#84bcd9ff"], [7, 4, "#72c2ebff"], [11, 1, "#84bcd9ff"], [12, 6, "#eff0f1ff"]]}, "control": "CE_ProgressBar", "id": "progress_bar.half", "rows": {"9": [[0, 1, "#91c0d6ff"], [1, 1, "#71c2ebff"], [2, 96, "#72c2ebff"], [98, 1, "#71c2ebff"], [99, 1, "#99c8dfff"], [100, 98, "#d2d3d5ff"], [198, 1, "#cfd0d2ff"], [199, 1, "#c2c4c4ff"]]}, "size": [200, 18], "state": {"flags": 128, "group": "Active", "name": "half"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 100, "#c6c8c9ff"]], "1": [[0, 1, "#c6c8c9ff"], [1, 98, "#f4f5f5ff"], [99, 1, "#c6c8c9ff"]], "100": [[0, 1, "#c6c8c9ff"], [1, 98, "#f4f5f5ff"], [99, 1, "#c6c8c9ff"]]}, "control": "PE_PanelMenu+PE_FrameMenu", "id": "menu.panel", "rows": {"0": [[0, 200, "#c6c8c9ff"]], "1": [[0, 1, "#c6c8c9ff"], [1, 198, "#f4f5f5ff"], [199, 1, "#c6c8c9ff"]], "50": [[0, 1, "#c6c8c9ff"], [1, 198, "#f4f5f5ff"], [199, 1, "#c6c8c9ff"]]}, "size": [200, 100], "state": {"flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#eff0f1ff", "cols": {"100": [[0, 29, "#eff0f1ff"]], "4": [[0, 29, "#eff0f1ff"]], "5": [[0, 29, "#eff0f1ff"]], "6": [[0, 29, "#eff0f1ff"]]}, "control": "CE_MenuItem", "id": "menu_item.normal", "rows": {"0": [[0, 200, "#eff0f1ff"]], "1": [[0, 200, "#eff0f1ff"]], "14": [[0, 200, "#eff0f1ff"]], "2": [[0, 200, "#eff0f1ff"]], "3": [[0, 200, "#eff0f1ff"]], "4": [[0, 200, "#eff0f1ff"]], "5": [[0, 200, "#eff0f1ff"]]}, "size": [200, 29], "state": {"flags": 0, "group": "Active", "name": "normal", "type": 0}}, +{"background": "#eff0f1ff", "cols": {"100": [[0, 1, "#399accff"], [1, 26, "#b9dceeff"], [27, 1, "#b8dbedff"], [28, 1, "#3a9bcdff"]], "4": [[0, 1, "#48a2d0ff"], [1, 1, "#add5ebff"], [2, 25, "#b9dceeff"], [27, 1, "#abd5eaff"], [28, 1, "#4ba2d0ff"]], "5": [[0, 1, "#399accff"], [1, 26, "#b9dceeff"], [27, 1, "#b8dbedff"], [28, 1, "#3a9bcdff"]], "6": [[0, 1, "#399accff"], [1, 26, "#b9dceeff"], [27, 1, "#b8dbedff"], [28, 1, "#3a9bcdff"]]}, "control": "CE_MenuItem", "id": "menu_item.selected", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#d3e3ebff"], [3, 1, "#78b8d9ff"], [4, 1, "#48a2d0ff"], [5, 189, "#399accff"], [194, 1, "#3b9ccdff"], [195, 1, "#48a2d0ff"], [196, 1, "#7ab9daff"], [197, 1, "#d4e3ecff"], [198, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#cedfebff"], [2, 1, "#4da4d1ff"], [3, 1, "#99cbe6ff"], [4, 1, "#add5ebff"], [5, 190, "#b9dceeff"], [195, 1, "#add5ebff"], [196, 1, "#98cce6ff"], [197, 1, "#4da4d1ff"], [198, 1, "#d0e1eaff"], [199, 1, "#eff0f1ff"]], "14": [[0, 1, "#399accff"], [1, 197, "#b9dceeff"], [198, 1, "#b8dbedff"], [199, 1, "#3a9bcdff"]], "2": [[0, 1, "#d3e3ebff"], [1, 1, "#4da4d1ff"], [2, 196, "#b9dceeff"], [198, 1, "#4ba3d0ff"], [199, 1, "#d6e4ecff"]], "3": [[0, 1, "#78b8daff"], [1, 1, "#99cce6ff"], [2, 196, "#b9dceeff"], [198, 1, "#97cbe5ff"], [199, 1, "#7abadaff"]], "4": [[0, 1, "#48a2d0ff"], [1, 1, "#add5ebff"], [2, 196, "#b9dceeff"], [198, 1, "#abd5eaff"], [199, 1, "#4ba2d0ff"]], "5": [[0, 1, "#399accff"], [1, 197, "#b9dceeff"], [198, 1, "#b8dbedff"], [199, 1, "#3a9bcdff"]]}, "size": [200, 29], "state": {"flags": 32768, "group": "Active", "name": "selected", "type": 0}}, +{"background": "#eff0f1ff", "cols": {"100": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 2, "#eff0f1ff"]], "4": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 2, "#eff0f1ff"]], "5": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 2, "#eff0f1ff"]], "6": [[0, 2, "#eff0f1ff"], [2, 1, "#c6c8c9ff"], [3, 2, "#eff0f1ff"]]}, "control": "CE_MenuItem", "id": "menu_item.separator", "rows": {"0": [[0, 200, "#eff0f1ff"]], "1": [[0, 200, "#eff0f1ff"]], "2": [[0, 4, "#eff0f1ff"], [4, 193, "#c6c8c9ff"], [197, 3, "#eff0f1ff"]], "3": [[0, 200, "#eff0f1ff"]], "4": [[0, 200, "#eff0f1ff"]], "5": [[0, 200, "#00303900"]]}, "size": [200, 5], "state": {"flags": 0, "group": "Active", "name": "separator", "type": 2}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#bdd9e9ff"], [3, 1, "#a5c9dbff"], [4, 1, "#bbc9d1ff"], [5, 25, "#c6c8c9ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#91c8e4ff"], [2, 1, "#3fafe9ff"], [3, 1, "#e1e2e4ff"], [4, 1, "#ebecedff"], [5, 25, "#eff0f1ff"]], "40": [[0, 3, "#3daee9ff"], [3, 27, "#eff0f1ff"]], "79": [[0, 2, "#eff0f1ff"], [2, 1, "#bdd9e9ff"], [3, 1, "#a5c8dcff"], [4, 1, "#bbc9d1ff"], [5, 25, "#c6c8c9ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.selected", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#bdd9e9ff"], [3, 1, "#6ab9e4ff"], [4, 1, "#4bb1e6ff"], [5, 70, "#3daee9ff"], [75, 1, "#4bb1e6ff"], [76, 1, "#6ab9e4ff"], [77, 1, "#bdd9e9ff"], [78, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#91c8e4ff"], [2, 1, "#3eaee9ff"], [3, 74, "#3daee9ff"], [77, 1, "#3fafe9ff"], [78, 1, "#91c8e4ff"], [79, 1, "#eff0f1ff"]], "15": [[0, 1, "#c6c8c9ff"], [1, 78, "#eff0f1ff"], [79, 1, "#c6c8c9ff"]], "2": [[0, 1, "#bdd9e9ff"], [1, 1, "#3fafe9ff"], [2, 76, "#3daee9ff"], [78, 1, "#3fafe9ff"], [79, 1, "#bdd9e9ff"]], "27": [[0, 1, "#c6c8c9ff"], [1, 78, "#eff0f1ff"], [79, 1, "#c6c8c9ff"]], "28": [[0, 1, "#c6c8c9ff"], [1, 78, "#eff0f1ff"], [79, 1, "#c6c8c9ff"]], "29": [[0, 1, "#c6c8c9ff"], [1, 78, "#eff0f1ff"], [79, 1, "#c6c8c9ff"]], "3": [[0, 1, "#a5c9dbff"], [1, 1, "#e1e2e4ff"], [2, 76, "#eff0f1ff"], [78, 1, "#e1e2e4ff"], [79, 1, "#a5c8dcff"]]}, "size": [80, 30], "state": {"document_mode": false, "flags": 32768, "group": "Active", "name": "selected"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 30, "#c7c8c9ff"]], "1": [[0, 30, "#c7c8c9ff"]], "40": [[0, 30, "#c7c8c9ff"]], "79": [[0, 30, "#c7c8c9ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.unselected", "rows": {"0": [[0, 80, "#c7c8c9ff"]], "1": [[0, 80, "#c7c8c9ff"]], "15": [[0, 80, "#c7c8c9ff"]], "2": [[0, 80, "#c7c8c9ff"]], "27": [[0, 80, "#c7c8c9ff"]], "28": [[0, 80, "#c7c8c9ff"]], "29": [[0, 80, "#c7c8c9ff"]], "3": [[0, 80, "#c7c8c9ff"]]}, "size": [80, 30], "state": {"document_mode": false, "flags": 0, "group": "Active", "name": "unselected"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 30, "#cbe3f0ff"]], "1": [[0, 30, "#cbe3f0ff"]], "40": [[0, 30, "#cbe3f0ff"]], "79": [[0, 30, "#cbe3f0ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.hover", "rows": {"0": [[0, 80, "#cbe3f0ff"]], "1": [[0, 80, "#cbe3f0ff"]], "15": [[0, 80, "#cbe3f0ff"]], "2": [[0, 80, "#cbe3f0ff"]], "27": [[0, 80, "#cbe3f0ff"]], "28": [[0, 80, "#cbe3f0ff"]], "29": [[0, 80, "#cbe3f0ff"]], "3": [[0, 80, "#cbe3f0ff"]]}, "size": [80, 30], "state": {"document_mode": false, "flags": 8192, "group": "Active", "name": "hover"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#bdd9e9ff"], [3, 1, "#a5c9dbff"], [4, 1, "#bbc9d1ff"], [5, 25, "#c6c8c9ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#91c8e4ff"], [2, 1, "#3fafe9ff"], [3, 1, "#e1e2e4ff"], [4, 1, "#ebecedff"], [5, 25, "#eff0f1ff"]], "40": [[0, 3, "#3daee9ff"], [3, 27, "#eff0f1ff"]], "79": [[0, 2, "#eff0f1ff"], [2, 1, "#bdd9e9ff"], [3, 1, "#a5c8dcff"], [4, 1, "#bbc9d1ff"], [5, 25, "#c6c8c9ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.selected.document_mode", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#bdd9e9ff"], [3, 1, "#6ab9e4ff"], [4, 1, "#4bb1e6ff"], [5, 70, "#3daee9ff"], [75, 1, "#4bb1e6ff"], [76, 1, "#6ab9e4ff"], [77, 1, "#bdd9e9ff"], [78, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#91c8e4ff"], [2, 1, "#3eaee9ff"], [3, 74, "#3daee9ff"], [77, 1, "#3fafe9ff"], [78, 1, "#91c8e4ff"], [79, 1, "#eff0f1ff"]], "15": [[0, 1, "#c6c8c9ff"], [1, 78, "#eff0f1ff"], [79, 1, "#c6c8c9ff"]], "2": [[0, 1, "#bdd9e9ff"], [1, 1, "#3fafe9ff"], [2, 76, "#3daee9ff"], [78, 1, "#3fafe9ff"], [79, 1, "#bdd9e9ff"]], "27": [[0, 1, "#c6c8c9ff"], [1, 78, "#eff0f1ff"], [79, 1, "#c6c8c9ff"]], "28": [[0, 1, "#c6c8c9ff"], [1, 78, "#eff0f1ff"], [79, 1, "#c6c8c9ff"]], "29": [[0, 1, "#c6c8c9ff"], [1, 78, "#eff0f1ff"], [79, 1, "#c6c8c9ff"]], "3": [[0, 1, "#a5c9dbff"], [1, 1, "#e1e2e4ff"], [2, 76, "#eff0f1ff"], [78, 1, "#e1e2e4ff"], [79, 1, "#a5c8dcff"]]}, "size": [80, 30], "state": {"document_mode": true, "flags": 32768, "group": "Active", "name": "selected.document_mode"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 30, "#c7c8c9ff"]], "1": [[0, 30, "#c7c8c9ff"]], "40": [[0, 30, "#c7c8c9ff"]], "79": [[0, 30, "#c7c8c9ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.unselected.document_mode", "rows": {"0": [[0, 80, "#c7c8c9ff"]], "1": [[0, 80, "#c7c8c9ff"]], "15": [[0, 80, "#c7c8c9ff"]], "2": [[0, 80, "#c7c8c9ff"]], "27": [[0, 80, "#c7c8c9ff"]], "28": [[0, 80, "#c7c8c9ff"]], "29": [[0, 80, "#c7c8c9ff"]], "3": [[0, 80, "#c7c8c9ff"]]}, "size": [80, 30], "state": {"document_mode": true, "flags": 0, "group": "Active", "name": "unselected.document_mode"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 29, "#fcfcfcff"], [29, 1, "#d1d2d2ff"]], "50": [[0, 29, "#fcfcfcff"], [29, 1, "#d1d2d2ff"]], "98": [[0, 29, "#fcfcfcff"], [29, 1, "#d1d2d2ff"]], "99": [[0, 30, "#d1d2d2ff"]]}, "control": "CE_HeaderSection", "id": "header.normal", "rows": {"0": [[0, 99, "#fcfcfcff"], [99, 1, "#d1d2d2ff"]], "15": [[0, 99, "#fcfcfcff"], [99, 1, "#d1d2d2ff"]], "28": [[0, 99, "#fcfcfcff"], [99, 1, "#d1d2d2ff"]], "29": [[0, 100, "#d1d2d2ff"]]}, "size": [100, 30], "state": {"flags": 2, "group": "Active", "name": "normal"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 29, "#d6ecf8ff"], [29, 1, "#b2c5ceff"]], "50": [[0, 29, "#d6ecf8ff"], [29, 1, "#b2c5ceff"]], "98": [[0, 29, "#d6ecf8ff"], [29, 1, "#b2c5ceff"]], "99": [[0, 30, "#b2c5ceff"]]}, "control": "CE_HeaderSection", "id": "header.hover", "rows": {"0": [[0, 99, "#d6ecf8ff"], [99, 1, "#b2c5ceff"]], "15": [[0, 99, "#d6ecf8ff"], [99, 1, "#b2c5ceff"]], "28": [[0, 99, "#d6ecf8ff"], [99, 1, "#b2c5ceff"]], "29": [[0, 100, "#b2c5ceff"]]}, "size": [100, 30], "state": {"flags": 8194, "group": "Active", "name": "hover"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 29, "#d6ecf8ff"], [29, 1, "#b2c5ceff"]], "50": [[0, 29, "#d6ecf8ff"], [29, 1, "#b2c5ceff"]], "98": [[0, 29, "#d6ecf8ff"], [29, 1, "#b2c5ceff"]], "99": [[0, 30, "#b2c5ceff"]]}, "control": "CE_HeaderSection", "id": "header.sunken", "rows": {"0": [[0, 99, "#d6ecf8ff"], [99, 1, "#b2c5ceff"]], "15": [[0, 99, "#d6ecf8ff"], [99, 1, "#b2c5ceff"]], "28": [[0, 99, "#d6ecf8ff"], [99, 1, "#b2c5ceff"]], "29": [[0, 100, "#b2c5ceff"]]}, "size": [100, 30], "state": {"flags": 4, "group": "Active", "name": "sunken"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 24, "#ffffffff"]], "1": [[0, 24, "#ffffffff"]], "100": [[0, 24, "#ffffffff"]], "2": [[0, 24, "#ffffffff"]], "3": [[0, 24, "#ffffffff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.normal", "rows": {"0": [[0, 200, "#ffffffff"]], "1": [[0, 200, "#ffffffff"]], "12": [[0, 200, "#ffffffff"]], "2": [[0, 200, "#ffffffff"]], "22": [[0, 200, "#ffffffff"]], "23": [[0, 200, "#ffffffff"]]}, "size": [200, 24], "state": {"flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 24, "#ffffffff"]], "1": [[0, 24, "#ffffffff"]], "100": [[0, 1, "#ffffffff"], [1, 1, "#5ebbecff"], [2, 19, "#c4e6f8ff"], [21, 1, "#c3e6f8ff"], [22, 1, "#5fbbecff"], [23, 1, "#ffffffff"]], "2": [[0, 3, "#ffffffff"], [3, 1, "#e7f5fcff"], [4, 1, "#96d2f3ff"], [5, 1, "#6bc1edff"], [6, 11, "#5ebbecff"], [17, 1, "#5fbcecff"], [18, 1, "#6cc1eeff"], [19, 1, "#95d3f2ff"], [20, 1, "#e8f5fcff"], [21, 3, "#ffffffff"]], "3": [[0, 2, "#ffffffff"], [2, 1, "#e0f2faff"], [3, 1, "#6cc2edff"], [4, 1, "#a9dbf5ff"], [5, 1, "#bae2f8ff"], [6, 12, "#c4e6f8ff"], [18, 1, "#bae2f7ff"], [19, 1, "#a8dbf4ff"], [20, 1, "#6bc1eeff"], [21, 1, "#e3f2fbff"], [22, 2, "#ffffffff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.hover", "rows": {"0": [[0, 200, "#ffffffff"]], "1": [[0, 4, "#ffffffff"], [4, 1, "#e7f5fcff"], [5, 1, "#96d2f3ff"], [6, 1, "#6bc1edff"], [7, 185, "#5ebbecff"], [192, 1, "#60bdecff"], [193, 1, "#6cc1eeff"], [194, 1, "#95d2f2ff"], [195, 1, "#e8f5fcff"], [196, 4, "#ffffffff"]], "12": [[0, 2, "#ffffffff"], [2, 1, "#5ebbecff"], [3, 193, "#c4e6f8ff"], [196, 1, "#c3e6f8ff"], [197, 1, "#5fbbecff"], [198, 2, "#ffffffff"]], "2": [[0, 3, "#ffffffff"], [3, 1, "#e0f2faff"], [4, 1, "#6cc1eeff"], [5, 1, "#aadcf5ff"], [6, 1, "#bae2f8ff"], [7, 186, "#c4e6f8ff"], [193, 1, "#bae2f7ff"], [194, 1, "#a8daf4ff"], [195, 1, "#6bc2edff"], [196, 1, "#e3f2fbff"], [197, 3, "#ffffffff"]], "22": [[0, 4, "#ffffffff"], [4, 1, "#eaf6fdff"], [5, 1, "#96d4f3ff"], [6, 1, "#6ec2eeff"], [7, 185, "#5fbbecff"], [192, 1, "#62bdedff"], [193, 1, "#6ec2eeff"], [194, 1, "#97d3f2ff"], [195, 1, "#ebf7fdff"], [196, 4, "#ffffffff"]], "23": [[0, 200, "#ffffffff"]]}, "size": [200, 24], "state": {"flags": 8192, "group": "Active", "name": "hover"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 24, "#ffffffff"]], "1": [[0, 24, "#ffffffff"]], "100": [[0, 1, "#ffffffff"], [1, 21, "#3daee9ff"], [22, 1, "#3eafe9ff"], [23, 1, "#ffffffff"]], "2": [[0, 3, "#ffffffff"], [3, 1, "#e1f3fcff"], [4, 1, "#78c6f0ff"], [5, 1, "#48b3eaff"], [6, 11, "#3daee9ff"], [17, 1, "#3eaee9ff"], [18, 1, "#48b3eaff"], [19, 1, "#78c6f0ff"], [20, 1, "#e3f3fcff"], [21, 3, "#ffffffff"]], "3": [[0, 2, "#ffffffff"], [2, 1, "#d5edfaff"], [3, 1, "#47b2eaff"], [4, 16, "#3daee9ff"], [20, 1, "#46b2eaff"], [21, 1, "#d8eefaff"], [22, 2, "#ffffffff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.selected", "rows": {"0": [[0, 200, "#ffffffff"]], "1": [[0, 4, "#ffffffff"], [4, 1, "#e1f3fcff"], [5, 1, "#78c6f0ff"], [6, 1, "#48b3eaff"], [7, 185, "#3daee9ff"], [192, 1, "#3eaee9ff"], [193, 1, "#48b3eaff"], [194, 1, "#78c6f0ff"], [195, 1, "#e3f3fcff"], [196, 4, "#ffffffff"]], "12": [[0, 2, "#ffffffff"], [2, 195, "#3daee9ff"], [197, 1, "#3eafe9ff"], [198, 2, "#ffffffff"]], "2": [[0, 3, "#ffffffff"], [3, 1, "#d5edfaff"], [4, 1, "#47b1eaff"], [5, 190, "#3daee9ff"], [195, 1, "#46b2eaff"], [196, 1, "#d8eefaff"], [197, 3, "#ffffffff"]], "22": [[0, 4, "#ffffffff"], [4, 1, "#e4f4fcff"], [5, 1, "#7ac7f0ff"], [6, 1, "#49b3eaff"], [7, 185, "#3eafe9ff"], [192, 1, "#3fafeaff"], [193, 1, "#4ab3eaff"], [194, 1, "#7bc7f1ff"], [195, 1, "#e6f5fcff"], [196, 4, "#ffffffff"]], "23": [[0, 200, "#ffffffff"]]}, "size": [200, 24], "state": {"flags": 32768, "group": "Active", "name": "selected"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 24, "#ffffffff"]], "1": [[0, 24, "#ffffffff"]], "100": [[0, 1, "#ffffffff"], [1, 21, "#44bfffff"], [22, 1, "#45c0ffff"], [23, 1, "#ffffffff"]], "2": [[0, 3, "#ffffffff"], [3, 1, "#e2f5ffff"], [4, 1, "#7cd1ffff"], [5, 1, "#4fc3ffff"], [6, 11, "#44bfffff"], [17, 1, "#46bfffff"], [18, 1, "#4fc2ffff"], [19, 1, "#7dd2ffff"], [20, 1, "#e4f6ffff"], [21, 3, "#ffffffff"]], "3": [[0, 2, "#ffffffff"], [2, 1, "#d7f0ffff"], [3, 1, "#4dc3ffff"], [4, 16, "#44bfffff"], [20, 1, "#4dc2ffff"], [21, 1, "#d9f2ffff"], [22, 2, "#ffffffff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.selected_hover", "rows": {"0": [[0, 200, "#ffffffff"]], "1": [[0, 4, "#ffffffff"], [4, 1, "#e2f5ffff"], [5, 1, "#7dd1ffff"], [6, 1, "#4fc3ffff"], [7, 185, "#44bfffff"], [192, 1, "#46bfffff"], [193, 1, "#4fc2ffff"], [194, 1, "#7dd2ffff"], [195, 1, "#e4f6ffff"], [196, 4, "#ffffffff"]], "12": [[0, 2, "#ffffffff"], [2, 195, "#44bfffff"], [197, 1, "#45c0ffff"], [198, 2, "#ffffffff"]], "2": [[0, 3, "#ffffffff"], [3, 1, "#d7f0ffff"], [4, 1, "#4dc2ffff"], [5, 190, "#44bfffff"], [195, 1, "#4cc2ffff"], [196, 1, "#d9f2ffff"], [197, 3, "#ffffffff"]], "22": [[0, 4, "#ffffffff"], [4, 1, "#e5f6ffff"], [5, 1, "#7ed3ffff"], [6, 1, "#51c3ffff"], [7, 185, "#45c0ffff"], [192, 1, "#46c0ffff"], [193, 1, "#50c3ffff"], [194, 1, "#7fd3ffff"], [195, 1, "#e7f7ffff"], [196, 4, "#ffffffff"]], "23": [[0, 200, "#ffffffff"]]}, "size": [200, 24], "state": {"flags": 40960, "group": "Active", "name": "selected_hover"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 24, "#ffffffff"]], "1": [[0, 24, "#ffffffff"]], "100": [[0, 1, "#ffffffff"], [1, 21, "#c2e0f5ff"], [22, 1, "#c3e0f5ff"], [23, 1, "#ffffffff"]], "2": [[0, 3, "#ffffffff"], [3, 1, "#f6fafdff"], [4, 1, "#d4e9f8ff"], [5, 1, "#c6e2f6ff"], [6, 12, "#c2e0f5ff"], [18, 1, "#c6e2f6ff"], [19, 1, "#d5eaf8ff"], [20, 1, "#f6fbfeff"], [21, 3, "#ffffffff"]], "3": [[0, 2, "#ffffffff"], [2, 1, "#f2f7fcff"], [3, 1, "#c5e2f6ff"], [4, 16, "#c2e0f5ff"], [20, 1, "#c5e1f5ff"], [21, 1, "#f2f8fcff"], [22, 2, "#ffffffff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.selected_inactive_window", "rows": {"0": [[0, 200, "#ffffffff"]], "1": [[0, 4, "#ffffffff"], [4, 1, "#f6fafdff"], [5, 1, "#d4e9f8ff"], [6, 1, "#c6e2f6ff"], [7, 185, "#c2e0f5ff"], [192, 1, "#c3e0f5ff"], [193, 1, "#c6e2f6ff"], [194, 1, "#d6eaf8ff"], [195, 1, "#f6fbfeff"], [196, 4, "#ffffffff"]], "12": [[0, 2, "#ffffffff"], [2, 195, "#c2e0f5ff"], [197, 1, "#c3e0f5ff"], [198, 2, "#ffffffff"]], "2": [[0, 3, "#ffffffff"], [3, 1, "#f2f7fcff"], [4, 1, "#c5e2f6ff"], [5, 190, "#c2e0f5ff"], [195, 1, "#c5e1f5ff"], [196, 1, "#f2f8fcff"], [197, 3, "#ffffffff"]], "22": [[0, 4, "#ffffffff"], [4, 1, "#f7fbfeff"], [5, 1, "#d5eaf8ff"], [6, 1, "#c6e2f6ff"], [7, 185, "#c3e0f5ff"], [192, 1, "#c3e1f5ff"], [193, 1, "#c6e3f6ff"], [194, 1, "#d5eaf8ff"], [195, 1, "#f7fbfeff"], [196, 4, "#ffffffff"]], "23": [[0, 200, "#ffffffff"]]}, "size": [200, 24], "state": {"flags": 32768, "group": "Inactive", "name": "selected_inactive_window"}}, +{"background": "#ffffffff", "cols": {"0": [[0, 24, "#ffffffff"]], "1": [[0, 24, "#ffffffff"]], "100": [[0, 24, "#ffffffff"]], "2": [[0, 24, "#ffffffff"]], "3": [[0, 24, "#ffffffff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.focus", "rows": {"0": [[0, 200, "#ffffffff"]], "1": [[0, 200, "#ffffffff"]], "12": [[0, 200, "#ffffffff"]], "2": [[0, 200, "#ffffffff"]], "22": [[0, 200, "#ffffffff"]], "23": [[0, 200, "#ffffffff"]]}, "size": [200, 24], "state": {"flags": 8388864, "group": "Active", "name": "focus"}}, +{"background": "#f7f7f7ff", "cols": {"0": [[0, 30, "#cdcdceff"]], "1": [[0, 1, "#cdcdceff"], [1, 28, "#f7f7f7ff"], [29, 1, "#cdcdceff"]], "50": [[0, 1, "#cdcdceff"], [1, 28, "#f7f7f7ff"], [29, 1, "#cdcdceff"]]}, "control": "PE_PanelTipLabel", "id": "tooltip.panel", "rows": {"0": [[0, 100, "#cdcdceff"]], "1": [[0, 1, "#cdcdceff"], [1, 98, "#f7f7f7ff"], [99, 1, "#cdcdceff"]], "15": [[0, 1, "#cdcdceff"], [1, 98, "#f7f7f7ff"], [99, 1, "#cdcdceff"]]}, "size": [100, 30], "state": {"flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d6d7d9ff"], [4, 1, "#cacccdff"], [5, 39, "#c6c8c9ff"], [44, 1, "#c7c9caff"], [45, 1, "#cbcccdff"], [46, 1, "#d7d8d9ff"], [47, 1, "#e9eaebff"], [48, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#e9eaeaff"], [2, 1, "#cdcfd0ff"], [3, 1, "#f0f1f1ff"], [4, 1, "#fafafaff"], [5, 40, "#ffffffff"], [45, 1, "#f9fafaff"], [46, 1, "#f0f1f1ff"], [47, 1, "#cccfd0ff"], [48, 1, "#e9eaecff"], [49, 1, "#eff0f1ff"]], "50": [[0, 1, "#c6c8c9ff"], [1, 47, "#ffffffff"], [48, 1, "#fefefeff"], [49, 1, "#c7c9caff"]]}, "control": "PE_Frame", "id": "frame.styled_panel", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d6d7d9ff"], [4, 1, "#cacccdff"], [5, 89, "#c6c8c9ff"], [94, 1, "#c7c9caff"], [95, 1, "#cbcccdff"], [96, 1, "#d6d8d9ff"], [97, 1, "#e9eaebff"], [98, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#e9eaeaff"], [2, 1, "#cdcecfff"], [3, 1, "#f0f1f1ff"], [4, 1, "#fafafaff"], [5, 90, "#ffffffff"], [95, 1, "#f9fafaff"], [96, 1, "#f1f2f2ff"], [97, 1, "#cccfd0ff"], [98, 1, "#e9eaecff"], [99, 1, "#eff0f1ff"]], "25": [[0, 1, "#c6c8c9ff"], [1, 97, "#ffffffff"], [98, 1, "#fefefeff"], [99, 1, "#c7c9caff"]]}, "size": [100, 50], "state": {"flags": 4, "group": "Active", "name": "sunken"}}, +{"background": "#eff0f1ff", "cols": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d5d7d8ff"], [4, 1, "#cacccdff"], [5, 49, "#c6c8c9ff"], [54, 1, "#c7c9caff"], [55, 1, "#cbcccdff"], [56, 1, "#d6d8d8ff"], [57, 1, "#e9eaebff"], [58, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#e8e9eaff"], [2, 1, "#cccecfff"], [3, 1, "#e8eaeaff"], [4, 1, "#f0f1f1ff"], [5, 50, "#f4f5f5ff"], [55, 1, "#eff1f1ff"], [56, 1, "#e8eaeaff"], [57, 1, "#cbcecfff"], [58, 1, "#e9e9ebff"], [59, 1, "#eff0f1ff"]], "2": [[0, 1, "#e8eaebff"], [1, 1, "#cccecfff"], [2, 56, "#f4f5f5ff"], [58, 1, "#cccdcdff"], [59, 1, "#e9eaecff"]], "50": [[0, 1, "#c6c8c9ff"], [1, 57, "#f4f5f5ff"], [58, 1, "#f3f4f4ff"], [59, 1, "#c7c9caff"]]}, "control": "PE_FrameGroupBox", "id": "group_box.frame", "rows": {"0": [[0, 2, "#eff0f1ff"], [2, 1, "#e8eaebff"], [3, 1, "#d5d7d8ff"], [4, 1, "#cacccdff"], [5, 89, "#c6c8c9ff"], [94, 1, "#c7c9caff"], [95, 1, "#cbcccdff"], [96, 1, "#d6d7d8ff"], [97, 1, "#e9eaebff"], [98, 2, "#eff0f1ff"]], "1": [[0, 1, "#eff0f1ff"], [1, 1, "#e8e9eaff"], [2, 1, "#cccecfff"], [3, 1, "#e8eaeaff"], [4, 1, "#f0f1f1ff"], [5, 90, "#f4f5f5ff"], [95, 1, "#eff1f1ff"], [96, 1, "#e8eaeaff"], [97, 1, "#cbcecfff"], [98, 1, "#e9e9ebff"], [99, 1, "#eff0f1ff"]], "2": [[0, 1, "#e8eaebff"], [1, 1, "#cccecfff"], [2, 96, "#f4f5f5ff"], [98, 1, "#ccceceff"], [99, 1, "#e9eaecff"]], "30": [[0, 1, "#c6c8c9ff"], [1, 97, "#f4f5f5ff"], [98, 1, "#f3f4f4ff"], [99, 1, "#c7c9caff"]]}, "size": [100, 60], "state": {"flags": 0, "group": "Active", "name": "normal"}} +] +}, +"BreezeDark": { +"environment": {"image_format": "ARGB32, filled opaque with the palette background, then painted", "kdeglobals_color_scheme": "BreezeDark", "platform": "offscreen", "scheme": "BreezeDark", "style": "breeze"}, +"records": [ +{"background": "#202326ff", "cols": {"0": [[0, 34, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2a2e31ff"], [4, 1, "#434649ff"], [5, 1, "#4e5154ff"], [6, 22, "#535659ff"], [28, 1, "#4e5154ff"], [29, 1, "#424549ff"], [30, 1, "#292c2fff"], [31, 1, "#1f2225ff"], [32, 2, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#353a3cff"], [3, 1, "#4c5052ff"], [4, 1, "#373a3eff"], [5, 1, "#2d3034ff"], [6, 22, "#292c30ff"], [28, 1, "#2d3034ff"], [29, 1, "#373a3eff"], [30, 1, "#4c5052ff"], [31, 1, "#35393bff"], [32, 1, "#1e2124ff"], [33, 1, "#202326ff"]], "42": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 30, "#292c30ff"], [32, 1, "#535659ff"], [33, 1, "#1c1f21ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.normal", "rows": {"0": [[0, 84, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2a2e31ff"], [4, 1, "#434649ff"], [5, 1, "#4e5154ff"], [6, 72, "#535659ff"], [78, 1, "#4e5154ff"], [79, 1, "#434649ff"], [80, 1, "#2a2e31ff"], [81, 3, "#202326ff"]], "17": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 80, "#292c30ff"], [82, 1, "#535659ff"], [83, 1, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#353a3cff"], [3, 1, "#4c5052ff"], [4, 1, "#373a3eff"], [5, 1, "#2d3034ff"], [6, 72, "#292c30ff"], [78, 1, "#2d3034ff"], [79, 1, "#373a3eff"], [80, 1, "#4c5052ff"], [81, 1, "#36393cff"], [82, 2, "#202326ff"]], "32": [[0, 2, "#202326ff"], [2, 1, "#1e2124ff"], [3, 1, "#292b2fff"], [4, 1, "#434649ff"], [5, 1, "#4f5154ff"], [6, 72, "#535659ff"], [78, 1, "#4f5154ff"], [79, 1, "#434649ff"], [80, 1, "#292b2eff"], [81, 1, "#1e2124ff"], [82, 2, "#202326ff"]], "33": [[0, 3, "#202326ff"], [3, 1, "#1f2225ff"], [4, 1, "#1d2023ff"], [5, 1, "#1c1f22ff"], [6, 72, "#1c1f21ff"], [78, 1, "#1c1f22ff"], [79, 1, "#1d2023ff"], [80, 1, "#1f2225ff"], [81, 3, "#202326ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 2, "group": "Active", "name": "normal"}}, +{"background": "#202326ff", "cols": {"0": [[0, 34, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#254150ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 22, "#3daee9ff"], [28, 1, "#3aa0d5ff"], [29, 1, "#3381acff"], [30, 1, "#243f4eff"], [31, 1, "#1f2225ff"], [32, 2, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#2c617dff"], [3, 1, "#399bceff"], [4, 1, "#30586fff"], [5, 1, "#2b3942ff"], [6, 22, "#292c30ff"], [28, 1, "#2b3942ff"], [29, 1, "#30586fff"], [30, 1, "#399bceff"], [31, 1, "#2c607cff"], [32, 1, "#1e2124ff"], [33, 1, "#202326ff"]], "42": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 30, "#292c30ff"], [32, 1, "#3daee9ff"], [33, 1, "#1c1f21ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.hover", "rows": {"0": [[0, 84, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#254150ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 72, "#3daee9ff"], [78, 1, "#3aa0d5ff"], [79, 1, "#3482acff"], [80, 1, "#254150ff"], [81, 3, "#202326ff"]], "17": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 80, "#292c30ff"], [82, 1, "#3daee9ff"], [83, 1, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#2c617dff"], [3, 1, "#399bceff"], [4, 1, "#30586fff"], [5, 1, "#2b3942ff"], [6, 72, "#292c30ff"], [78, 1, "#2b3942ff"], [79, 1, "#30586fff"], [80, 1, "#399bceff"], [81, 1, "#2c617cff"], [82, 2, "#202326ff"]], "32": [[0, 2, "#202326ff"], [2, 1, "#1e2124ff"], [3, 1, "#243e4eff"], [4, 1, "#3482acff"], [5, 1, "#3ba0d5ff"], [6, 72, "#3daee9ff"], [78, 1, "#3ba0d5ff"], [79, 1, "#3482acff"], [80, 1, "#243e4dff"], [81, 1, "#1e2124ff"], [82, 2, "#202326ff"]], "33": [[0, 3, "#202326ff"], [3, 1, "#1f2225ff"], [4, 1, "#1d2023ff"], [5, 1, "#1c1f22ff"], [6, 72, "#1c1f21ff"], [78, 1, "#1c1f22ff"], [79, 1, "#1d2023ff"], [80, 1, "#1f2225ff"], [81, 3, "#202326ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 8194, "group": "Active", "name": "hover"}}, +{"background": "#202326ff", "cols": {"0": [[0, 34, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#254150ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 22, "#3daee9ff"], [28, 1, "#3aa0d5ff"], [29, 1, "#3381acff"], [30, 1, "#243f4eff"], [31, 1, "#1f2225ff"], [32, 2, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#2c617dff"], [3, 1, "#399bceff"], [4, 1, "#30586fff"], [5, 1, "#2b3942ff"], [6, 22, "#292c30ff"], [28, 1, "#2b3942ff"], [29, 1, "#30586fff"], [30, 1, "#399bceff"], [31, 1, "#2c607cff"], [32, 1, "#1e2124ff"], [33, 1, "#202326ff"]], "42": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 30, "#292c30ff"], [32, 1, "#3daee9ff"], [33, 1, "#1c1f21ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.focus", "rows": {"0": [[0, 84, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#254150ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 72, "#3daee9ff"], [78, 1, "#3aa0d5ff"], [79, 1, "#3482acff"], [80, 1, "#254150ff"], [81, 3, "#202326ff"]], "17": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 80, "#292c30ff"], [82, 1, "#3daee9ff"], [83, 1, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#2c617dff"], [3, 1, "#399bceff"], [4, 1, "#30586fff"], [5, 1, "#2b3942ff"], [6, 72, "#292c30ff"], [78, 1, "#2b3942ff"], [79, 1, "#30586fff"], [80, 1, "#399bceff"], [81, 1, "#2c617cff"], [82, 2, "#202326ff"]], "32": [[0, 2, "#202326ff"], [2, 1, "#1e2124ff"], [3, 1, "#243e4eff"], [4, 1, "#3482acff"], [5, 1, "#3ba0d5ff"], [6, 72, "#3daee9ff"], [78, 1, "#3ba0d5ff"], [79, 1, "#3482acff"], [80, 1, "#243e4dff"], [81, 1, "#1e2124ff"], [82, 2, "#202326ff"]], "33": [[0, 3, "#202326ff"], [3, 1, "#1f2225ff"], [4, 1, "#1d2023ff"], [5, 1, "#1c1f22ff"], [6, 72, "#1c1f21ff"], [78, 1, "#1c1f22ff"], [79, 1, "#1d2023ff"], [80, 1, "#1f2225ff"], [81, 3, "#202326ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 8388866, "group": "Active", "name": "focus"}}, +{"background": "#202326ff", "cols": {"0": [[0, 34, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3585b0ff"], [5, 1, "#3ba2d8ff"], [6, 22, "#3daee9ff"], [28, 1, "#3ba2d8ff"], [29, 1, "#3585b0ff"], [30, 1, "#264151ff"], [31, 3, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#2e6380ff"], [3, 1, "#3a9ed3ff"], [4, 1, "#347294ff"], [5, 1, "#305c75ff"], [6, 22, "#2f5368ff"], [28, 1, "#305c75ff"], [29, 1, "#347294ff"], [30, 1, "#3a9ed3ff"], [31, 1, "#2e6380ff"], [32, 2, "#202326ff"]], "42": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 30, "#2f5368ff"], [32, 1, "#3daee9ff"], [33, 1, "#202326ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.pressed", "rows": {"0": [[0, 84, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3585b0ff"], [5, 1, "#3ba2d8ff"], [6, 72, "#3daee9ff"], [78, 1, "#3ba2d8ff"], [79, 1, "#3585b0ff"], [80, 1, "#264151ff"], [81, 3, "#202326ff"]], "17": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 80, "#2f5368ff"], [82, 1, "#3daee9ff"], [83, 1, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#2e6380ff"], [3, 1, "#3a9ed3ff"], [4, 1, "#347294ff"], [5, 1, "#305c75ff"], [6, 72, "#2f5368ff"], [78, 1, "#305c75ff"], [79, 1, "#347294ff"], [80, 1, "#3a9ed3ff"], [81, 1, "#2d637fff"], [82, 2, "#202326ff"]], "32": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3585b0ff"], [5, 1, "#3ba2d8ff"], [6, 72, "#3daee9ff"], [78, 1, "#3ba2d8ff"], [79, 1, "#3585b0ff"], [80, 1, "#264150ff"], [81, 3, "#202326ff"]], "33": [[0, 84, "#202326ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 4, "group": "Active", "name": "pressed"}}, +{"background": "#202326ff", "cols": {"0": [[0, 34, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#283840ff"], [4, 1, "#3b657cff"], [5, 1, "#457a97ff"], [6, 22, "#4882a1ff"], [28, 1, "#457a97ff"], [29, 1, "#3b657bff"], [30, 1, "#27363eff"], [31, 1, "#1f2225ff"], [32, 2, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#314e5eff"], [3, 1, "#447893ff"], [4, 1, "#365a6eff"], [5, 1, "#304c5dff"], [6, 22, "#2d4655ff"], [28, 1, "#304c5dff"], [29, 1, "#365a6eff"], [30, 1, "#447894ff"], [31, 1, "#304d5dff"], [32, 1, "#1e2124ff"], [33, 1, "#202326ff"]], "42": [[0, 1, "#202326ff"], [1, 1, "#4882a1ff"], [2, 30, "#2d4655ff"], [32, 1, "#4882a1ff"], [33, 1, "#1c1f21ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.default", "rows": {"0": [[0, 84, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#283840ff"], [4, 1, "#3b657cff"], [5, 1, "#457a97ff"], [6, 72, "#4882a1ff"], [78, 1, "#457a97ff"], [79, 1, "#3b657cff"], [80, 1, "#283840ff"], [81, 3, "#202326ff"]], "17": [[0, 1, "#202326ff"], [1, 1, "#4882a1ff"], [2, 80, "#2d4655ff"], [82, 1, "#4882a1ff"], [83, 1, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#314e5eff"], [3, 1, "#447893ff"], [4, 1, "#365a6eff"], [5, 1, "#304c5dff"], [6, 72, "#2d4655ff"], [78, 1, "#304c5dff"], [79, 1, "#365a6eff"], [80, 1, "#447893ff"], [81, 1, "#314d5eff"], [82, 2, "#202326ff"]], "32": [[0, 2, "#202326ff"], [2, 1, "#1e2124ff"], [3, 1, "#27353eff"], [4, 1, "#3b667cff"], [5, 1, "#457a97ff"], [6, 72, "#4882a1ff"], [78, 1, "#457a97ff"], [79, 1, "#3b667cff"], [80, 1, "#27343eff"], [81, 1, "#1e2124ff"], [82, 2, "#202326ff"]], "33": [[0, 3, "#202326ff"], [3, 1, "#1f2225ff"], [4, 1, "#1d2023ff"], [5, 1, "#1c1f22ff"], [6, 72, "#1c1f21ff"], [78, 1, "#1c1f22ff"], [79, 1, "#1d2023ff"], [80, 1, "#1f2225ff"], [81, 3, "#202326ff"]]}, "size": [84, 34], "state": {"features": 4, "flags": 2, "group": "Active", "name": "default"}}, +{"background": "#1f2124ff", "cols": {"0": [[0, 34, "#1f2124ff"]], "1": [[0, 3, "#1f2124ff"], [3, 1, "#242629ff"], [4, 1, "#2e3134ff"], [5, 1, "#343639ff"], [6, 22, "#35383bff"], [28, 1, "#343639ff"], [29, 1, "#2e3134ff"], [30, 1, "#242629ff"], [31, 3, "#1f2124ff"]], "2": [[0, 2, "#1f2124ff"], [2, 1, "#292b2fff"], [3, 1, "#333639ff"], [4, 1, "#2c2f32ff"], [5, 1, "#282b2fff"], [6, 22, "#272a2eff"], [28, 1, "#282b2fff"], [29, 1, "#2c2f32ff"], [30, 1, "#333639ff"], [31, 1, "#292b2fff"], [32, 2, "#1f2124ff"]], "42": [[0, 1, "#1f2124ff"], [1, 1, "#35383bff"], [2, 30, "#272a2eff"], [32, 1, "#35383bff"], [33, 1, "#1f2124ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.disabled", "rows": {"0": [[0, 84, "#1f2124ff"]], "1": [[0, 3, "#1f2124ff"], [3, 1, "#242629ff"], [4, 1, "#2e3134ff"], [5, 1, "#343639ff"], [6, 72, "#35383bff"], [78, 1, "#343639ff"], [79, 1, "#2e3134ff"], [80, 1, "#242629ff"], [81, 3, "#1f2124ff"]], "17": [[0, 1, "#1f2124ff"], [1, 1, "#35383bff"], [2, 80, "#272a2eff"], [82, 1, "#35383bff"], [83, 1, "#1f2124ff"]], "2": [[0, 2, "#1f2124ff"], [2, 1, "#292b2fff"], [3, 1, "#333639ff"], [4, 1, "#2c2f32ff"], [5, 1, "#282b2fff"], [6, 72, "#272a2eff"], [78, 1, "#282b2fff"], [79, 1, "#2c2f32ff"], [80, 1, "#333639ff"], [81, 1, "#292b2eff"], [82, 2, "#1f2124ff"]], "32": [[0, 3, "#1f2124ff"], [3, 1, "#242629ff"], [4, 1, "#2e3134ff"], [5, 1, "#343639ff"], [6, 72, "#35383bff"], [78, 1, "#343639ff"], [79, 1, "#2e3134ff"], [80, 1, "#232629ff"], [81, 3, "#1f2124ff"]], "33": [[0, 84, "#1f2124ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 2, "group": "Disabled", "name": "disabled"}}, +{"background": "#202326ff", "cols": {"0": [[0, 34, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2a2e31ff"], [4, 1, "#434649ff"], [5, 1, "#4e5154ff"], [6, 22, "#535659ff"], [28, 1, "#4e5154ff"], [29, 1, "#424549ff"], [30, 1, "#292c2fff"], [31, 1, "#1f2225ff"], [32, 2, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#353a3cff"], [3, 1, "#4c5052ff"], [4, 1, "#373a3eff"], [5, 1, "#2d3034ff"], [6, 22, "#292c30ff"], [28, 1, "#2d3034ff"], [29, 1, "#373a3eff"], [30, 1, "#4c5052ff"], [31, 1, "#35393bff"], [32, 1, "#1e2124ff"], [33, 1, "#202326ff"]], "42": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 30, "#292c30ff"], [32, 1, "#535659ff"], [33, 1, "#1c1f21ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.inactive_window", "rows": {"0": [[0, 84, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2a2e31ff"], [4, 1, "#434649ff"], [5, 1, "#4e5154ff"], [6, 72, "#535659ff"], [78, 1, "#4e5154ff"], [79, 1, "#434649ff"], [80, 1, "#2a2e31ff"], [81, 3, "#202326ff"]], "17": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 80, "#292c30ff"], [82, 1, "#535659ff"], [83, 1, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#353a3cff"], [3, 1, "#4c5052ff"], [4, 1, "#373a3eff"], [5, 1, "#2d3034ff"], [6, 72, "#292c30ff"], [78, 1, "#2d3034ff"], [79, 1, "#373a3eff"], [80, 1, "#4c5052ff"], [81, 1, "#36393cff"], [82, 2, "#202326ff"]], "32": [[0, 2, "#202326ff"], [2, 1, "#1e2124ff"], [3, 1, "#292b2fff"], [4, 1, "#434649ff"], [5, 1, "#4f5154ff"], [6, 72, "#535659ff"], [78, 1, "#4f5154ff"], [79, 1, "#434649ff"], [80, 1, "#292b2eff"], [81, 1, "#1e2124ff"], [82, 2, "#202326ff"]], "33": [[0, 3, "#202326ff"], [3, 1, "#1f2225ff"], [4, 1, "#1d2023ff"], [5, 1, "#1c1f22ff"], [6, 72, "#1c1f21ff"], [78, 1, "#1c1f22ff"], [79, 1, "#1d2023ff"], [80, 1, "#1f2225ff"], [81, 3, "#202326ff"]]}, "size": [84, 34], "state": {"features": 0, "flags": 2, "group": "Inactive", "name": "inactive_window"}}, +{"background": "#202326ff", "cols": {"0": [[0, 34, "#202326ff"]], "1": [[0, 34, "#202326ff"]], "2": [[0, 34, "#202326ff"]], "42": [[0, 34, "#202326ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.flat", "rows": {"0": [[0, 84, "#202326ff"]], "1": [[0, 84, "#202326ff"]], "17": [[0, 84, "#202326ff"]], "2": [[0, 84, "#202326ff"]], "32": [[0, 84, "#202326ff"]], "33": [[0, 84, "#202326ff"]]}, "size": [84, 34], "state": {"features": 1, "flags": 0, "group": "Active", "name": "flat"}}, +{"background": "#202326ff", "cols": {"0": [[0, 34, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 22, "#3daee9ff"], [28, 1, "#3aa0d5ff"], [29, 1, "#3482acff"], [30, 1, "#264151ff"], [31, 3, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#2e617eff"], [3, 1, "#399acdff"], [4, 1, "#2a5268ff"], [5, 1, "#233139ff"], [6, 22, "#202326ff"], [28, 1, "#233139ff"], [29, 1, "#2a5268ff"], [30, 1, "#399acdff"], [31, 1, "#2e617eff"], [32, 2, "#202326ff"]], "42": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 30, "#202326ff"], [32, 1, "#3daee9ff"], [33, 1, "#202326ff"]]}, "control": "CE_PushButtonBevel", "id": "push_button.flat_hover", "rows": {"0": [[0, 84, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 72, "#3daee9ff"], [78, 1, "#3aa0d5ff"], [79, 1, "#3482acff"], [80, 1, "#264151ff"], [81, 3, "#202326ff"]], "17": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 80, "#202326ff"], [82, 1, "#3daee9ff"], [83, 1, "#202326ff"]], "2": [[0, 2, "#202326ff"], [2, 1, "#2e617eff"], [3, 1, "#399acdff"], [4, 1, "#2a5268ff"], [5, 1, "#233139ff"], [6, 72, "#202326ff"], [78, 1, "#233139ff"], [79, 1, "#2a5268ff"], [80, 1, "#399acdff"], [81, 1, "#2d617dff"], [82, 2, "#202326ff"]], "32": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 72, "#3daee9ff"], [78, 1, "#3aa0d5ff"], [79, 1, "#3482acff"], [80, 1, "#264150ff"], [81, 3, "#202326ff"]], "33": [[0, 84, "#202326ff"]]}, "size": [84, 34], "state": {"features": 1, "flags": 8192, "group": "Active", "name": "flat_hover"}}, +{"background": "#202326ff", "cols": {"0": [[0, 28, "#202326ff"]], "1": [[0, 28, "#202326ff"]], "14": [[0, 28, "#202326ff"]]}, "control": "CC_ToolButton", "id": "tool_button.normal", "rows": {"0": [[0, 28, "#202326ff"]], "1": [[0, 28, "#202326ff"]], "14": [[0, 28, "#202326ff"]]}, "size": [28, 28], "state": {"flags": 4098, "group": "Active", "name": "normal"}}, +{"background": "#202326ff", "cols": {"0": [[0, 28, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 16, "#3daee9ff"], [22, 1, "#3aa0d5ff"], [23, 1, "#3482acff"], [24, 1, "#264151ff"], [25, 3, "#202326ff"]], "14": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 24, "#202326ff"], [26, 1, "#3daee9ff"], [27, 1, "#202326ff"]]}, "control": "CC_ToolButton", "id": "tool_button.hover", "rows": {"0": [[0, 28, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 16, "#3daee9ff"], [22, 1, "#3aa0d5ff"], [23, 1, "#3482acff"], [24, 1, "#264151ff"], [25, 3, "#202326ff"]], "14": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 24, "#202326ff"], [26, 1, "#3daee9ff"], [27, 1, "#202326ff"]]}, "size": [28, 28], "state": {"flags": 12290, "group": "Active", "name": "hover"}}, +{"background": "#202326ff", "cols": {"0": [[0, 28, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3484afff"], [5, 1, "#3ba1d7ff"], [6, 16, "#3daee9ff"], [22, 1, "#3ba1d7ff"], [23, 1, "#3484afff"], [24, 1, "#264151ff"], [25, 3, "#202326ff"]], "14": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 24, "#284c61ff"], [26, 1, "#3daee9ff"], [27, 1, "#202326ff"]]}, "control": "CC_ToolButton", "id": "tool_button.pressed", "rows": {"0": [[0, 28, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#264151ff"], [4, 1, "#3484afff"], [5, 1, "#3ba1d7ff"], [6, 16, "#3daee9ff"], [22, 1, "#3ba1d7ff"], [23, 1, "#3484afff"], [24, 1, "#264151ff"], [25, 3, "#202326ff"]], "14": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 24, "#284c61ff"], [26, 1, "#3daee9ff"], [27, 1, "#202326ff"]]}, "size": [28, 28], "state": {"flags": 12292, "group": "Active", "name": "pressed"}}, +{"background": "#202326ff", "cols": {"0": [[0, 28, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2b2e32ff"], [4, 1, "#45484aff"], [5, 1, "#4f5255ff"], [6, 16, "#535659ff"], [22, 1, "#4f5255ff"], [23, 1, "#45484aff"], [24, 1, "#2b2e32ff"], [25, 3, "#202326ff"]], "14": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 24, "#3c3f41ff"], [26, 1, "#535659ff"], [27, 1, "#202326ff"]]}, "control": "CC_ToolButton", "id": "tool_button.checked", "rows": {"0": [[0, 28, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2b2e32ff"], [4, 1, "#45484aff"], [5, 1, "#4f5255ff"], [6, 16, "#535659ff"], [22, 1, "#4f5255ff"], [23, 1, "#45484aff"], [24, 1, "#2b2e32ff"], [25, 3, "#202326ff"]], "14": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 24, "#3c3f41ff"], [26, 1, "#535659ff"], [27, 1, "#202326ff"]]}, "size": [28, 28], "state": {"flags": 4128, "group": "Active", "name": "checked"}}, +{"background": "#202326ff", "cols": {"0": [[0, 28, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2a2e31ff"], [4, 1, "#434649ff"], [5, 1, "#4e5154ff"], [6, 16, "#535659ff"], [22, 1, "#4e5154ff"], [23, 1, "#424549ff"], [24, 1, "#292c2fff"], [25, 1, "#1f2225ff"], [26, 2, "#202326ff"]], "14": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 24, "#292c30ff"], [26, 1, "#535659ff"], [27, 1, "#1c1f21ff"]]}, "control": "CC_ToolButton", "id": "tool_button.framed", "rows": {"0": [[0, 28, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2a2e31ff"], [4, 1, "#434649ff"], [5, 1, "#4e5154ff"], [6, 16, "#535659ff"], [22, 1, "#4e5154ff"], [23, 1, "#434649ff"], [24, 1, "#2a2e31ff"], [25, 3, "#202326ff"]], "14": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 24, "#292c30ff"], [26, 1, "#535659ff"], [27, 1, "#202326ff"]]}, "size": [28, 28], "state": {"flags": 2, "group": "Active", "name": "framed"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3d40ff"], [4, 1, "#484a4cff"], [5, 21, "#4c4e51ff"], [26, 1, "#4b4d51ff"], [27, 1, "#48494cff"], [28, 1, "#3a3d40ff"], [29, 1, "#26292cff"], [30, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#272a2cff"], [2, 1, "#44474aff"], [3, 1, "#232427ff"], [4, 1, "#191b1eff"], [5, 22, "#141618ff"], [27, 1, "#191c1eff"], [28, 1, "#222426ff"], [29, 1, "#45474aff"], [30, 1, "#25282cff"], [31, 1, "#202326ff"]], "2": [[0, 1, "#272a2cff"], [1, 1, "#44474aff"], [2, 28, "#141618ff"], [30, 1, "#46484aff"], [31, 1, "#26292cff"]], "75": [[0, 1, "#4c4e51ff"], [1, 29, "#141618ff"], [30, 1, "#151719ff"], [31, 1, "#4b4d50ff"]]}, "control": "PE_PanelLineEdit", "id": "line_edit.normal", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3d40ff"], [4, 1, "#484a4cff"], [5, 139, "#4c4e51ff"], [144, 1, "#4b4d50ff"], [145, 1, "#48494cff"], [146, 1, "#3a3d40ff"], [147, 1, "#26292cff"], [148, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#272a2cff"], [2, 1, "#44474aff"], [3, 1, "#222426ff"], [4, 1, "#191b1eff"], [5, 140, "#141618ff"], [145, 1, "#191c1eff"], [146, 1, "#232426ff"], [147, 1, "#44484aff"], [148, 1, "#26282cff"], [149, 1, "#202326ff"]], "16": [[0, 1, "#4c4e51ff"], [1, 147, "#141618ff"], [148, 1, "#151719ff"], [149, 1, "#4b4d50ff"]], "2": [[0, 1, "#272a2cff"], [1, 1, "#44474aff"], [2, 146, "#141618ff"], [148, 1, "#46474bff"], [149, 1, "#26292cff"]], "30": [[0, 1, "#202326ff"], [1, 1, "#25282cff"], [2, 1, "#46484aff"], [3, 1, "#232527ff"], [4, 1, "#1a1d1eff"], [5, 140, "#151719ff"], [145, 1, "#1b1c1eff"], [146, 1, "#242528ff"], [147, 1, "#46484bff"], [148, 1, "#26282bff"], [149, 1, "#202326ff"]], "31": [[0, 2, "#202326ff"], [2, 1, "#26292cff"], [3, 1, "#3b3d40ff"], [4, 1, "#47484cff"], [5, 140, "#4b4d50ff"], [145, 1, "#46484cff"], [146, 1, "#3a3c3fff"], [147, 1, "#26282bff"], [148, 2, "#202326ff"]]}, "size": [150, 32], "state": {"flags": 4, "group": "Active", "line_width": 6, "name": "normal"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#243944ff"], [3, 1, "#327aa0ff"], [4, 1, "#3aa1d6ff"], [5, 21, "#3daee9ff"], [26, 1, "#3cabe6ff"], [27, 1, "#3aa0d5ff"], [28, 1, "#31799fff"], [29, 1, "#243742ff"], [30, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#233945ff"], [2, 1, "#3999ccff"], [3, 1, "#1e3b4cff"], [4, 1, "#18242cff"], [5, 22, "#141618ff"], [27, 1, "#18252dff"], [28, 1, "#1e3d4dff"], [29, 1, "#389bceff"], [30, 1, "#233743ff"], [31, 1, "#202326ff"]], "2": [[0, 1, "#243944ff"], [1, 1, "#3899ccff"], [2, 28, "#141618ff"], [30, 1, "#399cd0ff"], [31, 1, "#243641ff"]], "75": [[0, 1, "#3daee9ff"], [1, 29, "#141618ff"], [30, 1, "#15181bff"], [31, 1, "#3cace6ff"]]}, "control": "PE_PanelLineEdit", "id": "line_edit.hover", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#243944ff"], [3, 1, "#327aa0ff"], [4, 1, "#3aa1d6ff"], [5, 139, "#3daee9ff"], [144, 1, "#3cabe5ff"], [145, 1, "#3aa0d5ff"], [146, 1, "#31799fff"], [147, 1, "#243742ff"], [148, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#233945ff"], [2, 1, "#3899ccff"], [3, 1, "#1d3b4bff"], [4, 1, "#18242cff"], [5, 140, "#141618ff"], [145, 1, "#18252dff"], [146, 1, "#1f3d4dff"], [147, 1, "#389bceff"], [148, 1, "#243743ff"], [149, 1, "#202326ff"]], "16": [[0, 1, "#3daee9ff"], [1, 147, "#141618ff"], [148, 1, "#15181bff"], [149, 1, "#3cace6ff"]], "2": [[0, 1, "#243944ff"], [1, 1, "#3999ccff"], [2, 146, "#141618ff"], [148, 1, "#399bcfff"], [149, 1, "#243641ff"]], "30": [[0, 1, "#202326ff"], [1, 1, "#233743ff"], [2, 1, "#399cd0ff"], [3, 1, "#1e3e4eff"], [4, 1, "#19272fff"], [5, 139, "#15181bff"], [144, 1, "#14171aff"], [145, 1, "#19272fff"], [146, 1, "#1f3e50ff"], [147, 1, "#399cd1ff"], [148, 1, "#243640ff"], [149, 1, "#202326ff"]], "31": [[0, 2, "#202326ff"], [2, 1, "#243641ff"], [3, 1, "#31789dff"], [4, 1, "#399ed3ff"], [5, 139, "#3cace6ff"], [144, 1, "#3caae4ff"], [145, 1, "#399dd2ff"], [146, 1, "#31779dff"], [147, 1, "#24353fff"], [148, 2, "#202326ff"]]}, "size": [150, 32], "state": {"flags": 8196, "group": "Active", "line_width": 6, "name": "hover"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#243944ff"], [3, 1, "#327aa0ff"], [4, 1, "#3aa1d6ff"], [5, 21, "#3daee9ff"], [26, 1, "#3cabe6ff"], [27, 1, "#3aa0d5ff"], [28, 1, "#31799fff"], [29, 1, "#243742ff"], [30, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#233945ff"], [2, 1, "#3999ccff"], [3, 1, "#1e3b4cff"], [4, 1, "#18242cff"], [5, 22, "#141618ff"], [27, 1, "#18252dff"], [28, 1, "#1e3d4dff"], [29, 1, "#389bceff"], [30, 1, "#233743ff"], [31, 1, "#202326ff"]], "2": [[0, 1, "#243944ff"], [1, 1, "#3899ccff"], [2, 28, "#141618ff"], [30, 1, "#399cd0ff"], [31, 1, "#243641ff"]], "75": [[0, 1, "#3daee9ff"], [1, 29, "#141618ff"], [30, 1, "#15181bff"], [31, 1, "#3cace6ff"]]}, "control": "PE_PanelLineEdit", "id": "line_edit.focus", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#243944ff"], [3, 1, "#327aa0ff"], [4, 1, "#3aa1d6ff"], [5, 139, "#3daee9ff"], [144, 1, "#3cabe5ff"], [145, 1, "#3aa0d5ff"], [146, 1, "#31799fff"], [147, 1, "#243742ff"], [148, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#233945ff"], [2, 1, "#3899ccff"], [3, 1, "#1d3b4bff"], [4, 1, "#18242cff"], [5, 140, "#141618ff"], [145, 1, "#18252dff"], [146, 1, "#1f3d4dff"], [147, 1, "#389bceff"], [148, 1, "#243743ff"], [149, 1, "#202326ff"]], "16": [[0, 1, "#3daee9ff"], [1, 147, "#141618ff"], [148, 1, "#15181bff"], [149, 1, "#3cace6ff"]], "2": [[0, 1, "#243944ff"], [1, 1, "#3999ccff"], [2, 146, "#141618ff"], [148, 1, "#399bcfff"], [149, 1, "#243641ff"]], "30": [[0, 1, "#202326ff"], [1, 1, "#233743ff"], [2, 1, "#399cd0ff"], [3, 1, "#1e3e4eff"], [4, 1, "#19272fff"], [5, 139, "#15181bff"], [144, 1, "#14171aff"], [145, 1, "#19272fff"], [146, 1, "#1f3e50ff"], [147, 1, "#399cd1ff"], [148, 1, "#243640ff"], [149, 1, "#202326ff"]], "31": [[0, 2, "#202326ff"], [2, 1, "#243641ff"], [3, 1, "#31789dff"], [4, 1, "#399ed3ff"], [5, 139, "#3cace6ff"], [144, 1, "#3caae4ff"], [145, 1, "#399dd2ff"], [146, 1, "#31779dff"], [147, 1, "#24353fff"], [148, 2, "#202326ff"]]}, "size": [150, 32], "state": {"flags": 8388868, "group": "Active", "line_width": 6, "name": "focus"}}, +{"background": "#1f2124ff", "cols": {"0": [[0, 2, "#1f2124ff"], [2, 1, "#212326ff"], [3, 1, "#27292dff"], [4, 1, "#2c2e31ff"], [5, 21, "#2d3033ff"], [26, 1, "#2c2f32ff"], [27, 1, "#2c2e31ff"], [28, 1, "#27292cff"], [29, 1, "#222326ff"], [30, 2, "#1f2124ff"]], "1": [[0, 1, "#1f2124ff"], [1, 1, "#202225ff"], [2, 1, "#2a2e2fff"], [3, 1, "#1a1c1eff"], [4, 1, "#15181aff"], [5, 22, "#131517ff"], [27, 1, "#15181aff"], [28, 1, "#1a1c1eff"], [29, 1, "#2a2d30ff"], [30, 1, "#212225ff"], [31, 1, "#1f2124ff"]], "2": [[0, 1, "#212326ff"], [1, 1, "#2a2d30ff"], [2, 28, "#131517ff"], [30, 1, "#2a2d31ff"], [31, 1, "#212326ff"]], "75": [[0, 1, "#2d3033ff"], [1, 29, "#131517ff"], [30, 1, "#141618ff"], [31, 1, "#2c2f32ff"]]}, "control": "PE_PanelLineEdit", "id": "line_edit.disabled", "rows": {"0": [[0, 2, "#1f2124ff"], [2, 1, "#212326ff"], [3, 1, "#272a2dff"], [4, 1, "#2c2e31ff"], [5, 139, "#2d3033ff"], [144, 1, "#2c2f32ff"], [145, 1, "#2c2e31ff"], [146, 1, "#262a2cff"], [147, 1, "#222326ff"], [148, 2, "#1f2124ff"]], "1": [[0, 1, "#1f2124ff"], [1, 1, "#202225ff"], [2, 1, "#2a2d30ff"], [3, 1, "#1a1c1eff"], [4, 1, "#15181aff"], [5, 140, "#131517ff"], [145, 1, "#15181aff"], [146, 1, "#1a1c1eff"], [147, 1, "#292e30ff"], [148, 1, "#212225ff"], [149, 1, "#1f2124ff"]], "16": [[0, 1, "#2d3033ff"], [1, 147, "#131517ff"], [148, 1, "#141618ff"], [149, 1, "#2c2f32ff"]], "2": [[0, 1, "#212326ff"], [1, 1, "#2a2e2fff"], [2, 146, "#131517ff"], [148, 1, "#2a2d30ff"], [149, 1, "#212326ff"]], "30": [[0, 1, "#1f2124ff"], [1, 1, "#212225ff"], [2, 1, "#2a2d31ff"], [3, 1, "#1a1c1fff"], [4, 1, "#16181aff"], [5, 139, "#141618ff"], [144, 1, "#131517ff"], [145, 1, "#16181aff"], [146, 1, "#191c1eff"], [147, 1, "#2a2e30ff"], [148, 1, "#202325ff"], [149, 1, "#1f2124ff"]], "31": [[0, 2, "#1f2124ff"], [2, 1, "#212326ff"], [3, 1, "#27292dff"], [4, 1, "#2b2e30ff"], [5, 139, "#2c2f32ff"], [144, 1, "#2d3033ff"], [145, 1, "#2b2e31ff"], [146, 1, "#262a2cff"], [147, 1, "#212326ff"], [148, 2, "#1f2124ff"]]}, "size": [150, 32], "state": {"flags": 4, "group": "Disabled", "line_width": 6, "name": "disabled"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#292c30ff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#212427ff"], [4, 1, "#34373aff"], [5, 1, "#444648ff"], [6, 1, "#4c4d50ff"], [7, 6, "#4c4e51ff"], [13, 1, "#4c4d50ff"], [14, 1, "#444648ff"], [15, 1, "#34373aff"], [16, 1, "#212427ff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#212427ff"], [3, 1, "#404345ff"], [4, 1, "#404245ff"], [5, 1, "#303237ff"], [6, 1, "#2a2d31ff"], [7, 6, "#292c30ff"], [13, 1, "#2a2d31ff"], [14, 1, "#303237ff"], [15, 1, "#404245ff"], [16, 1, "#404345ff"], [17, 1, "#212427ff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#292c30ff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.off", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#292c30ff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]], "12": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#292c30ff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#212427ff"], [4, 1, "#34373aff"], [5, 1, "#444648ff"], [6, 1, "#4c4d50ff"], [7, 6, "#4c4e51ff"], [13, 1, "#4c4d50ff"], [14, 1, "#444648ff"], [15, 1, "#34363aff"], [16, 1, "#212427ff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#212427ff"], [3, 1, "#404345ff"], [4, 1, "#404245ff"], [5, 1, "#303237ff"], [6, 1, "#2a2d31ff"], [7, 6, "#292c30ff"], [13, 1, "#2a2d31ff"], [14, 1, "#303237ff"], [15, 1, "#404245ff"], [16, 1, "#404345ff"], [17, 1, "#212427ff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#292c30ff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 8, "group": "Active", "name": "off"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 6, "#2f5368ff"], [9, 1, "#416275ff"], [10, 1, "#d9dfe3ff"], [11, 1, "#fcfcfcff"], [12, 1, "#d9dfe3ff"], [13, 1, "#416275ff"], [14, 3, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#22272cff"], [4, 1, "#3484aeff"], [5, 1, "#3da9e1ff"], [6, 1, "#3daee8ff"], [7, 6, "#3daee9ff"], [13, 1, "#3daee8ff"], [14, 1, "#3da9e1ff"], [15, 1, "#3484aeff"], [16, 1, "#22272cff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#22272cff"], [3, 2, "#3ba4daff"], [5, 1, "#337296ff"], [6, 1, "#2f576dff"], [7, 6, "#2f5368ff"], [13, 1, "#2f576dff"], [14, 1, "#337296ff"], [15, 2, "#3ba4daff"], [17, 1, "#22272cff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 7, "#2f5368ff"], [10, 1, "#416275ff"], [11, 1, "#e9eceeff"], [12, 1, "#fcfcfcff"], [13, 1, "#dae0e4ff"], [14, 1, "#426376ff"], [15, 2, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.on", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 1, "#2f5368ff"], [4, 1, "#426376ff"], [5, 1, "#dae0e4ff"], [6, 1, "#fcfcfcff"], [7, 1, "#d9dfe3ff"], [8, 2, "#416275ff"], [10, 1, "#d9dfe3ff"], [11, 1, "#fcfcfcff"], [12, 1, "#d9dfe3ff"], [13, 1, "#416275ff"], [14, 3, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "12": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 3, "#2f5368ff"], [6, 1, "#416275ff"], [7, 1, "#d9dfe3ff"], [8, 2, "#fcfcfcff"], [10, 1, "#d9dfe3ff"], [11, 1, "#416275ff"], [12, 5, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#22272cff"], [4, 1, "#3484aeff"], [5, 1, "#3da9e1ff"], [6, 1, "#3daee8ff"], [7, 6, "#3daee9ff"], [13, 1, "#3daee8ff"], [14, 1, "#3da9e1ff"], [15, 1, "#3582adff"], [16, 1, "#22272cff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#22272cff"], [3, 2, "#3ba4daff"], [5, 1, "#337296ff"], [6, 1, "#2f576dff"], [7, 6, "#2f5368ff"], [13, 1, "#2f576dff"], [14, 1, "#337296ff"], [15, 1, "#3ba3daff"], [16, 1, "#3ba4daff"], [17, 1, "#22272cff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 2, "#2f5368ff"], [5, 2, "#426376ff"], [7, 4, "#2f5368ff"], [11, 1, "#416275ff"], [12, 1, "#d9dfe3ff"], [13, 1, "#fcfcfcff"], [14, 1, "#d9dfe3ff"], [15, 1, "#416275ff"], [16, 1, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 32, "group": "Active", "name": "on"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 6, "#2f5368ff"], [9, 2, "#fcfcfcff"], [11, 6, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#22272cff"], [4, 1, "#3484aeff"], [5, 1, "#3da9e1ff"], [6, 1, "#3daee8ff"], [7, 6, "#3daee9ff"], [13, 1, "#3daee8ff"], [14, 1, "#3da9e1ff"], [15, 1, "#3484aeff"], [16, 1, "#22272cff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#22272cff"], [3, 2, "#3ba4daff"], [5, 1, "#337296ff"], [6, 1, "#2f576dff"], [7, 6, "#2f5368ff"], [13, 1, "#2f576dff"], [14, 1, "#337296ff"], [15, 2, "#3ba4daff"], [17, 1, "#22272cff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 14, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.partial", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 2, "#2f5368ff"], [5, 2, "#fcfcfcff"], [7, 2, "#2f5368ff"], [9, 2, "#fcfcfcff"], [11, 2, "#2f5368ff"], [13, 2, "#fcfcfcff"], [15, 2, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "12": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 14, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#22272cff"], [4, 1, "#3484aeff"], [5, 1, "#3da9e1ff"], [6, 1, "#3daee8ff"], [7, 6, "#3daee9ff"], [13, 1, "#3daee8ff"], [14, 1, "#3da9e1ff"], [15, 1, "#3582adff"], [16, 1, "#22272cff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#22272cff"], [3, 2, "#3ba4daff"], [5, 1, "#337296ff"], [6, 1, "#2f576dff"], [7, 6, "#2f5368ff"], [13, 1, "#2f576dff"], [14, 1, "#337296ff"], [15, 1, "#3ba3daff"], [16, 1, "#3ba4daff"], [17, 1, "#22272cff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 14, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 16, "group": "Active", "name": "partial"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 14, "#292c30ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#22262aff"], [4, 1, "#386c87ff"], [5, 1, "#3f99c8ff"], [6, 1, "#3dade6ff"], [7, 6, "#3daee9ff"], [13, 1, "#3dade6ff"], [14, 1, "#3f99c8ff"], [15, 1, "#386c87ff"], [16, 1, "#22262aff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#22262aff"], [3, 1, "#3e8fbaff"], [4, 1, "#3e8bb3ff"], [5, 1, "#334a5aff"], [6, 1, "#2a3035ff"], [7, 6, "#292c30ff"], [13, 1, "#2a3035ff"], [14, 1, "#334a5aff"], [15, 1, "#3e8bb3ff"], [16, 1, "#3e8fbaff"], [17, 1, "#22262aff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 14, "#292c30ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.hover_off", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 14, "#292c30ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "12": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 14, "#292c30ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#22262aff"], [4, 1, "#386c87ff"], [5, 1, "#3f99c8ff"], [6, 1, "#3dade6ff"], [7, 6, "#3daee9ff"], [13, 1, "#3dade6ff"], [14, 1, "#3f99c8ff"], [15, 1, "#386a87ff"], [16, 1, "#22262aff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#22262aff"], [3, 1, "#3e8fbaff"], [4, 1, "#3e8bb3ff"], [5, 1, "#334a5aff"], [6, 1, "#2a3035ff"], [7, 6, "#292c30ff"], [13, 1, "#2a3035ff"], [14, 1, "#334a5aff"], [15, 1, "#3e8bb3ff"], [16, 1, "#3e8fbaff"], [17, 1, "#22262aff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 14, "#292c30ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 8200, "group": "Active", "name": "hover_off"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 6, "#2f5368ff"], [9, 1, "#416275ff"], [10, 1, "#d9dfe3ff"], [11, 1, "#fcfcfcff"], [12, 1, "#d9dfe3ff"], [13, 1, "#416275ff"], [14, 3, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#22272cff"], [4, 1, "#3484aeff"], [5, 1, "#3da9e1ff"], [6, 1, "#3daee8ff"], [7, 6, "#3daee9ff"], [13, 1, "#3daee8ff"], [14, 1, "#3da9e1ff"], [15, 1, "#3484aeff"], [16, 1, "#22272cff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#22272cff"], [3, 2, "#3ba4daff"], [5, 1, "#337296ff"], [6, 1, "#2f576dff"], [7, 6, "#2f5368ff"], [13, 1, "#2f576dff"], [14, 1, "#337296ff"], [15, 2, "#3ba4daff"], [17, 1, "#22272cff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 7, "#2f5368ff"], [10, 1, "#416275ff"], [11, 1, "#e9eceeff"], [12, 1, "#fcfcfcff"], [13, 1, "#dae0e4ff"], [14, 1, "#426376ff"], [15, 2, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.focus_on", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 1, "#2f5368ff"], [4, 1, "#426376ff"], [5, 1, "#dae0e4ff"], [6, 1, "#fcfcfcff"], [7, 1, "#d9dfe3ff"], [8, 2, "#416275ff"], [10, 1, "#d9dfe3ff"], [11, 1, "#fcfcfcff"], [12, 1, "#d9dfe3ff"], [13, 1, "#416275ff"], [14, 3, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "12": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 3, "#2f5368ff"], [6, 1, "#416275ff"], [7, 1, "#d9dfe3ff"], [8, 2, "#fcfcfcff"], [10, 1, "#d9dfe3ff"], [11, 1, "#416275ff"], [12, 5, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#22272cff"], [4, 1, "#3484aeff"], [5, 1, "#3da9e1ff"], [6, 1, "#3daee8ff"], [7, 6, "#3daee9ff"], [13, 1, "#3daee8ff"], [14, 1, "#3da9e1ff"], [15, 1, "#3582adff"], [16, 1, "#22272cff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#22272cff"], [3, 2, "#3ba4daff"], [5, 1, "#337296ff"], [6, 1, "#2f576dff"], [7, 6, "#2f5368ff"], [13, 1, "#2f576dff"], [14, 1, "#337296ff"], [15, 1, "#3ba3daff"], [16, 1, "#3ba4daff"], [17, 1, "#22272cff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 2, "#2f5368ff"], [5, 2, "#426376ff"], [7, 4, "#2f5368ff"], [11, 1, "#416275ff"], [12, 1, "#d9dfe3ff"], [13, 1, "#fcfcfcff"], [14, 1, "#d9dfe3ff"], [15, 1, "#416275ff"], [16, 1, "#2f5368ff"], [17, 1, "#3daee9ff"], [18, 2, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 8388896, "group": "Active", "name": "focus_on"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#25282cff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#212427ff"], [4, 1, "#34373aff"], [5, 1, "#444548ff"], [6, 1, "#4c4d50ff"], [7, 6, "#4c4e51ff"], [13, 1, "#4c4d50ff"], [14, 1, "#444548ff"], [15, 1, "#34373aff"], [16, 1, "#212427ff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#212427ff"], [3, 1, "#3f4245ff"], [4, 1, "#3f4144ff"], [5, 1, "#2d2f33ff"], [6, 1, "#26292dff"], [7, 6, "#25282cff"], [13, 1, "#26292dff"], [14, 1, "#2d2f33ff"], [15, 1, "#3f4144ff"], [16, 1, "#3f4245ff"], [17, 1, "#212427ff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#25282cff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.pressed_off", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#25282cff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]], "12": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#25282cff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]], "2": [[0, 3, "#202326ff"], [3, 1, "#212427ff"], [4, 1, "#34373aff"], [5, 1, "#444548ff"], [6, 1, "#4c4d50ff"], [7, 6, "#4c4e51ff"], [13, 1, "#4c4d50ff"], [14, 1, "#444548ff"], [15, 1, "#33363aff"], [16, 1, "#212427ff"], [17, 3, "#202326ff"]], "3": [[0, 2, "#202326ff"], [2, 1, "#212427ff"], [3, 1, "#3f4245ff"], [4, 1, "#3f4144ff"], [5, 1, "#2d2f33ff"], [6, 1, "#26292dff"], [7, 6, "#25282cff"], [13, 1, "#26292dff"], [14, 1, "#2d2f33ff"], [15, 1, "#3f4144ff"], [16, 1, "#3f4245ff"], [17, 1, "#212427ff"], [18, 2, "#202326ff"]], "8": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 14, "#25282cff"], [17, 1, "#4c4e51ff"], [18, 2, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 12, "group": "Active", "name": "pressed_off"}}, +{"background": "#1f2124ff", "cols": {"10": [[0, 3, "#1f2124ff"], [3, 6, "#24272bff"], [9, 1, "#292c30ff"], [10, 1, "#555859ff"], [11, 1, "#606263ff"], [12, 1, "#555859ff"], [13, 1, "#292c30ff"], [14, 3, "#24272bff"], [17, 3, "#1f2124ff"]], "2": [[0, 4, "#1f2124ff"], [4, 1, "#202225ff"], [5, 1, "#202125ff"], [6, 8, "#1f2124ff"], [14, 1, "#202125ff"], [15, 1, "#202225ff"], [16, 4, "#1f2124ff"]], "3": [[0, 3, "#1f2124ff"], [3, 1, "#1f2225ff"], [4, 1, "#202225ff"], [5, 1, "#222529ff"], [6, 8, "#24272bff"], [14, 1, "#222529ff"], [15, 1, "#202225ff"], [16, 1, "#1f2225ff"], [17, 3, "#1f2124ff"]], "8": [[0, 3, "#1f2124ff"], [3, 7, "#24272bff"], [10, 1, "#292c30ff"], [11, 1, "#5a5d5eff"], [12, 1, "#606263ff"], [13, 1, "#565859ff"], [14, 1, "#2a2c30ff"], [15, 2, "#24272bff"], [17, 3, "#1f2124ff"]]}, "control": "PE_IndicatorCheckBox", "id": "check_box.disabled_on", "rows": {"10": [[0, 3, "#1f2124ff"], [3, 1, "#24272bff"], [4, 1, "#2a2c30ff"], [5, 1, "#565859ff"], [6, 1, "#606263ff"], [7, 1, "#555859ff"], [8, 2, "#292c30ff"], [10, 1, "#555859ff"], [11, 1, "#606263ff"], [12, 1, "#555859ff"], [13, 1, "#292c30ff"], [14, 3, "#24272bff"], [17, 3, "#1f2124ff"]], "12": [[0, 3, "#1f2124ff"], [3, 3, "#24272bff"], [6, 1, "#292c30ff"], [7, 1, "#555859ff"], [8, 2, "#606263ff"], [10, 1, "#555859ff"], [11, 1, "#292c30ff"], [12, 5, "#24272bff"], [17, 3, "#1f2124ff"]], "2": [[0, 4, "#1f2124ff"], [4, 1, "#202225ff"], [5, 1, "#202125ff"], [6, 8, "#1f2124ff"], [14, 2, "#202125ff"], [16, 4, "#1f2124ff"]], "3": [[0, 3, "#1f2124ff"], [3, 1, "#1f2225ff"], [4, 1, "#202225ff"], [5, 1, "#222529ff"], [6, 8, "#24272bff"], [14, 1, "#222529ff"], [15, 1, "#202224ff"], [16, 1, "#1f2225ff"], [17, 3, "#1f2124ff"]], "8": [[0, 3, "#1f2124ff"], [3, 2, "#24272bff"], [5, 2, "#2a2c30ff"], [7, 4, "#24272bff"], [11, 1, "#292c30ff"], [12, 1, "#555859ff"], [13, 1, "#606263ff"], [14, 1, "#555859ff"], [15, 1, "#292c30ff"], [16, 1, "#24272bff"], [17, 3, "#1f2124ff"]]}, "size": [20, 20], "state": {"flags": 32, "group": "Disabled", "name": "disabled_on"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#4a4c4fff"], [3, 1, "#2b2e32ff"], [4, 12, "#292c30ff"], [16, 1, "#2b2e32ff"], [17, 1, "#4a4c4fff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.off", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#4a4c4fff"], [3, 1, "#2b2e32ff"], [4, 12, "#292c30ff"], [16, 1, "#2b2e32ff"], [17, 1, "#4a4c4fff"], [18, 2, "#202326ff"]], "3": [[0, 5, "#202326ff"], [5, 1, "#383b3eff"], [6, 1, "#474a4dff"], [7, 1, "#393c40ff"], [8, 1, "#303236ff"], [9, 2, "#2b2e32ff"], [11, 1, "#303236ff"], [12, 1, "#393c40ff"], [13, 1, "#474a4dff"], [14, 1, "#383b3eff"], [15, 5, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 8, "group": "Active", "name": "off"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 1, "#305d74ff"], [4, 3, "#2f5368ff"], [7, 1, "#e8eceeff"], [8, 4, "#fcfcfcff"], [12, 1, "#e8ebedff"], [13, 3, "#2f5368ff"], [16, 1, "#305d74ff"], [17, 1, "#3daee8ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.on", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 1, "#305d74ff"], [4, 3, "#2f5368ff"], [7, 1, "#e8eceeff"], [8, 4, "#fcfcfcff"], [12, 1, "#e8ebedff"], [13, 3, "#2f5368ff"], [16, 1, "#305d74ff"], [17, 1, "#3daee8ff"], [18, 2, "#202326ff"]], "3": [[0, 4, "#202326ff"], [4, 1, "#20272cff"], [5, 1, "#3790c1ff"], [6, 1, "#3dace6ff"], [7, 1, "#3994c5ff"], [8, 1, "#337092ff"], [9, 2, "#305d74ff"], [11, 1, "#337092ff"], [12, 1, "#3994c5ff"], [13, 1, "#3dace6ff"], [14, 1, "#3790c1ff"], [15, 1, "#20272cff"], [16, 4, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 32, "group": "Active", "name": "on"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daae2ff"], [3, 1, "#2c353bff"], [4, 12, "#292c30ff"], [16, 1, "#2c353bff"], [17, 1, "#3ea9e1ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.hover_off", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daae2ff"], [3, 1, "#2c353bff"], [4, 12, "#292c30ff"], [16, 1, "#2c353bff"], [17, 1, "#3ea9e1ff"], [18, 2, "#202326ff"]], "3": [[0, 4, "#202326ff"], [4, 1, "#202529ff"], [5, 1, "#3b789aff"], [6, 1, "#3ea2d7ff"], [7, 1, "#3b7290ff"], [8, 1, "#324856ff"], [9, 2, "#2c353bff"], [11, 1, "#324856ff"], [12, 1, "#3b7290ff"], [13, 1, "#3ea2d7ff"], [14, 1, "#3b789aff"], [15, 1, "#202529ff"], [16, 4, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 8200, "group": "Active", "name": "hover_off"}}, +{"background": "#202326ff", "cols": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 1, "#305d74ff"], [4, 3, "#2f5368ff"], [7, 1, "#e8eceeff"], [8, 4, "#fcfcfcff"], [12, 1, "#e8ebedff"], [13, 3, "#2f5368ff"], [16, 1, "#305d74ff"], [17, 1, "#3daee8ff"], [18, 2, "#202326ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.focus_on", "rows": {"10": [[0, 2, "#202326ff"], [2, 1, "#3daee9ff"], [3, 1, "#305d74ff"], [4, 3, "#2f5368ff"], [7, 1, "#e8eceeff"], [8, 4, "#fcfcfcff"], [12, 1, "#e8ebedff"], [13, 3, "#2f5368ff"], [16, 1, "#305d74ff"], [17, 1, "#3daee8ff"], [18, 2, "#202326ff"]], "3": [[0, 4, "#202326ff"], [4, 1, "#20272cff"], [5, 1, "#3790c1ff"], [6, 1, "#3dace6ff"], [7, 1, "#3994c5ff"], [8, 1, "#337092ff"], [9, 2, "#305d74ff"], [11, 1, "#337092ff"], [12, 1, "#3994c5ff"], [13, 1, "#3dace6ff"], [14, 1, "#3790c1ff"], [15, 1, "#20272cff"], [16, 4, "#202326ff"]]}, "size": [20, 20], "state": {"flags": 8388896, "group": "Active", "name": "focus_on"}}, +{"background": "#1f2124ff", "cols": {"10": [[0, 2, "#1f2124ff"], [2, 1, "#202124ff"], [3, 4, "#24272bff"], [7, 1, "#5a5d5eff"], [8, 4, "#606263ff"], [12, 1, "#5b5c5dff"], [13, 4, "#24272bff"], [17, 3, "#1f2124ff"]]}, "control": "PE_IndicatorRadioButton", "id": "radio_button.disabled_on", "rows": {"10": [[0, 2, "#1f2124ff"], [2, 1, "#202124ff"], [3, 4, "#24272bff"], [7, 1, "#5a5d5eff"], [8, 4, "#606263ff"], [12, 1, "#5b5c5dff"], [13, 4, "#24272bff"], [17, 3, "#1f2124ff"]], "3": [[0, 6, "#1f2124ff"], [6, 1, "#1e2124ff"], [7, 1, "#212326ff"], [8, 1, "#242528ff"], [9, 2, "#24272bff"], [11, 1, "#242528ff"], [12, 1, "#212326ff"], [13, 1, "#1e2124ff"], [14, 6, "#1f2124ff"]]}, "size": [20, 20], "state": {"flags": 32, "group": "Disabled", "name": "disabled_on"}}, +{"background": "#202326ff", "cols": {"0": [[0, 32, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2a2e31ff"], [4, 1, "#434649ff"], [5, 1, "#4e5154ff"], [6, 20, "#535659ff"], [26, 1, "#4e5154ff"], [27, 1, "#424549ff"], [28, 1, "#292c2fff"], [29, 1, "#1f2225ff"], [30, 2, "#202326ff"]], "140": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 13, "#292c30ff"], [15, 1, "#585a5dff"], [16, 1, "#cfd0d1ff"], [17, 1, "#585a5dff"], [18, 12, "#292c30ff"], [30, 1, "#535659ff"], [31, 1, "#1c1f21ff"]], "40": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 28, "#292c30ff"], [30, 1, "#535659ff"], [31, 1, "#1c1f21ff"]]}, "control": "CC_ComboBox", "id": "combo_box.normal", "rows": {"0": [[0, 150, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#2a2e31ff"], [4, 1, "#434649ff"], [5, 1, "#4e5154ff"], [6, 138, "#535659ff"], [144, 1, "#4e5154ff"], [145, 1, "#434649ff"], [146, 1, "#2a2e31ff"], [147, 3, "#202326ff"]], "16": [[0, 1, "#202326ff"], [1, 1, "#535659ff"], [2, 132, "#292c30ff"], [134, 1, "#585a5dff"], [135, 1, "#cfd0d1ff"], [136, 1, "#585a5dff"], [137, 2, "#292c30ff"], [139, 1, "#585a5dff"], [140, 1, "#cfd0d1ff"], [141, 1, "#585a5dff"], [142, 6, "#292c30ff"], [148, 1, "#535659ff"], [149, 1, "#202326ff"]], "31": [[0, 3, "#202326ff"], [3, 1, "#1f2225ff"], [4, 1, "#1d2023ff"], [5, 1, "#1c1f22ff"], [6, 138, "#1c1f21ff"], [144, 1, "#1c1f22ff"], [145, 1, "#1d2023ff"], [146, 1, "#1f2225ff"], [147, 3, "#202326ff"]]}, "size": [150, 32], "state": {"editable": false, "flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#202326ff", "cols": {"0": [[0, 32, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#254150ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 20, "#3daee9ff"], [26, 1, "#3aa0d5ff"], [27, 1, "#3381acff"], [28, 1, "#243f4eff"], [29, 1, "#1f2225ff"], [30, 2, "#202326ff"]], "140": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 13, "#292c30ff"], [15, 1, "#585a5dff"], [16, 1, "#cfd0d1ff"], [17, 1, "#585a5dff"], [18, 12, "#292c30ff"], [30, 1, "#3daee9ff"], [31, 1, "#1c1f21ff"]], "40": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 28, "#292c30ff"], [30, 1, "#3daee9ff"], [31, 1, "#1c1f21ff"]]}, "control": "CC_ComboBox", "id": "combo_box.hover", "rows": {"0": [[0, 150, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#254150ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 138, "#3daee9ff"], [144, 1, "#3aa0d5ff"], [145, 1, "#3482acff"], [146, 1, "#254150ff"], [147, 3, "#202326ff"]], "16": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 132, "#292c30ff"], [134, 1, "#585a5dff"], [135, 1, "#cfd0d1ff"], [136, 1, "#585a5dff"], [137, 2, "#292c30ff"], [139, 1, "#585a5dff"], [140, 1, "#cfd0d1ff"], [141, 1, "#585a5dff"], [142, 6, "#292c30ff"], [148, 1, "#3daee9ff"], [149, 1, "#202326ff"]], "31": [[0, 3, "#202326ff"], [3, 1, "#1f2225ff"], [4, 1, "#1d2023ff"], [5, 1, "#1c1f22ff"], [6, 138, "#1c1f21ff"], [144, 1, "#1c1f22ff"], [145, 1, "#1d2023ff"], [146, 1, "#1f2225ff"], [147, 3, "#202326ff"]]}, "size": [150, 32], "state": {"editable": false, "flags": 8192, "group": "Active", "name": "hover"}}, +{"background": "#202326ff", "cols": {"0": [[0, 32, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#254150ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 20, "#3daee9ff"], [26, 1, "#3aa0d5ff"], [27, 1, "#3381acff"], [28, 1, "#243f4eff"], [29, 1, "#1f2225ff"], [30, 2, "#202326ff"]], "140": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 13, "#292c30ff"], [15, 1, "#606265ff"], [16, 1, "#ececedff"], [17, 1, "#606265ff"], [18, 12, "#292c30ff"], [30, 1, "#3daee9ff"], [31, 1, "#1c1f21ff"]], "40": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 28, "#292c30ff"], [30, 1, "#3daee9ff"], [31, 1, "#1c1f21ff"]]}, "control": "CC_ComboBox", "id": "combo_box.focus", "rows": {"0": [[0, 150, "#202326ff"]], "1": [[0, 3, "#202326ff"], [3, 1, "#254150ff"], [4, 1, "#3482acff"], [5, 1, "#3aa0d5ff"], [6, 138, "#3daee9ff"], [144, 1, "#3aa0d5ff"], [145, 1, "#3482acff"], [146, 1, "#254150ff"], [147, 3, "#202326ff"]], "16": [[0, 1, "#202326ff"], [1, 1, "#3daee9ff"], [2, 132, "#292c30ff"], [134, 1, "#606265ff"], [135, 1, "#ececedff"], [136, 1, "#606265ff"], [137, 2, "#292c30ff"], [139, 1, "#606265ff"], [140, 1, "#ececedff"], [141, 1, "#606265ff"], [142, 6, "#292c30ff"], [148, 1, "#3daee9ff"], [149, 1, "#202326ff"]], "31": [[0, 3, "#202326ff"], [3, 1, "#1f2225ff"], [4, 1, "#1d2023ff"], [5, 1, "#1c1f22ff"], [6, 138, "#1c1f21ff"], [144, 1, "#1c1f22ff"], [145, 1, "#1d2023ff"], [146, 1, "#1f2225ff"], [147, 3, "#202326ff"]]}, "size": [150, 32], "state": {"editable": false, "flags": 8388864, "group": "Active", "name": "focus"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3d40ff"], [4, 1, "#484a4cff"], [5, 21, "#4c4e51ff"], [26, 1, "#4b4d51ff"], [27, 1, "#48494cff"], [28, 1, "#3a3d40ff"], [29, 1, "#26292cff"], [30, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#272a2cff"], [2, 1, "#44474aff"], [3, 1, "#232427ff"], [4, 1, "#191b1eff"], [5, 22, "#141618ff"], [27, 1, "#191c1eff"], [28, 1, "#222426ff"], [29, 1, "#45474aff"], [30, 1, "#25282cff"], [31, 1, "#202326ff"]], "140": [[0, 1, "#4c4e51ff"], [1, 14, "#141618ff"], [15, 1, "#494a4cff"], [16, 1, "#cccdceff"], [17, 1, "#494a4cff"], [18, 12, "#141618ff"], [30, 1, "#151719ff"], [31, 1, "#4b4d50ff"]], "40": [[0, 1, "#4c4e51ff"], [1, 29, "#141618ff"], [30, 1, "#151719ff"], [31, 1, "#4b4d50ff"]]}, "control": "CC_ComboBox", "id": "combo_box.editable", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3d40ff"], [4, 1, "#484a4cff"], [5, 139, "#4c4e51ff"], [144, 1, "#4b4d50ff"], [145, 1, "#48494cff"], [146, 1, "#3a3d40ff"], [147, 1, "#26292cff"], [148, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#272a2cff"], [2, 1, "#44474aff"], [3, 1, "#222426ff"], [4, 1, "#191b1eff"], [5, 140, "#141618ff"], [145, 1, "#191c1eff"], [146, 1, "#232426ff"], [147, 1, "#44484aff"], [148, 1, "#26282cff"], [149, 1, "#202326ff"]], "16": [[0, 1, "#4c4e51ff"], [1, 133, "#141618ff"], [134, 1, "#494a4cff"], [135, 1, "#cccdceff"], [136, 1, "#494a4cff"], [137, 2, "#141618ff"], [139, 1, "#494a4cff"], [140, 1, "#cccdceff"], [141, 1, "#494a4cff"], [142, 6, "#141618ff"], [148, 1, "#151719ff"], [149, 1, "#4b4d50ff"]], "31": [[0, 2, "#202326ff"], [2, 1, "#26292cff"], [3, 1, "#3b3d40ff"], [4, 1, "#47484cff"], [5, 140, "#4b4d50ff"], [145, 1, "#46484cff"], [146, 1, "#3a3c3fff"], [147, 1, "#26282bff"], [148, 2, "#202326ff"]]}, "size": [150, 32], "state": {"editable": true, "flags": 0, "group": "Active", "name": "editable"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3d40ff"], [4, 1, "#484a4cff"], [5, 21, "#4c4e51ff"], [26, 1, "#4b4d51ff"], [27, 1, "#48494cff"], [28, 1, "#3a3d40ff"], [29, 1, "#26292cff"], [30, 2, "#202326ff"]], "40": [[0, 1, "#4c4e51ff"], [1, 29, "#141618ff"], [30, 1, "#151719ff"], [31, 1, "#4b4d50ff"]], "90": [[0, 1, "#4c4e51ff"], [1, 8, "#141618ff"], [9, 1, "#48494bff"], [10, 1, "#caccccff"], [11, 1, "#48494bff"], [12, 8, "#141618ff"], [20, 1, "#48494bff"], [21, 1, "#caccccff"], [22, 1, "#48494bff"], [23, 7, "#141618ff"], [30, 1, "#151719ff"], [31, 1, "#4b4d50ff"]]}, "control": "CC_SpinBox", "id": "spin_box.normal", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3d40ff"], [4, 1, "#484a4cff"], [5, 89, "#4c4e51ff"], [94, 1, "#4b4d50ff"], [95, 1, "#48494cff"], [96, 1, "#3a3d40ff"], [97, 1, "#26292cff"], [98, 2, "#202326ff"]], "16": [[0, 1, "#4c4e51ff"], [1, 97, "#141618ff"], [98, 1, "#151719ff"], [99, 1, "#4b4d50ff"]], "24": [[0, 1, "#4c4e51ff"], [1, 86, "#141618ff"], [87, 2, "#47484aff"], [89, 9, "#141618ff"], [98, 1, "#151719ff"], [99, 1, "#4b4d50ff"]], "31": [[0, 2, "#202326ff"], [2, 1, "#26292cff"], [3, 1, "#3b3d40ff"], [4, 1, "#47484cff"], [5, 90, "#4b4d50ff"], [95, 1, "#46484cff"], [96, 1, "#3a3c3fff"], [97, 1, "#26282bff"], [98, 2, "#202326ff"]], "8": [[0, 1, "#4c4e51ff"], [1, 85, "#141618ff"], [86, 1, "#48494bff"], [87, 2, "#d9dadaff"], [89, 1, "#48494bff"], [90, 8, "#141618ff"], [98, 1, "#151719ff"], [99, 1, "#4b4d50ff"]]}, "size": [100, 32], "state": {"flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#243944ff"], [3, 1, "#327aa0ff"], [4, 1, "#3aa1d6ff"], [5, 21, "#3daee9ff"], [26, 1, "#3cabe6ff"], [27, 1, "#3aa0d5ff"], [28, 1, "#31799fff"], [29, 1, "#243742ff"], [30, 2, "#202326ff"]], "40": [[0, 1, "#3daee9ff"], [1, 29, "#141618ff"], [30, 1, "#15181bff"], [31, 1, "#3cace6ff"]], "90": [[0, 1, "#3daee9ff"], [1, 8, "#141618ff"], [9, 1, "#48494bff"], [10, 1, "#caccccff"], [11, 1, "#48494bff"], [12, 8, "#141618ff"], [20, 1, "#48494bff"], [21, 1, "#caccccff"], [22, 1, "#48494bff"], [23, 7, "#141618ff"], [30, 1, "#15181bff"], [31, 1, "#3cace6ff"]]}, "control": "CC_SpinBox", "id": "spin_box.hover", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#243944ff"], [3, 1, "#327aa0ff"], [4, 1, "#3aa1d6ff"], [5, 89, "#3daee9ff"], [94, 1, "#3cabe5ff"], [95, 1, "#3aa0d5ff"], [96, 1, "#31799fff"], [97, 1, "#243742ff"], [98, 2, "#202326ff"]], "16": [[0, 1, "#3daee9ff"], [1, 97, "#141618ff"], [98, 1, "#15181bff"], [99, 1, "#3cace6ff"]], "24": [[0, 1, "#3daee9ff"], [1, 86, "#141618ff"], [87, 2, "#47484aff"], [89, 9, "#141618ff"], [98, 1, "#15181bff"], [99, 1, "#3cace6ff"]], "31": [[0, 2, "#202326ff"], [2, 1, "#243641ff"], [3, 1, "#31789dff"], [4, 1, "#399ed3ff"], [5, 89, "#3cace6ff"], [94, 1, "#3caae4ff"], [95, 1, "#399dd2ff"], [96, 1, "#31779dff"], [97, 1, "#24353fff"], [98, 2, "#202326ff"]], "8": [[0, 1, "#3daee9ff"], [1, 85, "#141618ff"], [86, 1, "#48494bff"], [87, 2, "#d9dadaff"], [89, 1, "#48494bff"], [90, 8, "#141618ff"], [98, 1, "#15181bff"], [99, 1, "#3cace6ff"]]}, "size": [100, 32], "state": {"flags": 8192, "group": "Active", "name": "hover"}}, +{"background": "#202326ff", "cols": {"10": [[0, 7, "#202326ff"], [7, 1, "#457c99ff"], [8, 4, "#3584aeff"], [12, 1, "#457c99ff"], [13, 7, "#202326ff"]], "190": [[0, 7, "#202326ff"], [7, 1, "#585a5cff"], [8, 4, "#3e4144ff"], [12, 1, "#585a5cff"], [13, 7, "#202326ff"]], "63": [[0, 1, "#202326ff"], [1, 1, "#515457ff"], [2, 1, "#2b2e32ff"], [3, 14, "#292c30ff"], [17, 1, "#2b2e33ff"], [18, 1, "#515457ff"], [19, 1, "#1e2124ff"]]}, "control": "CC_Slider", "id": "slider.normal", "rows": {"10": [[0, 1, "#45728bff"], [1, 1, "#3585b0ff"], [2, 53, "#3584aeff"], [55, 1, "#51565aff"], [56, 1, "#2b2e32ff"], [57, 14, "#292c30ff"], [71, 1, "#2b2e33ff"], [72, 1, "#515558ff"], [73, 1, "#383b3eff"], [74, 124, "#3e4144ff"], [198, 1, "#424548ff"], [199, 1, "#505355ff"]]}, "size": [200, 20], "state": {"flags": 128, "group": "Active", "handle_rect": [54, 0, 20, 20], "name": "normal", "value": 30}}, +{"background": "#202326ff", "cols": {"10": [[0, 7, "#202326ff"], [7, 1, "#457c99ff"], [8, 4, "#3584aeff"], [12, 1, "#457c99ff"], [13, 7, "#202326ff"]], "190": [[0, 7, "#202326ff"], [7, 1, "#585a5cff"], [8, 4, "#3e4144ff"], [12, 1, "#585a5cff"], [13, 7, "#202326ff"]], "63": [[0, 1, "#202326ff"], [1, 1, "#3ca8e0ff"], [2, 1, "#2a3239ff"], [3, 14, "#292c30ff"], [17, 1, "#2a333aff"], [18, 1, "#3ca8e0ff"], [19, 1, "#1e2124ff"]]}, "control": "CC_Slider", "id": "slider.handle_hover", "rows": {"10": [[0, 1, "#45728bff"], [1, 1, "#3585b0ff"], [2, 53, "#3584aeff"], [55, 1, "#3caae3ff"], [56, 1, "#2a3239ff"], [57, 14, "#292c30ff"], [71, 1, "#2a333aff"], [72, 1, "#3ca9e1ff"], [73, 1, "#383b3eff"], [74, 124, "#3e4144ff"], [198, 1, "#424548ff"], [199, 1, "#505355ff"]]}, "size": [200, 20], "state": {"flags": 8320, "group": "Active", "handle_rect": [54, 0, 20, 20], "name": "handle_hover", "value": 30}}, +{"background": "#202326ff", "cols": {"10": [[0, 7, "#202326ff"], [7, 1, "#457c99ff"], [8, 4, "#3584aeff"], [12, 1, "#457c99ff"], [13, 7, "#202326ff"]], "190": [[0, 7, "#202326ff"], [7, 1, "#585a5cff"], [8, 4, "#3e4144ff"], [12, 1, "#585a5cff"], [13, 7, "#202326ff"]], "63": [[0, 1, "#202326ff"], [1, 1, "#3ca8e0ff"], [2, 1, "#2a3239ff"], [3, 14, "#292c30ff"], [17, 1, "#2a333aff"], [18, 1, "#3ca8e0ff"], [19, 1, "#1e2124ff"]]}, "control": "CC_Slider", "id": "slider.focus", "rows": {"10": [[0, 1, "#45728bff"], [1, 1, "#3585b0ff"], [2, 53, "#3584aeff"], [55, 1, "#3caae3ff"], [56, 1, "#2a3239ff"], [57, 14, "#292c30ff"], [71, 1, "#2a333aff"], [72, 1, "#3ca9e1ff"], [73, 1, "#383b3eff"], [74, 124, "#3e4144ff"], [198, 1, "#424548ff"], [199, 1, "#505355ff"]]}, "size": [200, 20], "state": {"flags": 8388992, "group": "Active", "handle_rect": [54, 0, 20, 20], "name": "focus", "value": 30}}, +{"background": "#1f2124ff", "cols": {"10": [[0, 7, "#1f2124ff"], [7, 1, "#323337ff"], [8, 4, "#2a2b2eff"], [12, 1, "#323337ff"], [13, 7, "#1f2124ff"]], "190": [[0, 7, "#1f2124ff"], [7, 1, "#323337ff"], [8, 4, "#2a2b2eff"], [12, 1, "#323337ff"], [13, 7, "#1f2124ff"]], "63": [[0, 1, "#1f2124ff"], [1, 1, "#35373aff"], [2, 1, "#272b2fff"], [3, 14, "#272a2eff"], [17, 1, "#282b2fff"], [18, 1, "#35373aff"], [19, 1, "#1d1f22ff"]]}, "control": "CC_Slider", "id": "slider.disabled", "rows": {"10": [[0, 1, "#303133ff"], [1, 1, "#2b2c2fff"], [2, 53, "#2a2b2eff"], [55, 1, "#35373aff"], [56, 1, "#272b2fff"], [57, 14, "#272a2eff"], [71, 1, "#282b2fff"], [72, 1, "#35373aff"], [73, 1, "#27272aff"], [74, 124, "#2a2b2eff"], [198, 1, "#2b2c2fff"], [199, 1, "#303133ff"]]}, "size": [200, 20], "state": {"flags": 128, "group": "Disabled", "handle_rect": [54, 0, 20, 20], "name": "disabled", "value": 30}}, +{"background": "#202326ff", "cols": {"0": [[0, 200, "#4c4e50ff"]], "10": [[0, 3, "#202326ff"], [3, 1, "#535457ff"], [4, 1, "#4a4d4fff"], [5, 28, "#46494bff"], [33, 1, "#4a4d4fff"], [34, 1, "#535457ff"], [35, 165, "#202326ff"]]}, "control": "CC_ScrollBar", "id": "scroll_bar.normal", "rows": {"150": [[0, 1, "#4c4e50ff"], [1, 20, "#202326ff"]], "18": [[0, 1, "#4c4e50ff"], [1, 6, "#202326ff"], [7, 1, "#5b5d60ff"], [8, 6, "#46494bff"], [14, 1, "#5b5d60ff"], [15, 6, "#202326ff"]]}, "size": [21, 200], "state": {"active_sub_control": "none", "flags": 0, "group": "Active", "name": "normal", "slider_rect": [0, 3, 21, 32]}}, +{"background": "#202326ff", "cols": {"0": [[0, 200, "#4c4e50ff"]], "10": [[0, 3, "#202326ff"], [3, 1, "#535457ff"], [4, 1, "#4a4d4fff"], [5, 28, "#46494bff"], [33, 1, "#4a4d4fff"], [34, 1, "#535457ff"], [35, 165, "#202326ff"]]}, "control": "CC_ScrollBar", "id": "scroll_bar.hover", "rows": {"150": [[0, 1, "#4c4e50ff"], [1, 20, "#202326ff"]], "18": [[0, 1, "#4c4e50ff"], [1, 6, "#202326ff"], [7, 1, "#5b5d60ff"], [8, 6, "#46494bff"], [14, 1, "#5b5d60ff"], [15, 6, "#202326ff"]]}, "size": [21, 200], "state": {"active_sub_control": "none", "flags": 8192, "group": "Active", "name": "hover", "slider_rect": [0, 3, 21, 32]}}, +{"background": "#202326ff", "cols": {"0": [[0, 200, "#4c4e50ff"]], "10": [[0, 3, "#202326ff"], [3, 1, "#2a5169ff"], [4, 1, "#2f6a8aff"], [5, 28, "#2f6888ff"], [33, 1, "#2f6a8aff"], [34, 1, "#2a5169ff"], [35, 165, "#202326ff"]]}, "control": "CC_ScrollBar", "id": "scroll_bar.slider_hover", "rows": {"150": [[0, 1, "#4c4e50ff"], [1, 20, "#202326ff"]], "18": [[0, 1, "#4c4e50ff"], [1, 6, "#202326ff"], [7, 1, "#2c5a75ff"], [8, 6, "#2f6888ff"], [14, 1, "#2c5a75ff"], [15, 6, "#202326ff"]]}, "size": [21, 200], "state": {"active_sub_control": "SC_ScrollBarSlider", "flags": 8192, "group": "Active", "name": "slider_hover", "slider_rect": [0, 3, 21, 32]}}, +{"background": "#202326ff", "cols": {"0": [[0, 200, "#4c4e50ff"]], "10": [[0, 3, "#202326ff"], [3, 1, "#2a5169ff"], [4, 1, "#2f6a8aff"], [5, 28, "#2f6888ff"], [33, 1, "#2f6a8aff"], [34, 1, "#2a5169ff"], [35, 165, "#202326ff"]]}, "control": "CC_ScrollBar", "id": "scroll_bar.slider_pressed", "rows": {"150": [[0, 1, "#4c4e50ff"], [1, 20, "#202326ff"]], "18": [[0, 1, "#4c4e50ff"], [1, 6, "#202326ff"], [7, 1, "#2c5a75ff"], [8, 6, "#2f6888ff"], [14, 1, "#2c5a75ff"], [15, 6, "#202326ff"]]}, "size": [21, 200], "state": {"active_sub_control": "SC_ScrollBarSlider", "flags": 8196, "group": "Active", "name": "slider_pressed", "slider_rect": [0, 3, 21, 32]}}, +{"background": "#202326ff", "cols": {"150": [[0, 6, "#202326ff"], [6, 1, "#585a5cff"], [7, 4, "#3e4144ff"], [11, 1, "#585a5cff"], [12, 6, "#202326ff"]], "50": [[0, 6, "#202326ff"], [6, 1, "#457c99ff"], [7, 4, "#3584aeff"], [11, 1, "#457c99ff"], [12, 6, "#202326ff"]]}, "control": "CE_ProgressBar", "id": "progress_bar.half", "rows": {"9": [[0, 1, "#45728bff"], [1, 1, "#3585b0ff"], [2, 96, "#3584aeff"], [98, 1, "#3585b0ff"], [99, 1, "#3b6982ff"], [100, 98, "#3e4144ff"], [198, 1, "#424548ff"], [199, 1, "#505355ff"]]}, "size": [200, 18], "state": {"flags": 128, "group": "Active", "name": "half"}}, +{"background": "#202326ff", "cols": {"0": [[0, 100, "#4c4e51ff"]], "1": [[0, 1, "#4c4e51ff"], [1, 98, "#1c1f22ff"], [99, 1, "#4c4e51ff"]], "100": [[0, 1, "#4c4e51ff"], [1, 98, "#1c1f22ff"], [99, 1, "#4c4e51ff"]]}, "control": "PE_PanelMenu+PE_FrameMenu", "id": "menu.panel", "rows": {"0": [[0, 200, "#4c4e51ff"]], "1": [[0, 1, "#4c4e51ff"], [1, 198, "#1c1f22ff"], [199, 1, "#4c4e51ff"]], "50": [[0, 1, "#4c4e51ff"], [1, 198, "#1c1f22ff"], [199, 1, "#4c4e51ff"]]}, "size": [200, 100], "state": {"flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#202326ff", "cols": {"100": [[0, 29, "#202326ff"]], "4": [[0, 29, "#202326ff"]], "5": [[0, 29, "#202326ff"]], "6": [[0, 29, "#202326ff"]]}, "control": "CE_MenuItem", "id": "menu_item.normal", "rows": {"0": [[0, 200, "#202326ff"]], "1": [[0, 200, "#202326ff"]], "14": [[0, 200, "#202326ff"]], "2": [[0, 200, "#202326ff"]], "3": [[0, 200, "#202326ff"]], "4": [[0, 200, "#202326ff"]], "5": [[0, 200, "#202326ff"]]}, "size": [200, 29], "state": {"flags": 0, "group": "Active", "name": "normal", "type": 0}}, +{"background": "#202326ff", "cols": {"100": [[0, 1, "#5abaecff"], [1, 26, "#284c61ff"], [27, 1, "#294d63ff"], [28, 1, "#59b9eaff"]], "4": [[0, 1, "#55addcff"], [1, 1, "#2c576eff"], [2, 25, "#284c61ff"], [27, 1, "#2e5870ff"], [28, 1, "#54acd9ff"]], "5": [[0, 1, "#5abaecff"], [1, 26, "#284c61ff"], [27, 1, "#294d63ff"], [28, 1, "#59b9eaff"]], "6": [[0, 1, "#5abaecff"], [1, 26, "#284c61ff"], [27, 1, "#294d63ff"], [28, 1, "#59b9eaff"]]}, "control": "CE_MenuItem", "id": "menu_item.selected", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#293a44ff"], [3, 1, "#4685a7ff"], [4, 1, "#55addcff"], [5, 189, "#5abaecff"], [194, 1, "#5ab8e9ff"], [195, 1, "#54addbff"], [196, 1, "#4584a6ff"], [197, 1, "#283942ff"], [198, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#2a3e48ff"], [2, 1, "#53a9d6ff"], [3, 1, "#356784ff"], [4, 1, "#2c576eff"], [5, 190, "#284c61ff"], [195, 1, "#2d576eff"], [196, 1, "#356884ff"], [197, 1, "#54aad7ff"], [198, 1, "#283c47ff"], [199, 1, "#202326ff"]], "14": [[0, 1, "#5abaecff"], [1, 197, "#284c61ff"], [198, 1, "#294d63ff"], [199, 1, "#59b9eaff"]], "2": [[0, 1, "#293a44ff"], [1, 1, "#53a9d6ff"], [2, 196, "#284c61ff"], [198, 1, "#54aad8ff"], [199, 1, "#283841ff"]], "3": [[0, 1, "#4584a7ff"], [1, 1, "#346783ff"], [2, 196, "#284c61ff"], [198, 1, "#356885ff"], [199, 1, "#4583a5ff"]], "4": [[0, 1, "#55addcff"], [1, 1, "#2c576eff"], [2, 196, "#284c61ff"], [198, 1, "#2e5870ff"], [199, 1, "#54acd9ff"]], "5": [[0, 1, "#5abaecff"], [1, 197, "#284c61ff"], [198, 1, "#294d63ff"], [199, 1, "#59b9eaff"]]}, "size": [200, 29], "state": {"flags": 32768, "group": "Active", "name": "selected", "type": 0}}, +{"background": "#202326ff", "cols": {"100": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 2, "#202326ff"]], "4": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 2, "#202326ff"]], "5": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 2, "#202326ff"]], "6": [[0, 2, "#202326ff"], [2, 1, "#4c4e51ff"], [3, 2, "#202326ff"]]}, "control": "CE_MenuItem", "id": "menu_item.separator", "rows": {"0": [[0, 200, "#202326ff"]], "1": [[0, 200, "#202326ff"]], "2": [[0, 4, "#202326ff"], [4, 193, "#4c4e51ff"], [197, 3, "#202326ff"]], "3": [[0, 200, "#202326ff"]], "4": [[0, 200, "#202326ff"]], "5": [[0, 200, "#00303900"]]}, "size": [200, 5], "state": {"flags": 0, "group": "Active", "name": "separator", "type": 2}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#2f4b5dff"], [3, 1, "#3d6275ff"], [4, 1, "#46545dff"], [5, 25, "#4c4e51ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#386f8dff"], [2, 1, "#3eaee8ff"], [3, 1, "#2f3134ff"], [4, 1, "#24282aff"], [5, 25, "#202326ff"]], "40": [[0, 3, "#3daee9ff"], [3, 27, "#202326ff"]], "79": [[0, 2, "#202326ff"], [2, 1, "#2f4b5dff"], [3, 1, "#3e6276ff"], [4, 1, "#46545dff"], [5, 25, "#4c4e51ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.selected", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#2f4b5dff"], [3, 1, "#3d8db8ff"], [4, 1, "#3ea4daff"], [5, 70, "#3daee9ff"], [75, 1, "#3ea4daff"], [76, 1, "#3d8db8ff"], [77, 1, "#2f4b5dff"], [78, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#386f8dff"], [2, 1, "#3dade8ff"], [3, 74, "#3daee9ff"], [77, 1, "#3eaee8ff"], [78, 1, "#386f8dff"], [79, 1, "#202326ff"]], "15": [[0, 1, "#4c4e51ff"], [1, 78, "#202326ff"], [79, 1, "#4c4e51ff"]], "2": [[0, 1, "#2f4b5dff"], [1, 1, "#3eaee8ff"], [2, 76, "#3daee9ff"], [78, 1, "#3eaee8ff"], [79, 1, "#2f4b5dff"]], "27": [[0, 1, "#4c4e51ff"], [1, 78, "#202326ff"], [79, 1, "#4c4e51ff"]], "28": [[0, 1, "#4c4e51ff"], [1, 78, "#202326ff"], [79, 1, "#4c4e51ff"]], "29": [[0, 1, "#4c4e51ff"], [1, 78, "#202326ff"], [79, 1, "#4c4e51ff"]], "3": [[0, 1, "#3d6275ff"], [1, 1, "#2f3134ff"], [2, 76, "#202326ff"], [78, 1, "#2f3235ff"], [79, 1, "#3e6276ff"]]}, "size": [80, 30], "state": {"document_mode": false, "flags": 32768, "group": "Active", "name": "selected"}}, +{"background": "#202326ff", "cols": {"0": [[0, 30, "#1b1d20ff"]], "1": [[0, 30, "#1b1d20ff"]], "40": [[0, 30, "#1b1d20ff"]], "79": [[0, 30, "#1b1d20ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.unselected", "rows": {"0": [[0, 80, "#1b1d20ff"]], "1": [[0, 80, "#1b1d20ff"]], "15": [[0, 80, "#1b1d20ff"]], "2": [[0, 80, "#1b1d20ff"]], "27": [[0, 80, "#1b1d20ff"]], "28": [[0, 80, "#1b1d20ff"]], "29": [[0, 80, "#1b1d20ff"]], "3": [[0, 80, "#1b1d20ff"]]}, "size": [80, 30], "state": {"document_mode": false, "flags": 0, "group": "Active", "name": "unselected"}}, +{"background": "#202326ff", "cols": {"0": [[0, 30, "#263f4dff"]], "1": [[0, 30, "#263f4dff"]], "40": [[0, 30, "#263f4dff"]], "79": [[0, 30, "#263f4dff"]]}, "control": "CE_TabBarTabShape", "id": "tab.hover", "rows": {"0": [[0, 80, "#263f4dff"]], "1": [[0, 80, "#263f4dff"]], "15": [[0, 80, "#263f4dff"]], "2": [[0, 80, "#263f4dff"]], "27": [[0, 80, "#263f4dff"]], "28": [[0, 80, "#263f4dff"]], "29": [[0, 80, "#263f4dff"]], "3": [[0, 80, "#263f4dff"]]}, "size": [80, 30], "state": {"document_mode": false, "flags": 8192, "group": "Active", "name": "hover"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#2f4b5dff"], [3, 1, "#3d6275ff"], [4, 1, "#46545dff"], [5, 25, "#4c4e51ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#386f8dff"], [2, 1, "#3eaee8ff"], [3, 1, "#2f3134ff"], [4, 1, "#24282aff"], [5, 25, "#202326ff"]], "40": [[0, 3, "#3daee9ff"], [3, 27, "#202326ff"]], "79": [[0, 2, "#202326ff"], [2, 1, "#2f4b5dff"], [3, 1, "#3e6276ff"], [4, 1, "#46545dff"], [5, 25, "#4c4e51ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.selected.document_mode", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#2f4b5dff"], [3, 1, "#3d8db8ff"], [4, 1, "#3ea4daff"], [5, 70, "#3daee9ff"], [75, 1, "#3ea4daff"], [76, 1, "#3d8db8ff"], [77, 1, "#2f4b5dff"], [78, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#386f8dff"], [2, 1, "#3dade8ff"], [3, 74, "#3daee9ff"], [77, 1, "#3eaee8ff"], [78, 1, "#386f8dff"], [79, 1, "#202326ff"]], "15": [[0, 1, "#4c4e51ff"], [1, 78, "#202326ff"], [79, 1, "#4c4e51ff"]], "2": [[0, 1, "#2f4b5dff"], [1, 1, "#3eaee8ff"], [2, 76, "#3daee9ff"], [78, 1, "#3eaee8ff"], [79, 1, "#2f4b5dff"]], "27": [[0, 1, "#4c4e51ff"], [1, 78, "#202326ff"], [79, 1, "#4c4e51ff"]], "28": [[0, 1, "#4c4e51ff"], [1, 78, "#202326ff"], [79, 1, "#4c4e51ff"]], "29": [[0, 1, "#4c4e51ff"], [1, 78, "#202326ff"], [79, 1, "#4c4e51ff"]], "3": [[0, 1, "#3d6275ff"], [1, 1, "#2f3134ff"], [2, 76, "#202326ff"], [78, 1, "#2f3235ff"], [79, 1, "#3e6276ff"]]}, "size": [80, 30], "state": {"document_mode": true, "flags": 32768, "group": "Active", "name": "selected.document_mode"}}, +{"background": "#202326ff", "cols": {"0": [[0, 30, "#1b1d20ff"]], "1": [[0, 30, "#1b1d20ff"]], "40": [[0, 30, "#1b1d20ff"]], "79": [[0, 30, "#1b1d20ff"]]}, "control": "CE_TabBarTabShape", "id": "tab.unselected.document_mode", "rows": {"0": [[0, 80, "#1b1d20ff"]], "1": [[0, 80, "#1b1d20ff"]], "15": [[0, 80, "#1b1d20ff"]], "2": [[0, 80, "#1b1d20ff"]], "27": [[0, 80, "#1b1d20ff"]], "28": [[0, 80, "#1b1d20ff"]], "29": [[0, 80, "#1b1d20ff"]], "3": [[0, 80, "#1b1d20ff"]]}, "size": [80, 30], "state": {"document_mode": true, "flags": 0, "group": "Active", "name": "unselected.document_mode"}}, +{"background": "#141618ff", "cols": {"0": [[0, 29, "#292c30ff"], [29, 1, "#535558ff"]], "50": [[0, 29, "#292c30ff"], [29, 1, "#535558ff"]], "98": [[0, 29, "#292c30ff"], [29, 1, "#535558ff"]], "99": [[0, 30, "#535558ff"]]}, "control": "CE_HeaderSection", "id": "header.normal", "rows": {"0": [[0, 99, "#292c30ff"], [99, 1, "#535558ff"]], "15": [[0, 99, "#292c30ff"], [99, 1, "#535558ff"]], "28": [[0, 99, "#292c30ff"], [99, 1, "#535558ff"]], "29": [[0, 100, "#535558ff"]]}, "size": [100, 30], "state": {"flags": 2, "group": "Active", "name": "normal"}}, +{"background": "#141618ff", "cols": {"0": [[0, 29, "#2d4655ff"], [29, 1, "#566a76ff"]], "50": [[0, 29, "#2d4655ff"], [29, 1, "#566a76ff"]], "98": [[0, 29, "#2d4655ff"], [29, 1, "#566a76ff"]], "99": [[0, 30, "#566a76ff"]]}, "control": "CE_HeaderSection", "id": "header.hover", "rows": {"0": [[0, 99, "#2d4655ff"], [99, 1, "#566a76ff"]], "15": [[0, 99, "#2d4655ff"], [99, 1, "#566a76ff"]], "28": [[0, 99, "#2d4655ff"], [99, 1, "#566a76ff"]], "29": [[0, 100, "#566a76ff"]]}, "size": [100, 30], "state": {"flags": 8194, "group": "Active", "name": "hover"}}, +{"background": "#141618ff", "cols": {"0": [[0, 29, "#2d4655ff"], [29, 1, "#566a76ff"]], "50": [[0, 29, "#2d4655ff"], [29, 1, "#566a76ff"]], "98": [[0, 29, "#2d4655ff"], [29, 1, "#566a76ff"]], "99": [[0, 30, "#566a76ff"]]}, "control": "CE_HeaderSection", "id": "header.sunken", "rows": {"0": [[0, 99, "#2d4655ff"], [99, 1, "#566a76ff"]], "15": [[0, 99, "#2d4655ff"], [99, 1, "#566a76ff"]], "28": [[0, 99, "#2d4655ff"], [99, 1, "#566a76ff"]], "29": [[0, 100, "#566a76ff"]]}, "size": [100, 30], "state": {"flags": 4, "group": "Active", "name": "sunken"}}, +{"background": "#141618ff", "cols": {"0": [[0, 24, "#141618ff"]], "1": [[0, 24, "#141618ff"]], "100": [[0, 24, "#141618ff"]], "2": [[0, 24, "#141618ff"]], "3": [[0, 24, "#141618ff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.normal", "rows": {"0": [[0, 200, "#141618ff"]], "1": [[0, 200, "#141618ff"]], "12": [[0, 200, "#141618ff"]], "2": [[0, 200, "#141618ff"]], "22": [[0, 200, "#141618ff"]], "23": [[0, 200, "#141618ff"]]}, "size": [200, 24], "state": {"flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#141618ff", "cols": {"0": [[0, 24, "#141618ff"]], "1": [[0, 24, "#141618ff"]], "100": [[0, 1, "#141618ff"], [1, 1, "#3694c5ff"], [2, 19, "#204357ff"], [21, 1, "#214458ff"], [22, 1, "#3592c3ff"], [23, 1, "#141618ff"]], "2": [[0, 3, "#141618ff"], [3, 1, "#192831ff"], [4, 1, "#2c698aff"], [5, 1, "#3389b5ff"], [6, 11, "#3694c5ff"], [17, 1, "#3693c3ff"], [18, 1, "#3388b6ff"], [19, 1, "#2a6989ff"], [20, 1, "#192730ff"], [21, 3, "#141618ff"]], "3": [[0, 2, "#141618ff"], [2, 1, "#1a2d37ff"], [3, 1, "#348ab7ff"], [4, 1, "#265975ff"], [5, 1, "#234b63ff"], [6, 12, "#204357ff"], [18, 1, "#224c62ff"], [19, 1, "#265975ff"], [20, 1, "#348bb7ff"], [21, 1, "#1a2b35ff"], [22, 2, "#141618ff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.hover", "rows": {"0": [[0, 200, "#141618ff"]], "1": [[0, 4, "#141618ff"], [4, 1, "#192831ff"], [5, 1, "#2b698bff"], [6, 1, "#3389b5ff"], [7, 185, "#3694c5ff"], [192, 1, "#3794c3ff"], [193, 1, "#3388b6ff"], [194, 1, "#2b6889ff"], [195, 1, "#192730ff"], [196, 4, "#141618ff"]], "12": [[0, 2, "#141618ff"], [2, 1, "#3694c5ff"], [3, 193, "#204357ff"], [196, 1, "#214458ff"], [197, 1, "#3592c3ff"], [198, 2, "#141618ff"]], "2": [[0, 3, "#141618ff"], [3, 1, "#1a2d37ff"], [4, 1, "#348ab7ff"], [5, 1, "#265975ff"], [6, 1, "#234b63ff"], [7, 186, "#204357ff"], [193, 1, "#224c62ff"], [194, 1, "#265975ff"], [195, 1, "#348bb7ff"], [196, 1, "#1a2b35ff"], [197, 3, "#141618ff"]], "22": [[0, 4, "#141618ff"], [4, 1, "#19272fff"], [5, 1, "#2a6889ff"], [6, 1, "#3387b4ff"], [7, 185, "#3592c3ff"], [192, 1, "#3692c2ff"], [193, 1, "#3287b3ff"], [194, 1, "#296787ff"], [195, 1, "#18262eff"], [196, 4, "#141618ff"]], "23": [[0, 200, "#141618ff"]]}, "size": [200, 24], "state": {"flags": 8192, "group": "Active", "name": "hover"}}, +{"background": "#141618ff", "cols": {"0": [[0, 24, "#141618ff"]], "1": [[0, 24, "#141618ff"]], "100": [[0, 1, "#141618ff"], [1, 21, "#3daee9ff"], [22, 1, "#3cade8ff"], [23, 1, "#141618ff"]], "2": [[0, 3, "#141618ff"], [3, 1, "#1a2e38ff"], [4, 1, "#307faaff"], [5, 1, "#3aa6ddff"], [6, 11, "#3daee9ff"], [17, 1, "#3dade7ff"], [18, 1, "#3ba5dcff"], [19, 1, "#307fa9ff"], [20, 1, "#1a2c37ff"], [21, 3, "#141618ff"]], "3": [[0, 2, "#141618ff"], [2, 1, "#1d3645ff"], [3, 1, "#3ba7deff"], [4, 16, "#3daee9ff"], [20, 1, "#3ba7dfff"], [21, 1, "#1d3543ff"], [22, 2, "#141618ff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.selected", "rows": {"0": [[0, 200, "#141618ff"]], "1": [[0, 4, "#141618ff"], [4, 1, "#1a2e38ff"], [5, 1, "#317faaff"], [6, 1, "#3aa6ddff"], [7, 185, "#3daee9ff"], [192, 1, "#3dade7ff"], [193, 1, "#3ba5dcff"], [194, 1, "#307fa9ff"], [195, 1, "#1a2c37ff"], [196, 4, "#141618ff"]], "12": [[0, 2, "#141618ff"], [2, 195, "#3daee9ff"], [197, 1, "#3cade8ff"], [198, 2, "#141618ff"]], "2": [[0, 3, "#141618ff"], [3, 1, "#1d3645ff"], [4, 1, "#3ba6dfff"], [5, 190, "#3daee9ff"], [195, 1, "#3ba8dfff"], [196, 1, "#1d3543ff"], [197, 3, "#141618ff"]], "22": [[0, 4, "#141618ff"], [4, 1, "#192b35ff"], [5, 1, "#307ea8ff"], [6, 1, "#3aa4dbff"], [7, 185, "#3cade8ff"], [192, 1, "#3cace7ff"], [193, 1, "#3aa3daff"], [194, 1, "#307ea8ff"], [195, 1, "#192a33ff"], [196, 4, "#141618ff"]], "23": [[0, 200, "#141618ff"]]}, "size": [200, 24], "state": {"flags": 32768, "group": "Active", "name": "selected"}}, +{"background": "#141618ff", "cols": {"0": [[0, 24, "#141618ff"]], "1": [[0, 24, "#141618ff"]], "100": [[0, 1, "#141618ff"], [1, 21, "#44bfffff"], [22, 1, "#44befeff"], [23, 1, "#141618ff"]], "2": [[0, 3, "#141618ff"], [3, 1, "#1b303bff"], [4, 1, "#358bb9ff"], [5, 1, "#42b5f2ff"], [6, 11, "#44bfffff"], [17, 1, "#44befdff"], [18, 1, "#41b5f1ff"], [19, 1, "#358bb8ff"], [20, 1, "#1b2f3aff"], [21, 3, "#141618ff"]], "3": [[0, 2, "#141618ff"], [2, 1, "#1f394aff"], [3, 1, "#43b7f3ff"], [4, 16, "#44bfffff"], [20, 1, "#42b7f4ff"], [21, 1, "#1e3848ff"], [22, 2, "#141618ff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.selected_hover", "rows": {"0": [[0, 200, "#141618ff"]], "1": [[0, 4, "#141618ff"], [4, 1, "#1b303bff"], [5, 1, "#368bb9ff"], [6, 1, "#42b5f2ff"], [7, 185, "#44bfffff"], [192, 1, "#44befdff"], [193, 1, "#41b5f1ff"], [194, 1, "#358bb8ff"], [195, 1, "#1b2f3aff"], [196, 4, "#141618ff"]], "12": [[0, 2, "#141618ff"], [2, 195, "#44bfffff"], [197, 1, "#44befeff"], [198, 2, "#141618ff"]], "2": [[0, 3, "#141618ff"], [3, 1, "#1f394aff"], [4, 1, "#42b7f4ff"], [5, 190, "#44bfffff"], [195, 1, "#42b7f4ff"], [196, 1, "#1e3847ff"], [197, 3, "#141618ff"]], "22": [[0, 4, "#141618ff"], [4, 1, "#1a2d38ff"], [5, 1, "#358ab7ff"], [6, 1, "#41b4f0ff"], [7, 185, "#44befeff"], [192, 1, "#43bdfcff"], [193, 1, "#40b3efff"], [194, 1, "#348ab6ff"], [195, 1, "#1a2c36ff"], [196, 4, "#141618ff"]], "23": [[0, 200, "#141618ff"]]}, "size": [200, 24], "state": {"flags": 40960, "group": "Active", "name": "selected_hover"}}, +{"background": "#141618ff", "cols": {"0": [[0, 24, "#141618ff"]], "1": [[0, 24, "#141618ff"]], "100": [[0, 1, "#141618ff"], [1, 22, "#1b4155ff"], [23, 1, "#141618ff"]], "2": [[0, 3, "#141618ff"], [3, 1, "#151d21ff"], [4, 1, "#193443ff"], [5, 1, "#1a3f52ff"], [6, 12, "#1b4155ff"], [18, 1, "#1a3f52ff"], [19, 1, "#193342ff"], [20, 1, "#151c21ff"], [21, 3, "#141618ff"]], "3": [[0, 2, "#141618ff"], [2, 1, "#152025ff"], [3, 1, "#1b3f52ff"], [4, 16, "#1b4155ff"], [20, 1, "#1b3f52ff"], [21, 1, "#151f25ff"], [22, 2, "#141618ff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.selected_inactive_window", "rows": {"0": [[0, 200, "#141618ff"]], "1": [[0, 4, "#141618ff"], [4, 1, "#151d21ff"], [5, 1, "#193443ff"], [6, 1, "#1a3f52ff"], [7, 186, "#1b4155ff"], [193, 1, "#1a3f52ff"], [194, 1, "#193342ff"], [195, 1, "#151c21ff"], [196, 4, "#141618ff"]], "12": [[0, 2, "#141618ff"], [2, 196, "#1b4155ff"], [198, 2, "#141618ff"]], "2": [[0, 3, "#141618ff"], [3, 1, "#152025ff"], [4, 1, "#1b3f52ff"], [5, 190, "#1b4155ff"], [195, 1, "#1a3f52ff"], [196, 1, "#151f25ff"], [197, 3, "#141618ff"]], "22": [[0, 4, "#141618ff"], [4, 1, "#151c21ff"], [5, 1, "#183341ff"], [6, 1, "#1b3e51ff"], [7, 185, "#1b4155ff"], [192, 1, "#1b4055ff"], [193, 1, "#1b3e50ff"], [194, 1, "#193442ff"], [195, 1, "#141b20ff"], [196, 4, "#141618ff"]], "23": [[0, 200, "#141618ff"]]}, "size": [200, 24], "state": {"flags": 32768, "group": "Inactive", "name": "selected_inactive_window"}}, +{"background": "#141618ff", "cols": {"0": [[0, 24, "#141618ff"]], "1": [[0, 24, "#141618ff"]], "100": [[0, 24, "#141618ff"]], "2": [[0, 24, "#141618ff"]], "3": [[0, 24, "#141618ff"]]}, "control": "PE_PanelItemViewItem", "id": "item_view_row.focus", "rows": {"0": [[0, 200, "#141618ff"]], "1": [[0, 200, "#141618ff"]], "12": [[0, 200, "#141618ff"]], "2": [[0, 200, "#141618ff"]], "22": [[0, 200, "#141618ff"]], "23": [[0, 200, "#141618ff"]]}, "size": [200, 24], "state": {"flags": 8388864, "group": "Active", "name": "focus"}}, +{"background": "#292c30ff", "cols": {"0": [[0, 30, "#535659ff"]], "1": [[0, 1, "#535659ff"], [1, 28, "#292c30ff"], [29, 1, "#535659ff"]], "50": [[0, 1, "#535659ff"], [1, 28, "#292c30ff"], [29, 1, "#535659ff"]]}, "control": "PE_PanelTipLabel", "id": "tooltip.panel", "rows": {"0": [[0, 100, "#535659ff"]], "1": [[0, 1, "#535659ff"], [1, 98, "#292c30ff"], [99, 1, "#535659ff"]], "15": [[0, 1, "#535659ff"], [1, 98, "#292c30ff"], [99, 1, "#535659ff"]]}, "size": [100, 30], "state": {"flags": 0, "group": "Active", "name": "normal"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3d40ff"], [4, 1, "#484a4cff"], [5, 39, "#4c4e51ff"], [44, 1, "#4b4d51ff"], [45, 1, "#48494cff"], [46, 1, "#3a3d40ff"], [47, 1, "#26292cff"], [48, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#272a2cff"], [2, 1, "#44474aff"], [3, 1, "#232427ff"], [4, 1, "#191b1eff"], [5, 40, "#141618ff"], [45, 1, "#191c1eff"], [46, 1, "#222426ff"], [47, 1, "#45474aff"], [48, 1, "#25282cff"], [49, 1, "#202326ff"]], "50": [[0, 1, "#4c4e51ff"], [1, 47, "#141618ff"], [48, 1, "#151719ff"], [49, 1, "#4b4d50ff"]]}, "control": "PE_Frame", "id": "frame.styled_panel", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3d40ff"], [4, 1, "#484a4cff"], [5, 89, "#4c4e51ff"], [94, 1, "#4b4d50ff"], [95, 1, "#48494cff"], [96, 1, "#3a3d40ff"], [97, 1, "#26292cff"], [98, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#272a2cff"], [2, 1, "#44474aff"], [3, 1, "#222426ff"], [4, 1, "#191b1eff"], [5, 90, "#141618ff"], [95, 1, "#191c1eff"], [96, 1, "#232426ff"], [97, 1, "#44484aff"], [98, 1, "#26282cff"], [99, 1, "#202326ff"]], "25": [[0, 1, "#4c4e51ff"], [1, 97, "#141618ff"], [98, 1, "#151719ff"], [99, 1, "#4b4d50ff"]]}, "size": [100, 50], "state": {"flags": 4, "group": "Active", "name": "sunken"}}, +{"background": "#202326ff", "cols": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3e41ff"], [4, 1, "#484a4cff"], [5, 49, "#4c4e51ff"], [54, 1, "#4b4e51ff"], [55, 1, "#48494dff"], [56, 1, "#3b3e41ff"], [57, 1, "#26292cff"], [58, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#282b2dff"], [2, 1, "#44484bff"], [3, 1, "#292b2eff"], [4, 1, "#202327ff"], [5, 50, "#1c1f22ff"], [55, 1, "#202427ff"], [56, 1, "#282b2eff"], [57, 1, "#46484bff"], [58, 1, "#26292dff"], [59, 1, "#202326ff"]], "2": [[0, 1, "#272a2cff"], [1, 1, "#45484bff"], [2, 56, "#1c1f22ff"], [58, 1, "#47484bff"], [59, 1, "#26292cff"]], "50": [[0, 1, "#4c4e51ff"], [1, 57, "#1c1f22ff"], [58, 1, "#1d2023ff"], [59, 1, "#4b4d50ff"]]}, "control": "PE_FrameGroupBox", "id": "group_box.frame", "rows": {"0": [[0, 2, "#202326ff"], [2, 1, "#272a2cff"], [3, 1, "#3b3e41ff"], [4, 1, "#484a4cff"], [5, 89, "#4c4e51ff"], [94, 1, "#4b4e51ff"], [95, 1, "#48494dff"], [96, 1, "#3b3d41ff"], [97, 1, "#26292cff"], [98, 2, "#202326ff"]], "1": [[0, 1, "#202326ff"], [1, 1, "#282b2dff"], [2, 1, "#45484bff"], [3, 1, "#282b2eff"], [4, 1, "#202327ff"], [5, 90, "#1c1f22ff"], [95, 1, "#202427ff"], [96, 1, "#282a2eff"], [97, 1, "#45494bff"], [98, 1, "#26292dff"], [99, 1, "#202326ff"]], "2": [[0, 1, "#272a2cff"], [1, 1, "#44484bff"], [2, 96, "#1c1f22ff"], [98, 1, "#47484bff"], [99, 1, "#26292cff"]], "30": [[0, 1, "#4c4e51ff"], [1, 97, "#1c1f22ff"], [98, 1, "#1d2023ff"], [99, 1, "#4b4d50ff"]]}, "size": [100, 60], "state": {"flags": 0, "group": "Active", "name": "normal"}} +] +} +} +} diff --git a/tools/kde_golden/vectors/resolve.json b/tools/kde_golden/vectors/resolve.json new file mode 100644 index 000000000..1145233e3 --- /dev/null +++ b/tools/kde_golden/vectors/resolve.json @@ -0,0 +1,19 @@ +{ +"meta": {"generator":"tools/kde_golden/driver (kde_golden)","image":"invent-registry.kde.org/neon/docker-images/plasma@sha256:f58f9b97f218ee976cf928f6fe00abb929a644aded8c9dfb02f77a9b2a6e1732","kcolorscheme_build":"6.31.0","kguiaddons_build":"6.31.0","kind":"resolve","packages":{"breeze":"4:6.8.0-0zneon+24.04+noble+release+build80","fonts-hack":"3.003-3","fonts-noto-core":"20201225-2","kde-style-breeze":"4:6.8.0-0zneon+24.04+noble+release+build80","kde-style-oxygen":"4:6.8.0-0zneon+24.04+noble+release+build59","kf6-kcolorscheme":"6.31.0-0zneon+24.04+noble+release+build47","kf6-kconfig":"6.31.0-0zneon+24.04+noble+release+build36","kf6-kguiaddons":"6.31.0-0zneon+24.04+noble+release+build59","libkf6colorscheme-dev":"6.31.0-0zneon+24.04+noble+release+build47","plasma-workspace":"4:6.8.0-0zneon+24.04+noble+release+build99","qt6-base":"6.11.1-0zneon+24.04+noble+release+build162"},"qt_build":"6.11.1","qt_runtime":"6.11.1","readme":"tools/kde_golden/README.md documents every field"}, +"missing_installed": [], +"schemes": [ +{"contrastF":0.4,"frameContrast":0.2,"input":{"ColorEffects:Disabled":{"Color":"56,56,56","ColorAmount":"0","ColorEffect":"0","ContrastAmount":"0.65","ContrastEffect":"1","IntensityAmount":"0.1","IntensityEffect":"2"},"ColorEffects:Inactive":{"ChangeSelectionColor":"true","Color":"112,111,110","ColorAmount":"0.025","ColorEffect":"2","ContrastAmount":"0.1","ContrastEffect":"2","Enable":"false","IntensityAmount":"0","IntensityEffect":"0"},"Colors:Button":{"BackgroundAlternate":"163,212,250","BackgroundNormal":"252,252,252","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"112,125,138","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"35,38,41","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Complementary":{"BackgroundAlternate":"27,30,32","BackgroundNormal":"42,46,50","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"161,169,177","ForegroundLink":"29,153,243","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Header":{"BackgroundAlternate":"239,240,241","BackgroundNormal":"222,224,226","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"112,125,138","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"35,38,41","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Header][Inactive":{"BackgroundAlternate":"227,229,231","BackgroundNormal":"239,240,241","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"112,125,138","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"35,38,41","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Selection":{"BackgroundAlternate":"163,212,250","BackgroundNormal":"61,174,233","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"255,255,255","ForegroundInactive":"112,125,138","ForegroundLink":"253,188,75","ForegroundNegative":"176,55,69","ForegroundNeutral":"198,92,0","ForegroundNormal":"255,255,255","ForegroundPositive":"23,104,57","ForegroundVisited":"155,89,182"},"Colors:Tooltip":{"BackgroundAlternate":"239,240,241","BackgroundNormal":"247,247,247","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"112,125,138","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"35,38,41","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:View":{"BackgroundAlternate":"247,247,247","BackgroundNormal":"255,255,255","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"112,125,138","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"35,38,41","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Window":{"BackgroundAlternate":"227,229,231","BackgroundNormal":"239,240,241","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"112,125,138","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"35,38,41","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"General":{"ColorScheme":"BreezeLight","Name":"Breeze Light","shadeSortColumn":"true"},"KDE":{"contrast":"4"},"WM":{"activeBackground":"227,229,231","activeBlend":"227,229,231","activeForeground":"35,38,41","inactiveBackground":"239,240,241","inactiveBlend":"239,240,241","inactiveForeground":"112,125,138"}},"name":"BreezeLight","origin":"installed","palette":{"Active":{"Accent":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"AlternateBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Highlight":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"PlaceholderText":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]},"Text":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"WindowText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}},"Disabled":{"Accent":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"AlternateBase":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Base":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"ButtonText":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Highlight":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"HighlightedText":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#a3cae2ff","u16":[42003,51899,58104,65535]},"LinkVisited":{"hex":"#d6bae1ff","u16":[54932,47677,57833,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"PlaceholderText":{"hex":"#c2c8ceff","u16":[49903,51372,52827,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]},"Text":{"hex":"#aaabacff","u16":[43578,43869,44159,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"WindowText":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]}},"Inactive":{"Accent":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"AlternateBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Highlight":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"HighlightedText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"PlaceholderText":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]},"Text":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"WindowText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}},"set_supported":{"Button":true,"Complementary":true,"Header":true,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"Light":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Mid":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Midlight":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"Shadow":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d8edf7ff","u16":[55550,60800,63569,65535]},"Alternate":{"hex":"#8cccf9ff","u16":[36095,52366,64033,65535]},"Link":{"hex":"#dfeaf2ff","u16":[57339,60254,62215,65535]},"Negative":{"hex":"#f7e4e6ff","u16":[63468,58657,59124,65535]},"Neutral":{"hex":"#fae6d4ff","u16":[64273,59033,54466,65535]},"Normal":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"Positive":{"hex":"#d9eee2ff","u16":[55792,61232,58043,65535]},"Visited":{"hex":"#eee6f2ff","u16":[61262,59074,62174,65535]}},"decoration":{"Focus":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Hover":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]}},"foreground":{"Active":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Inactive":{"hex":"#c0c6ccff","u16":[49437,50892,52336,65535]},"Link":{"hex":"#a2c8e0ff","u16":[41595,51411,57571,65535]},"Negative":{"hex":"#eeafb6ff","u16":[61133,45056,46720,65535]},"Neutral":{"hex":"#f9c088ff","u16":[63954,49376,34973,65535]},"Normal":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Positive":{"hex":"#a9d6bcff","u16":[43472,55048,48360,65535]},"Visited":{"hex":"#d4b8dfff","u16":[54420,47217,57302,65535]}},"shade":{"Dark":{"hex":"#8d8d8dff","u16":[36252,36252,36252,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c8c8c8ff","u16":[51382,51382,51382,65535]},"Midlight":{"hex":"#f7f7f7ff","u16":[63513,63513,63513,65535]},"Shadow":{"hex":"#494949ff","u16":[18840,18840,18840,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"Light":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Mid":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Midlight":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"Shadow":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#191b1dff","u16":[6338,6942,7546,65535]},"Light":{"hex":"#40464cff","u16":[16436,18002,19567,65535]},"Mid":{"hex":"#25292cff","u16":[9506,10412,11317,65535]},"Midlight":{"hex":"#363b40ff","u16":[13917,15242,16568,65535]},"Shadow":{"hex":"#121415ff","u16":[4597,5035,5473,65535]}}},"Disabled":{"background":{"Active":{"hex":"#20333dff","u16":[8232,13011,15777,65535]},"Alternate":{"hex":"#1a1d1fff","u16":[6615,7349,7839,65535]},"Link":{"hex":"#1c3242ff","u16":[7317,12882,17081,65535]},"Negative":{"hex":"#42282eff","u16":[16927,10389,11726,65535]},"Neutral":{"hex":"#3e2c1cff","u16":[16044,11402,7322,65535]},"Normal":{"hex":"#282c30ff","u16":[10289,11269,12249,65535]},"Positive":{"hex":"#1e352bff","u16":[7632,13521,11035,65535]},"Visited":{"hex":"#352c3fff","u16":[13699,11243,16190,65535]}},"decoration":{"Focus":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]},"Hover":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]}},"foreground":{"Active":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]},"Inactive":{"hex":"#50555aff","u16":[20493,21816,23139,65535]},"Link":{"hex":"#245070ff","u16":[9175,20444,28797,65535]},"Negative":{"hex":"#63333bff","u16":[25380,13156,15078,65535]},"Neutral":{"hex":"#6c431fff","u16":[27781,17272,7962,65535]},"Normal":{"hex":"#6e7173ff","u16":[28296,28933,29569,65535]},"Positive":{"hex":"#27573fff","u16":[10032,22245,16194,65535]},"Visited":{"hex":"#4e3a5cff","u16":[19978,14956,23567,65535]}},"shade":{"Dark":{"hex":"#181a1cff","u16":[6042,6617,7193,65535]},"Light":{"hex":"#3e444aff","u16":[16045,17573,19101,65535]},"Mid":{"hex":"#23272aff","u16":[9063,9926,10789,65535]},"Midlight":{"hex":"#353a3fff","u16":[13498,14784,16069,65535]},"Shadow":{"hex":"#111314ff","u16":[4385,4802,5220,65535]}}},"Inactive":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#191b1dff","u16":[6338,6942,7546,65535]},"Light":{"hex":"#40464cff","u16":[16436,18002,19567,65535]},"Mid":{"hex":"#25292cff","u16":[9506,10412,11317,65535]},"Midlight":{"hex":"#363b40ff","u16":[13917,15242,16568,65535]},"Shadow":{"hex":"#121415ff","u16":[4597,5035,5473,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#bddcf1ff","u16":[48617,56620,61865,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#bed8edff","u16":[48753,55447,60903,65535]},"Negative":{"hex":"#eecccfff","u16":[61219,52482,53238,65535]},"Neutral":{"hex":"#f7cfb7ff","u16":[63498,53297,47153,65535]},"Normal":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]},"Positive":{"hex":"#85ecb2ff","u16":[34118,60679,45709,65535]},"Visited":{"hex":"#ddcfe5ff","u16":[56819,53117,58755,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#7f8489ff","u16":[32707,33931,35117,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b8bdc1ff","u16":[47203,48452,49677,65535]},"Midlight":{"hex":"#e6e8e9ff","u16":[59237,59610,59984,65535]},"Shadow":{"hex":"#47494cff","u16":[18190,18870,19530,65535]}}},"Disabled":{"background":{"Active":{"hex":"#b6d4e4ff","u16":[46809,54437,58578,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#c1d1dcff","u16":[49596,53745,56654,65535]},"Negative":{"hex":"#e2c8ccff","u16":[58118,51520,52524,65535]},"Neutral":{"hex":"#e5cbb3ff","u16":[58796,52066,46125,65535]},"Normal":{"hex":"#d3d6d8ff","u16":[54197,54906,55612,65535]},"Positive":{"hex":"#b3d7c5ff","u16":[46026,55365,50692,65535]},"Visited":{"hex":"#d6cbddff","u16":[54939,52124,56916,65535]}},"decoration":{"Focus":{"hex":"#93c6e0ff","u16":[37880,50983,57656,65535]},"Hover":{"hex":"#93c6e0ff","u16":[37880,50983,57656,65535]}},"foreground":{"Active":{"hex":"#93c6e0ff","u16":[37880,50983,57656,65535]},"Inactive":{"hex":"#aeb5bbff","u16":[44665,46432,48183,65535]},"Link":{"hex":"#94b6ccff","u16":[37989,46772,52533,65535]},"Negative":{"hex":"#d99fa6ff","u16":[55802,40796,42771,65535]},"Neutral":{"hex":"#e2b080ff","u16":[58104,45186,32907,65535]},"Normal":{"hex":"#95979aff","u16":[38352,38927,39503,65535]},"Positive":{"hex":"#97c5acff","u16":[38695,50589,44220,65535]},"Visited":{"hex":"#c0a7ceff","u16":[49363,42888,52873,65535]}},"shade":{"Dark":{"hex":"#797e82ff","u16":[31176,32344,33476,65535]},"Light":{"hex":"#f3f4f5ff","u16":[62526,62699,62873,65535]},"Mid":{"hex":"#afb5baff","u16":[45016,46458,47864,65535]},"Midlight":{"hex":"#dcdee0ff","u16":[56538,57086,57635,65535]},"Shadow":{"hex":"#46494bff","u16":[17969,18642,19295,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#5ab7dcff","u16":[23235,46907,56572,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#b3ab74ff","u16":[46003,43823,29737,65535]},"Negative":{"hex":"#aa96b0ff","u16":[43694,38429,45353,65535]},"Neutral":{"hex":"#b29b74ff","u16":[45660,39812,29747,65535]},"Normal":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Positive":{"hex":"#32ada4ff","u16":[12736,44427,42135,65535]},"Visited":{"hex":"#9799d3ff","u16":[38815,39221,54248,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Negative":{"hex":"#b03745ff","u16":[45232,14135,17733,65535]},"Neutral":{"hex":"#c65c00ff","u16":[50886,23644,0,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#176839ff","u16":[5911,26728,14649,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#246689ff","u16":[9206,26259,35163,65535]},"Light":{"hex":"#7ac6f9ff","u16":[31305,50791,63873,65535]},"Mid":{"hex":"#3596c9ff","u16":[13498,38503,51559,65535]},"Midlight":{"hex":"#41b9f7ff","u16":[16627,47428,63511,65535]},"Shadow":{"hex":"#174259ff","u16":[5979,17055,22838,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#d8daddff","u16":[55389,56136,56880,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#e4daeaff","u16":[58594,56069,60044,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#b8bfc5ff","u16":[47365,48981,50583,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#cbb1d8ff","u16":[52205,45373,55410,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#b3d9f4ff","u16":[45899,55787,62650,65535]},"Alternate":{"hex":"#b0d8f3ff","u16":[45158,55455,62438,65535]},"Link":{"hex":"#b5d4efff","u16":[46454,54609,61533,65535]},"Negative":{"hex":"#e8c9cfff","u16":[59622,51622,53083,65535]},"Neutral":{"hex":"#f4cbb0ff","u16":[62731,52266,45109,65535]},"Normal":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"Positive":{"hex":"#75e9b8ff","u16":[30182,59846,47181,65535]},"Visited":{"hex":"#d6cbe6ff","u16":[55093,52228,59148,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#4b88a8ff","u16":[19360,34951,43113,65535]},"Light":{"hex":"#f8fbfeff","u16":[63734,64548,65196,65535]},"Mid":{"hex":"#75c2ecff","u16":[30160,49859,60666,65535]},"Midlight":{"hex":"#d1e7f7ff","u16":[53672,59338,63476,65535]},"Shadow":{"hex":"#2b4d5fff","u16":[10934,19739,24349,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#cfcfcfff","u16":[53208,53208,53208,65535]},"Light":{"hex":"#ddddddff","u16":[56880,56880,56880,65535]},"Mid":{"hex":"#ddddddff","u16":[56880,56880,56880,65535]},"Midlight":{"hex":"#ebebebff","u16":[60288,60288,60288,65535]},"Shadow":{"hex":"#b5b5b5ff","u16":[46599,46599,46599,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d2e8f4ff","u16":[53968,59692,62698,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#dae6eeff","u16":[55898,59123,61286,65535]},"Negative":{"hex":"#f4dfe1ff","u16":[62678,57357,57876,65535]},"Neutral":{"hex":"#f7e1cdff","u16":[63504,57761,52725,65535]},"Normal":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Positive":{"hex":"#d0ebdbff","u16":[53508,60379,56375,65535]},"Visited":{"hex":"#eae1eeff","u16":[60238,57818,61243,65535]}},"decoration":{"Focus":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]},"Hover":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]}},"foreground":{"Active":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]},"Inactive":{"hex":"#bdc3c8ff","u16":[48657,50093,51518,65535]},"Link":{"hex":"#9fc5ddff","u16":[40903,50598,56692,65535]},"Negative":{"hex":"#eaacb3ff","u16":[60222,44302,45948,65535]},"Neutral":{"hex":"#f5bd86ff","u16":[62984,48622,34500,65535]},"Normal":{"hex":"#a5a6a7ff","u16":[42332,42590,42849,65535]},"Positive":{"hex":"#a6d3b9ff","u16":[42675,54252,47564,65535]},"Visited":{"hex":"#d0b5dcff","u16":[53574,46448,56425,65535]}},"shade":{"Dark":{"hex":"#8a8a8aff","u16":[35533,35533,35533,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c4c4ff","u16":[50497,50497,50497,65535]},"Midlight":{"hex":"#f3f3f3ff","u16":[62379,62379,62379,65535]},"Shadow":{"hex":"#494949ff","u16":[18863,18863,18863,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#cfcfcfff","u16":[53208,53208,53208,65535]},"Light":{"hex":"#ddddddff","u16":[56880,56880,56880,65535]},"Mid":{"hex":"#ddddddff","u16":[56880,56880,56880,65535]},"Midlight":{"hex":"#ebebebff","u16":[60288,60288,60288,65535]},"Shadow":{"hex":"#b5b5b5ff","u16":[46599,46599,46599,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#ecf6fcff","u16":[60771,63172,64842,65535]},"Alternate":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Link":{"hex":"#e8f0f8ff","u16":[59682,61808,63658,65535]},"Negative":{"hex":"#faecedff","u16":[64334,60663,60940,65535]},"Neutral":{"hex":"#fef1eaff","u16":[65257,61930,60105,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#ddfbe8ff","u16":[56769,64393,59555,65535]},"Visited":{"hex":"#f2ecf6ff","u16":[62319,60713,63097,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#d9d9d9ff","u16":[55727,55727,55727,65535]},"Light":{"hex":"#e6e6e6ff","u16":[59214,59214,59214,65535]},"Mid":{"hex":"#e6e6e6ff","u16":[59214,59214,59214,65535]},"Midlight":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"Shadow":{"hex":"#c1c1c1ff","u16":[49526,49526,49526,65535]}}},"Disabled":{"background":{"Active":{"hex":"#dceff9ff","u16":[56552,61460,64057,65535]},"Alternate":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Link":{"hex":"#e3edf4ff","u16":[58230,60929,62750,65535]},"Negative":{"hex":"#f9e7e9ff","u16":[63907,59452,59885,65535]},"Neutral":{"hex":"#fce9d8ff","u16":[64691,59802,55559,65535]},"Normal":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"Positive":{"hex":"#def0e6ff","u16":[57106,61751,59018,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61864,59835,62710,65535]}},"decoration":{"Focus":{"hex":"#a3daf5ff","u16":[41938,56116,63017,65535]},"Hover":{"hex":"#a3daf5ff","u16":[41938,56116,63017,65535]}},"foreground":{"Active":{"hex":"#a3daf5ff","u16":[41938,56116,63017,65535]},"Inactive":{"hex":"#c2c8ceff","u16":[49903,51372,52827,65535]},"Link":{"hex":"#a3cae2ff","u16":[42003,51899,58104,65535]},"Negative":{"hex":"#f0b1b8ff","u16":[61685,45506,47184,65535]},"Neutral":{"hex":"#fbc289ff","u16":[64548,49824,35228,65535]},"Normal":{"hex":"#aaabacff","u16":[43578,43869,44159,65535]},"Positive":{"hex":"#abd8beff","u16":[43950,55525,48837,65535]},"Visited":{"hex":"#d6bae1ff","u16":[54932,47677,57833,65535]}},"shade":{"Dark":{"hex":"#8f8f8fff","u16":[36684,36684,36684,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#cacacaff","u16":[51909,51909,51909,65535]},"Midlight":{"hex":"#fafafaff","u16":[64191,64191,64191,65535]},"Shadow":{"hex":"#494949ff","u16":[18809,18809,18809,65535]}}},"Inactive":{"background":{"Active":{"hex":"#ecf6fcff","u16":[60771,63172,64842,65535]},"Alternate":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Link":{"hex":"#e8f0f8ff","u16":[59682,61808,63658,65535]},"Negative":{"hex":"#faecedff","u16":[64334,60663,60940,65535]},"Neutral":{"hex":"#fef1eaff","u16":[65257,61930,60105,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#ddfbe8ff","u16":[56769,64393,59555,65535]},"Visited":{"hex":"#f2ecf6ff","u16":[62319,60713,63097,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#d9d9d9ff","u16":[55727,55727,55727,65535]},"Light":{"hex":"#e6e6e6ff","u16":[59214,59214,59214,65535]},"Mid":{"hex":"#e6e6e6ff","u16":[59214,59214,59214,65535]},"Midlight":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"Shadow":{"hex":"#c1c1c1ff","u16":[49526,49526,49526,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#d8daddff","u16":[55389,56136,56880,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#e4daeaff","u16":[58594,56069,60044,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#b8bfc5ff","u16":[47365,48981,50583,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#cbb1d8ff","u16":[52205,45373,55410,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}}}},"sha256":"c46470b75e7df3e1ebff344748b92347f8d5adfa306aef9c91c4d2edae2cd6df","source":"/usr/share/color-schemes/BreezeLight.colors"}, +{"contrastF":0.4,"frameContrast":0.2,"input":{"ColorEffects:Disabled":{"Color":"56,56,56","ColorAmount":"0","ColorEffect":"0","ContrastAmount":"0.65","ContrastEffect":"1","IntensityAmount":"0.1","IntensityEffect":"2"},"ColorEffects:Inactive":{"ChangeSelectionColor":"true","Color":"112,111,110","ColorAmount":"0.025","ColorEffect":"2","ContrastAmount":"0.1","ContrastEffect":"2","Enable":"false","IntensityAmount":"0","IntensityEffect":"0"},"Colors:Button":{"BackgroundAlternate":"30,87,116","BackgroundNormal":"41,44,48","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"161,169,177","ForegroundLink":"29,153,243","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Complementary":{"BackgroundAlternate":"30,87,116","BackgroundNormal":"32,35,38","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"161,169,177","ForegroundLink":"29,153,243","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Header":{"BackgroundAlternate":"32,35,38","BackgroundNormal":"41,44,48","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"161,169,177","ForegroundLink":"29,153,243","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Header][Inactive":{"BackgroundAlternate":"41,44,48","BackgroundNormal":"32,35,38","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"161,169,177","ForegroundLink":"29,153,243","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Selection":{"BackgroundAlternate":"30,87,116","BackgroundNormal":"61,174,233","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"252,252,252","ForegroundInactive":"161,169,177","ForegroundLink":"253,188,75","ForegroundNegative":"176,55,69","ForegroundNeutral":"198,92,0","ForegroundNormal":"252,252,252","ForegroundPositive":"23,104,57","ForegroundVisited":"155,89,182"},"Colors:Tooltip":{"BackgroundAlternate":"32,35,38","BackgroundNormal":"41,44,48","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"161,169,177","ForegroundLink":"29,153,243","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:View":{"BackgroundAlternate":"29,31,34","BackgroundNormal":"20,22,24","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"161,169,177","ForegroundLink":"29,153,243","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"Colors:Window":{"BackgroundAlternate":"41,44,48","BackgroundNormal":"32,35,38","DecorationFocus":"61,174,233","DecorationHover":"61,174,233","ForegroundActive":"61,174,233","ForegroundInactive":"161,169,177","ForegroundLink":"29,153,243","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"155,89,182"},"General":{"ColorScheme":"BreezeDark","Name":"Breeze Dark","shadeSortColumn":"true"},"KDE":{"contrast":"4"},"WM":{"activeBackground":"39,44,49","activeBlend":"252,252,252","activeForeground":"252,252,252","inactiveBackground":"32,36,40","inactiveBlend":"161,169,177","inactiveForeground":"161,169,177"}},"name":"BreezeDark","origin":"installed","palette":{"Active":{"Accent":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"AlternateBase":{"hex":"#1d1f22ff","u16":[7453,7967,8738,65535]},"Base":{"hex":"#141618ff","u16":[5140,5654,6168,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"ButtonText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Dark":{"hex":"#131516ff","u16":[4829,5282,5735,65535]},"Highlight":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"HighlightedText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Light":{"hex":"#393e43ff","u16":[14585,15952,17319,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#1c1f21ff","u16":[7247,7927,8606,65535]},"Midlight":{"hex":"#2e3337ff","u16":[11896,13011,14126,65535]},"PlaceholderText":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Shadow":{"hex":"#0e0f10ff","u16":[3512,3841,4171,65535]},"Text":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ToolTipBase":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"ToolTipText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Window":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"WindowText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}},"Disabled":{"Accent":{"hex":"#1f2124ff","u16":[7839,8574,9309,65535]},"AlternateBase":{"hex":"#1c1e20ff","u16":[7104,7594,8329,65535]},"Base":{"hex":"#131517ff","u16":[4900,5390,5880,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#272a2eff","u16":[10044,10779,11759,65535]},"ButtonText":{"hex":"#6d6f72ff","u16":[28136,28614,29251,65535]},"Dark":{"hex":"#121415ff","u16":[4603,5035,5466,65535]},"Highlight":{"hex":"#1f2124ff","u16":[7839,8574,9309,65535]},"HighlightedText":{"hex":"#686a6cff","u16":[26703,27181,27658,65535]},"Light":{"hex":"#383d42ff","u16":[14337,15681,17025,65535]},"Link":{"hex":"#164160ff","u16":[5672,16622,24657,65535]},"LinkVisited":{"hex":"#402b4cff","u16":[16475,11135,19427,65535]},"Mid":{"hex":"#1b1d20ff","u16":[6909,7556,8204,65535]},"Midlight":{"hex":"#2d3136ff","u16":[11618,12707,13796,65535]},"PlaceholderText":{"hex":"#42464aff","u16":[16990,17994,18998,65535]},"Shadow":{"hex":"#0d0e0fff","u16":[3349,3663,3977,65535]},"Text":{"hex":"#606263ff","u16":[24792,25111,25430,65535]},"ToolTipBase":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"ToolTipText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Window":{"hex":"#1f2124ff","u16":[7839,8574,9309,65535]},"WindowText":{"hex":"#686a6cff","u16":[26703,27181,27658,65535]}},"Inactive":{"Accent":{"hex":"#1b4155ff","u16":[6925,16757,21966,65535]},"AlternateBase":{"hex":"#1d1f22ff","u16":[7453,7967,8738,65535]},"Base":{"hex":"#141618ff","u16":[5140,5654,6168,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"ButtonText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Dark":{"hex":"#131516ff","u16":[4829,5282,5735,65535]},"Highlight":{"hex":"#1b4155ff","u16":[6925,16757,21966,65535]},"HighlightedText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Light":{"hex":"#393e43ff","u16":[14585,15952,17319,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#1c1f21ff","u16":[7247,7927,8606,65535]},"Midlight":{"hex":"#2e3337ff","u16":[11896,13011,14126,65535]},"PlaceholderText":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Shadow":{"hex":"#0e0f10ff","u16":[3512,3841,4171,65535]},"Text":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ToolTipBase":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"ToolTipText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Window":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"WindowText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}}},"set_supported":{"Button":true,"Complementary":true,"Header":true,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#1b3a4bff","u16":[6896,14933,19288,65535]},"Alternate":{"hex":"#1e5774ff","u16":[7710,22359,29812,65535]},"Link":{"hex":"#133853ff","u16":[4917,14324,21273,65535]},"Negative":{"hex":"#552329ff","u16":[21732,9020,10600,65535]},"Neutral":{"hex":"#512d0cff","u16":[20833,11468,3200,65535]},"Normal":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"Positive":{"hex":"#163d28ff","u16":[5574,15604,10191,65535]},"Visited":{"hex":"#402a4bff","u16":[16359,10883,19199,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#181a1cff","u16":[6188,6640,7244,65535]},"Light":{"hex":"#40444bff","u16":[16369,17567,19164,65535]},"Mid":{"hex":"#24272aff","u16":[9281,9960,10866,65535]},"Midlight":{"hex":"#363a3fff","u16":[13785,14794,16138,65535]},"Shadow":{"hex":"#111314ff","u16":[4490,4818,5256,65535]}}},"Disabled":{"background":{"Active":{"hex":"#1f313cff","u16":[7990,12570,15303,65535]},"Alternate":{"hex":"#1d536fff","u16":[7349,21313,28418,65535]},"Link":{"hex":"#1c3041ff","u16":[7087,12438,16585,65535]},"Negative":{"hex":"#40272cff","u16":[16526,9950,11269,65535]},"Neutral":{"hex":"#3d2b1bff","u16":[15671,10956,6941,65535]},"Normal":{"hex":"#272a2eff","u16":[10044,10779,11759,65535]},"Positive":{"hex":"#1d3329ff","u16":[7399,13073,10618,65535]},"Visited":{"hex":"#342a3dff","u16":[13371,10802,15678,65535]}},"decoration":{"Focus":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]},"Hover":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]}},"foreground":{"Active":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]},"Inactive":{"hex":"#4f5459ff","u16":[20334,21497,22821,65535]},"Link":{"hex":"#234e6fff","u16":[9016,20126,28479,65535]},"Negative":{"hex":"#623239ff","u16":[25221,12837,14760,65535]},"Neutral":{"hex":"#6b421eff","u16":[27622,16952,7643,65535]},"Normal":{"hex":"#6d6f72ff","u16":[28136,28614,29251,65535]},"Positive":{"hex":"#26553eff","u16":[9873,21926,15875,65535]},"Visited":{"hex":"#4d395aff","u16":[19819,14638,23249,65535]}},"shade":{"Dark":{"hex":"#17191bff","u16":[5898,6330,6905,65535]},"Light":{"hex":"#3e4349ff","u16":[15996,17167,18727,65535]},"Mid":{"hex":"#222528ff","u16":[8848,9495,10359,65535]},"Midlight":{"hex":"#34383dff","u16":[13383,14362,15668,65535]},"Shadow":{"hex":"#111214ff","u16":[4282,4596,5013,65535]}}},"Inactive":{"background":{"Active":{"hex":"#1b3a4bff","u16":[6896,14933,19288,65535]},"Alternate":{"hex":"#1e5774ff","u16":[7710,22359,29812,65535]},"Link":{"hex":"#133853ff","u16":[4917,14324,21273,65535]},"Negative":{"hex":"#552329ff","u16":[21732,9020,10600,65535]},"Neutral":{"hex":"#512d0cff","u16":[20833,11468,3200,65535]},"Normal":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"Positive":{"hex":"#163d28ff","u16":[5574,15604,10191,65535]},"Visited":{"hex":"#402a4bff","u16":[16359,10883,19199,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#181a1cff","u16":[6188,6640,7244,65535]},"Light":{"hex":"#40444bff","u16":[16369,17567,19164,65535]},"Mid":{"hex":"#24272aff","u16":[9281,9960,10866,65535]},"Midlight":{"hex":"#363a3fff","u16":[13785,14794,16138,65535]},"Shadow":{"hex":"#111314ff","u16":[4490,4818,5256,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#163342ff","u16":[5768,13132,17061,65535]},"Alternate":{"hex":"#1e5774ff","u16":[7710,22359,29812,65535]},"Link":{"hex":"#0f3049ff","u16":[3907,12432,18670,65535]},"Negative":{"hex":"#491d22ff","u16":[18861,7508,8858,65535]},"Neutral":{"hex":"#472609ff","u16":[18363,9864,2300,65535]},"Normal":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"Positive":{"hex":"#123522ff","u16":[4517,13637,8654,65535]},"Visited":{"hex":"#372440ff","u16":[14073,9174,16553,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#131516ff","u16":[4829,5282,5735,65535]},"Light":{"hex":"#393e43ff","u16":[14585,15952,17319,65535]},"Mid":{"hex":"#1c1f21ff","u16":[7247,7927,8606,65535]},"Midlight":{"hex":"#2e3337ff","u16":[11896,13011,14126,65535]},"Shadow":{"hex":"#0e0f10ff","u16":[3512,3841,4171,65535]}}},"Disabled":{"background":{"Active":{"hex":"#182932ff","u16":[6250,10508,12917,65535]},"Alternate":{"hex":"#1d536fff","u16":[7349,21313,28418,65535]},"Link":{"hex":"#152837ff","u16":[5382,10359,14083,65535]},"Negative":{"hex":"#371f23ff","u16":[14113,8013,9100,65535]},"Neutral":{"hex":"#342314ff","u16":[13355,8977,5114,65535]},"Normal":{"hex":"#1f2124ff","u16":[7839,8574,9309,65535]},"Positive":{"hex":"#162b21ff","u16":[5678,10951,8536,65535]},"Visited":{"hex":"#2c2234ff","u16":[11222,8830,13239,65535]}},"decoration":{"Focus":{"hex":"#285065ff","u16":[10326,20493,26029,65535]},"Hover":{"hex":"#285065ff","u16":[10326,20493,26029,65535]}},"foreground":{"Active":{"hex":"#285065ff","u16":[10326,20493,26029,65535]},"Inactive":{"hex":"#4a4e53ff","u16":[18901,20064,21228,65535]},"Link":{"hex":"#1e4969ff","u16":[7582,18692,26887,65535]},"Negative":{"hex":"#5d2c33ff","u16":[23788,11404,13168,65535]},"Neutral":{"hex":"#663c18ff","u16":[26188,15520,6051,65535]},"Normal":{"hex":"#686a6cff","u16":[26703,27181,27658,65535]},"Positive":{"hex":"#215038ff","u16":[8440,20493,14282,65535]},"Visited":{"hex":"#483354ff","u16":[18386,13204,21657,65535]}},"shade":{"Dark":{"hex":"#121415ff","u16":[4603,5035,5466,65535]},"Light":{"hex":"#383d42ff","u16":[14337,15681,17025,65535]},"Mid":{"hex":"#1b1d20ff","u16":[6909,7556,8204,65535]},"Midlight":{"hex":"#2d3136ff","u16":[11618,12707,13796,65535]},"Shadow":{"hex":"#0d0e0fff","u16":[3349,3663,3977,65535]}}},"Inactive":{"background":{"Active":{"hex":"#163342ff","u16":[5768,13132,17061,65535]},"Alternate":{"hex":"#1e5774ff","u16":[7710,22359,29812,65535]},"Link":{"hex":"#0f3049ff","u16":[3907,12432,18670,65535]},"Negative":{"hex":"#491d22ff","u16":[18861,7508,8858,65535]},"Neutral":{"hex":"#472609ff","u16":[18363,9864,2300,65535]},"Normal":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"Positive":{"hex":"#123522ff","u16":[4517,13637,8654,65535]},"Visited":{"hex":"#372440ff","u16":[14073,9174,16553,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#131516ff","u16":[4829,5282,5735,65535]},"Light":{"hex":"#393e43ff","u16":[14585,15952,17319,65535]},"Mid":{"hex":"#1c1f21ff","u16":[7247,7927,8606,65535]},"Midlight":{"hex":"#2e3337ff","u16":[11896,13011,14126,65535]},"Shadow":{"hex":"#0e0f10ff","u16":[3512,3841,4171,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#1b3a4bff","u16":[6896,14933,19288,65535]},"Alternate":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"Link":{"hex":"#133853ff","u16":[4917,14324,21273,65535]},"Negative":{"hex":"#552329ff","u16":[21732,9020,10600,65535]},"Neutral":{"hex":"#512d0cff","u16":[20833,11468,3200,65535]},"Normal":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"Positive":{"hex":"#163d28ff","u16":[5574,15604,10191,65535]},"Visited":{"hex":"#402a4bff","u16":[16359,10883,19199,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#181a1cff","u16":[6188,6640,7244,65535]},"Light":{"hex":"#40444bff","u16":[16369,17567,19164,65535]},"Mid":{"hex":"#24272aff","u16":[9281,9960,10866,65535]},"Midlight":{"hex":"#363a3fff","u16":[13785,14794,16138,65535]},"Shadow":{"hex":"#111314ff","u16":[4490,4818,5256,65535]}}},"Disabled":{"background":{"Active":{"hex":"#1f313cff","u16":[7990,12570,15303,65535]},"Alternate":{"hex":"#1f2124ff","u16":[7839,8574,9309,65535]},"Link":{"hex":"#1c3041ff","u16":[7087,12438,16585,65535]},"Negative":{"hex":"#40272cff","u16":[16526,9950,11269,65535]},"Neutral":{"hex":"#3d2b1bff","u16":[15671,10956,6941,65535]},"Normal":{"hex":"#272a2eff","u16":[10044,10779,11759,65535]},"Positive":{"hex":"#1d3329ff","u16":[7399,13073,10618,65535]},"Visited":{"hex":"#342a3dff","u16":[13371,10802,15678,65535]}},"decoration":{"Focus":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]},"Hover":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]}},"foreground":{"Active":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]},"Inactive":{"hex":"#4f5459ff","u16":[20334,21497,22821,65535]},"Link":{"hex":"#234e6fff","u16":[9016,20126,28479,65535]},"Negative":{"hex":"#623239ff","u16":[25221,12837,14760,65535]},"Neutral":{"hex":"#6b421eff","u16":[27622,16952,7643,65535]},"Normal":{"hex":"#6d6f72ff","u16":[28136,28614,29251,65535]},"Positive":{"hex":"#26553eff","u16":[9873,21926,15875,65535]},"Visited":{"hex":"#4d395aff","u16":[19819,14638,23249,65535]}},"shade":{"Dark":{"hex":"#17191bff","u16":[5898,6330,6905,65535]},"Light":{"hex":"#3e4349ff","u16":[15996,17167,18727,65535]},"Mid":{"hex":"#222528ff","u16":[8848,9495,10359,65535]},"Midlight":{"hex":"#34383dff","u16":[13383,14362,15668,65535]},"Shadow":{"hex":"#111214ff","u16":[4282,4596,5013,65535]}}},"Inactive":{"background":{"Active":{"hex":"#163342ff","u16":[5768,13132,17061,65535]},"Alternate":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"Link":{"hex":"#0f3049ff","u16":[3907,12432,18670,65535]},"Negative":{"hex":"#491d22ff","u16":[18861,7508,8858,65535]},"Neutral":{"hex":"#472609ff","u16":[18363,9864,2300,65535]},"Normal":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"Positive":{"hex":"#123522ff","u16":[4517,13637,8654,65535]},"Visited":{"hex":"#372440ff","u16":[14073,9174,16553,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#131516ff","u16":[4829,5282,5735,65535]},"Light":{"hex":"#393e43ff","u16":[14585,15952,17319,65535]},"Mid":{"hex":"#1c1f21ff","u16":[7247,7927,8606,65535]},"Midlight":{"hex":"#2e3337ff","u16":[11896,13011,14126,65535]},"Shadow":{"hex":"#0e0f10ff","u16":[3512,3841,4171,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#5fb6daff","u16":[24519,46759,56078,65535]},"Alternate":{"hex":"#1e5774ff","u16":[7710,22359,29812,65535]},"Link":{"hex":"#b3ab74ff","u16":[46003,43823,29737,65535]},"Negative":{"hex":"#aa96b0ff","u16":[43694,38429,45353,65535]},"Neutral":{"hex":"#b29b74ff","u16":[45660,39812,29747,65535]},"Normal":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Positive":{"hex":"#32ada4ff","u16":[12736,44427,42135,65535]},"Visited":{"hex":"#9799d3ff","u16":[38815,39221,54248,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Negative":{"hex":"#b03745ff","u16":[45232,14135,17733,65535]},"Neutral":{"hex":"#c65c00ff","u16":[50886,23644,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#176839ff","u16":[5911,26728,14649,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#246689ff","u16":[9206,26259,35163,65535]},"Light":{"hex":"#7ac6f9ff","u16":[31305,50791,63873,65535]},"Mid":{"hex":"#3596c9ff","u16":[13498,38503,51559,65535]},"Midlight":{"hex":"#41b9f7ff","u16":[16627,47428,63511,65535]},"Shadow":{"hex":"#174259ff","u16":[5979,17055,22838,65535]}}},"Disabled":{"background":{"Active":{"hex":"#182932ff","u16":[6250,10508,12917,65535]},"Alternate":{"hex":"#272a2eff","u16":[10044,10779,11759,65535]},"Link":{"hex":"#152837ff","u16":[5382,10359,14083,65535]},"Negative":{"hex":"#371f23ff","u16":[14113,8013,9100,65535]},"Neutral":{"hex":"#342314ff","u16":[13355,8977,5114,65535]},"Normal":{"hex":"#1f2124ff","u16":[7839,8574,9309,65535]},"Positive":{"hex":"#162b21ff","u16":[5678,10951,8536,65535]},"Visited":{"hex":"#2c2234ff","u16":[11222,8830,13239,65535]}},"decoration":{"Focus":{"hex":"#285065ff","u16":[10326,20493,26029,65535]},"Hover":{"hex":"#285065ff","u16":[10326,20493,26029,65535]}},"foreground":{"Active":{"hex":"#285065ff","u16":[10326,20493,26029,65535]},"Inactive":{"hex":"#4a4e53ff","u16":[18901,20064,21228,65535]},"Link":{"hex":"#1e4969ff","u16":[7582,18692,26887,65535]},"Negative":{"hex":"#5d2c33ff","u16":[23788,11404,13168,65535]},"Neutral":{"hex":"#663c18ff","u16":[26188,15520,6051,65535]},"Normal":{"hex":"#686a6cff","u16":[26703,27181,27658,65535]},"Positive":{"hex":"#215038ff","u16":[8440,20493,14282,65535]},"Visited":{"hex":"#483354ff","u16":[18386,13204,21657,65535]}},"shade":{"Dark":{"hex":"#121415ff","u16":[4603,5035,5466,65535]},"Light":{"hex":"#383d42ff","u16":[14337,15681,17025,65535]},"Mid":{"hex":"#1b1d20ff","u16":[6909,7556,8204,65535]},"Midlight":{"hex":"#2d3136ff","u16":[11618,12707,13796,65535]},"Shadow":{"hex":"#0d0e0fff","u16":[3349,3663,3977,65535]}}},"Inactive":{"background":{"Active":{"hex":"#1b4a63ff","u16":[6989,19050,25370,65535]},"Alternate":{"hex":"#1e475cff","u16":[7822,18208,23768,65535]},"Link":{"hex":"#12486cff","u16":[4643,18446,27874,65535]},"Negative":{"hex":"#68323fff","u16":[26686,12898,16217,65535]},"Neutral":{"hex":"#603d1aff","u16":[24702,15719,6629,65535]},"Normal":{"hex":"#1b4155ff","u16":[6925,16757,21966,65535]},"Positive":{"hex":"#154e3aff","u16":[5442,19994,14886,65535]},"Visited":{"hex":"#4a3b68ff","u16":[19071,15048,26829,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#102632ff","u16":[4066,9840,12899,65535]},"Light":{"hex":"#245873ff","u16":[9343,22609,29637,65535]},"Mid":{"hex":"#18394bff","u16":[6091,14738,19320,65535]},"Midlight":{"hex":"#204d65ff","u16":[8205,19854,26025,65535]},"Shadow":{"hex":"#0b1c24ff","u16":[2932,7095,9300,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#1b3a4bff","u16":[6896,14933,19288,65535]},"Alternate":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"Link":{"hex":"#133853ff","u16":[4917,14324,21273,65535]},"Negative":{"hex":"#552329ff","u16":[21732,9020,10600,65535]},"Neutral":{"hex":"#512d0cff","u16":[20833,11468,3200,65535]},"Normal":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"Positive":{"hex":"#163d28ff","u16":[5574,15604,10191,65535]},"Visited":{"hex":"#402a4bff","u16":[16359,10883,19199,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#181a1cff","u16":[6188,6640,7244,65535]},"Light":{"hex":"#40444bff","u16":[16369,17567,19164,65535]},"Mid":{"hex":"#24272aff","u16":[9281,9960,10866,65535]},"Midlight":{"hex":"#363a3fff","u16":[13785,14794,16138,65535]},"Shadow":{"hex":"#111314ff","u16":[4490,4818,5256,65535]}}},"Disabled":{"background":{"Active":{"hex":"#1f313cff","u16":[7990,12570,15303,65535]},"Alternate":{"hex":"#1f2124ff","u16":[7839,8574,9309,65535]},"Link":{"hex":"#1c3041ff","u16":[7087,12438,16585,65535]},"Negative":{"hex":"#40272cff","u16":[16526,9950,11269,65535]},"Neutral":{"hex":"#3d2b1bff","u16":[15671,10956,6941,65535]},"Normal":{"hex":"#272a2eff","u16":[10044,10779,11759,65535]},"Positive":{"hex":"#1d3329ff","u16":[7399,13073,10618,65535]},"Visited":{"hex":"#342a3dff","u16":[13371,10802,15678,65535]}},"decoration":{"Focus":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]},"Hover":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]}},"foreground":{"Active":{"hex":"#2e556bff","u16":[11759,21926,27622,65535]},"Inactive":{"hex":"#4f5459ff","u16":[20334,21497,22821,65535]},"Link":{"hex":"#234e6fff","u16":[9016,20126,28479,65535]},"Negative":{"hex":"#623239ff","u16":[25221,12837,14760,65535]},"Neutral":{"hex":"#6b421eff","u16":[27622,16952,7643,65535]},"Normal":{"hex":"#6d6f72ff","u16":[28136,28614,29251,65535]},"Positive":{"hex":"#26553eff","u16":[9873,21926,15875,65535]},"Visited":{"hex":"#4d395aff","u16":[19819,14638,23249,65535]}},"shade":{"Dark":{"hex":"#17191bff","u16":[5898,6330,6905,65535]},"Light":{"hex":"#3e4349ff","u16":[15996,17167,18727,65535]},"Mid":{"hex":"#222528ff","u16":[8848,9495,10359,65535]},"Midlight":{"hex":"#34383dff","u16":[13383,14362,15668,65535]},"Shadow":{"hex":"#111214ff","u16":[4282,4596,5013,65535]}}},"Inactive":{"background":{"Active":{"hex":"#1b3a4bff","u16":[6896,14933,19288,65535]},"Alternate":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"Link":{"hex":"#133853ff","u16":[4917,14324,21273,65535]},"Negative":{"hex":"#552329ff","u16":[21732,9020,10600,65535]},"Neutral":{"hex":"#512d0cff","u16":[20833,11468,3200,65535]},"Normal":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"Positive":{"hex":"#163d28ff","u16":[5574,15604,10191,65535]},"Visited":{"hex":"#402a4bff","u16":[16359,10883,19199,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#181a1cff","u16":[6188,6640,7244,65535]},"Light":{"hex":"#40444bff","u16":[16369,17567,19164,65535]},"Mid":{"hex":"#24272aff","u16":[9281,9960,10866,65535]},"Midlight":{"hex":"#363a3fff","u16":[13785,14794,16138,65535]},"Shadow":{"hex":"#111314ff","u16":[4490,4818,5256,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#122b38ff","u16":[4508,11049,14513,65535]},"Alternate":{"hex":"#1d1f22ff","u16":[7453,7967,8738,65535]},"Link":{"hex":"#0b283dff","u16":[2774,10231,15673,65535]},"Negative":{"hex":"#3c161aff","u16":[15396,5670,6764,65535]},"Neutral":{"hex":"#3c1f05ff","u16":[15545,7989,1258,65535]},"Normal":{"hex":"#141618ff","u16":[5140,5654,6168,65535]},"Positive":{"hex":"#0d2c1bff","u16":[3334,11349,6883,65535]},"Visited":{"hex":"#2c1c34ff","u16":[11338,7094,13342,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#6d7883ff","u16":[27950,30745,33540,65535]},"Light":{"hex":"#a2b0bfff","u16":[41613,45334,49093,65535]},"Mid":{"hex":"#505860ff","u16":[20618,22680,24742,65535]},"Midlight":{"hex":"#828f9cff","u16":[33483,36832,40180,65535]},"Shadow":{"hex":"#828f9cff","u16":[33483,36832,40180,65535]}}},"Disabled":{"background":{"Active":{"hex":"#101e26ff","u16":[4138,7824,9858,65535]},"Alternate":{"hex":"#1c1e20ff","u16":[7104,7594,8329,65535]},"Link":{"hex":"#0d1e2aff","u16":[3316,7643,10848,65535]},"Negative":{"hex":"#2b1518ff","u16":[11025,5446,6284,65535]},"Neutral":{"hex":"#29190bff","u16":[10415,6388,2820,65535]},"Normal":{"hex":"#131517ff","u16":[4900,5390,5880,65535]},"Positive":{"hex":"#0e2017ff","u16":[3593,8184,5900,65535]},"Visited":{"hex":"#211827ff","u16":[8529,6234,10080,65535]}},"decoration":{"Focus":{"hex":"#21485dff","u16":[8415,18422,23800,65535]},"Hover":{"hex":"#21485dff","u16":[8415,18422,23800,65535]}},"foreground":{"Active":{"hex":"#21485dff","u16":[8415,18422,23800,65535]},"Inactive":{"hex":"#42464aff","u16":[16990,17994,18998,65535]},"Link":{"hex":"#164160ff","u16":[5672,16622,24657,65535]},"Negative":{"hex":"#55242bff","u16":[21877,9334,10938,65535]},"Neutral":{"hex":"#5e340fff","u16":[24278,13449,3822,65535]},"Normal":{"hex":"#606263ff","u16":[24792,25111,25430,65535]},"Positive":{"hex":"#19482fff","u16":[6529,18422,12053,65535]},"Visited":{"hex":"#402b4cff","u16":[16475,11135,19427,65535]}},"shade":{"Dark":{"hex":"#6d7782ff","u16":[27919,30711,33503,65535]},"Light":{"hex":"#a2b0bfff","u16":[41588,45314,49078,65535]},"Mid":{"hex":"#505860ff","u16":[20574,22631,24689,65535]},"Midlight":{"hex":"#828f9cff","u16":[33459,36805,40150,65535]},"Shadow":{"hex":"#828f9cff","u16":[33459,36805,40150,65535]}}},"Inactive":{"background":{"Active":{"hex":"#122b38ff","u16":[4508,11049,14513,65535]},"Alternate":{"hex":"#1d1f22ff","u16":[7453,7967,8738,65535]},"Link":{"hex":"#0b283dff","u16":[2774,10231,15673,65535]},"Negative":{"hex":"#3c161aff","u16":[15396,5670,6764,65535]},"Neutral":{"hex":"#3c1f05ff","u16":[15545,7989,1258,65535]},"Normal":{"hex":"#141618ff","u16":[5140,5654,6168,65535]},"Positive":{"hex":"#0d2c1bff","u16":[3334,11349,6883,65535]},"Visited":{"hex":"#2c1c34ff","u16":[11338,7094,13342,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#6d7883ff","u16":[27950,30745,33540,65535]},"Light":{"hex":"#a2b0bfff","u16":[41613,45334,49093,65535]},"Mid":{"hex":"#505860ff","u16":[20618,22680,24742,65535]},"Midlight":{"hex":"#828f9cff","u16":[33483,36832,40180,65535]},"Shadow":{"hex":"#828f9cff","u16":[33483,36832,40180,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#163342ff","u16":[5768,13132,17061,65535]},"Alternate":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"Link":{"hex":"#0f3049ff","u16":[3907,12432,18670,65535]},"Negative":{"hex":"#491d22ff","u16":[18861,7508,8858,65535]},"Neutral":{"hex":"#472609ff","u16":[18363,9864,2300,65535]},"Normal":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"Positive":{"hex":"#123522ff","u16":[4517,13637,8654,65535]},"Visited":{"hex":"#372440ff","u16":[14073,9174,16553,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#131516ff","u16":[4829,5282,5735,65535]},"Light":{"hex":"#393e43ff","u16":[14585,15952,17319,65535]},"Mid":{"hex":"#1c1f21ff","u16":[7247,7927,8606,65535]},"Midlight":{"hex":"#2e3337ff","u16":[11896,13011,14126,65535]},"Shadow":{"hex":"#0e0f10ff","u16":[3512,3841,4171,65535]}}},"Disabled":{"background":{"Active":{"hex":"#182932ff","u16":[6250,10508,12917,65535]},"Alternate":{"hex":"#272a2eff","u16":[10044,10779,11759,65535]},"Link":{"hex":"#152837ff","u16":[5382,10359,14083,65535]},"Negative":{"hex":"#371f23ff","u16":[14113,8013,9100,65535]},"Neutral":{"hex":"#342314ff","u16":[13355,8977,5114,65535]},"Normal":{"hex":"#1f2124ff","u16":[7839,8574,9309,65535]},"Positive":{"hex":"#162b21ff","u16":[5678,10951,8536,65535]},"Visited":{"hex":"#2c2234ff","u16":[11222,8830,13239,65535]}},"decoration":{"Focus":{"hex":"#285065ff","u16":[10326,20493,26029,65535]},"Hover":{"hex":"#285065ff","u16":[10326,20493,26029,65535]}},"foreground":{"Active":{"hex":"#285065ff","u16":[10326,20493,26029,65535]},"Inactive":{"hex":"#4a4e53ff","u16":[18901,20064,21228,65535]},"Link":{"hex":"#1e4969ff","u16":[7582,18692,26887,65535]},"Negative":{"hex":"#5d2c33ff","u16":[23788,11404,13168,65535]},"Neutral":{"hex":"#663c18ff","u16":[26188,15520,6051,65535]},"Normal":{"hex":"#686a6cff","u16":[26703,27181,27658,65535]},"Positive":{"hex":"#215038ff","u16":[8440,20493,14282,65535]},"Visited":{"hex":"#483354ff","u16":[18386,13204,21657,65535]}},"shade":{"Dark":{"hex":"#121415ff","u16":[4603,5035,5466,65535]},"Light":{"hex":"#383d42ff","u16":[14337,15681,17025,65535]},"Mid":{"hex":"#1b1d20ff","u16":[6909,7556,8204,65535]},"Midlight":{"hex":"#2d3136ff","u16":[11618,12707,13796,65535]},"Shadow":{"hex":"#0d0e0fff","u16":[3349,3663,3977,65535]}}},"Inactive":{"background":{"Active":{"hex":"#163342ff","u16":[5768,13132,17061,65535]},"Alternate":{"hex":"#292c30ff","u16":[10537,11308,12336,65535]},"Link":{"hex":"#0f3049ff","u16":[3907,12432,18670,65535]},"Negative":{"hex":"#491d22ff","u16":[18861,7508,8858,65535]},"Neutral":{"hex":"#472609ff","u16":[18363,9864,2300,65535]},"Normal":{"hex":"#202326ff","u16":[8224,8995,9766,65535]},"Positive":{"hex":"#123522ff","u16":[4517,13637,8654,65535]},"Visited":{"hex":"#372440ff","u16":[14073,9174,16553,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#131516ff","u16":[4829,5282,5735,65535]},"Light":{"hex":"#393e43ff","u16":[14585,15952,17319,65535]},"Mid":{"hex":"#1c1f21ff","u16":[7247,7927,8606,65535]},"Midlight":{"hex":"#2e3337ff","u16":[11896,13011,14126,65535]},"Shadow":{"hex":"#0e0f10ff","u16":[3512,3841,4171,65535]}}}}},"sha256":"11fce1d8ab97b529162e50eb200d1a93fab65c7612f9bebd0230a6a348d0cc21","source":"/usr/share/color-schemes/BreezeDark.colors"}, +{"contrastF":0.4,"frameContrast":0.2,"input":{"ColorEffects:Disabled":{"Color":"56,56,56","ColorAmount":"0","ColorEffect":"0","ContrastAmount":"0.65","ContrastEffect":"1","IntensityAmount":"0.1","IntensityEffect":"2"},"ColorEffects:Inactive":{"ChangeSelectionColor":"true","Color":"112,111,110","ColorAmount":"0.025","ColorEffect":"2","ContrastAmount":"0.1","ContrastEffect":"2","Enable":"false","IntensityAmount":"0","IntensityEffect":"0"},"Colors:Button":{"BackgroundAlternate":"189,195,199","BackgroundNormal":"239,240,241","DecorationFocus":"61,174,233","DecorationHover":"147,206,233","ForegroundActive":"61,174,233","ForegroundInactive":"127,140,141","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"198,92,0","ForegroundNormal":"35,38,39","ForegroundPositive":"39,174,96","ForegroundVisited":"127,140,141"},"Colors:Complementary":{"BackgroundAlternate":"59,64,69","BackgroundNormal":"49,54,59","DecorationFocus":"30,146,255","DecorationHover":"61,174,230","ForegroundActive":"147,206,233","ForegroundInactive":"175,176,179","ForegroundLink":"61,174,230","ForegroundNegative":"231,76,60","ForegroundNeutral":"253,188,75","ForegroundNormal":"239,240,241","ForegroundPositive":"46,204,113","ForegroundVisited":"61,174,230"},"Colors:Selection":{"BackgroundAlternate":"29,153,243","BackgroundNormal":"61,174,233","DecorationFocus":"61,174,233","DecorationHover":"147,206,233","ForegroundActive":"252,252,252","ForegroundInactive":"239,240,241","ForegroundLink":"253,188,75","ForegroundNegative":"176,55,69","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"23,104,57","ForegroundVisited":"189,195,199"},"Colors:Tooltip":{"BackgroundAlternate":"77,77,77","BackgroundNormal":"35,38,39","DecorationFocus":"61,174,233","DecorationHover":"147,206,233","ForegroundActive":"61,174,233","ForegroundInactive":"189,195,199","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"252,252,252","ForegroundPositive":"39,174,96","ForegroundVisited":"127,140,141"},"Colors:View":{"BackgroundAlternate":"239,240,241","BackgroundNormal":"252,252,252","DecorationFocus":"61,174,233","DecorationHover":"147,206,233","ForegroundActive":"61,174,233","ForegroundInactive":"127,140,141","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"35,38,39","ForegroundPositive":"39,174,96","ForegroundVisited":"127,140,141"},"Colors:Window":{"BackgroundAlternate":"189,195,199","BackgroundNormal":"239,240,241","DecorationFocus":"61,174,233","DecorationHover":"147,206,233","ForegroundActive":"61,174,233","ForegroundInactive":"127,140,141","ForegroundLink":"41,128,185","ForegroundNegative":"218,68,83","ForegroundNeutral":"246,116,0","ForegroundNormal":"35,38,39","ForegroundPositive":"39,174,96","ForegroundVisited":"127,140,141"},"General":{"ColorScheme":"BreezeClassic","Name":"Breeze Classic","accentActiveTitlebar":"true","accentInactiveTitlebar":"false","shadeSortColumn":"true"},"KDE":{"contrast":"4"},"WM":{"activeBackground":"71,80,87","activeBlend":"252,252,252","activeForeground":"252,252,252","inactiveBackground":"239,240,241","inactiveBlend":"75,71,67","inactiveForeground":"189,195,199"}},"name":"BreezeClassic","origin":"installed","palette":{"Active":{"Accent":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"AlternateBase":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Base":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"ButtonText":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Highlight":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"HighlightedText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"LinkVisited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"PlaceholderText":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]},"Text":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"ToolTipBase":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"ToolTipText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Window":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"WindowText":{"hex":"#232627ff","u16":[8995,9766,10023,65535]}},"Disabled":{"Accent":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"AlternateBase":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Base":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"ButtonText":{"hex":"#a0a1a2ff","u16":[41059,41475,41720,65535]},"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Highlight":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"HighlightedText":{"hex":"#a0a1a2ff","u16":[41059,41475,41720,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#a2c8e0ff","u16":[41595,51411,57571,65535]},"LinkVisited":{"hex":"#c7cbcbff","u16":[51017,52133,52218,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"PlaceholderText":{"hex":"#c7cbcbff","u16":[51017,52133,52218,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]},"Text":{"hex":"#a8a9a9ff","u16":[43129,43387,43472,65535]},"ToolTipBase":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"ToolTipText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Window":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"WindowText":{"hex":"#a0a1a2ff","u16":[41059,41475,41720,65535]}},"Inactive":{"Accent":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"AlternateBase":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Base":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"ButtonText":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Highlight":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"HighlightedText":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"LinkVisited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"PlaceholderText":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]},"Text":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"ToolTipBase":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"ToolTipText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Window":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"WindowText":{"hex":"#232627ff","u16":[8995,9766,10023,65535]}}},"set_supported":{"Button":true,"Complementary":true,"Header":false,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#f7deceff","u16":[63563,56954,53062,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#dee6e7ff","u16":[57140,59044,59270,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#c65c00ff","u16":[50886,23644,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#b3babfff","u16":[46008,47826,49030,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#ebdbceff","u16":[60270,56259,52901,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#dcdedfff","u16":[56481,57129,57326,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#bec3c4ff","u16":[48948,50221,50466,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#dbb28dff","u16":[56379,45777,36142,65535]},"Normal":{"hex":"#a0a1a2ff","u16":[41059,41475,41720,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#bec3c4ff","u16":[48948,50221,50466,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#f7deceff","u16":[63563,56954,53062,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#dee6e7ff","u16":[57140,59044,59270,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#c65c00ff","u16":[50886,23644,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#33444dff","u16":[13230,17572,19739,65535]},"Alternate":{"hex":"#3b4045ff","u16":[15163,16448,17733,65535]},"Link":{"hex":"#204355ff","u16":[8135,17104,21728,65535]},"Negative":{"hex":"#622c27ff","u16":[25139,11184,10089,65535]},"Neutral":{"hex":"#504023ff","u16":[20486,16381,9001,65535]},"Normal":{"hex":"#31363bff","u16":[12593,13878,15163,65535]},"Positive":{"hex":"#1a4830ff","u16":[6722,18565,12287,65535]},"Visited":{"hex":"#204355ff","u16":[8135,17104,21728,65535]}},"decoration":{"Focus":{"hex":"#1e92ffff","u16":[7710,37522,65535,65535]},"Hover":{"hex":"#3daee6ff","u16":[15677,44718,59110,65535]}},"foreground":{"Active":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]},"Inactive":{"hex":"#afb0b3ff","u16":[44975,45232,46003,65535]},"Link":{"hex":"#3daee6ff","u16":[15677,44718,59110,65535]},"Negative":{"hex":"#e74c3cff","u16":[59367,19532,15420,65535]},"Neutral":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#2ecc71ff","u16":[11822,52428,29041,65535]},"Visited":{"hex":"#3daee6ff","u16":[15677,44718,59110,65535]}},"shade":{"Dark":{"hex":"#1d2023ff","u16":[7395,8149,8904,65535]},"Light":{"hex":"#464d54ff","u16":[17874,19698,21522,65535]},"Mid":{"hex":"#2b3034ff","u16":[11084,12215,13346,65535]},"Midlight":{"hex":"#3c4248ff","u16":[15447,17023,18599,65535]},"Shadow":{"hex":"#151719ff","u16":[5349,5894,6441,65535]}}},"Disabled":{"background":{"Active":{"hex":"#2f3a40ff","u16":[12041,14808,16455,65535]},"Alternate":{"hex":"#383d42ff","u16":[14454,15679,16904,65535]},"Link":{"hex":"#263a45ff","u16":[9705,14907,17861,65535]},"Negative":{"hex":"#4a3030ff","u16":[19084,12321,12339,65535]},"Neutral":{"hex":"#3e372bff","u16":[15825,14245,10976,65535]},"Normal":{"hex":"#2f3338ff","u16":[12004,13229,14454,65535]},"Positive":{"hex":"#233d32ff","u16":[8905,15613,12901,65535]},"Visited":{"hex":"#263a45ff","u16":[9705,14907,17861,65535]}},"decoration":{"Focus":{"hex":"#28527aff","u16":[10375,21117,31260,65535]},"Hover":{"hex":"#335c71ff","u16":[13033,23518,29116,65535]}},"foreground":{"Active":{"hex":"#4f6672ff","u16":[20407,26262,29373,65535]},"Inactive":{"hex":"#595c60ff","u16":[22808,23690,24743,65535]},"Link":{"hex":"#335c71ff","u16":[13033,23518,29116,65535]},"Negative":{"hex":"#6b3b39ff","u16":[27610,15115,14540,65535]},"Neutral":{"hex":"#73603eff","u16":[29496,24718,15826,65535]},"Normal":{"hex":"#6e7275ff","u16":[28296,29178,30059,65535]},"Positive":{"hex":"#2e664aff","u16":[11747,26091,19084,65535]},"Visited":{"hex":"#335c71ff","u16":[13033,23518,29116,65535]}},"shade":{"Dark":{"hex":"#1b1e21ff","u16":[7049,7768,8488,65535]},"Light":{"hex":"#444b51ff","u16":[17376,19149,20922,65535]},"Mid":{"hex":"#292d32ff","u16":[10568,11646,12725,65535]},"Midlight":{"hex":"#3a4046ff","u16":[14928,16451,17975,65535]},"Shadow":{"hex":"#141618ff","u16":[5103,5624,6145,65535]}}},"Inactive":{"background":{"Active":{"hex":"#33444dff","u16":[13230,17572,19739,65535]},"Alternate":{"hex":"#3b4045ff","u16":[15163,16448,17733,65535]},"Link":{"hex":"#204355ff","u16":[8135,17104,21728,65535]},"Negative":{"hex":"#622c27ff","u16":[25139,11184,10089,65535]},"Neutral":{"hex":"#504023ff","u16":[20486,16381,9001,65535]},"Normal":{"hex":"#31363bff","u16":[12593,13878,15163,65535]},"Positive":{"hex":"#1a4830ff","u16":[6722,18565,12287,65535]},"Visited":{"hex":"#204355ff","u16":[8135,17104,21728,65535]}},"decoration":{"Focus":{"hex":"#1e92ffff","u16":[7710,37522,65535,65535]},"Hover":{"hex":"#3daee6ff","u16":[15677,44718,59110,65535]}},"foreground":{"Active":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]},"Inactive":{"hex":"#afb0b3ff","u16":[44975,45232,46003,65535]},"Link":{"hex":"#3daee6ff","u16":[15677,44718,59110,65535]},"Negative":{"hex":"#e74c3cff","u16":[59367,19532,15420,65535]},"Neutral":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#2ecc71ff","u16":[11822,52428,29041,65535]},"Visited":{"hex":"#3daee6ff","u16":[15677,44718,59110,65535]}},"shade":{"Dark":{"hex":"#1d2023ff","u16":[7395,8149,8904,65535]},"Light":{"hex":"#464d54ff","u16":[17874,19698,21522,65535]},"Mid":{"hex":"#2b3034ff","u16":[11084,12215,13346,65535]},"Midlight":{"hex":"#3c4248ff","u16":[15447,17023,18599,65535]},"Shadow":{"hex":"#151719ff","u16":[5349,5894,6441,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#dee6e7ff","u16":[57140,59044,59270,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#b3babfff","u16":[46008,47826,49030,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#dcdedfff","u16":[56481,57129,57326,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#bec3c4ff","u16":[48948,50221,50466,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a2ff","u16":[41059,41475,41720,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#bec3c4ff","u16":[48948,50221,50466,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#dee6e7ff","u16":[57140,59044,59270,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#5fb6daff","u16":[24519,46759,56078,65535]},"Alternate":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Link":{"hex":"#b3ab74ff","u16":[46003,43823,29737,65535]},"Negative":{"hex":"#aa96b0ff","u16":[43694,38429,45353,65535]},"Neutral":{"hex":"#cb9555ff","u16":[52118,38235,21913,65535]},"Normal":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Positive":{"hex":"#32ada4ff","u16":[12736,44427,42135,65535]},"Visited":{"hex":"#89afc3ff","u16":[35206,44847,50019,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Inactive":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Negative":{"hex":"#b03745ff","u16":[45232,14135,17733,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#176839ff","u16":[5911,26728,14649,65535]},"Visited":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]}},"shade":{"Dark":{"hex":"#246689ff","u16":[9206,26259,35163,65535]},"Light":{"hex":"#7ac6f9ff","u16":[31305,50791,63873,65535]},"Mid":{"hex":"#3596c9ff","u16":[13498,38503,51559,65535]},"Midlight":{"hex":"#41b9f7ff","u16":[16627,47428,63511,65535]},"Shadow":{"hex":"#174259ff","u16":[5979,17055,22838,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#b3babfff","u16":[46008,47826,49030,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#dcdedfff","u16":[56481,57129,57326,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#bec3c4ff","u16":[48948,50221,50466,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a2ff","u16":[41059,41475,41720,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#bec3c4ff","u16":[48948,50221,50466,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#b3d9f4ff","u16":[45899,55787,62650,65535]},"Alternate":{"hex":"#63bff1ff","u16":[25410,49055,62014,65535]},"Link":{"hex":"#b5d4efff","u16":[46454,54609,61533,65535]},"Negative":{"hex":"#e8c9cfff","u16":[59622,51622,53083,65535]},"Neutral":{"hex":"#f4cbb0ff","u16":[62731,52266,45109,65535]},"Normal":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"Positive":{"hex":"#75e9b8ff","u16":[30182,59846,47181,65535]},"Visited":{"hex":"#c5d5dcff","u16":[50597,54642,56482,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#4b88a8ff","u16":[19360,34951,43113,65535]},"Light":{"hex":"#f8fbfeff","u16":[63734,64548,65196,65535]},"Mid":{"hex":"#75c2ecff","u16":[30160,49859,60666,65535]},"Midlight":{"hex":"#d1e7f7ff","u16":[53672,59338,63476,65535]},"Shadow":{"hex":"#2b4d5fff","u16":[10934,19739,24349,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#183545ff","u16":[6119,13690,17608,65535]},"Alternate":{"hex":"#4d4d4dff","u16":[19789,19789,19789,65535]},"Link":{"hex":"#153042ff","u16":[5520,12451,16928,65535]},"Negative":{"hex":"#4d1f24ff","u16":[19746,7978,9242,65535]},"Neutral":{"hex":"#4a2809ff","u16":[19123,10359,2434,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#133723ff","u16":[4843,14233,8978,65535]},"Visited":{"hex":"#2c3031ff","u16":[11296,12414,12550,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#151617ff","u16":[5282,5735,5886,65535]},"Light":{"hex":"#3b4042ff","u16":[15202,16505,16940,65535]},"Mid":{"hex":"#1f2122ff","u16":[7926,8605,8832,65535]},"Midlight":{"hex":"#313536ff","u16":[12535,13609,13967,65535]},"Shadow":{"hex":"#0f1011ff","u16":[3839,4168,4278,65535]}}},"Disabled":{"background":{"Active":{"hex":"#1a2b34ff","u16":[6808,11169,13370,65535]},"Alternate":{"hex":"#494949ff","u16":[18864,18864,18864,65535]},"Link":{"hex":"#1a2a34ff","u16":[6802,10915,13469,65535]},"Negative":{"hex":"#3a2225ff","u16":[14878,8635,9451,65535]},"Neutral":{"hex":"#372515ff","u16":[14082,9603,5374,65535]},"Normal":{"hex":"#212425ff","u16":[8574,9309,9554,65535]},"Positive":{"hex":"#182d23ff","u16":[6227,11621,8879,65535]},"Visited":{"hex":"#262a2aff","u16":[9762,10680,10858,65535]}},"decoration":{"Focus":{"hex":"#2a5266ff","u16":[10804,20970,26188,65535]},"Hover":{"hex":"#475c66ff","u16":[18177,23715,26188,65535]}},"foreground":{"Active":{"hex":"#2a5266ff","u16":[10804,20970,26188,65535]},"Inactive":{"hex":"#55595bff","u16":[21779,22771,23273,65535]},"Link":{"hex":"#234256ff","u16":[9089,17027,22073,65535]},"Negative":{"hex":"#5e2e34ff","u16":[24266,11882,13327,65535]},"Neutral":{"hex":"#683e18ff","u16":[26666,15997,6210,65535]},"Normal":{"hex":"#6a6c6cff","u16":[27181,27658,27817,65535]},"Positive":{"hex":"#235238ff","u16":[8918,20970,14442,65535]},"Visited":{"hex":"#404647ff","u16":[16462,18055,18300,65535]}},"shade":{"Dark":{"hex":"#141516ff","u16":[5035,5466,5610,65535]},"Light":{"hex":"#3a3f41ff","u16":[14915,16194,16620,65535]},"Mid":{"hex":"#1d2021ff","u16":[7555,8203,8419,65535]},"Midlight":{"hex":"#303435ff","u16":[12217,13264,13613,65535]},"Shadow":{"hex":"#0e0f10ff","u16":[3661,3974,4079,65535]}}},"Inactive":{"background":{"Active":{"hex":"#183545ff","u16":[6119,13690,17608,65535]},"Alternate":{"hex":"#4d4d4dff","u16":[19789,19789,19789,65535]},"Link":{"hex":"#153042ff","u16":[5520,12451,16928,65535]},"Negative":{"hex":"#4d1f24ff","u16":[19746,7978,9242,65535]},"Neutral":{"hex":"#4a2809ff","u16":[19123,10359,2434,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#133723ff","u16":[4843,14233,8978,65535]},"Visited":{"hex":"#2c3031ff","u16":[11296,12414,12550,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#151617ff","u16":[5282,5735,5886,65535]},"Light":{"hex":"#3b4042ff","u16":[15202,16505,16940,65535]},"Mid":{"hex":"#1f2122ff","u16":[7926,8605,8832,65535]},"Midlight":{"hex":"#313536ff","u16":[12535,13609,13967,65535]},"Shadow":{"hex":"#0f1011ff","u16":[3839,4168,4278,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#ebf0f0ff","u16":[60480,61653,61745,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"Light":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Mid":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Midlight":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"Shadow":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d8edf7ff","u16":[55550,60800,63569,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#dfeaf2ff","u16":[57339,60254,62215,65535]},"Negative":{"hex":"#f7e4e6ff","u16":[63468,58657,59124,65535]},"Neutral":{"hex":"#fae6d4ff","u16":[64273,59033,54466,65535]},"Normal":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"Positive":{"hex":"#d9eee2ff","u16":[55792,61232,58043,65535]},"Visited":{"hex":"#e7e9eaff","u16":[59471,59982,60022,65535]}},"decoration":{"Focus":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Hover":{"hex":"#c3e3f1ff","u16":[49996,58299,61922,65535]}},"foreground":{"Active":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Inactive":{"hex":"#c7cbcbff","u16":[51017,52133,52218,65535]},"Link":{"hex":"#a2c8e0ff","u16":[41595,51411,57571,65535]},"Negative":{"hex":"#eeafb6ff","u16":[61133,45056,46720,65535]},"Neutral":{"hex":"#f9c088ff","u16":[63954,49376,34973,65535]},"Normal":{"hex":"#a8a9a9ff","u16":[43129,43387,43472,65535]},"Positive":{"hex":"#a9d6bcff","u16":[43472,55048,48360,65535]},"Visited":{"hex":"#c7cbcbff","u16":[51017,52133,52218,65535]}},"shade":{"Dark":{"hex":"#8d8d8dff","u16":[36252,36252,36252,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c8c8c8ff","u16":[51382,51382,51382,65535]},"Midlight":{"hex":"#f7f7f7ff","u16":[63513,63513,63513,65535]},"Shadow":{"hex":"#494949ff","u16":[18840,18840,18840,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#ebf0f0ff","u16":[60480,61653,61745,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"Light":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Mid":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Midlight":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"Shadow":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#dee6e7ff","u16":[57140,59044,59270,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#b3babfff","u16":[46008,47826,49030,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#dcdedfff","u16":[56481,57129,57326,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#bec3c4ff","u16":[48948,50221,50466,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a2ff","u16":[41059,41475,41720,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#bec3c4ff","u16":[48948,50221,50466,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#bdc3c7ff","u16":[48573,50115,51143,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#dee6e7ff","u16":[57140,59044,59270,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232627ff","u16":[8995,9766,10023,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#7f8c8dff","u16":[32639,35980,36237,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}}}},"sha256":"19dcbd1883c03d2d9e0176a39e3ec9df6c18d6e22f0474e6c886aa20aff76508","source":"/usr/share/color-schemes/BreezeClassic.colors"}, +{"contrastF":0.7000000000000001,"frameContrast":0.2,"input":{"ColorEffects:Disabled":{"Color":"56,56,56","ColorAmount":"0","ColorEffect":"0","ContrastAmount":"0.65","ContrastEffect":"1","IntensityAmount":"0.1","IntensityEffect":"2"},"ColorEffects:Inactive":{"ChangeSelectionColor":"true","Color":"112,111,110","ColorAmount":"-0.9","ColorEffect":"1","ContrastAmount":"0.25","ContrastEffect":"2","Enable":"true","IntensityAmount":"0","IntensityEffect":"0"},"Colors:Button":{"BackgroundAlternate":"224,223,222","BackgroundNormal":"223,220,217","DecorationFocus":"58,167,221","DecorationHover":"110,214,255","ForegroundActive":"146,76,157","ForegroundInactive":"137,136,135","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"34,31,30","ForegroundPositive":"0,110,40","ForegroundVisited":"100,74,155"},"Colors:Complementary":{"BackgroundAlternate":"196,224,255","BackgroundNormal":"24,21,19","DecorationFocus":"58,167,221","DecorationHover":"110,214,255","ForegroundActive":"255,128,224","ForegroundInactive":"137,136,135","ForegroundLink":"88,172,255","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"231,253,255","ForegroundPositive":"0,110,40","ForegroundVisited":"150,111,232"},"Colors:Selection":{"BackgroundAlternate":"62,138,204","BackgroundNormal":"67,172,232","DecorationFocus":"58,167,221","DecorationHover":"110,214,255","ForegroundActive":"108,36,119","ForegroundInactive":"199,226,248","ForegroundLink":"0,49,110","ForegroundNegative":"156,14,14","ForegroundNeutral":"255,221,0","ForegroundNormal":"255,255,255","ForegroundPositive":"128,255,128","ForegroundVisited":"69,40,134"},"Colors:Tooltip":{"BackgroundAlternate":"196,224,255","BackgroundNormal":"24,21,19","DecorationFocus":"58,167,221","DecorationHover":"110,214,255","ForegroundActive":"255,128,224","ForegroundInactive":"137,136,135","ForegroundLink":"88,172,255","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"231,253,255","ForegroundPositive":"0,110,40","ForegroundVisited":"150,111,232"},"Colors:View":{"BackgroundAlternate":"248,247,246","BackgroundNormal":"255,255,255","DecorationFocus":"58,167,221","DecorationHover":"110,214,255","ForegroundActive":"146,76,157","ForegroundInactive":"137,136,135","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"31,28,27","ForegroundPositive":"0,110,40","ForegroundVisited":"100,74,155"},"Colors:Window":{"BackgroundAlternate":"218,217,216","BackgroundNormal":"214,210,208","DecorationFocus":"58,167,221","DecorationHover":"110,214,255","ForegroundActive":"146,76,157","ForegroundInactive":"137,136,135","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"34,31,30","ForegroundPositive":"0,110,40","ForegroundVisited":"100,74,155"},"General":{"ColorScheme":"Oxygen","Name":"Oxygen","shadeSortColumn":"true"},"KDE":{"contrast":"7"},"WM":{"activeBackground":"48,174,232","activeForeground":"255,255,255","inactiveBackground":"224,223,222","inactiveForeground":"75,71,67"}},"name":"Oxygen","origin":"installed","palette":{"Active":{"Accent":{"hex":"#43ace8ff","u16":[17219,44204,59624,65535]},"AlternateBase":{"hex":"#f8f7f6ff","u16":[63736,63479,63222,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#dfdcd9ff","u16":[57311,56540,55769,65535]},"ButtonText":{"hex":"#221f1eff","u16":[8738,7967,7710,65535]},"Dark":{"hex":"#6a6663ff","u16":[27308,26139,25540,65535]},"Highlight":{"hex":"#43ace8ff","u16":[17219,44204,59624,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#fcfcfcff","u16":[64868,64810,64782,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"LinkVisited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]},"Mid":{"hex":"#b3aba7ff","u16":[45927,43961,42954,65535]},"Midlight":{"hex":"#e1deddff","u16":[57863,57141,56783,65535]},"PlaceholderText":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Shadow":{"hex":"#3e3b3aff","u16":[15866,15187,14839,65535]},"Text":{"hex":"#1f1c1bff","u16":[7967,7196,6939,65535]},"ToolTipBase":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"ToolTipText":{"hex":"#e7fdffff","u16":[59367,65021,65535,65535]},"Window":{"hex":"#d6d2d0ff","u16":[54998,53970,53456,65535]},"WindowText":{"hex":"#221f1eff","u16":[8738,7967,7710,65535]}},"Disabled":{"Accent":{"hex":"#cdc8c5ff","u16":[52684,51389,50738,65535]},"AlternateBase":{"hex":"#eeebe9ff","u16":[61105,60453,59795,65535]},"Base":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#d6d2ceff","u16":[54884,53854,52816,65535]},"ButtonText":{"hex":"#969390ff","u16":[38425,37690,37127,65535]},"Dark":{"hex":"#65615fff","u16":[26031,24916,24346,65535]},"Highlight":{"hex":"#cdc8c5ff","u16":[52684,51389,50738,65535]},"HighlightedText":{"hex":"#908c8bff","u16":[36992,36098,35694,65535]},"Light":{"hex":"#f2f1f0ff","u16":[62161,61859,61710,65535]},"Link":{"hex":"#97bcdfff","u16":[38808,48270,57321,65535]},"LinkVisited":{"hex":"#c1b6d7ff","u16":[49480,46689,55271,65535]},"Mid":{"hex":"#aba4a0ff","u16":[44022,42136,41171,65535]},"Midlight":{"hex":"#d9d5d3ff","u16":[55662,54707,54230,65535]},"PlaceholderText":{"hex":"#cccbcbff","u16":[52378,52263,52148,65535]},"Shadow":{"hex":"#3d3a39ff","u16":[15564,14897,14556,65535]},"Text":{"hex":"#a9a7a7ff","u16":[43305,42997,42894,65535]},"ToolTipBase":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"ToolTipText":{"hex":"#e7fdffff","u16":[59367,65021,65535,65535]},"Window":{"hex":"#cdc8c5ff","u16":[52684,51389,50738,65535]},"WindowText":{"hex":"#908c8bff","u16":[36992,36098,35694,65535]}},"Inactive":{"Accent":{"hex":"#77cdffff","u16":[30690,52623,65535,65535]},"AlternateBase":{"hex":"#f9f7f5ff","u16":[63934,63446,62957,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#e1dcd6ff","u16":[57898,56441,54962,65535]},"ButtonText":{"hex":"#302e2cff","u16":[12315,11750,11380,65535]},"Dark":{"hex":"#6d6560ff","u16":[28138,25946,24784,65535]},"Highlight":{"hex":"#77cdffff","u16":[30690,52623,65535,65535]},"HighlightedText":{"hex":"#192e3aff","u16":[6406,11812,14819,65535]},"Light":{"hex":"#fdfcfcff","u16":[64911,64801,64747,65535]},"Link":{"hex":"#196595ff","u16":[6435,25994,38342,65535]},"LinkVisited":{"hex":"#7c3cccff","u16":[31909,15310,52402,65535]},"Mid":{"hex":"#b8aaa2ff","u16":[47322,43637,41682,65535]},"Midlight":{"hex":"#e3dedbff","u16":[58391,57025,56340,65535]},"PlaceholderText":{"hex":"#8e8d8cff","u16":[36462,36267,36073,65535]},"Shadow":{"hex":"#403b38ff","u16":[16348,15075,14399,65535]},"Text":{"hex":"#313030ff","u16":[12590,12306,12211,65535]},"ToolTipBase":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"ToolTipText":{"hex":"#e7fdffff","u16":[59367,65021,65535,65535]},"Window":{"hex":"#d9d1ceff","u16":[55745,53804,52819,65535]},"WindowText":{"hex":"#2f2d2bff","u16":[12120,11458,11173,65535]}}},"set_supported":{"Button":true,"Complementary":true,"Header":false,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#dacadcff","u16":[56000,51927,56485,65535]},"Alternate":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"Link":{"hex":"#b7ceefff","u16":[46978,53038,61457,65535]},"Negative":{"hex":"#ebc5c4ff","u16":[60277,50531,50474,65535]},"Neutral":{"hex":"#f6cf69ff","u16":[63211,53134,26879,65535]},"Normal":{"hex":"#dfdcd9ff","u16":[57311,56540,55769,65535]},"Positive":{"hex":"#7be39eff","u16":[31679,58330,40673,65535]},"Visited":{"hex":"#cfcadbff","u16":[53279,51862,56384,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#924c9dff","u16":[37522,19532,40349,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#221f1eff","u16":[8738,7967,7710,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]}},"shade":{"Dark":{"hex":"#6e6b68ff","u16":[28210,27457,26691,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b8b3aeff","u16":[47169,45910,44630,65535]},"Midlight":{"hex":"#eae8e6ff","u16":[60025,59525,59028,65535]},"Shadow":{"hex":"#3e3c3aff","u16":[15875,15451,15020,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d6c7d4ff","u16":[54920,51222,54444,65535]},"Alternate":{"hex":"#d6d5d3ff","u16":[54968,54616,54263,65535]},"Link":{"hex":"#c2ccd8ff","u16":[49768,52540,55417,65535]},"Negative":{"hex":"#e1c4c2ff","u16":[57922,50378,49971,65535]},"Neutral":{"hex":"#dacba8ff","u16":[55943,52128,43100,65535]},"Normal":{"hex":"#d6d2ceff","u16":[54884,53854,52816,65535]},"Positive":{"hex":"#b9d1bdff","u16":[47589,53835,48626,65535]},"Visited":{"hex":"#cfc9d5ff","u16":[53246,51652,54701,65535]}},"decoration":{"Focus":{"hex":"#9ac1d2ff","u16":[39567,49501,54066,65535]},"Hover":{"hex":"#a5d2e1ff","u16":[42287,53845,57815,65535]}},"foreground":{"Active":{"hex":"#bea0beff","u16":[48883,41138,48869,65535]},"Inactive":{"hex":"#b8b6b3ff","u16":[47257,46693,46130,65535]},"Link":{"hex":"#88a6c2ff","u16":[35026,42548,49966,65535]},"Negative":{"hex":"#d18684ff","u16":[53710,34488,33910,65535]},"Neutral":{"hex":"#c5b386ff","u16":[50601,46008,34555,65535]},"Normal":{"hex":"#969390ff","u16":[38425,37690,37127,65535]},"Positive":{"hex":"#8aad94ff","u16":[35510,44465,37985,65535]},"Visited":{"hex":"#ada0beff","u16":[44390,41161,48807,65535]}},"shade":{"Dark":{"hex":"#696663ff","u16":[26890,26173,25443,65535]},"Light":{"hex":"#fcfbfbff","u16":[64707,64635,64565,65535]},"Mid":{"hex":"#b0aba7ff","u16":[45240,44034,42806,65535]},"Midlight":{"hex":"#e1dedbff","u16":[57749,57023,56298,65535]},"Shadow":{"hex":"#3d3b3aff","u16":[15652,15234,14809,65535]}}},"Inactive":{"background":{"Active":{"hex":"#dbcbdbff","u16":[56358,52122,56218,65535]},"Alternate":{"hex":"#e1dfddff","u16":[57766,57278,56789,65535]},"Link":{"hex":"#aad3edff","u16":[43799,54256,60979,65535]},"Negative":{"hex":"#e9c7c5ff","u16":[59840,51034,50611,65535]},"Neutral":{"hex":"#edd365ff","u16":[60858,54136,26007,65535]},"Normal":{"hex":"#e1dcd6ff","u16":[57898,56441,54962,65535]},"Positive":{"hex":"#7be3b0ff","u16":[31616,58324,45110,65535]},"Visited":{"hex":"#d4c9e1ff","u16":[54497,51716,57853,65535]}},"decoration":{"Focus":{"hex":"#46acd1ff","u16":[18005,44292,53678,65535]},"Hover":{"hex":"#86d8f0ff","u16":[34485,55640,61794,65535]}},"foreground":{"Active":{"hex":"#97539cff","u16":[38916,21245,40110,65535]},"Inactive":{"hex":"#8f8c89ff","u16":[36684,35927,35161,65535]},"Link":{"hex":"#0a6495ff","u16":[2661,25592,38353,65535]},"Negative":{"hex":"#c42119ff","u16":[50282,8598,6378,65535]},"Neutral":{"hex":"#a88900ff","u16":[43074,35307,0,65535]},"Normal":{"hex":"#302e2cff","u16":[12315,11750,11380,65535]},"Positive":{"hex":"#00733fff","u16":[0,29664,16284,65535]},"Visited":{"hex":"#7745b2ff","u16":[30514,17759,45744,65535]}},"shade":{"Dark":{"hex":"#706a65ff","u16":[28775,27359,25875,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbb2a8ff","u16":[48114,45747,43264,65535]},"Midlight":{"hex":"#ebe7e4ff","u16":[60408,59461,58511,65535]},"Shadow":{"hex":"#3f3c39ff","u16":[16193,15396,14561,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#3c2035ff","u16":[15508,8241,13516,65535]},"Alternate":{"hex":"#c4e0ffff","u16":[50372,57568,65535,65535]},"Link":{"hex":"#182a3cff","u16":[6220,10826,15427,65535]},"Negative":{"hex":"#400605ff","u16":[16353,1449,1334,65535]},"Neutral":{"hex":"#2f2304ff","u16":[12000,8917,1016,65535]},"Normal":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"Positive":{"hex":"#052510ff","u16":[1385,9425,4083,65535]},"Visited":{"hex":"#291f3bff","u16":[10514,7954,15288,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#58acffff","u16":[22616,44204,65535,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#e7fdffff","u16":[59367,65021,65535,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#966fe8ff","u16":[38550,28527,59624,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42506,37193,33650,65535]},"Light":{"hex":"#e5dad3ff","u16":[58782,55921,54181,65535]},"Mid":{"hex":"#796a60ff","u16":[31219,27317,24715,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]}}},"Disabled":{"background":{"Active":{"hex":"#281822ff","u16":[10225,6130,8751,65535]},"Alternate":{"hex":"#b0d7ffff","u16":[45226,55185,65535,65535]},"Link":{"hex":"#151e26ff","u16":[5376,7586,9878,65535]},"Negative":{"hex":"#330c0bff","u16":[13174,3130,2847,65535]},"Neutral":{"hex":"#231b09ff","u16":[8933,6923,2284,65535]},"Normal":{"hex":"#171412ff","u16":[5880,5145,4655,65535]},"Positive":{"hex":"#0d1f11ff","u16":[3268,7894,4418,65535]},"Visited":{"hex":"#201928ff","u16":[8148,6422,10407,65535]}},"decoration":{"Focus":{"hex":"#224556ff","u16":[8795,17663,21975,65535]},"Hover":{"hex":"#345461ff","u16":[13254,21693,24890,65535]}},"foreground":{"Active":{"hex":"#643857ff","u16":[25686,14320,22232,65535]},"Inactive":{"hex":"#3d3a39ff","u16":[15568,15005,14601,65535]},"Link":{"hex":"#2c4661ff","u16":[11367,18091,24890,65535]},"Negative":{"hex":"#4f0e0dff","u16":[20199,3601,3283,65535]},"Neutral":{"hex":"#4a380cff","u16":[18912,14320,3026,65535]},"Normal":{"hex":"#5c6161ff","u16":[23629,25037,24890,65535]},"Positive":{"hex":"#0f3219ff","u16":[3822,12776,6455,65535]},"Visited":{"hex":"#413259ff","u16":[16684,12862,22918,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42478,37168,33629,65535]},"Light":{"hex":"#e5dad3ff","u16":[58771,55904,54161,65535]},"Mid":{"hex":"#796a60ff","u16":[31179,27282,24683,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]}}},"Inactive":{"background":{"Active":{"hex":"#3a1f31ff","u16":[15011,7846,12561,65535]},"Alternate":{"hex":"#c4e0ffff","u16":[50372,57568,65535,65535]},"Link":{"hex":"#05293dff","u16":[1354,10646,15691,65535]},"Negative":{"hex":"#3e0804ff","u16":[16042,1988,948,65535]},"Neutral":{"hex":"#2d2203ff","u16":[11613,8729,881,65535]},"Normal":{"hex":"#1a1410ff","u16":[6675,5266,4187,65535]},"Positive":{"hex":"#062411ff","u16":[1510,9298,4427,65535]},"Visited":{"hex":"#291f36ff","u16":[10470,7907,13882,65535]}},"decoration":{"Focus":{"hex":"#009fd0ff","u16":[0,40773,53497,65535]},"Hover":{"hex":"#00c8f3ff","u16":[0,51437,62574,65535]}},"foreground":{"Active":{"hex":"#ef77cdff","u16":[61381,30480,52626,65535]},"Inactive":{"hex":"#87807aff","u16":[34702,32854,31455,65535]},"Link":{"hex":"#00a3fdff","u16":[0,41889,64909,65535]},"Negative":{"hex":"#b70a00ff","u16":[47037,2575,0,65535]},"Neutral":{"hex":"#a47a00ff","u16":[42168,31480,0,65535]},"Normal":{"hex":"#c9e0ddff","u16":[51726,57507,56697,65535]},"Positive":{"hex":"#00692dff","u16":[0,27091,11631,65535]},"Visited":{"hex":"#906cceff","u16":[36973,27725,53044,65535]}},"shade":{"Dark":{"hex":"#b38d70ff","u16":[46002,36292,28856,65535]},"Light":{"hex":"#edd8cbff","u16":[60812,55465,52067,65535]},"Mid":{"hex":"#836852ff","u16":[33787,26655,21194,65535]},"Midlight":{"hex":"#d7a987ff","u16":[55192,43542,34620,65535]},"Shadow":{"hex":"#d7a987ff","u16":[55192,43542,34620,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#d3c1d5ff","u16":[54283,49581,54830,65535]},"Alternate":{"hex":"#dad9d8ff","u16":[56026,55769,55512,65535]},"Link":{"hex":"#abc7edff","u16":[43821,51052,60911,65535]},"Negative":{"hex":"#e8bbbaff","u16":[59547,47942,47895,65535]},"Neutral":{"hex":"#edc662ff","u16":[60858,50846,25195,65535]},"Normal":{"hex":"#d6d2d0ff","u16":[54998,53970,53456,65535]},"Positive":{"hex":"#76da98ff","u16":[30268,56023,38990,65535]},"Visited":{"hex":"#c8c1d5ff","u16":[51330,49683,54833,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#924c9dff","u16":[37522,19532,40349,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#221f1eff","u16":[8738,7967,7710,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]}},"shade":{"Dark":{"hex":"#6a6663ff","u16":[27308,26139,25540,65535]},"Light":{"hex":"#fcfcfcff","u16":[64868,64810,64782,65535]},"Mid":{"hex":"#b3aba7ff","u16":[45927,43961,42954,65535]},"Midlight":{"hex":"#e1deddff","u16":[57863,57141,56783,65535]},"Shadow":{"hex":"#3e3b3aff","u16":[15866,15187,14839,65535]}}},"Disabled":{"background":{"Active":{"hex":"#cebeccff","u16":[52893,48806,52422,65535]},"Alternate":{"hex":"#d0cfcdff","u16":[53484,53148,52811,65535]},"Link":{"hex":"#b9c3cfff","u16":[47572,50207,53262,65535]},"Negative":{"hex":"#dabab9ff","u16":[56114,47920,47637,65535]},"Neutral":{"hex":"#d1c29fff","u16":[53796,49760,40913,65535]},"Normal":{"hex":"#cdc8c5ff","u16":[52684,51389,50738,65535]},"Positive":{"hex":"#b1c8b5ff","u16":[45436,51479,46532,65535]},"Visited":{"hex":"#c7c0cdff","u16":[51120,49276,52701,65535]}},"decoration":{"Focus":{"hex":"#98bacbff","u16":[39050,47759,52071,65535]},"Hover":{"hex":"#a1cbdaff","u16":[41472,52142,56110,65535]}},"foreground":{"Active":{"hex":"#b89ab8ff","u16":[47415,39558,47402,65535]},"Inactive":{"hex":"#b3afaeff","u16":[45885,45088,44642,65535]},"Link":{"hex":"#859fbbff","u16":[34077,40900,48041,65535]},"Negative":{"hex":"#cb807fff","u16":[52196,32912,32529,65535]},"Neutral":{"hex":"#bfad81ff","u16":[49168,44415,33121,65535]},"Normal":{"hex":"#908c8bff","u16":[36992,36098,35694,65535]},"Positive":{"hex":"#85a78eff","u16":[34077,42872,36551,65535]},"Visited":{"hex":"#a79ab8ff","u16":[42959,39571,47337,65535]}},"shade":{"Dark":{"hex":"#65615fff","u16":[26031,24916,24346,65535]},"Light":{"hex":"#f2f1f0ff","u16":[62161,61859,61710,65535]},"Mid":{"hex":"#aba4a0ff","u16":[44022,42136,41171,65535]},"Midlight":{"hex":"#d9d5d3ff","u16":[55662,54707,54230,65535]},"Shadow":{"hex":"#3d3a39ff","u16":[15564,14897,14556,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d4c2d3ff","u16":[54495,49806,54309,65535]},"Alternate":{"hex":"#dbd9d7ff","u16":[56224,55736,55246,65535]},"Link":{"hex":"#95cdeeff","u16":[38241,52592,61074,65535]},"Negative":{"hex":"#e3bdbcff","u16":[58331,48656,48380,65535]},"Neutral":{"hex":"#e4c95fff","u16":[58722,51745,24377,65535]},"Normal":{"hex":"#d9d1ceff","u16":[55745,53804,52819,65535]},"Positive":{"hex":"#76daa9ff","u16":[30296,55957,43398,65535]},"Visited":{"hex":"#cdc1daff","u16":[52576,49481,56119,65535]}},"decoration":{"Focus":{"hex":"#49acd1ff","u16":[18778,44148,53597,65535]},"Hover":{"hex":"#acd3e0ff","u16":[44265,54109,57515,65535]}},"foreground":{"Active":{"hex":"#955499ff","u16":[38305,21586,39425,65535]},"Inactive":{"hex":"#8f8b89ff","u16":[36773,35801,35207,65535]},"Link":{"hex":"#006396ff","u16":[0,25448,38509,65535]},"Negative":{"hex":"#c02723ff","u16":[49320,10047,8987,65535]},"Neutral":{"hex":"#a88900ff","u16":[43137,35187,0,65535]},"Normal":{"hex":"#2f2d2bff","u16":[12120,11458,11173,65535]},"Positive":{"hex":"#00733fff","u16":[0,29551,16251,65535]},"Visited":{"hex":"#7546aeff","u16":[30181,18069,44607,65535]}},"shade":{"Dark":{"hex":"#6d6560ff","u16":[28138,25946,24784,65535]},"Light":{"hex":"#fdfcfcff","u16":[64911,64801,64747,65535]},"Mid":{"hex":"#b8aaa2ff","u16":[47322,43637,41682,65535]},"Midlight":{"hex":"#e3dedbff","u16":[58391,57025,56340,65535]},"Shadow":{"hex":"#403b38ff","u16":[16348,15075,14399,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#9694bdff","u16":[38561,38131,48466,65535]},"Alternate":{"hex":"#3e8accff","u16":[15934,35466,52428,65535]},"Link":{"hex":"#329afbff","u16":[12977,39481,64413,65535]},"Negative":{"hex":"#bb8ba9ff","u16":[48092,35813,43322,65535]},"Neutral":{"hex":"#a0b14fff","u16":[41102,45571,20258,65535]},"Normal":{"hex":"#43ace8ff","u16":[17219,44204,59624,65535]},"Positive":{"hex":"#57bb92ff","u16":[22398,48154,37529,65535]},"Visited":{"hex":"#8894d4ff","u16":[34861,37915,54387,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#6c2477ff","u16":[27756,9252,30583,65535]},"Inactive":{"hex":"#c7e2f8ff","u16":[51143,58082,63736,65535]},"Link":{"hex":"#00316eff","u16":[0,12593,28270,65535]},"Negative":{"hex":"#9c0e0eff","u16":[40092,3598,3598,65535]},"Neutral":{"hex":"#ffdd00ff","u16":[65535,56797,0,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#215471ff","u16":[8379,21509,29012,65535]},"Light":{"hex":"#94ccf8ff","u16":[37938,52318,63666,65535]},"Mid":{"hex":"#3890c2ff","u16":[14427,37035,49955,65535]},"Midlight":{"hex":"#5abaf6ff","u16":[23020,47709,63118,65535]},"Shadow":{"hex":"#153649ff","u16":[5454,14000,18884,65535]}}},"Disabled":{"background":{"Active":{"hex":"#cebeccff","u16":[52893,48806,52422,65535]},"Alternate":{"hex":"#d0cfcdff","u16":[53484,53148,52811,65535]},"Link":{"hex":"#b9c3cfff","u16":[47572,50207,53262,65535]},"Negative":{"hex":"#dabab9ff","u16":[56114,47920,47637,65535]},"Neutral":{"hex":"#d1c29fff","u16":[53796,49760,40913,65535]},"Normal":{"hex":"#cdc8c5ff","u16":[52684,51389,50738,65535]},"Positive":{"hex":"#b1c8b5ff","u16":[45436,51479,46532,65535]},"Visited":{"hex":"#c7c0cdff","u16":[51120,49276,52701,65535]}},"decoration":{"Focus":{"hex":"#98bacbff","u16":[39050,47759,52071,65535]},"Hover":{"hex":"#a1cbdaff","u16":[41472,52142,56110,65535]}},"foreground":{"Active":{"hex":"#b89ab8ff","u16":[47415,39558,47402,65535]},"Inactive":{"hex":"#b3afaeff","u16":[45885,45088,44642,65535]},"Link":{"hex":"#859fbbff","u16":[34077,40900,48041,65535]},"Negative":{"hex":"#cb807fff","u16":[52196,32912,32529,65535]},"Neutral":{"hex":"#bfad81ff","u16":[49168,44415,33121,65535]},"Normal":{"hex":"#908c8bff","u16":[36992,36098,35694,65535]},"Positive":{"hex":"#85a78eff","u16":[34077,42872,36551,65535]},"Visited":{"hex":"#a79ab8ff","u16":[42959,39571,47337,65535]}},"shade":{"Dark":{"hex":"#65615fff","u16":[26031,24916,24346,65535]},"Light":{"hex":"#f2f1f0ff","u16":[62161,61859,61710,65535]},"Mid":{"hex":"#aba4a0ff","u16":[44022,42136,41171,65535]},"Midlight":{"hex":"#d9d5d3ff","u16":[55662,54707,54230,65535]},"Shadow":{"hex":"#3d3a39ff","u16":[15564,14897,14556,65535]}}},"Inactive":{"background":{"Active":{"hex":"#b0b5f3ff","u16":[45318,46412,62556,65535]},"Alternate":{"hex":"#88d1ffff","u16":[34829,53793,65535,65535]},"Link":{"hex":"#6fc0f9ff","u16":[28642,49264,64059,65535]},"Negative":{"hex":"#bdb5c7ff","u16":[48657,46510,51177,65535]},"Neutral":{"hex":"#9bc79eff","u16":[39728,51110,40482,65535]},"Normal":{"hex":"#77cdffff","u16":[30690,52623,65535,65535]},"Positive":{"hex":"#3ecbd5ff","u16":[15905,52052,54787,65535]},"Visited":{"hex":"#a1b7ecff","u16":[41442,47136,60614,65535]}},"decoration":{"Focus":{"hex":"#00ace4ff","u16":[0,44267,58657,65535]},"Hover":{"hex":"#7bd0ffff","u16":[31535,53475,65535,65535]}},"foreground":{"Active":{"hex":"#7550e3ff","u16":[30030,20474,58335,65535]},"Inactive":{"hex":"#6292adff","u16":[25241,37448,44447,65535]},"Link":{"hex":"#006297ff","u16":[0,25172,38914,65535]},"Negative":{"hex":"#9b4767ff","u16":[39899,18333,26514,65535]},"Neutral":{"hex":"#88923dff","u16":[35065,37414,15664,65535]},"Normal":{"hex":"#192e3aff","u16":[6406,11812,14819,65535]},"Positive":{"hex":"#00705eff","u16":[0,28851,24245,65535]},"Visited":{"hex":"#3256c2ff","u16":[12896,21979,49802,65535]}},"shade":{"Dark":{"hex":"#006688ff","u16":[0,26215,35039,65535]},"Light":{"hex":"#d7edffff","u16":[55286,60957,65535,65535]},"Mid":{"hex":"#00ade7ff","u16":[0,44471,59441,65535]},"Midlight":{"hex":"#9cd7ffff","u16":[40208,55336,65535,65535]},"Shadow":{"hex":"#003e53ff","u16":[0,15923,21283,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#3c2035ff","u16":[15508,8241,13516,65535]},"Alternate":{"hex":"#c4e0ffff","u16":[50372,57568,65535,65535]},"Link":{"hex":"#182a3cff","u16":[6220,10826,15427,65535]},"Negative":{"hex":"#400605ff","u16":[16353,1449,1334,65535]},"Neutral":{"hex":"#2f2304ff","u16":[12000,8917,1016,65535]},"Normal":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"Positive":{"hex":"#052510ff","u16":[1385,9425,4083,65535]},"Visited":{"hex":"#291f3bff","u16":[10514,7954,15288,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#58acffff","u16":[22616,44204,65535,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#e7fdffff","u16":[59367,65021,65535,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#966fe8ff","u16":[38550,28527,59624,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42506,37193,33650,65535]},"Light":{"hex":"#e5dad3ff","u16":[58782,55921,54181,65535]},"Mid":{"hex":"#796a60ff","u16":[31219,27317,24715,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]}}},"Disabled":{"background":{"Active":{"hex":"#281822ff","u16":[10225,6130,8751,65535]},"Alternate":{"hex":"#b0d7ffff","u16":[45226,55185,65535,65535]},"Link":{"hex":"#151e26ff","u16":[5376,7586,9878,65535]},"Negative":{"hex":"#330c0bff","u16":[13174,3130,2847,65535]},"Neutral":{"hex":"#231b09ff","u16":[8933,6923,2284,65535]},"Normal":{"hex":"#171412ff","u16":[5880,5145,4655,65535]},"Positive":{"hex":"#0d1f11ff","u16":[3268,7894,4418,65535]},"Visited":{"hex":"#201928ff","u16":[8148,6422,10407,65535]}},"decoration":{"Focus":{"hex":"#224556ff","u16":[8795,17663,21975,65535]},"Hover":{"hex":"#345461ff","u16":[13254,21693,24890,65535]}},"foreground":{"Active":{"hex":"#643857ff","u16":[25686,14320,22232,65535]},"Inactive":{"hex":"#3d3a39ff","u16":[15568,15005,14601,65535]},"Link":{"hex":"#2c4661ff","u16":[11367,18091,24890,65535]},"Negative":{"hex":"#4f0e0dff","u16":[20199,3601,3283,65535]},"Neutral":{"hex":"#4a380cff","u16":[18912,14320,3026,65535]},"Normal":{"hex":"#5c6161ff","u16":[23629,25037,24890,65535]},"Positive":{"hex":"#0f3219ff","u16":[3822,12776,6455,65535]},"Visited":{"hex":"#413259ff","u16":[16684,12862,22918,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42478,37168,33629,65535]},"Light":{"hex":"#e5dad3ff","u16":[58771,55904,54161,65535]},"Mid":{"hex":"#796a60ff","u16":[31179,27282,24683,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]}}},"Inactive":{"background":{"Active":{"hex":"#3a1f31ff","u16":[15011,7846,12561,65535]},"Alternate":{"hex":"#c4e0ffff","u16":[50372,57568,65535,65535]},"Link":{"hex":"#05293dff","u16":[1354,10646,15691,65535]},"Negative":{"hex":"#3e0804ff","u16":[16042,1988,948,65535]},"Neutral":{"hex":"#2d2203ff","u16":[11613,8729,881,65535]},"Normal":{"hex":"#1a1410ff","u16":[6675,5266,4187,65535]},"Positive":{"hex":"#062411ff","u16":[1510,9298,4427,65535]},"Visited":{"hex":"#291f36ff","u16":[10470,7907,13882,65535]}},"decoration":{"Focus":{"hex":"#009fd0ff","u16":[0,40773,53497,65535]},"Hover":{"hex":"#00c8f3ff","u16":[0,51437,62574,65535]}},"foreground":{"Active":{"hex":"#ef77cdff","u16":[61381,30480,52626,65535]},"Inactive":{"hex":"#87807aff","u16":[34702,32854,31455,65535]},"Link":{"hex":"#00a3fdff","u16":[0,41889,64909,65535]},"Negative":{"hex":"#b70a00ff","u16":[47037,2575,0,65535]},"Neutral":{"hex":"#a47a00ff","u16":[42168,31480,0,65535]},"Normal":{"hex":"#c9e0ddff","u16":[51726,57507,56697,65535]},"Positive":{"hex":"#00692dff","u16":[0,27091,11631,65535]},"Visited":{"hex":"#906cceff","u16":[36973,27725,53044,65535]}},"shade":{"Dark":{"hex":"#b38d70ff","u16":[46002,36292,28856,65535]},"Light":{"hex":"#edd8cbff","u16":[60812,55465,52067,65535]},"Mid":{"hex":"#836852ff","u16":[33787,26655,21194,65535]},"Midlight":{"hex":"#d7a987ff","u16":[55192,43542,34620,65535]},"Shadow":{"hex":"#d7a987ff","u16":[55192,43542,34620,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#f0eaf2ff","u16":[61790,60037,62113,65535]},"Alternate":{"hex":"#f8f7f6ff","u16":[63736,63479,63222,65535]},"Link":{"hex":"#dfe9f7ff","u16":[57398,59836,63573,65535]},"Negative":{"hex":"#f6e6e6ff","u16":[63304,59085,59085,65535]},"Neutral":{"hex":"#fcf0dbff","u16":[64670,61790,56227,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#baf8ceff","u16":[47784,63784,52916,65535]},"Visited":{"hex":"#e9e7f0ff","u16":[59959,59310,61613,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#924c9dff","u16":[37522,19532,40349,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#1f1c1bff","u16":[7967,7196,6939,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]}},"shade":{"Dark":{"hex":"#bdbdbdff","u16":[48684,48684,48684,65535]},"Light":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Mid":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Midlight":{"hex":"#ecececff","u16":[60542,60542,60542,65535]},"Shadow":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]}}},"Disabled":{"background":{"Active":{"hex":"#f1e9f2ff","u16":[61821,59832,62140,65535]},"Alternate":{"hex":"#eeebe9ff","u16":[61105,60453,59795,65535]},"Link":{"hex":"#e4ecf5ff","u16":[58470,60620,62873,65535]},"Negative":{"hex":"#f7e7e7ff","u16":[63522,59266,59266,65535]},"Neutral":{"hex":"#f1ebddff","u16":[61989,60497,56760,65535]},"Normal":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"Positive":{"hex":"#e0efe5ff","u16":[57469,61347,58850,65535]},"Visited":{"hex":"#eceaf2ff","u16":[60763,60075,62250,65535]}},"decoration":{"Focus":{"hex":"#a4d8f0ff","u16":[42079,55471,61700,65535]},"Hover":{"hex":"#ace9ffff","u16":[44274,59915,65535,65535]}},"foreground":{"Active":{"hex":"#d3b5d7ff","u16":[54168,46603,55334,65535]},"Inactive":{"hex":"#cccbcbff","u16":[52378,52263,52148,65535]},"Link":{"hex":"#97bcdfff","u16":[38808,48270,57321,65535]},"Negative":{"hex":"#e69b9bff","u16":[59205,39935,39935,65535]},"Neutral":{"hex":"#d9c99eff","u16":[55697,51581,40606,65535]},"Normal":{"hex":"#a9a7a7ff","u16":[43305,42997,42894,65535]},"Positive":{"hex":"#9ec3abff","u16":[40606,50037,44036,65535]},"Visited":{"hex":"#c1b6d7ff","u16":[49480,46689,55271,65535]}},"shade":{"Dark":{"hex":"#767676ff","u16":[30397,30397,30397,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c3c3c3ff","u16":[50064,50064,50064,65535]},"Midlight":{"hex":"#fcfcfcff","u16":[64880,64880,64880,65535]},"Shadow":{"hex":"#3d3d3dff","u16":[15585,15585,15585,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f3eaf4ff","u16":[62499,60260,62819,65535]},"Alternate":{"hex":"#f9f7f5ff","u16":[63934,63446,62957,65535]},"Link":{"hex":"#e0ecf6ff","u16":[57512,60572,63111,65535]},"Negative":{"hex":"#f9e8e8ff","u16":[63890,59498,59498,65535]},"Neutral":{"hex":"#fcf2cfff","u16":[64740,62274,53179,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#bff8dcff","u16":[49038,63773,56592,65535]},"Visited":{"hex":"#ede7f7ff","u16":[61025,59492,63560,65535]}},"decoration":{"Focus":{"hex":"#00afdbff","u16":[0,45047,56338,65535]},"Hover":{"hex":"#66dcffff","u16":[26281,56584,65535,65535]}},"foreground":{"Active":{"hex":"#a448afff","u16":[42114,18572,44897,65535]},"Inactive":{"hex":"#8e8d8cff","u16":[36462,36267,36073,65535]},"Link":{"hex":"#196595ff","u16":[6435,25994,38342,65535]},"Negative":{"hex":"#cd0000ff","u16":[52677,0,0,65535]},"Neutral":{"hex":"#a88b00ff","u16":[43097,35672,0,65535]},"Normal":{"hex":"#313030ff","u16":[12590,12306,12211,65535]},"Positive":{"hex":"#097545ff","u16":[2245,30036,17623,65535]},"Visited":{"hex":"#7c3cccff","u16":[31909,15310,52402,65535]}},"shade":{"Dark":{"hex":"#bdbdbdff","u16":[48684,48684,48684,65535]},"Light":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Mid":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Midlight":{"hex":"#ecececff","u16":[60542,60542,60542,65535]},"Shadow":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#d3c1d5ff","u16":[54283,49581,54830,65535]},"Alternate":{"hex":"#dad9d8ff","u16":[56026,55769,55512,65535]},"Link":{"hex":"#abc7edff","u16":[43821,51052,60911,65535]},"Negative":{"hex":"#e8bbbaff","u16":[59547,47942,47895,65535]},"Neutral":{"hex":"#edc662ff","u16":[60858,50846,25195,65535]},"Normal":{"hex":"#d6d2d0ff","u16":[54998,53970,53456,65535]},"Positive":{"hex":"#76da98ff","u16":[30268,56023,38990,65535]},"Visited":{"hex":"#c8c1d5ff","u16":[51330,49683,54833,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#924c9dff","u16":[37522,19532,40349,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#221f1eff","u16":[8738,7967,7710,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]}},"shade":{"Dark":{"hex":"#6a6663ff","u16":[27308,26139,25540,65535]},"Light":{"hex":"#fcfcfcff","u16":[64868,64810,64782,65535]},"Mid":{"hex":"#b3aba7ff","u16":[45927,43961,42954,65535]},"Midlight":{"hex":"#e1deddff","u16":[57863,57141,56783,65535]},"Shadow":{"hex":"#3e3b3aff","u16":[15866,15187,14839,65535]}}},"Disabled":{"background":{"Active":{"hex":"#cebeccff","u16":[52893,48806,52422,65535]},"Alternate":{"hex":"#d0cfcdff","u16":[53484,53148,52811,65535]},"Link":{"hex":"#b9c3cfff","u16":[47572,50207,53262,65535]},"Negative":{"hex":"#dabab9ff","u16":[56114,47920,47637,65535]},"Neutral":{"hex":"#d1c29fff","u16":[53796,49760,40913,65535]},"Normal":{"hex":"#cdc8c5ff","u16":[52684,51389,50738,65535]},"Positive":{"hex":"#b1c8b5ff","u16":[45436,51479,46532,65535]},"Visited":{"hex":"#c7c0cdff","u16":[51120,49276,52701,65535]}},"decoration":{"Focus":{"hex":"#98bacbff","u16":[39050,47759,52071,65535]},"Hover":{"hex":"#a1cbdaff","u16":[41472,52142,56110,65535]}},"foreground":{"Active":{"hex":"#b89ab8ff","u16":[47415,39558,47402,65535]},"Inactive":{"hex":"#b3afaeff","u16":[45885,45088,44642,65535]},"Link":{"hex":"#859fbbff","u16":[34077,40900,48041,65535]},"Negative":{"hex":"#cb807fff","u16":[52196,32912,32529,65535]},"Neutral":{"hex":"#bfad81ff","u16":[49168,44415,33121,65535]},"Normal":{"hex":"#908c8bff","u16":[36992,36098,35694,65535]},"Positive":{"hex":"#85a78eff","u16":[34077,42872,36551,65535]},"Visited":{"hex":"#a79ab8ff","u16":[42959,39571,47337,65535]}},"shade":{"Dark":{"hex":"#65615fff","u16":[26031,24916,24346,65535]},"Light":{"hex":"#f2f1f0ff","u16":[62161,61859,61710,65535]},"Mid":{"hex":"#aba4a0ff","u16":[44022,42136,41171,65535]},"Midlight":{"hex":"#d9d5d3ff","u16":[55662,54707,54230,65535]},"Shadow":{"hex":"#3d3a39ff","u16":[15564,14897,14556,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d4c2d3ff","u16":[54495,49806,54309,65535]},"Alternate":{"hex":"#dbd9d7ff","u16":[56224,55736,55246,65535]},"Link":{"hex":"#95cdeeff","u16":[38241,52592,61074,65535]},"Negative":{"hex":"#e3bdbcff","u16":[58331,48656,48380,65535]},"Neutral":{"hex":"#e4c95fff","u16":[58722,51745,24377,65535]},"Normal":{"hex":"#d9d1ceff","u16":[55745,53804,52819,65535]},"Positive":{"hex":"#76daa9ff","u16":[30296,55957,43398,65535]},"Visited":{"hex":"#cdc1daff","u16":[52576,49481,56119,65535]}},"decoration":{"Focus":{"hex":"#49acd1ff","u16":[18778,44148,53597,65535]},"Hover":{"hex":"#acd3e0ff","u16":[44265,54109,57515,65535]}},"foreground":{"Active":{"hex":"#955499ff","u16":[38305,21586,39425,65535]},"Inactive":{"hex":"#8f8b89ff","u16":[36773,35801,35207,65535]},"Link":{"hex":"#006396ff","u16":[0,25448,38509,65535]},"Negative":{"hex":"#c02723ff","u16":[49320,10047,8987,65535]},"Neutral":{"hex":"#a88900ff","u16":[43137,35187,0,65535]},"Normal":{"hex":"#2f2d2bff","u16":[12120,11458,11173,65535]},"Positive":{"hex":"#00733fff","u16":[0,29551,16251,65535]},"Visited":{"hex":"#7546aeff","u16":[30181,18069,44607,65535]}},"shade":{"Dark":{"hex":"#6d6560ff","u16":[28138,25946,24784,65535]},"Light":{"hex":"#fdfcfcff","u16":[64911,64801,64747,65535]},"Mid":{"hex":"#b8aaa2ff","u16":[47322,43637,41682,65535]},"Midlight":{"hex":"#e3dedbff","u16":[58391,57025,56340,65535]},"Shadow":{"hex":"#403b38ff","u16":[16348,15075,14399,65535]}}}}},"sha256":"ddae1440fd20100bcff97e08eb79c6f1fb79bbd4e4f7dc406ae724f8f94582a2","source":"/usr/share/color-schemes/Oxygen.colors"}, +{"contrastF":0.7000000000000001,"frameContrast":0.2,"input":{"ColorEffects:Disabled":{"ColorAmount":"0","ColorEffect":"0","ContrastAmount":"0.65","ContrastEffect":"1","IntensityAmount":"0.1","IntensityEffect":"2"},"ColorEffects:Inactive":{"Color":"112,111,110","ColorAmount":"0.025","ColorEffect":"2","ContrastAmount":"0.1","ContrastEffect":"2","IntensityAmount":"0","IntensityEffect":"0"},"Colors:Button":{"BackgroundAlternate":"224,223,222","BackgroundNormal":"232,231,230","DecorationFocus":"43,116,199","DecorationHover":"119,183,255","ForegroundActive":"255,128,224","ForegroundInactive":"136,135,134","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"20,19,18","ForegroundPositive":"0,110,40","ForegroundVisited":"69,40,134"},"Colors:Complementary":{"BackgroundAlternate":"196,224,255","BackgroundNormal":"24,21,19","DecorationFocus":"58,167,221","DecorationHover":"110,214,255","ForegroundActive":"255,128,224","ForegroundInactive":"137,136,135","ForegroundLink":"88,172,255","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"231,253,255","ForegroundPositive":"0,110,40","ForegroundVisited":"150,111,232"},"Colors:Selection":{"BackgroundAlternate":"62,138,204","BackgroundNormal":"65,139,212","DecorationFocus":"43,116,199","DecorationHover":"119,183,255","ForegroundActive":"255,128,224","ForegroundInactive":"165,193,228","ForegroundLink":"0,49,110","ForegroundNegative":"156,14,14","ForegroundNeutral":"255,221,0","ForegroundNormal":"255,255,255","ForegroundPositive":"128,255,128","ForegroundVisited":"69,40,134"},"Colors:Tooltip":{"BackgroundAlternate":"196,224,255","BackgroundNormal":"192,218,255","DecorationFocus":"43,116,199","DecorationHover":"119,183,255","ForegroundActive":"255,128,224","ForegroundInactive":"96,112,128","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"20,19,18","ForegroundPositive":"0,110,40","ForegroundVisited":"69,40,134"},"Colors:View":{"BackgroundAlternate":"248,247,246","BackgroundNormal":"255,255,255","DecorationFocus":"43,116,199","DecorationHover":"119,183,255","ForegroundActive":"255,128,224","ForegroundInactive":"136,135,134","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"20,19,18","ForegroundPositive":"0,110,40","ForegroundVisited":"69,40,134"},"Colors:Window":{"BackgroundAlternate":"218,217,216","BackgroundNormal":"224,223,222","DecorationFocus":"43,116,199","DecorationHover":"119,183,255","ForegroundActive":"255,128,224","ForegroundInactive":"136,135,134","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"20,19,18","ForegroundPositive":"0,110,40","ForegroundVisited":"69,40,134"},"General":{"Name":"Oxygen Cold"},"WM":{"activeBackground":"96,148,207","activeForeground":"255,255,255","inactiveBackground":"224,223,222","inactiveForeground":"20,19,18"}},"name":"OxygenCold","origin":"installed","palette":{"Active":{"Accent":{"hex":"#418bd4ff","u16":[16705,35723,54484,65535]},"AlternateBase":{"hex":"#f8f7f6ff","u16":[63736,63479,63222,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#e8e7e6ff","u16":[59624,59367,59110,65535]},"ButtonText":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Dark":{"hex":"#6e6c6bff","u16":[28142,27865,27587,65535]},"Highlight":{"hex":"#418bd4ff","u16":[16705,35723,54484,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"LinkVisited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]},"Mid":{"hex":"#b7b5b3ff","u16":[46976,46514,46049,65535]},"Midlight":{"hex":"#ebeaeaff","u16":[60378,60217,60056,65535]},"PlaceholderText":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Shadow":{"hex":"#3d3c3cff","u16":[15684,15530,15375,65535]},"Text":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"ToolTipBase":{"hex":"#c0daffff","u16":[49344,56026,65535,65535]},"ToolTipText":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Window":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"WindowText":{"hex":"#141312ff","u16":[5140,4883,4626,65535]}},"Disabled":{"Accent":{"hex":"#d6d5d3ff","u16":[54968,54616,54263,65535]},"AlternateBase":{"hex":"#eeebe9ff","u16":[61105,60453,59795,65535]},"Base":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#dedcdbff","u16":[56956,56571,56185,65535]},"ButtonText":{"hex":"#969595ff","u16":[38659,38414,38169,65535]},"Dark":{"hex":"#686766ff","u16":[26826,26562,26297,65535]},"Highlight":{"hex":"#d6d5d3ff","u16":[54968,54616,54263,65535]},"HighlightedText":{"hex":"#919190ff","u16":[37384,37139,36894,65535]},"Light":{"hex":"#fffffeff","u16":[65412,65409,65405,65535]},"Link":{"hex":"#97bcdfff","u16":[38808,48270,57321,65535]},"LinkVisited":{"hex":"#b6aad0ff","u16":[46802,43774,53459,65535]},"Mid":{"hex":"#afaeacff","u16":[45064,44621,44174,65535]},"Midlight":{"hex":"#e2e1e0ff","u16":[57959,57715,57472,65535]},"PlaceholderText":{"hex":"#cbcbcbff","u16":[52292,52177,52062,65535]},"Shadow":{"hex":"#3c3c3bff","u16":[15489,15337,15184,65535]},"Text":{"hex":"#a5a4a4ff","u16":[42321,42235,42149,65535]},"ToolTipBase":{"hex":"#c0daffff","u16":[49344,56026,65535,65535]},"ToolTipText":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Window":{"hex":"#d6d5d3ff","u16":[54968,54616,54263,65535]},"WindowText":{"hex":"#919190ff","u16":[37384,37139,36894,65535]}},"Inactive":{"Accent":{"hex":"#b4cceaff","u16":[46186,52325,60111,65535]},"AlternateBase":{"hex":"#f8f7f6ff","u16":[63736,63479,63222,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#e8e7e6ff","u16":[59624,59367,59110,65535]},"ButtonText":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Dark":{"hex":"#6e6c6bff","u16":[28142,27865,27587,65535]},"Highlight":{"hex":"#b4cceaff","u16":[46186,52325,60111,65535]},"HighlightedText":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"LinkVisited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]},"Mid":{"hex":"#b7b5b3ff","u16":[46976,46514,46049,65535]},"Midlight":{"hex":"#ebeaeaff","u16":[60378,60217,60056,65535]},"PlaceholderText":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Shadow":{"hex":"#3d3c3cff","u16":[15684,15530,15375,65535]},"Text":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"ToolTipBase":{"hex":"#c0daffff","u16":[49344,56026,65535,65535]},"ToolTipText":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Window":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"WindowText":{"hex":"#141312ff","u16":[5140,4883,4626,65535]}}},"set_supported":{"Button":true,"Complementary":true,"Header":false,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#fad4f0ff","u16":[64372,54562,61658,65535]},"Alternate":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"Link":{"hex":"#c4d7f2ff","u16":[50369,55183,62124,65535]},"Negative":{"hex":"#edd0cfff","u16":[60981,53341,53326,65535]},"Neutral":{"hex":"#f8da95ff","u16":[63609,55900,38195,65535]},"Normal":{"hex":"#e8e7e6ff","u16":[59624,59367,59110,65535]},"Positive":{"hex":"#81eda7ff","u16":[33034,60829,42909,65535]},"Visited":{"hex":"#cfcbdbff","u16":[53245,52132,56394,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#72706fff","u16":[29240,28849,28454,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bdbab8ff","u16":[48557,47908,47252,65535]},"Midlight":{"hex":"#f2f2f1ff","u16":[62241,62101,61962,65535]},"Shadow":{"hex":"#3d3d3cff","u16":[15804,15593,15379,65535]}}},"Disabled":{"background":{"Active":{"hex":"#eecde4ff","u16":[61041,52684,58662,65535]},"Alternate":{"hex":"#d6d5d3ff","u16":[54968,54616,54263,65535]},"Link":{"hex":"#cad6e3ff","u16":[52012,55116,58310,65535]},"Negative":{"hex":"#e8cfceff","u16":[59699,53118,52988,65535]},"Neutral":{"hex":"#e2d5b4ff","u16":[58092,54742,46219,65535]},"Normal":{"hex":"#dedcdbff","u16":[56956,56571,56185,65535]},"Positive":{"hex":"#c1dcc9ff","u16":[49705,56426,51708,65535]},"Visited":{"hex":"#d7d3dfff","u16":[55262,54127,57262,65535]}},"decoration":{"Focus":{"hex":"#99b6d7ff","u16":[39262,46878,55248,65535]},"Hover":{"hex":"#b1cdebff","u16":[45453,52673,60491,65535]}},"foreground":{"Active":{"hex":"#edb6dfff","u16":[60983,46724,57232,65535]},"Inactive":{"hex":"#bdbcbbff","u16":[48606,48359,48113,65535]},"Link":{"hex":"#8aadceff","u16":[35465,44409,53006,65535]},"Negative":{"hex":"#d78d8cff","u16":[55226,36219,36025,65535]},"Neutral":{"hex":"#caba8fff","u16":[52034,47759,36625,65535]},"Normal":{"hex":"#969595ff","u16":[38659,38414,38169,65535]},"Positive":{"hex":"#90b49cff","u16":[36944,46216,40055,65535]},"Visited":{"hex":"#a89cc0ff","u16":[43123,39985,49246,65535]}},"shade":{"Dark":{"hex":"#6c6b6aff","u16":[27873,27500,27123,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b5b3b0ff","u16":[46609,45985,45355,65535]},"Midlight":{"hex":"#e9e8e7ff","u16":[59788,59538,59288,65535]},"Shadow":{"hex":"#3d3c3bff","u16":[15690,15480,15268,65535]}}},"Inactive":{"background":{"Active":{"hex":"#fad4f0ff","u16":[64372,54562,61658,65535]},"Alternate":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"Link":{"hex":"#c4d7f2ff","u16":[50369,55183,62124,65535]},"Negative":{"hex":"#edd0cfff","u16":[60981,53341,53326,65535]},"Neutral":{"hex":"#f8da95ff","u16":[63609,55900,38195,65535]},"Normal":{"hex":"#e8e7e6ff","u16":[59624,59367,59110,65535]},"Positive":{"hex":"#81eda7ff","u16":[33034,60829,42909,65535]},"Visited":{"hex":"#cfcbdbff","u16":[53245,52132,56394,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#72706fff","u16":[29240,28849,28454,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bdbab8ff","u16":[48557,47908,47252,65535]},"Midlight":{"hex":"#f2f2f1ff","u16":[62241,62101,61962,65535]},"Shadow":{"hex":"#3d3d3cff","u16":[15804,15593,15379,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#3c2035ff","u16":[15508,8241,13516,65535]},"Alternate":{"hex":"#c4e0ffff","u16":[50372,57568,65535,65535]},"Link":{"hex":"#182a3cff","u16":[6220,10826,15427,65535]},"Negative":{"hex":"#400605ff","u16":[16353,1449,1334,65535]},"Neutral":{"hex":"#2f2304ff","u16":[12000,8917,1016,65535]},"Normal":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"Positive":{"hex":"#052510ff","u16":[1385,9425,4083,65535]},"Visited":{"hex":"#291f3bff","u16":[10514,7954,15288,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#58acffff","u16":[22616,44204,65535,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#e7fdffff","u16":[59367,65021,65535,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#966fe8ff","u16":[38550,28527,59624,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42506,37193,33650,65535]},"Light":{"hex":"#e5dad3ff","u16":[58782,55921,54181,65535]},"Mid":{"hex":"#796a60ff","u16":[31219,27317,24715,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]}}},"Disabled":{"background":{"Active":{"hex":"#281822ff","u16":[10225,6130,8751,65535]},"Alternate":{"hex":"#b0d7ffff","u16":[45226,55185,65535,65535]},"Link":{"hex":"#151e26ff","u16":[5376,7586,9878,65535]},"Negative":{"hex":"#330c0bff","u16":[13174,3130,2847,65535]},"Neutral":{"hex":"#231b09ff","u16":[8933,6923,2284,65535]},"Normal":{"hex":"#171412ff","u16":[5880,5145,4655,65535]},"Positive":{"hex":"#0d1f11ff","u16":[3268,7894,4418,65535]},"Visited":{"hex":"#201928ff","u16":[8148,6422,10407,65535]}},"decoration":{"Focus":{"hex":"#224556ff","u16":[8795,17663,21975,65535]},"Hover":{"hex":"#345461ff","u16":[13254,21693,24890,65535]}},"foreground":{"Active":{"hex":"#643857ff","u16":[25686,14320,22232,65535]},"Inactive":{"hex":"#3d3a39ff","u16":[15568,15005,14601,65535]},"Link":{"hex":"#2c4661ff","u16":[11367,18091,24890,65535]},"Negative":{"hex":"#4f0e0dff","u16":[20199,3601,3283,65535]},"Neutral":{"hex":"#4a380cff","u16":[18912,14320,3026,65535]},"Normal":{"hex":"#5c6161ff","u16":[23629,25037,24890,65535]},"Positive":{"hex":"#0f3219ff","u16":[3822,12776,6455,65535]},"Visited":{"hex":"#413259ff","u16":[16684,12862,22918,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42478,37168,33629,65535]},"Light":{"hex":"#e5dad3ff","u16":[58771,55904,54161,65535]},"Mid":{"hex":"#796a60ff","u16":[31179,27282,24683,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]}}},"Inactive":{"background":{"Active":{"hex":"#3c2035ff","u16":[15508,8241,13516,65535]},"Alternate":{"hex":"#c4e0ffff","u16":[50372,57568,65535,65535]},"Link":{"hex":"#182a3cff","u16":[6220,10826,15427,65535]},"Negative":{"hex":"#400605ff","u16":[16353,1449,1334,65535]},"Neutral":{"hex":"#2f2304ff","u16":[12000,8917,1016,65535]},"Normal":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"Positive":{"hex":"#052510ff","u16":[1385,9425,4083,65535]},"Visited":{"hex":"#291f3bff","u16":[10514,7954,15288,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#58acffff","u16":[22616,44204,65535,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#e7fdffff","u16":[59367,65021,65535,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#966fe8ff","u16":[38550,28527,59624,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42506,37193,33650,65535]},"Light":{"hex":"#e5dad3ff","u16":[58782,55921,54181,65535]},"Mid":{"hex":"#796a60ff","u16":[31219,27317,24715,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#f8cbecff","u16":[63758,52062,60560,65535]},"Alternate":{"hex":"#dad9d8ff","u16":[56026,55769,55512,65535]},"Link":{"hex":"#bad1f0ff","u16":[47846,53590,61679,65535]},"Negative":{"hex":"#ebc8c7ff","u16":[60341,51284,51266,65535]},"Neutral":{"hex":"#f6d273ff","u16":[63329,53866,29598,65535]},"Normal":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"Positive":{"hex":"#7ce5a1ff","u16":[31836,58980,41473,65535]},"Visited":{"hex":"#cac5d7ff","u16":[51802,50570,55262,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#6e6c6bff","u16":[28142,27865,27587,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b7b5b3ff","u16":[46976,46514,46049,65535]},"Midlight":{"hex":"#ebeaeaff","u16":[60378,60217,60056,65535]},"Shadow":{"hex":"#3d3c3cff","u16":[15684,15530,15375,65535]}}},"Disabled":{"background":{"Active":{"hex":"#e7c5deff","u16":[59485,50631,56991,65535]},"Alternate":{"hex":"#d0cfcdff","u16":[53484,53148,52811,65535]},"Link":{"hex":"#c3cfdcff","u16":[50098,53226,56468,65535]},"Negative":{"hex":"#e2c7c6ff","u16":[58193,51114,50980,65535]},"Neutral":{"hex":"#dbceacff","u16":[56200,52849,44298,65535]},"Normal":{"hex":"#d6d5d3ff","u16":[54968,54616,54263,65535]},"Positive":{"hex":"#bad4c2ff","u16":[47796,54538,49831,65535]},"Visited":{"hex":"#d0cbd8ff","u16":[53420,52204,55585,65535]}},"decoration":{"Focus":{"hex":"#94b1d2ff","u16":[38066,45594,53878,65535]},"Hover":{"hex":"#adc8e6ff","u16":[44338,51380,59026,65535]}},"foreground":{"Active":{"hex":"#e8b1d9ff","u16":[59517,45538,55835,65535]},"Inactive":{"hex":"#b8b7b6ff","u16":[47330,47085,46840,65535]},"Link":{"hex":"#87a8c7ff","u16":[34796,43067,51140,65535]},"Negative":{"hex":"#d28887ff","u16":[53865,34973,34781,65535]},"Neutral":{"hex":"#c6b58aff","u16":[50760,46486,35351,65535]},"Normal":{"hex":"#919190ff","u16":[37384,37139,36894,65535]},"Positive":{"hex":"#8baf97ff","u16":[35669,44942,38781,65535]},"Visited":{"hex":"#a397baff","u16":[41837,38720,47920,65535]}},"shade":{"Dark":{"hex":"#686766ff","u16":[26826,26562,26297,65535]},"Light":{"hex":"#fffffeff","u16":[65412,65409,65405,65535]},"Mid":{"hex":"#afaeacff","u16":[45064,44621,44174,65535]},"Midlight":{"hex":"#e2e1e0ff","u16":[57959,57715,57472,65535]},"Shadow":{"hex":"#3c3c3bff","u16":[15489,15337,15184,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f8cbecff","u16":[63758,52062,60560,65535]},"Alternate":{"hex":"#dad9d8ff","u16":[56026,55769,55512,65535]},"Link":{"hex":"#bad1f0ff","u16":[47846,53590,61679,65535]},"Negative":{"hex":"#ebc8c7ff","u16":[60341,51284,51266,65535]},"Neutral":{"hex":"#f6d273ff","u16":[63329,53866,29598,65535]},"Normal":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"Positive":{"hex":"#7ce5a1ff","u16":[31836,58980,41473,65535]},"Visited":{"hex":"#cac5d7ff","u16":[51802,50570,55262,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#6e6c6bff","u16":[28142,27865,27587,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b7b5b3ff","u16":[46976,46514,46049,65535]},"Midlight":{"hex":"#ebeaeaff","u16":[60378,60217,60056,65535]},"Shadow":{"hex":"#3d3c3cff","u16":[15684,15530,15375,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#b66fd8ff","u16":[46884,28628,55570,65535]},"Alternate":{"hex":"#3e8accff","u16":[15934,35466,52428,65535]},"Link":{"hex":"#267fe3ff","u16":[9667,32637,58389,65535]},"Negative":{"hex":"#b46898ff","u16":[46327,26740,39119,65535]},"Neutral":{"hex":"#89944bff","u16":[35286,38084,19239,65535]},"Normal":{"hex":"#418bd4ff","u16":[16705,35723,54484,65535]},"Positive":{"hex":"#4d9d81ff","u16":[19802,40322,33281,65535]},"Visited":{"hex":"#737ac1ff","u16":[29511,31271,49645,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#a5c1e4ff","u16":[42405,49601,58596,65535]},"Link":{"hex":"#00316eff","u16":[0,12593,28270,65535]},"Negative":{"hex":"#9c0e0eff","u16":[40092,3598,3598,65535]},"Neutral":{"hex":"#ffdd00ff","u16":[65535,56797,0,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#204467ff","u16":[8128,17382,26511,65535]},"Light":{"hex":"#6faaf0ff","u16":[28606,43688,61771,65535]},"Mid":{"hex":"#3776b3ff","u16":[14134,30224,46098,65535]},"Midlight":{"hex":"#4899eaff","u16":[18429,39410,60108,65535]},"Shadow":{"hex":"#152e46ff","u16":[5515,11793,17987,65535]}}},"Disabled":{"background":{"Active":{"hex":"#e7c5deff","u16":[59485,50631,56991,65535]},"Alternate":{"hex":"#d0cfcdff","u16":[53484,53148,52811,65535]},"Link":{"hex":"#c3cfdcff","u16":[50098,53226,56468,65535]},"Negative":{"hex":"#e2c7c6ff","u16":[58193,51114,50980,65535]},"Neutral":{"hex":"#dbceacff","u16":[56200,52849,44298,65535]},"Normal":{"hex":"#d6d5d3ff","u16":[54968,54616,54263,65535]},"Positive":{"hex":"#bad4c2ff","u16":[47796,54538,49831,65535]},"Visited":{"hex":"#d0cbd8ff","u16":[53420,52204,55585,65535]}},"decoration":{"Focus":{"hex":"#94b1d2ff","u16":[38066,45594,53878,65535]},"Hover":{"hex":"#adc8e6ff","u16":[44338,51380,59026,65535]}},"foreground":{"Active":{"hex":"#e8b1d9ff","u16":[59517,45538,55835,65535]},"Inactive":{"hex":"#b8b7b6ff","u16":[47330,47085,46840,65535]},"Link":{"hex":"#87a8c7ff","u16":[34796,43067,51140,65535]},"Negative":{"hex":"#d28887ff","u16":[53865,34973,34781,65535]},"Neutral":{"hex":"#c6b58aff","u16":[50760,46486,35351,65535]},"Normal":{"hex":"#919190ff","u16":[37384,37139,36894,65535]},"Positive":{"hex":"#8baf97ff","u16":[35669,44942,38781,65535]},"Visited":{"hex":"#a397baff","u16":[41837,38720,47920,65535]}},"shade":{"Dark":{"hex":"#686766ff","u16":[26826,26562,26297,65535]},"Light":{"hex":"#fffffeff","u16":[65412,65409,65405,65535]},"Mid":{"hex":"#afaeacff","u16":[45064,44621,44174,65535]},"Midlight":{"hex":"#e2e1e0ff","u16":[57959,57715,57472,65535]},"Shadow":{"hex":"#3c3c3bff","u16":[15489,15337,15184,65535]}}},"Inactive":{"background":{"Active":{"hex":"#ecb0e9ff","u16":[60569,45324,59758,65535]},"Alternate":{"hex":"#acc7e9ff","u16":[44269,51162,59757,65535]},"Link":{"hex":"#9cbff1ff","u16":[40133,48975,61893,65535]},"Negative":{"hex":"#e2b1b5ff","u16":[58074,45533,46564,65535]},"Neutral":{"hex":"#d9c06dff","u16":[55873,49335,27917,65535]},"Normal":{"hex":"#b4cceaff","u16":[46186,52325,60111,65535]},"Positive":{"hex":"#62d2a3ff","u16":[25066,53921,41904,65535]},"Visited":{"hex":"#b7b4cfff","u16":[47031,46171,53298,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#426587ff","u16":[17082,25925,34647,65535]},"Light":{"hex":"#edf2f9ff","u16":[60842,62163,64067,65535]},"Mid":{"hex":"#78aadeff","u16":[30723,43695,57099,65535]},"Midlight":{"hex":"#c6d8efff","u16":[50996,55417,61298,65535]},"Shadow":{"hex":"#283c51ff","u16":[10203,15484,20694,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#f0c3f1ff","u16":[61648,50021,61988,65535]},"Alternate":{"hex":"#c4e0ffff","u16":[50372,57568,65535,65535]},"Link":{"hex":"#b1caf3ff","u16":[45425,51960,62417,65535]},"Negative":{"hex":"#e6c0c4ff","u16":[59230,49458,50386,65535]},"Neutral":{"hex":"#e6cd7bff","u16":[59192,52793,31706,65535]},"Normal":{"hex":"#c0daffff","u16":[49344,56026,65535,65535]},"Positive":{"hex":"#69dfb1ff","u16":[27040,57348,45602,65535]},"Visited":{"hex":"#c2bfd8ff","u16":[49858,49133,55513,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#607080ff","u16":[24672,28784,32896,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#006eacff","u16":[0,28177,44177,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#6eb5ffff","u16":[28330,46546,65535,65535]},"Midlight":{"hex":"#d4e5ffff","u16":[54465,58874,65535,65535]},"Shadow":{"hex":"#003f62ff","u16":[0,16107,25254,65535]}}},"Disabled":{"background":{"Active":{"hex":"#cfc0f7ff","u16":[53223,49406,63364,65535]},"Alternate":{"hex":"#b0d7ffff","u16":[45226,55185,65535,65535]},"Link":{"hex":"#acc9f0ff","u16":[44233,51776,61714,65535]},"Negative":{"hex":"#cfc2d6ff","u16":[53120,49833,55113,65535]},"Neutral":{"hex":"#bfcacaff","u16":[49002,51881,51863,65535]},"Normal":{"hex":"#aed1ffff","u16":[44702,53600,65535,65535]},"Positive":{"hex":"#9bcfe1ff","u16":[39796,53278,57757,65535]},"Visited":{"hex":"#bbc6eaff","u16":[48166,50764,60257,65535]}},"decoration":{"Focus":{"hex":"#7caeeaff","u16":[31853,44824,60047,65535]},"Hover":{"hex":"#93c5ffff","u16":[37878,50633,65535,65535]}},"foreground":{"Active":{"hex":"#d0aff3ff","u16":[53429,44966,62367,65535]},"Inactive":{"hex":"#93adceff","u16":[37681,44421,53008,65535]},"Link":{"hex":"#6da5e1ff","u16":[28046,42344,57721,65535]},"Negative":{"hex":"#bc86a1ff","u16":[48226,34366,41273,65535]},"Neutral":{"hex":"#b2b29eff","u16":[45665,45690,40606,65535]},"Normal":{"hex":"#7e8da4ff","u16":[32289,36343,42149,65535]},"Positive":{"hex":"#77acabff","u16":[30574,44145,44036,65535]},"Visited":{"hex":"#8c94d2ff","u16":[36032,37990,53974,65535]}},"shade":{"Dark":{"hex":"#0069a4ff","u16":[0,26860,42111,65535]},"Light":{"hex":"#f2f7ffff","u16":[62186,63456,65535,65535]},"Mid":{"hex":"#59aeffff","u16":[22896,44841,65535,65535]},"Midlight":{"hex":"#c4dcffff","u16":[50378,56587,65535,65535]},"Shadow":{"hex":"#003e61ff","u16":[0,15841,24836,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f0c3f1ff","u16":[61648,50021,61988,65535]},"Alternate":{"hex":"#c4e0ffff","u16":[50372,57568,65535,65535]},"Link":{"hex":"#b1caf3ff","u16":[45425,51960,62417,65535]},"Negative":{"hex":"#e6c0c4ff","u16":[59230,49458,50386,65535]},"Neutral":{"hex":"#e6cd7bff","u16":[59192,52793,31706,65535]},"Normal":{"hex":"#c0daffff","u16":[49344,56026,65535,65535]},"Positive":{"hex":"#69dfb1ff","u16":[27040,57348,45602,65535]},"Visited":{"hex":"#c2bfd8ff","u16":[49858,49133,55513,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#607080ff","u16":[24672,28784,32896,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#006eacff","u16":[0,28177,44177,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#6eb5ffff","u16":[28330,46546,65535,65535]},"Midlight":{"hex":"#d4e5ffff","u16":[54465,58874,65535,65535]},"Shadow":{"hex":"#003f62ff","u16":[0,16107,25254,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#fff1fbff","u16":[65535,62022,64558,65535]},"Alternate":{"hex":"#f8f7f6ff","u16":[63736,63479,63222,65535]},"Link":{"hex":"#dfe9f7ff","u16":[57398,59836,63573,65535]},"Negative":{"hex":"#f6e6e6ff","u16":[63304,59085,59085,65535]},"Neutral":{"hex":"#fcf0dbff","u16":[64670,61790,56227,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#baf8ceff","u16":[47784,63784,52916,65535]},"Visited":{"hex":"#dfdce8ff","u16":[57377,56631,59591,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#bdbdbdff","u16":[48684,48684,48684,65535]},"Light":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Mid":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Midlight":{"hex":"#ecececff","u16":[60542,60542,60542,65535]},"Shadow":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]}}},"Disabled":{"background":{"Active":{"hex":"#fde6f7ff","u16":[65034,59000,63552,65535]},"Alternate":{"hex":"#eeebe9ff","u16":[61105,60453,59795,65535]},"Link":{"hex":"#e4ecf5ff","u16":[58470,60620,62873,65535]},"Negative":{"hex":"#f7e7e7ff","u16":[63522,59266,59266,65535]},"Neutral":{"hex":"#f1ebddff","u16":[61989,60497,56760,65535]},"Normal":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"Positive":{"hex":"#e0efe5ff","u16":[57469,61347,58850,65535]},"Visited":{"hex":"#ece9f2ff","u16":[60578,59920,62095,65535]}},"decoration":{"Focus":{"hex":"#a6c5e8ff","u16":[42545,50744,59666,65535]},"Hover":{"hex":"#bbdcffff","u16":[48178,56606,65535,65535]}},"foreground":{"Active":{"hex":"#ffc3f1ff","u16":[65535,50120,61893,65535]},"Inactive":{"hex":"#cbcbcbff","u16":[52292,52177,52062,65535]},"Link":{"hex":"#97bcdfff","u16":[38808,48270,57321,65535]},"Negative":{"hex":"#e69b9bff","u16":[59205,39935,39935,65535]},"Neutral":{"hex":"#d9c99eff","u16":[55697,51581,40606,65535]},"Normal":{"hex":"#a5a4a4ff","u16":[42321,42235,42149,65535]},"Positive":{"hex":"#9ec3abff","u16":[40606,50037,44036,65535]},"Visited":{"hex":"#b6aad0ff","u16":[46802,43774,53459,65535]}},"shade":{"Dark":{"hex":"#767676ff","u16":[30397,30397,30397,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c3c3c3ff","u16":[50064,50064,50064,65535]},"Midlight":{"hex":"#fcfcfcff","u16":[64880,64880,64880,65535]},"Shadow":{"hex":"#3d3d3dff","u16":[15585,15585,15585,65535]}}},"Inactive":{"background":{"Active":{"hex":"#fff1fbff","u16":[65535,62022,64558,65535]},"Alternate":{"hex":"#f8f7f6ff","u16":[63736,63479,63222,65535]},"Link":{"hex":"#dfe9f7ff","u16":[57398,59836,63573,65535]},"Negative":{"hex":"#f6e6e6ff","u16":[63304,59085,59085,65535]},"Neutral":{"hex":"#fcf0dbff","u16":[64670,61790,56227,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#baf8ceff","u16":[47784,63784,52916,65535]},"Visited":{"hex":"#dfdce8ff","u16":[57377,56631,59591,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#bdbdbdff","u16":[48684,48684,48684,65535]},"Light":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Mid":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Midlight":{"hex":"#ecececff","u16":[60542,60542,60542,65535]},"Shadow":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#f8cbecff","u16":[63758,52062,60560,65535]},"Alternate":{"hex":"#dad9d8ff","u16":[56026,55769,55512,65535]},"Link":{"hex":"#bad1f0ff","u16":[47846,53590,61679,65535]},"Negative":{"hex":"#ebc8c7ff","u16":[60341,51284,51266,65535]},"Neutral":{"hex":"#f6d273ff","u16":[63329,53866,29598,65535]},"Normal":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"Positive":{"hex":"#7ce5a1ff","u16":[31836,58980,41473,65535]},"Visited":{"hex":"#cac5d7ff","u16":[51802,50570,55262,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#6e6c6bff","u16":[28142,27865,27587,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b7b5b3ff","u16":[46976,46514,46049,65535]},"Midlight":{"hex":"#ebeaeaff","u16":[60378,60217,60056,65535]},"Shadow":{"hex":"#3d3c3cff","u16":[15684,15530,15375,65535]}}},"Disabled":{"background":{"Active":{"hex":"#e7c5deff","u16":[59485,50631,56991,65535]},"Alternate":{"hex":"#d0cfcdff","u16":[53484,53148,52811,65535]},"Link":{"hex":"#c3cfdcff","u16":[50098,53226,56468,65535]},"Negative":{"hex":"#e2c7c6ff","u16":[58193,51114,50980,65535]},"Neutral":{"hex":"#dbceacff","u16":[56200,52849,44298,65535]},"Normal":{"hex":"#d6d5d3ff","u16":[54968,54616,54263,65535]},"Positive":{"hex":"#bad4c2ff","u16":[47796,54538,49831,65535]},"Visited":{"hex":"#d0cbd8ff","u16":[53420,52204,55585,65535]}},"decoration":{"Focus":{"hex":"#94b1d2ff","u16":[38066,45594,53878,65535]},"Hover":{"hex":"#adc8e6ff","u16":[44338,51380,59026,65535]}},"foreground":{"Active":{"hex":"#e8b1d9ff","u16":[59517,45538,55835,65535]},"Inactive":{"hex":"#b8b7b6ff","u16":[47330,47085,46840,65535]},"Link":{"hex":"#87a8c7ff","u16":[34796,43067,51140,65535]},"Negative":{"hex":"#d28887ff","u16":[53865,34973,34781,65535]},"Neutral":{"hex":"#c6b58aff","u16":[50760,46486,35351,65535]},"Normal":{"hex":"#919190ff","u16":[37384,37139,36894,65535]},"Positive":{"hex":"#8baf97ff","u16":[35669,44942,38781,65535]},"Visited":{"hex":"#a397baff","u16":[41837,38720,47920,65535]}},"shade":{"Dark":{"hex":"#686766ff","u16":[26826,26562,26297,65535]},"Light":{"hex":"#fffffeff","u16":[65412,65409,65405,65535]},"Mid":{"hex":"#afaeacff","u16":[45064,44621,44174,65535]},"Midlight":{"hex":"#e2e1e0ff","u16":[57959,57715,57472,65535]},"Shadow":{"hex":"#3c3c3bff","u16":[15489,15337,15184,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f8cbecff","u16":[63758,52062,60560,65535]},"Alternate":{"hex":"#dad9d8ff","u16":[56026,55769,55512,65535]},"Link":{"hex":"#bad1f0ff","u16":[47846,53590,61679,65535]},"Negative":{"hex":"#ebc8c7ff","u16":[60341,51284,51266,65535]},"Neutral":{"hex":"#f6d273ff","u16":[63329,53866,29598,65535]},"Normal":{"hex":"#e0dfdeff","u16":[57568,57311,57054,65535]},"Positive":{"hex":"#7ce5a1ff","u16":[31836,58980,41473,65535]},"Visited":{"hex":"#cac5d7ff","u16":[51802,50570,55262,65535]}},"decoration":{"Focus":{"hex":"#2b74c7ff","u16":[11051,29812,51143,65535]},"Hover":{"hex":"#77b7ffff","u16":[30583,47031,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#888786ff","u16":[34952,34695,34438,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#141312ff","u16":[5140,4883,4626,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#6e6c6bff","u16":[28142,27865,27587,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b7b5b3ff","u16":[46976,46514,46049,65535]},"Midlight":{"hex":"#ebeaeaff","u16":[60378,60217,60056,65535]},"Shadow":{"hex":"#3d3c3cff","u16":[15684,15530,15375,65535]}}}}},"sha256":"d4ab861f7e396ddb1dd28ab7d83780c7d2eb3ba9feef832e1ffee7e18383c0ee","source":"/usr/share/color-schemes/OxygenCold.colors"}, +{"contrastF":0.7000000000000001,"frameContrast":0.2,"input":{"ColorEffects:Disabled":{"Color":"56,56,56","ColorAmount":"0","ColorEffect":"0","ContrastAmount":"0.65","ContrastEffect":"1","IntensityAmount":"0.1","IntensityEffect":"2"},"ColorEffects:Inactive":{"ChangeSelectionColor":"true","Color":"112,111,110","ColorAmount":"-0.9","ColorEffect":"1","ContrastAmount":"0.25","ContrastEffect":"2","Enable":"true","IntensityAmount":"0","IntensityEffect":"0"},"Colors:Button":{"BackgroundAlternate":"50,49,48","BackgroundNormal":"57,53,50","DecorationFocus":"255,217,190","DecorationHover":"255,229,208","ForegroundActive":"146,76,157","ForegroundInactive":"137,136,135","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"255,234,225","ForegroundPositive":"0,110,40","ForegroundVisited":"100,74,155"},"Colors:Complementary":{"BackgroundAlternate":"61,55,53","BackgroundNormal":"24,21,19","DecorationFocus":"58,167,221","DecorationHover":"110,214,255","ForegroundActive":"255,128,224","ForegroundInactive":"137,136,135","ForegroundLink":"88,172,255","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"231,253,255","ForegroundPositive":"0,110,40","ForegroundVisited":"150,111,232"},"Colors:Selection":{"BackgroundAlternate":"62,138,204","BackgroundNormal":"247,159,82","DecorationFocus":"240,213,194","DecorationHover":"255,229,208","ForegroundActive":"108,36,119","ForegroundInactive":"58,52,47","ForegroundLink":"0,49,110","ForegroundNegative":"156,14,14","ForegroundNeutral":"255,221,0","ForegroundNormal":"38,36,35","ForegroundPositive":"128,255,128","ForegroundVisited":"69,40,134"},"Colors:Tooltip":{"BackgroundAlternate":"49,45,38","BackgroundNormal":"24,21,19","DecorationFocus":"240,213,194","DecorationHover":"255,229,208","ForegroundActive":"255,128,224","ForegroundInactive":"137,136,135","ForegroundLink":"88,172,255","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"255,242,228","ForegroundPositive":"0,110,40","ForegroundVisited":"150,111,232"},"Colors:View":{"BackgroundAlternate":"33,31,30","BackgroundNormal":"30,29,29","DecorationFocus":"240,213,194","DecorationHover":"255,229,208","ForegroundActive":"146,76,157","ForegroundInactive":"137,136,135","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"255,230,222","ForegroundPositive":"0,110,40","ForegroundVisited":"100,74,155"},"Colors:Window":{"BackgroundAlternate":"31,31,31","BackgroundNormal":"38,36,35","DecorationFocus":"240,213,194","DecorationHover":"255,229,208","ForegroundActive":"146,76,157","ForegroundInactive":"137,136,135","ForegroundLink":"0,87,174","ForegroundNegative":"191,3,3","ForegroundNeutral":"176,128,0","ForegroundNormal":"255,232,223","ForegroundPositive":"0,110,40","ForegroundVisited":"100,74,155"},"General":{"Name":"Oxygen Dark","TintFactor":"0.15","shadeSortColumn":"true"},"KDE":{"contrast":"7"},"WM":{"activeBackground":"56,51,49","activeBlend":"0,0,0","activeForeground":"59,57,55","inactiveBackground":"24,23,23","inactiveForeground":"218,206,195"}},"name":"OxygenDark","origin":"installed","palette":{"Active":{"Accent":{"hex":"#f79f52ff","u16":[63479,40863,21074,65535]},"AlternateBase":{"hex":"#211f1eff","u16":[8481,7967,7710,65535]},"Base":{"hex":"#1e1d1dff","u16":[7710,7453,7453,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#393532ff","u16":[14649,13621,12850,65535]},"ButtonText":{"hex":"#ffeae1ff","u16":[65535,60138,57825,65535]},"Dark":{"hex":"#121211ff","u16":[4752,4502,4377,65535]},"Highlight":{"hex":"#f79f52ff","u16":[63479,40863,21074,65535]},"HighlightedText":{"hex":"#262423ff","u16":[9766,9252,8995,65535]},"Light":{"hex":"#494543ff","u16":[18829,17838,17343,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"LinkVisited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]},"Mid":{"hex":"#211f1eff","u16":[8412,7969,7748,65535]},"Midlight":{"hex":"#3b3836ff","u16":[15088,14294,13897,65535]},"PlaceholderText":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Shadow":{"hex":"#0d0d0cff","u16":[3455,3273,3182,65535]},"Text":{"hex":"#ffe6deff","u16":[65535,59110,57054,65535]},"ToolTipBase":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"ToolTipText":{"hex":"#fff2e4ff","u16":[65535,62194,58596,65535]},"Window":{"hex":"#262423ff","u16":[9766,9252,8995,65535]},"WindowText":{"hex":"#ffe8dfff","u16":[65535,59624,57311,65535]}},"Disabled":{"Accent":{"hex":"#242221ff","u16":[9309,8819,8574,65535]},"AlternateBase":{"hex":"#1f1e1dff","u16":[8084,7594,7349,65535]},"Base":{"hex":"#1d1c1cff","u16":[7349,7104,7104,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#363330ff","u16":[13964,12984,12249,65535]},"ButtonText":{"hex":"#786f6aff","u16":[30941,28504,27254,65535]},"Dark":{"hex":"#121110ff","u16":[4530,4291,4172,65535]},"Highlight":{"hex":"#242221ff","u16":[9309,8819,8574,65535]},"HighlightedText":{"hex":"#6d6460ff","u16":[27916,25625,24695,65535]},"Light":{"hex":"#484442ff","u16":[18539,17563,17075,65535]},"Link":{"hex":"#132f4cff","u16":[4778,12078,19538,65535]},"LinkVisited":{"hex":"#342b46ff","u16":[13352,10963,17908,65535]},"Mid":{"hex":"#1f1e1dff","u16":[8019,7597,7386,65535]},"Midlight":{"hex":"#393635ff","u16":[14769,13992,13603,65535]},"PlaceholderText":{"hex":"#403f3fff","u16":[16524,16279,16194,65535]},"Shadow":{"hex":"#0d0c0cff","u16":[3295,3121,3034,65535]},"Text":{"hex":"#685f5cff","u16":[26642,24339,23653,65535]},"ToolTipBase":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"ToolTipText":{"hex":"#fff2e4ff","u16":[65535,62194,58596,65535]},"Window":{"hex":"#242221ff","u16":[9309,8819,8574,65535]},"WindowText":{"hex":"#6d6460ff","u16":[27916,25625,24695,65535]}},"Inactive":{"Accent":{"hex":"#5e3900ff","u16":[24163,14680,0,65535]},"AlternateBase":{"hex":"#221f1dff","u16":[8838,7881,7376,65535]},"Base":{"hex":"#1f1d1dff","u16":[7886,7403,7403,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#3c342eff","u16":[15370,13463,11922,65535]},"ButtonText":{"hex":"#e4d9d3ff","u16":[58624,55752,54338,65535]},"Dark":{"hex":"#131110ff","u16":[4927,4460,4216,65535]},"Highlight":{"hex":"#5e3900ff","u16":[24163,14680,0,65535]},"HighlightedText":{"hex":"#f5d7c4ff","u16":[62890,55287,50298,65535]},"Light":{"hex":"#4c4541ff","u16":[19524,17673,16705,65535]},"Link":{"hex":"#005799ff","u16":[0,22341,39358,65535]},"LinkVisited":{"hex":"#6641a2ff","u16":[26248,16727,41666,65535]},"Mid":{"hex":"#221f1dff","u16":[8722,7895,7463,65535]},"Midlight":{"hex":"#3d3734ff","u16":[15645,14161,13386,65535]},"PlaceholderText":{"hex":"#84817fff","u16":[33940,33113,32746,65535]},"Shadow":{"hex":"#0e0d0cff","u16":[3582,3242,3065,65535]},"Text":{"hex":"#dcd1cdff","u16":[56502,53589,52752,65535]},"ToolTipBase":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"ToolTipText":{"hex":"#fff2e4ff","u16":[65535,62194,58596,65535]},"Window":{"hex":"#272422ff","u16":[10126,9166,8664,65535]},"WindowText":{"hex":"#dfd3cfff","u16":[57339,54281,53073,65535]}}},"set_supported":{"Button":true,"Complementary":true,"Header":false,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#4e3051ff","u16":[20109,12347,20715,65535]},"Alternate":{"hex":"#323130ff","u16":[12850,12593,12336,65535]},"Link":{"hex":"#113b65ff","u16":[4470,15186,25981,65535]},"Negative":{"hex":"#731110ff","u16":[29609,4398,4173,65535]},"Neutral":{"hex":"#4c3a0fff","u16":[19451,15023,3801,65535]},"Normal":{"hex":"#393532ff","u16":[14649,13621,12850,65535]},"Positive":{"hex":"#124322ff","u16":[4547,17188,8702,65535]},"Visited":{"hex":"#413359ff","u16":[16690,13216,22802,65535]}},"decoration":{"Focus":{"hex":"#ffd9beff","u16":[65535,55769,48830,65535]},"Hover":{"hex":"#ffe5d0ff","u16":[65535,58853,53456,65535]}},"foreground":{"Active":{"hex":"#924c9dff","u16":[37522,19532,40349,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#ffeae1ff","u16":[65535,60138,57825,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]}},"shade":{"Dark":{"hex":"#1c1a18ff","u16":[7128,6628,6253,65535]},"Light":{"hex":"#58524dff","u16":[22699,21106,19911,65535]},"Mid":{"hex":"#312e2bff","u16":[12600,11716,11053,65535]},"Midlight":{"hex":"#4a4541ff","u16":[19097,17756,16751,65535]},"Shadow":{"hex":"#141312ff","u16":[5155,4794,4522,65535]}}},"Disabled":{"background":{"Active":{"hex":"#433241ff","u16":[17150,12730,16600,65535]},"Alternate":{"hex":"#302f2eff","u16":[12249,12004,11759,65535]},"Link":{"hex":"#25384cff","u16":[9431,14455,19646,65535]},"Negative":{"hex":"#5f2220ff","u16":[24386,8839,8351,65535]},"Neutral":{"hex":"#41361eff","u16":[16643,13899,7772,65535]},"Normal":{"hex":"#363330ff","u16":[13964,12984,12249,65535]},"Positive":{"hex":"#253c2aff","u16":[9412,15408,10677,65535]},"Visited":{"hex":"#3c3344ff","u16":[15429,13205,17584,65535]}},"decoration":{"Focus":{"hex":"#78695eff","u16":[30941,27046,24253,65535]},"Hover":{"hex":"#786d64ff","u16":[30941,28075,25797,65535]}},"foreground":{"Active":{"hex":"#543a53ff","u16":[21596,14956,21424,65535]},"Inactive":{"hex":"#514e4cff","u16":[20823,20101,19538,65535]},"Link":{"hex":"#233e59ff","u16":[9077,15899,22882,65535]},"Negative":{"hex":"#632220ff","u16":[25453,8697,8219,65535]},"Neutral":{"hex":"#5e4c1fff","u16":[24167,19415,7962,65535]},"Normal":{"hex":"#786f6aff","u16":[30941,28504,27254,65535]},"Positive":{"hex":"#23462cff","u16":[9077,17871,11392,65535]},"Visited":{"hex":"#453a53ff","u16":[17651,14785,21252,65535]}},"shade":{"Dark":{"hex":"#1a1917ff","u16":[6795,6318,5960,65535]},"Light":{"hex":"#56504cff","u16":[22129,20576,19411,65535]},"Mid":{"hex":"#2f2b29ff","u16":[12014,11171,10538,65535]},"Midlight":{"hex":"#48433fff","u16":[18509,17210,16236,65535]},"Shadow":{"hex":"#131211ff","u16":[4919,4574,4314,65535]}}},"Inactive":{"background":{"Active":{"hex":"#532d50ff","u16":[21408,11543,20606,65535]},"Alternate":{"hex":"#33312fff","u16":[13044,12560,12064,65535]},"Link":{"hex":"#123d58ff","u16":[4643,15705,22720,65535]},"Negative":{"hex":"#70180eff","u16":[28771,6190,3542,65535]},"Neutral":{"hex":"#4a3b0eff","u16":[18927,15092,3571,65535]},"Normal":{"hex":"#3c342eff","u16":[15370,13463,11922,65535]},"Positive":{"hex":"#124226ff","u16":[4747,17079,9801,65535]},"Visited":{"hex":"#453255ff","u16":[17652,12968,21748,65535]}},"decoration":{"Focus":{"hex":"#e9cab4ff","u16":[59768,51978,46381,65535]},"Hover":{"hex":"#e8d4c5ff","u16":[59506,54497,50559,65535]}},"foreground":{"Active":{"hex":"#974399ff","u16":[38776,17122,39424,65535]},"Inactive":{"hex":"#89827cff","u16":[35219,33367,31853,65535]},"Link":{"hex":"#005992ff","u16":[0,22902,37623,65535]},"Negative":{"hex":"#b81100ff","u16":[47273,4277,0,65535]},"Neutral":{"hex":"#a37e00ff","u16":[41886,32342,0,65535]},"Normal":{"hex":"#e4d9d3ff","u16":[58624,55752,54338,65535]},"Positive":{"hex":"#006a32ff","u16":[0,27325,12940,65535]},"Visited":{"hex":"#674691ff","u16":[26539,18114,37162,65535]}},"shade":{"Dark":{"hex":"#1d1917ff","u16":[7479,6551,5801,65535]},"Light":{"hex":"#5d5148ff","u16":[23816,20861,18473,65535]},"Mid":{"hex":"#332d28ff","u16":[13220,11580,10255,65535]},"Midlight":{"hex":"#4e443cff","u16":[20036,17550,15542,65535]},"Shadow":{"hex":"#151210ff","u16":[5409,4738,4196,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#3c2035ff","u16":[15508,8241,13516,65535]},"Alternate":{"hex":"#3d3735ff","u16":[15677,14135,13621,65535]},"Link":{"hex":"#182a3cff","u16":[6220,10826,15427,65535]},"Negative":{"hex":"#400605ff","u16":[16353,1449,1334,65535]},"Neutral":{"hex":"#2f2304ff","u16":[12000,8917,1016,65535]},"Normal":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"Positive":{"hex":"#052510ff","u16":[1385,9425,4083,65535]},"Visited":{"hex":"#291f3bff","u16":[10514,7954,15288,65535]}},"decoration":{"Focus":{"hex":"#3aa7ddff","u16":[14906,42919,56797,65535]},"Hover":{"hex":"#6ed6ffff","u16":[28270,54998,65535,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#58acffff","u16":[22616,44204,65535,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#e7fdffff","u16":[59367,65021,65535,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#966fe8ff","u16":[38550,28527,59624,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42506,37193,33650,65535]},"Light":{"hex":"#e5dad3ff","u16":[58782,55921,54181,65535]},"Mid":{"hex":"#796a60ff","u16":[31219,27317,24715,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]}}},"Disabled":{"background":{"Active":{"hex":"#281822ff","u16":[10225,6130,8751,65535]},"Alternate":{"hex":"#3a3433ff","u16":[14944,13474,12984,65535]},"Link":{"hex":"#151e26ff","u16":[5376,7586,9878,65535]},"Negative":{"hex":"#330c0bff","u16":[13174,3130,2847,65535]},"Neutral":{"hex":"#231b09ff","u16":[8933,6923,2284,65535]},"Normal":{"hex":"#171412ff","u16":[5880,5145,4655,65535]},"Positive":{"hex":"#0d1f11ff","u16":[3268,7894,4418,65535]},"Visited":{"hex":"#201928ff","u16":[8148,6422,10407,65535]}},"decoration":{"Focus":{"hex":"#224556ff","u16":[8795,17663,21975,65535]},"Hover":{"hex":"#345461ff","u16":[13254,21693,24890,65535]}},"foreground":{"Active":{"hex":"#643857ff","u16":[25686,14320,22232,65535]},"Inactive":{"hex":"#3d3a39ff","u16":[15568,15005,14601,65535]},"Link":{"hex":"#2c4661ff","u16":[11367,18091,24890,65535]},"Negative":{"hex":"#4f0e0dff","u16":[20199,3601,3283,65535]},"Neutral":{"hex":"#4a380cff","u16":[18912,14320,3026,65535]},"Normal":{"hex":"#5c6161ff","u16":[23629,25037,24890,65535]},"Positive":{"hex":"#0f3219ff","u16":[3822,12776,6455,65535]},"Visited":{"hex":"#413259ff","u16":[16684,12862,22918,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42478,37168,33629,65535]},"Light":{"hex":"#e5dad3ff","u16":[58771,55904,54161,65535]},"Mid":{"hex":"#796a60ff","u16":[31179,27282,24683,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]}}},"Inactive":{"background":{"Active":{"hex":"#3a1f31ff","u16":[15011,7846,12561,65535]},"Alternate":{"hex":"#413632ff","u16":[16695,13850,12826,65535]},"Link":{"hex":"#05293dff","u16":[1354,10646,15691,65535]},"Negative":{"hex":"#3e0804ff","u16":[16042,1988,948,65535]},"Neutral":{"hex":"#2d2203ff","u16":[11613,8729,881,65535]},"Normal":{"hex":"#1a1410ff","u16":[6675,5266,4187,65535]},"Positive":{"hex":"#062411ff","u16":[1510,9298,4427,65535]},"Visited":{"hex":"#291f36ff","u16":[10470,7907,13882,65535]}},"decoration":{"Focus":{"hex":"#009fd0ff","u16":[0,40773,53497,65535]},"Hover":{"hex":"#00c8f3ff","u16":[0,51437,62574,65535]}},"foreground":{"Active":{"hex":"#ef77cdff","u16":[61381,30480,52626,65535]},"Inactive":{"hex":"#87807aff","u16":[34702,32854,31455,65535]},"Link":{"hex":"#00a3fdff","u16":[0,41889,64909,65535]},"Negative":{"hex":"#b70a00ff","u16":[47037,2575,0,65535]},"Neutral":{"hex":"#a47a00ff","u16":[42168,31480,0,65535]},"Normal":{"hex":"#c9e0ddff","u16":[51726,57507,56697,65535]},"Positive":{"hex":"#00692dff","u16":[0,27091,11631,65535]},"Visited":{"hex":"#906cceff","u16":[36973,27725,53044,65535]}},"shade":{"Dark":{"hex":"#b38d70ff","u16":[46002,36292,28856,65535]},"Light":{"hex":"#edd8cbff","u16":[60812,55465,52067,65535]},"Mid":{"hex":"#836852ff","u16":[33787,26655,21194,65535]},"Midlight":{"hex":"#d7a987ff","u16":[55192,43542,34620,65535]},"Shadow":{"hex":"#d7a987ff","u16":[55192,43542,34620,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#3b233eff","u16":[15082,8974,15812,65535]},"Alternate":{"hex":"#1f1f1fff","u16":[7967,7967,7967,65535]},"Link":{"hex":"#0b2b4cff","u16":[2701,11066,19503,65535]},"Negative":{"hex":"#550b0bff","u16":[21923,2813,2744,65535]},"Neutral":{"hex":"#3a2c09ff","u16":[14977,11427,2237,65535]},"Normal":{"hex":"#262423ff","u16":[9766,9252,8995,65535]},"Positive":{"hex":"#0a3118ff","u16":[2677,12695,6160,65535]},"Visited":{"hex":"#2f2543ff","u16":[12203,9592,17211,65535]}},"decoration":{"Focus":{"hex":"#f0d5c2ff","u16":[61680,54741,49858,65535]},"Hover":{"hex":"#ffe5d0ff","u16":[65535,58853,53456,65535]}},"foreground":{"Active":{"hex":"#924c9dff","u16":[37522,19532,40349,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#ffe8dfff","u16":[65535,59624,57311,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]}},"shade":{"Dark":{"hex":"#121211ff","u16":[4752,4502,4377,65535]},"Light":{"hex":"#494543ff","u16":[18829,17838,17343,65535]},"Mid":{"hex":"#211f1eff","u16":[8412,7969,7748,65535]},"Midlight":{"hex":"#3b3836ff","u16":[15088,14294,13897,65535]},"Shadow":{"hex":"#0d0d0cff","u16":[3455,3273,3182,65535]}}},"Disabled":{"background":{"Active":{"hex":"#312331ff","u16":[12511,8935,12577,65535]},"Alternate":{"hex":"#1e1e1eff","u16":[7594,7594,7594,65535]},"Link":{"hex":"#17283aff","u16":[5914,10346,14935,65535]},"Negative":{"hex":"#451716ff","u16":[17838,5914,5756,65535]},"Neutral":{"hex":"#2f2713ff","u16":[12141,9995,4877,65535]},"Normal":{"hex":"#242221ff","u16":[9309,8819,8574,65535]},"Positive":{"hex":"#172c1dff","u16":[5831,11183,7429,65535]},"Visited":{"hex":"#2b2434ff","u16":[10998,9349,13374,65535]}},"decoration":{"Focus":{"hex":"#685d56ff","u16":[26630,23996,22208,65535]},"Hover":{"hex":"#6d635bff","u16":[27916,25368,23408,65535]}},"foreground":{"Active":{"hex":"#48304aff","u16":[18570,12249,19035,65535]},"Inactive":{"hex":"#454443ff","u16":[17798,17394,17149,65535]},"Link":{"hex":"#183350ff","u16":[6051,13192,20493,65535]},"Negative":{"hex":"#571717ff","u16":[22428,5990,5831,65535]},"Neutral":{"hex":"#524116ff","u16":[21142,16707,5574,65535]},"Normal":{"hex":"#6d6460ff","u16":[27916,25625,24695,65535]},"Positive":{"hex":"#183b23ff","u16":[6051,15164,9003,65535]},"Visited":{"hex":"#392f49ff","u16":[14626,12078,18864,65535]}},"shade":{"Dark":{"hex":"#121110ff","u16":[4530,4291,4172,65535]},"Light":{"hex":"#484442ff","u16":[18539,17563,17075,65535]},"Mid":{"hex":"#1f1e1dff","u16":[8019,7597,7386,65535]},"Midlight":{"hex":"#393635ff","u16":[14769,13992,13603,65535]},"Shadow":{"hex":"#0d0c0cff","u16":[3295,3121,3034,65535]}}},"Inactive":{"background":{"Active":{"hex":"#401e41ff","u16":[16395,7795,16715,65535]},"Alternate":{"hex":"#1f1f1fff","u16":[7967,7967,7967,65535]},"Link":{"hex":"#0b2c44ff","u16":[2811,11351,17554,65535]},"Negative":{"hex":"#540d09ff","u16":[21615,3428,2363,65535]},"Neutral":{"hex":"#382c09ff","u16":[14433,11418,2188,65535]},"Normal":{"hex":"#272422ff","u16":[10126,9166,8664,65535]},"Positive":{"hex":"#0b311cff","u16":[2789,12576,7170,65535]},"Visited":{"hex":"#322345ff","u16":[12886,9100,17764,65535]}},"decoration":{"Focus":{"hex":"#dec2afff","u16":[56953,49895,44934,65535]},"Hover":{"hex":"#e4d0c0ff","u16":[58692,53349,49239,65535]}},"foreground":{"Active":{"hex":"#9a3ca2ff","u16":[39570,15386,41650,65535]},"Inactive":{"hex":"#86817eff","u16":[34394,33173,32376,65535]},"Link":{"hex":"#005897ff","u16":[0,22524,38747,65535]},"Negative":{"hex":"#b80900ff","u16":[47269,2326,0,65535]},"Neutral":{"hex":"#a27c00ff","u16":[41727,31988,0,65535]},"Normal":{"hex":"#dfd3cfff","u16":[57339,54281,53073,65535]},"Positive":{"hex":"#006a32ff","u16":[0,27163,12926,65535]},"Visited":{"hex":"#67439dff","u16":[26390,17164,40244,65535]}},"shade":{"Dark":{"hex":"#131110ff","u16":[4927,4460,4216,65535]},"Light":{"hex":"#4c4541ff","u16":[19524,17673,16705,65535]},"Mid":{"hex":"#221f1dff","u16":[8722,7895,7463,65535]},"Midlight":{"hex":"#3d3734ff","u16":[15645,14161,13386,65535]},"Shadow":{"hex":"#0e0d0cff","u16":[3582,3242,3065,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#d395a6ff","u16":[54117,38403,42666,65535]},"Alternate":{"hex":"#3e8accff","u16":[15934,35466,52428,65535]},"Link":{"hex":"#a3a3a7ff","u16":[41765,41810,42926,65535]},"Negative":{"hex":"#f7897aff","u16":[63525,35249,31405,65535]},"Neutral":{"hex":"#e3b31bff","u16":[58379,45895,6999,65535]},"Normal":{"hex":"#f79f52ff","u16":[63479,40863,21074,65535]},"Positive":{"hex":"#9ac764ff","u16":[39668,51085,25827,65535]},"Visited":{"hex":"#c796b5ff","u16":[51108,38675,46480,65535]}},"decoration":{"Focus":{"hex":"#f0d5c2ff","u16":[61680,54741,49858,65535]},"Hover":{"hex":"#ffe5d0ff","u16":[65535,58853,53456,65535]}},"foreground":{"Active":{"hex":"#6c2477ff","u16":[27756,9252,30583,65535]},"Inactive":{"hex":"#3a342fff","u16":[14906,13364,12079,65535]},"Link":{"hex":"#00316eff","u16":[0,12593,28270,65535]},"Negative":{"hex":"#9c0e0eff","u16":[40092,3598,3598,65535]},"Neutral":{"hex":"#ffdd00ff","u16":[65535,56797,0,65535]},"Normal":{"hex":"#262423ff","u16":[9766,9252,8995,65535]},"Positive":{"hex":"#80ff80ff","u16":[32896,65535,32896,65535]},"Visited":{"hex":"#452886ff","u16":[17733,10280,34438,65535]}},"shade":{"Dark":{"hex":"#7a4d22ff","u16":[31368,19703,8674,65535]},"Light":{"hex":"#facfb6ff","u16":[64373,53192,46823,65535]},"Mid":{"hex":"#d1833aff","u16":[53636,33691,14832,65535]},"Midlight":{"hex":"#f8b17fff","u16":[63785,45509,32701,65535]},"Shadow":{"hex":"#4d3015ff","u16":[19786,12428,5471,65535]}}},"Disabled":{"background":{"Active":{"hex":"#312331ff","u16":[12511,8935,12577,65535]},"Alternate":{"hex":"#1e1e1eff","u16":[7594,7594,7594,65535]},"Link":{"hex":"#17283aff","u16":[5914,10346,14935,65535]},"Negative":{"hex":"#451716ff","u16":[17838,5914,5756,65535]},"Neutral":{"hex":"#2f2713ff","u16":[12141,9995,4877,65535]},"Normal":{"hex":"#242221ff","u16":[9309,8819,8574,65535]},"Positive":{"hex":"#172c1dff","u16":[5831,11183,7429,65535]},"Visited":{"hex":"#2b2434ff","u16":[10998,9349,13374,65535]}},"decoration":{"Focus":{"hex":"#685d56ff","u16":[26630,23996,22208,65535]},"Hover":{"hex":"#6d635bff","u16":[27916,25368,23408,65535]}},"foreground":{"Active":{"hex":"#48304aff","u16":[18570,12249,19035,65535]},"Inactive":{"hex":"#454443ff","u16":[17798,17394,17149,65535]},"Link":{"hex":"#183350ff","u16":[6051,13192,20493,65535]},"Negative":{"hex":"#571717ff","u16":[22428,5990,5831,65535]},"Neutral":{"hex":"#524116ff","u16":[21142,16707,5574,65535]},"Normal":{"hex":"#6d6460ff","u16":[27916,25625,24695,65535]},"Positive":{"hex":"#183b23ff","u16":[6051,15164,9003,65535]},"Visited":{"hex":"#392f49ff","u16":[14626,12078,18864,65535]}},"shade":{"Dark":{"hex":"#121110ff","u16":[4530,4291,4172,65535]},"Light":{"hex":"#484442ff","u16":[18539,17563,17075,65535]},"Mid":{"hex":"#1f1e1dff","u16":[8019,7597,7386,65535]},"Midlight":{"hex":"#393635ff","u16":[14769,13992,13603,65535]},"Shadow":{"hex":"#0d0c0cff","u16":[3295,3121,3034,65535]}}},"Inactive":{"background":{"Active":{"hex":"#72313aff","u16":[29321,12526,15028,65535]},"Alternate":{"hex":"#5a3700ff","u16":[23185,14094,0,65535]},"Link":{"hex":"#174b5eff","u16":[6001,19152,24071,65535]},"Negative":{"hex":"#802600ff","u16":[32838,9713,0,65535]},"Neutral":{"hex":"#614100ff","u16":[24892,16708,0,65535]},"Normal":{"hex":"#5e3900ff","u16":[24163,14680,0,65535]},"Positive":{"hex":"#374c00ff","u16":[14070,19560,0,65535]},"Visited":{"hex":"#663840ff","u16":[26130,14335,16484,65535]}},"decoration":{"Focus":{"hex":"#f6c399ff","u16":[63280,50187,39266,65535]},"Hover":{"hex":"#f8d3b6ff","u16":[63775,54329,46787,65535]}},"foreground":{"Active":{"hex":"#a83d75ff","u16":[43251,15681,30112,65535]},"Inactive":{"hex":"#a57c50ff","u16":[42284,31914,20657,65535]},"Link":{"hex":"#005b8dff","u16":[0,23266,36118,65535]},"Negative":{"hex":"#b22500ff","u16":[45689,9447,0,65535]},"Neutral":{"hex":"#ae7a00ff","u16":[44775,31361,0,65535]},"Normal":{"hex":"#f5d7c4ff","u16":[62890,55287,50298,65535]},"Positive":{"hex":"#2f6900ff","u16":[12122,27048,0,65535]},"Visited":{"hex":"#7e426cff","u16":[32315,17032,27838,65535]}},"shade":{"Dark":{"hex":"#2e1c00ff","u16":[11757,7143,0,65535]},"Light":{"hex":"#865200ff","u16":[34556,20994,0,65535]},"Mid":{"hex":"#513100ff","u16":[20756,12610,0,65535]},"Midlight":{"hex":"#744600ff","u16":[29725,18059,0,65535]},"Shadow":{"hex":"#211400ff","u16":[8461,5140,0,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#3c2035ff","u16":[15508,8241,13516,65535]},"Alternate":{"hex":"#312d26ff","u16":[12593,11565,9766,65535]},"Link":{"hex":"#182a3cff","u16":[6220,10826,15427,65535]},"Negative":{"hex":"#400605ff","u16":[16353,1449,1334,65535]},"Neutral":{"hex":"#2f2304ff","u16":[12000,8917,1016,65535]},"Normal":{"hex":"#181513ff","u16":[6168,5397,4883,65535]},"Positive":{"hex":"#052510ff","u16":[1385,9425,4083,65535]},"Visited":{"hex":"#291f3bff","u16":[10514,7954,15288,65535]}},"decoration":{"Focus":{"hex":"#f0d5c2ff","u16":[61680,54741,49858,65535]},"Hover":{"hex":"#ffe5d0ff","u16":[65535,58853,53456,65535]}},"foreground":{"Active":{"hex":"#ff80e0ff","u16":[65535,32896,57568,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#58acffff","u16":[22616,44204,65535,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#fff2e4ff","u16":[65535,62194,58596,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#966fe8ff","u16":[38550,28527,59624,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42506,37193,33650,65535]},"Light":{"hex":"#e5dad3ff","u16":[58782,55921,54181,65535]},"Mid":{"hex":"#796a60ff","u16":[31219,27317,24715,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50997,44622,40373,65535]}}},"Disabled":{"background":{"Active":{"hex":"#281822ff","u16":[10225,6130,8751,65535]},"Alternate":{"hex":"#2f2b24ff","u16":[12004,11024,9309,65535]},"Link":{"hex":"#151e26ff","u16":[5376,7586,9878,65535]},"Negative":{"hex":"#330c0bff","u16":[13174,3130,2847,65535]},"Neutral":{"hex":"#231b09ff","u16":[8933,6923,2284,65535]},"Normal":{"hex":"#171412ff","u16":[5880,5145,4655,65535]},"Positive":{"hex":"#0d1f11ff","u16":[3268,7894,4418,65535]},"Visited":{"hex":"#201928ff","u16":[8148,6422,10407,65535]}},"decoration":{"Focus":{"hex":"#5f544cff","u16":[24400,21607,19660,65535]},"Hover":{"hex":"#645951ff","u16":[25686,22980,20861,65535]}},"foreground":{"Active":{"hex":"#643857ff","u16":[25686,14320,22232,65535]},"Inactive":{"hex":"#3d3a39ff","u16":[15568,15005,14601,65535]},"Link":{"hex":"#2c4661ff","u16":[11367,18091,24890,65535]},"Negative":{"hex":"#4f0e0dff","u16":[20199,3601,3283,65535]},"Neutral":{"hex":"#4a380cff","u16":[18912,14320,3026,65535]},"Normal":{"hex":"#645e58ff","u16":[25686,24094,22576,65535]},"Positive":{"hex":"#0f3219ff","u16":[3822,12776,6455,65535]},"Visited":{"hex":"#413259ff","u16":[16684,12862,22918,65535]}},"shade":{"Dark":{"hex":"#a59183ff","u16":[42478,37168,33629,65535]},"Light":{"hex":"#e5dad3ff","u16":[58771,55904,54161,65535]},"Mid":{"hex":"#796a60ff","u16":[31179,27282,24683,65535]},"Midlight":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]},"Shadow":{"hex":"#c6ae9dff","u16":[50975,44603,40355,65535]}}},"Inactive":{"background":{"Active":{"hex":"#3a1f31ff","u16":[15011,7846,12561,65535]},"Alternate":{"hex":"#342d1dff","u16":[13354,11463,7543,65535]},"Link":{"hex":"#05293dff","u16":[1354,10646,15691,65535]},"Negative":{"hex":"#3e0804ff","u16":[16042,1988,948,65535]},"Neutral":{"hex":"#2d2203ff","u16":[11613,8729,881,65535]},"Normal":{"hex":"#1a1410ff","u16":[6675,5266,4187,65535]},"Positive":{"hex":"#062411ff","u16":[1510,9298,4427,65535]},"Visited":{"hex":"#291f36ff","u16":[10470,7907,13882,65535]}},"decoration":{"Focus":{"hex":"#e0bea6ff","u16":[57586,48928,42565,65535]},"Hover":{"hex":"#e6ccb7ff","u16":[59041,52324,47011,65535]}},"foreground":{"Active":{"hex":"#ef77cdff","u16":[61381,30480,52626,65535]},"Inactive":{"hex":"#87807aff","u16":[34702,32854,31455,65535]},"Link":{"hex":"#00a3fdff","u16":[0,41889,64909,65535]},"Negative":{"hex":"#b70a00ff","u16":[47037,2575,0,65535]},"Neutral":{"hex":"#a47a00ff","u16":[42168,31480,0,65535]},"Normal":{"hex":"#e4d5c7ff","u16":[58584,54814,51248,65535]},"Positive":{"hex":"#00692dff","u16":[0,27091,11631,65535]},"Visited":{"hex":"#906cceff","u16":[36973,27725,53044,65535]}},"shade":{"Dark":{"hex":"#b38d70ff","u16":[46002,36292,28856,65535]},"Light":{"hex":"#edd8cbff","u16":[60812,55465,52067,65535]},"Mid":{"hex":"#836852ff","u16":[33787,26655,21194,65535]},"Midlight":{"hex":"#d7a987ff","u16":[55192,43542,34620,65535]},"Shadow":{"hex":"#d7a987ff","u16":[55192,43542,34620,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#331e36ff","u16":[13194,7715,13982,65535]},"Alternate":{"hex":"#211f1eff","u16":[8481,7967,7710,65535]},"Link":{"hex":"#082543ff","u16":[1958,9505,17116,65535]},"Negative":{"hex":"#4a0808ff","u16":[19073,2133,2133,65535]},"Neutral":{"hex":"#342707ff","u16":[13364,10141,1701,65535]},"Normal":{"hex":"#1e1d1dff","u16":[7710,7453,7453,65535]},"Positive":{"hex":"#082b14ff","u16":[1933,11017,5196,65535]},"Visited":{"hex":"#29203bff","u16":[10506,8218,15140,65535]}},"decoration":{"Focus":{"hex":"#f0d5c2ff","u16":[61680,54741,49858,65535]},"Hover":{"hex":"#ffe5d0ff","u16":[65535,58853,53456,65535]}},"foreground":{"Active":{"hex":"#924c9dff","u16":[37522,19532,40349,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#ffe6deff","u16":[65535,59110,57054,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]}},"shade":{"Dark":{"hex":"#0f0e0eff","u16":[3752,3627,3627,65535]},"Light":{"hex":"#444141ff","u16":[17381,16802,16802,65535]},"Mid":{"hex":"#1a1919ff","u16":[6644,6422,6422,65535]},"Midlight":{"hex":"#353333ff","u16":[13581,13128,13128,65535]},"Shadow":{"hex":"#0b0a0aff","u16":[2732,2641,2641,65535]}}},"Disabled":{"background":{"Active":{"hex":"#2a1d2bff","u16":[10699,7459,11044,65535]},"Alternate":{"hex":"#1f1e1dff","u16":[8084,7594,7349,65535]},"Link":{"hex":"#112233ff","u16":[4446,8738,13180,65535]},"Negative":{"hex":"#3c1212ff","u16":[15443,4594,4594,65535]},"Neutral":{"hex":"#28210fff","u16":[10385,8490,3788,65535]},"Normal":{"hex":"#1d1c1cff","u16":[7349,7104,7104,65535]},"Positive":{"hex":"#112518ff","u16":[4369,9541,6157,65535]},"Visited":{"hex":"#241f2eff","u16":[9259,7845,11794,65535]}},"decoration":{"Focus":{"hex":"#635953ff","u16":[25356,22882,21252,65535]},"Hover":{"hex":"#685e57ff","u16":[26642,24253,22453,65535]}},"foreground":{"Active":{"hex":"#432b46ff","u16":[17296,11135,18080,65535]},"Inactive":{"hex":"#403f3fff","u16":[16524,16279,16194,65535]},"Link":{"hex":"#132f4cff","u16":[4778,12078,19538,65535]},"Negative":{"hex":"#521313ff","u16":[21154,4875,4875,65535]},"Neutral":{"hex":"#4d3d12ff","u16":[19868,15593,4617,65535]},"Normal":{"hex":"#685f5cff","u16":[26642,24339,23653,65535]},"Positive":{"hex":"#13371fff","u16":[4778,14050,8047,65535]},"Visited":{"hex":"#342b46ff","u16":[13352,10963,17908,65535]}},"shade":{"Dark":{"hex":"#0e0d0dff","u16":[3576,3457,3457,65535]},"Light":{"hex":"#434141ff","u16":[17191,16618,16618,65535]},"Mid":{"hex":"#191818ff","u16":[6333,6122,6122,65535]},"Midlight":{"hex":"#343232ff","u16":[13366,12920,12920,65535]},"Shadow":{"hex":"#0a0a0aff","u16":[2604,2517,2517,65535]}}},"Inactive":{"background":{"Active":{"hex":"#38193bff","u16":[14458,6387,15237,65535]},"Alternate":{"hex":"#221f1dff","u16":[8838,7881,7376,65535]},"Link":{"hex":"#08263dff","u16":[2014,9703,15655,65535]},"Negative":{"hex":"#4a0707ff","u16":[18980,1863,1863,65535]},"Neutral":{"hex":"#322707ff","u16":[12786,10089,1714,65535]},"Normal":{"hex":"#1f1d1dff","u16":[7886,7403,7403,65535]},"Positive":{"hex":"#082a18ff","u16":[1992,10886,6142,65535]},"Visited":{"hex":"#2b1e3fff","u16":[11033,7639,16207,65535]}},"decoration":{"Focus":{"hex":"#dcc1aeff","u16":[56552,49555,44778,65535]},"Hover":{"hex":"#e3cebeff","u16":[58293,52937,48898,65535]}},"foreground":{"Active":{"hex":"#9b38a6ff","u16":[39893,14518,42763,65535]},"Inactive":{"hex":"#84817fff","u16":[33940,33113,32746,65535]},"Link":{"hex":"#005799ff","u16":[0,22341,39358,65535]},"Negative":{"hex":"#b80000ff","u16":[47256,0,0,65535]},"Neutral":{"hex":"#a27c00ff","u16":[41673,31843,0,65535]},"Normal":{"hex":"#dcd1cdff","u16":[56502,53589,52752,65535]},"Positive":{"hex":"#006932ff","u16":[0,27104,12845,65535]},"Visited":{"hex":"#6641a2ff","u16":[26248,16727,41666,65535]}},"shade":{"Dark":{"hex":"#0f0e0eff","u16":[3837,3602,3602,65535]},"Light":{"hex":"#454141ff","u16":[17777,16688,16688,65535]},"Mid":{"hex":"#1a1919ff","u16":[6795,6379,6379,65535]},"Midlight":{"hex":"#363333ff","u16":[13891,13040,13040,65535]},"Shadow":{"hex":"#0b0a0aff","u16":[2794,2622,2622,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#3b233eff","u16":[15082,8974,15812,65535]},"Alternate":{"hex":"#1f1f1fff","u16":[7967,7967,7967,65535]},"Link":{"hex":"#0b2b4cff","u16":[2701,11066,19503,65535]},"Negative":{"hex":"#550b0bff","u16":[21923,2813,2744,65535]},"Neutral":{"hex":"#3a2c09ff","u16":[14977,11427,2237,65535]},"Normal":{"hex":"#262423ff","u16":[9766,9252,8995,65535]},"Positive":{"hex":"#0a3118ff","u16":[2677,12695,6160,65535]},"Visited":{"hex":"#2f2543ff","u16":[12203,9592,17211,65535]}},"decoration":{"Focus":{"hex":"#f0d5c2ff","u16":[61680,54741,49858,65535]},"Hover":{"hex":"#ffe5d0ff","u16":[65535,58853,53456,65535]}},"foreground":{"Active":{"hex":"#924c9dff","u16":[37522,19532,40349,65535]},"Inactive":{"hex":"#898887ff","u16":[35209,34952,34695,65535]},"Link":{"hex":"#0057aeff","u16":[0,22359,44718,65535]},"Negative":{"hex":"#bf0303ff","u16":[49087,771,771,65535]},"Neutral":{"hex":"#b08000ff","u16":[45232,32896,0,65535]},"Normal":{"hex":"#ffe8dfff","u16":[65535,59624,57311,65535]},"Positive":{"hex":"#006e28ff","u16":[0,28270,10280,65535]},"Visited":{"hex":"#644a9bff","u16":[25700,19018,39835,65535]}},"shade":{"Dark":{"hex":"#121211ff","u16":[4752,4502,4377,65535]},"Light":{"hex":"#494543ff","u16":[18829,17838,17343,65535]},"Mid":{"hex":"#211f1eff","u16":[8412,7969,7748,65535]},"Midlight":{"hex":"#3b3836ff","u16":[15088,14294,13897,65535]},"Shadow":{"hex":"#0d0d0cff","u16":[3455,3273,3182,65535]}}},"Disabled":{"background":{"Active":{"hex":"#312331ff","u16":[12511,8935,12577,65535]},"Alternate":{"hex":"#1e1e1eff","u16":[7594,7594,7594,65535]},"Link":{"hex":"#17283aff","u16":[5914,10346,14935,65535]},"Negative":{"hex":"#451716ff","u16":[17838,5914,5756,65535]},"Neutral":{"hex":"#2f2713ff","u16":[12141,9995,4877,65535]},"Normal":{"hex":"#242221ff","u16":[9309,8819,8574,65535]},"Positive":{"hex":"#172c1dff","u16":[5831,11183,7429,65535]},"Visited":{"hex":"#2b2434ff","u16":[10998,9349,13374,65535]}},"decoration":{"Focus":{"hex":"#685d56ff","u16":[26630,23996,22208,65535]},"Hover":{"hex":"#6d635bff","u16":[27916,25368,23408,65535]}},"foreground":{"Active":{"hex":"#48304aff","u16":[18570,12249,19035,65535]},"Inactive":{"hex":"#454443ff","u16":[17798,17394,17149,65535]},"Link":{"hex":"#183350ff","u16":[6051,13192,20493,65535]},"Negative":{"hex":"#571717ff","u16":[22428,5990,5831,65535]},"Neutral":{"hex":"#524116ff","u16":[21142,16707,5574,65535]},"Normal":{"hex":"#6d6460ff","u16":[27916,25625,24695,65535]},"Positive":{"hex":"#183b23ff","u16":[6051,15164,9003,65535]},"Visited":{"hex":"#392f49ff","u16":[14626,12078,18864,65535]}},"shade":{"Dark":{"hex":"#121110ff","u16":[4530,4291,4172,65535]},"Light":{"hex":"#484442ff","u16":[18539,17563,17075,65535]},"Mid":{"hex":"#1f1e1dff","u16":[8019,7597,7386,65535]},"Midlight":{"hex":"#393635ff","u16":[14769,13992,13603,65535]},"Shadow":{"hex":"#0d0c0cff","u16":[3295,3121,3034,65535]}}},"Inactive":{"background":{"Active":{"hex":"#401e41ff","u16":[16395,7795,16715,65535]},"Alternate":{"hex":"#1f1f1fff","u16":[7967,7967,7967,65535]},"Link":{"hex":"#0b2c44ff","u16":[2811,11351,17554,65535]},"Negative":{"hex":"#540d09ff","u16":[21615,3428,2363,65535]},"Neutral":{"hex":"#382c09ff","u16":[14433,11418,2188,65535]},"Normal":{"hex":"#272422ff","u16":[10126,9166,8664,65535]},"Positive":{"hex":"#0b311cff","u16":[2789,12576,7170,65535]},"Visited":{"hex":"#322345ff","u16":[12886,9100,17764,65535]}},"decoration":{"Focus":{"hex":"#dec2afff","u16":[56953,49895,44934,65535]},"Hover":{"hex":"#e4d0c0ff","u16":[58692,53349,49239,65535]}},"foreground":{"Active":{"hex":"#9a3ca2ff","u16":[39570,15386,41650,65535]},"Inactive":{"hex":"#86817eff","u16":[34394,33173,32376,65535]},"Link":{"hex":"#005897ff","u16":[0,22524,38747,65535]},"Negative":{"hex":"#b80900ff","u16":[47269,2326,0,65535]},"Neutral":{"hex":"#a27c00ff","u16":[41727,31988,0,65535]},"Normal":{"hex":"#dfd3cfff","u16":[57339,54281,53073,65535]},"Positive":{"hex":"#006a32ff","u16":[0,27163,12926,65535]},"Visited":{"hex":"#67439dff","u16":[26390,17164,40244,65535]}},"shade":{"Dark":{"hex":"#131110ff","u16":[4927,4460,4216,65535]},"Light":{"hex":"#4c4541ff","u16":[19524,17673,16705,65535]},"Mid":{"hex":"#221f1dff","u16":[8722,7895,7463,65535]},"Midlight":{"hex":"#3d3734ff","u16":[15645,14161,13386,65535]},"Shadow":{"hex":"#0e0d0cff","u16":[3582,3242,3065,65535]}}}}},"sha256":"e52f4df2c05da0e5f10257b9e5daa27a62bec74fdfe34c09f29391c2efe8caeb","source":"/usr/share/color-schemes/OxygenDark.colors"}, +{"contrastF":0.7000000000000001,"frameContrast":0.2,"input":{"Colors:Button":{"BackgroundAlternate":"210,204,190","BackgroundNormal":"236,232,224","DecorationFocus":"200,90,10","DecorationHover":"230,130,50","ForegroundActive":"200,90,10","ForegroundInactive":"110,100,90","ForegroundLink":"10,90,200","ForegroundNegative":"190,20,40","ForegroundNeutral":"200,140,0","ForegroundNormal":"30,25,20","ForegroundPositive":"20,140,60","ForegroundVisited":"120,40,160"},"Colors:Complementary":{"BackgroundAlternate":"30,27,22","BackgroundNormal":"40,36,30","DecorationFocus":"255,160,60","DecorationHover":"255,190,110","ForegroundActive":"255,160,60","ForegroundInactive":"160,150,140","ForegroundLink":"100,170,255","ForegroundNegative":"255,90,100","ForegroundNeutral":"255,200,60","ForegroundNormal":"240,235,225","ForegroundPositive":"90,220,130","ForegroundVisited":"200,140,230"},"Colors:Selection":{"BackgroundAlternate":"170,80,20","BackgroundNormal":"200,90,10","DecorationFocus":"200,90,10","DecorationHover":"230,130,50","ForegroundActive":"255,240,200","ForegroundInactive":"240,220,200","ForegroundLink":"255,230,160","ForegroundNegative":"255,180,180","ForegroundNeutral":"255,230,120","ForegroundNormal":"255,255,255","ForegroundPositive":"180,255,190","ForegroundVisited":"240,200,240"},"Colors:Tooltip":{"BackgroundAlternate":"245,238,200","BackgroundNormal":"255,250,220","DecorationFocus":"200,90,10","DecorationHover":"230,130,50","ForegroundActive":"200,90,10","ForegroundInactive":"100,100,90","ForegroundLink":"10,90,200","ForegroundNegative":"190,20,40","ForegroundNeutral":"200,140,0","ForegroundNormal":"20,20,20","ForegroundPositive":"20,140,60","ForegroundVisited":"120,40,160"},"Colors:View":{"BackgroundAlternate":"240,236,226","BackgroundNormal":"250,248,240","DecorationFocus":"200,90,10","DecorationHover":"230,130,50","ForegroundActive":"200,90,10","ForegroundInactive":"120,110,100","ForegroundLink":"10,90,200","ForegroundNegative":"190,20,40","ForegroundNeutral":"200,140,0","ForegroundNormal":"40,30,20","ForegroundPositive":"20,140,60","ForegroundVisited":"120,40,160"},"Colors:Window":{"BackgroundAlternate":"220,214,202","BackgroundNormal":"230,226,216","DecorationFocus":"200,90,10","DecorationHover":"230,130,50","ForegroundActive":"200,90,10","ForegroundInactive":"110,100,90","ForegroundLink":"10,90,200","ForegroundNegative":"190,20,40","ForegroundNeutral":"200,140,0","ForegroundNormal":"30,25,20","ForegroundPositive":"20,140,60","ForegroundVisited":"120,40,160"},"General":{"ColorScheme":"SyntheticNoHeader","Name":"Synthetic No Header"},"KDE":{"contrast":"7"}},"name":"SyntheticNoHeader","origin":"synthetic","palette":{"Active":{"Accent":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"AlternateBase":{"hex":"#f0ece2ff","u16":[61680,60652,58082,65535]},"Base":{"hex":"#faf8f0ff","u16":[64250,63736,61680,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#ece8e0ff","u16":[60652,59624,57568,65535]},"ButtonText":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Dark":{"hex":"#706e68ff","u16":[28904,28256,26616,65535]},"Highlight":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"LinkVisited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]},"Mid":{"hex":"#bbb7adff","u16":[48156,47076,44344,65535]},"Midlight":{"hex":"#efede7ff","u16":[61547,60927,59404,65535]},"PlaceholderText":{"hex":"#786e64ff","u16":[30840,28270,25700,65535]},"Shadow":{"hex":"#3e3d39ff","u16":[15930,15573,14669,65535]},"Text":{"hex":"#281e14ff","u16":[10280,7710,5140,65535]},"ToolTipBase":{"hex":"#fffadcff","u16":[65535,64250,56540,65535]},"ToolTipText":{"hex":"#141414ff","u16":[5140,5140,5140,65535]},"Window":{"hex":"#e6e2d8ff","u16":[59110,58082,55512,65535]},"WindowText":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]}},"Disabled":{"Accent":{"hex":"#dcd7cbff","u16":[56625,55354,52142,65535]},"AlternateBase":{"hex":"#e7e1d0ff","u16":[59458,57789,53502,65535]},"Base":{"hex":"#f2edd6ff","u16":[62193,60818,55050,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#e3ddd1ff","u16":[58352,56793,53615,65535]},"ButtonText":{"hex":"#9c9891ff","u16":[40152,39087,37384,65535]},"Dark":{"hex":"#6b6963ff","u16":[27553,26935,25372,65535]},"Highlight":{"hex":"#dcd7cbff","u16":[56625,55354,52142,65535]},"HighlightedText":{"hex":"#99948dff","u16":[39197,38131,36111,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#98b8dfff","u16":[39130,47354,57189,65535]},"LinkVisited":{"hex":"#c7a5cfff","u16":[51086,42388,53265,65535]},"Mid":{"hex":"#b4b0a6ff","u16":[46206,45169,42548,65535]},"Midlight":{"hex":"#e7e3daff","u16":[59382,58401,55954,65535]},"PlaceholderText":{"hex":"#c3beb6ff","u16":[50099,48923,46792,65535]},"Shadow":{"hex":"#3d3c38ff","u16":[15762,15408,14514,65535]},"Text":{"hex":"#a8a49bff","u16":[43240,42064,39932,65535]},"ToolTipBase":{"hex":"#fffadcff","u16":[65535,64250,56540,65535]},"ToolTipText":{"hex":"#141414ff","u16":[5140,5140,5140,65535]},"Window":{"hex":"#dcd7cbff","u16":[56625,55354,52142,65535]},"WindowText":{"hex":"#99948dff","u16":[39197,38131,36111,65535]}},"Inactive":{"Accent":{"hex":"#f7bc9fff","u16":[63530,48385,40845,65535]},"AlternateBase":{"hex":"#f0ece2ff","u16":[61680,60652,58082,65535]},"Base":{"hex":"#faf8f0ff","u16":[64250,63736,61680,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#ece8e0ff","u16":[60652,59624,57568,65535]},"ButtonText":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Dark":{"hex":"#706e68ff","u16":[28904,28256,26616,65535]},"Highlight":{"hex":"#f7bc9fff","u16":[63530,48385,40845,65535]},"HighlightedText":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"LinkVisited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]},"Mid":{"hex":"#bbb7adff","u16":[48156,47076,44344,65535]},"Midlight":{"hex":"#efede7ff","u16":[61547,60927,59404,65535]},"PlaceholderText":{"hex":"#786e64ff","u16":[30840,28270,25700,65535]},"Shadow":{"hex":"#3e3d39ff","u16":[15930,15573,14669,65535]},"Text":{"hex":"#281e14ff","u16":[10280,7710,5140,65535]},"ToolTipBase":{"hex":"#fffadcff","u16":[65535,64250,56540,65535]},"ToolTipText":{"hex":"#141414ff","u16":[5140,5140,5140,65535]},"Window":{"hex":"#e6e2d8ff","u16":[59110,58082,55512,65535]},"WindowText":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]}}},"purpose":"no Colors:Header group and no ColorEffects groups: Header falls back, effects take their defaults","set_supported":{"Button":true,"Complementary":true,"Header":false,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#f5d6c5ff","u16":[63036,54908,50667,65535]},"Alternate":{"hex":"#d2ccbeff","u16":[53970,52428,48830,65535]},"Link":{"hex":"#ccd8f0ff","u16":[52406,55511,61737,65535]},"Negative":{"hex":"#edd1d3ff","u16":[60906,53698,54127,65535]},"Neutral":{"hex":"#f9dba5ff","u16":[64039,56343,42302,65535]},"Normal":{"hex":"#ece8e0ff","u16":[60652,59624,57568,65535]},"Positive":{"hex":"#8df1a9ff","u16":[36110,62036,43319,65535]},"Visited":{"hex":"#ddd0e3ff","u16":[56670,53338,58427,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#6e645aff","u16":[28270,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#757169ff","u16":[29979,28975,26899,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c2bbaeff","u16":[49745,48079,44635,65535]},"Midlight":{"hex":"#f5f3eeff","u16":[62892,62350,61284,65535]},"Shadow":{"hex":"#3f3d38ff","u16":[16127,15587,14471,65535]}}},"Disabled":{"background":{"Active":{"hex":"#e7d3c2ff","u16":[59462,54264,49913,65535]},"Alternate":{"hex":"#c8c2b5ff","u16":[51446,49976,46547,65535]},"Link":{"hex":"#ced7e3ff","u16":[52838,55295,58336,65535]},"Negative":{"hex":"#ead0ceff","u16":[60117,53459,53057,65535]},"Neutral":{"hex":"#e8d5acff","u16":[59637,54866,44083,65535]},"Normal":{"hex":"#e3ddd1ff","u16":[58352,56793,53615,65535]},"Positive":{"hex":"#c4dec2ff","u16":[50327,56952,49748,65535]},"Visited":{"hex":"#dfd2deff","u16":[57417,53949,57112,65535]}},"decoration":{"Focus":{"hex":"#d9ad88ff","u16":[55769,44390,34840,65535]},"Hover":{"hex":"#e6b98fff","u16":[59228,47621,36639,65535]}},"foreground":{"Active":{"hex":"#d9ad88ff","u16":[55769,44390,34840,65535]},"Inactive":{"hex":"#b7b1a9ff","u16":[47012,45518,43387,65535]},"Link":{"hex":"#90aed4ff","u16":[37131,44776,54422,65535]},"Negative":{"hex":"#d99395ff","u16":[55768,37862,38401,65535]},"Neutral":{"hex":"#d5be8bff","u16":[54729,48948,35669,65535]},"Normal":{"hex":"#9c9891ff","u16":[40152,39087,37384,65535]},"Positive":{"hex":"#99be9fff","u16":[39295,48948,40814,65535]},"Visited":{"hex":"#be9bc5ff","u16":[48794,39875,50565,65535]}},"shade":{"Dark":{"hex":"#6f6b64ff","u16":[28577,27620,25641,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bab4a7ff","u16":[47753,46153,42847,65535]},"Midlight":{"hex":"#ece9e1ff","u16":[60764,59761,57756,65535]},"Shadow":{"hex":"#3e3c38ff","u16":[16024,15488,14378,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f5d6c5ff","u16":[63036,54908,50667,65535]},"Alternate":{"hex":"#d2ccbeff","u16":[53970,52428,48830,65535]},"Link":{"hex":"#ccd8f0ff","u16":[52406,55511,61737,65535]},"Negative":{"hex":"#edd1d3ff","u16":[60906,53698,54127,65535]},"Neutral":{"hex":"#f9dba5ff","u16":[64039,56343,42302,65535]},"Normal":{"hex":"#ece8e0ff","u16":[60652,59624,57568,65535]},"Positive":{"hex":"#8df1a9ff","u16":[36110,62036,43319,65535]},"Visited":{"hex":"#ddd0e3ff","u16":[56670,53338,58427,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#6e645aff","u16":[28270,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#757169ff","u16":[29979,28975,26899,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c2bbaeff","u16":[49745,48079,44635,65535]},"Midlight":{"hex":"#f5f3eeff","u16":[62892,62350,61284,65535]},"Shadow":{"hex":"#3f3d38ff","u16":[16127,15587,14471,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#462f15ff","u16":[18061,11967,5448,65535]},"Alternate":{"hex":"#1e1b16ff","u16":[7710,6939,5654,65535]},"Link":{"hex":"#223347ff","u16":[8829,13093,18203,65535]},"Negative":{"hex":"#4f2122ff","u16":[20227,8510,8829,65535]},"Neutral":{"hex":"#433514ff","u16":[17108,13676,5175,65535]},"Normal":{"hex":"#28241eff","u16":[10280,9252,7710,65535]},"Positive":{"hex":"#1e3b25ff","u16":[7689,15281,9526,65535]},"Visited":{"hex":"#3c2c41ff","u16":[15365,11238,16705,65535]}},"decoration":{"Focus":{"hex":"#ffa03cff","u16":[65535,41120,15420,65535]},"Hover":{"hex":"#ffbe6eff","u16":[65535,48830,28270,65535]}},"foreground":{"Active":{"hex":"#ffa03cff","u16":[65535,41120,15420,65535]},"Inactive":{"hex":"#a0968cff","u16":[41120,38550,35980,65535]},"Link":{"hex":"#64aaffff","u16":[25700,43690,65535,65535]},"Negative":{"hex":"#ff5a64ff","u16":[65535,23130,25700,65535]},"Neutral":{"hex":"#ffc83cff","u16":[65535,51400,15420,65535]},"Normal":{"hex":"#f0ebe1ff","u16":[61680,60395,57825,65535]},"Positive":{"hex":"#5adc82ff","u16":[23130,56540,33410,65535]},"Visited":{"hex":"#c88ce6ff","u16":[51400,35980,59110,65535]}},"shade":{"Dark":{"hex":"#13120fff","u16":[5002,4502,3752,65535]},"Light":{"hex":"#4d453aff","u16":[19768,17791,14826,65535]},"Mid":{"hex":"#221f1aff","u16":[8855,7969,6641,65535]},"Midlight":{"hex":"#3e372eff","u16":[15848,14263,11886,65535]},"Shadow":{"hex":"#0e0d0bff","u16":[3636,3273,2728,65535]}}},"Disabled":{"background":{"Active":{"hex":"#342617ff","u16":[13419,9815,5791,65535]},"Alternate":{"hex":"#1d1a15ff","u16":[7349,6615,5390,65535]},"Link":{"hex":"#222932ff","u16":[8721,10609,12751,65535]},"Negative":{"hex":"#3b211fff","u16":[15249,8404,7957,65535]},"Neutral":{"hex":"#312815ff","u16":[12548,10408,5470,65535]},"Normal":{"hex":"#26221dff","u16":[9799,8819,7349,65535]},"Positive":{"hex":"#1f2d1fff","u16":[7853,11442,7857,65535]},"Visited":{"hex":"#30252eff","u16":[12220,9612,11939,65535]}},"decoration":{"Focus":{"hex":"#6e4c27ff","u16":[28234,19452,9922,65535]},"Hover":{"hex":"#6e5637ff","u16":[28234,22024,14209,65535]}},"foreground":{"Active":{"hex":"#6e4c27ff","u16":[28234,19452,9922,65535]},"Inactive":{"hex":"#4e4841ff","u16":[20089,18594,16782,65535]},"Link":{"hex":"#3a4f68ff","u16":[14944,20309,26642,65535]},"Negative":{"hex":"#6e3434ff","u16":[28234,13449,13352,65535]},"Neutral":{"hex":"#6e5927ff","u16":[28234,22882,9922,65535]},"Normal":{"hex":"#69655eff","u16":[26948,25882,24069,65535]},"Positive":{"hex":"#37603eff","u16":[14087,24596,15924,65535]},"Visited":{"hex":"#5c455fff","u16":[23518,17737,24498,65535]}},"shade":{"Dark":{"hex":"#13110eff","u16":[4768,4291,3576,65535]},"Light":{"hex":"#4c4439ff","u16":[19461,17514,14595,65535]},"Mid":{"hex":"#211e19ff","u16":[8441,7597,6331,65535]},"Midlight":{"hex":"#3c362dff","u16":[15511,13960,11633,65535]},"Shadow":{"hex":"#0d0c0aff","u16":[3468,3121,2601,65535]}}},"Inactive":{"background":{"Active":{"hex":"#462f15ff","u16":[18061,11967,5448,65535]},"Alternate":{"hex":"#1e1b16ff","u16":[7710,6939,5654,65535]},"Link":{"hex":"#223347ff","u16":[8829,13093,18203,65535]},"Negative":{"hex":"#4f2122ff","u16":[20227,8510,8829,65535]},"Neutral":{"hex":"#433514ff","u16":[17108,13676,5175,65535]},"Normal":{"hex":"#28241eff","u16":[10280,9252,7710,65535]},"Positive":{"hex":"#1e3b25ff","u16":[7689,15281,9526,65535]},"Visited":{"hex":"#3c2c41ff","u16":[15365,11238,16705,65535]}},"decoration":{"Focus":{"hex":"#ffa03cff","u16":[65535,41120,15420,65535]},"Hover":{"hex":"#ffbe6eff","u16":[65535,48830,28270,65535]}},"foreground":{"Active":{"hex":"#ffa03cff","u16":[65535,41120,15420,65535]},"Inactive":{"hex":"#a0968cff","u16":[41120,38550,35980,65535]},"Link":{"hex":"#64aaffff","u16":[25700,43690,65535,65535]},"Negative":{"hex":"#ff5a64ff","u16":[65535,23130,25700,65535]},"Neutral":{"hex":"#ffc83cff","u16":[65535,51400,15420,65535]},"Normal":{"hex":"#f0ebe1ff","u16":[61680,60395,57825,65535]},"Positive":{"hex":"#5adc82ff","u16":[23130,56540,33410,65535]},"Visited":{"hex":"#c88ce6ff","u16":[51400,35980,59110,65535]}},"shade":{"Dark":{"hex":"#13120fff","u16":[5002,4502,3752,65535]},"Light":{"hex":"#4d453aff","u16":[19768,17791,14826,65535]},"Mid":{"hex":"#221f1aff","u16":[8855,7969,6641,65535]},"Midlight":{"hex":"#3e372eff","u16":[15848,14263,11886,65535]},"Shadow":{"hex":"#0e0d0bff","u16":[3636,3273,2728,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#f4cfbbff","u16":[62786,53235,48073,65535]},"Alternate":{"hex":"#dcd6caff","u16":[56540,54998,51914,65535]},"Link":{"hex":"#c5d3efff","u16":[50620,54244,61307,65535]},"Negative":{"hex":"#eacbcdff","u16":[60194,52203,52639,65535]},"Neutral":{"hex":"#f9d58fff","u16":[63904,54710,36739,65535]},"Normal":{"hex":"#e6e2d8ff","u16":[59110,58082,55512,65535]},"Positive":{"hex":"#88eca3ff","u16":[35054,60578,41870,65535]},"Visited":{"hex":"#d9cae0ff","u16":[55688,51964,57566,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#6e645aff","u16":[28270,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#706e68ff","u16":[28904,28256,26616,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbb7adff","u16":[48156,47076,44344,65535]},"Midlight":{"hex":"#efede7ff","u16":[61547,60927,59404,65535]},"Shadow":{"hex":"#3e3d39ff","u16":[15930,15573,14669,65535]}}},"Disabled":{"background":{"Active":{"hex":"#e1cebcff","u16":[57920,52843,48412,65535]},"Alternate":{"hex":"#d2ccc0ff","u16":[53908,52425,49457,65535]},"Link":{"hex":"#c7d2deff","u16":[51216,53883,56944,65535]},"Negative":{"hex":"#e5cac8ff","u16":[58939,51935,51367,65535]},"Neutral":{"hex":"#e3d0a4ff","u16":[58254,53431,42023,65535]},"Normal":{"hex":"#dcd7cbff","u16":[56625,55354,52142,65535]},"Positive":{"hex":"#bed8bbff","u16":[48787,55537,48086,65535]},"Visited":{"hex":"#daccd9ff","u16":[56086,52469,55670,65535]}},"decoration":{"Focus":{"hex":"#d2aa88ff","u16":[53908,43671,35033,65535]},"Hover":{"hex":"#e2b68aff","u16":[58157,46698,35439,65535]}},"foreground":{"Active":{"hex":"#d2aa88ff","u16":[53908,43671,35033,65535]},"Inactive":{"hex":"#b3ada4ff","u16":[46057,44562,42112,65535]},"Link":{"hex":"#8dabceff","u16":[36232,43821,53035,65535]},"Negative":{"hex":"#d59090ff","u16":[54742,36935,37085,65535]},"Neutral":{"hex":"#d1bb86ff","u16":[53774,47992,34396,65535]},"Normal":{"hex":"#99948dff","u16":[39197,38131,36111,65535]},"Positive":{"hex":"#95bb9aff","u16":[38340,47992,39540,65535]},"Visited":{"hex":"#ba98bfff","u16":[47807,38942,49200,65535]}},"shade":{"Dark":{"hex":"#6b6963ff","u16":[27553,26935,25372,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b4b0a6ff","u16":[46206,45169,42548,65535]},"Midlight":{"hex":"#e7e3daff","u16":[59382,58401,55954,65535]},"Shadow":{"hex":"#3d3c38ff","u16":[15762,15408,14514,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f4cfbbff","u16":[62786,53235,48073,65535]},"Alternate":{"hex":"#dcd6caff","u16":[56540,54998,51914,65535]},"Link":{"hex":"#c5d3efff","u16":[50620,54244,61307,65535]},"Negative":{"hex":"#eacbcdff","u16":[60194,52203,52639,65535]},"Neutral":{"hex":"#f9d58fff","u16":[63904,54710,36739,65535]},"Normal":{"hex":"#e6e2d8ff","u16":[59110,58082,55512,65535]},"Positive":{"hex":"#88eca3ff","u16":[35054,60578,41870,65535]},"Visited":{"hex":"#d9cae0ff","u16":[55688,51964,57566,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#6e645aff","u16":[28270,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#706e68ff","u16":[28904,28256,26616,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbb7adff","u16":[48156,47076,44344,65535]},"Midlight":{"hex":"#efede7ff","u16":[61547,60927,59404,65535]},"Shadow":{"hex":"#3e3d39ff","u16":[15930,15573,14669,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#a57a4fff","u16":[42499,31354,20408,65535]},"Alternate":{"hex":"#aa5014ff","u16":[43690,20560,5140,65535]},"Link":{"hex":"#a87942ff","u16":[43290,31005,16889,65535]},"Negative":{"hex":"#ba6d4cff","u16":[47781,27923,19608,65535]},"Neutral":{"hex":"#a97932ff","u16":[43399,31083,12921,65535]},"Normal":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Positive":{"hex":"#8b834eff","u16":[35848,33758,20128,65535]},"Visited":{"hex":"#bb6c59ff","u16":[48177,27749,22775,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#fff0c8ff","u16":[65535,61680,51400,65535]},"Inactive":{"hex":"#f0dcc8ff","u16":[61680,56540,51400,65535]},"Link":{"hex":"#ffe6a0ff","u16":[65535,59110,41120,65535]},"Negative":{"hex":"#ffb4b4ff","u16":[65535,46260,46260,65535]},"Neutral":{"hex":"#ffe678ff","u16":[65535,59110,30840,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#b4ffbeff","u16":[46260,65535,48830,65535]},"Visited":{"hex":"#f0c8f0ff","u16":[61680,51400,61680,65535]}},"shade":{"Dark":{"hex":"#612c05ff","u16":[25011,11255,1251,65535]},"Light":{"hex":"#f9700cff","u16":[64056,28825,3203,65535]},"Mid":{"hex":"#aa4c08ff","u16":[43658,19646,2183,65535]},"Midlight":{"hex":"#df640bff","u16":[57269,25771,2863,65535]},"Shadow":{"hex":"#431e03ff","u16":[17237,7757,862,65535]}}},"Disabled":{"background":{"Active":{"hex":"#e1cebcff","u16":[57920,52843,48412,65535]},"Alternate":{"hex":"#d2ccc0ff","u16":[53908,52425,49457,65535]},"Link":{"hex":"#c7d2deff","u16":[51216,53883,56944,65535]},"Negative":{"hex":"#e5cac8ff","u16":[58939,51935,51367,65535]},"Neutral":{"hex":"#e3d0a4ff","u16":[58254,53431,42023,65535]},"Normal":{"hex":"#dcd7cbff","u16":[56625,55354,52142,65535]},"Positive":{"hex":"#bed8bbff","u16":[48787,55537,48086,65535]},"Visited":{"hex":"#daccd9ff","u16":[56086,52469,55670,65535]}},"decoration":{"Focus":{"hex":"#d2aa88ff","u16":[53908,43671,35033,65535]},"Hover":{"hex":"#e2b68aff","u16":[58157,46698,35439,65535]}},"foreground":{"Active":{"hex":"#d2aa88ff","u16":[53908,43671,35033,65535]},"Inactive":{"hex":"#b3ada4ff","u16":[46057,44562,42112,65535]},"Link":{"hex":"#8dabceff","u16":[36232,43821,53035,65535]},"Negative":{"hex":"#d59090ff","u16":[54742,36935,37085,65535]},"Neutral":{"hex":"#d1bb86ff","u16":[53774,47992,34396,65535]},"Normal":{"hex":"#99948dff","u16":[39197,38131,36111,65535]},"Positive":{"hex":"#95bb9aff","u16":[38340,47992,39540,65535]},"Visited":{"hex":"#ba98bfff","u16":[47807,38942,49200,65535]}},"shade":{"Dark":{"hex":"#6b6963ff","u16":[27553,26935,25372,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b4b0a6ff","u16":[46206,45169,42548,65535]},"Midlight":{"hex":"#e7e3daff","u16":[59382,58401,55954,65535]},"Shadow":{"hex":"#3d3c38ff","u16":[15762,15408,14514,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f6b090ff","u16":[63134,45330,36989,65535]},"Alternate":{"hex":"#f7b08bff","u16":[63416,45335,35740,65535]},"Link":{"hex":"#b2bddbff","u16":[45633,48495,56224,65535]},"Negative":{"hex":"#e6b0b0ff","u16":[59110,45142,45207,65535]},"Neutral":{"hex":"#fab643ff","u16":[64282,46677,17228,65535]},"Normal":{"hex":"#f7bc9fff","u16":[63530,48385,40845,65535]},"Positive":{"hex":"#8fd081ff","u16":[36637,53545,33244,65535]},"Visited":{"hex":"#daadd5ff","u16":[55937,44369,54698,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#6e645aff","u16":[28270,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#8c542bff","u16":[36006,21691,10963,65535]},"Light":{"hex":"#fdeee8ff","u16":[64962,61162,59645,65535]},"Mid":{"hex":"#ed8f48ff","u16":[60893,36683,18540,65535]},"Midlight":{"hex":"#f9ccb8ff","u16":[63954,52521,47303,65535]},"Shadow":{"hex":"#54321aff","u16":[21533,12972,6556,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#fae7dcff","u16":[64235,59292,56570,65535]},"Alternate":{"hex":"#f5eec8ff","u16":[62965,61166,51400,65535]},"Link":{"hex":"#dde6f5ff","u16":[56861,59010,62880,65535]},"Negative":{"hex":"#f4e1e1ff","u16":[62751,57759,57818,65535]},"Neutral":{"hex":"#fcecd1ff","u16":[64806,60692,53657,65535]},"Normal":{"hex":"#fffadcff","u16":[65535,64250,56540,65535]},"Positive":{"hex":"#c3f7cbff","u16":[50177,63504,52043,65535]},"Visited":{"hex":"#e7deebff","u16":[59465,56994,60335,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#64645aff","u16":[25700,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#141414ff","u16":[5140,5140,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#c6b800ff","u16":[50922,47201,0,65535]},"Light":{"hex":"#e2d200ff","u16":[58183,53932,0,65535]},"Mid":{"hex":"#e2d200ff","u16":[58183,53932,0,65535]},"Midlight":{"hex":"#fbe900ff","u16":[64492,59780,0,65535]},"Shadow":{"hex":"#8c8200ff","u16":[36042,33409,0,65535]}}},"Disabled":{"background":{"Active":{"hex":"#f7e4c3ff","u16":[63519,58538,50042,65535]},"Alternate":{"hex":"#ece3afff","u16":[60758,58398,44876,65535]},"Link":{"hex":"#dbebccff","u16":[56263,60348,52308,65535]},"Negative":{"hex":"#f7e1d0ff","u16":[63575,57931,53354,65535]},"Neutral":{"hex":"#f7e6acff","u16":[63408,59193,44284,65535]},"Normal":{"hex":"#fff07bff","u16":[65535,61747,31631,65535]},"Positive":{"hex":"#d9efa2ff","u16":[55809,61433,41576,65535]},"Visited":{"hex":"#efe3daff","u16":[61485,58390,55907,65535]}},"decoration":{"Focus":{"hex":"#e7b77dff","u16":[59434,47142,32045,65535]},"Hover":{"hex":"#f4c482ff","u16":[62796,50437,33300,65535]}},"foreground":{"Active":{"hex":"#e7b77dff","u16":[59434,47142,32045,65535]},"Inactive":{"hex":"#bfbca6ff","u16":[49180,48384,42750,65535]},"Link":{"hex":"#9cbad0ff","u16":[40118,47722,53375,65535]},"Negative":{"hex":"#e69e8fff","u16":[59091,40688,36816,65535]},"Neutral":{"hex":"#e1ca88ff","u16":[57755,51814,35032,65535]},"Normal":{"hex":"#a5a28fff","u16":[42321,41525,36747,65535]},"Positive":{"hex":"#a5ca9cff","u16":[42321,51814,40177,65535]},"Visited":{"hex":"#caa6c0ff","u16":[52012,42745,49452,65535]}},"shade":{"Dark":{"hex":"#7f7500ff","u16":[32531,30154,0,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#d1c200ff","u16":[53792,49862,0,65535]},"Midlight":{"hex":"#fff9d1ff","u16":[65535,63889,53726,65535]},"Shadow":{"hex":"#433e00ff","u16":[17122,15871,0,65535]}}},"Inactive":{"background":{"Active":{"hex":"#fae7dcff","u16":[64235,59292,56570,65535]},"Alternate":{"hex":"#f5eec8ff","u16":[62965,61166,51400,65535]},"Link":{"hex":"#dde6f5ff","u16":[56861,59010,62880,65535]},"Negative":{"hex":"#f4e1e1ff","u16":[62751,57759,57818,65535]},"Neutral":{"hex":"#fcecd1ff","u16":[64806,60692,53657,65535]},"Normal":{"hex":"#fffadcff","u16":[65535,64250,56540,65535]},"Positive":{"hex":"#c3f7cbff","u16":[50177,63504,52043,65535]},"Visited":{"hex":"#e7deebff","u16":[59465,56994,60335,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#64645aff","u16":[25700,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#141414ff","u16":[5140,5140,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#c6b800ff","u16":[50922,47201,0,65535]},"Light":{"hex":"#e2d200ff","u16":[58183,53932,0,65535]},"Mid":{"hex":"#e2d200ff","u16":[58183,53932,0,65535]},"Midlight":{"hex":"#fbe900ff","u16":[64492,59780,0,65535]},"Shadow":{"hex":"#8c8200ff","u16":[36042,33409,0,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#f9e6dcff","u16":[63881,59040,56599,65535]},"Alternate":{"hex":"#f0ece2ff","u16":[61680,60652,58082,65535]},"Link":{"hex":"#dde4f5ff","u16":[56679,58719,62899,65535]},"Negative":{"hex":"#f3e0e1ff","u16":[62546,57464,57768,65535]},"Neutral":{"hex":"#fbebd0ff","u16":[64585,60401,53387,65535]},"Normal":{"hex":"#faf8f0ff","u16":[64250,63736,61680,65535]},"Positive":{"hex":"#bdf7ccff","u16":[48611,63473,52376,65535]},"Visited":{"hex":"#e6ddecff","u16":[59172,56733,60544,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#786e64ff","u16":[30840,28270,25700,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#281e14ff","u16":[10280,7710,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#bab399ff","u16":[47709,46131,39232,65535]},"Light":{"hex":"#d5ceafff","u16":[54650,52842,44939,65535]},"Mid":{"hex":"#d5ceafff","u16":[54650,52842,44939,65535]},"Midlight":{"hex":"#ece4c2ff","u16":[60666,58659,49886,65535]},"Shadow":{"hex":"#827e6bff","u16":[33383,32279,27451,65535]}}},"Disabled":{"background":{"Active":{"hex":"#f3e2d2ff","u16":[62491,58118,53966,65535]},"Alternate":{"hex":"#e7e1d0ff","u16":[59458,57789,53502,65535]},"Link":{"hex":"#dde5edff","u16":[56866,58973,60990,65535]},"Negative":{"hex":"#f4e0ddff","u16":[62673,57490,56811,65535]},"Neutral":{"hex":"#f0e5c9ff","u16":[61663,58761,51722,65535]},"Normal":{"hex":"#f2edd6ff","u16":[62193,60818,55050,65535]},"Positive":{"hex":"#d6ebd2ff","u16":[54936,60350,53946,65535]},"Visited":{"hex":"#ebe1e9ff","u16":[60516,57886,59999,65535]}},"decoration":{"Focus":{"hex":"#e5b68cff","u16":[58881,46721,35854,65535]},"Hover":{"hex":"#f0c396ff","u16":[61794,50105,38549,65535]}},"foreground":{"Active":{"hex":"#e5b68cff","u16":[58881,46721,35854,65535]},"Inactive":{"hex":"#c3beb6ff","u16":[50099,48923,46792,65535]},"Link":{"hex":"#98b8dfff","u16":[39130,47354,57189,65535]},"Negative":{"hex":"#e29d9fff","u16":[58179,40356,40904,65535]},"Neutral":{"hex":"#dec895ff","u16":[56959,51495,38217,65535]},"Normal":{"hex":"#a8a49bff","u16":[43240,42064,39932,65535]},"Positive":{"hex":"#a2c8a9ff","u16":[41525,51495,43362,65535]},"Visited":{"hex":"#c7a5cfff","u16":[51086,42388,53265,65535]}},"shade":{"Dark":{"hex":"#777362ff","u16":[30631,29618,25189,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c5bfa2ff","u16":[50690,49014,41684,65535]},"Midlight":{"hex":"#f9f7edff","u16":[63981,63357,60848,65535]},"Shadow":{"hex":"#3f3d34ff","u16":[16203,15667,13324,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f9e6dcff","u16":[63881,59040,56599,65535]},"Alternate":{"hex":"#f0ece2ff","u16":[61680,60652,58082,65535]},"Link":{"hex":"#dde4f5ff","u16":[56679,58719,62899,65535]},"Negative":{"hex":"#f3e0e1ff","u16":[62546,57464,57768,65535]},"Neutral":{"hex":"#fbebd0ff","u16":[64585,60401,53387,65535]},"Normal":{"hex":"#faf8f0ff","u16":[64250,63736,61680,65535]},"Positive":{"hex":"#bdf7ccff","u16":[48611,63473,52376,65535]},"Visited":{"hex":"#e6ddecff","u16":[59172,56733,60544,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#786e64ff","u16":[30840,28270,25700,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#281e14ff","u16":[10280,7710,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#bab399ff","u16":[47709,46131,39232,65535]},"Light":{"hex":"#d5ceafff","u16":[54650,52842,44939,65535]},"Mid":{"hex":"#d5ceafff","u16":[54650,52842,44939,65535]},"Midlight":{"hex":"#ece4c2ff","u16":[60666,58659,49886,65535]},"Shadow":{"hex":"#827e6bff","u16":[33383,32279,27451,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#f4cfbbff","u16":[62786,53235,48073,65535]},"Alternate":{"hex":"#dcd6caff","u16":[56540,54998,51914,65535]},"Link":{"hex":"#c5d3efff","u16":[50620,54244,61307,65535]},"Negative":{"hex":"#eacbcdff","u16":[60194,52203,52639,65535]},"Neutral":{"hex":"#f9d58fff","u16":[63904,54710,36739,65535]},"Normal":{"hex":"#e6e2d8ff","u16":[59110,58082,55512,65535]},"Positive":{"hex":"#88eca3ff","u16":[35054,60578,41870,65535]},"Visited":{"hex":"#d9cae0ff","u16":[55688,51964,57566,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#6e645aff","u16":[28270,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#706e68ff","u16":[28904,28256,26616,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbb7adff","u16":[48156,47076,44344,65535]},"Midlight":{"hex":"#efede7ff","u16":[61547,60927,59404,65535]},"Shadow":{"hex":"#3e3d39ff","u16":[15930,15573,14669,65535]}}},"Disabled":{"background":{"Active":{"hex":"#e1cebcff","u16":[57920,52843,48412,65535]},"Alternate":{"hex":"#d2ccc0ff","u16":[53908,52425,49457,65535]},"Link":{"hex":"#c7d2deff","u16":[51216,53883,56944,65535]},"Negative":{"hex":"#e5cac8ff","u16":[58939,51935,51367,65535]},"Neutral":{"hex":"#e3d0a4ff","u16":[58254,53431,42023,65535]},"Normal":{"hex":"#dcd7cbff","u16":[56625,55354,52142,65535]},"Positive":{"hex":"#bed8bbff","u16":[48787,55537,48086,65535]},"Visited":{"hex":"#daccd9ff","u16":[56086,52469,55670,65535]}},"decoration":{"Focus":{"hex":"#d2aa88ff","u16":[53908,43671,35033,65535]},"Hover":{"hex":"#e2b68aff","u16":[58157,46698,35439,65535]}},"foreground":{"Active":{"hex":"#d2aa88ff","u16":[53908,43671,35033,65535]},"Inactive":{"hex":"#b3ada4ff","u16":[46057,44562,42112,65535]},"Link":{"hex":"#8dabceff","u16":[36232,43821,53035,65535]},"Negative":{"hex":"#d59090ff","u16":[54742,36935,37085,65535]},"Neutral":{"hex":"#d1bb86ff","u16":[53774,47992,34396,65535]},"Normal":{"hex":"#99948dff","u16":[39197,38131,36111,65535]},"Positive":{"hex":"#95bb9aff","u16":[38340,47992,39540,65535]},"Visited":{"hex":"#ba98bfff","u16":[47807,38942,49200,65535]}},"shade":{"Dark":{"hex":"#6b6963ff","u16":[27553,26935,25372,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b4b0a6ff","u16":[46206,45169,42548,65535]},"Midlight":{"hex":"#e7e3daff","u16":[59382,58401,55954,65535]},"Shadow":{"hex":"#3d3c38ff","u16":[15762,15408,14514,65535]}}},"Inactive":{"background":{"Active":{"hex":"#f4cfbbff","u16":[62786,53235,48073,65535]},"Alternate":{"hex":"#dcd6caff","u16":[56540,54998,51914,65535]},"Link":{"hex":"#c5d3efff","u16":[50620,54244,61307,65535]},"Negative":{"hex":"#eacbcdff","u16":[60194,52203,52639,65535]},"Neutral":{"hex":"#f9d58fff","u16":[63904,54710,36739,65535]},"Normal":{"hex":"#e6e2d8ff","u16":[59110,58082,55512,65535]},"Positive":{"hex":"#88eca3ff","u16":[35054,60578,41870,65535]},"Visited":{"hex":"#d9cae0ff","u16":[55688,51964,57566,65535]}},"decoration":{"Focus":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Hover":{"hex":"#e68232ff","u16":[59110,33410,12850,65535]}},"foreground":{"Active":{"hex":"#c85a0aff","u16":[51400,23130,2570,65535]},"Inactive":{"hex":"#6e645aff","u16":[28270,25700,23130,65535]},"Link":{"hex":"#0a5ac8ff","u16":[2570,23130,51400,65535]},"Negative":{"hex":"#be1428ff","u16":[48830,5140,10280,65535]},"Neutral":{"hex":"#c88c00ff","u16":[51400,35980,0,65535]},"Normal":{"hex":"#1e1914ff","u16":[7710,6425,5140,65535]},"Positive":{"hex":"#148c3cff","u16":[5140,35980,15420,65535]},"Visited":{"hex":"#7828a0ff","u16":[30840,10280,41120,65535]}},"shade":{"Dark":{"hex":"#706e68ff","u16":[28904,28256,26616,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbb7adff","u16":[48156,47076,44344,65535]},"Midlight":{"hex":"#efede7ff","u16":[61547,60927,59404,65535]},"Shadow":{"hex":"#3e3d39ff","u16":[15930,15573,14669,65535]}}}}},"sha256":"f64aeb4af3d1a47374df3125a2c7219c8c5bcf55aa0ef117f2c2acc04bb57f39","source_text":"[General]\nColorScheme=SyntheticNoHeader\nName=Synthetic No Header\n\n[KDE]\ncontrast=7\n\n[Colors:View]\nBackgroundNormal=250,248,240\nBackgroundAlternate=240,236,226\nForegroundNormal=40,30,20\nForegroundInactive=120,110,100\nForegroundActive=200,90,10\nForegroundLink=10,90,200\nForegroundVisited=120,40,160\nForegroundNegative=190,20,40\nForegroundNeutral=200,140,0\nForegroundPositive=20,140,60\nDecorationFocus=200,90,10\nDecorationHover=230,130,50\n\n[Colors:Window]\nBackgroundNormal=230,226,216\nBackgroundAlternate=220,214,202\nForegroundNormal=30,25,20\nForegroundInactive=110,100,90\nForegroundActive=200,90,10\nForegroundLink=10,90,200\nForegroundVisited=120,40,160\nForegroundNegative=190,20,40\nForegroundNeutral=200,140,0\nForegroundPositive=20,140,60\nDecorationFocus=200,90,10\nDecorationHover=230,130,50\n\n[Colors:Button]\nBackgroundNormal=236,232,224\nBackgroundAlternate=210,204,190\nForegroundNormal=30,25,20\nForegroundInactive=110,100,90\nForegroundActive=200,90,10\nForegroundLink=10,90,200\nForegroundVisited=120,40,160\nForegroundNegative=190,20,40\nForegroundNeutral=200,140,0\nForegroundPositive=20,140,60\nDecorationFocus=200,90,10\nDecorationHover=230,130,50\n\n[Colors:Selection]\nBackgroundNormal=200,90,10\nBackgroundAlternate=170,80,20\nForegroundNormal=255,255,255\nForegroundInactive=240,220,200\nForegroundActive=255,240,200\nForegroundLink=255,230,160\nForegroundVisited=240,200,240\nForegroundNegative=255,180,180\nForegroundNeutral=255,230,120\nForegroundPositive=180,255,190\nDecorationFocus=200,90,10\nDecorationHover=230,130,50\n\n[Colors:Tooltip]\nBackgroundNormal=255,250,220\nBackgroundAlternate=245,238,200\nForegroundNormal=20,20,20\nForegroundInactive=100,100,90\nForegroundActive=200,90,10\nForegroundLink=10,90,200\nForegroundVisited=120,40,160\nForegroundNegative=190,20,40\nForegroundNeutral=200,140,0\nForegroundPositive=20,140,60\nDecorationFocus=200,90,10\nDecorationHover=230,130,50\n\n[Colors:Complementary]\nBackgroundNormal=40,36,30\nBackgroundAlternate=30,27,22\nForegroundNormal=240,235,225\nForegroundInactive=160,150,140\nForegroundActive=255,160,60\nForegroundLink=100,170,255\nForegroundVisited=200,140,230\nForegroundNegative=255,90,100\nForegroundNeutral=255,200,60\nForegroundPositive=90,220,130\nDecorationFocus=255,160,60\nDecorationHover=255,190,110\n"}, +{"contrastF":0.2,"frameContrast":0.35,"input":{"ColorEffects:Disabled":{"Color":"56,56,56","ColorAmount":"0.5","ColorEffect":"3","ContrastAmount":"0.6","ContrastEffect":"2","IntensityAmount":"0.2","IntensityEffect":"3"},"ColorEffects:Inactive":{"ChangeSelectionColor":"true","Color":"112,111,110","ColorAmount":"0.3","ColorEffect":"1","ContrastAmount":"0.4","ContrastEffect":"1","Enable":"true","IntensityAmount":"0.25","IntensityEffect":"1"},"Colors:Button":{"BackgroundAlternate":"38,42,48","BackgroundNormal":"44,48,54","DecorationFocus":"90,200,250","DecorationHover":"90,200,250","ForegroundActive":"90,200,250","ForegroundInactive":"140,146,154","ForegroundLink":"80,160,255","ForegroundNegative":"240,80,90","ForegroundNeutral":"240,170,40","ForegroundNormal":"220,224,230","ForegroundPositive":"60,200,110","ForegroundVisited":"170,120,230"},"Colors:Complementary":{"BackgroundAlternate":"12,13,15","BackgroundNormal":"16,18,20","DecorationFocus":"90,200,250","DecorationHover":"90,200,250","ForegroundActive":"90,200,250","ForegroundInactive":"150,156,164","ForegroundLink":"80,160,255","ForegroundNegative":"240,80,90","ForegroundNeutral":"240,170,40","ForegroundNormal":"235,238,242","ForegroundPositive":"60,200,110","ForegroundVisited":"170,120,230"},"Colors:Header":{"BackgroundAlternate":"36,39,44","BackgroundNormal":"52,56,62","DecorationFocus":"90,200,250","DecorationHover":"90,200,250","ForegroundActive":"90,200,250","ForegroundInactive":"140,146,154","ForegroundLink":"80,160,255","ForegroundNegative":"240,80,90","ForegroundNeutral":"240,170,40","ForegroundNormal":"225,228,234","ForegroundPositive":"60,200,110","ForegroundVisited":"170,120,230"},"Colors:Selection":{"BackgroundAlternate":"60,150,200","BackgroundNormal":"90,200,250","DecorationFocus":"90,200,250","DecorationHover":"90,200,250","ForegroundActive":"0,0,0","ForegroundInactive":"40,60,70","ForegroundLink":"0,40,120","ForegroundNegative":"120,0,10","ForegroundNeutral":"110,70,0","ForegroundNormal":"10,10,10","ForegroundPositive":"0,90,30","ForegroundVisited":"70,20,110"},"Colors:Tooltip":{"BackgroundAlternate":"40,44,50","BackgroundNormal":"48,52,58","DecorationFocus":"90,200,250","DecorationHover":"90,200,250","ForegroundActive":"90,200,250","ForegroundInactive":"150,156,164","ForegroundLink":"80,160,255","ForegroundNegative":"240,80,90","ForegroundNeutral":"240,170,40","ForegroundNormal":"230,232,236","ForegroundPositive":"60,200,110","ForegroundVisited":"170,120,230"},"Colors:View":{"BackgroundAlternate":"32,35,40","BackgroundNormal":"24,26,30","DecorationFocus":"90,200,250","DecorationHover":"90,200,250","ForegroundActive":"90,200,250","ForegroundInactive":"140,146,154","ForegroundLink":"80,160,255","ForegroundNegative":"240,80,90","ForegroundNeutral":"240,170,40","ForegroundNormal":"220,224,230","ForegroundPositive":"60,200,110","ForegroundVisited":"170,120,230"},"Colors:Window":{"BackgroundAlternate":"30,33,38","BackgroundNormal":"36,39,44","DecorationFocus":"90,200,250","DecorationHover":"90,200,250","ForegroundActive":"90,200,250","ForegroundInactive":"140,146,154","ForegroundLink":"80,160,255","ForegroundNegative":"240,80,90","ForegroundNeutral":"240,170,40","ForegroundNormal":"220,224,230","ForegroundPositive":"60,200,110","ForegroundVisited":"170,120,230"},"General":{"ColorScheme":"SyntheticInactiveEffects","Name":"Synthetic Inactive Effects"},"KDE":{"contrast":"2","frameContrast":"0.35"}},"name":"SyntheticInactiveEffects","origin":"synthetic","palette":{"Active":{"Accent":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"AlternateBase":{"hex":"#202328ff","u16":[8224,8995,10280,65535]},"Base":{"hex":"#181a1eff","u16":[6168,6682,7710,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#2c3036ff","u16":[11308,12336,13878,65535]},"ButtonText":{"hex":"#dce0e6ff","u16":[56540,57568,59110,65535]},"Dark":{"hex":"#17191cff","u16":[5960,6456,7284,65535]},"Highlight":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"HighlightedText":{"hex":"#0a0a0aff","u16":[2570,2570,2570,65535]},"Light":{"hex":"#363b42ff","u16":[13982,15147,17089,65535]},"Link":{"hex":"#50a0ffff","u16":[20560,41120,65535,65535]},"LinkVisited":{"hex":"#aa78e6ff","u16":[43690,30840,59110,65535]},"Mid":{"hex":"#202327ff","u16":[8271,8961,10109,65535]},"Midlight":{"hex":"#2e3238ff","u16":[11876,12865,14515,65535]},"PlaceholderText":{"hex":"#8c929aff","u16":[35980,37522,39578,65535]},"Shadow":{"hex":"#111215ff","u16":[4330,4691,5292,65535]},"Text":{"hex":"#dce0e6ff","u16":[56540,57568,59110,65535]},"ToolTipBase":{"hex":"#30343aff","u16":[12336,13364,14906,65535]},"ToolTipText":{"hex":"#e6e8ecff","u16":[59110,59624,60652,65535]},"Window":{"hex":"#24272cff","u16":[9252,10023,11308,65535]},"WindowText":{"hex":"#dce0e6ff","u16":[56540,57568,59110,65535]}},"Disabled":{"Accent":{"hex":"#616367ff","u16":[24843,25438,26430,65535]},"AlternateBase":{"hex":"#606367ff","u16":[24663,25320,26416,65535]},"Base":{"hex":"#5f6266ff","u16":[24495,25073,26229,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#626468ff","u16":[25119,25778,26768,65535]},"ButtonText":{"hex":"#737579ff","u16":[29496,30187,31223,65535]},"Dark":{"hex":"#3e4042ff","u16":[16003,16386,17025,65535]},"Highlight":{"hex":"#616367ff","u16":[24843,25438,26430,65535]},"HighlightedText":{"hex":"#717478ff","u16":[29106,29716,30728,65535]},"Light":{"hex":"#707377ff","u16":[28770,29459,30608,65535]},"Link":{"hex":"#666e79ff","u16":[26089,28179,31037,65535]},"LinkVisited":{"hex":"#6c6b6eff","u16":[27676,27535,28356,65535]},"Mid":{"hex":"#56585bff","u16":[22079,22608,23490,65535]},"Midlight":{"hex":"#686a6fff","u16":[26722,27362,28429,65535]},"PlaceholderText":{"hex":"#6a6c70ff","u16":[27146,27726,28800,65535]},"Shadow":{"hex":"#2c2d2fff","u16":[11274,11544,11994,65535]},"Text":{"hex":"#6f7176ff","u16":[28637,29168,30205,65535]},"ToolTipBase":{"hex":"#30343aff","u16":[12336,13364,14906,65535]},"ToolTipText":{"hex":"#e6e8ecff","u16":[59110,59624,60652,65535]},"Window":{"hex":"#616367ff","u16":[24843,25438,26430,65535]},"WindowText":{"hex":"#717478ff","u16":[29106,29716,30728,65535]}},"Inactive":{"Accent":{"hex":"#6a9bb7ff","u16":[27285,39912,46996,65535]},"AlternateBase":{"hex":"#838b9aff","u16":[33711,35834,39515,65535]},"Base":{"hex":"#828a99ff","u16":[33514,35396,39312,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#868f9bff","u16":[34513,36626,39912,65535]},"ButtonText":{"hex":"#c5c7c9ff","u16":[50668,51037,51606,65535]},"Dark":{"hex":"#565a63ff","u16":[21974,23210,25344,65535]},"Highlight":{"hex":"#6a9bb7ff","u16":[27285,39912,46996,65535]},"HighlightedText":{"hex":"#c5ced4ff","u16":[50519,52853,54388,65535]},"Light":{"hex":"#969eabff","u16":[38578,40511,43876,65535]},"Link":{"hex":"#8aa8d9ff","u16":[35480,43104,55793,65535]},"LinkVisited":{"hex":"#a79db9ff","u16":[43030,40398,47521,65535]},"Mid":{"hex":"#757c87ff","u16":[30080,31773,34695,65535]},"Midlight":{"hex":"#8c94a1ff","u16":[35917,37939,41427,65535]},"PlaceholderText":{"hex":"#9ea3aaff","u16":[40660,41867,43663,65535]},"Shadow":{"hex":"#3a3d42ff","u16":[14817,15651,17090,65535]},"Text":{"hex":"#bfc0c2ff","u16":[49082,49418,49978,65535]},"ToolTipBase":{"hex":"#30343aff","u16":[12336,13364,14906,65535]},"ToolTipText":{"hex":"#e6e8ecff","u16":[59110,59624,60652,65535]},"Window":{"hex":"#858c99ff","u16":[34112,36032,39345,65535]},"WindowText":{"hex":"#c3c4c6ff","u16":[50020,50370,50928,65535]}}},"purpose":"dark scheme, Header without an Inactive subgroup, Inactive effects enabled with the selection change; every effect kind with non-zero amounts in both states","set_supported":{"Button":true,"Complementary":true,"Header":true,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#22404fff","u16":[8835,16447,20176,65535]},"Alternate":{"hex":"#262a30ff","u16":[9766,10794,12336,65535]},"Link":{"hex":"#233b57ff","u16":[8903,15066,22469,65535]},"Negative":{"hex":"#5a272cff","u16":[23224,10150,11418,65535]},"Neutral":{"hex":"#4b3918ff","u16":[19211,14696,6221,65535]},"Normal":{"hex":"#2c3036ff","u16":[11308,12336,13878,65535]},"Positive":{"hex":"#1c422cff","u16":[7072,17087,11212,65535]},"Visited":{"hex":"#403255ff","u16":[16369,12754,21754,65535]}},"decoration":{"Focus":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Hover":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]}},"foreground":{"Active":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Inactive":{"hex":"#8c929aff","u16":[35980,37522,39578,65535]},"Link":{"hex":"#50a0ffff","u16":[20560,41120,65535,65535]},"Negative":{"hex":"#f0505aff","u16":[61680,20560,23130,65535]},"Neutral":{"hex":"#f0aa28ff","u16":[61680,43690,10280,65535]},"Normal":{"hex":"#dce0e6ff","u16":[56540,57568,59110,65535]},"Positive":{"hex":"#3cc86eff","u16":[15420,51400,28270,65535]},"Visited":{"hex":"#aa78e6ff","u16":[43690,30840,59110,65535]}},"shade":{"Dark":{"hex":"#1c1f23ff","u16":[7284,7946,8940,65535]},"Light":{"hex":"#3d424aff","u16":[15569,16985,19108,65535]},"Mid":{"hex":"#272b30ff","u16":[10104,11023,12401,65535]},"Midlight":{"hex":"#353a41ff","u16":[13603,14839,16694,65535]},"Shadow":{"hex":"#151619ff","u16":[5279,5759,6479,65535]}}},"Disabled":{"background":{"Active":{"hex":"#63696eff","u16":[25334,26901,28236,65535]},"Alternate":{"hex":"#616368ff","u16":[24814,25557,26672,65535]},"Link":{"hex":"#636870ff","u16":[25371,26808,28698,65535]},"Negative":{"hex":"#6b666aff","u16":[27523,26302,27222,65535]},"Neutral":{"hex":"#686867ff","u16":[26729,26686,26393,65535]},"Normal":{"hex":"#626468ff","u16":[25119,25778,26768,65535]},"Positive":{"hex":"#626969ff","u16":[25127,27064,27095,65535]},"Visited":{"hex":"#676869ff","u16":[26516,26636,27074,65535]}},"decoration":{"Focus":{"hex":"#6c747aff","u16":[27662,29709,31292,65535]},"Hover":{"hex":"#6c747aff","u16":[27662,29709,31292,65535]}},"foreground":{"Active":{"hex":"#6c747aff","u16":[27662,29709,31292,65535]},"Inactive":{"hex":"#6c6e72ff","u16":[27638,28321,29332,65535]},"Link":{"hex":"#697079ff","u16":[27036,28799,31082,65535]},"Negative":{"hex":"#726b6fff","u16":[29199,27494,28436,65535]},"Neutral":{"hex":"#73726fff","u16":[29584,29281,28493,65535]},"Normal":{"hex":"#737579ff","u16":[29496,30187,31223,65535]},"Positive":{"hex":"#697372ff","u16":[27113,29652,29348,65535]},"Visited":{"hex":"#6e6e6fff","u16":[28179,28163,28479,65535]}},"shade":{"Dark":{"hex":"#3f4143ff","u16":[16181,16605,17243,65535]},"Light":{"hex":"#717478ff","u16":[29047,29809,30954,65535]},"Mid":{"hex":"#57595dff","u16":[22320,22906,23785,65535]},"Midlight":{"hex":"#696c70ff","u16":[26993,27701,28765,65535]},"Shadow":{"hex":"#2c2d2fff","u16":[11388,11686,12135,65535]}}},"Inactive":{"background":{"Active":{"hex":"#7698aeff","u16":[30199,39002,44695,65535]},"Alternate":{"hex":"#848d9cff","u16":[33871,36257,39985,65535]},"Link":{"hex":"#7c94baff","u16":[31941,38086,47690,65535]},"Negative":{"hex":"#b2888fff","u16":[45839,34840,36689,65535]},"Neutral":{"hex":"#a49074ff","u16":[42226,37069,29801,65535]},"Normal":{"hex":"#868f9bff","u16":[34513,36626,39912,65535]},"Positive":{"hex":"#709d85ff","u16":[28676,40315,34132,65535]},"Visited":{"hex":"#9490a7ff","u16":[37912,36990,43040,65535]}},"decoration":{"Focus":{"hex":"#85bfe0ff","u16":[34073,49016,57468,65535]},"Hover":{"hex":"#85bfe0ff","u16":[34073,49016,57468,65535]}},"foreground":{"Active":{"hex":"#85bfe0ff","u16":[34073,49016,57468,65535]},"Inactive":{"hex":"#a3a8afff","u16":[41878,43133,44917,65535]},"Link":{"hex":"#92add9ff","u16":[37520,44397,55776,65535]},"Negative":{"hex":"#cd9498ff","u16":[52645,38080,38971,65535]},"Neutral":{"hex":"#d7ae73ff","u16":[55380,44778,29647,65535]},"Normal":{"hex":"#c5c7c9ff","u16":[50668,51037,51606,65535]},"Positive":{"hex":"#77c290ff","u16":[30536,49885,37030,65535]},"Visited":{"hex":"#aba3baff","u16":[43951,41805,47854,65535]}},"shade":{"Dark":{"hex":"#575c64ff","u16":[22232,23593,25710,65535]},"Light":{"hex":"#98a0adff","u16":[38958,41144,44567,65535]},"Mid":{"hex":"#767e89ff","u16":[30419,32281,35177,65535]},"Midlight":{"hex":"#8d96a3ff","u16":[36311,38534,41991,65535]},"Shadow":{"hex":"#3a3e43ff","u16":[14947,15862,17285,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#162d38ff","u16":[5615,11619,14402,65535]},"Alternate":{"hex":"#0c0d0fff","u16":[3084,3341,3855,65535]},"Link":{"hex":"#15263cff","u16":[5297,9892,15330,65535]},"Negative":{"hex":"#3b1619ff","u16":[15233,5729,6432,65535]},"Neutral":{"hex":"#36270cff","u16":[13754,10070,3143,65535]},"Normal":{"hex":"#101214ff","u16":[4112,4626,5140,65535]},"Positive":{"hex":"#102e1bff","u16":[4067,11811,6995,65535]},"Visited":{"hex":"#291f38ff","u16":[10659,7865,14337,65535]}},"decoration":{"Focus":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Hover":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]}},"foreground":{"Active":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Inactive":{"hex":"#969ca4ff","u16":[38550,40092,42148,65535]},"Link":{"hex":"#50a0ffff","u16":[20560,41120,65535,65535]},"Negative":{"hex":"#f0505aff","u16":[61680,20560,23130,65535]},"Neutral":{"hex":"#f0aa28ff","u16":[61680,43690,10280,65535]},"Normal":{"hex":"#ebeef2ff","u16":[60395,61166,62194,65535]},"Positive":{"hex":"#3cc86eff","u16":[15420,51400,28270,65535]},"Visited":{"hex":"#aa78e6ff","u16":[43690,30840,59110,65535]}},"shade":{"Dark":{"hex":"#525c66ff","u16":[21002,23627,26253,65535]},"Light":{"hex":"#798897ff","u16":[31037,34916,38796,65535]},"Mid":{"hex":"#3c444bff","u16":[15517,17457,19397,65535]},"Midlight":{"hex":"#626e7aff","u16":[25146,28290,31433,65535]},"Shadow":{"hex":"#626e7aff","u16":[25146,28290,31433,65535]}}},"Disabled":{"background":{"Active":{"hex":"#5d666bff","u16":[23954,26204,27570,65535]},"Alternate":{"hex":"#5f6165ff","u16":[24306,24875,26013,65535]},"Link":{"hex":"#5d656fff","u16":[24001,26075,28424,65535]},"Negative":{"hex":"#6c6265ff","u16":[27713,25209,25928,65535]},"Neutral":{"hex":"#66655fff","u16":[26292,25847,24481,65535]},"Normal":{"hex":"#5e6165ff","u16":[24179,25014,25849,65535]},"Positive":{"hex":"#5c6764ff","u16":[23594,26488,25653,65535]},"Visited":{"hex":"#646468ff","u16":[25723,25752,26784,65535]}},"decoration":{"Focus":{"hex":"#657077ff","u16":[25902,28855,30528,65535]},"Hover":{"hex":"#657077ff","u16":[25902,28855,30528,65535]}},"foreground":{"Active":{"hex":"#657077ff","u16":[25902,28855,30528,65535]},"Inactive":{"hex":"#6a6c6fff","u16":[27138,27863,28635,65535]},"Link":{"hex":"#636d79ff","u16":[25515,28108,31079,65535]},"Negative":{"hex":"#75676aff","u16":[29947,26472,27196,65535]},"Neutral":{"hex":"#726e65ff","u16":[29190,28255,25996,65535]},"Normal":{"hex":"#6f7174ff","u16":[28442,29144,29863,65535]},"Positive":{"hex":"#62716bff","u16":[25220,29044,27572,65535]},"Visited":{"hex":"#6b6a6eff","u16":[27565,27368,28375,65535]}},"shade":{"Dark":{"hex":"#3d3f41ff","u16":[15575,16113,16651,65535]},"Light":{"hex":"#6d7175ff","u16":[28066,29035,30004,65535]},"Mid":{"hex":"#545759ff","u16":[21495,22237,22980,65535]},"Midlight":{"hex":"#65696cff","u16":[26047,26947,27846,65535]},"Shadow":{"hex":"#2b2c2eff","u16":[10989,11369,11749,65535]}}},"Inactive":{"background":{"Active":{"hex":"#6e93a9ff","u16":[28272,37686,43322,65535]},"Alternate":{"hex":"#818898ff","u16":[33209,35075,38954,65535]},"Link":{"hex":"#768fb4ff","u16":[30449,36706,46215,65535]},"Negative":{"hex":"#b38086ff","u16":[45952,32868,34497,65535]},"Neutral":{"hex":"#a08b6dff","u16":[41065,35600,27981,65535]},"Normal":{"hex":"#7f8a95ff","u16":[32698,35419,38248,65535]},"Positive":{"hex":"#69987dff","u16":[26869,39025,32069,65535]},"Visited":{"hex":"#8f8aa5ff","u16":[36679,35447,42415,65535]}},"decoration":{"Focus":{"hex":"#7bb7d9ff","u16":[31539,47111,55689,65535]},"Hover":{"hex":"#7bb7d9ff","u16":[31539,47111,55689,65535]}},"foreground":{"Active":{"hex":"#7bb7d9ff","u16":[31539,47111,55689,65535]},"Inactive":{"hex":"#a0a5abff","u16":[41002,42278,43930,65535]},"Link":{"hex":"#87a6d9ff","u16":[34610,42658,55868,65535]},"Negative":{"hex":"#d08b8eff","u16":[53338,35809,36622,65535]},"Neutral":{"hex":"#d2a66aff","u16":[54079,42662,27321,65535]},"Normal":{"hex":"#c3c4c6ff","u16":[50074,50378,50758,65535]},"Positive":{"hex":"#6ebb85ff","u16":[28351,48132,34285,65535]},"Visited":{"hex":"#a69bb8ff","u16":[42700,39888,47399,65535]}},"shade":{"Dark":{"hex":"#525960ff","u16":[21063,22815,24638,65535]},"Light":{"hex":"#8f9ba8ff","u16":[36828,39893,43079,65535]},"Mid":{"hex":"#707a83ff","u16":[28854,31256,33752,65535]},"Midlight":{"hex":"#86919dff","u16":[34469,37338,40320,65535]},"Shadow":{"hex":"#373c41ff","u16":[14263,15449,16684,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#274756ff","u16":[10013,18161,22151,65535]},"Alternate":{"hex":"#24272cff","u16":[9252,10023,11308,65535]},"Link":{"hex":"#284261ff","u16":[10188,16842,24837,65535]},"Negative":{"hex":"#652d33ff","u16":[25882,11614,12989,65535]},"Neutral":{"hex":"#523f1cff","u16":[21138,16304,7229,65535]},"Normal":{"hex":"#34383eff","u16":[13364,14392,15934,65535]},"Positive":{"hex":"#204a31ff","u16":[8163,18923,12611,65535]},"Visited":{"hex":"#47385eff","u16":[18305,14389,24130,65535]}},"decoration":{"Focus":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Hover":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]}},"foreground":{"Active":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Inactive":{"hex":"#8c929aff","u16":[35980,37522,39578,65535]},"Link":{"hex":"#50a0ffff","u16":[20560,41120,65535,65535]},"Negative":{"hex":"#f0505aff","u16":[61680,20560,23130,65535]},"Neutral":{"hex":"#f0aa28ff","u16":[61680,43690,10280,65535]},"Normal":{"hex":"#e1e4eaff","u16":[57825,58596,60138,65535]},"Positive":{"hex":"#3cc86eff","u16":[15420,51400,28270,65535]},"Visited":{"hex":"#aa78e6ff","u16":[43690,30840,59110,65535]}},"shade":{"Dark":{"hex":"#212428ff","u16":[8609,9271,10264,65535]},"Light":{"hex":"#444951ff","u16":[17410,18750,20759,65535]},"Mid":{"hex":"#2e3237ff","u16":[11935,12853,14230,65535]},"Midlight":{"hex":"#3c4148ff","u16":[15497,16689,18477,65535]},"Shadow":{"hex":"#181a1dff","u16":[6222,6700,7418,65535]}}},"Disabled":{"background":{"Active":{"hex":"#646a6fff","u16":[25804,27250,28462,65535]},"Alternate":{"hex":"#616367ff","u16":[24843,25438,26430,65535]},"Link":{"hex":"#656a70ff","u16":[25844,27157,28881,65535]},"Negative":{"hex":"#6c686bff","u16":[27789,26700,27502,65535]},"Neutral":{"hex":"#6a6968ff","u16":[27118,27045,26723,65535]},"Normal":{"hex":"#636669ff","u16":[25550,26129,26999,65535]},"Positive":{"hex":"#646b6bff","u16":[25612,27400,27385,65535]},"Visited":{"hex":"#69696bff","u16":[26897,26994,27371,65535]}},"decoration":{"Focus":{"hex":"#6e757bff","u16":[28154,30050,31496,65535]},"Hover":{"hex":"#6e757bff","u16":[28154,30050,31496,65535]}},"foreground":{"Active":{"hex":"#6e757bff","u16":[28154,30050,31496,65535]},"Inactive":{"hex":"#6d6f73ff","u16":[27951,28565,29472,65535]},"Link":{"hex":"#6b7179ff","u16":[27460,29068,31146,65535]},"Negative":{"hex":"#726c6fff","u16":[29251,27771,28588,65535]},"Neutral":{"hex":"#747370ff","u16":[29934,29620,28838,65535]},"Normal":{"hex":"#75777bff","u16":[30143,30702,31562,65535]},"Positive":{"hex":"#6b7473ff","u16":[27589,29931,29609,65535]},"Visited":{"hex":"#6f6e70ff","u16":[28409,28394,28666,65535]}},"shade":{"Dark":{"hex":"#404144ff","u16":[16458,16831,17392,65535]},"Light":{"hex":"#737579ff","u16":[29500,30168,31173,65535]},"Mid":{"hex":"#585a5dff","u16":[22698,23213,23985,65535]},"Midlight":{"hex":"#6b6d71ff","u16":[27428,28049,28983,65535]},"Shadow":{"hex":"#2d2e30ff","u16":[11569,11831,12226,65535]}}},"Inactive":{"background":{"Active":{"hex":"#7a9aafff","u16":[31261,39568,44916,65535]},"Alternate":{"hex":"#858c99ff","u16":[34112,36032,39345,65535]},"Link":{"hex":"#7f97bbff","u16":[32631,38706,48136,65535]},"Negative":{"hex":"#b48a91ff","u16":[46169,35579,37228,65535]},"Neutral":{"hex":"#a79376ff","u16":[42884,37683,30243,65535]},"Normal":{"hex":"#8a919cff","u16":[35369,37212,40065,65535]},"Positive":{"hex":"#729f87ff","u16":[29383,40926,34631,65535]},"Visited":{"hex":"#9692a9ff","u16":[38628,37599,43430,65535]}},"decoration":{"Focus":{"hex":"#8ac1e0ff","u16":[35356,49477,57526,65535]},"Hover":{"hex":"#8ac1e0ff","u16":[35356,49477,57526,65535]}},"foreground":{"Active":{"hex":"#8ac1e0ff","u16":[35356,49477,57526,65535]},"Inactive":{"hex":"#a5aab0ff","u16":[42426,43607,45291,65535]},"Link":{"hex":"#95afd9ff","u16":[38293,44884,55761,65535]},"Negative":{"hex":"#cc979aff","u16":[52545,38697,39551,65535]},"Neutral":{"hex":"#d9b076ff","u16":[55815,45352,30254,65535]},"Normal":{"hex":"#cacbcdff","u16":[51834,52111,52615,65535]},"Positive":{"hex":"#79c493ff","u16":[31167,50377,37669,65535]},"Visited":{"hex":"#ada5bcff","u16":[44410,42282,48253,65535]}},"shade":{"Dark":{"hex":"#595d64ff","u16":[22783,23970,25808,65535]},"Light":{"hex":"#9ba2aeff","u16":[39931,41759,44613,65535]},"Mid":{"hex":"#798089ff","u16":[31155,32779,35292,65535]},"Midlight":{"hex":"#9198a4ff","u16":[37180,39117,42116,65535]},"Shadow":{"hex":"#3b3e43ff","u16":[15267,16062,17294,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#4eadd8ff","u16":[20025,44500,55627,65535]},"Alternate":{"hex":"#3c96c8ff","u16":[15420,38550,51400,65535]},"Link":{"hex":"#7caaf9ff","u16":[31985,43747,64022,65535]},"Negative":{"hex":"#c6a1baff","u16":[50773,41484,47822,65535]},"Neutral":{"hex":"#9eb790ff","u16":[40635,46918,36887,65535]},"Normal":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Positive":{"hex":"#32c4a7ff","u16":[12763,50477,42825,65535]},"Visited":{"hex":"#a6a7c6ff","u16":[42684,42993,50789,65535]}},"decoration":{"Focus":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Hover":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]}},"foreground":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Inactive":{"hex":"#283c46ff","u16":[10280,15420,17990,65535]},"Link":{"hex":"#002878ff","u16":[0,10280,30840,65535]},"Negative":{"hex":"#78000aff","u16":[30840,0,2570,65535]},"Neutral":{"hex":"#6e4600ff","u16":[28270,17990,0,65535]},"Normal":{"hex":"#0a0a0aff","u16":[2570,2570,2570,65535]},"Positive":{"hex":"#005a1eff","u16":[0,23130,7710,65535]},"Visited":{"hex":"#46146eff","u16":[17990,5140,28270,65535]}},"shade":{"Dark":{"hex":"#2882a6ff","u16":[10240,33381,42590,65535]},"Light":{"hex":"#9dd8fbff","u16":[40315,55638,64599,65535]},"Mid":{"hex":"#36afe0ff","u16":[13825,45070,57503,65535]},"Midlight":{"hex":"#75cdfaff","u16":[29986,52808,64363,65535]},"Shadow":{"hex":"#195066ff","u16":[6330,20635,26328,65535]}}},"Disabled":{"background":{"Active":{"hex":"#61676dff","u16":[24983,26573,27932,65535]},"Alternate":{"hex":"#606267ff","u16":[24573,25267,26424,65535]},"Link":{"hex":"#61676fff","u16":[25016,26475,28456,65535]},"Negative":{"hex":"#6b6568ff","u16":[27415,25913,26816,65535]},"Neutral":{"hex":"#676665ff","u16":[26513,26335,25905,65535]},"Normal":{"hex":"#616367ff","u16":[24843,25438,26430,65535]},"Positive":{"hex":"#606868ff","u16":[24751,26755,26677,65535]},"Visited":{"hex":"#666668ff","u16":[26207,26289,26853,65535]}},"decoration":{"Focus":{"hex":"#6a7278ff","u16":[27247,29349,30966,65535]},"Hover":{"hex":"#6a7278ff","u16":[27247,29349,30966,65535]}},"foreground":{"Active":{"hex":"#6a7278ff","u16":[27247,29349,30966,65535]},"Inactive":{"hex":"#6b6d71ff","u16":[27433,28050,29044,65535]},"Link":{"hex":"#686f78ff","u16":[26689,28511,30928,65535]},"Negative":{"hex":"#726a6dff","u16":[29261,27183,28104,65535]},"Neutral":{"hex":"#72706dff","u16":[29366,28892,27908,65535]},"Normal":{"hex":"#717478ff","u16":[29106,29716,30728,65535]},"Positive":{"hex":"#687270ff","u16":[26690,29347,28887,65535]},"Visited":{"hex":"#6d6d6eff","u16":[27941,27889,28367,65535]}},"shade":{"Dark":{"hex":"#3e4042ff","u16":[16003,16386,17025,65535]},"Light":{"hex":"#707377ff","u16":[28770,29459,30608,65535]},"Mid":{"hex":"#56585bff","u16":[22079,22608,23490,65535]},"Midlight":{"hex":"#686a6fff","u16":[26722,27362,28429,65535]},"Shadow":{"hex":"#2c2d2fff","u16":[11274,11544,11994,65535]}}},"Inactive":{"background":{"Active":{"hex":"#72a0bbff","u16":[29212,41246,48159,65535]},"Alternate":{"hex":"#699ab6ff","u16":[26905,39620,46782,65535]},"Link":{"hex":"#789dc4ff","u16":[30884,40476,50399,65535]},"Negative":{"hex":"#ac939fff","u16":[44267,37717,40781,65535]},"Neutral":{"hex":"#9c9c83ff","u16":[39966,40026,33788,65535]},"Normal":{"hex":"#6a9bb7ff","u16":[27285,39912,46996,65535]},"Positive":{"hex":"#67a695ff","u16":[26513,42699,38399,65535]},"Visited":{"hex":"#8c9ab8ff","u16":[36099,39553,47300,65535]}},"decoration":{"Focus":{"hex":"#8dc4e4ff","u16":[36365,50491,58709,65535]},"Hover":{"hex":"#8dc4e4ff","u16":[36365,50491,58709,65535]}},"foreground":{"Active":{"hex":"#8dc4e4ff","u16":[36365,50491,58709,65535]},"Inactive":{"hex":"#9cafbbff","u16":[40125,44927,48176,65535]},"Link":{"hex":"#95b3dfff","u16":[38301,45958,57296,65535]},"Negative":{"hex":"#c79a9fff","u16":[51261,39465,40889,65535]},"Neutral":{"hex":"#d1b67dff","u16":[53787,46798,32032,65535]},"Normal":{"hex":"#c5ced4ff","u16":[50519,52853,54388,65535]},"Positive":{"hex":"#77c99cff","u16":[30576,51531,40036,65535]},"Visited":{"hex":"#aca8c1ff","u16":[44216,43069,49728,65535]}},"shade":{"Dark":{"hex":"#446476ff","u16":[17576,25710,30273,65535]},"Light":{"hex":"#77aecdff","u16":[30583,44737,52677,65535]},"Mid":{"hex":"#5d89a1ff","u16":[24006,35116,41349,65535]},"Midlight":{"hex":"#6fa3c0ff","u16":[28636,41888,49322,65535]},"Shadow":{"hex":"#2e434eff","u16":[11697,17110,20146,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#254352ff","u16":[9411,17284,21141,65535]},"Alternate":{"hex":"#282c32ff","u16":[10280,11308,12850,65535]},"Link":{"hex":"#253e5cff","u16":[9537,15947,23647,65535]},"Negative":{"hex":"#602a2fff","u16":[24551,10876,12198,65535]},"Neutral":{"hex":"#4e3c1aff","u16":[20152,15483,6715,65535]},"Normal":{"hex":"#30343aff","u16":[12336,13364,14906,65535]},"Positive":{"hex":"#1e462eff","u16":[7608,17996,11902,65535]},"Visited":{"hex":"#433559ff","u16":[17324,13560,22927,65535]}},"decoration":{"Focus":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Hover":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]}},"foreground":{"Active":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Inactive":{"hex":"#969ca4ff","u16":[38550,40092,42148,65535]},"Link":{"hex":"#50a0ffff","u16":[20560,41120,65535,65535]},"Negative":{"hex":"#f0505aff","u16":[61680,20560,23130,65535]},"Neutral":{"hex":"#f0aa28ff","u16":[61680,43690,10280,65535]},"Normal":{"hex":"#e6e8ecff","u16":[59110,59624,60652,65535]},"Positive":{"hex":"#3cc86eff","u16":[15420,51400,28270,65535]},"Visited":{"hex":"#aa78e6ff","u16":[43690,30840,59110,65535]}},"shade":{"Dark":{"hex":"#1f2125ff","u16":[7946,8609,9602,65535]},"Light":{"hex":"#40454dff","u16":[16479,17852,19912,65535]},"Mid":{"hex":"#2b2e34ff","u16":[11020,11938,13316,65535]},"Midlight":{"hex":"#393d44ff","u16":[14542,15754,17572,65535]},"Shadow":{"hex":"#16181bff","u16":[5751,6231,6950,65535]}}},"Disabled":{"background":{"Active":{"hex":"#63696eff","u16":[25567,27068,28336,65535]},"Alternate":{"hex":"#616468ff","u16":[24913,25627,26695,65535]},"Link":{"hex":"#646970ff","u16":[25604,26975,28776,65535]},"Negative":{"hex":"#6c676aff","u16":[27647,26496,27351,65535]},"Neutral":{"hex":"#696967ff","u16":[26916,26857,26549,65535]},"Normal":{"hex":"#636569ff","u16":[25329,25946,26871,65535]},"Positive":{"hex":"#636a6aff","u16":[25367,27224,27232,65535]},"Visited":{"hex":"#68686aff","u16":[26699,26807,27214,65535]}},"decoration":{"Focus":{"hex":"#6d747aff","u16":[27906,29870,31378,65535]},"Hover":{"hex":"#6d747aff","u16":[27906,29870,31378,65535]}},"foreground":{"Active":{"hex":"#6d747aff","u16":[27906,29870,31378,65535]},"Inactive":{"hex":"#6d7074ff","u16":[28111,28760,29721,65535]},"Link":{"hex":"#6a7179ff","u16":[27255,28938,31114,65535]},"Negative":{"hex":"#726c6fff","u16":[29221,27634,28507,65535]},"Neutral":{"hex":"#747370ff","u16":[29755,29445,28661,65535]},"Normal":{"hex":"#75777aff","u16":[29953,30551,31466,65535]},"Positive":{"hex":"#6a7473ff","u16":[27361,29796,29484,65535]},"Visited":{"hex":"#6e6e6fff","u16":[28298,28282,28576,65535]}},"shade":{"Dark":{"hex":"#3f4143ff","u16":[16316,16713,17309,65535]},"Light":{"hex":"#727579ff","u16":[29268,29981,31050,65535]},"Mid":{"hex":"#585a5dff","u16":[22504,23053,23874,65535]},"Midlight":{"hex":"#6a6c70ff","u16":[27205,27868,28861,65535]},"Shadow":{"hex":"#2d2e2fff","u16":[11476,11756,12175,65535]}}},"Inactive":{"background":{"Active":{"hex":"#7899aeff","u16":[30725,39275,44788,65535]},"Alternate":{"hex":"#858e9bff","u16":[34088,36374,39940,65535]},"Link":{"hex":"#7e95baff","u16":[32284,38384,47884,65535]},"Negative":{"hex":"#b38990ff","u16":[45993,35199,36942,65535]},"Neutral":{"hex":"#a69175ff","u16":[42539,37362,30009,65535]},"Normal":{"hex":"#88909bff","u16":[34939,36907,39957,65535]},"Positive":{"hex":"#719e86ff","u16":[29023,40610,34366,65535]},"Visited":{"hex":"#9591a8ff","u16":[38250,37294,43169,65535]}},"decoration":{"Focus":{"hex":"#87c0e0ff","u16":[34716,49247,57496,65535]},"Hover":{"hex":"#87c0e0ff","u16":[34716,49247,57496,65535]}},"foreground":{"Active":{"hex":"#87c0e0ff","u16":[34716,49247,57496,65535]},"Inactive":{"hex":"#a8acb2ff","u16":[43187,44279,45835,65535]},"Link":{"hex":"#93aed9ff","u16":[37905,44640,55766,65535]},"Negative":{"hex":"#cd9599ff","u16":[52591,38389,39261,65535]},"Neutral":{"hex":"#d8af75ff","u16":[55596,45064,29949,65535]},"Normal":{"hex":"#cacbcdff","u16":[52040,52269,52670,65535]},"Positive":{"hex":"#78c391ff","u16":[30850,50131,37349,65535]},"Visited":{"hex":"#aca4bbff","u16":[44163,42055,47989,65535]}},"shade":{"Dark":{"hex":"#585d64ff","u16":[22506,23774,25739,65535]},"Light":{"hex":"#99a1adff","u16":[39441,41439,44559,65535]},"Mid":{"hex":"#787f89ff","u16":[30786,32520,35207,65535]},"Midlight":{"hex":"#8f97a4ff","u16":[36744,38814,42021,65535]},"Shadow":{"hex":"#3b3e43ff","u16":[15107,15958,17277,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#18313dff","u16":[6292,12558,15566,65535]},"Alternate":{"hex":"#202328ff","u16":[8224,8995,10280,65535]},"Link":{"hex":"#182b41ff","u16":[6073,10951,16833,65535]},"Negative":{"hex":"#421a1dff","u16":[16954,6668,7544,65535]},"Neutral":{"hex":"#3a2b0fff","u16":[14861,10999,3841,65535]},"Normal":{"hex":"#181a1eff","u16":[6168,6682,7710,65535]},"Positive":{"hex":"#12321fff","u16":[4721,12877,7913,65535]},"Visited":{"hex":"#2e233eff","u16":[11876,8887,15931,65535]}},"decoration":{"Focus":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Hover":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]}},"foreground":{"Active":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Inactive":{"hex":"#8c929aff","u16":[35980,37522,39578,65535]},"Link":{"hex":"#50a0ffff","u16":[20560,41120,65535,65535]},"Negative":{"hex":"#f0505aff","u16":[61680,20560,23130,65535]},"Neutral":{"hex":"#f0aa28ff","u16":[61680,43690,10280,65535]},"Normal":{"hex":"#dce0e6ff","u16":[56540,57568,59110,65535]},"Positive":{"hex":"#3cc86eff","u16":[15420,51400,28270,65535]},"Visited":{"hex":"#aa78e6ff","u16":[43690,30840,59110,65535]}},"shade":{"Dark":{"hex":"#0f1113ff","u16":[3973,4304,4966,65535]},"Light":{"hex":"#2e323aff","u16":[11945,12940,14931,65535]},"Mid":{"hex":"#15171bff","u16":[5517,5977,6896,65535]},"Midlight":{"hex":"#25282fff","u16":[9577,10375,11972,65535]},"Shadow":{"hex":"#0b0c0eff","u16":[2894,3135,3617,65535]}}},"Disabled":{"background":{"Active":{"hex":"#5f666cff","u16":[24448,26241,27793,65535]},"Alternate":{"hex":"#606367ff","u16":[24663,25320,26416,65535]},"Link":{"hex":"#5f666fff","u16":[24482,26131,28468,65535]},"Negative":{"hex":"#6b6367ff","u16":[27515,25422,26420,65535]},"Neutral":{"hex":"#676562ff","u16":[26345,25950,25283,65535]},"Normal":{"hex":"#5f6266ff","u16":[24495,25073,26229,65535]},"Positive":{"hex":"#5e6766ff","u16":[24159,26471,26240,65535]},"Visited":{"hex":"#656568ff","u16":[25878,25893,26845,65535]}},"decoration":{"Focus":{"hex":"#677178ff","u16":[26547,28941,30773,65535]},"Hover":{"hex":"#677178ff","u16":[26547,28941,30773,65535]}},"foreground":{"Active":{"hex":"#677178ff","u16":[26547,28941,30773,65535]},"Inactive":{"hex":"#6a6c70ff","u16":[27146,27726,28800,65535]},"Link":{"hex":"#666e79ff","u16":[26089,28179,31037,65535]},"Negative":{"hex":"#73686cff","u16":[29584,26716,27722,65535]},"Neutral":{"hex":"#726f69ff","u16":[29207,28429,27052,65535]},"Normal":{"hex":"#6f7176ff","u16":[28637,29168,30205,65535]},"Positive":{"hex":"#65716eff","u16":[25937,29030,28300,65535]},"Visited":{"hex":"#6c6b6eff","u16":[27676,27535,28356,65535]}},"shade":{"Dark":{"hex":"#3d3f42ff","u16":[15779,16151,16896,65535]},"Light":{"hex":"#6f7176ff","u16":[28414,29084,30425,65535]},"Mid":{"hex":"#55575bff","u16":[21774,22288,23316,65535]},"Midlight":{"hex":"#67696eff","u16":[26376,26999,28243,65535]},"Shadow":{"hex":"#2b2c2eff","u16":[11129,11391,11917,65535]}}},"Inactive":{"background":{"Active":{"hex":"#7093abff","u16":[28836,37830,43858,65535]},"Alternate":{"hex":"#838b9aff","u16":[33711,35834,39515,65535]},"Link":{"hex":"#778fb8ff","u16":[30602,36877,47182,65535]},"Negative":{"hex":"#b38189ff","u16":[45877,33194,35273,65535]},"Neutral":{"hex":"#a18b6fff","u16":[41383,35788,28651,65535]},"Normal":{"hex":"#828a99ff","u16":[33514,35396,39312,65535]},"Positive":{"hex":"#6b9880ff","u16":[27439,39168,32828,65535]},"Visited":{"hex":"#908ba6ff","u16":[37034,35644,42735,65535]}},"decoration":{"Focus":{"hex":"#7db9dbff","u16":[32186,47607,56277,65535]},"Hover":{"hex":"#7db9dbff","u16":[32186,47607,56277,65535]}},"foreground":{"Active":{"hex":"#7db9dbff","u16":[32186,47607,56277,65535]},"Inactive":{"hex":"#9ea3aaff","u16":[40660,41867,43663,65535]},"Link":{"hex":"#8aa8d9ff","u16":[35480,43104,55793,65535]},"Negative":{"hex":"#cf8e91ff","u16":[53135,36422,37304,65535]},"Neutral":{"hex":"#d4a86dff","u16":[54451,43213,27961,65535]},"Normal":{"hex":"#bfc0c2ff","u16":[49082,49418,49978,65535]},"Positive":{"hex":"#71bd89ff","u16":[28957,48581,35099,65535]},"Visited":{"hex":"#a79db9ff","u16":[43030,40398,47521,65535]}},"shade":{"Dark":{"hex":"#545963ff","u16":[21588,22801,25323,65535]},"Light":{"hex":"#949ba9ff","u16":[38114,39833,43472,65535]},"Mid":{"hex":"#737a87ff","u16":[29568,31229,34684,65535]},"Midlight":{"hex":"#8991a1ff","u16":[35318,37301,41428,65535]},"Shadow":{"hex":"#393c43ff","u16":[14601,15422,17127,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#1e3946ff","u16":[7689,14672,18060,65535]},"Alternate":{"hex":"#1e2126ff","u16":[7710,8481,9766,65535]},"Link":{"hex":"#1e334eff","u16":[7645,13225,19942,65535]},"Negative":{"hex":"#502126ff","u16":[20476,8604,9688,65535]},"Neutral":{"hex":"#433314ff","u16":[17253,13023,5133,65535]},"Normal":{"hex":"#24272cff","u16":[9252,10023,11308,65535]},"Positive":{"hex":"#173b26ff","u16":[6022,15191,9714,65535]},"Visited":{"hex":"#382b4bff","u16":[14384,11032,19157,65535]}},"decoration":{"Focus":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Hover":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]}},"foreground":{"Active":{"hex":"#5ac8faff","u16":[23130,51400,64250,65535]},"Inactive":{"hex":"#8c929aff","u16":[35980,37522,39578,65535]},"Link":{"hex":"#50a0ffff","u16":[20560,41120,65535,65535]},"Negative":{"hex":"#f0505aff","u16":[61680,20560,23130,65535]},"Neutral":{"hex":"#f0aa28ff","u16":[61680,43690,10280,65535]},"Normal":{"hex":"#dce0e6ff","u16":[56540,57568,59110,65535]},"Positive":{"hex":"#3cc86eff","u16":[15420,51400,28270,65535]},"Visited":{"hex":"#aa78e6ff","u16":[43690,30840,59110,65535]}},"shade":{"Dark":{"hex":"#17191cff","u16":[5960,6456,7284,65535]},"Light":{"hex":"#363b42ff","u16":[13982,15147,17089,65535]},"Mid":{"hex":"#202327ff","u16":[8271,8961,10109,65535]},"Midlight":{"hex":"#2e3238ff","u16":[11876,12865,14515,65535]},"Shadow":{"hex":"#111215ff","u16":[4330,4691,5292,65535]}}},"Disabled":{"background":{"Active":{"hex":"#61676dff","u16":[24983,26573,27932,65535]},"Alternate":{"hex":"#606267ff","u16":[24573,25267,26424,65535]},"Link":{"hex":"#61676fff","u16":[25016,26475,28456,65535]},"Negative":{"hex":"#6b6568ff","u16":[27415,25913,26816,65535]},"Neutral":{"hex":"#676665ff","u16":[26513,26335,25905,65535]},"Normal":{"hex":"#616367ff","u16":[24843,25438,26430,65535]},"Positive":{"hex":"#606868ff","u16":[24751,26755,26677,65535]},"Visited":{"hex":"#666668ff","u16":[26207,26289,26853,65535]}},"decoration":{"Focus":{"hex":"#6a7278ff","u16":[27247,29349,30966,65535]},"Hover":{"hex":"#6a7278ff","u16":[27247,29349,30966,65535]}},"foreground":{"Active":{"hex":"#6a7278ff","u16":[27247,29349,30966,65535]},"Inactive":{"hex":"#6b6d71ff","u16":[27433,28050,29044,65535]},"Link":{"hex":"#686f78ff","u16":[26689,28511,30928,65535]},"Negative":{"hex":"#726a6dff","u16":[29261,27183,28104,65535]},"Neutral":{"hex":"#72706dff","u16":[29366,28892,27908,65535]},"Normal":{"hex":"#717478ff","u16":[29106,29716,30728,65535]},"Positive":{"hex":"#687270ff","u16":[26690,29347,28887,65535]},"Visited":{"hex":"#6d6d6eff","u16":[27941,27889,28367,65535]}},"shade":{"Dark":{"hex":"#3e4042ff","u16":[16003,16386,17025,65535]},"Light":{"hex":"#707377ff","u16":[28770,29459,30608,65535]},"Mid":{"hex":"#56585bff","u16":[22079,22608,23490,65535]},"Midlight":{"hex":"#686a6fff","u16":[26722,27362,28429,65535]},"Shadow":{"hex":"#2c2d2fff","u16":[11274,11544,11994,65535]}}},"Inactive":{"background":{"Active":{"hex":"#7396acff","u16":[29542,38445,44208,65535]},"Alternate":{"hex":"#828b9aff","u16":[33502,35747,39646,65535]},"Link":{"hex":"#7a92b8ff","u16":[31409,37502,47219,65535]},"Negative":{"hex":"#b2858cff","u16":[45763,34088,35958,65535]},"Neutral":{"hex":"#a38e72ff","u16":[41809,36456,29186,65535]},"Normal":{"hex":"#858c99ff","u16":[34112,36032,39345,65535]},"Positive":{"hex":"#6e9b82ff","u16":[28148,39749,33447,65535]},"Visited":{"hex":"#928da6ff","u16":[37495,36351,42738,65535]}},"decoration":{"Focus":{"hex":"#81bddeff","u16":[33171,48449,57030,65535]},"Hover":{"hex":"#81bddeff","u16":[33171,48449,57030,65535]}},"foreground":{"Active":{"hex":"#81bddeff","u16":[33171,48449,57030,65535]},"Inactive":{"hex":"#a1a6adff","u16":[41387,42604,44373,65535]},"Link":{"hex":"#8fabd9ff","u16":[36685,43861,55755,65535]},"Negative":{"hex":"#ce9295ff","u16":[52824,37405,38286,65535]},"Neutral":{"hex":"#d6ac71ff","u16":[55003,44132,28936,65535]},"Normal":{"hex":"#c3c4c6ff","u16":[50020,50370,50928,65535]},"Positive":{"hex":"#74c08dff","u16":[29896,49342,36223,65535]},"Visited":{"hex":"#aaa0baff","u16":[43567,41221,47697,65535]}},"shade":{"Dark":{"hex":"#565a63ff","u16":[21974,23210,25344,65535]},"Light":{"hex":"#969eabff","u16":[38578,40511,43876,65535]},"Mid":{"hex":"#757c87ff","u16":[30080,31773,34695,65535]},"Midlight":{"hex":"#8c94a1ff","u16":[35917,37939,41427,65535]},"Shadow":{"hex":"#3a3d42ff","u16":[14817,15651,17090,65535]}}}}},"sha256":"8d209d8ff01f1ef4b7d5db0d16816d34563955942cb94f8d1971e34e90e37117","source_text":"[General]\nColorScheme=SyntheticInactiveEffects\nName=Synthetic Inactive Effects\n\n[KDE]\ncontrast=2\nframeContrast=0.35\n\n[ColorEffects:Inactive]\nEnable=true\nChangeSelectionColor=true\nIntensityEffect=1\nIntensityAmount=0.25\nColorEffect=1\nColorAmount=0.3\nColor=112,111,110\nContrastEffect=1\nContrastAmount=0.4\n\n[ColorEffects:Disabled]\nIntensityEffect=3\nIntensityAmount=0.2\nColorEffect=3\nColorAmount=0.5\nColor=56,56,56\nContrastEffect=2\nContrastAmount=0.6\n\n[Colors:View]\nBackgroundNormal=24,26,30\nBackgroundAlternate=32,35,40\nForegroundNormal=220,224,230\nForegroundInactive=140,146,154\nForegroundActive=90,200,250\nForegroundLink=80,160,255\nForegroundVisited=170,120,230\nForegroundNegative=240,80,90\nForegroundNeutral=240,170,40\nForegroundPositive=60,200,110\nDecorationFocus=90,200,250\nDecorationHover=90,200,250\n\n[Colors:Window]\nBackgroundNormal=36,39,44\nBackgroundAlternate=30,33,38\nForegroundNormal=220,224,230\nForegroundInactive=140,146,154\nForegroundActive=90,200,250\nForegroundLink=80,160,255\nForegroundVisited=170,120,230\nForegroundNegative=240,80,90\nForegroundNeutral=240,170,40\nForegroundPositive=60,200,110\nDecorationFocus=90,200,250\nDecorationHover=90,200,250\n\n[Colors:Button]\nBackgroundNormal=44,48,54\nBackgroundAlternate=38,42,48\nForegroundNormal=220,224,230\nForegroundInactive=140,146,154\nForegroundActive=90,200,250\nForegroundLink=80,160,255\nForegroundVisited=170,120,230\nForegroundNegative=240,80,90\nForegroundNeutral=240,170,40\nForegroundPositive=60,200,110\nDecorationFocus=90,200,250\nDecorationHover=90,200,250\n\n[Colors:Selection]\nBackgroundNormal=90,200,250\nBackgroundAlternate=60,150,200\nForegroundNormal=10,10,10\nForegroundInactive=40,60,70\nForegroundActive=0,0,0\nForegroundLink=0,40,120\nForegroundVisited=70,20,110\nForegroundNegative=120,0,10\nForegroundNeutral=110,70,0\nForegroundPositive=0,90,30\nDecorationFocus=90,200,250\nDecorationHover=90,200,250\n\n[Colors:Tooltip]\nBackgroundNormal=48,52,58\nBackgroundAlternate=40,44,50\nForegroundNormal=230,232,236\nForegroundInactive=150,156,164\nForegroundActive=90,200,250\nForegroundLink=80,160,255\nForegroundVisited=170,120,230\nForegroundNegative=240,80,90\nForegroundNeutral=240,170,40\nForegroundPositive=60,200,110\nDecorationFocus=90,200,250\nDecorationHover=90,200,250\n\n[Colors:Complementary]\nBackgroundNormal=16,18,20\nBackgroundAlternate=12,13,15\nForegroundNormal=235,238,242\nForegroundInactive=150,156,164\nForegroundActive=90,200,250\nForegroundLink=80,160,255\nForegroundVisited=170,120,230\nForegroundNegative=240,80,90\nForegroundNeutral=240,170,40\nForegroundPositive=60,200,110\nDecorationFocus=90,200,250\nDecorationHover=90,200,250\n\n[Colors:Header]\nBackgroundNormal=52,56,62\nBackgroundAlternate=36,39,44\nForegroundNormal=225,228,234\nForegroundInactive=140,146,154\nForegroundActive=90,200,250\nForegroundLink=80,160,255\nForegroundVisited=170,120,230\nForegroundNegative=240,80,90\nForegroundNeutral=240,170,40\nForegroundPositive=60,200,110\nDecorationFocus=90,200,250\nDecorationHover=90,200,250\n"}, +{"contrastF":0.5,"frameContrast":0.2,"input":{"ColorEffects:Disabled":{"ColorAmount":"0.8","ColorEffect":"1","ContrastAmount":"0.5","ContrastEffect":"1","IntensityAmount":"-0.5","IntensityEffect":"1"},"ColorEffects:Inactive":{"ChangeSelectionColor":"false","Color":"200,0,0","ColorAmount":"0.25","ColorEffect":"2","ContrastAmount":"0.3","ContrastEffect":"2","Enable":"true","IntensityAmount":"0.15","IntensityEffect":"2"},"Colors:Button":{"BackgroundAlternate":"220,222,226","BackgroundNormal":"248,249,250","DecorationFocus":"0,120,215","DecorationHover":"60,150,230","ForegroundActive":"0,120,215","ForegroundInactive":"110,116,124","ForegroundLink":"0,100,200","ForegroundNegative":"200,30,40","ForegroundNeutral":"190,120,0","ForegroundNormal":"20,22,26","ForegroundPositive":"20,130,50","ForegroundVisited":"110,60,180"},"Colors:Complementary":{"BackgroundAlternate":"24,26,30","BackgroundNormal":"30,32,36","DecorationFocus":"80,170,255","DecorationHover":"110,190,255","ForegroundActive":"80,170,255","ForegroundInactive":"150,156,164","ForegroundLink":"80,170,255","ForegroundNegative":"255,90,100","ForegroundNeutral":"255,190,60","ForegroundNormal":"240,242,245","ForegroundPositive":"80,210,120","ForegroundVisited":"180,140,240"},"Colors:Header":{"BackgroundAlternate":"240,241,243","BackgroundNormal":"226,228,232","DecorationFocus":"0,120,215","DecorationHover":"60,150,230","ForegroundActive":"0,120,215","ForegroundInactive":"110,116,124","ForegroundLink":"0,100,200","ForegroundNegative":"200,30,40","ForegroundNeutral":"190,120,0","ForegroundNormal":"20,22,26","ForegroundPositive":"20,130,50","ForegroundVisited":"110,60,180"},"Colors:Header][Inactive":{"BackgroundAlternate":"226,228,232","BackgroundNormal":"240,241,243","DecorationFocus":"0,120,215","DecorationHover":"60,150,230","ForegroundActive":"0,120,215","ForegroundInactive":"130,136,144","ForegroundLink":"0,100,200","ForegroundNegative":"200,30,40","ForegroundNeutral":"190,120,0","ForegroundNormal":"90,94,100","ForegroundPositive":"20,130,50","ForegroundVisited":"110,60,180"},"Colors:Selection":{"BackgroundAlternate":"0,100,180","BackgroundNormal":"0,120,215","DecorationFocus":"0,120,215","DecorationHover":"60,150,230","ForegroundActive":"255,255,255","ForegroundInactive":"200,220,240","ForegroundLink":"220,240,255","ForegroundNegative":"255,200,200","ForegroundNeutral":"255,230,170","ForegroundNormal":"255,255,255","ForegroundPositive":"200,255,210","ForegroundVisited":"230,210,255"},"Colors:Tooltip":{"BackgroundAlternate":"240,240,240","BackgroundNormal":"250,250,250","DecorationFocus":"0,120,215","DecorationHover":"60,150,230","ForegroundActive":"0,120,215","ForegroundInactive":"110,116,124","ForegroundLink":"0,100,200","ForegroundNegative":"200,30,40","ForegroundNeutral":"190,120,0","ForegroundNormal":"20,22,26","ForegroundPositive":"20,130,50","ForegroundVisited":"110,60,180"},"Colors:View":{"BackgroundAlternate":"245,247,250","BackgroundNormal":"255,255,255","DecorationFocus":"0,120,215","DecorationHover":"60,150,230","ForegroundActive":"0,120,215","ForegroundInactive":"110,116,124","ForegroundLink":"0,100,200","ForegroundNegative":"200,30,40","ForegroundNeutral":"190,120,0","ForegroundNormal":"20,22,26","ForegroundPositive":"20,130,50","ForegroundVisited":"110,60,180"},"Colors:View][Inactive":{"BackgroundNormal":"250,250,250","ForegroundNormal":"60,62,66"},"Colors:Window":{"BackgroundAlternate":"228,230,233","BackgroundNormal":"240,241,243","DecorationFocus":"0,120,215","DecorationHover":"60,150,230","ForegroundActive":"0,120,215","ForegroundInactive":"110,116,124","ForegroundLink":"0,100,200","ForegroundNegative":"200,30,40","ForegroundNeutral":"190,120,0","ForegroundNormal":"20,22,26","ForegroundPositive":"20,130,50","ForegroundVisited":"110,60,180"},"Colors:Window][Inactive":{"BackgroundNormal":"232,233,235"},"General":{"ColorScheme":"SyntheticInactiveSubgroups","Name":"Synthetic Inactive Subgroups"},"KDE":{"contrast":"5"}},"name":"SyntheticInactiveSubgroups","origin":"synthetic","palette":{"Active":{"Accent":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"AlternateBase":{"hex":"#f5f7faff","u16":[62965,63479,64250,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#f8f9faff","u16":[63736,63993,64250,65535]},"ButtonText":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Dark":{"hex":"#7d8798ff","u16":[32106,34594,39044,65535]},"Highlight":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#0064c8ff","u16":[0,25700,51400,65535]},"LinkVisited":{"hex":"#6e3cb4ff","u16":[28270,15420,46260,65535]},"Mid":{"hex":"#c2c6cfff","u16":[49805,50997,53302,65535]},"Midlight":{"hex":"#f8f9faff","u16":[63815,63928,64153,65535]},"PlaceholderText":{"hex":"#6e747cff","u16":[28270,29812,31868,65535]},"Shadow":{"hex":"#41464fff","u16":[16630,17918,20223,65535]},"Text":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"ToolTipBase":{"hex":"#fafafaff","u16":[64250,64250,64250,65535]},"ToolTipText":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Window":{"hex":"#f0f1f3ff","u16":[61680,61937,62451,65535]},"WindowText":{"hex":"#14161aff","u16":[5140,5654,6682,65535]}},"Disabled":{"Accent":{"hex":"#a3a5a8ff","u16":[41949,42377,43224,65535]},"AlternateBase":{"hex":"#a9aeb6ff","u16":[43517,44814,46701,65535]},"Base":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#aeb1b5ff","u16":[44776,45599,46409,65535]},"ButtonText":{"hex":"#000000ff","u16":[0,0,0,65535]},"Dark":{"hex":"#5a5c5eff","u16":[23242,23572,24220,65535]},"Highlight":{"hex":"#a3a5a8ff","u16":[41949,42377,43224,65535]},"HighlightedText":{"hex":"#000000ff","u16":[0,0,0,65535]},"Light":{"hex":"#c1c2c4ff","u16":[49510,49769,50287,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"LinkVisited":{"hex":"#000000ff","u16":[0,0,0,65535]},"Mid":{"hex":"#8b8d91ff","u16":[35646,36152,37147,65535]},"Midlight":{"hex":"#afb0b3ff","u16":[44895,45252,45961,65535]},"PlaceholderText":{"hex":"#000000ff","u16":[0,0,0,65535]},"Shadow":{"hex":"#3b3b3dff","u16":[15064,15278,15698,65535]},"Text":{"hex":"#000000ff","u16":[0,0,0,65535]},"ToolTipBase":{"hex":"#fafafaff","u16":[64250,64250,64250,65535]},"ToolTipText":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Window":{"hex":"#a3a5a8ff","u16":[41949,42377,43224,65535]},"WindowText":{"hex":"#000000ff","u16":[0,0,0,65535]}},"Inactive":{"Accent":{"hex":"#325496ff","u16":[12850,21483,38490,65535]},"AlternateBase":{"hex":"#deacacff","u16":[57069,44219,44219,65535]},"Base":{"hex":"#e0aeaeff","u16":[57606,44756,44756,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#ddaeb1ff","u16":[56723,44662,45438,65535]},"ButtonText":{"hex":"#572626ff","u16":[22438,9692,9863,65535]},"Dark":{"hex":"#924d52ff","u16":[37406,19760,21038,65535]},"Highlight":{"hex":"#325496ff","u16":[12850,21483,38490,65535]},"HighlightedText":{"hex":"#cca8b8ff","u16":[52410,43154,47345,65535]},"Light":{"hex":"#e2c6c7ff","u16":[58098,50765,51147,65535]},"Link":{"hex":"#795e6dff","u16":[30987,24236,28108,65535]},"LinkVisited":{"hex":"#a74485ff","u16":[42925,17447,34205,65535]},"Mid":{"hex":"#c78286ff","u16":[51192,33326,34448,65535]},"Midlight":{"hex":"#d8b0b2ff","u16":[55608,45124,45695,65535]},"PlaceholderText":{"hex":"#8a5d61ff","u16":[35555,23861,24995,65535]},"Shadow":{"hex":"#5d3134ff","u16":[23810,12578,13391,65535]},"Text":{"hex":"#683637ff","u16":[26739,13992,14198,65535]},"ToolTipBase":{"hex":"#fafafaff","u16":[64250,64250,64250,65535]},"ToolTipText":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Window":{"hex":"#d3a2a5ff","u16":[54219,41725,42429,65535]},"WindowText":{"hex":"#5b292aff","u16":[23296,10599,10792,65535]}}},"purpose":"Inactive effects enabled without the selection change, plus explicit Inactive subgroups for Header, View and (partial) Window; Disabled uses shade, desaturate and fade","set_supported":{"Button":true,"Complementary":true,"Header":true,"Selection":true,"Tooltip":true,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#dfebf9ff","u16":[57422,60368,63979,65535]},"Alternate":{"hex":"#dcdee2ff","u16":[56540,57054,58082,65535]},"Link":{"hex":"#dde7f7ff","u16":[56799,59455,63597,65535]},"Negative":{"hex":"#f5e2e3ff","u16":[63046,58084,58292,65535]},"Neutral":{"hex":"#faead5ff","u16":[64320,60047,54842,65535]},"Normal":{"hex":"#f8f9faff","u16":[63736,63993,64250,65535]},"Positive":{"hex":"#bcf7cbff","u16":[48291,63351,52070,65535]},"Visited":{"hex":"#e6e1f1ff","u16":[59156,57710,61846,65535]}},"decoration":{"Focus":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Hover":{"hex":"#3c96e6ff","u16":[15420,38550,59110,65535]}},"foreground":{"Active":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Inactive":{"hex":"#6e747cff","u16":[28270,29812,31868,65535]},"Link":{"hex":"#0064c8ff","u16":[0,25700,51400,65535]},"Negative":{"hex":"#c81e28ff","u16":[51400,7710,10280,65535]},"Neutral":{"hex":"#be7800ff","u16":[48830,30840,0,65535]},"Normal":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Positive":{"hex":"#148232ff","u16":[5140,33410,12850,65535]},"Visited":{"hex":"#6e3cb4ff","u16":[28270,15420,46260,65535]}},"shade":{"Dark":{"hex":"#bfcad4ff","u16":[49149,51839,54383,65535]},"Light":{"hex":"#d4dbe1ff","u16":[54537,56243,57897,65535]},"Mid":{"hex":"#d4dbe1ff","u16":[54537,56243,57897,65535]},"Midlight":{"hex":"#e7ebeeff","u16":[59351,60268,61173,65535]},"Shadow":{"hex":"#99aab9ff","u16":[39309,43683,47603,65535]}}},"Disabled":{"background":{"Active":{"hex":"#999b9eff","u16":[39204,39923,40633,65535]},"Alternate":{"hex":"#838487ff","u16":[33653,34038,34804,65535]},"Link":{"hex":"#999b9eff","u16":[39204,39923,40633,65535]},"Negative":{"hex":"#999b9eff","u16":[39204,39923,40633,65535]},"Neutral":{"hex":"#a0a09fff","u16":[41019,41024,40857,65535]},"Normal":{"hex":"#aeb1b5ff","u16":[44776,45599,46409,65535]},"Positive":{"hex":"#999b9eff","u16":[39204,39923,40633,65535]},"Visited":{"hex":"#999b9eff","u16":[39204,39923,40633,65535]}},"decoration":{"Focus":{"hex":"#000000ff","u16":[0,0,0,65535]},"Hover":{"hex":"#3d4247ff","u16":[15653,16967,18347,65535]}},"foreground":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Inactive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"Negative":{"hex":"#000000ff","u16":[0,0,0,65535]},"Neutral":{"hex":"#2b2927ff","u16":[10939,10529,9985,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Visited":{"hex":"#000000ff","u16":[0,0,0,65535]}},"shade":{"Dark":{"hex":"#616365ff","u16":[24868,25363,25849,65535]},"Light":{"hex":"#cecfd1ff","u16":[52846,53297,53746,65535]},"Mid":{"hex":"#94979aff","u16":[37965,38721,39463,65535]},"Midlight":{"hex":"#babcbfff","u16":[47732,48408,49077,65535]},"Shadow":{"hex":"#3d3e40ff","u16":[15741,16055,16362,65535]}}},"Inactive":{"background":{"Active":{"hex":"#bbabb7ff","u16":[48001,43995,46949,65535]},"Alternate":{"hex":"#ca9b9fff","u16":[52010,39759,40946,65535]},"Link":{"hex":"#bda9b4ff","u16":[48508,43461,46310,65535]},"Negative":{"hex":"#d8a3a5ff","u16":[55394,41788,42396,65535]},"Neutral":{"hex":"#d6a69aff","u16":[55070,42740,39646,65535]},"Normal":{"hex":"#ddaeb1ff","u16":[56723,44662,45438,65535]},"Positive":{"hex":"#c9a89fff","u16":[51604,43274,40845,65535]},"Visited":{"hex":"#d1a0c0ff","u16":[53810,41047,49315,65535]}},"decoration":{"Focus":{"hex":"#685d79ff","u16":[26793,23925,31035,65535]},"Hover":{"hex":"#6e7094ff","u16":[28196,28706,37912,65535]}},"foreground":{"Active":{"hex":"#685d79ff","u16":[26793,23925,31035,65535]},"Inactive":{"hex":"#87575aff","u16":[34620,22415,23232,65535]},"Link":{"hex":"#66516cff","u16":[26226,20822,27665,65535]},"Negative":{"hex":"#b7333bff","u16":[46920,13121,15093,65535]},"Neutral":{"hex":"#a56245ff","u16":[42310,25131,17661,65535]},"Normal":{"hex":"#572626ff","u16":[22438,9692,9863,65535]},"Positive":{"hex":"#745b49ff","u16":[29685,23381,18729,65535]},"Visited":{"hex":"#8b3380ff","u16":[35616,13157,32939,65535]}},"shade":{"Dark":{"hex":"#a14e55ff","u16":[41346,19976,21871,65535]},"Light":{"hex":"#ebd3d4ff","u16":[60418,54148,54521,65535]},"Mid":{"hex":"#d18a8fff","u16":[53726,35441,36758,65535]},"Midlight":{"hex":"#e2bbbdff","u16":[57962,48012,48632,65535]},"Shadow":{"hex":"#643035ff","u16":[25710,12421,13600,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#1a3045ff","u16":[6660,12335,17816,65535]},"Alternate":{"hex":"#181a1eff","u16":[6168,6682,7710,65535]},"Link":{"hex":"#1a3045ff","u16":[6660,12335,17816,65535]},"Negative":{"hex":"#491f22ff","u16":[18772,7898,8802,65535]},"Neutral":{"hex":"#3f3115ff","u16":[16246,12627,5388,65535]},"Normal":{"hex":"#1e2024ff","u16":[7710,8224,9252,65535]},"Positive":{"hex":"#193723ff","u16":[6372,14142,9061,65535]},"Visited":{"hex":"#332943ff","u16":[13004,10615,17107,65535]}},"decoration":{"Focus":{"hex":"#50aaffff","u16":[20560,43690,65535,65535]},"Hover":{"hex":"#6ebeffff","u16":[28270,48830,65535,65535]}},"foreground":{"Active":{"hex":"#50aaffff","u16":[20560,43690,65535,65535]},"Inactive":{"hex":"#969ca4ff","u16":[38550,40092,42148,65535]},"Link":{"hex":"#50aaffff","u16":[20560,43690,65535,65535]},"Negative":{"hex":"#ff5a64ff","u16":[65535,23130,25700,65535]},"Neutral":{"hex":"#ffbe3cff","u16":[65535,48830,15420,65535]},"Normal":{"hex":"#f0f2f5ff","u16":[61680,62194,62965,65535]},"Positive":{"hex":"#50d278ff","u16":[20560,53970,30840,65535]},"Visited":{"hex":"#b48cf0ff","u16":[46260,35980,61680,65535]}},"shade":{"Dark":{"hex":"#111214ff","u16":[4288,4573,5145,65535]},"Light":{"hex":"#3b3f46ff","u16":[15072,16077,18086,65535]},"Mid":{"hex":"#1a1c1fff","u16":[6745,7195,8094,65535]},"Midlight":{"hex":"#2f3238ff","u16":[12054,12857,14464,65535]},"Shadow":{"hex":"#0c0d0fff","u16":[3120,3328,3744,65535]}}},"Disabled":{"background":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Alternate":{"hex":"#000000ff","u16":[0,0,0,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"Negative":{"hex":"#000000ff","u16":[0,0,0,65535]},"Neutral":{"hex":"#000000ff","u16":[0,0,0,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Visited":{"hex":"#000000ff","u16":[0,0,0,65535]}},"decoration":{"Focus":{"hex":"#000000ff","u16":[0,0,0,65535]},"Hover":{"hex":"#000000ff","u16":[0,0,0,65535]}},"foreground":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Inactive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"Negative":{"hex":"#000000ff","u16":[0,0,0,65535]},"Neutral":{"hex":"#000000ff","u16":[0,0,0,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Visited":{"hex":"#000000ff","u16":[0,0,0,65535]}},"shade":{"Dark":{"hex":"#808080ff","u16":[32929,32929,32929,65535]},"Light":{"hex":"#bebebeff","u16":[48896,48896,48896,65535]},"Mid":{"hex":"#5d5d5dff","u16":[24029,24029,24029,65535]},"Midlight":{"hex":"#9a9a9aff","u16":[39593,39593,39593,65535]},"Shadow":{"hex":"#9a9a9aff","u16":[39593,39593,39593,65535]}}},"Inactive":{"background":{"Active":{"hex":"#392a39ff","u16":[14765,10753,14619,65535]},"Alternate":{"hex":"#431215ff","u16":[17147,4655,5371,65535]},"Link":{"hex":"#392a39ff","u16":[14765,10753,14619,65535]},"Negative":{"hex":"#521c20ff","u16":[21153,7303,8167,65535]},"Neutral":{"hex":"#4a2819ff","u16":[19111,10207,6393,65535]},"Normal":{"hex":"#471619ff","u16":[18221,5729,6445,65535]},"Positive":{"hex":"#382e23ff","u16":[14293,11861,9033,65535]},"Visited":{"hex":"#45252eff","u16":[17814,9587,11907,65535]}},"decoration":{"Focus":{"hex":"#726b95ff","u16":[29170,27396,38342,65535]},"Hover":{"hex":"#817596ff","u16":[33048,30003,38555,65535]}},"foreground":{"Active":{"hex":"#726b95ff","u16":[29170,27396,38342,65535]},"Inactive":{"hex":"#90636aff","u16":[37057,25352,27170,65535]},"Link":{"hex":"#726b95ff","u16":[29170,27396,38342,65535]},"Negative":{"hex":"#c4464eff","u16":[50436,17891,19974,65535]},"Neutral":{"hex":"#c37640ff","u16":[50228,30415,16490,65535]},"Normal":{"hex":"#bd8d90ff","u16":[48682,36200,36902,65535]},"Positive":{"hex":"#747f5aff","u16":[29720,32567,23049,65535]},"Visited":{"hex":"#9a607aff","u16":[39665,24778,31339,65535]}},"shade":{"Dark":{"hex":"#270c0eff","u16":[10133,3186,3584,65535]},"Light":{"hex":"#79262bff","u16":[31072,9769,10990,65535]},"Mid":{"hex":"#3e1316ff","u16":[15934,5010,5636,65535]},"Midlight":{"hex":"#631f23ff","u16":[25556,8035,9039,65535]},"Shadow":{"hex":"#1d090aff","u16":[7361,2314,2604,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#c3daf5ff","u16":[50156,56013,62930,65535]},"Alternate":{"hex":"#f0f1f3ff","u16":[61680,61937,62451,65535]},"Link":{"hex":"#c4d7f3ff","u16":[50274,55140,62470,65535]},"Negative":{"hex":"#edcecfff","u16":[60781,52900,53302,65535]},"Neutral":{"hex":"#f6d4a6ff","u16":[63323,54589,42622,65535]},"Normal":{"hex":"#e2e4e8ff","u16":[58082,58596,59624,65535]},"Positive":{"hex":"#89eca7ff","u16":[35326,60572,42954,65535]},"Visited":{"hex":"#d7cfe7ff","u16":[55255,53074,59411,65535]}},"decoration":{"Focus":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Hover":{"hex":"#3c96e6ff","u16":[15420,38550,59110,65535]}},"foreground":{"Active":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Inactive":{"hex":"#6e747cff","u16":[28270,29812,31868,65535]},"Link":{"hex":"#0064c8ff","u16":[0,25700,51400,65535]},"Negative":{"hex":"#c81e28ff","u16":[51400,7710,10280,65535]},"Neutral":{"hex":"#be7800ff","u16":[48830,30840,0,65535]},"Normal":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Positive":{"hex":"#148232ff","u16":[5140,33410,12850,65535]},"Visited":{"hex":"#6e3cb4ff","u16":[28270,15420,46260,65535]}},"shade":{"Dark":{"hex":"#767f90ff","u16":[30351,32716,36978,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b8bdc8ff","u16":[47185,48621,51393,65535]},"Midlight":{"hex":"#ebedefff","u16":[60510,60848,61529,65535]},"Shadow":{"hex":"#41464fff","u16":[16618,17913,20247,65535]}}},"Disabled":{"background":{"Active":{"hex":"#808185ff","u16":[32785,33253,34181,65535]},"Alternate":{"hex":"#a3a5a8ff","u16":[41949,42377,43224,65535]},"Link":{"hex":"#808185ff","u16":[32785,33253,34181,65535]},"Negative":{"hex":"#808185ff","u16":[32785,33253,34181,65535]},"Neutral":{"hex":"#808185ff","u16":[32785,33253,34181,65535]},"Normal":{"hex":"#8d8f93ff","u16":[36305,36823,37849,65535]},"Positive":{"hex":"#808185ff","u16":[32785,33253,34181,65535]},"Visited":{"hex":"#808185ff","u16":[32785,33253,34181,65535]}},"decoration":{"Focus":{"hex":"#000000ff","u16":[0,0,0,65535]},"Hover":{"hex":"#000000ff","u16":[0,0,0,65535]}},"foreground":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Inactive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"Negative":{"hex":"#000000ff","u16":[0,0,0,65535]},"Neutral":{"hex":"#000000ff","u16":[0,0,0,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Visited":{"hex":"#000000ff","u16":[0,0,0,65535]}},"shade":{"Dark":{"hex":"#4f5052ff","u16":[20189,20477,21048,65535]},"Light":{"hex":"#a8aaadff","u16":[43258,43653,44442,65535]},"Mid":{"hex":"#797b7fff","u16":[31185,31630,32511,65535]},"Midlight":{"hex":"#999b9eff","u16":[39239,39739,40733,65535]},"Shadow":{"hex":"#353537ff","u16":[13548,13741,14124,65535]}}},"Inactive":{"background":{"Active":{"hex":"#b8a6b2ff","u16":[47275,42596,45635,65535]},"Alternate":{"hex":"#ce9fa4ff","u16":[53038,40837,42118,65535]},"Link":{"hex":"#baa4afff","u16":[47819,42069,45047,65535]},"Negative":{"hex":"#d49da0ff","u16":[54534,40354,41029,65535]},"Neutral":{"hex":"#d5a093ff","u16":[54637,41165,37791,65535]},"Normal":{"hex":"#d8a8abff","u16":[55563,43163,44052,65535]},"Positive":{"hex":"#c6a399ff","u16":[50910,41857,39270,65535]},"Visited":{"hex":"#cf9abbff","u16":[53195,39537,48160,65535]}},"decoration":{"Focus":{"hex":"#6b5c77ff","u16":[27426,23769,30619,65535]},"Hover":{"hex":"#726f91ff","u16":[29376,28528,37295,65535]}},"foreground":{"Active":{"hex":"#6b5c77ff","u16":[27426,23769,30619,65535]},"Inactive":{"hex":"#936469ff","u16":[37696,25754,27026,65535]},"Link":{"hex":"#67506aff","u16":[26599,20674,27288,65535]},"Negative":{"hex":"#b3353cff","u16":[46091,13605,15546,65535]},"Neutral":{"hex":"#a46144ff","u16":[42257,25048,17600,65535]},"Normal":{"hex":"#79494bff","u16":[31200,18701,19261,65535]},"Positive":{"hex":"#735b49ff","u16":[29532,23302,18663,65535]},"Visited":{"hex":"#89347eff","u16":[35231,13341,32297,65535]}},"shade":{"Dark":{"hex":"#994d54ff","u16":[39387,19858,21711,65535]},"Light":{"hex":"#e7ccceff","u16":[59335,52442,52899,65535]},"Mid":{"hex":"#cd868bff","u16":[52562,34335,35793,65535]},"Midlight":{"hex":"#ddb5b8ff","u16":[56871,46547,47264,65535]},"Shadow":{"hex":"#603135ff","u16":[24786,12497,13663,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#4b86afff","u16":[19297,34522,45033,65535]},"Alternate":{"hex":"#0064b4ff","u16":[0,25700,46260,65535]},"Link":{"hex":"#4785b1ff","u16":[18346,34221,45558,65535]},"Negative":{"hex":"#637fadff","u16":[25474,32653,44398,65535]},"Neutral":{"hex":"#65848dff","u16":[25975,33948,36164,65535]},"Normal":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Positive":{"hex":"#4b8898ff","u16":[19270,35017,38953,65535]},"Visited":{"hex":"#4380caff","u16":[17259,32986,51992,65535]}},"decoration":{"Focus":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Hover":{"hex":"#3c96e6ff","u16":[15420,38550,59110,65535]}},"foreground":{"Active":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Inactive":{"hex":"#c8dcf0ff","u16":[51400,56540,61680,65535]},"Link":{"hex":"#dcf0ffff","u16":[56540,61680,65535,65535]},"Negative":{"hex":"#ffc8c8ff","u16":[65535,51400,51400,65535]},"Neutral":{"hex":"#ffe6aaff","u16":[65535,59110,43690,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#c8ffd2ff","u16":[51400,65535,53970,65535]},"Visited":{"hex":"#e6d2ffff","u16":[59110,53970,65535,65535]}},"shade":{"Dark":{"hex":"#004378ff","u16":[0,17150,30727,65535]},"Light":{"hex":"#2591ffff","u16":[9524,37194,65535,65535]},"Mid":{"hex":"#0068baff","u16":[0,26663,47772,65535]},"Midlight":{"hex":"#0083ecff","u16":[0,33791,60542,65535]},"Shadow":{"hex":"#002e53ff","u16":[0,11860,21250,65535]}}},"Disabled":{"background":{"Active":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Alternate":{"hex":"#919395ff","u16":[37232,37693,38382,65535]},"Link":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Negative":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Neutral":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Normal":{"hex":"#a3a5a8ff","u16":[41949,42377,43224,65535]},"Positive":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Visited":{"hex":"#919295ff","u16":[37164,37542,38294,65535]}},"decoration":{"Focus":{"hex":"#000000ff","u16":[0,0,0,65535]},"Hover":{"hex":"#2d3135ff","u16":[11668,12578,13559,65535]}},"foreground":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Inactive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"Negative":{"hex":"#000000ff","u16":[0,0,0,65535]},"Neutral":{"hex":"#000000ff","u16":[0,0,0,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Visited":{"hex":"#000000ff","u16":[0,0,0,65535]}},"shade":{"Dark":{"hex":"#5a5c5eff","u16":[23242,23572,24220,65535]},"Light":{"hex":"#c1c2c4ff","u16":[49510,49769,50287,65535]},"Mid":{"hex":"#8b8d91ff","u16":[35646,36152,37147,65535]},"Midlight":{"hex":"#afb0b3ff","u16":[44895,45252,45961,65535]},"Shadow":{"hex":"#3b3b3dff","u16":[15064,15278,15698,65535]}}},"Inactive":{"background":{"Active":{"hex":"#5b5593ff","u16":[23333,21822,37811,65535]},"Alternate":{"hex":"#32467dff","u16":[12850,17903,32225,65535]},"Link":{"hex":"#545596ff","u16":[21590,21935,38670,65535]},"Negative":{"hex":"#555494ff","u16":[21963,21659,37944,65535]},"Neutral":{"hex":"#525986ff","u16":[21005,22805,34363,65535]},"Normal":{"hex":"#325496ff","u16":[12850,21483,38490,65535]},"Positive":{"hex":"#515a82ff","u16":[20880,23095,33416,65535]},"Visited":{"hex":"#555497ff","u16":[21798,21590,38867,65535]}},"decoration":{"Focus":{"hex":"#325496ff","u16":[12850,21483,38490,65535]},"Hover":{"hex":"#4164aaff","u16":[16635,25691,43584,65535]}},"foreground":{"Active":{"hex":"#cca8b8ff","u16":[52410,43154,47345,65535]},"Inactive":{"hex":"#a593b6ff","u16":[42379,37722,46773,65535]},"Link":{"hex":"#ba9eb7ff","u16":[47745,40601,47148,65535]},"Negative":{"hex":"#ac8facff","u16":[44144,36692,44267,65535]},"Neutral":{"hex":"#af9db2ff","u16":[44953,40352,45818,65535]},"Normal":{"hex":"#cca8b8ff","u16":[52410,43154,47345,65535]},"Positive":{"hex":"#b1a3baff","u16":[45454,41991,47694,65535]},"Visited":{"hex":"#ae91b3ff","u16":[44791,37366,45997,65535]}},"shade":{"Dark":{"hex":"#1c2e53ff","u16":[7146,11947,21404,65535]},"Light":{"hex":"#3f6abeff","u16":[16319,27283,48882,65535]},"Mid":{"hex":"#2c4982ff","u16":[11182,18695,33494,65535]},"Midlight":{"hex":"#395faaff","u16":[14599,24408,43730,65535]},"Shadow":{"hex":"#14213bff","u16":[5085,8502,15231,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#e1ecf9ff","u16":[57803,60603,64030,65535]},"Alternate":{"hex":"#f0f0f0ff","u16":[61680,61680,61680,65535]},"Link":{"hex":"#dee8f8ff","u16":[57136,59680,63648,65535]},"Negative":{"hex":"#f6e3e4ff","u16":[63172,58358,58547,65535]},"Neutral":{"hex":"#fbebd8ff","u16":[64390,60331,55443,65535]},"Normal":{"hex":"#fafafaff","u16":[64250,64250,64250,65535]},"Positive":{"hex":"#c0f7cdff","u16":[49244,63422,52659,65535]},"Visited":{"hex":"#e7e2f1ff","u16":[59376,57957,61951,65535]}},"decoration":{"Focus":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Hover":{"hex":"#3c96e6ff","u16":[15420,38550,59110,65535]}},"foreground":{"Active":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Inactive":{"hex":"#6e747cff","u16":[28270,29812,31868,65535]},"Link":{"hex":"#0064c8ff","u16":[0,25700,51400,65535]},"Negative":{"hex":"#c81e28ff","u16":[51400,7710,10280,65535]},"Neutral":{"hex":"#be7800ff","u16":[48830,30840,0,65535]},"Normal":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Positive":{"hex":"#148232ff","u16":[5140,33410,12850,65535]},"Visited":{"hex":"#6e3cb4ff","u16":[28270,15420,46260,65535]}},"shade":{"Dark":{"hex":"#cacacaff","u16":[51852,51852,51852,65535]},"Light":{"hex":"#dbdbdbff","u16":[56351,56351,56351,65535]},"Mid":{"hex":"#dbdbdbff","u16":[56351,56351,56351,65535]},"Midlight":{"hex":"#ebebebff","u16":[60455,60455,60455,65535]},"Shadow":{"hex":"#aaaaaaff","u16":[43571,43571,43571,65535]}}},"Disabled":{"background":{"Active":{"hex":"#9c9c9cff","u16":[40134,40134,40134,65535]},"Alternate":{"hex":"#a3a3a3ff","u16":[41969,41969,41969,65535]},"Link":{"hex":"#9c9c9cff","u16":[40134,40134,40134,65535]},"Negative":{"hex":"#9c9c9cff","u16":[40134,40134,40134,65535]},"Neutral":{"hex":"#a3a19fff","u16":[42002,41464,40757,65535]},"Normal":{"hex":"#b3b3b3ff","u16":[45925,45925,45925,65535]},"Positive":{"hex":"#9c9c9cff","u16":[40134,40134,40134,65535]},"Visited":{"hex":"#9c9c9cff","u16":[40134,40134,40134,65535]}},"decoration":{"Focus":{"hex":"#000000ff","u16":[0,0,0,65535]},"Hover":{"hex":"#3f4449ff","u16":[16117,17466,18876,65535]}},"foreground":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Inactive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"Negative":{"hex":"#000000ff","u16":[0,0,0,65535]},"Neutral":{"hex":"#2e2d2aff","u16":[11889,11438,10845,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Visited":{"hex":"#000000ff","u16":[0,0,0,65535]}},"shade":{"Dark":{"hex":"#636363ff","u16":[25539,25539,25539,65535]},"Light":{"hex":"#d1d1d1ff","u16":[53724,53724,53724,65535]},"Mid":{"hex":"#989898ff","u16":[38963,38963,38963,65535]},"Midlight":{"hex":"#bebebeff","u16":[48745,48745,48745,65535]},"Shadow":{"hex":"#3f3f3fff","u16":[16108,16108,16108,65535]}}},"Inactive":{"background":{"Active":{"hex":"#bcacb6ff","u16":[48255,44244,46852,65535]},"Alternate":{"hex":"#d9a7a7ff","u16":[55816,42966,42966,65535]},"Link":{"hex":"#beaab4ff","u16":[48755,43711,46247,65535]},"Negative":{"hex":"#d9a3a5ff","u16":[55788,41974,42417,65535]},"Neutral":{"hex":"#d8a79aff","u16":[55521,42906,39591,65535]},"Normal":{"hex":"#e0aeaeff","u16":[57606,44756,44756,65535]},"Positive":{"hex":"#cba99eff","u16":[52203,43396,40625,65535]},"Visited":{"hex":"#d2a1bfff","u16":[53971,41361,49016,65535]}},"decoration":{"Focus":{"hex":"#685d79ff","u16":[26835,23935,30994,65535]},"Hover":{"hex":"#6e7093ff","u16":[28263,28716,37863,65535]}},"foreground":{"Active":{"hex":"#685d79ff","u16":[26835,23935,30994,65535]},"Inactive":{"hex":"#87575aff","u16":[34701,22421,23172,65535]},"Link":{"hex":"#66516cff","u16":[26268,20842,27647,65535]},"Negative":{"hex":"#b8323aff","u16":[47177,12967,14796,65535]},"Neutral":{"hex":"#a56244ff","u16":[42373,25129,17595,65535]},"Normal":{"hex":"#582626ff","u16":[22505,9722,9857,65535]},"Positive":{"hex":"#745b49ff","u16":[29772,23390,18686,65535]},"Visited":{"hex":"#8b337fff","u16":[35736,13202,32697,65535]}},"shade":{"Dark":{"hex":"#a74b4bff","u16":[42978,19314,19314,65535]},"Light":{"hex":"#edd4d4ff","u16":[60981,54373,54373,65535]},"Mid":{"hex":"#d68989ff","u16":[54878,35334,35334,65535]},"Midlight":{"hex":"#e5bbbbff","u16":[58728,48135,48135,65535]},"Shadow":{"hex":"#682f2fff","u16":[26665,11983,11983,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#e7f0faff","u16":[59436,61619,64322,65535]},"Alternate":{"hex":"#f5f7faff","u16":[62965,63479,64250,65535]},"Link":{"hex":"#e4ecf9ff","u16":[58623,60692,63953,65535]},"Negative":{"hex":"#f8e8e8ff","u16":[63632,59579,59737,65535]},"Neutral":{"hex":"#fcf0e2ff","u16":[64647,61600,58007,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#cdf8d7ff","u16":[52778,63812,55361,65535]},"Visited":{"hex":"#ebe6f3ff","u16":[60270,59042,62508,65535]}},"decoration":{"Focus":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Hover":{"hex":"#3c96e6ff","u16":[15420,38550,59110,65535]}},"foreground":{"Active":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Inactive":{"hex":"#6e747cff","u16":[28270,29812,31868,65535]},"Link":{"hex":"#0064c8ff","u16":[0,25700,51400,65535]},"Negative":{"hex":"#c81e28ff","u16":[51400,7710,10280,65535]},"Neutral":{"hex":"#be7800ff","u16":[48830,30840,0,65535]},"Normal":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Positive":{"hex":"#148232ff","u16":[5140,33410,12850,65535]},"Visited":{"hex":"#6e3cb4ff","u16":[28270,15420,46260,65535]}},"shade":{"Dark":{"hex":"#d0d0d0ff","u16":[53502,53502,53502,65535]},"Light":{"hex":"#e1e1e1ff","u16":[57849,57849,57849,65535]},"Mid":{"hex":"#e1e1e1ff","u16":[57849,57849,57849,65535]},"Midlight":{"hex":"#f1f1f1ff","u16":[61836,61836,61836,65535]},"Shadow":{"hex":"#b1b1b1ff","u16":[45587,45587,45587,65535]}}},"Disabled":{"background":{"Active":{"hex":"#a1a1a1ff","u16":[41470,41470,41470,65535]},"Alternate":{"hex":"#a9aeb6ff","u16":[43517,44814,46701,65535]},"Link":{"hex":"#a1a1a1ff","u16":[41470,41470,41470,65535]},"Negative":{"hex":"#a1a1a1ff","u16":[41470,41470,41470,65535]},"Neutral":{"hex":"#acaaa6ff","u16":[44203,43580,42756,65535]},"Normal":{"hex":"#bababaff","u16":[47824,47824,47824,65535]},"Positive":{"hex":"#a1a1a1ff","u16":[41470,41470,41470,65535]},"Visited":{"hex":"#a1a1a1ff","u16":[41470,41470,41470,65535]}},"decoration":{"Focus":{"hex":"#000000ff","u16":[0,0,0,65535]},"Hover":{"hex":"#464c53ff","u16":[18042,19638,21295,65535]}},"foreground":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Inactive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"Negative":{"hex":"#000000ff","u16":[0,0,0,65535]},"Neutral":{"hex":"#3b3936ff","u16":[15111,14546,13799,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Visited":{"hex":"#000000ff","u16":[0,0,0,65535]}},"shade":{"Dark":{"hex":"#676767ff","u16":[26595,26595,26595,65535]},"Light":{"hex":"#d9d9d9ff","u16":[55840,55840,55840,65535]},"Mid":{"hex":"#9d9d9dff","u16":[40451,40451,40451,65535]},"Midlight":{"hex":"#c5c5c5ff","u16":[50597,50597,50597,65535]},"Shadow":{"hex":"#404040ff","u16":[16503,16503,16503,65535]}}},"Inactive":{"background":{"Active":{"hex":"#b9b0baff","u16":[47484,45304,47816,65535]},"Alternate":{"hex":"#deacacff","u16":[57069,44219,44219,65535]},"Link":{"hex":"#bfacb1ff","u16":[49147,44242,45468,65535]},"Negative":{"hex":"#e3a1a5ff","u16":[58283,41378,42506,65535]},"Neutral":{"hex":"#e7a38bff","u16":[59331,42017,35629,65535]},"Normal":{"hex":"#e0aeaeff","u16":[57606,44756,44756,65535]},"Positive":{"hex":"#c9ada1ff","u16":[51730,44382,41375,65535]},"Visited":{"hex":"#d2a5b9ff","u16":[53909,42357,47632,65535]}},"decoration":{"Focus":{"hex":"#777e97ff","u16":[30468,32502,38840,65535]},"Hover":{"hex":"#9695a8ff","u16":[38647,38420,43151,65535]}},"foreground":{"Active":{"hex":"#777e97ff","u16":[30468,32502,38840,65535]},"Inactive":{"hex":"#8a5d61ff","u16":[35555,23861,24995,65535]},"Link":{"hex":"#795e6dff","u16":[30987,24236,28108,65535]},"Negative":{"hex":"#d23647ff","u16":[53874,13874,18122,65535]},"Neutral":{"hex":"#c7662eff","u16":[51237,26226,11810,65535]},"Normal":{"hex":"#683637ff","u16":[26739,13992,14198,65535]},"Positive":{"hex":"#877763ff","u16":[34714,30535,25526,65535]},"Visited":{"hex":"#a74485ff","u16":[42925,17447,34205,65535]}},"shade":{"Dark":{"hex":"#a74b4bff","u16":[42978,19314,19314,65535]},"Light":{"hex":"#edd4d4ff","u16":[60981,54373,54373,65535]},"Mid":{"hex":"#d68989ff","u16":[54878,35334,35334,65535]},"Midlight":{"hex":"#e5bbbbff","u16":[58728,48135,48135,65535]},"Shadow":{"hex":"#682f2fff","u16":[26665,11983,11983,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#d5e5f7ff","u16":[54755,58731,63548,65535]},"Alternate":{"hex":"#e4e6e9ff","u16":[58596,59110,59881,65535]},"Link":{"hex":"#d4e1f6ff","u16":[54413,57846,63140,65535]},"Negative":{"hex":"#f2dadbff","u16":[62249,56129,56404,65535]},"Neutral":{"hex":"#f9e2c5ff","u16":[63925,58008,50547,65535]},"Normal":{"hex":"#f0f1f3ff","u16":[61680,61937,62451,65535]},"Positive":{"hex":"#a3f4b9ff","u16":[41870,62764,47551,65535]},"Visited":{"hex":"#e1daedff","u16":[57722,55985,60955,65535]}},"decoration":{"Focus":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Hover":{"hex":"#3c96e6ff","u16":[15420,38550,59110,65535]}},"foreground":{"Active":{"hex":"#0078d7ff","u16":[0,30840,55255,65535]},"Inactive":{"hex":"#6e747cff","u16":[28270,29812,31868,65535]},"Link":{"hex":"#0064c8ff","u16":[0,25700,51400,65535]},"Negative":{"hex":"#c81e28ff","u16":[51400,7710,10280,65535]},"Neutral":{"hex":"#be7800ff","u16":[48830,30840,0,65535]},"Normal":{"hex":"#14161aff","u16":[5140,5654,6682,65535]},"Positive":{"hex":"#148232ff","u16":[5140,33410,12850,65535]},"Visited":{"hex":"#6e3cb4ff","u16":[28270,15420,46260,65535]}},"shade":{"Dark":{"hex":"#7d8798ff","u16":[32106,34594,39044,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c2c6cfff","u16":[49805,50997,53302,65535]},"Midlight":{"hex":"#f8f9faff","u16":[63815,63928,64153,65535]},"Shadow":{"hex":"#41464fff","u16":[16630,17918,20223,65535]}}},"Disabled":{"background":{"Active":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Alternate":{"hex":"#919395ff","u16":[37232,37693,38382,65535]},"Link":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Negative":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Neutral":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Normal":{"hex":"#a3a5a8ff","u16":[41949,42377,43224,65535]},"Positive":{"hex":"#919295ff","u16":[37164,37542,38294,65535]},"Visited":{"hex":"#919295ff","u16":[37164,37542,38294,65535]}},"decoration":{"Focus":{"hex":"#000000ff","u16":[0,0,0,65535]},"Hover":{"hex":"#2d3135ff","u16":[11668,12578,13559,65535]}},"foreground":{"Active":{"hex":"#000000ff","u16":[0,0,0,65535]},"Inactive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Link":{"hex":"#000000ff","u16":[0,0,0,65535]},"Negative":{"hex":"#000000ff","u16":[0,0,0,65535]},"Neutral":{"hex":"#000000ff","u16":[0,0,0,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#000000ff","u16":[0,0,0,65535]},"Visited":{"hex":"#000000ff","u16":[0,0,0,65535]}},"shade":{"Dark":{"hex":"#5a5c5eff","u16":[23242,23572,24220,65535]},"Light":{"hex":"#c1c2c4ff","u16":[49510,49769,50287,65535]},"Mid":{"hex":"#8b8d91ff","u16":[35646,36152,37147,65535]},"Midlight":{"hex":"#afb0b3ff","u16":[44895,45252,45961,65535]},"Shadow":{"hex":"#3b3b3dff","u16":[15064,15278,15698,65535]}}},"Inactive":{"background":{"Active":{"hex":"#b3a4aeff","u16":[45916,42114,44776,65535]},"Alternate":{"hex":"#cfa0a2ff","u16":[53231,41038,41690,65535]},"Link":{"hex":"#b7a0a6ff","u16":[47097,41246,42754,65535]},"Negative":{"hex":"#d8969bff","u16":[55559,38462,39753,65535]},"Neutral":{"hex":"#dc9885ff","u16":[56453,39006,34072,65535]},"Normal":{"hex":"#d3a2a5ff","u16":[54219,41725,42429,65535]},"Positive":{"hex":"#c1a194ff","u16":[49681,41320,38063,65535]},"Visited":{"hex":"#cc98b0ff","u16":[52348,39016,45188,65535]}},"decoration":{"Focus":{"hex":"#827d92ff","u16":[33431,32030,37546,65535]},"Hover":{"hex":"#a493a2ff","u16":[42217,37748,41538,65535]}},"foreground":{"Active":{"hex":"#827d92ff","u16":[33431,32030,37546,65535]},"Inactive":{"hex":"#8a5c61ff","u16":[35409,23682,24878,65535]},"Link":{"hex":"#7a5d6cff","u16":[31369,23975,27643,65535]},"Negative":{"hex":"#c83d4bff","u16":[51490,15633,19338,65535]},"Neutral":{"hex":"#c06741ff","u16":[49464,26585,16714,65535]},"Normal":{"hex":"#5b292aff","u16":[23296,10599,10792,65535]},"Positive":{"hex":"#867663ff","u16":[34512,30403,25566,65535]},"Visited":{"hex":"#a34681ff","u16":[41856,17915,33264,65535]}},"shade":{"Dark":{"hex":"#924d52ff","u16":[37406,19760,21038,65535]},"Light":{"hex":"#e2c6c7ff","u16":[58098,50765,51147,65535]},"Mid":{"hex":"#c78286ff","u16":[51192,33326,34448,65535]},"Midlight":{"hex":"#d8b0b2ff","u16":[55608,45124,45695,65535]},"Shadow":{"hex":"#5d3134ff","u16":[23810,12578,13391,65535]}}}}},"sha256":"ac9ad89f34b65297f5f939a695deb0672788b6b872aa1b2a0d905731365a67df","source_text":"[General]\nColorScheme=SyntheticInactiveSubgroups\nName=Synthetic Inactive Subgroups\n\n[KDE]\ncontrast=5\n\n[ColorEffects:Inactive]\nEnable=true\nChangeSelectionColor=false\nIntensityEffect=2\nIntensityAmount=0.15\nColorEffect=2\nColorAmount=0.25\nColor=200,0,0\nContrastEffect=2\nContrastAmount=0.3\n\n[ColorEffects:Disabled]\nIntensityEffect=1\nIntensityAmount=-0.5\nColorEffect=1\nColorAmount=0.8\nContrastEffect=1\nContrastAmount=0.5\n\n[Colors:View]\nBackgroundNormal=255,255,255\nBackgroundAlternate=245,247,250\nForegroundNormal=20,22,26\nForegroundInactive=110,116,124\nForegroundActive=0,120,215\nForegroundLink=0,100,200\nForegroundVisited=110,60,180\nForegroundNegative=200,30,40\nForegroundNeutral=190,120,0\nForegroundPositive=20,130,50\nDecorationFocus=0,120,215\nDecorationHover=60,150,230\n\n[Colors:View][Inactive]\nBackgroundNormal=250,250,250\nForegroundNormal=60,62,66\n\n[Colors:Window]\nBackgroundNormal=240,241,243\nBackgroundAlternate=228,230,233\nForegroundNormal=20,22,26\nForegroundInactive=110,116,124\nForegroundActive=0,120,215\nForegroundLink=0,100,200\nForegroundVisited=110,60,180\nForegroundNegative=200,30,40\nForegroundNeutral=190,120,0\nForegroundPositive=20,130,50\nDecorationFocus=0,120,215\nDecorationHover=60,150,230\n\n[Colors:Window][Inactive]\nBackgroundNormal=232,233,235\n\n[Colors:Button]\nBackgroundNormal=248,249,250\nBackgroundAlternate=220,222,226\nForegroundNormal=20,22,26\nForegroundInactive=110,116,124\nForegroundActive=0,120,215\nForegroundLink=0,100,200\nForegroundVisited=110,60,180\nForegroundNegative=200,30,40\nForegroundNeutral=190,120,0\nForegroundPositive=20,130,50\nDecorationFocus=0,120,215\nDecorationHover=60,150,230\n\n[Colors:Selection]\nBackgroundNormal=0,120,215\nBackgroundAlternate=0,100,180\nForegroundNormal=255,255,255\nForegroundInactive=200,220,240\nForegroundActive=255,255,255\nForegroundLink=220,240,255\nForegroundVisited=230,210,255\nForegroundNegative=255,200,200\nForegroundNeutral=255,230,170\nForegroundPositive=200,255,210\nDecorationFocus=0,120,215\nDecorationHover=60,150,230\n\n[Colors:Tooltip]\nBackgroundNormal=250,250,250\nBackgroundAlternate=240,240,240\nForegroundNormal=20,22,26\nForegroundInactive=110,116,124\nForegroundActive=0,120,215\nForegroundLink=0,100,200\nForegroundVisited=110,60,180\nForegroundNegative=200,30,40\nForegroundNeutral=190,120,0\nForegroundPositive=20,130,50\nDecorationFocus=0,120,215\nDecorationHover=60,150,230\n\n[Colors:Complementary]\nBackgroundNormal=30,32,36\nBackgroundAlternate=24,26,30\nForegroundNormal=240,242,245\nForegroundInactive=150,156,164\nForegroundActive=80,170,255\nForegroundLink=80,170,255\nForegroundVisited=180,140,240\nForegroundNegative=255,90,100\nForegroundNeutral=255,190,60\nForegroundPositive=80,210,120\nDecorationFocus=80,170,255\nDecorationHover=110,190,255\n\n[Colors:Header]\nBackgroundNormal=226,228,232\nBackgroundAlternate=240,241,243\nForegroundNormal=20,22,26\nForegroundInactive=110,116,124\nForegroundActive=0,120,215\nForegroundLink=0,100,200\nForegroundVisited=110,60,180\nForegroundNegative=200,30,40\nForegroundNeutral=190,120,0\nForegroundPositive=20,130,50\nDecorationFocus=0,120,215\nDecorationHover=60,150,230\n\n[Colors:Header][Inactive]\nBackgroundNormal=240,241,243\nBackgroundAlternate=226,228,232\nForegroundNormal=90,94,100\nForegroundInactive=130,136,144\nForegroundActive=0,120,215\nForegroundLink=0,100,200\nForegroundVisited=110,60,180\nForegroundNegative=200,30,40\nForegroundNeutral=190,120,0\nForegroundPositive=20,130,50\nDecorationFocus=0,120,215\nDecorationHover=60,150,230\n"}, +{"contrastF":0.4,"frameContrast":0.2,"input":{"Colors:Selection":{"BackgroundNormal":"61,174,233","ForegroundNormal":"255,255,255"},"Colors:View":{"BackgroundNormal":"252,252,252","DecorationFocus":"61,174,233","ForegroundNormal":"35,38,41"},"Colors:Window":{"BackgroundNormal":"239,240,241","ForegroundNormal":"35,38,41"},"General":{"AccentColor":"233,58,154","ColorScheme":"SyntheticAccentKeys","Name":"Synthetic Accent Keys","TintFactor":"0.3","TitlebarIsAccentColored":"true"},"KDE":{"contrast":"4"},"WM":{"activeBackground":"200,40,120","activeForeground":"255,255,255","inactiveBackground":"240,230,236","inactiveForeground":"120,100,110"}},"name":"SyntheticAccentKeys","origin":"synthetic","palette":{"Active":{"Accent":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"AlternateBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Base":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Highlight":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"PlaceholderText":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]},"Text":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"WindowText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}},"Disabled":{"Accent":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"AlternateBase":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Base":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"ButtonText":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Highlight":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"HighlightedText":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#a2c8e0ff","u16":[41595,51411,57571,65535]},"LinkVisited":{"hex":"#d4b8dfff","u16":[54420,47217,57302,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"PlaceholderText":{"hex":"#c0c6ccff","u16":[49437,50892,52336,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]},"Text":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"WindowText":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]}},"Inactive":{"Accent":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"AlternateBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Base":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Highlight":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"HighlightedText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"PlaceholderText":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]},"Text":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"WindowText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}},"purpose":"a light scheme carrying the [General] accent keys (AccentColor, TintFactor, TitlebarIsAccentColored) and a [WM] group; shows which of them KColorScheme itself reads","set_supported":{"Button":false,"Complementary":false,"Header":false,"Selection":true,"Tooltip":false,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"Light":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Mid":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Midlight":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"Shadow":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d8edf7ff","u16":[55550,60800,63569,65535]},"Alternate":{"hex":"#8cccf9ff","u16":[36095,52366,64033,65535]},"Link":{"hex":"#dfeaf2ff","u16":[57339,60254,62215,65535]},"Negative":{"hex":"#f7e4e6ff","u16":[63468,58657,59124,65535]},"Neutral":{"hex":"#fae6d4ff","u16":[64273,59033,54466,65535]},"Normal":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"Positive":{"hex":"#d9eee2ff","u16":[55792,61232,58043,65535]},"Visited":{"hex":"#eee6f2ff","u16":[61262,59074,62174,65535]}},"decoration":{"Focus":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Hover":{"hex":"#c3e3f1ff","u16":[49996,58299,61922,65535]}},"foreground":{"Active":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Inactive":{"hex":"#c0c6ccff","u16":[49437,50892,52336,65535]},"Link":{"hex":"#a2c8e0ff","u16":[41595,51411,57571,65535]},"Negative":{"hex":"#eeafb6ff","u16":[61133,45056,46720,65535]},"Neutral":{"hex":"#f9c088ff","u16":[63954,49376,34973,65535]},"Normal":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Positive":{"hex":"#a9d6bcff","u16":[43472,55048,48360,65535]},"Visited":{"hex":"#d4b8dfff","u16":[54420,47217,57302,65535]}},"shade":{"Dark":{"hex":"#8d8d8dff","u16":[36252,36252,36252,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c8c8c8ff","u16":[51382,51382,51382,65535]},"Midlight":{"hex":"#f7f7f7ff","u16":[63513,63513,63513,65535]},"Shadow":{"hex":"#494949ff","u16":[18840,18840,18840,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"Light":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Mid":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Midlight":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"Shadow":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#191b1dff","u16":[6338,6942,7546,65535]},"Light":{"hex":"#40464cff","u16":[16436,18002,19567,65535]},"Mid":{"hex":"#25292cff","u16":[9506,10412,11317,65535]},"Midlight":{"hex":"#363b40ff","u16":[13917,15242,16568,65535]},"Shadow":{"hex":"#121415ff","u16":[4597,5035,5473,65535]}}},"Disabled":{"background":{"Active":{"hex":"#20333dff","u16":[8232,13011,15777,65535]},"Alternate":{"hex":"#1a1d1fff","u16":[6615,7349,7839,65535]},"Link":{"hex":"#1c3242ff","u16":[7317,12882,17081,65535]},"Negative":{"hex":"#42282eff","u16":[16927,10389,11726,65535]},"Neutral":{"hex":"#3e2c1cff","u16":[16044,11402,7322,65535]},"Normal":{"hex":"#282c30ff","u16":[10289,11269,12249,65535]},"Positive":{"hex":"#1e352bff","u16":[7632,13521,11035,65535]},"Visited":{"hex":"#352c3fff","u16":[13699,11243,16190,65535]}},"decoration":{"Focus":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]},"Hover":{"hex":"#4b616dff","u16":[19293,24988,27940,65535]}},"foreground":{"Active":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]},"Inactive":{"hex":"#50555aff","u16":[20493,21816,23139,65535]},"Link":{"hex":"#245070ff","u16":[9175,20444,28797,65535]},"Negative":{"hex":"#63333bff","u16":[25380,13156,15078,65535]},"Neutral":{"hex":"#6c431fff","u16":[27781,17272,7962,65535]},"Normal":{"hex":"#6e7173ff","u16":[28296,28933,29569,65535]},"Positive":{"hex":"#27573fff","u16":[10032,22245,16194,65535]},"Visited":{"hex":"#4e3a5cff","u16":[19978,14956,23567,65535]}},"shade":{"Dark":{"hex":"#181a1cff","u16":[6042,6617,7193,65535]},"Light":{"hex":"#3e444aff","u16":[16045,17573,19101,65535]},"Mid":{"hex":"#23272aff","u16":[9063,9926,10789,65535]},"Midlight":{"hex":"#353a3fff","u16":[13498,14784,16069,65535]},"Shadow":{"hex":"#111314ff","u16":[4385,4802,5220,65535]}}},"Inactive":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#191b1dff","u16":[6338,6942,7546,65535]},"Light":{"hex":"#40464cff","u16":[16436,18002,19567,65535]},"Mid":{"hex":"#25292cff","u16":[9506,10412,11317,65535]},"Midlight":{"hex":"#363b40ff","u16":[13917,15242,16568,65535]},"Shadow":{"hex":"#121415ff","u16":[4597,5035,5473,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#e4daeaff","u16":[58594,56069,60044,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#b8bfc5ff","u16":[47365,48981,50583,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#cbb1d8ff","u16":[52205,45373,55410,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#5ab7dcff","u16":[23235,46907,56572,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#b3ab74ff","u16":[46003,43823,29737,65535]},"Negative":{"hex":"#aa96b0ff","u16":[43694,38429,45353,65535]},"Neutral":{"hex":"#b29b74ff","u16":[45660,39812,29747,65535]},"Normal":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Positive":{"hex":"#32ada4ff","u16":[12736,44427,42135,65535]},"Visited":{"hex":"#9799d3ff","u16":[38815,39221,54248,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Negative":{"hex":"#b03745ff","u16":[45232,14135,17733,65535]},"Neutral":{"hex":"#c65c00ff","u16":[50886,23644,0,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#176839ff","u16":[5911,26728,14649,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#246689ff","u16":[9206,26259,35163,65535]},"Light":{"hex":"#7ac6f9ff","u16":[31305,50791,63873,65535]},"Mid":{"hex":"#3596c9ff","u16":[13498,38503,51559,65535]},"Midlight":{"hex":"#41b9f7ff","u16":[16627,47428,63511,65535]},"Shadow":{"hex":"#174259ff","u16":[5979,17055,22838,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#d8daddff","u16":[55389,56136,56880,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#e4daeaff","u16":[58594,56069,60044,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#b8bfc5ff","u16":[47365,48981,50583,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#cbb1d8ff","u16":[52205,45373,55410,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#b3d9f4ff","u16":[45899,55787,62650,65535]},"Alternate":{"hex":"#b0d8f3ff","u16":[45158,55455,62438,65535]},"Link":{"hex":"#b5d4efff","u16":[46454,54609,61533,65535]},"Negative":{"hex":"#e8c9cfff","u16":[59622,51622,53083,65535]},"Neutral":{"hex":"#f4cbb0ff","u16":[62731,52266,45109,65535]},"Normal":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"Positive":{"hex":"#75e9b8ff","u16":[30182,59846,47181,65535]},"Visited":{"hex":"#d6cbe6ff","u16":[55093,52228,59148,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#4b88a8ff","u16":[19360,34951,43113,65535]},"Light":{"hex":"#f8fbfeff","u16":[63734,64548,65196,65535]},"Mid":{"hex":"#75c2ecff","u16":[30160,49859,60666,65535]},"Midlight":{"hex":"#d1e7f7ff","u16":[53672,59338,63476,65535]},"Shadow":{"hex":"#2b4d5fff","u16":[10934,19739,24349,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#cfcfcfff","u16":[53208,53208,53208,65535]},"Light":{"hex":"#ddddddff","u16":[56880,56880,56880,65535]},"Mid":{"hex":"#ddddddff","u16":[56880,56880,56880,65535]},"Midlight":{"hex":"#ebebebff","u16":[60288,60288,60288,65535]},"Shadow":{"hex":"#b5b5b5ff","u16":[46599,46599,46599,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d2e8f4ff","u16":[53968,59692,62698,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#dae6eeff","u16":[55898,59123,61286,65535]},"Negative":{"hex":"#f4dfe1ff","u16":[62678,57357,57876,65535]},"Neutral":{"hex":"#f7e1cdff","u16":[63504,57761,52725,65535]},"Normal":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Positive":{"hex":"#d0ebdbff","u16":[53508,60379,56375,65535]},"Visited":{"hex":"#eae1eeff","u16":[60238,57818,61243,65535]}},"decoration":{"Focus":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]},"Hover":{"hex":"#c1e0edff","u16":[49531,57442,60912,65535]}},"foreground":{"Active":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]},"Inactive":{"hex":"#bdc3c8ff","u16":[48657,50093,51518,65535]},"Link":{"hex":"#9fc5ddff","u16":[40903,50598,56692,65535]},"Negative":{"hex":"#eaacb3ff","u16":[60222,44302,45948,65535]},"Neutral":{"hex":"#f5bd86ff","u16":[62984,48622,34500,65535]},"Normal":{"hex":"#a5a6a7ff","u16":[42332,42590,42849,65535]},"Positive":{"hex":"#a6d3b9ff","u16":[42675,54252,47564,65535]},"Visited":{"hex":"#d0b5dcff","u16":[53574,46448,56425,65535]}},"shade":{"Dark":{"hex":"#8a8a8aff","u16":[35533,35533,35533,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c4c4ff","u16":[50497,50497,50497,65535]},"Midlight":{"hex":"#f3f3f3ff","u16":[62379,62379,62379,65535]},"Shadow":{"hex":"#494949ff","u16":[18863,18863,18863,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#cfcfcfff","u16":[53208,53208,53208,65535]},"Light":{"hex":"#ddddddff","u16":[56880,56880,56880,65535]},"Mid":{"hex":"#ddddddff","u16":[56880,56880,56880,65535]},"Midlight":{"hex":"#ebebebff","u16":[60288,60288,60288,65535]},"Shadow":{"hex":"#b5b5b5ff","u16":[46599,46599,46599,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"Light":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Mid":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Midlight":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"Shadow":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d8edf7ff","u16":[55550,60800,63569,65535]},"Alternate":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Link":{"hex":"#dfeaf2ff","u16":[57339,60254,62215,65535]},"Negative":{"hex":"#f7e4e6ff","u16":[63468,58657,59124,65535]},"Neutral":{"hex":"#fae6d4ff","u16":[64273,59033,54466,65535]},"Normal":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"Positive":{"hex":"#d9eee2ff","u16":[55792,61232,58043,65535]},"Visited":{"hex":"#eee6f2ff","u16":[61262,59074,62174,65535]}},"decoration":{"Focus":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Hover":{"hex":"#c3e3f1ff","u16":[49996,58299,61922,65535]}},"foreground":{"Active":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Inactive":{"hex":"#c0c6ccff","u16":[49437,50892,52336,65535]},"Link":{"hex":"#a2c8e0ff","u16":[41595,51411,57571,65535]},"Negative":{"hex":"#eeafb6ff","u16":[61133,45056,46720,65535]},"Neutral":{"hex":"#f9c088ff","u16":[63954,49376,34973,65535]},"Normal":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Positive":{"hex":"#a9d6bcff","u16":[43472,55048,48360,65535]},"Visited":{"hex":"#d4b8dfff","u16":[54420,47217,57302,65535]}},"shade":{"Dark":{"hex":"#8d8d8dff","u16":[36252,36252,36252,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c8c8c8ff","u16":[51382,51382,51382,65535]},"Midlight":{"hex":"#f7f7f7ff","u16":[63513,63513,63513,65535]},"Shadow":{"hex":"#494949ff","u16":[18840,18840,18840,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#d5d5d5ff","u16":[54787,54787,54787,65535]},"Light":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Mid":{"hex":"#e3e3e3ff","u16":[58341,58341,58341,65535]},"Midlight":{"hex":"#f0f0f0ff","u16":[61653,61653,61653,65535]},"Shadow":{"hex":"#bcbcbcff","u16":[48440,48440,48440,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#d8daddff","u16":[55389,56136,56880,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#e4daeaff","u16":[58594,56069,60044,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#b8bfc5ff","u16":[47365,48981,50583,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#cbb1d8ff","u16":[52205,45373,55410,65535]}},"shade":{"Dark":{"hex":"#82878cff","u16":[33341,34691,35987,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bbc0c5ff","u16":[48130,49391,50621,65535]},"Midlight":{"hex":"#ebedeeff","u16":[60472,60791,61110,65535]},"Shadow":{"hex":"#474a4cff","u16":[18200,18937,19645,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#888e93ff","u16":[34977,36393,37752,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c4c8ccff","u16":[50399,51464,52509,65535]},"Midlight":{"hex":"#f6f7f7ff","u16":[63312,63448,63585,65535]},"Shadow":{"hex":"#474a4cff","u16":[18207,18944,19651,65535]}}}}},"sha256":"30632aee976f7120b57c107e3a1bed1cc5bfa141f222044f739c9554bdc8a93c","source_text":"[General]\nColorScheme=SyntheticAccentKeys\nName=Synthetic Accent Keys\nAccentColor=233,58,154\nTintFactor=0.3\nTitlebarIsAccentColored=true\n\n[KDE]\ncontrast=4\n\n[WM]\nactiveBackground=200,40,120\nactiveForeground=255,255,255\ninactiveBackground=240,230,236\ninactiveForeground=120,100,110\n\n[Colors:View]\nBackgroundNormal=252,252,252\nForegroundNormal=35,38,41\nDecorationFocus=61,174,233\n\n[Colors:Window]\nBackgroundNormal=239,240,241\nForegroundNormal=35,38,41\n\n[Colors:Selection]\nBackgroundNormal=61,174,233\nForegroundNormal=255,255,255\n"}, +{"contrastF":0.7000000000000001,"frameContrast":0.2,"input":{},"name":"SyntheticEmpty","origin":"synthetic","palette":{"Active":{"Accent":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"AlternateBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#71757aff","u16":[28983,30156,31282,65535]},"Highlight":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#bdc1c6ff","u16":[48453,49685,50888,65535]},"Midlight":{"hex":"#f9fafaff","u16":[64065,64155,64245,65535]},"PlaceholderText":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15087,15697,16284,65535]},"Text":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"WindowText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}},"Disabled":{"Accent":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"AlternateBase":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Base":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"ButtonText":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Dark":{"hex":"#6b7074ff","u16":[27627,28746,29820,65535]},"Highlight":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"HighlightedText":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#a3cae2ff","u16":[42003,51899,58104,65535]},"LinkVisited":{"hex":"#d6bae1ff","u16":[54932,47677,57833,65535]},"Mid":{"hex":"#b4babfff","u16":[46330,47756,49140,65535]},"Midlight":{"hex":"#eff0f1ff","u16":[61309,61573,61838,65535]},"PlaceholderText":{"hex":"#c2c8ceff","u16":[49903,51372,52827,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15081,15692,16278,65535]},"Text":{"hex":"#aaabacff","u16":[43578,43869,44159,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"WindowText":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]}},"Inactive":{"Accent":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"AlternateBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Base":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#71757aff","u16":[28983,30156,31282,65535]},"Highlight":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"HighlightedText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#bdc1c6ff","u16":[48453,49685,50888,65535]},"Midlight":{"hex":"#f9fafaff","u16":[64065,64155,64245,65535]},"PlaceholderText":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15087,15697,16284,65535]},"Text":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"WindowText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}}},"purpose":"an empty file: every value is KColorScheme's built-in default","set_supported":{"Button":false,"Complementary":false,"Header":false,"Selection":false,"Tooltip":false,"View":false,"Window":false},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#b9b9b9ff","u16":[47575,47575,47575,65535]},"Light":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"Mid":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"Midlight":{"hex":"#e8e8e8ff","u16":[59693,59693,59693,65535]},"Shadow":{"hex":"#868686ff","u16":[34535,34535,34535,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d8edf7ff","u16":[55550,60800,63569,65535]},"Alternate":{"hex":"#8cccf9ff","u16":[36095,52366,64033,65535]},"Link":{"hex":"#dfeaf2ff","u16":[57339,60254,62215,65535]},"Negative":{"hex":"#f7e4e6ff","u16":[63468,58657,59124,65535]},"Neutral":{"hex":"#fae6d4ff","u16":[64273,59033,54466,65535]},"Normal":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"Positive":{"hex":"#d9eee2ff","u16":[55792,61232,58043,65535]},"Visited":{"hex":"#eee6f2ff","u16":[61262,59074,62174,65535]}},"decoration":{"Focus":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Hover":{"hex":"#c3e3f1ff","u16":[49996,58299,61922,65535]}},"foreground":{"Active":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Inactive":{"hex":"#c0c6ccff","u16":[49437,50892,52336,65535]},"Link":{"hex":"#a2c8e0ff","u16":[41595,51411,57571,65535]},"Negative":{"hex":"#eeafb6ff","u16":[61133,45056,46720,65535]},"Neutral":{"hex":"#f9c088ff","u16":[63954,49376,34973,65535]},"Normal":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Positive":{"hex":"#a9d6bcff","u16":[43472,55048,48360,65535]},"Visited":{"hex":"#d4b8dfff","u16":[54420,47217,57302,65535]}},"shade":{"Dark":{"hex":"#757575ff","u16":[30039,30039,30039,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c1c1c1ff","u16":[49575,49575,49575,65535]},"Midlight":{"hex":"#fafafaff","u16":[64224,64224,64224,65535]},"Shadow":{"hex":"#3d3d3dff","u16":[15611,15611,15611,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#b9b9b9ff","u16":[47575,47575,47575,65535]},"Light":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"Mid":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"Midlight":{"hex":"#e8e8e8ff","u16":[59693,59693,59693,65535]},"Shadow":{"hex":"#868686ff","u16":[34535,34535,34535,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#141618ff","u16":[5252,5752,6253,65535]},"Light":{"hex":"#474d54ff","u16":[18170,19901,21631,65535]},"Mid":{"hex":"#24282bff","u16":[9291,10176,11061,65535]},"Midlight":{"hex":"#3a4045ff","u16":[14977,16403,17829,65535]},"Shadow":{"hex":"#0f1012ff","u16":[3809,4172,4535,65535]}}},"Disabled":{"background":{"Active":{"hex":"#20333dff","u16":[8232,13011,15777,65535]},"Alternate":{"hex":"#1a1d1fff","u16":[6615,7349,7839,65535]},"Link":{"hex":"#1c3242ff","u16":[7317,12882,17081,65535]},"Negative":{"hex":"#42282eff","u16":[16927,10389,11726,65535]},"Neutral":{"hex":"#3e2c1cff","u16":[16044,11402,7322,65535]},"Normal":{"hex":"#282c30ff","u16":[10289,11269,12249,65535]},"Positive":{"hex":"#1e352bff","u16":[7632,13521,11035,65535]},"Visited":{"hex":"#352c3fff","u16":[13699,11243,16190,65535]}},"decoration":{"Focus":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]},"Hover":{"hex":"#4b616dff","u16":[19293,24988,27940,65535]}},"foreground":{"Active":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]},"Inactive":{"hex":"#50555aff","u16":[20493,21816,23139,65535]},"Link":{"hex":"#245070ff","u16":[9175,20444,28797,65535]},"Negative":{"hex":"#63333bff","u16":[25380,13156,15078,65535]},"Neutral":{"hex":"#6c431fff","u16":[27781,17272,7962,65535]},"Normal":{"hex":"#6e7173ff","u16":[28296,28933,29569,65535]},"Positive":{"hex":"#27573fff","u16":[10032,22245,16194,65535]},"Visited":{"hex":"#4e3a5cff","u16":[19978,14956,23567,65535]}},"shade":{"Dark":{"hex":"#131517ff","u16":[5006,5483,5960,65535]},"Light":{"hex":"#454c52ff","u16":[17791,19485,21180,65535]},"Mid":{"hex":"#222629ff","u16":[8858,9702,10546,65535]},"Midlight":{"hex":"#393e44ff","u16":[14575,15963,17351,65535]},"Shadow":{"hex":"#0e0f11ff","u16":[3633,3979,4325,65535]}}},"Inactive":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#141618ff","u16":[5252,5752,6253,65535]},"Light":{"hex":"#474d54ff","u16":[18170,19901,21631,65535]},"Mid":{"hex":"#24282bff","u16":[9291,10176,11061,65535]},"Midlight":{"hex":"#3a4045ff","u16":[14977,16403,17829,65535]},"Shadow":{"hex":"#0f1012ff","u16":[3809,4172,4535,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#bddcf1ff","u16":[48617,56620,61865,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#bed8edff","u16":[48753,55447,60903,65535]},"Negative":{"hex":"#eecccfff","u16":[61219,52482,53238,65535]},"Neutral":{"hex":"#f7cfb7ff","u16":[63498,53297,47153,65535]},"Normal":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]},"Positive":{"hex":"#85ecb2ff","u16":[34118,60679,45709,65535]},"Visited":{"hex":"#ddcfe5ff","u16":[56819,53117,58755,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#696d71ff","u16":[27102,28116,29099,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b1b6bcff","u16":[45476,46876,48242,65535]},"Midlight":{"hex":"#eaebecff","u16":[60102,60421,60742,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15073,15636,16183,65535]}}},"Disabled":{"background":{"Active":{"hex":"#b6d4e4ff","u16":[46809,54437,58578,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#c1d1dcff","u16":[49596,53745,56654,65535]},"Negative":{"hex":"#e2c8ccff","u16":[58118,51520,52524,65535]},"Neutral":{"hex":"#e5cbb3ff","u16":[58796,52066,46125,65535]},"Normal":{"hex":"#d3d6d8ff","u16":[54197,54906,55612,65535]},"Positive":{"hex":"#b3d7c5ff","u16":[46026,55365,50692,65535]},"Visited":{"hex":"#d6cbddff","u16":[54939,52124,56916,65535]}},"decoration":{"Focus":{"hex":"#93c6e0ff","u16":[37880,50983,57656,65535]},"Hover":{"hex":"#b4d1deff","u16":[46270,53637,57131,65535]}},"foreground":{"Active":{"hex":"#93c6e0ff","u16":[37880,50983,57656,65535]},"Inactive":{"hex":"#aeb5bbff","u16":[44665,46432,48183,65535]},"Link":{"hex":"#94b6ccff","u16":[37989,46772,52533,65535]},"Negative":{"hex":"#d99fa6ff","u16":[55802,40796,42771,65535]},"Neutral":{"hex":"#e2b080ff","u16":[58104,45186,32907,65535]},"Normal":{"hex":"#95979aff","u16":[38352,38927,39503,65535]},"Positive":{"hex":"#97c5acff","u16":[38695,50589,44220,65535]},"Visited":{"hex":"#c0a7ceff","u16":[49363,42888,52873,65535]}},"shade":{"Dark":{"hex":"#65686cff","u16":[25834,26801,27739,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#a9afb5ff","u16":[43411,45003,46547,65535]},"Midlight":{"hex":"#e0e1e3ff","u16":[57463,57950,58438,65535]},"Shadow":{"hex":"#3a3c3eff","u16":[14890,15447,15988,65535]}}},"Inactive":{"background":{"Active":{"hex":"#bddcf1ff","u16":[48617,56620,61865,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#bed8edff","u16":[48753,55447,60903,65535]},"Negative":{"hex":"#eecccfff","u16":[61219,52482,53238,65535]},"Neutral":{"hex":"#f7cfb7ff","u16":[63498,53297,47153,65535]},"Normal":{"hex":"#dee0e2ff","u16":[57054,57568,58082,65535]},"Positive":{"hex":"#85ecb2ff","u16":[34118,60679,45709,65535]},"Visited":{"hex":"#ddcfe5ff","u16":[56819,53117,58755,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#696d71ff","u16":[27102,28116,29099,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b1b6bcff","u16":[45476,46876,48242,65535]},"Midlight":{"hex":"#eaebecff","u16":[60102,60421,60742,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15073,15636,16183,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#5ab7dcff","u16":[23235,46907,56572,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#b3ab74ff","u16":[46003,43823,29737,65535]},"Negative":{"hex":"#aa96b0ff","u16":[43694,38429,45353,65535]},"Neutral":{"hex":"#b29b74ff","u16":[45660,39812,29747,65535]},"Normal":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Positive":{"hex":"#32ada4ff","u16":[12736,44427,42135,65535]},"Visited":{"hex":"#9799d3ff","u16":[38815,39221,54248,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Negative":{"hex":"#b03745ff","u16":[45232,14135,17733,65535]},"Neutral":{"hex":"#c65c00ff","u16":[50886,23644,0,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#176839ff","u16":[5911,26728,14649,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#1e5571ff","u16":[7628,21759,29137,65535]},"Light":{"hex":"#92cef9ff","u16":[37530,52828,64075,65535]},"Mid":{"hex":"#3392c3ff","u16":[13128,37447,50145,65535]},"Midlight":{"hex":"#52bcf8ff","u16":[21175,48262,63634,65535]},"Shadow":{"hex":"#13374aff","u16":[4954,14132,18924,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#d8daddff","u16":[55389,56136,56880,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#e4daeaff","u16":[58594,56069,60044,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#b8bfc5ff","u16":[47365,48981,50583,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#cbb1d8ff","u16":[52205,45373,55410,65535]}},"shade":{"Dark":{"hex":"#6b7074ff","u16":[27627,28746,29820,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b4babfff","u16":[46330,47756,49140,65535]},"Midlight":{"hex":"#eff0f1ff","u16":[61309,61573,61838,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15081,15692,16278,65535]}}},"Inactive":{"background":{"Active":{"hex":"#b3d9f4ff","u16":[45899,55787,62650,65535]},"Alternate":{"hex":"#b0d8f3ff","u16":[45158,55455,62438,65535]},"Link":{"hex":"#b5d4efff","u16":[46454,54609,61533,65535]},"Negative":{"hex":"#e8c9cfff","u16":[59622,51622,53083,65535]},"Neutral":{"hex":"#f4cbb0ff","u16":[62731,52266,45109,65535]},"Normal":{"hex":"#c2e0f5ff","u16":[49797,57489,62894,65535]},"Positive":{"hex":"#75e9b8ff","u16":[30182,59846,47181,65535]},"Visited":{"hex":"#d6cbe6ff","u16":[55093,52228,59148,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#3e718bff","u16":[16042,28962,35725,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#68bde9ff","u16":[26841,48457,59772,65535]},"Midlight":{"hex":"#d7eaf8ff","u16":[55164,60075,63712,65535]},"Shadow":{"hex":"#23404fff","u16":[9060,16357,20176,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#b2b2b2ff","u16":[45692,45692,45692,65535]},"Light":{"hex":"#ccccccff","u16":[52436,52436,52436,65535]},"Mid":{"hex":"#ccccccff","u16":[52436,52436,52436,65535]},"Midlight":{"hex":"#e3e3e3ff","u16":[58272,58272,58272,65535]},"Shadow":{"hex":"#7b7b7bff","u16":[31695,31695,31695,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d2e8f4ff","u16":[53968,59692,62698,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#dae6eeff","u16":[55898,59123,61286,65535]},"Negative":{"hex":"#f4dfe1ff","u16":[62678,57357,57876,65535]},"Neutral":{"hex":"#f7e1cdff","u16":[63504,57761,52725,65535]},"Normal":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Positive":{"hex":"#d0ebdbff","u16":[53508,60379,56375,65535]},"Visited":{"hex":"#eae1eeff","u16":[60238,57818,61243,65535]}},"decoration":{"Focus":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]},"Hover":{"hex":"#c1e0edff","u16":[49531,57442,60912,65535]}},"foreground":{"Active":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]},"Inactive":{"hex":"#bdc3c8ff","u16":[48657,50093,51518,65535]},"Link":{"hex":"#9fc5ddff","u16":[40903,50598,56692,65535]},"Negative":{"hex":"#eaacb3ff","u16":[60222,44302,45948,65535]},"Neutral":{"hex":"#f5bd86ff","u16":[62984,48622,34500,65535]},"Normal":{"hex":"#a5a6a7ff","u16":[42332,42590,42849,65535]},"Positive":{"hex":"#a6d3b9ff","u16":[42675,54252,47564,65535]},"Visited":{"hex":"#d0b5dcff","u16":[53574,46448,56425,65535]}},"shade":{"Dark":{"hex":"#737373ff","u16":[29444,29444,29444,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bebebeff","u16":[48753,48753,48753,65535]},"Midlight":{"hex":"#f6f6f6ff","u16":[63124,63124,63124,65535]},"Shadow":{"hex":"#3d3d3dff","u16":[15630,15630,15630,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#b2b2b2ff","u16":[45692,45692,45692,65535]},"Light":{"hex":"#ccccccff","u16":[52436,52436,52436,65535]},"Mid":{"hex":"#ccccccff","u16":[52436,52436,52436,65535]},"Midlight":{"hex":"#e3e3e3ff","u16":[58272,58272,58272,65535]},"Shadow":{"hex":"#7b7b7bff","u16":[31695,31695,31695,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#ecf6fcff","u16":[60771,63172,64842,65535]},"Alternate":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Link":{"hex":"#e8f0f8ff","u16":[59682,61808,63658,65535]},"Negative":{"hex":"#faecedff","u16":[64334,60663,60940,65535]},"Neutral":{"hex":"#fef1eaff","u16":[65257,61930,60105,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#ddfbe8ff","u16":[56769,64393,59555,65535]},"Visited":{"hex":"#f2ecf6ff","u16":[62319,60713,63097,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#bdbdbdff","u16":[48684,48684,48684,65535]},"Light":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Mid":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Midlight":{"hex":"#ecececff","u16":[60542,60542,60542,65535]},"Shadow":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]}}},"Disabled":{"background":{"Active":{"hex":"#dceff9ff","u16":[56552,61460,64057,65535]},"Alternate":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Link":{"hex":"#e3edf4ff","u16":[58230,60929,62750,65535]},"Negative":{"hex":"#f9e7e9ff","u16":[63907,59452,59885,65535]},"Neutral":{"hex":"#fce9d8ff","u16":[64691,59802,55559,65535]},"Normal":{"hex":"#f3f3f3ff","u16":[62470,62470,62470,65535]},"Positive":{"hex":"#def0e6ff","u16":[57106,61751,59018,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61864,59835,62710,65535]}},"decoration":{"Focus":{"hex":"#a3daf5ff","u16":[41938,56116,63017,65535]},"Hover":{"hex":"#c3e5f3ff","u16":[50224,58822,62559,65535]}},"foreground":{"Active":{"hex":"#a3daf5ff","u16":[41938,56116,63017,65535]},"Inactive":{"hex":"#c2c8ceff","u16":[49903,51372,52827,65535]},"Link":{"hex":"#a3cae2ff","u16":[42003,51899,58104,65535]},"Negative":{"hex":"#f0b1b8ff","u16":[61685,45506,47184,65535]},"Neutral":{"hex":"#fbc289ff","u16":[64548,49824,35228,65535]},"Normal":{"hex":"#aaabacff","u16":[43578,43869,44159,65535]},"Positive":{"hex":"#abd8beff","u16":[43950,55525,48837,65535]},"Visited":{"hex":"#d6bae1ff","u16":[54932,47677,57833,65535]}},"shade":{"Dark":{"hex":"#767676ff","u16":[30397,30397,30397,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c3c3c3ff","u16":[50064,50064,50064,65535]},"Midlight":{"hex":"#fcfcfcff","u16":[64880,64880,64880,65535]},"Shadow":{"hex":"#3d3d3dff","u16":[15585,15585,15585,65535]}}},"Inactive":{"background":{"Active":{"hex":"#ecf6fcff","u16":[60771,63172,64842,65535]},"Alternate":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Link":{"hex":"#e8f0f8ff","u16":[59682,61808,63658,65535]},"Negative":{"hex":"#faecedff","u16":[64334,60663,60940,65535]},"Neutral":{"hex":"#fef1eaff","u16":[65257,61930,60105,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#ddfbe8ff","u16":[56769,64393,59555,65535]},"Visited":{"hex":"#f2ecf6ff","u16":[62319,60713,63097,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#bdbdbdff","u16":[48684,48684,48684,65535]},"Light":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Mid":{"hex":"#d6d6d6ff","u16":[54997,54997,54997,65535]},"Midlight":{"hex":"#ecececff","u16":[60542,60542,60542,65535]},"Shadow":{"hex":"#8d8d8dff","u16":[36141,36141,36141,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#71757aff","u16":[28983,30156,31282,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bdc1c6ff","u16":[48453,49685,50888,65535]},"Midlight":{"hex":"#f9fafaff","u16":[64065,64155,64245,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15087,15697,16284,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c9e2f0ff","u16":[51633,58104,61582,65535]},"Alternate":{"hex":"#d8daddff","u16":[55389,56136,56880,65535]},"Link":{"hex":"#d1e0eaff","u16":[53755,57510,60082,65535]},"Negative":{"hex":"#efd8dbff","u16":[61309,55555,56290,65535]},"Neutral":{"hex":"#f2dac5ff","u16":[62133,56004,50594,65535]},"Normal":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Positive":{"hex":"#c3e6d3ff","u16":[50108,59146,54349,65535]},"Visited":{"hex":"#e4daeaff","u16":[58594,56069,60044,65535]}},"decoration":{"Focus":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Hover":{"hex":"#bddbe9ff","u16":[48529,56272,59804,65535]}},"foreground":{"Active":{"hex":"#9cd1ebff","u16":[40116,53610,60344,65535]},"Inactive":{"hex":"#b8bfc5ff","u16":[47365,48981,50583,65535]},"Link":{"hex":"#9bc0d9ff","u16":[39710,49467,55672,65535]},"Negative":{"hex":"#e5a8afff","u16":[58782,43247,45071,65535]},"Neutral":{"hex":"#efb984ff","u16":[61470,47568,34027,65535]},"Normal":{"hex":"#a0a1a3ff","u16":[41059,41475,41892,65535]},"Positive":{"hex":"#a1cfb5ff","u16":[41402,53136,46608,65535]},"Visited":{"hex":"#cbb1d8ff","u16":[52205,45373,55410,65535]}},"shade":{"Dark":{"hex":"#6b7074ff","u16":[27627,28746,29820,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#b4babfff","u16":[46330,47756,49140,65535]},"Midlight":{"hex":"#eff0f1ff","u16":[61309,61573,61838,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15081,15692,16278,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d5eaf7ff","u16":[54852,60051,63563,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#d4e5f2ff","u16":[54604,58745,62251,65535]},"Negative":{"hex":"#f5dddfff","u16":[62983,56715,57221,65535]},"Neutral":{"hex":"#fbe1d2ff","u16":[64389,57815,54057,65535]},"Normal":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Positive":{"hex":"#adf6caff","u16":[44364,63230,51951,65535]},"Visited":{"hex":"#e8deeeff","u16":[59726,57050,61072,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#71757aff","u16":[28983,30156,31282,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bdc1c6ff","u16":[48453,49685,50888,65535]},"Midlight":{"hex":"#f9fafaff","u16":[64065,64155,64245,65535]},"Shadow":{"hex":"#3b3d3fff","u16":[15087,15697,16284,65535]}}}}},"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","source_text":""}, +{"contrastF":1.5,"frameContrast":0.2,"input":{"ColorEffects:Disabled":{"ColorEffect":"0","ContrastAmount":"2.5","ContrastEffect":"1","IntensityAmount":"0.4","IntensityEffect":"7"},"Colors:Selection":{"BackgroundNormal":"0,0,0"},"Colors:View":{"BackgroundAlternate":"10,20,30,128","BackgroundNormal":"#ff8800","ForegroundActive":"red","ForegroundInactive":"300,20","ForegroundLink":"","ForegroundNormal":"12 , 34 , 56","ForegroundPositive":"0,200,0","ForegroundVisited":"256,-1,1000"},"Colors:Window":{"BackgroundNormal":"200,200,200","ForegroundNormal":"1,2,3"},"General":{"ColorScheme":"SyntheticMalformed","Name":"Synthetic Malformed"},"KDE":{"contrast":"15"}},"name":"SyntheticMalformed","origin":"synthetic","palette":{"Active":{"Accent":{"hex":"#000000ff","u16":[0,0,0,65535]},"AlternateBase":{"hex":"#0a141e80","u16":[2570,5140,7710,32896]},"Base":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Highlight":{"hex":"#000000ff","u16":[0,0,0,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Link":{"valid":false},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"PlaceholderText":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]},"Text":{"hex":"#0c2238ff","u16":[3084,8738,14392,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"WindowText":{"hex":"#010203ff","u16":[257,514,771,65535]}},"Disabled":{"Accent":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"AlternateBase":{"hex":"#0a141e80","u16":[2570,5140,7710,32896]},"Base":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Highlight":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"HighlightedText":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Link":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"LinkVisited":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"PlaceholderText":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]},"Text":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"WindowText":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"Inactive":{"Accent":{"hex":"#919191ff","u16":[37249,37249,37249,65535]},"AlternateBase":{"hex":"#0a141e80","u16":[2570,5140,7710,32896]},"Base":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Highlight":{"hex":"#919191ff","u16":[37249,37249,37249,65535]},"HighlightedText":{"hex":"#010203ff","u16":[257,514,771,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Link":{"valid":false},"LinkVisited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"PlaceholderText":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]},"Text":{"hex":"#0c2238ff","u16":[3084,8738,14392,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"WindowText":{"hex":"#010203ff","u16":[257,514,771,65535]}}},"purpose":"value syntax edge cases (hex, alpha, spaces, named colour, too few components, empty value), an out-of-range contrast and effect id, a duplicated key and a duplicated group","set_supported":{"Button":false,"Complementary":false,"Header":false,"Selection":true,"Tooltip":false,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#979797ff","u16":[38725,38725,38725,65535]},"Light":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"Mid":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"Midlight":{"hex":"#e0e0e0ff","u16":[57652,57652,57652,65535]},"Shadow":{"hex":"#000000ff","u16":[0,0,0,65535]}}},"Disabled":{"background":{"Active":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Negative":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Neutral":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Visited":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}},"decoration":{"Focus":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Hover":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}},"foreground":{"Active":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Inactive":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Link":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Negative":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Neutral":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Visited":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]}},"shade":{"Dark":{"hex":"#979797ff","u16":[38725,38725,38725,65535]},"Light":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"Mid":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"Midlight":{"hex":"#e0e0e0ff","u16":[57652,57652,57652,65535]},"Shadow":{"hex":"#000000ff","u16":[0,0,0,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#979797ff","u16":[38725,38725,38725,65535]},"Light":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"Mid":{"hex":"#c0c0c0ff","u16":[49288,49288,49288,65535]},"Midlight":{"hex":"#e0e0e0ff","u16":[57652,57652,57652,65535]},"Shadow":{"hex":"#000000ff","u16":[0,0,0,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#0f1012ff","u16":[3790,4151,4512,65535]},"Light":{"hex":"#4d545bff","u16":[19725,21604,23482,65535]},"Mid":{"hex":"#23272aff","u16":[9070,9934,10798,65535]},"Midlight":{"hex":"#3e444aff","u16":[15953,17472,18992,65535]},"Shadow":{"hex":"#0b0c0dff","u16":[2749,3010,3272,65535]}}},"Disabled":{"background":{"Active":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Negative":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Neutral":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Visited":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]}},"decoration":{"Focus":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Hover":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]}},"foreground":{"Active":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Inactive":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Link":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Negative":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Neutral":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Visited":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]}},"shade":{"Dark":{"hex":"#0f1012ff","u16":[3790,4151,4512,65535]},"Light":{"hex":"#4d545bff","u16":[19725,21604,23482,65535]},"Mid":{"hex":"#23272aff","u16":[9070,9934,10798,65535]},"Midlight":{"hex":"#3e444aff","u16":[15953,17472,18992,65535]},"Shadow":{"hex":"#0b0c0dff","u16":[2749,3010,3272,65535]}}},"Inactive":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#0f1012ff","u16":[3790,4151,4512,65535]},"Light":{"hex":"#4d545bff","u16":[19725,21604,23482,65535]},"Mid":{"hex":"#23272aff","u16":[9070,9934,10798,65535]},"Midlight":{"hex":"#3e444aff","u16":[15953,17472,18992,65535]},"Shadow":{"hex":"#0b0c0dff","u16":[2749,3010,3272,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#8fc9ecff","u16":[36692,51778,60566,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#98c5e6ff","u16":[38948,50592,59122,65535]},"Negative":{"hex":"#e3b4b7ff","u16":[58317,46137,47125,65535]},"Neutral":{"hex":"#f6b386ff","u16":[63180,46046,34370,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#71d59cff","u16":[29144,54796,39975,65535]},"Visited":{"hex":"#ccb8d5ff","u16":[52449,47208,54864,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#010203ff","u16":[257,514,771,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Negative":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Neutral":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Visited":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"decoration":{"Focus":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Hover":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"foreground":{"Active":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Inactive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Link":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Negative":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Neutral":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Visited":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"shade":{"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]}}},"Inactive":{"background":{"Active":{"hex":"#8fc9ecff","u16":[36692,51778,60566,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#98c5e6ff","u16":[38948,50592,59122,65535]},"Negative":{"hex":"#e3b4b7ff","u16":[58317,46137,47125,65535]},"Neutral":{"hex":"#f6b386ff","u16":[63180,46046,34370,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#71d59cff","u16":[29144,54796,39975,65535]},"Visited":{"hex":"#ccb8d5ff","u16":[52449,47208,54864,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#010203ff","u16":[257,514,771,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#343434ff","u16":[13263,13263,13263,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#352710ff","u16":[13520,10047,4008,65535]},"Negative":{"hex":"#2d0e12ff","u16":[11474,3586,4498,65535]},"Neutral":{"hex":"#2e1500ff","u16":[11869,5515,0,65535]},"Normal":{"hex":"#000000ff","u16":[0,0,0,65535]},"Positive":{"hex":"#061b0fff","u16":[1557,7040,3859,65535]},"Visited":{"hex":"#25152bff","u16":[9456,5429,11103,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Negative":{"hex":"#b03745ff","u16":[45232,14135,17733,65535]},"Neutral":{"hex":"#c65c00ff","u16":[50886,23644,0,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#176839ff","u16":[5911,26728,14649,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#acacacff","u16":[44180,44180,44180,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#7d7d7dff","u16":[32240,32240,32240,65535]},"Midlight":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]},"Shadow":{"hex":"#cfcfcfff","u16":[53121,53121,53121,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Negative":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Neutral":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Visited":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"decoration":{"Focus":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Hover":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"foreground":{"Active":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Inactive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Link":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Negative":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Neutral":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Visited":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"shade":{"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]}}},"Inactive":{"background":{"Active":{"hex":"#4f9ec8ff","u16":[20250,40707,51388,65535]},"Alternate":{"hex":"#9b9d9eff","u16":[39930,40284,40633,65535]},"Link":{"hex":"#5193beff","u16":[20929,37794,48845,65535]},"Negative":{"hex":"#ca747cff","u16":[51945,29763,31762,65535]},"Neutral":{"hex":"#eb750cff","u16":[60466,30148,3093,65535]},"Normal":{"hex":"#919191ff","u16":[37249,37249,37249,65535]},"Positive":{"hex":"#2faa63ff","u16":[12113,43715,25456,65535]},"Visited":{"hex":"#a47fb3ff","u16":[42031,32593,46113,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#010203ff","u16":[257,514,771,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#333333ff","u16":[13079,13079,13079,65535]},"Light":{"hex":"#bababaff","u16":[47703,47703,47703,65535]},"Mid":{"hex":"#777777ff","u16":[30540,30540,30540,65535]},"Midlight":{"hex":"#a3a3a3ff","u16":[41799,41799,41799,65535]},"Shadow":{"hex":"#222222ff","u16":[8754,8754,8754,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#8d8d8dff","u16":[36279,36279,36279,65535]},"Light":{"hex":"#b9b9b9ff","u16":[47486,47486,47486,65535]},"Mid":{"hex":"#b9b9b9ff","u16":[47486,47486,47486,65535]},"Midlight":{"hex":"#dbdbdbff","u16":[56169,56169,56169,65535]},"Shadow":{"hex":"#000000ff","u16":[0,0,0,65535]}}},"Disabled":{"background":{"Active":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Negative":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Neutral":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Visited":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]}},"decoration":{"Focus":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Hover":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]}},"foreground":{"Active":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Inactive":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Link":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Negative":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Neutral":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Visited":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]}},"shade":{"Dark":{"hex":"#8d8d8dff","u16":[36279,36279,36279,65535]},"Light":{"hex":"#b9b9b9ff","u16":[47486,47486,47486,65535]},"Mid":{"hex":"#b9b9b9ff","u16":[47486,47486,47486,65535]},"Midlight":{"hex":"#dbdbdbff","u16":[56169,56169,56169,65535]},"Shadow":{"hex":"#000000ff","u16":[0,0,0,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#8d8d8dff","u16":[36279,36279,36279,65535]},"Light":{"hex":"#b9b9b9ff","u16":[47486,47486,47486,65535]},"Mid":{"hex":"#b9b9b9ff","u16":[47486,47486,47486,65535]},"Midlight":{"hex":"#dbdbdbff","u16":[56169,56169,56169,65535]},"Shadow":{"hex":"#000000ff","u16":[0,0,0,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#ff7971ff","u16":[65535,30980,28951,65535]},"Alternate":{"hex":"#0a141e80","u16":[2570,5140,7710,32896]},"Link":{"hex":"#e27800ff","u16":[57972,30918,0,65535]},"Negative":{"hex":"#f67f68ff","u16":[63116,32718,26783,65535]},"Neutral":{"hex":"#ff7f25ff","u16":[65535,32583,9453,65535]},"Normal":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Positive":{"hex":"#02c700ff","u16":[430,51261,0,65535]},"Visited":{"hex":"#cd9195ff","u16":[52611,37234,38193,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"valid":false},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#0c2238ff","u16":[3084,8738,14392,65535]},"Positive":{"hex":"#00c800ff","u16":[0,51400,0,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#5a3000ff","u16":[23011,12272,0,65535]},"Light":{"hex":"#ffc7afff","u16":[65535,51085,44976,65535]},"Mid":{"hex":"#cf6e00ff","u16":[53200,28374,0,65535]},"Midlight":{"hex":"#ffa26eff","u16":[65535,41684,28368,65535]},"Shadow":{"hex":"#3a1f00ff","u16":[14865,7928,0,65535]}}},"Disabled":{"background":{"Active":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Alternate":{"hex":"#0a141e80","u16":[2570,5140,7710,32896]},"Link":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Negative":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Neutral":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Normal":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Positive":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Visited":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]}},"decoration":{"Focus":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Hover":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]}},"foreground":{"Active":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Inactive":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Link":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Negative":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Neutral":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Normal":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Positive":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Visited":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]}},"shade":{"Dark":{"hex":"#5a3000ff","u16":[23011,12272,0,65535]},"Light":{"hex":"#ffc7afff","u16":[65535,51085,44976,65535]},"Mid":{"hex":"#cf6e00ff","u16":[53200,28374,0,65535]},"Midlight":{"hex":"#ffa26eff","u16":[65535,41684,28368,65535]},"Shadow":{"hex":"#3a1f00ff","u16":[14865,7928,0,65535]}}},"Inactive":{"background":{"Active":{"hex":"#ff7971ff","u16":[65535,30980,28951,65535]},"Alternate":{"hex":"#0a141e80","u16":[2570,5140,7710,32896]},"Link":{"hex":"#e27800ff","u16":[57972,30918,0,65535]},"Negative":{"hex":"#f67f68ff","u16":[63116,32718,26783,65535]},"Neutral":{"hex":"#ff7f25ff","u16":[65535,32583,9453,65535]},"Normal":{"hex":"#ff8800ff","u16":[65535,34952,0,65535]},"Positive":{"hex":"#02c700ff","u16":[430,51261,0,65535]},"Visited":{"hex":"#cd9195ff","u16":[52611,37234,38193,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"valid":false},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#0c2238ff","u16":[3084,8738,14392,65535]},"Positive":{"hex":"#00c800ff","u16":[0,51400,0,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#5a3000ff","u16":[23011,12272,0,65535]},"Light":{"hex":"#ffc7afff","u16":[65535,51085,44976,65535]},"Mid":{"hex":"#cf6e00ff","u16":[53200,28374,0,65535]},"Midlight":{"hex":"#ffa26eff","u16":[65535,41684,28368,65535]},"Shadow":{"hex":"#3a1f00ff","u16":[14865,7928,0,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#8fc9ecff","u16":[36692,51778,60566,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#98c5e6ff","u16":[38948,50592,59122,65535]},"Negative":{"hex":"#e3b4b7ff","u16":[58317,46137,47125,65535]},"Neutral":{"hex":"#f6b386ff","u16":[63180,46046,34370,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#71d59cff","u16":[29144,54796,39975,65535]},"Visited":{"hex":"#ccb8d5ff","u16":[52449,47208,54864,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#010203ff","u16":[257,514,771,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]}}},"Disabled":{"background":{"Active":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Negative":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Neutral":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Visited":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"decoration":{"Focus":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Hover":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"foreground":{"Active":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Inactive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Link":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Negative":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Neutral":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Visited":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]}},"shade":{"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]}}},"Inactive":{"background":{"Active":{"hex":"#8fc9ecff","u16":[36692,51778,60566,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#98c5e6ff","u16":[38948,50592,59122,65535]},"Negative":{"hex":"#e3b4b7ff","u16":[58317,46137,47125,65535]},"Neutral":{"hex":"#f6b386ff","u16":[63180,46046,34370,65535]},"Normal":{"hex":"#c8c8c8ff","u16":[51400,51400,51400,65535]},"Positive":{"hex":"#71d59cff","u16":[29144,54796,39975,65535]},"Visited":{"hex":"#ccb8d5ff","u16":[52449,47208,54864,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#010203ff","u16":[257,514,771,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#464646ff","u16":[18048,18048,18048,65535]},"Light":{"hex":"#fafafaff","u16":[64277,64277,64277,65535]},"Mid":{"hex":"#9f9f9fff","u16":[40931,40931,40931,65535]},"Midlight":{"hex":"#d8d8d8ff","u16":[55573,55573,55573,65535]},"Shadow":{"hex":"#2a2a2aff","u16":[10816,10816,10816,65535]}}}}},"sha256":"b9d9f11d67c015390ea379ed5cf717f876432fe5377aa951b2692c68916bb4cf","source_text":"[General]\nColorScheme=SyntheticMalformed\nName=Synthetic Malformed\n\n[KDE]\ncontrast=15\n\n[ColorEffects:Disabled]\nIntensityEffect=7\nIntensityAmount=0.4\nColorEffect=0\nContrastEffect=1\nContrastAmount=2.5\n\n[Colors:View]\nBackgroundNormal=#ff8800\nBackgroundAlternate=10,20,30,128\nForegroundNormal= 12 , 34 , 56 \nForegroundInactive=300,20\nForegroundActive=red\nForegroundLink=\nForegroundVisited=256,-1,1000\nForegroundPositive=0,100,0\nForegroundPositive=0,200,0\n\n[Colors:Window]\nBackgroundNormal=100,100,100\nForegroundNormal=1,2,3\n\n[Colors:Window]\nBackgroundNormal=200,200,200\n\n[Colors:Selection]\nBackgroundNormal=0,0,0\n"}, +{"contrastF":0.7000000000000001,"frameContrast":0.2,"input":{"ColorEffects:Inactive":{"ChangeSelectionColor":"off","Enable":""},"Colors:View":{"BackgroundAlternate":"#abc","BackgroundNormal":"+250,240,230","DecorationFocus":"#803daee9","ForegroundActive":"Red","ForegroundInactive":"#123456789abc","ForegroundLink":"grey","ForegroundNegative":"10,20,30,","ForegroundNeutral":"0x10,20,30","ForegroundNormal":"#123456789","ForegroundPositive":"transparent","ForegroundVisited":"rebeccapurple"},"Colors:Window":{"BackgroundNormal":"200,190,180"},"General":{"ColorScheme":"SyntheticValueForms","Name":"Synthetic Value Forms"}},"name":"SyntheticValueForms","origin":"synthetic","palette":{"Active":{"Accent":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"AlternateBase":{"hex":"#aabbccff","u16":[43690,48059,52428,65535]},"Base":{"hex":"#faf0e6ff","u16":[64250,61680,59110,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Dark":{"hex":"#615c58ff","u16":[25011,23760,22510,65535]},"Highlight":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"HighlightedText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Light":{"hex":"#eae6e2ff","u16":[60012,59007,58046,65535]},"Link":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"LinkVisited":{"valid":false},"Mid":{"hex":"#a59d95ff","u16":[42502,40377,38252,65535]},"Midlight":{"hex":"#d4cbc3ff","u16":[54432,52252,50101,65535]},"PlaceholderText":{"hex":"#12569aff","u16":[4660,22136,39612,65535]},"Shadow":{"hex":"#3c3936ff","u16":[15322,14556,13790,65535]},"Text":{"hex":"#124578ff","u16":[4657,17764,30871,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#c8beb4ff","u16":[51400,48830,46260,65535]},"WindowText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]}},"Disabled":{"Accent":{"hex":"#bfb5acff","u16":[48996,46547,44097,65535]},"AlternateBase":{"hex":"#9fb3c6ff","u16":[40797,45925,50935,65535]},"Base":{"hex":"#f6e3d0ff","u16":[63220,58439,53409,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"ButtonText":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Dark":{"hex":"#5d5853ff","u16":[23841,22649,21457,65535]},"Highlight":{"hex":"#bfb5acff","u16":[48996,46547,44097,65535]},"HighlightedText":{"hex":"#88827dff","u16":[34848,33514,32178,65535]},"Light":{"hex":"#e1dbd6ff","u16":[57767,56313,54905,65535]},"Link":{"hex":"#c6bfb8ff","u16":[50984,49158,47320,65535]},"LinkVisited":{"hex":"#9b958fff","u16":[39810,38217,36625,65535]},"Mid":{"hex":"#9e968fff","u16":[40692,38658,36623,65535]},"Midlight":{"hex":"#cdc2b8ff","u16":[52581,49963,47344,65535]},"PlaceholderText":{"hex":"#9eb2c6ff","u16":[40507,45717,50805,65535]},"Shadow":{"hex":"#3a3734ff","u16":[14913,14167,13422,65535]},"Text":{"hex":"#9facb9ff","u16":[40904,44209,47470,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#bfb5acff","u16":[48996,46547,44097,65535]},"WindowText":{"hex":"#88827dff","u16":[34848,33514,32178,65535]}},"Inactive":{"Accent":{"hex":"#00aff0ff","u16":[0,45047,61740,65535]},"AlternateBase":{"hex":"#9bbddcff","u16":[39872,48547,56423,65535]},"Base":{"hex":"#ffefdfff","u16":[65535,61450,57236,65535]},"BrightText":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Button":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"ButtonText":{"hex":"#343537ff","u16":[13414,13729,14039,65535]},"Dark":{"hex":"#655c52ff","u16":[25924,23591,21115,65535]},"Highlight":{"hex":"#00aff0ff","u16":[0,45047,61740,65535]},"HighlightedText":{"hex":"#f2faffff","u16":[62257,64220,65535,65535]},"Light":{"hex":"#ece5deff","u16":[60771,58875,57027,65535]},"Link":{"hex":"#8a847dff","u16":[35549,33906,32214,65535]},"LinkVisited":{"hex":"#292623ff","u16":[10494,9731,8930,65535]},"Mid":{"hex":"#ab9c8cff","u16":[44055,40090,35883,65535]},"Midlight":{"hex":"#dacabaff","u16":[56041,51960,47744,65535]},"PlaceholderText":{"hex":"#356082ff","u16":[13566,24734,33448,65535]},"Shadow":{"hex":"#3e3832ff","u16":[15881,14452,12935,65535]},"Text":{"hex":"#354f65ff","u16":[13694,20399,26017,65535]},"ToolTipBase":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"ToolTipText":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Window":{"hex":"#cfbda9ff","u16":[53278,48483,43395,65535]},"WindowText":{"hex":"#322f2dff","u16":[12773,12144,11495,65535]}}},"purpose":"more value forms: an empty boolean (Inactive effects Enable=), a signed component, the 3-, 9- and 12-digit hex forms, colour names in other cases, transparent, a trailing comma, a hex component and an unknown CSS name","set_supported":{"Button":false,"Complementary":false,"Header":false,"Selection":false,"Tooltip":false,"View":true,"Window":true},"sets":{"Button":{"Active":{"background":{"Active":{"hex":"#e8f3fbff","u16":[59585,62560,64614,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#e4eef7ff","u16":[58702,61204,63367,65535]},"Negative":{"hex":"#f9e9eaff","u16":[64094,59877,60196,65535]},"Neutral":{"hex":"#fdeee5ff","u16":[65131,61115,58894,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d4fae2ff","u16":[54552,64146,58102,65535]},"Visited":{"hex":"#f1e9f4ff","u16":[61816,59985,62701,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#b9b9b9ff","u16":[47575,47575,47575,65535]},"Light":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"Mid":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"Midlight":{"hex":"#e8e8e8ff","u16":[59693,59693,59693,65535]},"Shadow":{"hex":"#868686ff","u16":[34535,34535,34535,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d8edf7ff","u16":[55550,60800,63569,65535]},"Alternate":{"hex":"#8cccf9ff","u16":[36095,52366,64033,65535]},"Link":{"hex":"#dfeaf2ff","u16":[57339,60254,62215,65535]},"Negative":{"hex":"#f7e4e6ff","u16":[63468,58657,59124,65535]},"Neutral":{"hex":"#fae6d4ff","u16":[64273,59033,54466,65535]},"Normal":{"hex":"#f0f0f0ff","u16":[61735,61735,61735,65535]},"Positive":{"hex":"#d9eee2ff","u16":[55792,61232,58043,65535]},"Visited":{"hex":"#eee6f2ff","u16":[61262,59074,62174,65535]}},"decoration":{"Focus":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Hover":{"hex":"#c3e3f1ff","u16":[49996,58299,61922,65535]}},"foreground":{"Active":{"hex":"#a2d8f3ff","u16":[41654,55609,62421,65535]},"Inactive":{"hex":"#c0c6ccff","u16":[49437,50892,52336,65535]},"Link":{"hex":"#a2c8e0ff","u16":[41595,51411,57571,65535]},"Negative":{"hex":"#eeafb6ff","u16":[61133,45056,46720,65535]},"Neutral":{"hex":"#f9c088ff","u16":[63954,49376,34973,65535]},"Normal":{"hex":"#a8a9aaff","u16":[43111,43390,43668,65535]},"Positive":{"hex":"#a9d6bcff","u16":[43472,55048,48360,65535]},"Visited":{"hex":"#d4b8dfff","u16":[54420,47217,57302,65535]}},"shade":{"Dark":{"hex":"#757575ff","u16":[30039,30039,30039,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#c1c1c1ff","u16":[49575,49575,49575,65535]},"Midlight":{"hex":"#fafafaff","u16":[64224,64224,64224,65535]},"Shadow":{"hex":"#3d3d3dff","u16":[15611,15611,15611,65535]}}},"Inactive":{"background":{"Active":{"hex":"#e3f5fcff","u16":[58341,63009,64822,65535]},"Alternate":{"hex":"#9cd5ffff","u16":[40208,54680,65535,65535]},"Link":{"hex":"#e5eff6ff","u16":[58751,61508,63159,65535]},"Negative":{"hex":"#ffe8edff","u16":[65421,59691,60919,65535]},"Neutral":{"hex":"#fcefe0ff","u16":[64758,61502,57680,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#d2fae7ff","u16":[54011,64324,59265,65535]},"Visited":{"hex":"#f6e8fbff","u16":[63221,59722,64413,65535]}},"decoration":{"Focus":{"hex":"#00b6e6ff","u16":[0,46845,59198,65535]},"Hover":{"hex":"#5dd9ffff","u16":[23837,55708,65535,65535]}},"foreground":{"Active":{"hex":"#00b6e6ff","u16":[0,46845,59198,65535]},"Inactive":{"hex":"#76828eff","u16":[30379,33503,36442,65535]},"Link":{"hex":"#3a87adff","u16":[14823,34774,44470,65535]},"Negative":{"hex":"#fe0050ff","u16":[65159,0,20458,65535]},"Neutral":{"hex":"#dc8c00ff","u16":[56474,36005,0,65535]},"Normal":{"hex":"#343537ff","u16":[13414,13729,14039,65535]},"Positive":{"hex":"#20b373ff","u16":[8261,45937,29571,65535]},"Visited":{"hex":"#c039e3ff","u16":[49258,14648,58229,65535]}},"shade":{"Dark":{"hex":"#b9b9b9ff","u16":[47575,47575,47575,65535]},"Light":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"Mid":{"hex":"#d2d2d2ff","u16":[54043,54043,54043,65535]},"Midlight":{"hex":"#e8e8e8ff","u16":[59693,59693,59693,65535]},"Shadow":{"hex":"#868686ff","u16":[34535,34535,34535,65535]}}}},"Complementary":{"Active":{"background":{"Active":{"hex":"#1c3c4dff","u16":[7084,15330,19761,65535]},"Alternate":{"hex":"#1b1e20ff","u16":[6939,7710,8224,65535]},"Link":{"hex":"#143955ff","u16":[5076,14725,21803,65535]},"Negative":{"hex":"#57242bff","u16":[22290,9359,10973,65535]},"Neutral":{"hex":"#532e0dff","u16":[21296,11822,3397,65535]},"Normal":{"hex":"#2a2e32ff","u16":[10794,11822,12850,65535]},"Positive":{"hex":"#163e29ff","u16":[5742,16007,10505,65535]},"Visited":{"hex":"#412c4dff","u16":[16787,11259,19761,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#a1a9b1ff","u16":[41377,43433,45489,65535]},"Link":{"hex":"#1d99f3ff","u16":[7453,39321,62451,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#fcfcfcff","u16":[64764,64764,64764,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#141618ff","u16":[5252,5752,6253,65535]},"Light":{"hex":"#474d54ff","u16":[18170,19901,21631,65535]},"Mid":{"hex":"#24282bff","u16":[9291,10176,11061,65535]},"Midlight":{"hex":"#3a4045ff","u16":[14977,16403,17829,65535]},"Shadow":{"hex":"#0f1012ff","u16":[3809,4172,4535,65535]}}},"Disabled":{"background":{"Active":{"hex":"#20333dff","u16":[8232,13011,15777,65535]},"Alternate":{"hex":"#1a1d1fff","u16":[6615,7349,7839,65535]},"Link":{"hex":"#1c3242ff","u16":[7317,12882,17081,65535]},"Negative":{"hex":"#42282eff","u16":[16927,10389,11726,65535]},"Neutral":{"hex":"#3e2c1cff","u16":[16044,11402,7322,65535]},"Normal":{"hex":"#282c30ff","u16":[10289,11269,12249,65535]},"Positive":{"hex":"#1e352bff","u16":[7632,13521,11035,65535]},"Visited":{"hex":"#352c3fff","u16":[13699,11243,16190,65535]}},"decoration":{"Focus":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]},"Hover":{"hex":"#4b616dff","u16":[19293,24988,27940,65535]}},"foreground":{"Active":{"hex":"#2e576dff","u16":[11918,22245,27940,65535]},"Inactive":{"hex":"#50555aff","u16":[20493,21816,23139,65535]},"Link":{"hex":"#245070ff","u16":[9175,20444,28797,65535]},"Negative":{"hex":"#63333bff","u16":[25380,13156,15078,65535]},"Neutral":{"hex":"#6c431fff","u16":[27781,17272,7962,65535]},"Normal":{"hex":"#6e7173ff","u16":[28296,28933,29569,65535]},"Positive":{"hex":"#27573fff","u16":[10032,22245,16194,65535]},"Visited":{"hex":"#4e3a5cff","u16":[19978,14956,23567,65535]}},"shade":{"Dark":{"hex":"#131517ff","u16":[5006,5483,5960,65535]},"Light":{"hex":"#454c52ff","u16":[17791,19485,21180,65535]},"Mid":{"hex":"#222629ff","u16":[8858,9702,10546,65535]},"Midlight":{"hex":"#393e44ff","u16":[14575,15963,17351,65535]},"Shadow":{"hex":"#0e0f11ff","u16":[3633,3979,4325,65535]}}},"Inactive":{"background":{"Active":{"hex":"#0a3c4eff","u16":[2599,15367,19971,65535]},"Alternate":{"hex":"#181e22ff","u16":[6279,7813,8746,65535]},"Link":{"hex":"#0a3a52ff","u16":[2690,14783,21073,65535]},"Negative":{"hex":"#61182dff","u16":[24882,6143,11455,65535]},"Neutral":{"hex":"#4b310eff","u16":[19371,12604,3707,65535]},"Normal":{"hex":"#272e36ff","u16":[9901,11938,13794,65535]},"Positive":{"hex":"#0a3e2dff","u16":[2662,15936,11640,65535]},"Visited":{"hex":"#422954ff","u16":[17036,10580,21661,65535]}},"decoration":{"Focus":{"hex":"#00a7dfff","u16":[0,42903,57343,65535]},"Hover":{"hex":"#60c5edff","u16":[24628,50520,60803,65535]}},"foreground":{"Active":{"hex":"#00a7dfff","u16":[0,42903,57343,65535]},"Inactive":{"hex":"#8fa1b2ff","u16":[36789,41455,45844,65535]},"Link":{"hex":"#0094deff","u16":[0,38084,57174,65535]},"Negative":{"hex":"#e61c4eff","u16":[59015,7314,20110,65535]},"Neutral":{"hex":"#d97a00ff","u16":[55760,31353,0,65535]},"Normal":{"hex":"#e2e4e6ff","u16":[58036,58559,59093,65535]},"Positive":{"hex":"#00a667ff","u16":[0,42664,26590,65535]},"Visited":{"hex":"#9b4ec1ff","u16":[39783,19975,49600,65535]}},"shade":{"Dark":{"hex":"#13171aff","u16":[4818,5809,6712,65535]},"Light":{"hex":"#414e5aff","u16":[16667,20096,23220,65535]},"Mid":{"hex":"#21282eff","u16":[8523,10276,11874,65535]},"Midlight":{"hex":"#35404aff","u16":[13737,16564,19139,65535]},"Shadow":{"hex":"#0e1013ff","u16":[3494,4213,4868,65535]}}}},"Header":{"Active":{"background":{"Active":{"hex":"#78c3eaff","u16":[30928,50157,60086,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#88bee2ff","u16":[34959,48892,58027,65535]},"Negative":{"hex":"#e1aaacff","u16":[57701,43666,44286,65535]},"Neutral":{"hex":"#f7a86fff","u16":[63573,43095,28453,65535]},"Normal":{"hex":"#c8beb4ff","u16":[51400,48830,46260,65535]},"Positive":{"hex":"#6dcd8fff","u16":[28078,52627,36812,65535]},"Visited":{"hex":"#c8afcdff","u16":[51300,44976,52731,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#615c58ff","u16":[25011,23760,22510,65535]},"Light":{"hex":"#eae6e2ff","u16":[60012,59007,58046,65535]},"Mid":{"hex":"#a59d95ff","u16":[42502,40377,38252,65535]},"Midlight":{"hex":"#d4cbc3ff","u16":[54432,52252,50101,65535]},"Shadow":{"hex":"#3c3936ff","u16":[15322,14556,13790,65535]}}},"Disabled":{"background":{"Active":{"hex":"#9db6bfff","u16":[40277,46683,49169,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#a5b3baff","u16":[42401,46109,47855,65535]},"Negative":{"hex":"#cfa8a5ff","u16":[53190,43151,42377,65535]},"Neutral":{"hex":"#d4aa83ff","u16":[54406,43586,33729,65535]},"Normal":{"hex":"#bfb5acff","u16":[48996,46547,44097,65535]},"Positive":{"hex":"#9cb99dff","u16":[40082,47525,40275,65535]},"Visited":{"hex":"#c0abb9ff","u16":[49430,43977,47646,65535]}},"decoration":{"Focus":{"hex":"#90b0bdff","u16":[37078,45175,48641,65535]},"Hover":{"hex":"#adbabdff","u16":[44452,47918,48641,65535]}},"foreground":{"Active":{"hex":"#90b0bdff","u16":[37078,45175,48641,65535]},"Inactive":{"hex":"#a19f9eff","u16":[41451,40973,40495,65535]},"Link":{"hex":"#8aa0adff","u16":[35363,41230,44526,65535]},"Negative":{"hex":"#cb8a88ff","u16":[52176,35408,35040,65535]},"Neutral":{"hex":"#ce9c70ff","u16":[52941,40202,28663,65535]},"Normal":{"hex":"#88827dff","u16":[34848,33514,32178,65535]},"Positive":{"hex":"#89b090ff","u16":[35192,45175,36894,65535]},"Visited":{"hex":"#b392afff","u16":[45975,37487,44971,65535]}},"shade":{"Dark":{"hex":"#5d5853ff","u16":[23841,22649,21457,65535]},"Light":{"hex":"#e1dbd6ff","u16":[57767,56313,54905,65535]},"Mid":{"hex":"#9e968fff","u16":[40692,38658,36623,65535]},"Midlight":{"hex":"#cdc2b8ff","u16":[52581,49963,47344,65535]},"Shadow":{"hex":"#3a3734ff","u16":[14913,14167,13422,65535]}}},"Inactive":{"background":{"Active":{"hex":"#90c2d0ff","u16":[36953,49846,53514,65535]},"Alternate":{"hex":"#eef0f2ff","u16":[61223,61712,62199,65535]},"Link":{"hex":"#91bfcfff","u16":[37140,49092,53310,65535]},"Negative":{"hex":"#eda6a6ff","u16":[60927,42537,42661,65535]},"Neutral":{"hex":"#f0ae3fff","u16":[61578,44611,16261,65535]},"Normal":{"hex":"#cfbda9ff","u16":[53278,48483,43395,65535]},"Positive":{"hex":"#82ca8dff","u16":[33284,51902,36153,65535]},"Visited":{"hex":"#ccafc6ff","u16":[52551,44871,50873,65535]}},"decoration":{"Focus":{"hex":"#67b1ccff","u16":[26530,45435,52350,65535]},"Hover":{"hex":"#c2c3bbff","u16":[49844,50177,48043,65535]}},"foreground":{"Active":{"hex":"#67b1ccff","u16":[26530,45435,52350,65535]},"Inactive":{"hex":"#7d7f82ff","u16":[32032,32716,33393,65535]},"Link":{"hex":"#3e85a7ff","u16":[15814,34265,42846,65535]},"Negative":{"hex":"#f42a39ff","u16":[62647,10752,14713,65535]},"Neutral":{"hex":"#da8b00ff","u16":[56057,35621,0,65535]},"Normal":{"hex":"#322f2dff","u16":[12773,12144,11495,65535]},"Positive":{"hex":"#44af67ff","u16":[17432,45070,26418,65535]},"Visited":{"hex":"#a55cacff","u16":[42446,23757,44121,65535]}},"shade":{"Dark":{"hex":"#655c52ff","u16":[25924,23591,21115,65535]},"Light":{"hex":"#ece5deff","u16":[60771,58875,57027,65535]},"Mid":{"hex":"#ab9c8cff","u16":[44055,40090,35883,65535]},"Midlight":{"hex":"#dacabaff","u16":[56041,51960,47744,65535]},"Shadow":{"hex":"#3e3832ff","u16":[15881,14452,12935,65535]}}}},"Selection":{"Active":{"background":{"Active":{"hex":"#5ab7dcff","u16":[23235,46907,56572,65535]},"Alternate":{"hex":"#a3d4faff","u16":[41891,54484,64250,65535]},"Link":{"hex":"#b3ab74ff","u16":[46003,43823,29737,65535]},"Negative":{"hex":"#aa96b0ff","u16":[43694,38429,45353,65535]},"Neutral":{"hex":"#b29b74ff","u16":[45660,39812,29747,65535]},"Normal":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Positive":{"hex":"#32ada4ff","u16":[12736,44427,42135,65535]},"Visited":{"hex":"#9799d3ff","u16":[38815,39221,54248,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#fdbc4bff","u16":[65021,48316,19275,65535]},"Negative":{"hex":"#b03745ff","u16":[45232,14135,17733,65535]},"Neutral":{"hex":"#c65c00ff","u16":[50886,23644,0,65535]},"Normal":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Positive":{"hex":"#176839ff","u16":[5911,26728,14649,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#1e5571ff","u16":[7628,21759,29137,65535]},"Light":{"hex":"#92cef9ff","u16":[37530,52828,64075,65535]},"Mid":{"hex":"#3392c3ff","u16":[13128,37447,50145,65535]},"Midlight":{"hex":"#52bcf8ff","u16":[21175,48262,63634,65535]},"Shadow":{"hex":"#13374aff","u16":[4954,14132,18924,65535]}}},"Disabled":{"background":{"Active":{"hex":"#9db6bfff","u16":[40277,46683,49169,65535]},"Alternate":{"hex":"#d8daddff","u16":[55389,56136,56880,65535]},"Link":{"hex":"#a5b3baff","u16":[42401,46109,47855,65535]},"Negative":{"hex":"#cfa8a5ff","u16":[53190,43151,42377,65535]},"Neutral":{"hex":"#d4aa83ff","u16":[54406,43586,33729,65535]},"Normal":{"hex":"#bfb5acff","u16":[48996,46547,44097,65535]},"Positive":{"hex":"#9cb99dff","u16":[40082,47525,40275,65535]},"Visited":{"hex":"#c0abb9ff","u16":[49430,43977,47646,65535]}},"decoration":{"Focus":{"hex":"#90b0bdff","u16":[37078,45175,48641,65535]},"Hover":{"hex":"#adbabdff","u16":[44452,47918,48641,65535]}},"foreground":{"Active":{"hex":"#90b0bdff","u16":[37078,45175,48641,65535]},"Inactive":{"hex":"#a19f9eff","u16":[41451,40973,40495,65535]},"Link":{"hex":"#8aa0adff","u16":[35363,41230,44526,65535]},"Negative":{"hex":"#cb8a88ff","u16":[52176,35408,35040,65535]},"Neutral":{"hex":"#ce9c70ff","u16":[52941,40202,28663,65535]},"Normal":{"hex":"#88827dff","u16":[34848,33514,32178,65535]},"Positive":{"hex":"#89b090ff","u16":[35192,45175,36894,65535]},"Visited":{"hex":"#b392afff","u16":[45975,37487,44971,65535]}},"shade":{"Dark":{"hex":"#5d5853ff","u16":[23841,22649,21457,65535]},"Light":{"hex":"#e1dbd6ff","u16":[57767,56313,54905,65535]},"Mid":{"hex":"#9e968fff","u16":[40692,38658,36623,65535]},"Midlight":{"hex":"#cdc2b8ff","u16":[52581,49963,47344,65535]},"Shadow":{"hex":"#3a3734ff","u16":[14913,14167,13422,65535]}}},"Inactive":{"background":{"Active":{"hex":"#47b8dcff","u16":[18304,47384,56431,65535]},"Alternate":{"hex":"#9cd5ffff","u16":[40208,54680,65535,65535]},"Link":{"hex":"#50b9b7ff","u16":[20567,47559,47046,65535]},"Negative":{"hex":"#50a1f4ff","u16":[20583,41345,62581,65535]},"Neutral":{"hex":"#3dabc5ff","u16":[15688,44019,50751,65535]},"Normal":{"hex":"#00aff0ff","u16":[0,45047,61740,65535]},"Positive":{"hex":"#00aacdff","u16":[0,43766,52591,65535]},"Visited":{"hex":"#52a0ffff","u16":[20971,41173,65535,65535]}},"decoration":{"Focus":{"hex":"#00aff0ff","u16":[0,45047,61740,65535]},"Hover":{"hex":"#6ccdffff","u16":[27638,52703,65535,65535]}},"foreground":{"Active":{"hex":"#f2faffff","u16":[62257,64220,65535,65535]},"Inactive":{"hex":"#0088b5ff","u16":[0,34932,46497,65535]},"Link":{"hex":"#80d4b1ff","u16":[32978,54498,45610,65535]},"Negative":{"hex":"#5361aaff","u16":[21354,24801,43671,65535]},"Neutral":{"hex":"#528686ff","u16":[21046,34421,34324,65535]},"Normal":{"hex":"#f2faffff","u16":[62257,64220,65535,65535]},"Positive":{"hex":"#00696cff","u16":[0,26957,27776,65535]},"Visited":{"hex":"#0074eaff","u16":[0,29814,60169,65535]}},"shade":{"Dark":{"hex":"#005575ff","u16":[0,21919,30042,65535]},"Light":{"hex":"#89ceffff","u16":[35335,53057,65535,65535]},"Mid":{"hex":"#0093c9ff","u16":[0,37723,51702,65535]},"Midlight":{"hex":"#38bdffff","u16":[14467,48592,65535,65535]},"Shadow":{"hex":"#00374cff","u16":[0,14236,19512,65535]}}}},"Tooltip":{"Active":{"background":{"Active":{"hex":"#e0effaff","u16":[57630,61532,64195,65535]},"Alternate":{"hex":"#eff0f1ff","u16":[61423,61680,61937,65535]},"Link":{"hex":"#deeaf5ff","u16":[57046,60196,62891,65535]},"Negative":{"hex":"#f8e4e5ff","u16":[63672,58581,58970,65535]},"Neutral":{"hex":"#fce9ddff","u16":[64875,59755,56885,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c5f8d8ff","u16":[50674,63750,55628,65535]},"Visited":{"hex":"#ede5f1ff","u16":[60978,58784,62034,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#b2b2b2ff","u16":[45692,45692,45692,65535]},"Light":{"hex":"#ccccccff","u16":[52436,52436,52436,65535]},"Mid":{"hex":"#ccccccff","u16":[52436,52436,52436,65535]},"Midlight":{"hex":"#e3e3e3ff","u16":[58272,58272,58272,65535]},"Shadow":{"hex":"#7b7b7bff","u16":[31695,31695,31695,65535]}}},"Disabled":{"background":{"Active":{"hex":"#d2e8f4ff","u16":[53968,59692,62698,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58364,58826,59286,65535]},"Link":{"hex":"#dae6eeff","u16":[55898,59123,61286,65535]},"Negative":{"hex":"#f4dfe1ff","u16":[62678,57357,57876,65535]},"Neutral":{"hex":"#f7e1cdff","u16":[63504,57761,52725,65535]},"Normal":{"hex":"#ebebebff","u16":[60511,60511,60511,65535]},"Positive":{"hex":"#d0ebdbff","u16":[53508,60379,56375,65535]},"Visited":{"hex":"#eae1eeff","u16":[60238,57818,61243,65535]}},"decoration":{"Focus":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]},"Hover":{"hex":"#c1e0edff","u16":[49531,57442,60912,65535]}},"foreground":{"Active":{"hex":"#a0d5efff","u16":[41134,54770,61453,65535]},"Inactive":{"hex":"#bdc3c8ff","u16":[48657,50093,51518,65535]},"Link":{"hex":"#9fc5ddff","u16":[40903,50598,56692,65535]},"Negative":{"hex":"#eaacb3ff","u16":[60222,44302,45948,65535]},"Neutral":{"hex":"#f5bd86ff","u16":[62984,48622,34500,65535]},"Normal":{"hex":"#a5a6a7ff","u16":[42332,42590,42849,65535]},"Positive":{"hex":"#a6d3b9ff","u16":[42675,54252,47564,65535]},"Visited":{"hex":"#d0b5dcff","u16":[53574,46448,56425,65535]}},"shade":{"Dark":{"hex":"#737373ff","u16":[29444,29444,29444,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#bebebeff","u16":[48753,48753,48753,65535]},"Midlight":{"hex":"#f6f6f6ff","u16":[63124,63124,63124,65535]},"Shadow":{"hex":"#3d3d3dff","u16":[15630,15630,15630,65535]}}},"Inactive":{"background":{"Active":{"hex":"#d9f2fbff","u16":[55650,62136,64606,65535]},"Alternate":{"hex":"#eef0f2ff","u16":[61223,61712,62199,65535]},"Link":{"hex":"#deebf4ff","u16":[56958,60518,62623,65535]},"Negative":{"hex":"#fee3e9ff","u16":[65251,58252,59763,65535]},"Neutral":{"hex":"#fbead6ff","u16":[64538,60186,54973,65535]},"Normal":{"hex":"#f7f7f7ff","u16":[63479,63479,63479,65535]},"Positive":{"hex":"#c1f9deff","u16":[49630,63986,57116,65535]},"Visited":{"hex":"#f3e3f9ff","u16":[62507,58410,63910,65535]}},"decoration":{"Focus":{"hex":"#00b6e6ff","u16":[0,46811,59158,65535]},"Hover":{"hex":"#5fd8feff","u16":[24475,55628,65328,65535]}},"foreground":{"Active":{"hex":"#00b6e6ff","u16":[0,46811,59158,65535]},"Inactive":{"hex":"#76828dff","u16":[30450,33447,36278,65535]},"Link":{"hex":"#3987adff","u16":[14573,34741,44499,65535]},"Negative":{"hex":"#fd004fff","u16":[65086,0,20424,65535]},"Neutral":{"hex":"#dc8c00ff","u16":[56439,35968,0,65535]},"Normal":{"hex":"#343536ff","u16":[13282,13599,13911,65535]},"Positive":{"hex":"#1fb373ff","u16":[7991,45914,29515,65535]},"Visited":{"hex":"#bd3de0ff","u16":[48682,15602,57452,65535]}},"shade":{"Dark":{"hex":"#b2b2b2ff","u16":[45692,45692,45692,65535]},"Light":{"hex":"#ccccccff","u16":[52436,52436,52436,65535]},"Mid":{"hex":"#ccccccff","u16":[52436,52436,52436,65535]},"Midlight":{"hex":"#e3e3e3ff","u16":[58272,58272,58272,65535]},"Shadow":{"hex":"#7b7b7bff","u16":[31695,31695,31695,65535]}}}},"View":{"Active":{"background":{"Active":{"hex":"#fedddcff","u16":[65374,56718,56599,65535]},"Alternate":{"hex":"#aabbccff","u16":[43690,48059,52428,65535]},"Link":{"hex":"#e6e4e3ff","u16":[59132,58674,58224,65535]},"Negative":{"hex":"#f7dedfff","u16":[63418,57074,57345,65535]},"Neutral":{"hex":"#fce2d4ff","u16":[64832,58169,54463,65535]},"Normal":{"hex":"#faf0e6ff","u16":[64250,61680,59110,65535]},"Positive":{"hex":"#faf0e600","u16":[64250,61680,59110,5]},"Visited":{"hex":"#cbc3bbff","u16":[52175,50088,47999,65535]}},"decoration":{"Focus":{"hex":"#3daee980","u16":[15677,44718,59881,32896]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#ff0000ff","u16":[65535,0,0,65535]},"Inactive":{"hex":"#12569aff","u16":[4660,22136,39612,65535]},"Link":{"hex":"#808080ff","u16":[32896,32896,32896,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#124578ff","u16":[4657,17764,30871,65535]},"Positive":{"hex":"#00000000","u16":[0,0,0,0]},"Visited":{"valid":false}},"shade":{"Dark":{"hex":"#8d714bff","u16":[36163,29047,19385,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#e8ba7cff","u16":[59628,47895,31964,65535]},"Midlight":{"hex":"#fefbf8ff","u16":[65151,64404,63683,65535]},"Shadow":{"hex":"#493a27ff","u16":[18684,15007,10015,65535]}}},"Disabled":{"background":{"Active":{"hex":"#fcd6d1ff","u16":[64744,54950,53627,65535]},"Alternate":{"hex":"#9fb3c6ff","u16":[40797,45925,50935,65535]},"Link":{"hex":"#e5ded8ff","u16":[58762,57117,55470,65535]},"Negative":{"hex":"#f6d8d5ff","u16":[63164,55640,54641,65535]},"Neutral":{"hex":"#fadabeff","u16":[64155,56019,48819,65535]},"Normal":{"hex":"#f6e3d0ff","u16":[63220,58439,53409,65535]},"Positive":{"hex":"#f6e3d0a6","u16":[63220,58439,53409,42600]},"Visited":{"hex":"#e2dcd5ff","u16":[58147,56480,54823,65535]}},"decoration":{"Focus":{"hex":"#c2d9dfd3","u16":[49909,55668,57294,54111]},"Hover":{"hex":"#c7dbdfff","u16":[51039,56187,57237,65535]}},"foreground":{"Active":{"hex":"#fb8f87ff","u16":[64625,36758,34723,65535]},"Inactive":{"hex":"#9eb2c6ff","u16":[40507,45717,50805,65535]},"Link":{"hex":"#c6bfb8ff","u16":[50984,49158,47320,65535]},"Negative":{"hex":"#eda8a6ff","u16":[60843,43134,42741,65535]},"Neutral":{"hex":"#f8b979ff","u16":[63609,47433,31011,65535]},"Normal":{"hex":"#9facb9ff","u16":[40904,44209,47470,65535]},"Positive":{"hex":"#f6e3d0a6","u16":[63220,58439,53409,42598]},"Visited":{"hex":"#9b958fff","u16":[39810,38217,36625,65535]}},"shade":{"Dark":{"hex":"#866c48ff","u16":[34473,27688,18477,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#dfb377ff","u16":[57283,46009,30704,65535]},"Midlight":{"hex":"#faefe5ff","u16":[64192,61500,58801,65535]},"Shadow":{"hex":"#493a27ff","u16":[18705,15023,10025,65535]}}},"Inactive":{"background":{"Active":{"hex":"#ffdedaff","u16":[65535,56964,55956,65535]},"Alternate":{"hex":"#9bbddcff","u16":[39872,48547,56423,65535]},"Link":{"hex":"#e9e5e0ff","u16":[59897,58726,57574,65535]},"Negative":{"hex":"#ffdcdfff","u16":[65523,56595,57394,65535]},"Neutral":{"hex":"#fbe4cbff","u16":[64527,58611,52164,65535]},"Normal":{"hex":"#ffefdfff","u16":[65535,61450,57236,65535]},"Positive":{"hex":"#e0caa273","u16":[57489,51807,41733,29623]},"Visited":{"hex":"#d6cabfff","u16":[54974,52015,48996,65535]}},"decoration":{"Focus":{"hex":"#4ab5d5bb","u16":[19063,46465,54708,48044]},"Hover":{"hex":"#90d4e5ff","u16":[37000,54488,58923,65535]}},"foreground":{"Active":{"hex":"#ff300fff","u16":[65535,12463,3770,65535]},"Inactive":{"hex":"#356082ff","u16":[13566,24734,33448,65535]},"Link":{"hex":"#8a847dff","u16":[35549,33906,32214,65535]},"Negative":{"hex":"#ff0032ff","u16":[65470,0,12974,65535]},"Neutral":{"hex":"#dc8b00ff","u16":[56656,35785,0,65535]},"Normal":{"hex":"#354f65ff","u16":[13694,20399,26017,65535]},"Positive":{"hex":"#3224004c","u16":[12773,9215,0,19415]},"Visited":{"hex":"#292623ff","u16":[10494,9731,8930,65535]}},"shade":{"Dark":{"hex":"#996e00ff","u16":[39262,28323,0,65535]},"Light":{"hex":"#ffffffff","u16":[65535,65535,65535,65535]},"Mid":{"hex":"#fcb600ff","u16":[64738,46702,0,65535]},"Midlight":{"hex":"#fffaf6ff","u16":[65535,64338,63175,65535]},"Shadow":{"hex":"#4f3900ff","u16":[20285,14633,0,65535]}}}},"Window":{"Active":{"background":{"Active":{"hex":"#78c3eaff","u16":[30928,50157,60086,65535]},"Alternate":{"hex":"#e3e5e7ff","u16":[58339,58853,59367,65535]},"Link":{"hex":"#88bee2ff","u16":[34959,48892,58027,65535]},"Negative":{"hex":"#e1aaacff","u16":[57701,43666,44286,65535]},"Neutral":{"hex":"#f7a86fff","u16":[63573,43095,28453,65535]},"Normal":{"hex":"#c8beb4ff","u16":[51400,48830,46260,65535]},"Positive":{"hex":"#6dcd8fff","u16":[28078,52627,36812,65535]},"Visited":{"hex":"#c8afcdff","u16":[51300,44976,52731,65535]}},"decoration":{"Focus":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Hover":{"hex":"#93cee9ff","u16":[37779,52942,59881,65535]}},"foreground":{"Active":{"hex":"#3daee9ff","u16":[15677,44718,59881,65535]},"Inactive":{"hex":"#707d8aff","u16":[28784,32125,35466,65535]},"Link":{"hex":"#2980b9ff","u16":[10537,32896,47545,65535]},"Negative":{"hex":"#da4453ff","u16":[56026,17476,21331,65535]},"Neutral":{"hex":"#f67400ff","u16":[63222,29812,0,65535]},"Normal":{"hex":"#232629ff","u16":[8995,9766,10537,65535]},"Positive":{"hex":"#27ae60ff","u16":[10023,44718,24672,65535]},"Visited":{"hex":"#9b59b6ff","u16":[39835,22873,46774,65535]}},"shade":{"Dark":{"hex":"#615c58ff","u16":[25011,23760,22510,65535]},"Light":{"hex":"#eae6e2ff","u16":[60012,59007,58046,65535]},"Mid":{"hex":"#a59d95ff","u16":[42502,40377,38252,65535]},"Midlight":{"hex":"#d4cbc3ff","u16":[54432,52252,50101,65535]},"Shadow":{"hex":"#3c3936ff","u16":[15322,14556,13790,65535]}}},"Disabled":{"background":{"Active":{"hex":"#9db6bfff","u16":[40277,46683,49169,65535]},"Alternate":{"hex":"#d8daddff","u16":[55389,56136,56880,65535]},"Link":{"hex":"#a5b3baff","u16":[42401,46109,47855,65535]},"Negative":{"hex":"#cfa8a5ff","u16":[53190,43151,42377,65535]},"Neutral":{"hex":"#d4aa83ff","u16":[54406,43586,33729,65535]},"Normal":{"hex":"#bfb5acff","u16":[48996,46547,44097,65535]},"Positive":{"hex":"#9cb99dff","u16":[40082,47525,40275,65535]},"Visited":{"hex":"#c0abb9ff","u16":[49430,43977,47646,65535]}},"decoration":{"Focus":{"hex":"#90b0bdff","u16":[37078,45175,48641,65535]},"Hover":{"hex":"#adbabdff","u16":[44452,47918,48641,65535]}},"foreground":{"Active":{"hex":"#90b0bdff","u16":[37078,45175,48641,65535]},"Inactive":{"hex":"#a19f9eff","u16":[41451,40973,40495,65535]},"Link":{"hex":"#8aa0adff","u16":[35363,41230,44526,65535]},"Negative":{"hex":"#cb8a88ff","u16":[52176,35408,35040,65535]},"Neutral":{"hex":"#ce9c70ff","u16":[52941,40202,28663,65535]},"Normal":{"hex":"#88827dff","u16":[34848,33514,32178,65535]},"Positive":{"hex":"#89b090ff","u16":[35192,45175,36894,65535]},"Visited":{"hex":"#b392afff","u16":[45975,37487,44971,65535]}},"shade":{"Dark":{"hex":"#5d5853ff","u16":[23841,22649,21457,65535]},"Light":{"hex":"#e1dbd6ff","u16":[57767,56313,54905,65535]},"Mid":{"hex":"#9e968fff","u16":[40692,38658,36623,65535]},"Midlight":{"hex":"#cdc2b8ff","u16":[52581,49963,47344,65535]},"Shadow":{"hex":"#3a3734ff","u16":[14913,14167,13422,65535]}}},"Inactive":{"background":{"Active":{"hex":"#90c2d0ff","u16":[36953,49846,53514,65535]},"Alternate":{"hex":"#e1e5e9ff","u16":[57937,58917,59888,65535]},"Link":{"hex":"#91bfcfff","u16":[37140,49092,53310,65535]},"Negative":{"hex":"#eda6a6ff","u16":[60927,42537,42661,65535]},"Neutral":{"hex":"#f0ae3fff","u16":[61578,44611,16261,65535]},"Normal":{"hex":"#cfbda9ff","u16":[53278,48483,43395,65535]},"Positive":{"hex":"#82ca8dff","u16":[33284,51902,36153,65535]},"Visited":{"hex":"#ccafc6ff","u16":[52551,44871,50873,65535]}},"decoration":{"Focus":{"hex":"#67b1ccff","u16":[26530,45435,52350,65535]},"Hover":{"hex":"#c2c3bbff","u16":[49844,50177,48043,65535]}},"foreground":{"Active":{"hex":"#67b1ccff","u16":[26530,45435,52350,65535]},"Inactive":{"hex":"#7d7f82ff","u16":[32032,32716,33393,65535]},"Link":{"hex":"#3e85a7ff","u16":[15814,34265,42846,65535]},"Negative":{"hex":"#f42a39ff","u16":[62647,10752,14713,65535]},"Neutral":{"hex":"#da8b00ff","u16":[56057,35621,0,65535]},"Normal":{"hex":"#322f2dff","u16":[12773,12144,11495,65535]},"Positive":{"hex":"#44af67ff","u16":[17432,45070,26418,65535]},"Visited":{"hex":"#a55cacff","u16":[42446,23757,44121,65535]}},"shade":{"Dark":{"hex":"#655c52ff","u16":[25924,23591,21115,65535]},"Light":{"hex":"#ece5deff","u16":[60771,58875,57027,65535]},"Mid":{"hex":"#ab9c8cff","u16":[44055,40090,35883,65535]},"Midlight":{"hex":"#dacabaff","u16":[56041,51960,47744,65535]},"Shadow":{"hex":"#3e3832ff","u16":[15881,14452,12935,65535]}}}}},"sha256":"019747061cd0af87864d100759cbb4c381c86d9e99b444b508fdbeeb12d7ffb1","source_text":"[General]\nColorScheme=SyntheticValueForms\nName=Synthetic Value Forms\n\n[ColorEffects:Inactive]\nEnable=\nChangeSelectionColor=off\n\n[Colors:View]\nBackgroundNormal=+250,240,230\nBackgroundAlternate=#abc\nForegroundNormal=#123456789\nForegroundInactive=#123456789abc\nForegroundActive=Red\nForegroundLink=grey\nForegroundVisited=rebeccapurple\nForegroundNegative=10,20,30,\nForegroundNeutral=0x10,20,30\nForegroundPositive=transparent\nDecorationFocus=#803daee9\n\n[Colors:Window]\nBackgroundNormal=200,190,180\n"} +] +} diff --git a/tools/kde_golden/vectors/sizes.json b/tools/kde_golden/vectors/sizes.json new file mode 100644 index 000000000..7c4160eb4 --- /dev/null +++ b/tools/kde_golden/vectors/sizes.json @@ -0,0 +1,838 @@ +{ +"meta": {"generator":"tools/kde_golden/driver (kde_golden)","image":"invent-registry.kde.org/neon/docker-images/plasma@sha256:f58f9b97f218ee976cf928f6fe00abb929a644aded8c9dfb02f77a9b2a6e1732","kcolorscheme_build":"6.31.0","kguiaddons_build":"6.31.0","kind":"sizes","packages":{"breeze":"4:6.8.0-0zneon+24.04+noble+release+build80","fonts-hack":"3.003-3","fonts-noto-core":"20201225-2","kde-style-breeze":"4:6.8.0-0zneon+24.04+noble+release+build80","kde-style-oxygen":"4:6.8.0-0zneon+24.04+noble+release+build59","kf6-kcolorscheme":"6.31.0-0zneon+24.04+noble+release+build47","kf6-kconfig":"6.31.0-0zneon+24.04+noble+release+build36","kf6-kguiaddons":"6.31.0-0zneon+24.04+noble+release+build59","libkf6colorscheme-dev":"6.31.0-0zneon+24.04+noble+release+build47","plasma-workspace":"4:6.8.0-0zneon+24.04+noble+release+build99","qt6-base":"6.11.1-0zneon+24.04+noble+release+build162"},"qt_build":"6.11.1","qt_runtime":"6.11.1","readme":"tools/kde_golden/README.md documents every field"}, +"environment": {"default_font_before_override":"Sans Serif,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0","device_pixel_ratio":1,"logical_dpi":96,"physical_dpi":100,"platform":"offscreen","style":"breeze","widgets_parented":"every measured widget except QMenu has a plain QWidget parent"}, +"pixel_metrics": { +"PM_ButtonDefaultIndicator": 0, +"PM_ButtonIconSize": 16, +"PM_ButtonMargin": 6, +"PM_ButtonShiftHorizontal": 0, +"PM_ButtonShiftVertical": 0, +"PM_CheckBoxLabelSpacing": 4, +"PM_ComboBoxFrameWidth": 6, +"PM_DefaultFrameWidth": 0, +"PM_DialogButtonsButtonHeight": 0, +"PM_DialogButtonsButtonWidth": 0, +"PM_DialogButtonsSeparator": 0, +"PM_DockWidgetFrameWidth": 0, +"PM_DockWidgetHandleExtent": 8, +"PM_DockWidgetSeparatorExtent": 1, +"PM_DockWidgetTitleBarButtonMargin": 6, +"PM_DockWidgetTitleMargin": 2, +"PM_ExclusiveIndicatorHeight": 20, +"PM_ExclusiveIndicatorWidth": 20, +"PM_FocusFrameHMargin": 2, +"PM_FocusFrameVMargin": 2, +"PM_HeaderDefaultSectionSizeHorizontal": 100, +"PM_HeaderDefaultSectionSizeVertical": 30, +"PM_HeaderGripMargin": 4, +"PM_HeaderMargin": 6, +"PM_HeaderMarkSize": 10, +"PM_IconViewIconSize": 32, +"PM_IndicatorHeight": 20, +"PM_IndicatorWidth": 20, +"PM_LargeIconSize": 32, +"PM_LayoutBottomMargin": 6, +"PM_LayoutHorizontalSpacing": 6, +"PM_LayoutLeftMargin": 6, +"PM_LayoutRightMargin": 6, +"PM_LayoutTopMargin": 6, +"PM_LayoutVerticalSpacing": 6, +"PM_LineEditIconMargin": 4, +"PM_LineEditIconSize": 16, +"PM_ListViewIconSize": 16, +"PM_MaximumDragDistance": -1, +"PM_MdiSubWindowFrameWidth": 4, +"PM_MdiSubWindowMinimizedWidth": 196, +"PM_MenuBarHMargin": 0, +"PM_MenuBarItemSpacing": 0, +"PM_MenuBarPanelWidth": 0, +"PM_MenuBarVMargin": 0, +"PM_MenuButtonIndicator": 20, +"PM_MenuDesktopFrameWidth": 0, +"PM_MenuHMargin": 4, +"PM_MenuPanelWidth": 0, +"PM_MenuScrollerHeight": 10, +"PM_MenuTearoffHeight": 10, +"PM_MenuVMargin": 4, +"PM_MessageBoxIconSize": 64, +"PM_ProgressBarChunkWidth": 9, +"PM_RadioButtonLabelSpacing": 4, +"PM_ScrollBarExtent": 21, +"PM_ScrollBarSliderMin": 20, +"PM_ScrollView_ScrollBarOverlap": 0, +"PM_ScrollView_ScrollBarSpacing": 0, +"PM_SizeGripSize": 13, +"PM_SliderControlThickness": 20, +"PM_SliderLength": 20, +"PM_SliderSpaceAvailable": 0, +"PM_SliderThickness": 20, +"PM_SliderTickmarkOffset": 0, +"PM_SmallIconSize": 16, +"PM_SpinBoxFrameWidth": 6, +"PM_SpinBoxSliderHeight": 2, +"PM_SplitterWidth": 1, +"PM_SubMenuOverlap": 0, +"PM_TabBarBaseHeight": 0, +"PM_TabBarBaseOverlap": 2, +"PM_TabBarIconSize": 16, +"PM_TabBarScrollButtonWidth": 16, +"PM_TabBarTabHSpace": 16, +"PM_TabBarTabOverlap": 1, +"PM_TabBarTabShiftHorizontal": 0, +"PM_TabBarTabShiftVertical": 0, +"PM_TabBarTabVSpace": 8, +"PM_TabBar_ScrollButtonOverlap": 1, +"PM_TabCloseIndicatorHeight": 16, +"PM_TabCloseIndicatorWidth": 16, +"PM_TextCursorWidth": 1, +"PM_TitleBarButtonIconSize": 16, +"PM_TitleBarButtonSize": 16, +"PM_TitleBarHeight": 24, +"PM_ToolBarExtensionExtent": 28, +"PM_ToolBarFrameWidth": 0, +"PM_ToolBarHandleExtent": 10, +"PM_ToolBarIconSize": 22, +"PM_ToolBarItemMargin": 6, +"PM_ToolBarItemSpacing": 0, +"PM_ToolBarSeparatorExtent": 8, +"PM_ToolTipLabelFrameWidth": 3, +"PM_TreeViewIndentation": 20 +}, +"widget_pixel_metrics": { +"QCheckBox": {}, +"QComboBox": {}, +"QComboBox.editable": {}, +"QFrame.styled_panel": {}, +"QGroupBox": {}, +"QHeaderView.horizontal": {}, +"QLineEdit": {"PM_DefaultFrameWidth":6,"PM_MenuPanelWidth":6,"PM_ScrollView_ScrollBarSpacing":12,"PM_SubMenuOverlap":-6,"PM_TabBarBaseHeight":6}, +"QListView": {}, +"QMenu": {"PM_LayoutBottomMargin":10,"PM_LayoutLeftMargin":10,"PM_LayoutRightMargin":10,"PM_LayoutTopMargin":10}, +"QMenuBar": {}, +"QProgressBar": {}, +"QPushButton": {}, +"QRadioButton": {}, +"QScrollBar": {}, +"QSlider": {}, +"QSpinBox": {}, +"QStatusBar": {}, +"QTabBar": {}, +"QTabWidget": {"PM_DefaultFrameWidth":2,"PM_MenuPanelWidth":2,"PM_ScrollView_ScrollBarSpacing":4,"PM_SubMenuOverlap":-2,"PM_TabBarBaseHeight":2}, +"QTableView": {}, +"QToolBar": {}, +"QToolButton": {}, +"QTreeView": {} +}, +"style_hints": { +"SH_BlinkCursorWhenTextSelected": 1, +"SH_Button_FocusPolicy": 11, +"SH_ComboBox_AllowWheelScrolling": 1, +"SH_ComboBox_LayoutDirection": 0, +"SH_ComboBox_ListMouseTracking": 1, +"SH_ComboBox_ListMouseTracking_Active": 0, +"SH_ComboBox_ListMouseTracking_Current": 1, +"SH_ComboBox_Popup": 0, +"SH_ComboBox_PopupFrameStyle": 22, +"SH_ComboBox_UseNativePopup": 0, +"SH_Dial_BackgroundRole": 10, +"SH_DialogButtonBox_ButtonsHaveIcons": 1, +"SH_DialogButtonLayout": 0, +"SH_DialogButtons_DefaultButton": 0, +"SH_DitherDisabledText": 0, +"SH_DockWidget_ButtonsHaveFrame": 0, +"SH_DrawMenuBarSeparator": 0, +"SH_EtchDisabledText": 0, +"SH_FocusFrame_AboveWidget": 1, +"SH_FocusFrame_Mask": 0, +"SH_FontDialog_SelectAssociatedText": 0, +"SH_FormLayoutFieldGrowthPolicy": 1, +"SH_FormLayoutFormAlignment": 33, +"SH_FormLayoutLabelAlignment": 2, +"SH_FormLayoutWrapPolicy": 0, +"SH_GroupBox_TextLabelColor": 0, +"SH_GroupBox_TextLabelVerticalAlignment": 128, +"SH_Header_ArrowAlignment": 130, +"SH_ItemView_ActivateItemOnSingleClick": 0, +"SH_ItemView_ArrowKeysNavigateIntoChildren": 1, +"SH_ItemView_ChangeHighlightOnFocus": 0, +"SH_ItemView_DrawDelegateFrame": 0, +"SH_ItemView_EllipsisLocation": 2, +"SH_ItemView_MovementWithoutUpdatingSelection": 1, +"SH_ItemView_PaintAlternatingRowColorsForEmptyArea": 0, +"SH_ItemView_ScrollMode": 0, +"SH_ItemView_ShowDecorationSelected": 0, +"SH_LineEdit_PasswordCharacter": 9679, +"SH_LineEdit_PasswordMaskDelay": 0, +"SH_ListViewExpand_SelectMouseType": 2, +"SH_MainWindow_SpaceBelowMenuBar": 0, +"SH_MenuBar_AltKeyNavigation": 0, +"SH_MenuBar_MouseTracking": 1, +"SH_Menu_AllowActiveAndDisabled": 0, +"SH_Menu_FadeOutOnHide": 0, +"SH_Menu_FillScreenWithScroll": 1, +"SH_Menu_FlashTriggeredItem": 0, +"SH_Menu_KeyboardSearch": 0, +"SH_Menu_Mask": 0, +"SH_Menu_MouseTracking": 1, +"SH_Menu_Scrollable": 1, +"SH_Menu_SelectionWrap": 1, +"SH_Menu_SloppySubMenus": 1, +"SH_Menu_SpaceActivatesItem": 0, +"SH_Menu_SubMenuDontStartSloppyOnLeave": 0, +"SH_Menu_SubMenuPopupDelay": 150, +"SH_Menu_SubMenuResetWhenReenteringParent": 0, +"SH_Menu_SubMenuSloppyCloseTimeout": 300, +"SH_Menu_SubMenuSloppySelectOtherActions": 1, +"SH_Menu_SubMenuUniDirection": 0, +"SH_Menu_SubMenuUniDirectionFailCount": 1, +"SH_Menu_SupportsSections": 1, +"SH_MessageBox_CenterButtons": 0, +"SH_MessageBox_TextInteractionFlags": 5, +"SH_MessageBox_UseBorderForButtonSpacing": 0, +"SH_PrintDialog_RightAlignButtons": 0, +"SH_ProgressDialog_CenterCancelButton": 0, +"SH_ProgressDialog_TextLabelAlignment": 132, +"SH_RequestSoftwareInputPanel": 1, +"SH_RichText_FullWidthSelection": 0, +"SH_RubberBand_Mask": 0, +"SH_ScrollBar_ContextMenu": 1, +"SH_ScrollBar_LeftClickAbsolutePosition": 1, +"SH_ScrollBar_MiddleClickAbsolutePosition": 1, +"SH_ScrollBar_RollBetweenButtons": 0, +"SH_ScrollBar_ScrollWhenPointerLeavesControl": 0, +"SH_ScrollBar_Transient": 0, +"SH_ScrollView_FrameOnlyAroundContents": 0, +"SH_Slider_AbsoluteSetButtons": 4, +"SH_Slider_PageSetButtons": 1, +"SH_Slider_SloppyKeyEvents": 0, +"SH_Slider_SnapToValue": 0, +"SH_Slider_StopMouseOverSlider": 0, +"SH_SpinBox_AnimateButton": 0, +"SH_SpinBox_ButtonsInsideFrame": 1, +"SH_SpinBox_ClickAutoRepeatRate": 150, +"SH_SpinBox_ClickAutoRepeatThreshold": 500, +"SH_SpinBox_KeyPressAutoRepeatRate": 75, +"SH_SpinBox_SelectOnStep": 1, +"SH_SpinBox_StepModifier": 67108864, +"SH_SpinControls_DisableOnBounds": 1, +"SH_Splitter_OpaqueResize": 1, +"SH_TabBar_Alignment": 1, +"SH_TabBar_AllowWheelScrolling": 1, +"SH_TabBar_ChangeCurrentDelay": 500, +"SH_TabBar_CloseButtonPosition": 1, +"SH_TabBar_ElideMode": 3, +"SH_TabBar_PreferNoArrows": 0, +"SH_TabBar_SelectMouseType": 2, +"SH_TabWidget_DefaultTabPosition": 0, +"SH_Table_AlwaysDrawLeftTopGridLines": 0, +"SH_Table_GridLineColor": -1, +"SH_TextControl_FocusIndicatorTextCharFormat": 1, +"SH_TitleBar_AutoRaise": 0, +"SH_TitleBar_ModifyNotification": 1, +"SH_TitleBar_NoBorder": 1, +"SH_TitleBar_ShowToolTipsOnButtons": 1, +"SH_ToolBar_Movable": 1, +"SH_ToolBox_SelectedPageTitleBold": 0, +"SH_ToolButtonStyle": 2, +"SH_ToolButton_PopupDelay": 600, +"SH_ToolTipLabel_Opacity": 255, +"SH_ToolTip_FallAsleepDelay": 2000, +"SH_ToolTip_Mask": 0, +"SH_ToolTip_WakeUpDelay": 700, +"SH_UnderlineShortcut": 1, +"SH_Widget_Animate": 1, +"SH_Widget_Animation_Duration": 100, +"SH_Widget_ShareActivation": 0, +"SH_WindowFrame_Mask": 0, +"SH_WizardStyle": 0, +"SH_Workspace_FillSpaceOnMaximize": 0 +}, +"sub_rects": { +"check_box": {"contents":[24,0,37,22],"focus":[21,0,40,22],"indicator":[0,1,20,20],"rect":[0,0,61,22]}, +"combo_box": {"arrow":[128,6,20,20],"edit_field":[6,6,124,20],"frame":[0,0,0,0],"popup":[0,0,150,32],"rect":[0,0,150,32]}, +"combo_box.editable": {"arrow":[128,6,20,20],"edit_field":[6,6,124,20],"rect":[0,0,150,32]}, +"item_view_item": {"check":[7,2,20,20],"decoration":[33,4,16,16],"focus":[48,0,152,24],"rect":[0,0,200,24],"text":[52,0,144,24]}, +"line_edit": {"contents":[6,6,123,20],"rect":[0,0,135,32]}, +"progress_bar": {"contents":[0,10,80,6],"groove":[0,10,160,6],"label":[164,0,34,26],"rect":[0,0,200,26]}, +"push_button": {"contents":[2,2,80,30],"focus":[1,1,82,32],"rect":[0,0,84,34]}, +"radio_button": {"contents":[24,0,35,22],"focus":[21,0,38,22],"indicator":[0,1,20,20],"rect":[0,0,59,22]}, +"scroll_bar.vertical.value_0": {"add_line":[0,197,21,3],"add_page":[0,35,21,162],"groove":[0,3,21,194],"page_step":20,"range":[0,100],"rect":[0,0,21,200],"slider":[0,3,21,32],"sub_line":[0,0,21,3],"sub_page":[0,3,21,0]}, +"scroll_bar.vertical.value_100": {"add_line":[0,197,21,3],"add_page":[0,197,21,0],"groove":[0,3,21,194],"page_step":20,"range":[0,100],"rect":[0,0,21,200],"slider":[0,165,21,32],"sub_line":[0,0,21,3],"sub_page":[0,3,21,162]}, +"scroll_bar.vertical.value_50": {"add_line":[0,197,21,3],"add_page":[0,116,21,81],"groove":[0,3,21,194],"page_step":20,"range":[0,100],"rect":[0,0,21,200],"slider":[0,84,21,32],"sub_line":[0,0,21,3],"sub_page":[0,3,21,81]}, +"slider.horizontal.ticks_0": {"groove":[0,7,200,6],"handle":[54,0,20,20],"rect":[0,0,200,20],"value":30}, +"slider.horizontal.ticks_2": {"groove":[0,5,200,6],"handle":[54,-2,20,20],"rect":[0,0,200,18],"value":30}, +"slider.horizontal.ticks_3": {"groove":[0,5,200,6],"handle":[54,-2,20,20],"rect":[0,0,200,16],"value":30}, +"spin_box": {"down":[78,16,20,10],"edit_field":[6,6,74,20],"frame":[0,0,100,32],"rect":[0,0,100,32],"up":[78,6,20,10]}, +"tab": {"rect":[0,0,80,30],"text":[8,4,64,22]} +}, +"size_from_contents": [ +{"contents":[0,0],"id":"CT_PushButton","option":{"text":"OK"},"out":[84,34]}, +{"contents":[20,16],"id":"CT_PushButton","option":{"text":"OK"},"out":[84,34]}, +{"contents":[60,18],"id":"CT_PushButton","option":{"text":"OK"},"out":[84,34]}, +{"contents":[100,24],"id":"CT_PushButton","option":{"text":"OK"},"out":[84,34]}, +{"contents":[100,32],"id":"CT_PushButton","option":{"text":"OK"},"out":[84,34]}, +{"contents":[0,0],"id":"CT_PushButton.flat","option":{"flat":true,"text":"OK"},"out":[84,34]}, +{"contents":[20,16],"id":"CT_PushButton.flat","option":{"flat":true,"text":"OK"},"out":[84,34]}, +{"contents":[60,18],"id":"CT_PushButton.flat","option":{"flat":true,"text":"OK"},"out":[84,34]}, +{"contents":[100,24],"id":"CT_PushButton.flat","option":{"flat":true,"text":"OK"},"out":[84,34]}, +{"contents":[100,32],"id":"CT_PushButton.flat","option":{"flat":true,"text":"OK"},"out":[84,34]}, +{"contents":[0,0],"id":"CT_ToolButton.auto_raise","option":{"auto_raise":true,"icon_size":16},"out":[12,12]}, +{"contents":[20,16],"id":"CT_ToolButton.auto_raise","option":{"auto_raise":true,"icon_size":16},"out":[32,28]}, +{"contents":[60,18],"id":"CT_ToolButton.auto_raise","option":{"auto_raise":true,"icon_size":16},"out":[72,30]}, +{"contents":[100,24],"id":"CT_ToolButton.auto_raise","option":{"auto_raise":true,"icon_size":16},"out":[112,36]}, +{"contents":[100,32],"id":"CT_ToolButton.auto_raise","option":{"auto_raise":true,"icon_size":16},"out":[112,44]}, +{"contents":[0,0],"id":"CT_ToolButton.framed","option":{"auto_raise":false,"icon_size":16},"out":[16,16]}, +{"contents":[20,16],"id":"CT_ToolButton.framed","option":{"auto_raise":false,"icon_size":16},"out":[36,32]}, +{"contents":[60,18],"id":"CT_ToolButton.framed","option":{"auto_raise":false,"icon_size":16},"out":[76,34]}, +{"contents":[100,24],"id":"CT_ToolButton.framed","option":{"auto_raise":false,"icon_size":16},"out":[116,40]}, +{"contents":[100,32],"id":"CT_ToolButton.framed","option":{"auto_raise":false,"icon_size":16},"out":[116,48]}, +{"contents":[0,0],"id":"CT_LineEdit","option":{"line_width":6},"out":[12,12]}, +{"contents":[20,16],"id":"CT_LineEdit","option":{"line_width":6},"out":[32,28]}, +{"contents":[60,18],"id":"CT_LineEdit","option":{"line_width":6},"out":[72,30]}, +{"contents":[100,24],"id":"CT_LineEdit","option":{"line_width":6},"out":[112,36]}, +{"contents":[100,32],"id":"CT_LineEdit","option":{"line_width":6},"out":[112,44]}, +{"contents":[0,0],"id":"CT_ComboBox","option":{"editable":false,"frame":true},"out":[38,32]}, +{"contents":[20,16],"id":"CT_ComboBox","option":{"editable":false,"frame":true},"out":[58,32]}, +{"contents":[60,18],"id":"CT_ComboBox","option":{"editable":false,"frame":true},"out":[98,32]}, +{"contents":[100,24],"id":"CT_ComboBox","option":{"editable":false,"frame":true},"out":[138,36]}, +{"contents":[100,32],"id":"CT_ComboBox","option":{"editable":false,"frame":true},"out":[138,44]}, +{"contents":[0,0],"id":"CT_ComboBox.editable","option":{"editable":true,"frame":true},"out":[38,32]}, +{"contents":[20,16],"id":"CT_ComboBox.editable","option":{"editable":true,"frame":true},"out":[58,32]}, +{"contents":[60,18],"id":"CT_ComboBox.editable","option":{"editable":true,"frame":true},"out":[98,32]}, +{"contents":[100,24],"id":"CT_ComboBox.editable","option":{"editable":true,"frame":true},"out":[138,36]}, +{"contents":[100,32],"id":"CT_ComboBox.editable","option":{"editable":true,"frame":true},"out":[138,44]}, +{"contents":[0,0],"id":"CT_SpinBox","option":{"frame":true},"out":[32,20]}, +{"contents":[20,16],"id":"CT_SpinBox","option":{"frame":true},"out":[52,28]}, +{"contents":[60,18],"id":"CT_SpinBox","option":{"frame":true},"out":[92,30]}, +{"contents":[100,24],"id":"CT_SpinBox","option":{"frame":true},"out":[132,36]}, +{"contents":[100,32],"id":"CT_SpinBox","option":{"frame":true},"out":[132,44]}, +{"contents":[0,0],"id":"CT_CheckBox","option":{"text":"X"},"out":[24,20]}, +{"contents":[20,16],"id":"CT_CheckBox","option":{"text":"X"},"out":[44,20]}, +{"contents":[60,18],"id":"CT_CheckBox","option":{"text":"X"},"out":[84,22]}, +{"contents":[100,24],"id":"CT_CheckBox","option":{"text":"X"},"out":[124,28]}, +{"contents":[100,32],"id":"CT_CheckBox","option":{"text":"X"},"out":[124,36]}, +{"contents":[0,0],"id":"CT_RadioButton","option":{"text":"X"},"out":[24,20]}, +{"contents":[20,16],"id":"CT_RadioButton","option":{"text":"X"},"out":[44,20]}, +{"contents":[60,18],"id":"CT_RadioButton","option":{"text":"X"},"out":[84,22]}, +{"contents":[100,24],"id":"CT_RadioButton","option":{"text":"X"},"out":[124,28]}, +{"contents":[100,32],"id":"CT_RadioButton","option":{"text":"X"},"out":[124,36]}, +{"contents":[0,0],"id":"CT_Slider","option":{"orientation":1},"out":[0,0]}, +{"contents":[20,16],"id":"CT_Slider","option":{"orientation":1},"out":[20,16]}, +{"contents":[60,18],"id":"CT_Slider","option":{"orientation":1},"out":[60,18]}, +{"contents":[100,24],"id":"CT_Slider","option":{"orientation":1},"out":[100,24]}, +{"contents":[100,32],"id":"CT_Slider","option":{"orientation":1},"out":[100,32]}, +{"contents":[0,0],"id":"CT_Slider.ticks_both","option":{"orientation":1,"ticks":3},"out":[0,-4]}, +{"contents":[20,16],"id":"CT_Slider.ticks_both","option":{"orientation":1,"ticks":3},"out":[20,12]}, +{"contents":[60,18],"id":"CT_Slider.ticks_both","option":{"orientation":1,"ticks":3},"out":[60,14]}, +{"contents":[100,24],"id":"CT_Slider.ticks_both","option":{"orientation":1,"ticks":3},"out":[100,20]}, +{"contents":[100,32],"id":"CT_Slider.ticks_both","option":{"orientation":1,"ticks":3},"out":[100,28]}, +{"contents":[0,0],"id":"CT_ProgressBar","option":{"text_visible":true},"out":[6,18]}, +{"contents":[20,16],"id":"CT_ProgressBar","option":{"text_visible":true},"out":[20,18]}, +{"contents":[60,18],"id":"CT_ProgressBar","option":{"text_visible":true},"out":[60,18]}, +{"contents":[100,24],"id":"CT_ProgressBar","option":{"text_visible":true},"out":[100,24]}, +{"contents":[100,32],"id":"CT_ProgressBar","option":{"text_visible":true},"out":[100,32]}, +{"contents":[0,0],"id":"CT_MenuItem.normal","option":{"check_type":0,"max_icon_width":0,"text":"Item","type":0},"out":[59,29]}, +{"contents":[20,16],"id":"CT_MenuItem.normal","option":{"check_type":0,"max_icon_width":0,"text":"Item","type":0},"out":[59,29]}, +{"contents":[60,18],"id":"CT_MenuItem.normal","option":{"check_type":0,"max_icon_width":0,"text":"Item","type":0},"out":[59,29]}, +{"contents":[100,24],"id":"CT_MenuItem.normal","option":{"check_type":0,"max_icon_width":0,"text":"Item","type":0},"out":[59,29]}, +{"contents":[100,32],"id":"CT_MenuItem.normal","option":{"check_type":0,"max_icon_width":0,"text":"Item","type":0},"out":[59,29]}, +{"contents":[0,0],"id":"CT_MenuItem.normal.icon_column","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":0},"out":[77,29]}, +{"contents":[20,16],"id":"CT_MenuItem.normal.icon_column","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":0},"out":[77,29]}, +{"contents":[60,18],"id":"CT_MenuItem.normal.icon_column","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":0},"out":[77,29]}, +{"contents":[100,24],"id":"CT_MenuItem.normal.icon_column","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":0},"out":[77,29]}, +{"contents":[100,32],"id":"CT_MenuItem.normal.icon_column","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":0},"out":[77,29]}, +{"contents":[0,0],"id":"CT_MenuItem.shortcut","option":{"check_type":0,"max_icon_width":16,"text":"Save\tCtrl+S","type":0},"out":[93,29]}, +{"contents":[20,16],"id":"CT_MenuItem.shortcut","option":{"check_type":0,"max_icon_width":16,"text":"Save\tCtrl+S","type":0},"out":[93,29]}, +{"contents":[60,18],"id":"CT_MenuItem.shortcut","option":{"check_type":0,"max_icon_width":16,"text":"Save\tCtrl+S","type":0},"out":[93,29]}, +{"contents":[100,24],"id":"CT_MenuItem.shortcut","option":{"check_type":0,"max_icon_width":16,"text":"Save\tCtrl+S","type":0},"out":[93,29]}, +{"contents":[100,32],"id":"CT_MenuItem.shortcut","option":{"check_type":0,"max_icon_width":16,"text":"Save\tCtrl+S","type":0},"out":[93,29]}, +{"contents":[0,0],"id":"CT_MenuItem.checkable","option":{"check_type":2,"max_icon_width":16,"text":"Item","type":0},"out":[99,29]}, +{"contents":[20,16],"id":"CT_MenuItem.checkable","option":{"check_type":2,"max_icon_width":16,"text":"Item","type":0},"out":[99,29]}, +{"contents":[60,18],"id":"CT_MenuItem.checkable","option":{"check_type":2,"max_icon_width":16,"text":"Item","type":0},"out":[99,29]}, +{"contents":[100,24],"id":"CT_MenuItem.checkable","option":{"check_type":2,"max_icon_width":16,"text":"Item","type":0},"out":[99,29]}, +{"contents":[100,32],"id":"CT_MenuItem.checkable","option":{"check_type":2,"max_icon_width":16,"text":"Item","type":0},"out":[99,29]}, +{"contents":[0,0],"id":"CT_MenuItem.submenu","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":3},"out":[77,29]}, +{"contents":[20,16],"id":"CT_MenuItem.submenu","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":3},"out":[77,29]}, +{"contents":[60,18],"id":"CT_MenuItem.submenu","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":3},"out":[77,29]}, +{"contents":[100,24],"id":"CT_MenuItem.submenu","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":3},"out":[77,29]}, +{"contents":[100,32],"id":"CT_MenuItem.submenu","option":{"check_type":0,"max_icon_width":16,"text":"Item","type":3},"out":[77,29]}, +{"contents":[0,0],"id":"CT_MenuItem.separator","option":{"check_type":0,"max_icon_width":16,"text":"","type":2},"out":[9,5]}, +{"contents":[20,16],"id":"CT_MenuItem.separator","option":{"check_type":0,"max_icon_width":16,"text":"","type":2},"out":[9,5]}, +{"contents":[60,18],"id":"CT_MenuItem.separator","option":{"check_type":0,"max_icon_width":16,"text":"","type":2},"out":[9,5]}, +{"contents":[100,24],"id":"CT_MenuItem.separator","option":{"check_type":0,"max_icon_width":16,"text":"","type":2},"out":[9,5]}, +{"contents":[100,32],"id":"CT_MenuItem.separator","option":{"check_type":0,"max_icon_width":16,"text":"","type":2},"out":[9,5]}, +{"contents":[0,0],"id":"CT_MenuItem.section","option":{"check_type":0,"max_icon_width":16,"text":"Section","type":2},"out":[56,28]}, +{"contents":[20,16],"id":"CT_MenuItem.section","option":{"check_type":0,"max_icon_width":16,"text":"Section","type":2},"out":[56,28]}, +{"contents":[60,18],"id":"CT_MenuItem.section","option":{"check_type":0,"max_icon_width":16,"text":"Section","type":2},"out":[56,28]}, +{"contents":[100,24],"id":"CT_MenuItem.section","option":{"check_type":0,"max_icon_width":16,"text":"Section","type":2},"out":[56,28]}, +{"contents":[100,32],"id":"CT_MenuItem.section","option":{"check_type":0,"max_icon_width":16,"text":"Section","type":2},"out":[56,28]}, +{"contents":[0,0],"id":"CT_TabBarTab","option":{"shape":0,"text":"Tab"},"out":[80,30]}, +{"contents":[20,16],"id":"CT_TabBarTab","option":{"shape":0,"text":"Tab"},"out":[80,30]}, +{"contents":[60,18],"id":"CT_TabBarTab","option":{"shape":0,"text":"Tab"},"out":[80,30]}, +{"contents":[100,24],"id":"CT_TabBarTab","option":{"shape":0,"text":"Tab"},"out":[100,30]}, +{"contents":[100,32],"id":"CT_TabBarTab","option":{"shape":0,"text":"Tab"},"out":[100,32]}, +{"contents":[0,0],"id":"CT_TabBarTab.document_mode","option":{"document_mode":true,"shape":0,"text":"Tab"},"out":[80,34]}, +{"contents":[20,16],"id":"CT_TabBarTab.document_mode","option":{"document_mode":true,"shape":0,"text":"Tab"},"out":[80,34]}, +{"contents":[60,18],"id":"CT_TabBarTab.document_mode","option":{"document_mode":true,"shape":0,"text":"Tab"},"out":[80,34]}, +{"contents":[100,24],"id":"CT_TabBarTab.document_mode","option":{"document_mode":true,"shape":0,"text":"Tab"},"out":[100,34]}, +{"contents":[100,32],"id":"CT_TabBarTab.document_mode","option":{"document_mode":true,"shape":0,"text":"Tab"},"out":[100,34]}, +{"contents":[0,0],"id":"CT_HeaderSection","option":{"text":"Name"},"out":[49,30]}, +{"contents":[20,16],"id":"CT_HeaderSection","option":{"text":"Name"},"out":[49,30]}, +{"contents":[60,18],"id":"CT_HeaderSection","option":{"text":"Name"},"out":[72,30]}, +{"contents":[100,24],"id":"CT_HeaderSection","option":{"text":"Name"},"out":[112,36]}, +{"contents":[100,32],"id":"CT_HeaderSection","option":{"text":"Name"},"out":[112,44]}, +{"contents":[0,0],"id":"CT_ItemViewItem","option":{"text":"Row"},"out":[41,24]}, +{"contents":[20,16],"id":"CT_ItemViewItem","option":{"text":"Row"},"out":[41,24]}, +{"contents":[60,18],"id":"CT_ItemViewItem","option":{"text":"Row"},"out":[41,24]}, +{"contents":[100,24],"id":"CT_ItemViewItem","option":{"text":"Row"},"out":[41,24]}, +{"contents":[100,32],"id":"CT_ItemViewItem","option":{"text":"Row"},"out":[41,24]} +], +"monospace_fonts": [ +{"advance":{"0":[7,7.21875],"Cancel":[43,43.3125],"M":[7,7.21875],"OK":[14,14.4375],"The quick brown fox":[137,137.15625],"x":[7,7.21875]},"exact_match":true,"metrics":{"ascent":11,"average_char_width":7,"cap_height":9,"descent":3,"height":14,"leading":0,"line_spacing":14,"max_width":7,"x_height":7},"metrics_f":{"ascent":11.125,"average_char_width":7.21875,"cap_height":8.75,"descent":2.828125,"height":13.953125,"leading":0,"line_spacing":13.953125,"max_width":7,"x_height":6.5625},"requested_family":"Hack","requested_point_size":9,"resolved_family":"Hack","resolved_pixel_size":12,"resolved_point_size":9,"resolved_style":"Regular","to_string":"Hack,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"}, +{"advance":{"0":[8,7.8125],"Cancel":[47,46.875],"M":[8,7.8125],"OK":[16,15.625],"The quick brown fox":[148,148.4375],"x":[8,7.8125]},"exact_match":false,"metrics":{"ascent":12,"average_char_width":8,"cap_height":9,"descent":3,"height":15,"leading":0,"line_spacing":15,"max_width":8,"x_height":7},"metrics_f":{"ascent":12.0625,"average_char_width":7.828125,"cap_height":9.484375,"descent":3.0625,"height":15.125,"leading":0,"line_spacing":15.125,"max_width":8,"x_height":7.109375},"requested_family":"Hack","requested_point_size":10,"resolved_family":"Hack","resolved_pixel_size":13,"resolved_point_size":9.75,"resolved_style":"Regular","to_string":"Hack,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"}, +{"advance":{"0":[9,9.015625],"Cancel":[54,54.09375],"M":[9,9.015625],"OK":[18,18.03125],"The quick brown fox":[171,171.296875],"x":[9,9.015625]},"exact_match":false,"metrics":{"ascent":14,"average_char_width":9,"cap_height":11,"descent":4,"height":18,"leading":0,"line_spacing":18,"max_width":9,"x_height":8},"metrics_f":{"ascent":13.921875,"average_char_width":9.03125,"cap_height":10.9375,"descent":3.53125,"height":17.453125,"leading":0,"line_spacing":17.453125,"max_width":9,"x_height":8.203125},"requested_family":"Hack","requested_point_size":11,"resolved_family":"Hack","resolved_pixel_size":15,"resolved_point_size":11.25,"resolved_style":"Regular","to_string":"Hack,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"} +], +"fonts": [ +{"advance":{"0":[7,7.421875],"Cancel":[40,40.421875],"M":[12,11.78125],"OK":[18,18.1875],"The quick brown fox":[123,123.078125],"x":[7,6.875]},"exact_match":false,"metrics":{"ascent":14,"average_char_width":8,"cap_height":9,"descent":4,"height":18,"leading":0,"line_spacing":18,"max_width":37,"x_height":7},"metrics_f":{"ascent":13.890625,"average_char_width":7.5,"cap_height":9.28125,"descent":3.796875,"height":17.6875,"leading":0,"line_spacing":17.6875,"max_width":37,"x_height":6.96875},"requested_family":"Noto Sans","requested_point_size":10,"resolved_family":"Noto Sans","resolved_pixel_size":13,"resolved_point_size":9.75,"resolved_style":"Regular","to_string":"Noto Sans,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"}, +{"advance":{"0":[6,6.28125],"Cancel":[34,34.1875],"M":[10,9.96875],"OK":[15,15.375],"The quick brown fox":[104,104.125],"x":[6,5.8125]},"exact_match":false,"metrics":{"ascent":12,"average_char_width":6,"cap_height":8,"descent":3,"height":15,"leading":0,"line_spacing":15,"max_width":31,"x_height":6},"metrics_f":{"ascent":11.75,"average_char_width":6.34375,"cap_height":7.859375,"descent":3.21875,"height":14.96875,"leading":0,"line_spacing":14.96875,"max_width":31,"x_height":5.890625},"requested_family":"Noto Sans","requested_point_size":8,"resolved_family":"Noto Sans","resolved_pixel_size":11,"resolved_point_size":8.25,"resolved_style":"Regular","to_string":"Noto Sans,8,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"}, +{"advance":{"0":[7,6.859375],"Cancel":[37,37.3125],"M":[11,10.875],"OK":[17,16.78125],"The quick brown fox":[114,113.625],"x":[6,6.34375]},"exact_match":true,"metrics":{"ascent":13,"average_char_width":7,"cap_height":9,"descent":4,"height":17,"leading":0,"line_spacing":17,"max_width":34,"x_height":6},"metrics_f":{"ascent":12.8125,"average_char_width":6.921875,"cap_height":8.5625,"descent":3.515625,"height":16.328125,"leading":0,"line_spacing":16.328125,"max_width":34,"x_height":6.4375},"requested_family":"Noto Sans","requested_point_size":9,"resolved_family":"Noto Sans","resolved_pixel_size":12,"resolved_point_size":9,"resolved_style":"Regular","to_string":"Noto Sans,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"}, +{"advance":{"0":[9,8.578125],"Cancel":[47,46.640625],"M":[14,13.59375],"OK":[21,20.984375],"The quick brown fox":[142,142.015625],"x":[8,7.921875]},"exact_match":false,"metrics":{"ascent":16,"average_char_width":9,"cap_height":11,"descent":4,"height":20,"leading":0,"line_spacing":20,"max_width":43,"x_height":8},"metrics_f":{"ascent":16.03125,"average_char_width":8.65625,"cap_height":10.703125,"descent":4.390625,"height":20.421875,"leading":0,"line_spacing":20.421875,"max_width":43,"x_height":8.046875},"requested_family":"Noto Sans","requested_point_size":11,"resolved_family":"Noto Sans","resolved_pixel_size":15,"resolved_point_size":11.25,"resolved_style":"Regular","to_string":"Noto Sans,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"}, +{"advance":{"0":[9,9.140625],"Cancel":[50,49.765625],"M":[15,14.5],"OK":[22,22.375],"The quick brown fox":[152,151.5],"x":[8,8.453125]},"exact_match":true,"metrics":{"ascent":17,"average_char_width":9,"cap_height":11,"descent":5,"height":22,"leading":0,"line_spacing":22,"max_width":45,"x_height":9},"metrics_f":{"ascent":17.09375,"average_char_width":9.234375,"cap_height":11.421875,"descent":4.6875,"height":21.78125,"leading":0,"line_spacing":21.78125,"max_width":45,"x_height":8.578125},"requested_family":"Noto Sans","requested_point_size":12,"resolved_family":"Noto Sans","resolved_pixel_size":16,"resolved_point_size":12,"resolved_style":"Regular","to_string":"Noto Sans,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"}, +{"advance":{"0":[11,10.859375],"Cancel":[59,59.09375],"M":[17,17.21875],"OK":[27,26.578125],"The quick brown fox":[180,179.953125],"x":[10,10.046875]},"exact_match":false,"metrics":{"ascent":20,"average_char_width":11,"cap_height":14,"descent":6,"height":26,"leading":0,"line_spacing":26,"max_width":54,"x_height":10},"metrics_f":{"ascent":20.296875,"average_char_width":10.96875,"cap_height":13.5625,"descent":5.5625,"height":25.859375,"leading":0,"line_spacing":25.859375,"max_width":54,"x_height":10.1875},"requested_family":"Noto Sans","requested_point_size":14,"resolved_family":"Noto Sans","resolved_pixel_size":19,"resolved_point_size":14.25,"resolved_style":"Regular","to_string":"Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0"} +], +"widgets": [ +{"class":"QLabel","config":{"text":"Label"},"font":"Noto Sans 10","id":"label","minimum_size_hint":[33,18],"size_hint":[33,18]}, +{"class":"QPushButton","config":{"text":"OK"},"font":"Noto Sans 10","id":"push_button","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"text":"Cancel"},"font":"Noto Sans 10","id":"push_button","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"text":"Apply changes"},"font":"Noto Sans 10","id":"push_button","minimum_size_hint":[106,34],"size_hint":[106,34]}, +{"class":"QPushButton","config":{"icon":true,"text":"Save"},"font":"Noto Sans 10","id":"push_button.icon","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"default":true,"text":"OK"},"font":"Noto Sans 10","id":"push_button.default","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"flat":true,"text":"OK"},"font":"Noto Sans 10","id":"push_button.flat","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"icon":true},"font":"Noto Sans 10","id":"push_button.icon_only","minimum_size_hint":[31,32],"size_hint":[31,32]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 10","id":"tool_button.auto_raise.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 10","id":"tool_button.auto_raise.22","minimum_size_hint":[34,34],"size_hint":[34,34]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 10","id":"tool_button.framed.16","minimum_size_hint":[32,32],"size_hint":[32,32]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 10","id":"tool_button.framed.22","minimum_size_hint":[38,38],"size_hint":[38,38]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":2,"text":"Tool"},"font":"Noto Sans 10","id":"tool_button.text_beside_icon.16","minimum_size_hint":[64,30],"size_hint":[64,30]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":3,"text":"Tool"},"font":"Noto Sans 10","id":"tool_button.text_under_icon.22","minimum_size_hint":[44,56],"size_hint":[44,56]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":1,"style":0,"text":"Tool"},"font":"Noto Sans 10","id":"tool_button.menu_button_popup.16","minimum_size_hint":[48,28],"size_hint":[48,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":2,"style":0,"text":"Tool"},"font":"Noto Sans 10","id":"tool_button.instant_popup.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QLineEdit","font":"Noto Sans 10","id":"line_edit","minimum_size_hint":[53,32],"size_hint":[135,32]}, +{"class":"QLineEdit","config":{"clear_button":true},"font":"Noto Sans 10","id":"line_edit.clear_button","minimum_size_hint":[79,32],"size_hint":[161,32]}, +{"class":"QLineEdit","config":{"frame":false},"font":"Noto Sans 10","id":"line_edit.frameless","minimum_size_hint":[41,20],"size_hint":[123,20]}, +{"class":"QComboBox","config":{"items":["Item","Longer item text"]},"font":"Noto Sans 10","id":"combo_box","minimum_size_hint":[139,32],"size_hint":[139,32]}, +{"class":"QComboBox","config":{"editable":true},"font":"Noto Sans 10","id":"combo_box.editable","minimum_size_hint":[139,32],"size_hint":[87,32]}, +{"class":"QComboBox","config":{"icon":true},"font":"Noto Sans 10","id":"combo_box.icon","minimum_size_hint":[87,32],"size_hint":[87,32]}, +{"class":"QSpinBox","config":{"range":[0,99]},"font":"Noto Sans 10","id":"spin_box.0_99","minimum_size_hint":[52,32],"size_hint":[52,32]}, +{"class":"QSpinBox","config":{"range":[0,9999]},"font":"Noto Sans 10","id":"spin_box.0_9999","minimum_size_hint":[67,32],"size_hint":[67,32]}, +{"class":"QDoubleSpinBox","config":{"decimals":2,"range":[0,100]},"font":"Noto Sans 10","id":"double_spin_box","minimum_size_hint":[78,32],"size_hint":[78,32]}, +{"class":"QSpinBox","config":{"buttons":false,"range":[0,99]},"font":"Noto Sans 10","id":"spin_box.no_buttons","minimum_size_hint":[32,32],"size_hint":[32,32]}, +{"class":"QCheckBox","config":{"text":"Check"},"font":"Noto Sans 10","id":"check_box","minimum_size_hint":[61,22],"size_hint":[61,22]}, +{"class":"QCheckBox","font":"Noto Sans 10","id":"check_box.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QRadioButton","config":{"text":"Radio"},"font":"Noto Sans 10","id":"radio_button","minimum_size_hint":[59,22],"size_hint":[59,22]}, +{"class":"QRadioButton","font":"Noto Sans 10","id":"radio_button.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":0},"font":"Noto Sans 10","id":"slider.horizontal","minimum_size_hint":[20,20],"size_hint":[84,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":2},"font":"Noto Sans 10","id":"slider.horizontal.ticks_below","minimum_size_hint":[20,23],"size_hint":[84,23]}, +{"class":"QSlider","config":{"orientation":1,"ticks":3},"font":"Noto Sans 10","id":"slider.horizontal.ticks_both","minimum_size_hint":[20,26],"size_hint":[84,26]}, +{"class":"QSlider","config":{"orientation":2,"ticks":0},"font":"Noto Sans 10","id":"slider.vertical","minimum_size_hint":[20,20],"size_hint":[20,84]}, +{"class":"QScrollBar","font":"Noto Sans 10","id":"scroll_bar.horizontal","minimum_size_hint":[-1,-1],"size_hint":[62,21]}, +{"class":"QScrollBar","font":"Noto Sans 10","id":"scroll_bar.vertical","minimum_size_hint":[-1,-1],"size_hint":[21,62]}, +{"class":"QProgressBar","config":{"text_visible":true},"font":"Noto Sans 10","id":"progress_bar","minimum_size_hint":[91,20],"size_hint":[91,26]}, +{"class":"QProgressBar","config":{"text_visible":false},"font":"Noto Sans 10","id":"progress_bar.no_text","minimum_size_hint":[91,20],"size_hint":[91,26]}, +{"class":"QProgressBar","config":{"orientation":2},"font":"Noto Sans 10","id":"progress_bar.vertical","minimum_size_hint":[20,91],"size_hint":[26,91]}, +{"class":"QMenu","config":{"icons":true},"font":"Noto Sans 10","id":"menu.with_icon","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,173,29],"icon":true,"kind":"item","text":"Open"},{"geometry":[4,33,173,29],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,62,173,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,67,173,28],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,95,173,29],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,124,173,29],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[181,157]}, +{"class":"QMenu","config":{"icons":false},"font":"Noto Sans 10","id":"menu.no_icons","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,151,29],"icon":false,"kind":"item","text":"Open"},{"geometry":[4,33,151,29],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,62,151,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,67,151,28],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,95,151,29],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,124,151,29],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[159,157]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 10","id":"tab_bar","minimum_size_hint":[139,30],"size_hint":[244,30],"tab_rects":[[0,0,80,30],[80,0,84,30],[164,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":true,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 10","id":"tab_bar.document_mode","minimum_size_hint":[139,30],"size_hint":[244,30],"tab_rects":[[0,0,80,30],[80,0,84,30],[164,0,80,30]]}, +{"class":"QTabBar","config":{"closable":true,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 10","id":"tab_bar.closable","minimum_size_hint":[139,30],"size_hint":[264,30],"tab_rects":[[0,0,80,30],[80,0,104,30],[184,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":2,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 10","id":"tab_bar.west","minimum_size_hint":[30,139],"size_hint":[30,244],"tab_rects":[[0,0,30,80],[0,80,30,84],[0,164,30,80]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":1,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 10","id":"tab_bar.south","minimum_size_hint":[139,30],"size_hint":[244,30],"tab_rects":[[0,0,80,30],[80,0,84,30],[164,0,80,30]]}, +{"class":"QHeaderView","default_section_size":100,"font":"Noto Sans 10","id":"header.horizontal","minimum_section_size":49,"minimum_size_hint":[83,83],"section_size_hint":[51,49],"size_hint":[51,30]}, +{"class":"QHeaderView","default_section_size":30,"font":"Noto Sans 10","id":"header.vertical","minimum_section_size":30,"minimum_size_hint":[83,83],"section_size_hint":[30,30],"size_hint":[20,30]}, +{"class":"QTableView","font":"Noto Sans 10","id":"table_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":23}, +{"class":"QHeaderView","config":{"sort_indicator":true},"font":"Noto Sans 10","id":"header.horizontal.sort_indicator","minimum_size_hint":[83,83],"section_size_hint":[65,52],"size_hint":[65,30]}, +{"class":"QListView","config":{"check":false,"icon":false},"font":"Noto Sans 10","id":"list_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":24}, +{"class":"QListView","config":{"check":false,"icon":true},"font":"Noto Sans 10","id":"list_view.icon","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":26}, +{"class":"QListView","config":{"check":true,"icon":false},"font":"Noto Sans 10","id":"list_view.check","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":26}, +{"class":"QTreeView","config":{"icon":false},"font":"Noto Sans 10","header_height":30,"id":"tree_view","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":24}, +{"class":"QTreeView","config":{"icon":true},"font":"Noto Sans 10","header_height":30,"id":"tree_view.icon","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":26}, +{"class":"QLabel","config":{"text":"Label"},"font":"Noto Sans 8","id":"label","minimum_size_hint":[28,15],"size_hint":[28,15]}, +{"class":"QPushButton","config":{"text":"OK"},"font":"Noto Sans 8","id":"push_button","minimum_size_hint":[84,32],"size_hint":[84,32]}, +{"class":"QPushButton","config":{"text":"Cancel"},"font":"Noto Sans 8","id":"push_button","minimum_size_hint":[84,32],"size_hint":[84,32]}, +{"class":"QPushButton","config":{"text":"Apply changes"},"font":"Noto Sans 8","id":"push_button","minimum_size_hint":[92,32],"size_hint":[92,32]}, +{"class":"QPushButton","config":{"icon":true,"text":"Save"},"font":"Noto Sans 8","id":"push_button.icon","minimum_size_hint":[84,32],"size_hint":[84,32]}, +{"class":"QPushButton","config":{"default":true,"text":"OK"},"font":"Noto Sans 8","id":"push_button.default","minimum_size_hint":[84,32],"size_hint":[84,32]}, +{"class":"QPushButton","config":{"flat":true,"text":"OK"},"font":"Noto Sans 8","id":"push_button.flat","minimum_size_hint":[84,32],"size_hint":[84,32]}, +{"class":"QPushButton","config":{"icon":true},"font":"Noto Sans 8","id":"push_button.icon_only","minimum_size_hint":[31,32],"size_hint":[31,32]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 8","id":"tool_button.auto_raise.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 8","id":"tool_button.auto_raise.22","minimum_size_hint":[34,34],"size_hint":[34,34]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 8","id":"tool_button.framed.16","minimum_size_hint":[32,32],"size_hint":[32,32]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 8","id":"tool_button.framed.22","minimum_size_hint":[38,38],"size_hint":[38,38]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":2,"text":"Tool"},"font":"Noto Sans 8","id":"tool_button.text_beside_icon.16","minimum_size_hint":[60,28],"size_hint":[60,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":3,"text":"Tool"},"font":"Noto Sans 8","id":"tool_button.text_under_icon.22","minimum_size_hint":[40,53],"size_hint":[40,53]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":1,"style":0,"text":"Tool"},"font":"Noto Sans 8","id":"tool_button.menu_button_popup.16","minimum_size_hint":[48,28],"size_hint":[48,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":2,"style":0,"text":"Tool"},"font":"Noto Sans 8","id":"tool_button.instant_popup.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QLineEdit","font":"Noto Sans 8","id":"line_edit","minimum_size_hint":[47,29],"size_hint":[118,29]}, +{"class":"QLineEdit","config":{"clear_button":true},"font":"Noto Sans 8","id":"line_edit.clear_button","minimum_size_hint":[73,29],"size_hint":[144,29]}, +{"class":"QLineEdit","config":{"frame":false},"font":"Noto Sans 8","id":"line_edit.frameless","minimum_size_hint":[35,17],"size_hint":[106,17]}, +{"class":"QComboBox","config":{"items":["Item","Longer item text"]},"font":"Noto Sans 8","id":"combo_box","minimum_size_hint":[123,32],"size_hint":[123,32]}, +{"class":"QComboBox","config":{"editable":true},"font":"Noto Sans 8","id":"combo_box.editable","minimum_size_hint":[123,32],"size_hint":[80,32]}, +{"class":"QComboBox","config":{"icon":true},"font":"Noto Sans 8","id":"combo_box.icon","minimum_size_hint":[82,32],"size_hint":[82,32]}, +{"class":"QSpinBox","config":{"range":[0,99]},"font":"Noto Sans 8","id":"spin_box.0_99","minimum_size_hint":[49,29],"size_hint":[49,29]}, +{"class":"QSpinBox","config":{"range":[0,9999]},"font":"Noto Sans 8","id":"spin_box.0_9999","minimum_size_hint":[62,29],"size_hint":[62,29]}, +{"class":"QDoubleSpinBox","config":{"decimals":2,"range":[0,100]},"font":"Noto Sans 8","id":"double_spin_box","minimum_size_hint":[71,29],"size_hint":[71,29]}, +{"class":"QSpinBox","config":{"buttons":false,"range":[0,99]},"font":"Noto Sans 8","id":"spin_box.no_buttons","minimum_size_hint":[29,29],"size_hint":[29,29]}, +{"class":"QCheckBox","config":{"text":"Check"},"font":"Noto Sans 8","id":"check_box","minimum_size_hint":[56,20],"size_hint":[56,20]}, +{"class":"QCheckBox","font":"Noto Sans 8","id":"check_box.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QRadioButton","config":{"text":"Radio"},"font":"Noto Sans 8","id":"radio_button","minimum_size_hint":[54,20],"size_hint":[54,20]}, +{"class":"QRadioButton","font":"Noto Sans 8","id":"radio_button.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":0},"font":"Noto Sans 8","id":"slider.horizontal","minimum_size_hint":[20,20],"size_hint":[84,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":2},"font":"Noto Sans 8","id":"slider.horizontal.ticks_below","minimum_size_hint":[20,23],"size_hint":[84,23]}, +{"class":"QSlider","config":{"orientation":1,"ticks":3},"font":"Noto Sans 8","id":"slider.horizontal.ticks_both","minimum_size_hint":[20,26],"size_hint":[84,26]}, +{"class":"QSlider","config":{"orientation":2,"ticks":0},"font":"Noto Sans 8","id":"slider.vertical","minimum_size_hint":[20,20],"size_hint":[20,84]}, +{"class":"QScrollBar","font":"Noto Sans 8","id":"scroll_bar.horizontal","minimum_size_hint":[-1,-1],"size_hint":[62,21]}, +{"class":"QScrollBar","font":"Noto Sans 8","id":"scroll_bar.vertical","minimum_size_hint":[-1,-1],"size_hint":[21,62]}, +{"class":"QProgressBar","config":{"text_visible":true},"font":"Noto Sans 8","id":"progress_bar","minimum_size_hint":[87,17],"size_hint":[87,23]}, +{"class":"QProgressBar","config":{"text_visible":false},"font":"Noto Sans 8","id":"progress_bar.no_text","minimum_size_hint":[87,17],"size_hint":[87,23]}, +{"class":"QProgressBar","config":{"orientation":2},"font":"Noto Sans 8","id":"progress_bar.vertical","minimum_size_hint":[17,87],"size_hint":[23,87]}, +{"class":"QMenu","config":{"icons":true},"font":"Noto Sans 8","id":"menu.with_icon","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,158,29],"icon":true,"kind":"item","text":"Open"},{"geometry":[4,33,158,29],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,62,158,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,67,158,28],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,95,158,29],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,124,158,29],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[166,157]}, +{"class":"QMenu","config":{"icons":false},"font":"Noto Sans 8","id":"menu.no_icons","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,136,29],"icon":false,"kind":"item","text":"Open"},{"geometry":[4,33,136,29],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,62,136,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,67,136,28],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,95,136,29],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,124,136,29],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[144,157]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 8","id":"tab_bar","minimum_size_hint":[139,30],"size_hint":[240,30],"tab_rects":[[0,0,80,30],[80,0,80,30],[160,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":true,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 8","id":"tab_bar.document_mode","minimum_size_hint":[139,30],"size_hint":[240,30],"tab_rects":[[0,0,80,30],[80,0,80,30],[160,0,80,30]]}, +{"class":"QTabBar","config":{"closable":true,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 8","id":"tab_bar.closable","minimum_size_hint":[139,30],"size_hint":[254,30],"tab_rects":[[0,0,80,30],[80,0,94,30],[174,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":2,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 8","id":"tab_bar.west","minimum_size_hint":[30,139],"size_hint":[30,240],"tab_rects":[[0,0,30,80],[0,80,30,80],[0,160,30,80]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":1,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 8","id":"tab_bar.south","minimum_size_hint":[139,30],"size_hint":[240,30],"tab_rects":[[0,0,80,30],[80,0,80,30],[160,0,80,30]]}, +{"class":"QHeaderView","default_section_size":100,"font":"Noto Sans 8","id":"header.horizontal","minimum_section_size":43,"minimum_size_hint":[83,83],"section_size_hint":[45,43],"size_hint":[45,27]}, +{"class":"QHeaderView","default_section_size":30,"font":"Noto Sans 8","id":"header.vertical","minimum_section_size":27,"minimum_size_hint":[83,83],"section_size_hint":[27,27],"size_hint":[19,27]}, +{"class":"QTableView","font":"Noto Sans 8","id":"table_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":20}, +{"class":"QHeaderView","config":{"sort_indicator":true},"font":"Noto Sans 8","id":"header.horizontal.sort_indicator","minimum_size_hint":[83,83],"section_size_hint":[59,48],"size_hint":[59,27]}, +{"class":"QListView","config":{"check":false,"icon":false},"font":"Noto Sans 8","id":"list_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":21}, +{"class":"QListView","config":{"check":false,"icon":true},"font":"Noto Sans 8","id":"list_view.icon","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":24}, +{"class":"QListView","config":{"check":true,"icon":false},"font":"Noto Sans 8","id":"list_view.check","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":26}, +{"class":"QTreeView","config":{"icon":false},"font":"Noto Sans 8","header_height":27,"id":"tree_view","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":21}, +{"class":"QTreeView","config":{"icon":true},"font":"Noto Sans 8","header_height":27,"id":"tree_view.icon","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":24}, +{"class":"QLabel","config":{"text":"Label"},"font":"Noto Sans 9","id":"label","minimum_size_hint":[31,17],"size_hint":[31,17]}, +{"class":"QPushButton","config":{"text":"OK"},"font":"Noto Sans 9","id":"push_button","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"text":"Cancel"},"font":"Noto Sans 9","id":"push_button","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"text":"Apply changes"},"font":"Noto Sans 9","id":"push_button","minimum_size_hint":[98,34],"size_hint":[98,34]}, +{"class":"QPushButton","config":{"icon":true,"text":"Save"},"font":"Noto Sans 9","id":"push_button.icon","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"default":true,"text":"OK"},"font":"Noto Sans 9","id":"push_button.default","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"flat":true,"text":"OK"},"font":"Noto Sans 9","id":"push_button.flat","minimum_size_hint":[84,34],"size_hint":[84,34]}, +{"class":"QPushButton","config":{"icon":true},"font":"Noto Sans 9","id":"push_button.icon_only","minimum_size_hint":[31,32],"size_hint":[31,32]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 9","id":"tool_button.auto_raise.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 9","id":"tool_button.auto_raise.22","minimum_size_hint":[34,34],"size_hint":[34,34]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 9","id":"tool_button.framed.16","minimum_size_hint":[32,32],"size_hint":[32,32]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 9","id":"tool_button.framed.22","minimum_size_hint":[38,38],"size_hint":[38,38]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":2,"text":"Tool"},"font":"Noto Sans 9","id":"tool_button.text_beside_icon.16","minimum_size_hint":[62,29],"size_hint":[62,29]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":3,"text":"Tool"},"font":"Noto Sans 9","id":"tool_button.text_under_icon.22","minimum_size_hint":[42,55],"size_hint":[42,55]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":1,"style":0,"text":"Tool"},"font":"Noto Sans 9","id":"tool_button.menu_button_popup.16","minimum_size_hint":[48,28],"size_hint":[48,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":2,"style":0,"text":"Tool"},"font":"Noto Sans 9","id":"tool_button.instant_popup.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QLineEdit","font":"Noto Sans 9","id":"line_edit","minimum_size_hint":[50,31],"size_hint":[118,31]}, +{"class":"QLineEdit","config":{"clear_button":true},"font":"Noto Sans 9","id":"line_edit.clear_button","minimum_size_hint":[76,31],"size_hint":[144,31]}, +{"class":"QLineEdit","config":{"frame":false},"font":"Noto Sans 9","id":"line_edit.frameless","minimum_size_hint":[38,19],"size_hint":[106,19]}, +{"class":"QComboBox","config":{"items":["Item","Longer item text"]},"font":"Noto Sans 9","id":"combo_box","minimum_size_hint":[131,32],"size_hint":[131,32]}, +{"class":"QComboBox","config":{"editable":true},"font":"Noto Sans 9","id":"combo_box.editable","minimum_size_hint":[131,32],"size_hint":[80,32]}, +{"class":"QComboBox","config":{"icon":true},"font":"Noto Sans 9","id":"combo_box.icon","minimum_size_hint":[85,32],"size_hint":[85,32]}, +{"class":"QSpinBox","config":{"range":[0,99]},"font":"Noto Sans 9","id":"spin_box.0_99","minimum_size_hint":[51,31],"size_hint":[51,31]}, +{"class":"QSpinBox","config":{"range":[0,9999]},"font":"Noto Sans 9","id":"spin_box.0_9999","minimum_size_hint":[65,31],"size_hint":[65,31]}, +{"class":"QDoubleSpinBox","config":{"decimals":2,"range":[0,100]},"font":"Noto Sans 9","id":"double_spin_box","minimum_size_hint":[75,31],"size_hint":[75,31]}, +{"class":"QSpinBox","config":{"buttons":false,"range":[0,99]},"font":"Noto Sans 9","id":"spin_box.no_buttons","minimum_size_hint":[31,31],"size_hint":[31,31]}, +{"class":"QCheckBox","config":{"text":"Check"},"font":"Noto Sans 9","id":"check_box","minimum_size_hint":[59,21],"size_hint":[59,21]}, +{"class":"QCheckBox","font":"Noto Sans 9","id":"check_box.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QRadioButton","config":{"text":"Radio"},"font":"Noto Sans 9","id":"radio_button","minimum_size_hint":[56,21],"size_hint":[56,21]}, +{"class":"QRadioButton","font":"Noto Sans 9","id":"radio_button.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":0},"font":"Noto Sans 9","id":"slider.horizontal","minimum_size_hint":[20,20],"size_hint":[84,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":2},"font":"Noto Sans 9","id":"slider.horizontal.ticks_below","minimum_size_hint":[20,23],"size_hint":[84,23]}, +{"class":"QSlider","config":{"orientation":1,"ticks":3},"font":"Noto Sans 9","id":"slider.horizontal.ticks_both","minimum_size_hint":[20,26],"size_hint":[84,26]}, +{"class":"QSlider","config":{"orientation":2,"ticks":0},"font":"Noto Sans 9","id":"slider.vertical","minimum_size_hint":[20,20],"size_hint":[20,84]}, +{"class":"QScrollBar","font":"Noto Sans 9","id":"scroll_bar.horizontal","minimum_size_hint":[-1,-1],"size_hint":[62,21]}, +{"class":"QScrollBar","font":"Noto Sans 9","id":"scroll_bar.vertical","minimum_size_hint":[-1,-1],"size_hint":[21,62]}, +{"class":"QProgressBar","config":{"text_visible":true},"font":"Noto Sans 9","id":"progress_bar","minimum_size_hint":[91,19],"size_hint":[91,25]}, +{"class":"QProgressBar","config":{"text_visible":false},"font":"Noto Sans 9","id":"progress_bar.no_text","minimum_size_hint":[91,19],"size_hint":[91,25]}, +{"class":"QProgressBar","config":{"orientation":2},"font":"Noto Sans 9","id":"progress_bar.vertical","minimum_size_hint":[19,91],"size_hint":[25,91]}, +{"class":"QMenu","config":{"icons":true},"font":"Noto Sans 9","id":"menu.with_icon","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,165,29],"icon":true,"kind":"item","text":"Open"},{"geometry":[4,33,165,29],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,62,165,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,67,165,28],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,95,165,29],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,124,165,29],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[173,157]}, +{"class":"QMenu","config":{"icons":false},"font":"Noto Sans 9","id":"menu.no_icons","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,143,29],"icon":false,"kind":"item","text":"Open"},{"geometry":[4,33,143,29],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,62,143,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,67,143,28],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,95,143,29],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,124,143,29],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[151,157]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 9","id":"tab_bar","minimum_size_hint":[139,30],"size_hint":[240,30],"tab_rects":[[0,0,80,30],[80,0,80,30],[160,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":true,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 9","id":"tab_bar.document_mode","minimum_size_hint":[139,30],"size_hint":[240,30],"tab_rects":[[0,0,80,30],[80,0,80,30],[160,0,80,30]]}, +{"class":"QTabBar","config":{"closable":true,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 9","id":"tab_bar.closable","minimum_size_hint":[139,30],"size_hint":[259,30],"tab_rects":[[0,0,80,30],[80,0,99,30],[179,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":2,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 9","id":"tab_bar.west","minimum_size_hint":[30,139],"size_hint":[30,240],"tab_rects":[[0,0,30,80],[0,80,30,80],[0,160,30,80]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":1,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 9","id":"tab_bar.south","minimum_size_hint":[139,30],"size_hint":[240,30],"tab_rects":[[0,0,80,30],[80,0,80,30],[160,0,80,30]]}, +{"class":"QHeaderView","default_section_size":100,"font":"Noto Sans 9","id":"header.horizontal","minimum_section_size":46,"minimum_size_hint":[83,83],"section_size_hint":[48,46],"size_hint":[48,29]}, +{"class":"QHeaderView","default_section_size":30,"font":"Noto Sans 9","id":"header.vertical","minimum_section_size":29,"minimum_size_hint":[83,83],"section_size_hint":[29,29],"size_hint":[19,29]}, +{"class":"QTableView","font":"Noto Sans 9","id":"table_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":22}, +{"class":"QHeaderView","config":{"sort_indicator":true},"font":"Noto Sans 9","id":"header.horizontal.sort_indicator","minimum_size_hint":[83,83],"section_size_hint":[62,50],"size_hint":[62,29]}, +{"class":"QListView","config":{"check":false,"icon":false},"font":"Noto Sans 9","id":"list_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":23}, +{"class":"QListView","config":{"check":false,"icon":true},"font":"Noto Sans 9","id":"list_view.icon","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":25}, +{"class":"QListView","config":{"check":true,"icon":false},"font":"Noto Sans 9","id":"list_view.check","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":26}, +{"class":"QTreeView","config":{"icon":false},"font":"Noto Sans 9","header_height":29,"id":"tree_view","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":23}, +{"class":"QTreeView","config":{"icon":true},"font":"Noto Sans 9","header_height":29,"id":"tree_view.icon","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":25}, +{"class":"QLabel","config":{"text":"Label"},"font":"Noto Sans 11","id":"label","minimum_size_hint":[38,21],"size_hint":[38,21]}, +{"class":"QPushButton","config":{"text":"OK"},"font":"Noto Sans 11","id":"push_button","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"text":"Cancel"},"font":"Noto Sans 11","id":"push_button","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"text":"Apply changes"},"font":"Noto Sans 11","id":"push_button","minimum_size_hint":[120,38],"size_hint":[120,38]}, +{"class":"QPushButton","config":{"icon":true,"text":"Save"},"font":"Noto Sans 11","id":"push_button.icon","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"default":true,"text":"OK"},"font":"Noto Sans 11","id":"push_button.default","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"flat":true,"text":"OK"},"font":"Noto Sans 11","id":"push_button.flat","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"icon":true},"font":"Noto Sans 11","id":"push_button.icon_only","minimum_size_hint":[31,32],"size_hint":[31,32]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 11","id":"tool_button.auto_raise.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 11","id":"tool_button.auto_raise.22","minimum_size_hint":[34,34],"size_hint":[34,34]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 11","id":"tool_button.framed.16","minimum_size_hint":[32,32],"size_hint":[32,32]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 11","id":"tool_button.framed.22","minimum_size_hint":[38,38],"size_hint":[38,38]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":2,"text":"Tool"},"font":"Noto Sans 11","id":"tool_button.text_beside_icon.16","minimum_size_hint":[70,33],"size_hint":[70,33]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":3,"text":"Tool"},"font":"Noto Sans 11","id":"tool_button.text_under_icon.22","minimum_size_hint":[50,59],"size_hint":[50,59]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":1,"style":0,"text":"Tool"},"font":"Noto Sans 11","id":"tool_button.menu_button_popup.16","minimum_size_hint":[48,28],"size_hint":[48,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":2,"style":0,"text":"Tool"},"font":"Noto Sans 11","id":"tool_button.instant_popup.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QLineEdit","font":"Noto Sans 11","id":"line_edit","minimum_size_hint":[59,34],"size_hint":[152,34]}, +{"class":"QLineEdit","config":{"clear_button":true},"font":"Noto Sans 11","id":"line_edit.clear_button","minimum_size_hint":[85,34],"size_hint":[178,34]}, +{"class":"QLineEdit","config":{"frame":false},"font":"Noto Sans 11","id":"line_edit.frameless","minimum_size_hint":[47,22],"size_hint":[140,22]}, +{"class":"QComboBox","config":{"items":["Item","Longer item text"]},"font":"Noto Sans 11","id":"combo_box","minimum_size_hint":[155,35],"size_hint":[155,35]}, +{"class":"QComboBox","config":{"editable":true},"font":"Noto Sans 11","id":"combo_box.editable","minimum_size_hint":[155,35],"size_hint":[94,35]}, +{"class":"QComboBox","config":{"icon":true},"font":"Noto Sans 11","id":"combo_box.icon","minimum_size_hint":[90,35],"size_hint":[90,35]}, +{"class":"QSpinBox","config":{"range":[0,99]},"font":"Noto Sans 11","id":"spin_box.0_99","minimum_size_hint":[55,34],"size_hint":[55,34]}, +{"class":"QSpinBox","config":{"range":[0,9999]},"font":"Noto Sans 11","id":"spin_box.0_9999","minimum_size_hint":[72,34],"size_hint":[72,34]}, +{"class":"QDoubleSpinBox","config":{"decimals":2,"range":[0,100]},"font":"Noto Sans 11","id":"double_spin_box","minimum_size_hint":[85,34],"size_hint":[85,34]}, +{"class":"QSpinBox","config":{"buttons":false,"range":[0,99]},"font":"Noto Sans 11","id":"spin_box.no_buttons","minimum_size_hint":[35,34],"size_hint":[35,34]}, +{"class":"QCheckBox","config":{"text":"Check"},"font":"Noto Sans 11","id":"check_box","minimum_size_hint":[67,25],"size_hint":[67,25]}, +{"class":"QCheckBox","font":"Noto Sans 11","id":"check_box.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QRadioButton","config":{"text":"Radio"},"font":"Noto Sans 11","id":"radio_button","minimum_size_hint":[64,25],"size_hint":[64,25]}, +{"class":"QRadioButton","font":"Noto Sans 11","id":"radio_button.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":0},"font":"Noto Sans 11","id":"slider.horizontal","minimum_size_hint":[20,20],"size_hint":[84,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":2},"font":"Noto Sans 11","id":"slider.horizontal.ticks_below","minimum_size_hint":[20,23],"size_hint":[84,23]}, +{"class":"QSlider","config":{"orientation":1,"ticks":3},"font":"Noto Sans 11","id":"slider.horizontal.ticks_both","minimum_size_hint":[20,26],"size_hint":[84,26]}, +{"class":"QSlider","config":{"orientation":2,"ticks":0},"font":"Noto Sans 11","id":"slider.vertical","minimum_size_hint":[20,20],"size_hint":[20,84]}, +{"class":"QScrollBar","font":"Noto Sans 11","id":"scroll_bar.horizontal","minimum_size_hint":[-1,-1],"size_hint":[62,21]}, +{"class":"QScrollBar","font":"Noto Sans 11","id":"scroll_bar.vertical","minimum_size_hint":[-1,-1],"size_hint":[21,62]}, +{"class":"QProgressBar","config":{"text_visible":true},"font":"Noto Sans 11","id":"progress_bar","minimum_size_hint":[99,22],"size_hint":[99,28]}, +{"class":"QProgressBar","config":{"text_visible":false},"font":"Noto Sans 11","id":"progress_bar.no_text","minimum_size_hint":[99,22],"size_hint":[99,28]}, +{"class":"QProgressBar","config":{"orientation":2},"font":"Noto Sans 11","id":"progress_bar.vertical","minimum_size_hint":[22,99],"size_hint":[28,99]}, +{"class":"QMenu","config":{"icons":true},"font":"Noto Sans 11","id":"menu.with_icon","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,189,30],"icon":true,"kind":"item","text":"Open"},{"geometry":[4,34,189,30],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,64,189,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,69,189,28],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,97,189,30],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,127,189,30],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[197,161]}, +{"class":"QMenu","config":{"icons":false},"font":"Noto Sans 11","id":"menu.no_icons","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,167,30],"icon":false,"kind":"item","text":"Open"},{"geometry":[4,34,167,30],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,64,167,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,69,167,28],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,97,167,30],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,127,167,30],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[175,161]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 11","id":"tab_bar","minimum_size_hint":[139,30],"size_hint":[255,30],"tab_rects":[[0,0,80,30],[80,0,95,30],[175,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":true,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 11","id":"tab_bar.document_mode","minimum_size_hint":[139,30],"size_hint":[255,30],"tab_rects":[[0,0,80,30],[80,0,95,30],[175,0,80,30]]}, +{"class":"QTabBar","config":{"closable":true,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 11","id":"tab_bar.closable","minimum_size_hint":[139,30],"size_hint":[275,30],"tab_rects":[[0,0,80,30],[80,0,115,30],[195,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":2,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 11","id":"tab_bar.west","minimum_size_hint":[30,139],"size_hint":[30,255],"tab_rects":[[0,0,30,80],[0,80,30,95],[0,175,30,80]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":1,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 11","id":"tab_bar.south","minimum_size_hint":[139,30],"size_hint":[255,30],"tab_rects":[[0,0,80,30],[80,0,95,30],[175,0,80,30]]}, +{"class":"QHeaderView","default_section_size":100,"font":"Noto Sans 11","id":"header.horizontal","minimum_section_size":55,"minimum_size_hint":[83,83],"section_size_hint":[57,55],"size_hint":[57,33]}, +{"class":"QHeaderView","default_section_size":32,"font":"Noto Sans 11","id":"header.vertical","minimum_section_size":32,"minimum_size_hint":[83,83],"section_size_hint":[33,33],"size_hint":[21,33]}, +{"class":"QTableView","font":"Noto Sans 11","id":"table_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":26}, +{"class":"QHeaderView","config":{"sort_indicator":true},"font":"Noto Sans 11","id":"header.horizontal.sort_indicator","minimum_size_hint":[83,83],"section_size_hint":[71,55],"size_hint":[71,33]}, +{"class":"QListView","config":{"check":false,"icon":false},"font":"Noto Sans 11","id":"list_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":27}, +{"class":"QListView","config":{"check":false,"icon":true},"font":"Noto Sans 11","id":"list_view.icon","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":29}, +{"class":"QListView","config":{"check":true,"icon":false},"font":"Noto Sans 11","id":"list_view.check","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":27}, +{"class":"QTreeView","config":{"icon":false},"font":"Noto Sans 11","header_height":33,"id":"tree_view","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":27}, +{"class":"QTreeView","config":{"icon":true},"font":"Noto Sans 11","header_height":33,"id":"tree_view.icon","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":29}, +{"class":"QLabel","config":{"text":"Label"},"font":"Noto Sans 12","id":"label","minimum_size_hint":[41,22],"size_hint":[41,22]}, +{"class":"QPushButton","config":{"text":"OK"},"font":"Noto Sans 12","id":"push_button","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"text":"Cancel"},"font":"Noto Sans 12","id":"push_button","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"text":"Apply changes"},"font":"Noto Sans 12","id":"push_button","minimum_size_hint":[126,38],"size_hint":[126,38]}, +{"class":"QPushButton","config":{"icon":true,"text":"Save"},"font":"Noto Sans 12","id":"push_button.icon","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"default":true,"text":"OK"},"font":"Noto Sans 12","id":"push_button.default","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"flat":true,"text":"OK"},"font":"Noto Sans 12","id":"push_button.flat","minimum_size_hint":[84,38],"size_hint":[84,38]}, +{"class":"QPushButton","config":{"icon":true},"font":"Noto Sans 12","id":"push_button.icon_only","minimum_size_hint":[31,32],"size_hint":[31,32]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 12","id":"tool_button.auto_raise.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 12","id":"tool_button.auto_raise.22","minimum_size_hint":[34,34],"size_hint":[34,34]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 12","id":"tool_button.framed.16","minimum_size_hint":[32,32],"size_hint":[32,32]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 12","id":"tool_button.framed.22","minimum_size_hint":[38,38],"size_hint":[38,38]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":2,"text":"Tool"},"font":"Noto Sans 12","id":"tool_button.text_beside_icon.16","minimum_size_hint":[72,34],"size_hint":[72,34]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":3,"text":"Tool"},"font":"Noto Sans 12","id":"tool_button.text_under_icon.22","minimum_size_hint":[52,60],"size_hint":[52,60]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":1,"style":0,"text":"Tool"},"font":"Noto Sans 12","id":"tool_button.menu_button_popup.16","minimum_size_hint":[48,28],"size_hint":[48,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":2,"style":0,"text":"Tool"},"font":"Noto Sans 12","id":"tool_button.instant_popup.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QLineEdit","font":"Noto Sans 12","id":"line_edit","minimum_size_hint":[61,36],"size_hint":[152,36]}, +{"class":"QLineEdit","config":{"clear_button":true},"font":"Noto Sans 12","id":"line_edit.clear_button","minimum_size_hint":[87,36],"size_hint":[178,36]}, +{"class":"QLineEdit","config":{"frame":false},"font":"Noto Sans 12","id":"line_edit.frameless","minimum_size_hint":[49,24],"size_hint":[140,24]}, +{"class":"QComboBox","config":{"items":["Item","Longer item text"]},"font":"Noto Sans 12","id":"combo_box","minimum_size_hint":[162,36],"size_hint":[162,36]}, +{"class":"QComboBox","config":{"editable":true},"font":"Noto Sans 12","id":"combo_box.editable","minimum_size_hint":[162,36],"size_hint":[94,36]}, +{"class":"QComboBox","config":{"icon":true},"font":"Noto Sans 12","id":"combo_box.icon","minimum_size_hint":[93,36],"size_hint":[93,36]}, +{"class":"QSpinBox","config":{"range":[0,99]},"font":"Noto Sans 12","id":"spin_box.0_99","minimum_size_hint":[56,36],"size_hint":[56,36]}, +{"class":"QSpinBox","config":{"range":[0,9999]},"font":"Noto Sans 12","id":"spin_box.0_9999","minimum_size_hint":[75,36],"size_hint":[75,36]}, +{"class":"QDoubleSpinBox","config":{"decimals":2,"range":[0,100]},"font":"Noto Sans 12","id":"double_spin_box","minimum_size_hint":[88,36],"size_hint":[88,36]}, +{"class":"QSpinBox","config":{"buttons":false,"range":[0,99]},"font":"Noto Sans 12","id":"spin_box.no_buttons","minimum_size_hint":[36,36],"size_hint":[36,36]}, +{"class":"QCheckBox","config":{"text":"Check"},"font":"Noto Sans 12","id":"check_box","minimum_size_hint":[70,26],"size_hint":[70,26]}, +{"class":"QCheckBox","font":"Noto Sans 12","id":"check_box.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QRadioButton","config":{"text":"Radio"},"font":"Noto Sans 12","id":"radio_button","minimum_size_hint":[67,26],"size_hint":[67,26]}, +{"class":"QRadioButton","font":"Noto Sans 12","id":"radio_button.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":0},"font":"Noto Sans 12","id":"slider.horizontal","minimum_size_hint":[20,20],"size_hint":[84,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":2},"font":"Noto Sans 12","id":"slider.horizontal.ticks_below","minimum_size_hint":[20,23],"size_hint":[84,23]}, +{"class":"QSlider","config":{"orientation":1,"ticks":3},"font":"Noto Sans 12","id":"slider.horizontal.ticks_both","minimum_size_hint":[20,26],"size_hint":[84,26]}, +{"class":"QSlider","config":{"orientation":2,"ticks":0},"font":"Noto Sans 12","id":"slider.vertical","minimum_size_hint":[20,20],"size_hint":[20,84]}, +{"class":"QScrollBar","font":"Noto Sans 12","id":"scroll_bar.horizontal","minimum_size_hint":[-1,-1],"size_hint":[62,21]}, +{"class":"QScrollBar","font":"Noto Sans 12","id":"scroll_bar.vertical","minimum_size_hint":[-1,-1],"size_hint":[21,62]}, +{"class":"QProgressBar","config":{"text_visible":true},"font":"Noto Sans 12","id":"progress_bar","minimum_size_hint":[99,24],"size_hint":[99,30]}, +{"class":"QProgressBar","config":{"text_visible":false},"font":"Noto Sans 12","id":"progress_bar.no_text","minimum_size_hint":[99,24],"size_hint":[99,30]}, +{"class":"QProgressBar","config":{"orientation":2},"font":"Noto Sans 12","id":"progress_bar.vertical","minimum_size_hint":[24,99],"size_hint":[30,99]}, +{"class":"QMenu","config":{"icons":true},"font":"Noto Sans 12","id":"menu.with_icon","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,197,31],"icon":true,"kind":"item","text":"Open"},{"geometry":[4,35,197,31],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,66,197,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,71,197,30],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,101,197,31],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,132,197,31],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[205,167]}, +{"class":"QMenu","config":{"icons":false},"font":"Noto Sans 12","id":"menu.no_icons","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,175,31],"icon":false,"kind":"item","text":"Open"},{"geometry":[4,35,175,31],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,66,175,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,71,175,30],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,101,175,31],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,132,175,31],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[183,167]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 12","id":"tab_bar","minimum_size_hint":[139,30],"size_hint":[260,30],"tab_rects":[[0,0,80,30],[80,0,100,30],[180,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":true,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 12","id":"tab_bar.document_mode","minimum_size_hint":[139,30],"size_hint":[260,30],"tab_rects":[[0,0,80,30],[80,0,100,30],[180,0,80,30]]}, +{"class":"QTabBar","config":{"closable":true,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 12","id":"tab_bar.closable","minimum_size_hint":[139,30],"size_hint":[280,30],"tab_rects":[[0,0,80,30],[80,0,120,30],[200,0,80,30]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":2,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 12","id":"tab_bar.west","minimum_size_hint":[30,139],"size_hint":[30,260],"tab_rects":[[0,0,30,80],[0,80,30,100],[0,180,30,80]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":1,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 12","id":"tab_bar.south","minimum_size_hint":[139,30],"size_hint":[260,30],"tab_rects":[[0,0,80,30],[80,0,100,30],[180,0,80,30]]}, +{"class":"QHeaderView","default_section_size":100,"font":"Noto Sans 12","id":"header.horizontal","minimum_section_size":57,"minimum_size_hint":[83,83],"section_size_hint":[60,57],"size_hint":[60,34]}, +{"class":"QHeaderView","default_section_size":34,"font":"Noto Sans 12","id":"header.vertical","minimum_section_size":34,"minimum_size_hint":[83,83],"section_size_hint":[34,34],"size_hint":[22,34]}, +{"class":"QTableView","font":"Noto Sans 12","id":"table_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":27}, +{"class":"QHeaderView","config":{"sort_indicator":true},"font":"Noto Sans 12","id":"header.horizontal.sort_indicator","minimum_size_hint":[83,83],"section_size_hint":[74,57],"size_hint":[74,34]}, +{"class":"QListView","config":{"check":false,"icon":false},"font":"Noto Sans 12","id":"list_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":28}, +{"class":"QListView","config":{"check":false,"icon":true},"font":"Noto Sans 12","id":"list_view.icon","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":30}, +{"class":"QListView","config":{"check":true,"icon":false},"font":"Noto Sans 12","id":"list_view.check","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":28}, +{"class":"QTreeView","config":{"icon":false},"font":"Noto Sans 12","header_height":34,"id":"tree_view","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":28}, +{"class":"QTreeView","config":{"icon":true},"font":"Noto Sans 12","header_height":34,"id":"tree_view.icon","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":30}, +{"class":"QLabel","config":{"text":"Label"},"font":"Noto Sans 14","id":"label","minimum_size_hint":[48,26],"size_hint":[48,26]}, +{"class":"QPushButton","config":{"text":"OK"},"font":"Noto Sans 14","id":"push_button","minimum_size_hint":[84,42],"size_hint":[84,42]}, +{"class":"QPushButton","config":{"text":"Cancel"},"font":"Noto Sans 14","id":"push_button","minimum_size_hint":[84,42],"size_hint":[84,42]}, +{"class":"QPushButton","config":{"text":"Apply changes"},"font":"Noto Sans 14","id":"push_button","minimum_size_hint":[146,42],"size_hint":[146,42]}, +{"class":"QPushButton","config":{"icon":true,"text":"Save"},"font":"Noto Sans 14","id":"push_button.icon","minimum_size_hint":[84,42],"size_hint":[84,42]}, +{"class":"QPushButton","config":{"default":true,"text":"OK"},"font":"Noto Sans 14","id":"push_button.default","minimum_size_hint":[84,42],"size_hint":[84,42]}, +{"class":"QPushButton","config":{"flat":true,"text":"OK"},"font":"Noto Sans 14","id":"push_button.flat","minimum_size_hint":[84,42],"size_hint":[84,42]}, +{"class":"QPushButton","config":{"icon":true},"font":"Noto Sans 14","id":"push_button.icon_only","minimum_size_hint":[31,32],"size_hint":[31,32]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 14","id":"tool_button.auto_raise.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 14","id":"tool_button.auto_raise.22","minimum_size_hint":[34,34],"size_hint":[34,34]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":16,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 14","id":"tool_button.framed.16","minimum_size_hint":[32,32],"size_hint":[32,32]}, +{"class":"QToolButton","config":{"auto_raise":false,"icon_size":22,"popup_mode":-1,"style":0,"text":"Tool"},"font":"Noto Sans 14","id":"tool_button.framed.22","minimum_size_hint":[38,38],"size_hint":[38,38]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":-1,"style":2,"text":"Tool"},"font":"Noto Sans 14","id":"tool_button.text_beside_icon.16","minimum_size_hint":[80,38],"size_hint":[80,38]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":22,"popup_mode":-1,"style":3,"text":"Tool"},"font":"Noto Sans 14","id":"tool_button.text_under_icon.22","minimum_size_hint":[60,64],"size_hint":[60,64]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":1,"style":0,"text":"Tool"},"font":"Noto Sans 14","id":"tool_button.menu_button_popup.16","minimum_size_hint":[48,28],"size_hint":[48,28]}, +{"class":"QToolButton","config":{"auto_raise":true,"icon_size":16,"popup_mode":2,"style":0,"text":"Tool"},"font":"Noto Sans 14","id":"tool_button.instant_popup.16","minimum_size_hint":[28,28],"size_hint":[28,28]}, +{"class":"QLineEdit","font":"Noto Sans 14","id":"line_edit","minimum_size_hint":[70,40],"size_hint":[186,40]}, +{"class":"QLineEdit","config":{"clear_button":true},"font":"Noto Sans 14","id":"line_edit.clear_button","minimum_size_hint":[96,40],"size_hint":[212,40]}, +{"class":"QLineEdit","config":{"frame":false},"font":"Noto Sans 14","id":"line_edit.frameless","minimum_size_hint":[58,28],"size_hint":[174,28]}, +{"class":"QComboBox","config":{"items":["Item","Longer item text"]},"font":"Noto Sans 14","id":"combo_box","minimum_size_hint":[185,40],"size_hint":[185,40]}, +{"class":"QComboBox","config":{"editable":true},"font":"Noto Sans 14","id":"combo_box.editable","minimum_size_hint":[185,40],"size_hint":[108,40]}, +{"class":"QComboBox","config":{"icon":true},"font":"Noto Sans 14","id":"combo_box.icon","minimum_size_hint":[99,40],"size_hint":[99,40]}, +{"class":"QSpinBox","config":{"range":[0,99]},"font":"Noto Sans 14","id":"spin_box.0_99","minimum_size_hint":[61,40],"size_hint":[61,40]}, +{"class":"QSpinBox","config":{"range":[0,9999]},"font":"Noto Sans 14","id":"spin_box.0_9999","minimum_size_hint":[82,40],"size_hint":[82,40]}, +{"class":"QDoubleSpinBox","config":{"decimals":2,"range":[0,100]},"font":"Noto Sans 14","id":"double_spin_box","minimum_size_hint":[98,40],"size_hint":[98,40]}, +{"class":"QSpinBox","config":{"buttons":false,"range":[0,99]},"font":"Noto Sans 14","id":"spin_box.no_buttons","minimum_size_hint":[41,40],"size_hint":[41,40]}, +{"class":"QCheckBox","config":{"text":"Check"},"font":"Noto Sans 14","id":"check_box","minimum_size_hint":[78,30],"size_hint":[78,30]}, +{"class":"QCheckBox","font":"Noto Sans 14","id":"check_box.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QRadioButton","config":{"text":"Radio"},"font":"Noto Sans 14","id":"radio_button","minimum_size_hint":[75,30],"size_hint":[75,30]}, +{"class":"QRadioButton","font":"Noto Sans 14","id":"radio_button.no_text","minimum_size_hint":[20,20],"size_hint":[20,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":0},"font":"Noto Sans 14","id":"slider.horizontal","minimum_size_hint":[20,20],"size_hint":[84,20]}, +{"class":"QSlider","config":{"orientation":1,"ticks":2},"font":"Noto Sans 14","id":"slider.horizontal.ticks_below","minimum_size_hint":[20,23],"size_hint":[84,23]}, +{"class":"QSlider","config":{"orientation":1,"ticks":3},"font":"Noto Sans 14","id":"slider.horizontal.ticks_both","minimum_size_hint":[20,26],"size_hint":[84,26]}, +{"class":"QSlider","config":{"orientation":2,"ticks":0},"font":"Noto Sans 14","id":"slider.vertical","minimum_size_hint":[20,20],"size_hint":[20,84]}, +{"class":"QScrollBar","font":"Noto Sans 14","id":"scroll_bar.horizontal","minimum_size_hint":[-1,-1],"size_hint":[62,21]}, +{"class":"QScrollBar","font":"Noto Sans 14","id":"scroll_bar.vertical","minimum_size_hint":[-1,-1],"size_hint":[21,62]}, +{"class":"QProgressBar","config":{"text_visible":true},"font":"Noto Sans 14","id":"progress_bar","minimum_size_hint":[107,28],"size_hint":[107,34]}, +{"class":"QProgressBar","config":{"text_visible":false},"font":"Noto Sans 14","id":"progress_bar.no_text","minimum_size_hint":[107,28],"size_hint":[107,34]}, +{"class":"QProgressBar","config":{"orientation":2},"font":"Noto Sans 14","id":"progress_bar.vertical","minimum_size_hint":[28,107],"size_hint":[34,107]}, +{"class":"QMenu","config":{"icons":true},"font":"Noto Sans 14","id":"menu.with_icon","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,219,35],"icon":true,"kind":"item","text":"Open"},{"geometry":[4,39,219,35],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,74,219,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,79,219,34],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,113,219,35],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,148,219,35],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[227,187]}, +{"class":"QMenu","config":{"icons":false},"font":"Noto Sans 14","id":"menu.no_icons","minimum_size_hint":[-1,-1],"rows":[{"geometry":[4,4,197,35],"icon":false,"kind":"item","text":"Open"},{"geometry":[4,39,197,35],"icon":false,"kind":"shortcut","text":"Save"},{"geometry":[4,74,197,5],"icon":false,"kind":"separator","text":""},{"geometry":[4,79,197,34],"icon":false,"kind":"section","text":"Section"},{"geometry":[4,113,197,35],"icon":false,"kind":"checkable","text":"Checkable"},{"geometry":[4,148,197,35],"icon":false,"kind":"submenu","text":"Submenu"}],"size_hint":[205,187]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 14","id":"tab_bar","minimum_size_hint":[139,34],"size_hint":[276,34],"tab_rects":[[0,0,80,34],[80,0,116,34],[196,0,80,34]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":true,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 14","id":"tab_bar.document_mode","minimum_size_hint":[139,34],"size_hint":[276,34],"tab_rects":[[0,0,80,34],[80,0,116,34],[196,0,80,34]]}, +{"class":"QTabBar","config":{"closable":true,"document_mode":false,"shape":0,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 14","id":"tab_bar.closable","minimum_size_hint":[139,34],"size_hint":[299,34],"tab_rects":[[0,0,80,34],[80,0,136,34],[216,0,83,34]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":2,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 14","id":"tab_bar.west","minimum_size_hint":[34,139],"size_hint":[34,276],"tab_rects":[[0,0,34,80],[0,80,34,116],[0,196,34,80]]}, +{"class":"QTabBar","config":{"closable":false,"document_mode":false,"shape":1,"tabs":["Tab","Second tab","Third"]},"font":"Noto Sans 14","id":"tab_bar.south","minimum_size_hint":[139,34],"size_hint":[276,34],"tab_rects":[[0,0,80,34],[80,0,116,34],[196,0,80,34]]}, +{"class":"QHeaderView","default_section_size":100,"font":"Noto Sans 14","id":"header.horizontal","minimum_section_size":66,"minimum_size_hint":[83,83],"section_size_hint":[69,66],"size_hint":[69,38]}, +{"class":"QHeaderView","default_section_size":38,"font":"Noto Sans 14","id":"header.vertical","minimum_section_size":38,"minimum_size_hint":[83,83],"section_size_hint":[38,38],"size_hint":[23,38]}, +{"class":"QTableView","font":"Noto Sans 14","id":"table_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":31}, +{"class":"QHeaderView","config":{"sort_indicator":true},"font":"Noto Sans 14","id":"header.horizontal.sort_indicator","minimum_size_hint":[83,83],"section_size_hint":[83,66],"size_hint":[83,38]}, +{"class":"QListView","config":{"check":false,"icon":false},"font":"Noto Sans 14","id":"list_view","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":32}, +{"class":"QListView","config":{"check":false,"icon":true},"font":"Noto Sans 14","id":"list_view.icon","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":34}, +{"class":"QListView","config":{"check":true,"icon":false},"font":"Noto Sans 14","id":"list_view.check","minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":32}, +{"class":"QTreeView","config":{"icon":false},"font":"Noto Sans 14","header_height":38,"id":"tree_view","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":32}, +{"class":"QTreeView","config":{"icon":true},"font":"Noto Sans 14","header_height":38,"id":"tree_view.icon","indentation":20,"minimum_size_hint":[83,83],"size_hint":[256,192],"size_hint_for_row":34} +], +"legacy_font_weights": [ +{"legacy":0,"weight":100}, +{"legacy":1,"weight":100}, +{"legacy":2,"weight":100}, +{"legacy":3,"weight":100}, +{"legacy":4,"weight":100}, +{"legacy":5,"weight":100}, +{"legacy":6,"weight":100}, +{"legacy":7,"weight":200}, +{"legacy":8,"weight":200}, +{"legacy":9,"weight":200}, +{"legacy":10,"weight":200}, +{"legacy":11,"weight":200}, +{"legacy":12,"weight":200}, +{"legacy":13,"weight":200}, +{"legacy":14,"weight":200}, +{"legacy":15,"weight":200}, +{"legacy":16,"weight":200}, +{"legacy":17,"weight":200}, +{"legacy":18,"weight":200}, +{"legacy":19,"weight":300}, +{"legacy":20,"weight":300}, +{"legacy":21,"weight":300}, +{"legacy":22,"weight":300}, +{"legacy":23,"weight":300}, +{"legacy":24,"weight":300}, +{"legacy":25,"weight":300}, +{"legacy":26,"weight":300}, +{"legacy":27,"weight":300}, +{"legacy":28,"weight":300}, +{"legacy":29,"weight":300}, +{"legacy":30,"weight":300}, +{"legacy":31,"weight":300}, +{"legacy":32,"weight":300}, +{"legacy":33,"weight":300}, +{"legacy":34,"weight":300}, +{"legacy":35,"weight":300}, +{"legacy":36,"weight":300}, +{"legacy":37,"weight":300}, +{"legacy":38,"weight":400}, +{"legacy":39,"weight":400}, +{"legacy":40,"weight":400}, +{"legacy":41,"weight":400}, +{"legacy":42,"weight":400}, +{"legacy":43,"weight":400}, +{"legacy":44,"weight":400}, +{"legacy":45,"weight":400}, +{"legacy":46,"weight":400}, +{"legacy":47,"weight":400}, +{"legacy":48,"weight":400}, +{"legacy":49,"weight":400}, +{"legacy":50,"weight":400}, +{"legacy":51,"weight":400}, +{"legacy":52,"weight":400}, +{"legacy":53,"weight":400}, +{"legacy":54,"weight":500}, +{"legacy":55,"weight":500}, +{"legacy":56,"weight":500}, +{"legacy":57,"weight":500}, +{"legacy":58,"weight":500}, +{"legacy":59,"weight":500}, +{"legacy":60,"weight":500}, +{"legacy":61,"weight":600}, +{"legacy":62,"weight":600}, +{"legacy":63,"weight":600}, +{"legacy":64,"weight":600}, +{"legacy":65,"weight":600}, +{"legacy":66,"weight":600}, +{"legacy":67,"weight":600}, +{"legacy":68,"weight":600}, +{"legacy":69,"weight":600}, +{"legacy":70,"weight":700}, +{"legacy":71,"weight":700}, +{"legacy":72,"weight":700}, +{"legacy":73,"weight":700}, +{"legacy":74,"weight":700}, +{"legacy":75,"weight":700}, +{"legacy":76,"weight":700}, +{"legacy":77,"weight":700}, +{"legacy":78,"weight":700}, +{"legacy":79,"weight":800}, +{"legacy":80,"weight":800}, +{"legacy":81,"weight":800}, +{"legacy":82,"weight":800}, +{"legacy":83,"weight":800}, +{"legacy":84,"weight":800}, +{"legacy":85,"weight":900}, +{"legacy":86,"weight":900}, +{"legacy":87,"weight":900}, +{"legacy":88,"weight":900}, +{"legacy":89,"weight":900}, +{"legacy":90,"weight":900}, +{"legacy":91,"weight":900}, +{"legacy":92,"weight":900}, +{"legacy":93,"weight":900}, +{"legacy":94,"weight":900}, +{"legacy":95,"weight":900}, +{"legacy":96,"weight":900}, +{"legacy":97,"weight":900}, +{"legacy":98,"weight":900}, +{"legacy":99,"weight":900} +], +"tooltip": {"class":"QTipLabel","contents_margins":[0,0,0,0],"font":"Noto Sans 10","margin":4,"size_hint":[78,26],"text":"Tooltip text","text_size":[69,18]} +} diff --git a/tools/kde_golden/vectors/versions.txt b/tools/kde_golden/vectors/versions.txt new file mode 100644 index 000000000..a7abbc42e --- /dev/null +++ b/tools/kde_golden/vectors/versions.txt @@ -0,0 +1,11 @@ +breeze 4:6.8.0-0zneon+24.04+noble+release+build80 +fonts-hack 3.003-3 +fonts-noto-core 20201225-2 +kde-style-breeze 4:6.8.0-0zneon+24.04+noble+release+build80 +kde-style-oxygen 4:6.8.0-0zneon+24.04+noble+release+build59 +kf6-kcolorscheme 6.31.0-0zneon+24.04+noble+release+build47 +kf6-kconfig 6.31.0-0zneon+24.04+noble+release+build36 +kf6-kguiaddons 6.31.0-0zneon+24.04+noble+release+build59 +libkf6colorscheme-dev 6.31.0-0zneon+24.04+noble+release+build47 +plasma-workspace 4:6.8.0-0zneon+24.04+noble+release+build99 +qt6-base 6.11.1-0zneon+24.04+noble+release+build162 diff --git a/tools/reader/kde_visual_check.py b/tools/reader/kde_visual_check.py new file mode 100755 index 000000000..554f09e71 --- /dev/null +++ b/tools/reader/kde_visual_check.py @@ -0,0 +1,1125 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech + +"""The KDE Breeze preset as it renders in the real widget catalog, measured. + + tools/reader/kde_visual_check.sh # all three modes + tools/reader/kde_visual_check.sh --modes system # one of them + tools/reader/kde_visual_check.sh --out DIR # keep logs and PNGs + +Run it through `kde_visual_check.sh`, which builds the debug `widget-catalog` +and `teksilo-automation-mcp` into this checkout's `target/` and then starts +this driver inside `private_session.sh`: a private D-Bus bus, runtime +directory, `kwin_wayland --virtual` and configuration home, so neither the +catalog nor `plasma-apply-colorscheme` touches the desktop or the user's +`~/.config`. Started any other way, the driver refuses to run. + +The private session keeps the desktop's `XDG_CONFIG_DIRS`, which on a Plasma +desktop lists `~/.config/kdedefaults` (written by the Global Theme settings) +before `/etc/xdg`, and the catalog reads every `kdeglobals` of that list +(specification b.7). The driver therefore gives the catalog and +`plasma-apply-colorscheme` a configuration list of their own, one private +directory, and sets `KDE_SKIP_KDERC`, so no file of this machine's cascade +(a widget style, a font, an animation factor, a look-and-feel package) +decides what is measured. That directory holds one `kdeglobals` naming the +scheme `BreezeClassic` (`CASCADE_DEFAULT_SCHEME`), a scheme no mode asks +for: `plasma-apply-colorscheme` writes no `ColorScheme` key when asked for the +scheme the cascade already defaults to, so with the default left to the +machine one of the two Breeze schemes would be seeded as nothing. + +For each mode, `kde-light`, `kde-dark` and `system`: + +1. The private configuration home is given a Breeze colour scheme by the + installed `plasma-apply-colorscheme` (BreezeLight for `kde-light` and + `system`, BreezeDark for `kde-dark`), and the driver checks that the + private `kdeglobals` names it. The catalog runs with + `XDG_CURRENT_DESKTOP=KDE` and `KDE_FULL_SESSION=true`, so the native path + takes the session for a Breeze desktop. +2. `widget-catalog --theme ` starts; the driver reads its automation + bridge's endpoint descriptor under the private runtime directory and + attaches `teksilo-automation-mcp --connect`. +3. It opens the catalog tabs the controls live on and measures each control + with `layout_tree`, found by its Rust type name: a push button, the + tooltip a hover over an icon button opens, a line edit's frame, a combo + box, a spin box, a check box row, a radio button row, a switch, a + slider, a progress bar, a list row and a tree row, a table header and a table row, + a row of a menu opened from a menu bar, a tab of a horizontal tab bar and + a scroll bar's extent. Every + height is printed beside the Breeze value Plasma 6.8 reports + (`tools/kde_golden/vectors/sizes.json`, the entry named in the table), the + specification's computed size (`engineering/docs/kde-breeze-spec.md` + k.4, at Plasma's default 10 pt font) and the height the preset means to + give it, with the documented deviation where there is one. A control + passes when it is within 1 px of the preset's height. The catalog is built + at the Compact density, where the preset's density ladder leaves every + Breeze height as it is (`[unpublished]`: KDE publishes no touch ladder). + It also reads which entry the title-bar theme switcher shows (the bridge + reports no theme id; the switcher names `kde.light` "KDE Light", + `kde.dark` "KDE Dark" and follow-the-desktop "System"), and checks that + the push button is painted by the preset's own chrome + (`teksilo_theme_kde::styles::chrome::BreezeButtonChrome`), which in + `system` mode is what tells Breeze's chrome from Int UI's (Int UI's push + button is 24 px high, Breeze's 34). +4. In `system` mode only, it then changes the desktop while the catalog + runs: `plasma-apply-colorscheme BreezeDark`, then the accent `#e93a9a`. + Two things are checked for each change. + - The window presented a frame because of it. The catalog runs with + `TEKSILO_IDLE_TRACE=1`, whose report counts the frames rendered to the + window's surface (not the offscreen renders a screenshot makes). The + driver makes no bridge call for `QUIET_WINDOW` seconds after the + change and reads how many frames were presented in that span, and + compares that with the same span measured just before with nothing + changed (where only the frame its own previous call asked for is + drawn): the change must present more. + - The tree paints in the new colours. Screenshots, which render the + current tree offscreen, of a push button and of the selected tab of + the catalog's tab bar are polled with a deadline until they change; + the button must turn darker and the selected tab's changed pixels move + toward the new accent. + Then it checks that nothing was rebuilt: the `layout_tree` node ids and + the bounds of every active node are the ones measured before the change. + The screenshots do not read the presented frame, so what is shown is + that a frame was presented after the change and that the tree it renders + paints in the new colours. + +Some heights are not the preset's to give, and are printed as INFO rather +than judged: the catalog's `TableView` sets its row height itself (28 px, +the row printed as "table row"), so does its `TreeView` (28 px), and a +`ListView` lays its rows out at its own uniform item height (32 px) whatever +the style. The preset's table row height (24, D7) therefore has no table in +the catalog to show it, and no list or tree row in the catalog has an icon +or a check box; `NOT_IN_CATALOG` names both. + +Exit status: 0 when every judged control passes, 1 on a failed check (the +reason is printed), 2 when the driver cannot run (not a private session, +binaries missing). +""" + +from __future__ import annotations + +import argparse +import base64 +import json +import os +import select +import shutil +import subprocess +import sys +import tempfile +import time +import zlib +from dataclasses import dataclass +from pathlib import Path + +READER_DIR = Path(__file__).resolve().parent +REPO = READER_DIR.parents[1] +TARGET = REPO / "target" / "debug" +CATALOG = TARGET / "widget-catalog" +MCP = TARGET / "teksilo-automation-mcp" +SIZES = REPO / "tools" / "kde_golden" / "vectors" / "sizes.json" + +MODES = ("kde-light", "kde-dark", "system") +#: The scheme each mode's desktop is seeded with. +SEED_SCHEME = {"kde-light": "BreezeLight", "kde-dark": "BreezeDark", "system": "BreezeLight"} +#: What the title-bar theme switcher shows for each mode's theme. +SWITCHER_ENTRY = {"kde-light": "KDE Light", "kde-dark": "KDE Dark", "system": "System"} +#: The accent the system mode applies while the catalog runs: Plasma's pink +#: swatch (specification, section h). +LIVE_ACCENT = "#e93a9a" +#: The scheme the private configuration list defaults to: a Breeze scheme no +#: mode seeds, so `plasma-apply-colorscheme` always writes the one asked for. +CASCADE_DEFAULT_SCHEME = "BreezeClassic" + +#: How long a launch, a tab switch, a menu or a repaint is waited for. +LAUNCH_DEADLINE = 90.0 +STEP_DEADLINE = 20.0 +REPAINT_DEADLINE = 15.0 +POLL = 0.1 +#: The span after a desktop change in which no bridge call is made, so every +#: frame presented in it is the catalog's own (the desktop watch reports a +#: change 150 ms after the signal). +QUIET_WINDOW = 2.0 +#: How long the catalog's idle trace waits between two reports. +TRACE_PERIOD = 1.1 +#: The tolerance of a judged height, in logical pixels. +TOLERANCE = 1.0 + +BUTTON = "teksilo_widgets::button::Button" +BREEZE_BUTTON_CHROME = "teksilo_theme_kde::styles::chrome::BreezeButtonChrome" +TEXT_INPUT = "teksilo_widgets::text_input::TextInput" +FIELD_CHROME = "teksilo_theme_kde::styles::text_input::BreezeFieldChrome" +COMBO = "teksilo_widgets::combo_box::ComboBox<" +SPIN = "teksilo_widgets::spin_box::SpinBox<" +CHECKBOX = "teksilo_widgets::checkbox::Checkbox" +RADIO = "teksilo_widgets::radio_button::RadioButton" +SWITCH_BODY = "teksilo_theme_kde::styles::toggle::BreezeSwitchBody" +SLIDER_BODY = "teksilo_theme_kde::styles::slider::BreezeSliderBody" +PROGRESS_BODY = "teksilo_theme_kde::styles::progress_bar::BreezeProgressBody" +ICON_BUTTON = "teksilo_widgets::icon_button::IconButton" +TOOLTIP = "teksilo_widgets::tooltip::TooltipWidget" +POPUP_FRAME = "teksilo_theme_kde::styles::popup::BreezePopupFrame" +TEXT = "teksilo_widgets::primitives::text_widget::TextWidget" +LIST_ITEM = "teksilo_widgets::standard_item::StandardListItem" +TREE_ITEM = "teksilo_widgets::standard_item::StandardTreeItem" +LIST_ROW_WRAPPER = "teksilo_widgets::list_item_a11y::ListItemWrapper" +TREE_ROW_WRAPPER = "teksilo_widgets::list_item_a11y::TreeItemWrapper" +TABLE_VIEW = "teksilo_widgets::table_view::TableView<" +TABLE_HEADER = "teksilo_widgets::table_view::table_header::TableHeader" +TABLE_ROW = "teksilo_widgets::table_view::body::BodyRow" +MENU_BAR_TRIGGER = "teksilo_widgets::menu_bar::MenuBarTrigger" +MENU_ITEM = "teksilo_widgets::menu_item::MenuItem" +TAB_HEADER = "teksilo_widgets::tab_widget::header::TabHeader" +SCROLL_AREA = "teksilo_widgets::scroll_area::ScrollArea" +SCROLL_BAR = "teksilo_widgets::scroll_bar::ScrollBar" +MAX_SIZE = "teksilo_widgets::primitives::max_size::MaxSize" +TAB_CONTENT = "widget_catalog::TabContent" + +#: Controls the catalog has no instance of, with the reason. A control listed +#: here is reported, never silently skipped; any other control that cannot be +#: found fails the check. +NOT_IN_CATALOG = { + "list row with an icon or a check box": ( + "no StandardListItem or StandardTreeItem in the widget catalog has a leading " + "icon or a check box (tabs/data.rs and tabs/touch.rs build text-only rows), so " + "Breeze's 26 px row (list_view.icon, list_view.check) has nothing to compare with" + ), + "table row at the preset's height": ( + "both tables in the catalog set their own row height (tabs/data.rs: TableView " + ".row_height(28.0), TreeTableView .row_height(26.0)), so the style's 24 px row " + "(D7, against Breeze's 23 with its grid line) is never laid out there; the " + "TableView row is measured as INFO at the height the catalog asks for" + ), +} + + +class CheckFailed(Exception): + """A failed check, with the reason.""" + + +class CannotRun(Exception): + """The driver cannot run here.""" + + +# --------------------------------------------------------------------------- +# The session +# --------------------------------------------------------------------------- + + +def user_config_home() -> Path: + import pwd + + return Path(pwd.getpwuid(os.getuid()).pw_dir) / ".config" + + +def check_private_session() -> Path: + """The private configuration home, after refusing anything else.""" + if os.environ.get("TEKSILO_READER_INNER") != "1": + raise CannotRun("not inside tools/reader/private_session.sh; run kde_visual_check.sh") + runtime = os.environ.get("XDG_RUNTIME_DIR", "") + if not Path(runtime).name.startswith("teksilo-reader-run."): + raise CannotRun(f"the runtime directory {runtime!r} is not the private session's") + if os.environ.get("DISPLAY"): + raise CannotRun("DISPLAY is set: this is not the private session") + raw = os.environ.get("XDG_CONFIG_HOME", "") + if not raw: + raise CannotRun("XDG_CONFIG_HOME is unset: plasma-apply-colorscheme would write ~/.config") + config = Path(raw).resolve() + if config == user_config_home().resolve(): + raise CannotRun(f"XDG_CONFIG_HOME is the user's own {config}") + if not config.is_relative_to(Path(runtime).resolve()): + raise CannotRun(f"XDG_CONFIG_HOME {config} is not inside the private runtime directory") + return config + + +def stat_or_none(path: Path) -> tuple | None: + try: + st = path.stat() + except FileNotFoundError: + return None + return (st.st_mtime_ns, st.st_size, st.st_ino) + + +@dataclass +class Desktop: + """The private desktop's KDE configuration, written by Plasma's own tool.""" + + config: Path + #: The one directory of the private `XDG_CONFIG_DIRS`. + config_dirs: Path + user_kdeglobals: Path + user_stat: tuple | None + + @classmethod + def open(cls, config: Path) -> "Desktop": + if shutil.which("plasma-apply-colorscheme") is None: + raise CannotRun("plasma-apply-colorscheme is not installed") + # Inside the private runtime directory, which check_private_session + # has vetted. + config_dirs = Path(os.environ["XDG_RUNTIME_DIR"]) / "kde-visual-check-config-dirs" + config_dirs.mkdir(mode=0o700, exist_ok=True) + (config_dirs / "kdeglobals").write_text( + f"[General]\nColorScheme={CASCADE_DEFAULT_SCHEME}\n") + user = user_config_home() / "kdeglobals" + return cls(config, config_dirs, user, stat_or_none(user)) + + @property + def kdeglobals(self) -> Path: + return self.config / "kdeglobals" + + def session_env(self, **extra: str) -> dict[str, str]: + """The environment the catalog and Plasma's tool both run with. + + `XDG_CONFIG_DIRS` is the private list alone: the session's own value + names the desktop's `~/.config/kdedefaults` and `/etc/xdg`, whose + `kdeglobals` would otherwise set the widget style, the fonts and the + default scheme of what is measured. `KDE_SKIP_KDERC` leaves + `/etc/kde5rc` out of the cascade for the same reason. + """ + env = dict(os.environ, XDG_CONFIG_HOME=str(self.config), + XDG_CONFIG_DIRS=str(self.config_dirs), KDE_SKIP_KDERC="1") + env.update(extra) + return env + + def env(self) -> dict[str, str]: + # The tool needs no window; it would otherwise try the private + # compositor and an X display that does not exist. + return self.session_env(QT_QPA_PLATFORM="offscreen") + + def run_tool(self, *args: str) -> None: + proc = subprocess.run(["plasma-apply-colorscheme", *args], env=self.env(), + capture_output=True, text=True, timeout=60) + if proc.returncode != 0: + raise CheckFailed(f"plasma-apply-colorscheme {' '.join(args)} failed " + f"({proc.returncode}): {proc.stdout.strip()} {proc.stderr.strip()}") + self.guard_user_config() + + def guard_user_config(self) -> None: + if stat_or_none(self.user_kdeglobals) != self.user_stat: + raise CheckFailed(f"{self.user_kdeglobals} changed during the run") + + def general(self) -> dict[str, str]: + group: dict[str, str] = {} + current = None + try: + text = self.kdeglobals.read_text() + except FileNotFoundError: + return group + for line in text.splitlines(): + line = line.strip() + if line.startswith("["): + current = line + elif current == "[General]" and "=" in line: + key, value = line.split("=", 1) + group[key.strip()] = value.strip() + return group + + def seed(self, scheme: str) -> None: + """Start the desktop afresh on `scheme`, at its own accent.""" + for name in ("kdeglobals", "kdedefaults"): + path = self.config / name + if path.is_dir(): + shutil.rmtree(path) + elif path.exists(): + path.unlink() + # With no kdeglobals of its own the tool takes the cascade's default + # for the scheme in use, and writes no ColorScheme key when asked for + # that one. The private cascade defaults to CASCADE_DEFAULT_SCHEME, + # which no mode seeds, so the requested scheme is always written. + if scheme == CASCADE_DEFAULT_SCHEME: + raise CannotRun(f"{scheme} is the private cascade's default and cannot be seeded") + self.run_tool(scheme) + self.expect_scheme(scheme) + + def expect_scheme(self, scheme: str) -> None: + found = self.general().get("ColorScheme") + if found != scheme: + raise CheckFailed(f"the private {self.kdeglobals} names the scheme {found!r}, " + f"not {scheme!r}") + + +# --------------------------------------------------------------------------- +# The catalog and its bridge +# --------------------------------------------------------------------------- + + +class Bridge: + """The catalog and the MCP server attached to its automation bridge.""" + + def __init__(self, args: list[str], env: dict[str, str], log_dir: Path, name: str): + self.log_path = log_dir / f"{name}-catalog.log" + self.mcp_log_path = log_dir / f"{name}-mcp.log" + self.mcp: subprocess.Popen | None = None + self._id = 0 + with open(self.log_path, "w") as log: + self.app = subprocess.Popen([str(CATALOG), *args], stdout=log, + stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, + env=env) + try: + self._attach(env) + except BaseException: + self.close() + raise + + def _attach(self, env: dict[str, str]) -> None: + descriptor = Path(env["XDG_RUNTIME_DIR"]) / "teksilo-automation" / f"{self.app.pid}.json" + deadline = time.monotonic() + LAUNCH_DEADLINE + desc = None + while desc is None: + if self.app.poll() is not None: + raise CheckFailed(f"the catalog exited ({self.app.returncode}) before " + f"publishing its bridge; its log is {self.log_path}") + if time.monotonic() > deadline: + raise CheckFailed(f"no readable bridge descriptor at {descriptor} within " + f"{LAUNCH_DEADLINE:.0f} s (is the catalog a debug build?)") + try: + desc = json.loads(descriptor.read_text()) + except (OSError, ValueError): + time.sleep(POLL) + with open(self.mcp_log_path, "w") as log: + self.mcp = subprocess.Popen( + [str(MCP), "--connect", desc["address"], "--token", desc["token"]], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=log, + text=True, bufsize=1, env=env) + self._send("initialize", { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": {"name": "kde_visual_check", "version": "1"}, + }) + self._recv(30) + self._send("notifications/initialized", notify=True) + + def close(self) -> None: + for proc in (self.mcp, self.app): + if proc is not None and proc.poll() is None: + proc.terminate() + for proc in (self.mcp, self.app): + if proc is None: + continue + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait() + + def _send(self, method: str, params=None, notify: bool = False) -> None: + msg: dict = {"jsonrpc": "2.0", "method": method} + if params is not None: + msg["params"] = params + if not notify: + self._id += 1 + msg["id"] = self._id + assert self.mcp is not None and self.mcp.stdin is not None + self.mcp.stdin.write(json.dumps(msg) + "\n") + self.mcp.stdin.flush() + + def _recv(self, timeout: float) -> dict: + assert self.mcp is not None and self.mcp.stdout is not None + end = time.monotonic() + timeout + while True: + left = end - time.monotonic() + if left <= 0 or self.mcp.poll() is not None: + raise CheckFailed(f"no reply from the MCP server; its log is {self.mcp_log_path}") + ready, _, _ = select.select([self.mcp.stdout], [], [], left) + if not ready: + continue + line = self.mcp.stdout.readline() + if not line: + raise CheckFailed(f"the MCP server closed its output; its log is " + f"{self.mcp_log_path}") + if not line.strip(): + continue + msg = json.loads(line) + if msg.get("id") == self._id: + return msg + + def call(self, name: str, args: dict | None = None, timeout: float = 60) -> tuple[dict, dict]: + """One tool call: the raw result and its JSON payload.""" + self._send("tools/call", {"name": name, "arguments": args or {}}) + msg = self._recv(timeout) + if "error" in msg: + raise CheckFailed(f"{name}: {msg['error']}") + result = msg.get("result", {}) + payload = result.get("structuredContent") + if payload is None: + text = "".join(c.get("text", "") for c in result.get("content", []) + if c.get("type") == "text") + try: + payload = json.loads(text) if text.strip() else {} + except ValueError: + payload = {"text": text} + if result.get("isError"): + raise CheckFailed(f"{name} {args or {}}: {payload}") + return result, payload + + # -- views of the tree ---------------------------------------------------- + + def layout(self) -> "Layout": + _, payload = self.call("layout_tree") + return Layout(payload.get("nodes", [])) + + def at_nodes(self) -> list[dict]: + _, payload = self.call("snapshot_tree") + return payload.get("nodes", []) + + def at_node(self, role: str, label: str) -> dict: + found = [n for n in self.at_nodes() if n.get("role") == role and n.get("label") == label] + if not found: + raise CheckFailed(f"no {role} node labelled {label!r} in the accessibility tree") + return found[0] + + def screenshot(self, node: int | None = None) -> "Image": + res, _ = self.call("screenshot", {"node": node} if node is not None else {}) + for c in res.get("content", []): + if c.get("type") == "image" and c.get("data"): + return Image.from_png(base64.b64decode(c["data"])) + raise CheckFailed(f"the screenshot of node {node} carried no image") + + +class Layout: + """One `layout_tree` reply, indexed.""" + + def __init__(self, nodes: list[dict]): + self.nodes = nodes + self.by_id = {n["id"]: n for n in nodes} + + def descendants(self, root: int): + stack = [root] + while stack: + node = self.by_id[stack.pop()] + yield node + stack.extend(reversed(node.get("children", []))) + + def ancestors(self, node: dict): + parent = node.get("parent") + while parent is not None and parent in self.by_id: + node = self.by_id[parent] + yield node + parent = node.get("parent") + + def active_tab_content(self) -> dict: + found = [n for n in self.nodes if n["type"] == TAB_CONTENT and n["active"]] + if len(found) != 1: + raise CheckFailed(f"{len(found)} active catalog tab contents, expected one") + return found[0] + + def find(self, root: int | None, pred, what: str) -> list[dict]: + """Every active, laid-out node under `root` (or anywhere) matching `pred`.""" + pool = self.descendants(root) if root is not None else iter(self.nodes) + found = [n for n in pool if n["active"] and n["bounds"]["height"] > 0 + and n["bounds"]["width"] > 0 and pred(n)] + if not found: + raise CheckFailed(f"no {what} in the layout tree") + return found + + +def is_type(prefix: str): + return lambda n: n["type"] == prefix or (prefix.endswith("<") and n["type"].startswith(prefix)) + + +# --------------------------------------------------------------------------- +# Screenshots +# --------------------------------------------------------------------------- + + +class Image: + """An 8-bit RGB or RGBA PNG, decoded.""" + + def __init__(self, width: int, height: int, pixels: list[tuple[int, int, int]]): + self.width, self.height, self.pixels = width, height, pixels + + @classmethod + def from_png(cls, data: bytes) -> "Image": + if data[:8] != b"\x89PNG\r\n\x1a\n": + raise CheckFailed("the screenshot is not a PNG") + pos, idat, header = 8, b"", None + while pos < len(data): + length = int.from_bytes(data[pos:pos + 4], "big") + kind = data[pos + 4:pos + 8] + body = data[pos + 8:pos + 8 + length] + pos += 12 + length + if kind == b"IHDR": + header = body + elif kind == b"IDAT": + idat += body + elif kind == b"IEND": + break + if header is None: + raise CheckFailed("the screenshot PNG has no header") + width = int.from_bytes(header[0:4], "big") + height = int.from_bytes(header[4:8], "big") + depth, color, interlace = header[8], header[9], header[12] + channels = {2: 3, 6: 4}.get(color) + if depth != 8 or channels is None or interlace != 0: + raise CheckFailed(f"unsupported screenshot PNG (depth {depth}, colour {color})") + raw = zlib.decompress(idat) + stride = width * channels + prev = bytearray(stride) + pixels: list[tuple[int, int, int]] = [] + at = 0 + for _ in range(height): + kind = raw[at] + line = bytearray(raw[at + 1:at + 1 + stride]) + at += 1 + stride + for i in range(stride): + left = line[i - channels] if i >= channels else 0 + up = prev[i] + ul = prev[i - channels] if i >= channels else 0 + if kind == 1: + line[i] = (line[i] + left) & 255 + elif kind == 2: + line[i] = (line[i] + up) & 255 + elif kind == 3: + line[i] = (line[i] + ((left + up) >> 1)) & 255 + elif kind == 4: + p = left + up - ul + pa, pb, pc = abs(p - left), abs(p - up), abs(p - ul) + pred = left if pa <= pb and pa <= pc else (up if pb <= pc else ul) + line[i] = (line[i] + pred) & 255 + pixels.extend(tuple(line[i:i + 3]) for i in range(0, stride, channels)) + prev = line + return cls(width, height, pixels) + + def changed(self, other: "Image", threshold: int = 24) -> list[int]: + """The indices of the pixels that differ by more than `threshold` levels.""" + if (self.width, self.height) != (other.width, other.height): + return list(range(len(self.pixels))) + return [i for i, (a, b) in enumerate(zip(self.pixels, other.pixels)) + if max(abs(a[k] - b[k]) for k in range(3)) > threshold] + + def mean(self, indices: list[int] | None = None) -> tuple[float, float, float]: + pool = [self.pixels[i] for i in indices] if indices else self.pixels + n = max(len(pool), 1) + return tuple(sum(p[k] for p in pool) / n for k in range(3)) # type: ignore[return-value] + + +def luma(rgb: tuple[float, float, float]) -> float: + return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] + + +# --------------------------------------------------------------------------- +# References +# --------------------------------------------------------------------------- + + +class Breeze: + """The heights Plasma 6.8 reports, from the Docker oracle's sizes.json.""" + + def __init__(self, path: Path): + data = json.loads(path.read_text()) + self.widgets = {w["id"]: w for w in data["widgets"] if w.get("font") == "Noto Sans 10"} + self.tooltip = data["tooltip"] + if self.tooltip.get("font") != "Noto Sans 10": + raise CannotRun(f"{SIZES} measures its tooltip at {self.tooltip.get('font')!r}") + + def widget(self, wid: str) -> dict: + if wid not in self.widgets: + raise CannotRun(f"{SIZES} has no {wid!r} entry at Noto Sans 10") + return self.widgets[wid] + + def hint_height(self, wid: str) -> tuple[float, str]: + return self.widget(wid)["size_hint"][1], f"{wid} size_hint h" + + def hint_width(self, wid: str) -> tuple[float, str]: + return self.widget(wid)["size_hint"][0], f"{wid} size_hint w" + + def row(self, wid: str) -> tuple[float, str]: + return self.widget(wid)["size_hint_for_row"], f"{wid} size_hint_for_row" + + def menu_item(self) -> tuple[float, str]: + rows = [r for r in self.widget("menu.with_icon")["rows"] if r["kind"] == "item"] + return rows[0]["geometry"][3], "menu.with_icon rows[item] h" + + def tab(self) -> tuple[float, str]: + return self.widget("tab_bar")["tab_rects"][0][3], "tab_bar tab_rects[0] h" + + def tooltip_height(self) -> float: + return self.tooltip["size_hint"][1] + + +@dataclass +class Spec: + """One control's expectations.""" + + control: str + #: Plasma's value and the sizes.json entry it is read from; the value is + #: None for a control the file has no entry for. + breeze: tuple[float | None, str] + #: The specification's k.4 size at H = 18, as text (it can carry a note). + spec_k4: str + #: The height the preset gives it, or None for an INFO row. + intended: float | None + note: str + + +@dataclass +class Row: + spec: Spec + node: dict + measured: float + + @property + def status(self) -> str: + if self.spec.intended is None: + return "INFO" + return "PASS" if abs(self.measured - self.spec.intended) <= TOLERANCE else "FAIL" + + +def fmt(v: float) -> str: + return f"{v:g}" if v == int(v) else f"{v:.2f}" + + +# --------------------------------------------------------------------------- +# The check +# --------------------------------------------------------------------------- + + +class ModeCheck: + def __init__(self, mode: str, desktop: Desktop, breeze: Breeze, out: Path): + self.mode, self.desktop, self.breeze, self.out = mode, desktop, breeze, out + self.rows: list[Row] = [] + self.notes: list[str] = [] + self.failures: list[str] = [] + self.bridge: Bridge | None = None + + # -- driving --------------------------------------------------------------- + + def wait_for(self, what: str, probe, deadline: float = STEP_DEADLINE): + """`probe()`'s first value that is not None, polled until `deadline`.""" + end = time.monotonic() + deadline + last: Exception | None = None + while time.monotonic() < end: + try: + value = probe() + except CheckFailed as e: + last = e + value = None + if value is not None: + return value + time.sleep(POLL) + raise CheckFailed(f"{what} within {deadline:.0f} s" + (f" ({last})" if last else "")) + + def open_tab(self, label: str) -> Layout: + """Select the catalog tab `label` and return the layout once it shows.""" + b = self.bridge + assert b is not None + tab = b.at_node("Tab", label) + b.call("invoke_action", {"node": tab["id"], "action": "click"}) + + def shown(): + layout = b.layout() + content = layout.active_tab_content() + # The tab header the click selected sits in the bar; the content + # it shows is laid out when its ScrollArea has a size. + areas = [n for n in layout.descendants(content["id"]) + if n["type"] == SCROLL_AREA and n["active"] + and n.get("parent") == content["id"] and n["bounds"]["height"] > 0] + selected = b.at_node("Tab", label).get("selected") + return layout if areas and selected else None + + return self.wait_for(f"the {label} tab did not show", shown) + + def measure(self, spec: Spec, node: dict) -> None: + self.rows.append(Row(spec, node, node["bounds"]["height"])) + + # -- one mode -------------------------------------------------------------- + + def run(self) -> None: + self.desktop.seed(SEED_SCHEME[self.mode]) + env = self.desktop.session_env(XDG_CURRENT_DESKTOP="KDE", KDE_FULL_SESSION="true") + if self.mode == "system": + # Counts the frames presented to the window (see live_change). + env["TEKSILO_IDLE_TRACE"] = "1" + self.bridge = Bridge(["--theme", self.mode, "--tab", "buttons"], env, self.out, self.mode) + try: + self.measure_all() + if self.mode == "system": + self.live_change() + finally: + self.bridge.close() + self.bridge = None + self.desktop.guard_user_config() + + def measure_all(self) -> None: + b = self.bridge + assert b is not None + br = self.breeze + + # The theme switcher's entry, and Breeze's chrome on the push button. + combo = self.wait_for("the title-bar theme switcher did not appear", + lambda: next((n for n in b.at_nodes() + if n.get("role") == "ComboBox" + and n.get("label") == "Theme"), None), + LAUNCH_DEADLINE) + entry = combo.get("value") + self.notes.append(f"theme switcher shows {entry!r} (expected {SWITCHER_ENTRY[self.mode]!r})") + if entry != SWITCHER_ENTRY[self.mode]: + self.failures.append(f"the theme switcher shows {entry!r}, not " + f"{SWITCHER_ENTRY[self.mode]!r}") + + layout = self.open_tab("Buttons") + tab = layout.active_tab_content()["id"] + button = layout.find(tab, is_type(BUTTON), "push button on the Buttons tab")[0] + chrome = [n for n in layout.descendants(button["id"]) if n["type"] == BREEZE_BUTTON_CHROME] + self.notes.append(f"push button painted by {BREEZE_BUTTON_CHROME}: " + f"{'yes' if chrome else 'NO'}") + if not chrome: + self.failures.append("the push button is not painted by the Breeze button chrome") + self.measure(Spec("push button", br.hint_height("push_button"), "34", 34, + "Breeze's H + 16"), button) + self.measure_tooltip(layout, tab) + + layout = self.open_tab("Text") + tab = layout.active_tab_content()["id"] + field = layout.find(tab, is_type(TEXT_INPUT), "TextInput on the Text tab")[0] + frames = layout.find(field["id"], is_type(FIELD_CHROME), "Breeze line-edit frame " + "in the TextInput") + self.measure(Spec("line edit (its frame)", br.hint_height("line_edit"), "32", 32, + f"TextInput node {fmt(field['bounds']['height'])} with its " + "validation line"), frames[0]) + spin = layout.find(tab, is_type(SPIN), "SpinBox on the Text tab")[0] + self.measure(Spec("spin box", br.hint_height("spin_box.0_99"), "32", 32, ""), spin) + + layout = self.open_tab("Inputs") + tab = layout.active_tab_content()["id"] + combo_node = layout.find(tab, is_type(COMBO), "ComboBox on the Inputs tab")[0] + self.measure(Spec("combo box", br.hint_height("combo_box"), "32", 32, ""), combo_node) + check = layout.find(tab, is_type(CHECKBOX), "Checkbox on the Inputs tab")[0] + self.measure(Spec("check box row", br.hint_height("check_box"), "22", 24, + "D8: the 24 px target, the 20 px indicator unchanged"), check) + radio = layout.find(tab, is_type(RADIO), "RadioButton on the Inputs tab")[0] + self.measure(Spec("radio button row", br.hint_height("radio_button"), "22", 24, + "D8: the 24 px target, the 20 px indicator unchanged"), radio) + # Breeze's switch is a QtQuick control (k.5.9): sizes.json, measured + # from QWidgets, has no entry for it. + switch = layout.find(tab, is_type(SWITCH_BODY), "Breeze switch on the Inputs tab")[0] + self.measure(Spec("switch (Toggle)", (None, "no entry: a QtQuick control"), "18 (36 x 18)", + 24, f"documented: the node is raised to the 24 px target, the painted " + f"36 x 18 unchanged; node {fmt(switch['bounds']['width'])} wide"), + switch) + # The catalog's sliders have no tick marks (Breeze's 23 and 26 are the + # ticked ones); the first horizontal one is the volume slider. + slider = layout.find(tab, lambda n: is_type(SLIDER_BODY)(n) + and n["bounds"]["width"] > n["bounds"]["height"], + "horizontal Breeze slider on the Inputs tab")[0] + self.measure(Spec("slider (horizontal)", br.hint_height("slider.horizontal"), "20", 24, + "documented: the node is raised to the 24 px target, the " + "painted 20 px unchanged"), slider) + + layout = self.open_tab("Indicators") + tab = layout.active_tab_content()["id"] + # The first horizontal bar is the determinate one; the vertical one is + # given a thickness of 8 by the catalog. + progress = layout.find(tab, lambda n: is_type(PROGRESS_BODY)(n) + and n["bounds"]["width"] > n["bounds"]["height"], + "horizontal Breeze progress bar on the Indicators tab")[0] + self.measure(Spec("progress bar", br.hint_height("progress_bar"), "26", 26, + "Qt's H + 8, the 6 px groove centred"), progress) + + layout = self.open_tab("Data") + tab = layout.active_tab_content()["id"] + + def outside(wrapper: str): + return lambda n: not any(a["type"] == wrapper for a in layout.ancestors(n)) + + item = layout.find(tab, lambda n: is_type(LIST_ITEM)(n) and outside(LIST_ROW_WRAPPER)(n), + "StandardListItem outside a ListView on the Data tab")[0] + self.measure(Spec("list row, text", br.row("list_view"), "24", 24, + "a view's first row is not one taller"), item) + tree = layout.find(tab, lambda n: is_type(TREE_ITEM)(n) and outside(TREE_ROW_WRAPPER)(n), + "StandardTreeItem outside a TreeView on the Data tab")[0] + self.measure(Spec("tree row, text", br.row("tree_view"), "24", 24, ""), tree) + lv_row = layout.find(tab, is_type(LIST_ROW_WRAPPER), "ListView row on the Data tab")[0] + self.measure(Spec("ListView row", br.row("list_view"), "24", None, + "the ListView's own uniform item height, not the style's"), lv_row) + tv_row = layout.find(tab, is_type(TREE_ROW_WRAPPER), "TreeView row on the Data tab")[0] + self.measure(Spec("TreeView row", br.row("tree_view"), "24", None, + "set by the catalog: .item_height(28.0)"), tv_row) + table = layout.find(tab, is_type(TABLE_VIEW), "TableView on the Data tab")[0] + header = layout.find(table["id"], is_type(TABLE_HEADER), "TableView header")[0] + self.measure(Spec("table header", br.hint_height("header.horizontal"), "30", 30, ""), + header) + table_row = layout.find(table["id"], is_type(TABLE_ROW), "TableView row")[0] + self.measure(Spec("table row", br.row("table_view"), "22 (23 with the grid)", None, + "set by the catalog: .row_height(28.0); the preset's is 24 (D7)"), + table_row) + + layout = self.open_tab("Containers") + tab = layout.active_tab_content()["id"] + headers = layout.find(tab, lambda n: is_type(TAB_HEADER)(n) + and n["bounds"]["width"] > n["bounds"]["height"], + "horizontal tab on the Containers tab") + self.measure(Spec("tab", br.tab(), "30", 30, "Compact; Breeze's static-bar 34 is " + "never used"), headers[0]) + demo_area = layout.find(tab, lambda n: is_type(SCROLL_AREA)(n) + and layout.by_id.get(n.get("parent"), {}).get("type") == MAX_SIZE, + "ScrollArea demo on the Containers tab")[0] + bar = layout.find(demo_area["id"], lambda n: is_type(SCROLL_BAR)(n) + and n.get("parent") == demo_area["id"], + "scroll bar of the ScrollArea demo")[0] + self.rows.append(Row(Spec("scroll bar extent (ScrollArea)", + br.hint_width("scroll_bar.vertical"), "21", 12, + "documented: a ScrollArea lays its bars out 12 px"), + bar, min(bar["bounds"]["width"], bar["bounds"]["height"]))) + standalone = layout.find(tab, lambda n: is_type(SCROLL_BAR)(n) + and layout.by_id.get(n.get("parent"), {}).get("type") == MAX_SIZE, + "standalone ScrollBar on the Containers tab")[0] + self.rows.append(Row(Spec("scroll bar extent (ScrollBar)", + br.hint_height("scroll_bar.horizontal"), "21", 8, + "documented: a ScrollBar is 8 px unless the app sizes it"), + standalone, min(standalone["bounds"]["width"], + standalone["bounds"]["height"]))) + + layout = self.open_tab("Menus") + tab = layout.active_tab_content()["id"] + trigger = layout.find(tab, is_type(MENU_BAR_TRIGGER), "menu bar trigger on the Menus tab")[0] + b.call("inject_pointer", {"node": trigger["id"], + "x": trigger["bounds"]["width"] / 2, + "y": trigger["bounds"]["height"] / 2}) + + def popup_rows(): + lay = b.layout() + inside = {n["id"] for n in lay.descendants(lay.active_tab_content()["id"])} + rows = [n for n in lay.nodes if n["type"] == MENU_ITEM and n["active"] + and n["id"] not in inside and n["bounds"]["height"] > 0] + return rows or None + + rows = self.wait_for("no menu opened from the Menus tab's menu bar", popup_rows) + self.measure(Spec("menu item (opened menu)", br.menu_item(), "29", 29, + "Teksilo reserves the icon column on every row"), rows[0]) + b.call("inject_key", {"key": "Escape"}) + self.wait_for("the menu did not close", lambda: None if popup_rows() else True) + + def measure_tooltip(self, layout: Layout, tab: int) -> None: + """The tooltip a hover over the Buttons tab's first icon button opens.""" + b = self.bridge + assert b is not None + icon = layout.find(tab, is_type(ICON_BUTTON), "icon button on the Buttons tab")[0] + b.call("inject_pointer", {"node": icon["id"], "action": "move", + "x": icon["bounds"]["width"] / 2, + "y": icon["bounds"]["height"] / 2}) + + def frames(): + lay = b.layout() + tips = [n for n in lay.nodes if n["type"] == TOOLTIP and n["active"] + and n["bounds"]["height"] > 0] + if not tips: + return None + frame = [n for n in lay.descendants(tips[0]["id"]) if n["type"] == POPUP_FRAME] + text = [n for n in lay.descendants(tips[0]["id"]) if n["type"] == TEXT] + return (frame[0], text[0]) if frame and text else None + + frame, text = self.wait_for("no tooltip opened over the icon button", frames) + # The known limitation: a plain TooltipWidget writes its text in the + # small style and TextWidget lays one line out at the face's own + # natural height (it ignores the style's line_height), so the frame + # is that height plus Breeze's 4 px on each side, not Plasma's 26. + line = text["bounds"]["height"] + self.measure(Spec("tooltip, one line", (self.breeze.tooltip_height(), "tooltip size_hint h"), + "26", line + 8, + f"known limitation: the text's natural {fmt(line)} px height " + "+ 2 x 4"), frame) + # Away from every control, so the tooltip closes. + b.call("inject_pointer", {"action": "move", "x": 2, "y": 2}) + self.wait_for("the tooltip did not close", lambda: None if frames() else True) + + # -- live change (system mode) --------------------------------------------- + + def presented_frames(self, change=None) -> int: + """Frames presented to the window in `QUIET_WINDOW` s, with `change` run first. + + The catalog's idle trace prints its counters on the first event at + least `TRACE_PERIOD` after its last report, and a bridge call is such + an event, its report written before the call is answered. So: one + call after a quiet `TRACE_PERIOD` closes the span before, then no call + for `QUIET_WINDOW`, then one more call closes the span measured. The + frame the first call itself asks for falls in that span; the control + span, measured with `change` None, has it too. + """ + b = self.bridge + assert b is not None + time.sleep(TRACE_PERIOD) + b.call("list_windows") + offset = b.log_path.stat().st_size + if change is not None: + change() + time.sleep(QUIET_WINDOW) + b.call("list_windows") + with open(b.log_path, "rb") as log: + log.seek(offset) + lines = log.read().decode(errors="replace").splitlines() + reports = [ln for ln in lines if ln.startswith("teksilo_idle_trace ")] + if not reports: + raise CheckFailed(f"the catalog's idle trace printed no report in {b.log_path} " + "(TEKSILO_IDLE_TRACE not honoured?)") + total = 0 + for ln in reports: + field = next((f for f in ln.split() if f.startswith("rendered_frames=")), None) + if field is None: + raise CheckFailed(f"an idle trace report has no rendered_frames: {ln}") + total += int(field.split("=", 1)[1]) + return total + + def live_change(self) -> None: + b = self.bridge + assert b is not None + layout = self.open_tab("Buttons") + tab = layout.active_tab_content()["id"] + button = layout.find(tab, is_type(BUTTON), "push button on the Buttons tab")[0] + selected = b.at_node("Tab", "Buttons") + before = b.layout() + shot_button = b.screenshot(button["id"]) + shot_tab = b.screenshot(selected["id"]) + + def repainted(node: int, reference: Image): + def probe(): + img = b.screenshot(node) + return img if len(reference.changed(img)) >= 20 else None + return probe + + def frames_after(what: str, change) -> None: + control = self.presented_frames() + changed = self.presented_frames(change) + self.notes.append(f"{what}: {changed} frames presented in the {QUIET_WINDOW:g} s " + f"after it with no bridge call, {control} in the same span with " + "nothing changed") + if changed <= control: + self.failures.append(f"the window presented no frame for {what} ({changed} " + f"frames after it, {control} with nothing changed)") + + def to_dark() -> None: + self.desktop.run_tool("BreezeDark") + self.desktop.expect_scheme("BreezeDark") + + frames_after("the scheme change to BreezeDark", to_dark) + dark_button = self.wait_for("the push button does not paint differently after the " + "scheme changed to BreezeDark", + repainted(button["id"], shot_button), REPAINT_DEADLINE) + l0, l1 = luma(shot_button.mean()), luma(dark_button.mean()) + self.notes.append(f"BreezeDark: the push button paints at mean luma {l0:.1f} -> " + f"{l1:.1f}, {len(shot_button.changed(dark_button))} of " + f"{len(dark_button.pixels)} pixels changed") + if not l1 < l0 - 30: + self.failures.append(f"the push button did not turn dark (mean luma {l0:.1f} -> " + f"{l1:.1f})") + dark_tab = b.screenshot(selected["id"]) + + frames_after(f"the accent change to {LIVE_ACCENT}", + lambda: self.desktop.run_tool("-a", LIVE_ACCENT)) + accent_tab = self.wait_for("the selected tab does not paint differently after the " + f"accent changed to {LIVE_ACCENT}", + repainted(selected["id"], dark_tab), REPAINT_DEADLINE) + moved = dark_tab.changed(accent_tab) + r0, g0, _ = dark_tab.mean(moved) + r1, g1, _ = accent_tab.mean(moved) + self.notes.append(f"accent {LIVE_ACCENT}: the selected tab paints {len(moved)} pixels " + f"differently, their mean red {r0:.0f} -> {r1:.0f}, green {g0:.0f} -> " + f"{g1:.0f}") + if not (r1 > r0 and g1 < g0): + self.failures.append("the selected tab's changed pixels did not move toward the " + f"accent {LIVE_ACCENT}") + if not len(shot_tab.changed(accent_tab)) >= 20: + self.failures.append("the selected tab looks as it did before both changes") + + after = b.layout() + ids0, ids1 = set(before.by_id), set(after.by_id) + if ids0 != ids1: + self.failures.append(f"the layout tree was rebuilt: {len(ids0 - ids1)} nodes gone, " + f"{len(ids1 - ids0)} new") + moved_nodes = [i for i, n in before.by_id.items() + if n["active"] and after.by_id.get(i, {}).get("bounds") != n["bounds"]] + if moved_nodes: + self.failures.append(f"{len(moved_nodes)} active nodes changed bounds, first " + f"{moved_nodes[0]}") + self.notes.append(f"no rebuild: {len(ids1)} layout node ids before and after " + f"({'same' if ids0 == ids1 else 'DIFFERENT'}), " + f"{len(moved_nodes)} active nodes with other bounds") + + # -- report ---------------------------------------------------------------- + + def report(self) -> str: + head = ("control", "measured", "Breeze 6.8 (sizes.json entry)", "spec k.4", + "preset", "deviation / note", "result") + lines = [] + for r in self.rows: + lines.append(( + r.spec.control, + fmt(r.measured), + ("-" if r.spec.breeze[0] is None else fmt(r.spec.breeze[0])) + + f" ({r.spec.breeze[1]})", + r.spec.spec_k4, + "-" if r.spec.intended is None else fmt(r.spec.intended), + r.spec.note, + r.status, + )) + # A mode that failed before measuring anything still prints its head. + widths = [max([len(h)] + [len(row[i]) for row in lines]) for i, h in enumerate(head)] + sep = " " + out = [f"== {self.mode} ==", + sep.join(h.ljust(w) for h, w in zip(head, widths)), + sep.join("-" * w for w in widths)] + out += [sep.join(c.ljust(w) for c, w in zip(row, widths)).rstrip() for row in lines] + out += [f"note: {n}" for n in self.notes] + for control, reason in NOT_IN_CATALOG.items(): + out.append(f"not in catalog: {control}: {reason}") + return "\n".join(out) + + def ok(self) -> bool: + return not self.failures and all(r.status != "FAIL" for r in self.rows) + + +def main(argv: list[str] | None = None) -> int: + ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + ap.add_argument("--modes", default=",".join(MODES), + help="comma-separated modes, from: " + ", ".join(MODES)) + ap.add_argument("--out", type=Path, + help="keep the catalog and MCP logs here (default: discarded with the " + "private session)") + args = ap.parse_args(argv) + modes = [m for m in args.modes.split(",") if m] + unknown = [m for m in modes if m not in MODES] + if unknown: + print(f"kde_visual_check: unknown mode {', '.join(unknown)}", file=sys.stderr) + return 2 + try: + config = check_private_session() + missing = [str(p) for p in (CATALOG, MCP) if not p.exists()] + if missing: + raise CannotRun("missing binaries (run kde_visual_check.sh, which builds them): " + + ", ".join(missing)) + desktop = Desktop.open(config) + breeze = Breeze(SIZES) + except CannotRun as e: + print(f"kde_visual_check: cannot run: {e}", file=sys.stderr) + return 2 + out = args.out.resolve() if args.out else Path(tempfile.mkdtemp( + prefix="kde-visual-check.", dir=os.environ["XDG_RUNTIME_DIR"])) + out.mkdir(parents=True, exist_ok=True) + + failed = [] + for mode in modes: + check = ModeCheck(mode, desktop, breeze, out) + try: + check.run() + except CheckFailed as e: + check.failures.append(str(e)) + print(check.report()) + for f in check.failures: + print(f"FAIL: {f}") + for r in check.rows: + if r.status == "FAIL": + print(f"FAIL: {r.spec.control} is {fmt(r.measured)} px, the preset gives " + f"{fmt(r.spec.intended or 0)} (node {r.node['id']}, {r.node['type']})") + print() + if not check.ok(): + failed.append(mode) + if failed: + where = f"logs in {out}" if args.out else "run again with --out DIR to keep the logs" + print(f"kde_visual_check: FAILED in {', '.join(failed)}; {where}") + return 1 + print(f"kde_visual_check: all {len(modes)} modes pass") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/reader/kde_visual_check.sh b/tools/reader/kde_visual_check.sh new file mode 100755 index 000000000..d0c489541 --- /dev/null +++ b/tools/reader/kde_visual_check.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 FernTech +# +# Measure the KDE Breeze preset in the real widget catalog, in a private +# session. +# +# tools/reader/kde_visual_check.sh # kde-light, kde-dark, system +# tools/reader/kde_visual_check.sh --modes system # one mode +# tools/reader/kde_visual_check.sh --out DIR # keep the logs +# +# Started from the desktop, it builds the debug `widget-catalog` and +# `teksilo-automation-mcp` into this checkout's `target/` (debug, so the +# automation bridge is in), then runs itself again through +# `private_session.sh`, so the catalog and `plasma-apply-colorscheme` only +# ever run in the private session: its own D-Bus bus, runtime directory, +# `kwin_wayland --virtual` and configuration, data and state homes. Inside, +# it runs `kde_visual_check.py`, whose docstring says what is measured and +# against what. Needs `kwin_wayland`, `dbus-run-session`, `gdbus` and +# `plasma-apply-colorscheme`. +# +# The exit code is the driver's: 0 when every check passes, 1 on a failed +# check, 2 when it cannot run. + +set -uo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo="$(cd "$here/../.." && pwd)" + +if [[ -z "${TEKSILO_READER_INNER:-}" ]]; then + # The build runs outside the private session: cargo and its wrappers find + # their jobserver and caches under the desktop's runtime directory. + if ! RUSTC_WRAPPER= cargo build --quiet -p widget-catalog -p teksilo-automation-mcp \ + --manifest-path "$repo/Cargo.toml" --target-dir "$repo/target"; then + echo "kde_visual_check.sh: the build failed" >&2 + exit 2 + fi + exec "$here/private_session.sh" "$here/kde_visual_check.sh" "$@" +fi + +exec python3 -I "$here/kde_visual_check.py" "$@"