@oksigenia/access-panel 0.4.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @oksigenia/access-panel
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `scope=` for per-container accessibility — confine a panel's effects to a host element instead of the whole page, so several panels can each adapt their own region in a multi-pane app (a data dashboard, a scientific viewer, an IDE) without clobbering each other. Opt-in: with no `scope` attribute, behaviour is unchanged.
8
+
9
+ - `<oksigenia-access-panel scope="#map-pane">` applies its effect classes to `#map-pane` (not `body`) and injects a per-instance stylesheet anchored to that selector. Multiple scoped instances coexist, each over its own region with its own `storage-key`.
10
+ - A scoped instance auto-excludes the controls that cannot be regionalised: the full-screen overlays (grayscale, reading guide, reading mask), the colour-blind root filter, and the window-level big cursor. A per-pane panel only offers what it can actually confine.
11
+ - Text size in scoped mode scales the container's `font-size`, so it scales `em`/inherited text inside the pane but not `rem` (which is always relative to `<html>`). Hosts that want per-pane text scaling size that pane's text in `em`.
12
+ - `effects-exclude` composes with `scope`: under a scope, the essential-media exclusion is anchored to the scope selector.
13
+
14
+ This rounds out the toolkit for embedding the panel in non-generic, multi-region platforms (alongside `effects-exclude` and `trigger="none"`). It is a component-only capability — the generic WordPress/Moodle plugins, whose sites adapt globally, neither expose nor need it.
15
+
16
+ ## 0.5.0
17
+
18
+ ### Minor Changes
19
+
20
+ - Make the panel context-aware so it fits non-generic platforms (multi-window data tools, video/3D viewers, scientific imaging) without forking. Everything here is opt-in: with none of the new attributes set, the markup and behaviour are byte-for-byte what they were. No breaking changes.
21
+
22
+ - **Control curation** — `controls="contrast,text-size,colorblind"` (whitelist) and/or `exclude="reading-guide,reading-mask,grayscale"` (blacklist) pick which of the 17 controls an instance offers. A window with only a video or a 3D scene can drop the prose controls (line height, reading guide/mask) that are noise there. Section headings disappear when all their controls are gone. A profile preset recomposes to whatever it can still legitimately set, and hides once it would bundle fewer than two controls — a profile that collapses to a single control is redundant with that control's own button. `presets="none"` drops the profiles row entirely, for tools that don't want it. Control ids are stamped as `data-control` on each button.
23
+
24
+ - **Programmatic open + host-driven trigger** — `trigger="none"` renders the panel without its floating launcher, and the element exposes `.open()`, `.close()`, `.toggle()`. A host can open the panel from its own button inside an existing control cluster — for layouts with no room for a floating trigger. Focus returns to whatever opened the panel.
25
+
26
+ - **Bounded effect exclusion** — `effects-exclude="video, canvas, .no-a11y-filter"` keeps the high-contrast filter off elements where colour is information (a marine video, a depth-coded canvas, scientific imagery), so high-contrast still fixes the UI without destroying data. Grayscale is intentionally not excludable per surface (a full-screen `backdrop-filter` can't be reverted by a descendant) — drop the grayscale control via `exclude=` on instances where colour is data.
27
+
28
+ - **Nine-anchor placement** — `position` now accepts the full 3×3 grid: the existing six plus `top-center`, `mid-center`, `bottom-center`. The host declares where its free gap is instead of the component assuming a corner.
29
+
30
+ - **Bounded nudge** — `nudge` (or `nudge="50"` for a custom px cap) lets the user reposition the trigger within limits, by drag or by arrow keys while it is focused, persisted per instance and clamped so it can never be lost off-screen. This is itself an accessibility affordance: a reduced visual field, a screen magnifier or one-handed reach can move the launcher out of the way without burying it.
31
+
32
+ - **Porthole trigger** — `trigger-icon="porthole"` frames the standard accessibility glyph in a metal ring (the glyph is never replaced, preserving discoverability). The trigger button is also exposed as `::part(trigger)` for hosts that want to compose their own frame.
33
+
3
34
  ## 0.4.4
4
35
 
5
36
  ### Patch Changes
package/README.md CHANGED
@@ -61,10 +61,50 @@ Attributes:
61
61
  | Attribute | Default | Notes |
62
62
  |---|---|---|
63
63
  | `locale` | `navigator.language` | One of `es`, `en`, `gn`, `fr`, `it`, `de`, `nl`, `sv`. Regional variants (`es-PY` → `es`) work. |
64
- | `position` | `mid-left` | One of `top-left`, `top-right`, `mid-left`, `mid-right`, `bottom-left`, `bottom-right`. |
64
+ | `position` | `mid-left` | The 3×3 grid: `top-left`, `top-center`, `top-right`, `mid-left`, `mid-center`, `mid-right`, `bottom-left`, `bottom-center`, `bottom-right`. |
65
65
  | `position-mobile` | inherits `position` | Optional. Same values as `position`. Applied on viewports ≤768px. Useful when the desktop position overlaps mobile hero CTAs. |
66
- | `trigger-icon` | `vitruvian` | One of `vitruvian`, `wheelchair`, `eye`, `universal`. |
66
+ | `trigger-icon` | `vitruvian` | One of `vitruvian`, `wheelchair`, `eye`, `universal`, `porthole`. `porthole` frames the standard glyph in a ring without replacing it. |
67
67
  | `storage-key` | `oksiacSettings` | localStorage key for persisted preferences. |
68
+ | `controls` | all 17 | Whitelist of control ids to offer, comma-separated (e.g. `contrast,text-size,colorblind`). Anything not listed is hidden. |
69
+ | `exclude` | none | Blacklist of control ids to remove from the full set (e.g. `reading-guide,reading-mask,grayscale`). Applied after `controls`. |
70
+ | `presets` | all four | `none` drops the profiles row. A profile also self-hides once curation leaves it bundling fewer than two controls (a one-control profile is redundant with that control). |
71
+ | `trigger` | floating | `none` renders the panel without its floating launcher; open it from your own button via `.open()`. |
72
+ | `effects-exclude` | none | CSS selectors kept free of the destructive high-contrast filter (e.g. `video, canvas, .no-a11y-filter`) — for surfaces where colour is information. |
73
+ | `nudge` | off | Present (or `nudge="50"` for a custom px cap, default 80) lets the user reposition the trigger within bounds, by drag or arrow keys, persisted per instance. |
74
+ | `scope` | whole page | A CSS selector (e.g. `#map-pane`) to confine the panel's effects to one container instead of `body`. For multi-pane apps where each region adapts on its own. See below. |
75
+
76
+ Control ids for `controls`/`exclude`: `text-size`, `line-height`, `text-align`, `readable-font`, `dyslexia-font`, `letter-spacing`, `contrast`, `grayscale`, `hide-images`, `highlight-links`, `colorblind`, `reading-guide`, `reading-mask`, `big-cursor`, `big-targets`, `pause-anim`, `focus`.
77
+
78
+ ### Driving the panel from your own button
79
+
80
+ With `trigger="none"`, mount the panel and open it from anywhere:
81
+
82
+ ```html
83
+ <oksigenia-access-panel id="a11y" trigger="none" controls="contrast,text-size,colorblind"></oksigenia-access-panel>
84
+ <button id="a11y-btn">Accessibility</button>
85
+ <script type="module">
86
+ import '@oksigenia/access-panel/web-component';
87
+ const panel = document.getElementById('a11y');
88
+ document.getElementById('a11y-btn').addEventListener('click', () => panel.toggle());
89
+ </script>
90
+ ```
91
+
92
+ `.open()`, `.close()` and `.toggle()` are available on the element. The floating trigger is also exposed as `::part(trigger)` if you want to restyle it instead of replacing it.
93
+
94
+ ### Per-container accessibility (scoped mode)
95
+
96
+ By default the panel adapts the whole page — which is what a normal content site wants. In a **multi-pane app** (a data dashboard, a scientific viewer, an editor with several panels) you may want each region to adapt on its own: high contrast on the chart pane but the video untouched, bigger text in the data pane but not elsewhere. `scope` confines a panel's effects to one container:
97
+
98
+ ```html
99
+ <div id="map-pane">…</div>
100
+ <oksigenia-access-panel scope="#map-pane" trigger="none" effects-exclude="canvas"></oksigenia-access-panel>
101
+ ```
102
+
103
+ The instance applies its effect classes to `#map-pane` instead of `body` and injects a stylesheet anchored to that selector. Mount one per pane (each with its own `storage-key`) and they coexist without clobbering each other. The dialog opens **over its own pane** (anchored to the scope's box, not a shared viewport corner), so each pane's panel reads as belonging to that pane.
104
+
105
+ A scoped instance **auto-excludes** the controls that can't be confined to a container: the full-screen overlays (grayscale, reading guide, reading mask), the colour-blind root filter, and the window-level big cursor. If you need one of those globally, mount a separate non-scoped instance.
106
+
107
+ Text size in scoped mode scales the container's `font-size`, so it scales `em`/inherited text inside the pane but **not `rem`** — `rem` is always relative to `<html>` and there is no CSS way to anchor it to a container. Size that pane's text in `em` if you want per-pane text scaling.
68
108
 
69
109
  ## Theming with CSS variables
70
110
 
@@ -161,9 +201,52 @@ intentionally cover three orthogonal axes:
161
201
  - **Orientation** (4 controls): reading guide, big cursor, pause animations,
162
202
  focus outline.
163
203
 
164
- You can disable individual controls by hiding them with CSS in the
165
- shadow root if you want a smaller panel for your site, but the API
166
- contract is to ship the full set.
204
+ You can curate which controls appear with the `controls` / `exclude` attributes (see the attributes table above) if you want a smaller panel for your site; the default is to ship the full set.
205
+
206
+ ## Scientific basis & references
207
+
208
+ Oksigenia Access is a presentation-layer tool: it lets a visitor adapt how a page is shown to them. The controls map to recognised accessibility standards and, where relevant, to peer-reviewed research. This section documents what each control is grounded in — and, just as importantly, where the evidence is weak, so nothing here claims more authority than it has. A floating panel does not make a site conformant; conformance is editorial work on the content (alt text, transcripts, semantics, source-design contrast, keyboard operability). These references are the basis for the *adaptations the panel offers*, not a conformance claim.
209
+
210
+ ### Standards and legal framework
211
+
212
+ - **WCAG 2.2** — W3C Recommendation, the technical baseline most controls map to: <https://www.w3.org/TR/WCAG22/>
213
+ - **EN 301 549** — the European harmonised accessibility standard (references WCAG).
214
+ - **Directive (EU) 2016/2102** — accessibility of public-sector websites and apps: <https://eur-lex.europa.eu/eli/dir/2016/2102/oj>
215
+ - **Directive (EU) 2019/882 (European Accessibility Act)** — applies from 28 June 2025: <https://eur-lex.europa.eu/eli/dir/2019/882/oj>
216
+ - **Spain — Real Decreto 1112/2018 (BOE)** — transposes Directive 2016/2102: <https://www.boe.es/eli/es/rd/2018/09/07/1112>
217
+
218
+ ### What each control is grounded in
219
+
220
+ | Control | Basis |
221
+ |---|---|
222
+ | Text size | WCAG 1.4.4 Resize Text, 1.4.10 Reflow |
223
+ | Line height · letter spacing · text alignment | WCAG 1.4.12 Text Spacing (line height ≥ 1.5, letter spacing ≥ 0.12em, word spacing ≥ 0.16em) |
224
+ | High contrast | WCAG 1.4.3 (AA), 1.4.6 (AAA), 1.4.11 Non-text Contrast |
225
+ | Highlight links | WCAG 1.4.1 Use of Color |
226
+ | Big targets | WCAG 2.5.8 Target Size Minimum (AA, 24px), 2.5.5 Enhanced (AAA, 44px) |
227
+ | Pause animations | WCAG 2.2.2 Pause/Stop/Hide, 2.3.3 Animation from Interactions; CSS `prefers-reduced-motion` |
228
+ | Focus highlight | WCAG 2.4.7 Focus Visible (AA), 2.4.11/2.4.13 Focus Appearance (2.2) |
229
+ | Readable font | British Dyslexia Association — Dyslexia Style Guide |
230
+ | Reading guide · reading mask · hide images | Cognitive-accessibility reading aids (WCAG cognitive guidance / COGA); no single Success Criterion mandates them |
231
+ | Colour-blind filters | Colour-vision-deficiency **simulation** — Viénot, Brettel & Mollon (1999); Brettel, Viénot & Mollon (1997); Machado, Oliveira & Fernandes (2009) |
232
+
233
+ ### Honesty notes
234
+
235
+ **The colour-blind control is a _simulation_, not a _correction_.** It renders the page as a person with that deficiency would see it — a design/QA aid for people with normal vision, not an aid for a colour-blind visitor (who already sees that way). The matrices are the SVG colour-matrix filters widely used on the web; their rigorous basis is the simulation literature cited above, and the exact circulating values are approximations (for production-grade accuracy see [DaltonLens](https://daltonlens.org/cvd-simulation-svg-filters/)). Helping a colour-blind user is a design problem (colour-blind-safe palettes, shape in addition to colour), not a screen filter.
236
+
237
+ **"Dyslexia font" has mixed evidence.** Studies on special dyslexia typefaces (e.g. OpenDyslexic) are inconclusive, and several find no benefit over a good standard font. What _is_ evidence-based is spacing (WCAG 1.4.12 and the BDA guide). We offer the font as a common preference, not as a research-backed remedy.
238
+
239
+ ### Sources
240
+
241
+ - W3C — *Web Content Accessibility Guidelines (WCAG) 2.2*: <https://www.w3.org/TR/WCAG22/> (per-criterion notes: <https://www.w3.org/WAI/WCAG22/Understanding/>)
242
+ - Directive (EU) 2016/2102: <https://eur-lex.europa.eu/eli/dir/2016/2102/oj>
243
+ - Directive (EU) 2019/882 (European Accessibility Act): <https://eur-lex.europa.eu/eli/dir/2019/882/oj>
244
+ - Real Decreto 1112/2018 (BOE): <https://www.boe.es/eli/es/rd/2018/09/07/1112>
245
+ - British Dyslexia Association — *Dyslexia Style Guide* (archived copy; the BDA's live document is access-restricted): <https://web.archive.org/web/20250701150153/https://www.bdadyslexia.org.uk/advice/employers/creating-a-dyslexia-friendly-workplace/dyslexia-friendly-style-guide>
246
+ - Viénot, F., Brettel, H., & Mollon, J. D. (1999). *Digital video colourmaps for checking the legibility of displays by dichromats.* Color Research & Application.
247
+ - Brettel, H., Viénot, F., & Mollon, J. D. (1997). *Computerized simulation of color appearance for dichromats.* JOSA A.
248
+ - Machado, G. M., Oliveira, M. M., & Fernandes, L. A. F. (2009). *A physiologically-based model for simulation of color vision deficiency.* IEEE TVCG.
249
+ - DaltonLens — *Accurate SVG filters for color blindness simulation*: <https://daltonlens.org/cvd-simulation-svg-filters/>
167
250
 
168
251
  ## License
169
252
 
package/dist/index.d.ts CHANGED
@@ -38,24 +38,9 @@ interface Translation {
38
38
  declare function getTranslation(locale: string): Translation;
39
39
  declare function supportedLocales(): readonly LocaleCode[];
40
40
 
41
- type TriggerIcon = 'vitruvian' | 'wheelchair' | 'eye' | 'universal';
41
+ type TriggerIcon = 'vitruvian' | 'wheelchair' | 'eye' | 'universal' | 'porthole';
42
42
  declare const COLORBLIND_FILTERS_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"position:absolute;width:0;height:0;overflow:hidden;\" aria-hidden=\"true\"><defs><filter id=\"oks-filter-protanopia\"><feColorMatrix type=\"matrix\" values=\"0.567 0.433 0 0 0 0.558 0.442 0 0 0 0 0.242 0.758 0 0 0 0 0 1 0\"/></filter><filter id=\"oks-filter-deuteranopia\"><feColorMatrix type=\"matrix\" values=\"0.625 0.375 0 0 0 0.7 0.3 0 0 0 0 0.3 0.7 0 0 0 0 0 1 0\"/></filter><filter id=\"oks-filter-tritanopia\"><feColorMatrix type=\"matrix\" values=\"0.95 0.05 0 0 0 0 0.433 0.567 0 0 0 0.475 0.525 0 0 0 0 0 1 0\"/></filter></defs></svg>";
43
43
 
44
- type Position = 'top-left' | 'top-right' | 'mid-left' | 'mid-right' | 'bottom-left' | 'bottom-right';
45
- interface RenderOptions {
46
- t: Translation;
47
- triggerIcon: TriggerIcon;
48
- position: Position;
49
- }
50
- /**
51
- * HTML del panel completo (trigger + dialog). Pensado para inyectarse
52
- * dentro del Shadow DOM del custom element.
53
- */
54
- declare function buildPanelHtml(opts: RenderOptions): string;
55
- /** Mapea Position a CSS para wrapper y panel. Inyectado en el Shadow DOM.
56
- * `mobile` opcional aplica una posición distinta del wrapper en viewport ≤768px. */
57
- declare function positionCss(position: Position, mobile?: Position): string;
58
-
59
44
  interface PanelState {
60
45
  /** Niveles 1..4 → 0 desactivado. */
61
46
  zoom: number;
@@ -90,20 +75,104 @@ declare function loadState(key: string): PanelState;
90
75
  declare function saveState(key: string, state: PanelState): void;
91
76
  declare function isStateEmpty(state: PanelState): boolean;
92
77
 
78
+ type ControlId = 'text-size' | 'line-height' | 'text-align' | 'readable-font' | 'dyslexia-font' | 'letter-spacing' | 'contrast' | 'grayscale' | 'hide-images' | 'highlight-links' | 'colorblind' | 'reading-guide' | 'reading-mask' | 'big-cursor' | 'big-targets' | 'pause-anim' | 'focus';
79
+ /** Render order (also the order `exclude=`/`controls=` are validated against). */
80
+ declare const ALL_CONTROLS: readonly ControlId[];
81
+ type PresetId = 'lowvision' | 'dyslexia' | 'motor' | 'calm';
82
+ /** Preset profiles: set the listed flags, leave the rest as-is.
83
+ * Apply is additive — pressing two presets unions their flags. */
84
+ declare const PRESETS: Readonly<Record<PresetId, Partial<PanelState>>>;
85
+ declare const PRESET_IDS: readonly PresetId[];
86
+ /**
87
+ * Resolve which controls an instance offers from its `controls=` (whitelist)
88
+ * and `exclude=` (blacklist) attributes. Both optional; unknown ids ignored.
89
+ * No attributes ⇒ all 17 (current behavior).
90
+ */
91
+ declare function resolveEnabledControls(controlsAttr?: string | null, excludeAttr?: string | null): Set<ControlId>;
92
+ /**
93
+ * Controls that cannot be confined to a container (`scope=`): the full-screen
94
+ * overlays (grayscale, reading guide, reading mask), the colour-blind root
95
+ * `filter` on `<html>`, and the window-level big cursor. A scoped instance
96
+ * auto-excludes these — a per-pane panel only offers what it can regionalise.
97
+ */
98
+ declare const NON_SCOPABLE_CONTROLS: readonly ControlId[];
99
+ /** Remove the non-scopable controls from an enabled set (scoped mode). */
100
+ declare function scopedControls(enabled: Set<ControlId>): Set<ControlId>;
101
+
102
+ type Position = 'top-left' | 'top-center' | 'top-right' | 'mid-left' | 'mid-center' | 'mid-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
103
+ interface RenderOptions {
104
+ t: Translation;
105
+ triggerIcon: TriggerIcon;
106
+ position: Position;
107
+ /** Controls offered by this instance. Defaults to all 17. */
108
+ enabled?: Set<ControlId>;
109
+ /** Render the floating trigger button. `false` for host-driven mode
110
+ * (`trigger="none"`): the panel renders without its launcher and the host
111
+ * opens it via `.open()`. Defaults to true. */
112
+ showTrigger?: boolean;
113
+ /** Render the profile presets row. `false` (`presets="none"`) drops it
114
+ * entirely. Defaults to true; individual presets still self-hide under the
115
+ * ≥2-control rule. */
116
+ showPresets?: boolean;
117
+ }
118
+ /**
119
+ * HTML del panel completo (trigger + dialog). Pensado para inyectarse
120
+ * dentro del Shadow DOM del custom element.
121
+ */
122
+ declare function buildPanelHtml(opts: RenderOptions): string;
123
+ /** Mapea Position a CSS para wrapper y panel. Inyectado en el Shadow DOM.
124
+ * `mobile` opcional aplica una posición distinta del wrapper en viewport ≤768px. */
125
+ declare function positionCss(position: Position, mobile?: Position): string;
126
+
93
127
  interface BehaviorOptions {
94
128
  /** Llave localStorage. Default oksiacSettings. */
95
129
  storageKey?: string;
96
130
  /** Locale para los aria-label dinámicos (nivel de los multinivel). Default en. */
97
131
  locale?: string;
132
+ /** Controles que ofrece esta instancia (#1). Default: los 17. Filtra qué
133
+ * flags puede aplicar un perfil. */
134
+ enabled?: Set<ControlId>;
135
+ /** Desplazamiento máximo del usuario para el trigger en px (#5). 0/ausente
136
+ * desactiva el nudge (comportamiento actual). */
137
+ nudgeMax?: number;
138
+ /** Contenedor al que confinar los efectos (`scope=`). Si se pasa, las clases
139
+ * scopables van a este elemento en vez de a `body`, y los efectos no
140
+ * regionalizables (overlays, daltonismo, cursor) no se aplican. */
141
+ scopeEl?: HTMLElement | null;
142
+ }
143
+ /** Dispose function with imperative open/close/toggle attached (#2). Calling
144
+ * the value disposes; the methods drive the panel host-side. */
145
+ interface PanelController {
146
+ (): void;
147
+ open(): void;
148
+ close(): void;
149
+ toggle(): void;
98
150
  }
99
151
  /**
100
152
  * Engancha toda la lógica al panel ya renderizado dentro de `root`
101
- * (que es el shadowRoot del custom element). Devuelve un dispose para
102
- * limpiar.
153
+ * (que es el shadowRoot del custom element). Devuelve un PanelController:
154
+ * invocarlo limpia; sus métodos abren/cierran el panel.
103
155
  */
104
- declare function bindPanelBehavior(root: ShadowRoot, opts?: BehaviorOptions): () => void;
156
+ declare function bindPanelBehavior(root: ShadowRoot, opts?: BehaviorOptions): PanelController;
105
157
 
106
- declare const PANEL_CSS = "\n:host {\n --oks-btn-size: 55px;\n --oks-bg: #000;\n --oks-icon: #fff;\n --oks-h-bg: #fff;\n --oks-h-icon: #000;\n --oks-z: 9999999;\n}\n.oks-access-wrapper {\n position: fixed;\n z-index: var(--oks-z);\n line-height: 1;\n}\n.oks-access-btn {\n width: var(--oks-btn-size);\n height: var(--oks-btn-size);\n border-radius: 50%;\n background: var(--oks-bg);\n color: var(--oks-icon);\n border: 2px solid #fff;\n box-shadow: 0 4px 15px rgba(0,0,0,0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: 0.2s;\n padding: 0;\n}\n.oks-access-btn:hover {\n background: var(--oks-h-bg);\n color: var(--oks-h-icon);\n transform: scale(1.1);\n}\n.oks-access-btn svg {\n fill: currentColor;\n display: block;\n margin: 0 auto;\n width: 60%;\n height: 60%;\n}\n.oks-active-badge {\n position: absolute;\n top: -2px;\n right: -2px;\n width: 20px;\n height: 20px;\n background: #25D366;\n border-radius: 50%;\n border: 2px solid #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #fff;\n opacity: 0;\n pointer-events: none;\n}\n.oks-active-badge svg { width: 12px; height: 12px; }\n.oks-access-wrapper.has-active .oks-active-badge { opacity: 1; }\n\n.oks-access-panel {\n position: fixed;\n width: 340px;\n max-height: 90vh;\n background: #fff;\n border-radius: 20px;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n z-index: var(--oks-z);\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: 0.2s;\n border: 1px solid rgba(0,0,0,0.1);\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n color: #333;\n}\n.oks-access-panel.is-open { opacity: 1; pointer-events: all; }\n.oks-access-header {\n padding: 15px 20px;\n border-bottom: 1px solid #eee;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n.oks-access-header h3 { margin: 0; font-size: 18px; color: #000; }\n.oks-access-close {\n background: #f0f0f0;\n color: #333;\n border: 1px solid #ddd;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: 0.2s;\n padding: 0;\n}\n.oks-access-close:hover { background: #e0e0e0; border-color: #ccc; }\n.oks-access-close svg { width: 24px; height: 24px; stroke-width: 2.5px; }\n.oks-access-content { padding: 0 20px 20px; overflow-y: auto; }\n.oks-access-title {\n margin: 10px 0 5px;\n font-size: 11px;\n font-weight: 800;\n text-transform: uppercase;\n color: #888;\n}\n.oks-access-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; }\n.oks-access-presets { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }\n.oks-preset {\n background: #f0f4f8;\n border: 2px solid transparent;\n border-radius: 8px;\n padding: 8px 4px;\n display: flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n font: inherit;\n color: #333;\n min-width: 0;\n transition: 0.15s;\n}\n.oks-preset:hover { background: #000; color: #fff; }\n.oks-preset:focus-visible { outline: 2px solid #000; outline-offset: 2px; }\n/* Click feedback: a 250 ms flip + slight squeeze. Pure transient \u2014 the\n button does not carry persistent active state because a preset is a\n trigger, not a mode. */\n.oks-preset.is-flashing {\n background: #000;\n color: #fff;\n transform: scale(0.96);\n}\n.oks-preset .oks-icon { font-size: 22px; margin-bottom: 4px; }\n.oks-preset .oks-icon svg { width: 20px; height: 20px; }\n.oks-preset .oks-label {\n font-size: 10px; font-weight: 700; text-transform: uppercase;\n text-align: center; line-height: 1.15;\n overflow-wrap: anywhere; word-break: break-word;\n /* Reserve room for 2 lines so 1-line labels (DISLEXIA, MOTOR) and\n 2-line labels (BAJA VISI\u00D3N, SIN DISTRAC.) end up the same height,\n centred. Without this the row looks ragged. */\n min-height: 2.3em;\n display: flex; align-items: center; justify-content: center;\n}\n.oks-access-opt {\n background: #f9f9f9;\n border: 2px solid #eee;\n border-radius: 10px;\n padding: 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n min-width: 0;\n min-height: 70px;\n color: #333;\n transition: 0.2s;\n font: inherit;\n}\n.oks-access-opt.full-width { grid-column: span 2; }\n.oks-access-opt.is-active {\n border-color: #000;\n background: #fff;\n box-shadow: 0 0 0 1px #000;\n}\n.oks-access-opt:hover { background: #000; color: #fff; border-color: #000; }\n.oks-icon { font-size: 28px; margin-bottom: 3px; display: block; line-height: 1; }\n.oks-icon svg { width: 24px; height: 24px; fill: currentColor; }\n.oks-label {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n text-align: center;\n line-height: 1.2;\n overflow-wrap: anywhere;\n word-break: break-word;\n}\n.oks-levels { display: flex; gap: 3px; height: 5px; width: 50%; margin-top: 5px; }\n.oks-levels span { flex: 1; background: #ddd; border-radius: 3px; }\n.oks-access-opt[data-level=\"1\"] .oks-levels span:nth-child(1),\n.oks-access-opt[data-level=\"2\"] .oks-levels span:nth-child(-n+2),\n.oks-access-opt[data-level=\"3\"] .oks-levels span:nth-child(-n+3),\n.oks-access-opt[data-level=\"4\"] .oks-levels span:nth-child(-n+4) { background: #000; }\n.oks-access-opt:hover .oks-levels span { background: #555; }\n.oks-access-footer {\n padding: 12px 20px;\n border-top: 1px solid #eee;\n text-align: center;\n}\n.oks-access-reset {\n width: 100%;\n padding: 8px;\n border: 2px solid #000;\n color: #000;\n background: transparent;\n font-weight: 700;\n cursor: pointer;\n border-radius: 6px;\n font-size: 12px;\n}\n.oks-access-reset:hover { background: #000; color: #fff; }\n.oks-access-branding { margin-top: 12px; font-size: 12px; color: #000; font-weight: 700; }\n.oks-access-branding a { color: #000; text-decoration: none; border-bottom: 1px dotted #000; }\n\n@media (prefers-reduced-motion: reduce) {\n .oks-access-btn, .oks-access-panel, .oks-access-close,\n .oks-preset, .oks-access-opt { transition: none; }\n .oks-access-btn:hover { transform: scale(1); }\n .oks-preset.is-flashing { transform: none; }\n}\n@media (max-width: 768px) {\n .oks-access-panel {\n width: 100%; height: 100%; max-height: 100%;\n top: 0; left: 0; right: 0; bottom: 0;\n border-radius: 0;\n }\n .oks-access-opt { min-height: 72px; padding: 10px 8px; }\n .oks-icon svg { width: 26px; height: 26px; }\n .oks-label { font-size: 12px; line-height: 1.25; }\n .oks-access-grid { gap: 8px; }\n .oks-access-content { padding: 0 14px 20px; }\n .oks-access-title { font-size: 11px; margin: 10px 0 4px; }\n /* Cursor grande no aplica en t\u00E1ctil: oculto y dejo el \u00FAltimo bot\u00F3n\n de la secci\u00F3n ocupando 2 columnas para no romper la grilla par. */\n .oks-access-opt[data-class=\"oks-big-cursor\"] { display: none; }\n .oks-access-opt[data-class=\"oks-a11y-focus\"] { grid-column: span 2; }\n .oks-access-reset { padding: 14px; font-size: 14px; }\n}\n";
158
+ declare const PANEL_CSS = "\n:host {\n --oks-btn-size: 55px;\n --oks-bg: #000;\n --oks-icon: #fff;\n --oks-h-bg: #fff;\n --oks-h-icon: #000;\n --oks-z: 9999999;\n}\n.oks-access-wrapper {\n position: fixed;\n z-index: var(--oks-z);\n line-height: 1;\n}\n.oks-access-btn {\n width: var(--oks-btn-size);\n height: var(--oks-btn-size);\n border-radius: 50%;\n background: var(--oks-bg);\n color: var(--oks-icon);\n border: 2px solid #fff;\n box-shadow: 0 4px 15px rgba(0,0,0,0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: 0.2s;\n padding: 0;\n}\n.oks-access-btn:hover {\n background: var(--oks-h-bg);\n color: var(--oks-h-icon);\n transform: scale(1.1);\n}\n.oks-access-btn svg {\n fill: currentColor;\n display: block;\n margin: 0 auto;\n width: 60%;\n height: 60%;\n}\n/* Porthole preset: a brushed-metal ring framing the standard glyph. The glyph\n itself is untouched (discoverability), only the button gets the frame.\n Hosts wanting a bespoke frame can target ::part(trigger) from light DOM. */\n.oks-access-btn[data-trigger-icon=\"porthole\"] {\n box-shadow: 0 0 0 4px #b0bec5, 0 0 0 7px #546e7a, 0 6px 18px rgba(0,0,0,0.3);\n}\n.oks-active-badge {\n position: absolute;\n top: -2px;\n right: -2px;\n width: 20px;\n height: 20px;\n background: #25D366;\n border-radius: 50%;\n border: 2px solid #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #fff;\n opacity: 0;\n pointer-events: none;\n}\n.oks-active-badge svg { width: 12px; height: 12px; }\n.oks-access-wrapper.has-active .oks-active-badge { opacity: 1; }\n\n.oks-access-panel {\n position: fixed;\n width: 340px;\n max-height: 90vh;\n background: #fff;\n border-radius: 20px;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n z-index: var(--oks-z);\n display: flex;\n flex-direction: column;\n opacity: 0;\n pointer-events: none;\n transition: 0.2s;\n border: 1px solid rgba(0,0,0,0.1);\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n color: #333;\n}\n.oks-access-panel.is-open { opacity: 1; pointer-events: all; }\n.oks-access-header {\n padding: 15px 20px;\n border-bottom: 1px solid #eee;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n.oks-access-header h3 { margin: 0; font-size: 18px; color: #000; }\n.oks-access-close {\n background: #f0f0f0;\n color: #333;\n border: 1px solid #ddd;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: 0.2s;\n padding: 0;\n}\n.oks-access-close:hover { background: #e0e0e0; border-color: #ccc; }\n.oks-access-close svg { width: 24px; height: 24px; stroke-width: 2.5px; }\n.oks-access-content { padding: 0 20px 20px; overflow-y: auto; }\n.oks-access-title {\n margin: 10px 0 5px;\n font-size: 11px;\n font-weight: 800;\n text-transform: uppercase;\n color: #888;\n}\n.oks-access-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; }\n.oks-access-presets { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }\n.oks-preset {\n background: #f0f4f8;\n border: 2px solid transparent;\n border-radius: 8px;\n padding: 8px 4px;\n display: flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n font: inherit;\n color: #333;\n min-width: 0;\n transition: 0.15s;\n}\n.oks-preset:hover { background: #000; color: #fff; }\n.oks-preset:focus-visible { outline: 2px solid #000; outline-offset: 2px; }\n/* Click feedback: a 250 ms flip + slight squeeze. Pure transient \u2014 the\n button does not carry persistent active state because a preset is a\n trigger, not a mode. */\n.oks-preset.is-flashing {\n background: #000;\n color: #fff;\n transform: scale(0.96);\n}\n.oks-preset .oks-icon { font-size: 22px; margin-bottom: 4px; }\n.oks-preset .oks-icon svg { width: 20px; height: 20px; }\n.oks-preset .oks-label {\n font-size: 10px; font-weight: 700; text-transform: uppercase;\n text-align: center; line-height: 1.15;\n overflow-wrap: anywhere; word-break: break-word;\n /* Reserve room for 2 lines so 1-line labels (DISLEXIA, MOTOR) and\n 2-line labels (BAJA VISI\u00D3N, SIN DISTRAC.) end up the same height,\n centred. Without this the row looks ragged. */\n min-height: 2.3em;\n display: flex; align-items: center; justify-content: center;\n}\n.oks-access-opt {\n background: #f9f9f9;\n border: 2px solid #eee;\n border-radius: 10px;\n padding: 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n min-width: 0;\n min-height: 70px;\n color: #333;\n transition: 0.2s;\n font: inherit;\n}\n.oks-access-opt.full-width { grid-column: span 2; }\n.oks-access-opt.is-active {\n border-color: #000;\n background: #fff;\n box-shadow: 0 0 0 1px #000;\n}\n.oks-access-opt:hover { background: #000; color: #fff; border-color: #000; }\n.oks-icon { font-size: 28px; margin-bottom: 3px; display: block; line-height: 1; }\n.oks-icon svg { width: 24px; height: 24px; fill: currentColor; }\n.oks-label {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n text-align: center;\n line-height: 1.2;\n overflow-wrap: anywhere;\n word-break: break-word;\n}\n.oks-levels { display: flex; gap: 3px; height: 5px; width: 50%; margin-top: 5px; }\n.oks-levels span { flex: 1; background: #ddd; border-radius: 3px; }\n.oks-access-opt[data-level=\"1\"] .oks-levels span:nth-child(1),\n.oks-access-opt[data-level=\"2\"] .oks-levels span:nth-child(-n+2),\n.oks-access-opt[data-level=\"3\"] .oks-levels span:nth-child(-n+3),\n.oks-access-opt[data-level=\"4\"] .oks-levels span:nth-child(-n+4) { background: #000; }\n.oks-access-opt:hover .oks-levels span { background: #555; }\n.oks-access-footer {\n padding: 12px 20px;\n border-top: 1px solid #eee;\n text-align: center;\n}\n.oks-access-reset {\n width: 100%;\n padding: 8px;\n border: 2px solid #000;\n color: #000;\n background: transparent;\n font-weight: 700;\n cursor: pointer;\n border-radius: 6px;\n font-size: 12px;\n}\n.oks-access-reset:hover { background: #000; color: #fff; }\n.oks-access-branding { margin-top: 12px; font-size: 12px; color: #000; font-weight: 700; }\n.oks-access-branding a { color: #000; text-decoration: none; border-bottom: 1px dotted #000; }\n\n@media (prefers-reduced-motion: reduce) {\n .oks-access-btn, .oks-access-panel, .oks-access-close,\n .oks-preset, .oks-access-opt { transition: none; }\n .oks-access-btn:hover { transform: scale(1); }\n .oks-preset.is-flashing { transform: none; }\n}\n@media (max-width: 768px) {\n .oks-access-panel {\n width: 100%; height: 100%; max-height: 100%;\n top: 0; left: 0; right: 0; bottom: 0;\n border-radius: 0;\n }\n .oks-access-opt { min-height: 72px; padding: 10px 8px; }\n .oks-icon svg { width: 26px; height: 26px; }\n .oks-label { font-size: 12px; line-height: 1.25; }\n .oks-access-grid { gap: 8px; }\n .oks-access-content { padding: 0 14px 20px; }\n .oks-access-title { font-size: 11px; margin: 10px 0 4px; }\n /* Cursor grande no aplica en t\u00E1ctil: oculto y dejo el \u00FAltimo bot\u00F3n\n de la secci\u00F3n ocupando 2 columnas para no romper la grilla par. */\n .oks-access-opt[data-class=\"oks-big-cursor\"] { display: none; }\n .oks-access-opt[data-class=\"oks-a11y-focus\"] { grid-column: span 2; }\n .oks-access-reset { padding: 14px; font-size: 14px; }\n}\n";
107
159
  declare const EFFECT_CSS = "\nhtml.oks-colorblind-1 { filter: url('#oks-filter-protanopia'); }\nhtml.oks-colorblind-2 { filter: url('#oks-filter-deuteranopia'); }\nhtml.oks-colorblind-3 { filter: url('#oks-filter-tritanopia'); }\n\n/* Text-size levels.\n Applied to <html> via :has(), not to <body>. rem is anchored to the root\n element, so a site whose CSS sizes things in rem (most modern Astro / Tailwind\n builds) needs the root font-size to change for the scale to take effect.\n A previous version applied this to body with %, which only moved descendants\n that inherited font-size from body \u2014 anything sized in rem stayed locked to\n the 16px default of <html>. An even earlier version used the universal\n selector with em and compounded the factor at every nesting level.\n :has(body.oks-zoom-N) is the right anchor: one change at the root, rem\n descendants scale exactly once. Hard-coded px is intentionally left alone;\n browser zoom covers that case. */\nhtml:has(body.oks-zoom-1) { font-size: 110% !important; }\nhtml:has(body.oks-zoom-2) { font-size: 120% !important; }\nhtml:has(body.oks-zoom-3) { font-size: 135% !important; }\nhtml:has(body.oks-zoom-4) { font-size: 150% !important; }\n\nbody.oks-lh-1 * { line-height: 1.6 !important; }\nbody.oks-lh-2 * { line-height: 1.9 !important; }\nbody.oks-lh-3 * { line-height: 2.2 !important; }\n\nbody.oks-a11y-font { font-family: Arial, sans-serif !important; }\n\nbody.oks-dyslexia * {\n font-family: 'Comic Sans MS', 'Verdana', sans-serif !important;\n letter-spacing: 0.05em !important;\n word-spacing: 0.1em !important;\n line-height: 1.6 !important;\n}\n\nbody.oks-a11y-hide img { opacity: 0 !important; visibility: hidden !important; }\nbody.oks-a11y-links a { text-decoration: underline !important; background: #ff0 !important; color: #000 !important; }\n\nbody.oks-align-1 * { text-align: left !important; }\nbody.oks-align-2 * { text-align: center !important; }\nbody.oks-align-3 * { text-align: right !important; }\n\nbody.oks-a11y-pause * { animation: none !important; transition: none !important; }\n\nbody.oks-ls-1 * { letter-spacing: 0.05em !important; }\nbody.oks-ls-2 * { letter-spacing: 0.10em !important; }\nbody.oks-ls-3 * { letter-spacing: 0.16em !important; }\n\nbody.oks-a11y-focus a:not(oksigenia-access-panel):not(oksigenia-access-panel *),\nbody.oks-a11y-focus button:not(oksigenia-access-panel):not(oksigenia-access-panel *),\nbody.oks-a11y-focus input,\nbody.oks-a11y-focus select,\nbody.oks-a11y-focus textarea {\n outline: 2px dashed rgba(0, 95, 204, 0.45) !important;\n outline-offset: 2px !important;\n}\nbody.oks-a11y-focus *:focus-visible {\n outline: 3px solid #005fcc !important;\n outline-offset: 3px !important;\n box-shadow: 0 0 0 6px rgba(0, 95, 204, 0.25) !important;\n}\n\nbody.oks-a11y-contrast.oks-a11y-focus *:focus,\nbody.oks-a11y-contrast.oks-a11y-focus *:focus-visible {\n outline-color: #0ff !important;\n box-shadow: 0 0 0 6px rgba(0, 255, 255, 0.3) !important;\n}\n\n@media (pointer: fine) {\n body.oks-big-cursor, body.oks-big-cursor a, body.oks-big-cursor button {\n cursor: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 24 24'%3E%3Cpath d='M4 2L4 18L8 14L11 21L14 19.5L11 13L15.5 13Z' stroke='white' stroke-width='4' stroke-linejoin='round' fill='white'/%3E%3Cpath d='M4 2L4 18L8 14L11 21L14 19.5L11 13L15.5 13Z' fill='black'/%3E%3C/svg%3E\") 8 4, auto !important;\n }\n}\n\nbody.oks-a11y-contrast,\nbody.oks-a11y-contrast *:not(oksigenia-access-panel):not(oksigenia-access-panel *) {\n background-color: #000 !important;\n color: #ff0 !important;\n border-color: #ff0 !important;\n text-shadow: none !important;\n box-shadow: none !important;\n}\nbody.oks-a11y-contrast img { filter: grayscale(100%) contrast(120%) !important; }\nbody.oks-a11y-contrast a:not(oksigenia-access-panel *) { color: #0ff !important; text-decoration: underline !important; }\n\n/* High-contrast applies background:#000 to every descendant of body to flip\n the page to inverted colours. That selector also catches our own overlays\n (.oks-reading-guide, .oks-overlay-effect), which would then paint a solid\n black band on top of the text and defeat their purpose. Restore the\n overlay-specific values here so they keep working in high-contrast mode. */\nbody.oks-a11y-contrast .oks-reading-guide {\n background-color: rgba(255, 255, 0, 0.25) !important;\n border-top-color: #ff0 !important;\n border-bottom-color: #ff0 !important;\n}\nbody.oks-a11y-contrast .oks-overlay-effect {\n background-color: transparent !important;\n}\n\n.oks-overlay-effect {\n position: fixed; top: 0; left: 0;\n width: 100%; height: 100%;\n pointer-events: none;\n z-index: 999990;\n display: none;\n backdrop-filter: grayscale(100%);\n}\n.oks-overlay-effect.is-active { display: block; }\n\n.oks-reading-guide {\n position: fixed; left: 0;\n width: 100%; height: 50px;\n background: rgba(255, 255, 0, 0.2);\n border-top: 3px solid red;\n border-bottom: 3px solid red;\n pointer-events: none;\n z-index: 2147483647;\n display: none;\n transform: translateY(-50%);\n}\nbody.oks-a11y-guide .oks-reading-guide { display: block; }\n\n/* Reading mask: dark overlay leaving a horizontal band lit around the cursor.\n --oks-mask-y is updated from JS on mousemove; band is \u00B190px around it. */\n.oks-reading-mask {\n position: fixed; top: 0; left: 0;\n width: 100vw; height: 100vh;\n pointer-events: none;\n z-index: 2147483646;\n background: rgba(0, 0, 0, 0.75);\n display: none;\n clip-path: polygon(\n 0 0, 100% 0,\n 100% calc(var(--oks-mask-y, 50vh) - 90px),\n 0 calc(var(--oks-mask-y, 50vh) - 90px),\n 0 calc(var(--oks-mask-y, 50vh) + 90px),\n 100% calc(var(--oks-mask-y, 50vh) + 90px),\n 100% 100%, 0 100%\n );\n}\nbody.oks-a11y-mask .oks-reading-mask { display: block; }\nbody.oks-a11y-contrast .oks-reading-mask { background-color: rgba(0, 0, 0, 0.85) !important; }\n\n/* Big targets: bump interactive hit-areas to WCAG 2.5.5 (44\u00D744 minimum).\n Only adjusts padding + min-* \u2014 never display, so layouts that rely on\n inline flow or grid placement survive. Exempts our own shadow-DOM host. */\nbody.oks-a11y-bigtargets a:not(oksigenia-access-panel):not(oksigenia-access-panel *),\nbody.oks-a11y-bigtargets button:not(oksigenia-access-panel):not(oksigenia-access-panel *),\nbody.oks-a11y-bigtargets [role=\"button\"]:not(oksigenia-access-panel):not(oksigenia-access-panel *),\nbody.oks-a11y-bigtargets input[type=\"checkbox\"]:not(oksigenia-access-panel):not(oksigenia-access-panel *),\nbody.oks-a11y-bigtargets input[type=\"radio\"]:not(oksigenia-access-panel):not(oksigenia-access-panel *),\nbody.oks-a11y-bigtargets summary:not(oksigenia-access-panel):not(oksigenia-access-panel *) {\n min-height: 44px !important;\n min-width: 44px !important;\n padding: 8px 12px !important;\n box-sizing: border-box !important;\n}\n";
160
+ /**
161
+ * Scoped-mode effect CSS (#scope). Emits only the controls that can be confined
162
+ * to a container by selector — the same rules as EFFECT_CSS but anchored to the
163
+ * scope element instead of `body`. The non-scopable effects (colour-blind, which
164
+ * is a `filter` on `<html>`; the full-screen overlays grayscale / reading guide /
165
+ * reading mask; and big cursor) are NOT here — a scoped instance auto-excludes
166
+ * those controls, so they never appear in a per-pane panel.
167
+ *
168
+ * Text size scopes as the container's `font-size`, so it scales `em`/inherited
169
+ * text inside the pane but not `rem` (which is always relative to `<html>` —
170
+ * there is no CSS way to anchor rem to a container). Hosts that want per-pane
171
+ * text scaling size that pane's text in `em`.
172
+ *
173
+ * `s` is a host-provided CSS selector (e.g. `#map-pane`), trusted like the
174
+ * effects-exclude value.
175
+ */
176
+ declare function scopedEffectCss(s: string): string;
108
177
 
109
- export { type BehaviorOptions, COLORBLIND_FILTERS_SVG, DEFAULT_STATE, EFFECT_CSS, type LocaleCode, PANEL_CSS, type PanelState, type Position, type Translation, type TriggerIcon, bindPanelBehavior, buildPanelHtml, getTranslation, isStateEmpty, loadState, positionCss, saveState, supportedLocales };
178
+ export { ALL_CONTROLS, type BehaviorOptions, COLORBLIND_FILTERS_SVG, type ControlId, DEFAULT_STATE, EFFECT_CSS, type LocaleCode, NON_SCOPABLE_CONTROLS, PANEL_CSS, PRESETS, PRESET_IDS, type PanelController, type PanelState, type Position, type PresetId, type Translation, type TriggerIcon, bindPanelBehavior, buildPanelHtml, getTranslation, isStateEmpty, loadState, positionCss, resolveEnabledControls, saveState, scopedControls, scopedEffectCss, supportedLocales };