# Locator Style Color Management

Design proposal, researched 2026-10-19. This document defines the recommended first implementation; product palette modules and gallery posts have not been changed. Runnable SCSS examples below are a reference scaffold, not a completed Directory layout.

Read [Locator Style System](locator-style-system.md) for authoritative-server, activation, and Settings History requirements. The WordPress gallery posts remain authoritative for published styles; local source and generated CSS are generation/backup resources.

## Recommendation

Use Dart Sass modules (`@use`), named palette maps, and scoped CSS custom properties. Use MUI-compatible color roles and keep layout separate from color definitions. Each layout/palette combination compiles to a self-contained CSS asset and is published as its own Locator Style post.

This fits the existing Sass toolchain and works for the current shortcode/HTML locator as well as future React components. No browser-side React/MUI dependency is needed to use a palette. Sharing role names with MUI does not automatically theme MUI components; a future React adapter must explicitly map tokens into `createTheme()`.

Palette identity and display name are separate: `mui-default` / “MUI Default”; `slp-default` / “SLP Default”. Start with an explicitly light MUI Default preset, independent of the dark background used by a documentation screenshot. Dark mode should be a separately defined scheme if added later.

## Options considered

| Approach | Fit and decision |
| --- | --- |
| Sass variables/maps only | Existing toolchain; deterministic output, but compile-time values alone offer less flexibility for HTML/React reuse. Use maps as the authoring format. |
| CSS custom properties only | Native browser theming and inheritance; still needs a consistent palette definition and build contract. Use as the delivered token format. |
| Sass modules + CSS custom properties | Recommended first stage: one reusable palette, one shared layout, separate variant entrypoints. |
| MUI ThemeProvider/CSS-variable theme | Appropriate for React/MUI consumers, but ThemeProvider does not recolor the current locator's arbitrary HTML by itself. Add an adapter when needed. |
| Material Color Utilities / Theme Builder | Recommended optional palette-generation tool; import reviewed output into our token roles. Material 3 roles require an explicit mapping and are not identical to MUI defaults. |
| DTCG JSON + Style Dictionary | Established option for generating several platform formats from design tokens. Defer until JSON/React/design-tool synchronization warrants another build dependency; do not maintain independent JSON and Sass copies by hand. |
| Runtime `color-mix()` / OKLCH | Useful for future theme experiments. Prefer explicit, reviewed sRGB values in the first published presets; color interpolation alone does not establish contrast or harmonious role assignments. |

Reference: [Sass modules](https://sass-lang.com/documentation/at-rules/use/), [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties), [Style Dictionary](https://styledictionary.com/info/tokens/), [DTCG format](https://www.designtokens.org/tr/2025.10/format/), [color-mix](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/color-mix). DTCG is a Community Group specification, not a W3C Recommendation.

## Existing code and migration boundary

Observed in local source:

- Palettes already live in `style-gallery-service/assets/saas/colors/palette/`. Preserve the existing `saas` directory spelling; Sass is the language.
- Existing partials include `_material_colors.scss`, `_blue_hint_green.scss`, `_gray_and_navy.scss`, and `_coolors_one.scss`. They expose different global variable sets. `_material_colors.scss` references Vuetify and is not a complete MUI theme.
- `assets/styles/Directory/stylesheet.scss` currently contains quoted hex strings and a small MUI-inspired palette, plus metadata. Its palette declarations do not themselves produce component styling. Store new Sass colors as color values, such as `#1976d2`, not quoted strings.
- `assets/saas/slp/_locator_style_components.scss` has reusable mixins but hardcodes some colors. Palette-aware styles must replace those color literals with semantic variables or accept explicit color parameters; adding a palette file alone cannot recolor them.
- Core `store-locator-plus/package.json` provides `sass:css` and `sass:css:min`. Installed versions observed: Dart Sass 1.105.1 and MUI 7.3.11. Live unversioned MUI docs describe a newer major version; use the installed version when generating a baseline.
- Sass `@import` and global built-ins are deprecated. New modules use `@use`, `sass:map`, and `sass:meta`; do not extend the deprecated import/global-variable pattern. Existing unrelated styles need not be rewritten in this first step. [Sass migration guidance](https://sass-lang.com/documentation/breaking-changes/import/).

## Token contract

Use three levels:

1. Palette tokens: explicit colors such as `primary-main`, `secondary-light`, and `background-paper`.
2. Locator semantic tokens: `--slp-text`, `--slp-surface`, `--slp-link`, `--slp-button-background`, `--slp-field-border`, `--slp-featured-background`, etc.
3. Component CSS: layout rules reference semantic tokens instead of literal colors. A palette should not contain selectors or layout dimensions.

MUI role groups use `light`, `main`, `dark`, and `contrastText`. The Sass/CSS spelling for the final token is `contrast-text`; a future MUI adapter maps it back to `contrastText`. This preset snapshots the six groups from the locally installed MUI light theme. Neutral/action tokens are also explicit; `control-border` and `featured-background` are SLP extensions. [MUI v7 palette reference](https://v7.mui.com/material-ui/customization/palette/).

Keep literals out of new palette-aware layout code except `transparent`, `currentColor`, and explicit browser/system-color handling. Include token groups even when an individual layout does not yet use them so all palette modules have the same interface.

## Proposed files and working SCSS scaffold

Paths in the code comments are relative to the `style-gallery-service` plugin. These are proposed files; creating them in the product repository requires the normal `start-revision` kickoff.

```scss
// assets/saas/colors/palette/_mui_default.scss
$id: "mui-default";
$label: "MUI Default";
$version: "2610.19.01";

$colors: (
  primary-light: #42a5f5,
  primary-main: #1976d2,
  primary-dark: #1565c0,
  primary-contrast-text: #fff,
  secondary-light: #ba68c8,
  secondary-main: #9c27b0,
  secondary-dark: #7b1fa2,
  secondary-contrast-text: #fff,
  error-light: #ef5350,
  error-main: #d32f2f,
  error-dark: #c62828,
  error-contrast-text: #fff,
  warning-light: #ff9800,
  warning-main: #ed6c02,
  warning-dark: #e65100,
  warning-contrast-text: #fff,
  info-light: #03a9f4,
  info-main: #0288d1,
  info-dark: #01579b,
  info-contrast-text: #fff,
  success-light: #4caf50,
  success-main: #2e7d32,
  success-dark: #1b5e20,
  success-contrast-text: #fff,
  background-default: #fff,
  background-paper: #fff,
  text-primary: rgba(0, 0, 0, 0.87),
  text-secondary: rgba(0, 0, 0, 0.6),
  text-disabled: rgba(0, 0, 0, 0.38),
  divider: rgba(0, 0, 0, 0.12),
  action-hover: rgba(0, 0, 0, 0.04),
  action-selected: rgba(0, 0, 0, 0.08),
  action-focus: rgba(0, 0, 0, 0.12),
  control-border: #767676,
  featured-background: #f3e5f5
);
```

```scss
// assets/saas/colors/_locator_theme.scss
@use "sass:map";
@use "sass:meta";

@mixin emit($colors) {
  @each $role in primary, secondary, error, warning, info, success {
    @each $shade in light, main, dark, contrast-text {
      $key: "#{$role}-#{$shade}";
      @if not map.has-key($colors, $key) {
        @error "Missing locator palette token: #{$key}";
      }
    }
  }
  @each $key in background-default, background-paper, text-primary,
    text-secondary, text-disabled, divider, action-hover,
    action-selected, action-focus, control-border, featured-background {
    @if not map.has-key($colors, $key) {
      @error "Missing locator palette token: #{$key}";
    }
  }
  @each $key, $value in $colors {
    @if meta.type-of($value) != color {
      @error "Locator palette token must be a Sass color: #{$key}";
    }
    --slp-palette-#{$key}: #{$value};
  }

  --slp-text: var(--slp-palette-text-primary);
  --slp-text-muted: var(--slp-palette-text-secondary);
  --slp-background: var(--slp-palette-background-default);
  --slp-surface: var(--slp-palette-background-paper);
  --slp-link: var(--slp-palette-primary-dark);
  --slp-button-background: var(--slp-palette-primary-main);
  --slp-button-hover: var(--slp-palette-primary-dark);
  --slp-button-text: var(--slp-palette-primary-contrast-text);
  --slp-field-border: var(--slp-palette-control-border);
  --slp-focus-ring: var(--slp-palette-primary-dark);
  --slp-result-hover: var(--slp-palette-action-hover);
  --slp-featured-background: var(--slp-palette-featured-background);
}
```

```scss
// assets/styles/Directory/_directory.scss
// Illustrative token consumers; complete Directory layout remains future work.
@mixin render {
  .store_locator_plus {
    color: var(--slp-text);
    background-color: var(--slp-background);

    a { color: var(--slp-link); }
    .slp_ui_button {
      color: var(--slp-button-text);
      background-color: var(--slp-button-background);
      &:hover { background-color: var(--slp-button-hover); }
    }
    input, select {
      color: var(--slp-text);
      background-color: var(--slp-surface);
      border: 1px solid var(--slp-field-border);
    }
    :is(a, button, input, select):focus-visible {
      outline: 2px solid var(--slp-focus-ring);
      outline-offset: 2px;
    }
    .results_entry:hover { background-color: var(--slp-result-hover); }
    .results_entry.featured {
      background-color: var(--slp-featured-background);
    }
  }
}
```

```scss
// assets/styles/Directory/stylesheet-mui-default.scss
@use "../../saas/colors/palette/mui_default" as palette;
@use "../../saas/colors/locator_theme" as theme;
@use "directory";

.store_locator_plus {
  @include theme.emit(palette.$colors);
  #style_name { content: "Directory : #{palette.$label}"; }
  #style_version { content: "2610.19.01"; }
  #palette_name { content: palette.$label; }
  #palette_version { content: palette.$version; }
}
@include directory.render;
```

For SLP Default, create `_slp_default.scss` with the same exported metadata and complete color-map keys, then add `stylesheet-slp-default.scss` using that module and the same `directory.render` mixin. Change palette values in one shared file; never copy the full Directory layout per palette. Choose the actual SLP colors separately; none are prescribed here.

The palette/debug rules do not add DOM elements and are not new SmartOptions. Use the emitted CSS text to inspect these markers. Palette selection is embodied in the compiled artifact and gallery post.

## Scope, compatibility, and future React use

- Emit palette variables on `.store_locator_plus`, not document `:root`. The prefix `--slp-` avoids MUI/host-theme naming collisions. These variables inherit into descendants.
- Confirm the actual SaaS embed, bubble, category widget, and WordPress DOM roots during implementation. Google Maps windows or widgets rendered outside the wrapper need an explicitly scoped token root and component rules there. A selector on the wrapper cannot style a detached popup.
- Multiple different palettes in one document require instance-specific root selectors or attributes in both token emission and layout rules. The current saved active-style system is per installation; this proposal does not add per-instance selection.
- Set palette primitives and semantic aliases on the same root. If a descendant overrides palette primitives, re-emit aliases there too; inherited aliases can already be resolved against ancestor values.
- Reuse existing mixins with explicit semantic arguments where supported. Parameterize remaining hardcoded bubble/header colors as those components become palette-aware; preserve their layout behavior.
- Avoid introducing cascade layers only for these new rules: unlayered legacy/host rules can outrank them. Scope and inspect computed styles first.
- No automatic dark-mode switching in the first stage; a light palette must explicitly provide appropriate surface/text colors regardless of the surrounding site theme.
- Future React adapter: convert the palette's six role groups into MUI `palette` values and map neutrals/action values explicitly. Use the same reviewed palette data to generate both outputs if React sharing is implemented. Do not parse compiled CSS as the authoring source or maintain a second hand-edited JS palette.

## Choosing colors and validating their use

- Start with the [MUI default theme](https://v7.mui.com/material-ui/customization/palette/) for familiar semantic roles. `createTheme()` / `augmentColor()` can derive shade/contrast candidates, but they do not choose an entire coordinated brand scheme from a single color.
- For coordinated seed-based schemes, use Google's [Material Theme Builder](https://material-foundation.github.io/material-theme-builder/) and [Material Color Utilities](https://github.com/material-foundation/material-color-utilities). Preserve exported colors/provenance and explicitly map Material 3 `onPrimary`, surface, container, and other roles to the locator contract; do not label a Material 3 generated scheme “MUI Default”.
- [Adobe Color](https://color.adobe.com/create/color-wheel) provides another color-wheel/harmony starting point. Coolors is already referenced by a local legacy palette, but selecting a pleasing swatch set still needs semantic mapping and contrast review.
- Record each preset's ID, label, version, source URL/tool, generator version/seed if applicable, mode, and reviewed contrast pairs in a small adjacent palette README. Snapshot explicit output values so dependencies or online tools cannot silently change an existing named preset.
- Require 4.5:1 for normal text and 3:1 for qualifying large text. Check relevant control boundaries/state indicators against adjacent colors at 3:1 where required. Evaluate actual foreground/background pairs, including hover/focus/featured states and alpha compositing. Use more than color alone to convey state. [WCAG text contrast](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html), [non-text contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html).
- Local MUI 7.3.11 measurements: white text on warning-main is about 3.12:1; white on info-main about 3.86:1. The exact MUI preset retains those values for fidelity, but these pairs must not be used for normal-sized locator text. Choose a separately reviewed semantic foreground/background pair for notices. The primary button example measures about 4.61:1 with the local MUI utility; also validate its hover state and rendered styling.
- Compiler validation proves token completeness/types; it cannot prove accessibility. Do not assume `contrastText` or a generator's output certifies every use.

## Build and deployment procedure

1. Before product edits, run `start-revision` for `style-gallery-service`. Add the palette module, common emitter, and shared layout/variant entrypoints. Keep existing published styles unchanged while introducing the first variant.
2. Inspect `.idea/watcherTasks.xml` and the “All SLP Unminified” scope. Existing enabled watchers call Yarn from the core plugin and generate same-directory expanded and compressed CSS. Confirm that changes to a shared partial rebuild every affected entrypoint. Rebuild affected variants explicitly if dependency tracking does not cover them; compiling a partial alone is insufficient.
3. For an explicit verified rebuild, use the existing core scripts. Source `local.env`; absolute paths avoid IDE macro ambiguity. Commands below are implementation instructions, not commands already run against the product tree:

```sh
source ./Vendors/JetBrains/local.env
style_service_dir="$WORDPRESS_CODE_DIR/wp-content/plugins/style-gallery-service"
core_plugin_dir="$WORDPRESS_CODE_DIR/wp-content/plugins/store-locator-plus"
style_entry="$style_service_dir/assets/styles/Directory/stylesheet-mui-default.scss"
yarn --cwd "$core_plugin_dir" sass:css "$style_entry" "${style_entry%.scss}.css"
yarn --cwd "$core_plugin_dir" sass:css:min "$style_entry" "${style_entry%.scss}.min.css"
```

4. Expected outputs are `stylesheet-mui-default.css` and `stylesheet-mui-default.min.css`; later also the corresponding `stylesheet-slp-default.*` files. Each includes its palette declarations, aliases, and full layout. Use the existing no-source-map build flags. A separate palette-only CSS asset is optional for tooling; published variants must not depend on fetching it.
5. Validate required keys and color types; inspect output for literal colors/custom properties, correct style/palette markers, no residual Sass/module imports, no quoted color declarations, and no missing CSS-variable definitions. Build all dependents when a palette changes. Bump palette and affected style versions, retain generated deployment assets, and review any changes to settings alongside CSS.
6. Validate the completed layout in WordPress and SaaS at desktop/mobile sizes, including focus, controls, featured results, and map bubbles. Use Playwright under the project's testing workflow when implementing application-level validation. The scaffold here is not evidence of those tests passing.
7. Publish “Directory : MUI Default” as a separate Locator Style post. Paste the entire variant `.min.css` into its `css` field; publish the intended layout/settings and description/image. Later publish “Directory : SLP Default” separately with its own compiled CSS and the same layout settings unless intentionally changed. Track each post ID/slug/artifact mapping. No server-side SCSS compiler or new palette REST setting is required.
8. Read the authoritative post back over REST to verify the published CSS/settings. Local build success alone does not prove publication. Back up previous server values before replacing an existing post; retain those for publisher rollback.
9. The Style panel refreshes catalog/cache information. Customers invoke Activate or Reload to fetch and apply the latest post values. Preserve the existing Settings History recording path; publishing palettes does not introduce automatic customer style application.

## Implementation plan and acceptance criteria

First implement MUI Default, the common emitter/validation, and one complete Directory layout consuming semantic tokens. Then add the separately chosen SLP Default palette and its tiny entrypoint. A preview/palette editor or DTCG/Style Dictionary pipeline can follow once there is a real shared-output requirement.

Accept the first stage when: both variants build with modern Sass; the same layout changes affect both; one palette change affects every variant using it; compiled CSS is self-contained; every color-bearing component uses the token contract; required contrast pairs pass; independent gallery posts activate/reload correctly; and Settings History behavior is retained. Browser/deployment criteria remain unverified until implementation.

## Reference verification performed

On 2026-10-19, the four SCSS blocks above were extracted into a temporary directory and compiled with the installed Dart Sass 1.105.1. Expanded output was 3,309 bytes and compressed output 3,044 bytes. Validation confirmed 47 emitted custom properties, resolution of all 24 variable references, rejection of a missing required token, rejection of a quoted color string, and identical layout output when switching to a temporary alternate palette. Deprecated import/global-built-in usage was configured to fail compilation; no compiler diagnostics were emitted. The temporary alternate colors were test data, not a proposed SLP Default palette.

The local MUI light palette and contrast examples were read through installed MUI 7.3.11. No product files, dependencies, compiled product assets, or gallery posts were changed. No browser or deployment test was performed. A lightweight RAG prefetch timed out after 25 seconds; this proposal relies on local source and the primary documentation linked above.
