@oksigenia/access-panel 0.7.0 → 0.7.2

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,23 @@
1
1
  # @oksigenia/access-panel
2
2
 
3
+ ## 0.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Two fixes:
8
+
9
+ - **The open panel no longer overflows short desktop viewports.** The panel was capped at a flat `90vh` while sitting 80–100px from its anchored edge, so at 1280×720 or on a 1366×768 laptop a `bottom-*` panel ran past the top of the screen and took the header with the close button with it (`top-*` panels overflowed the bottom the same way). Each anchor now caps the panel to the room it actually has (`calc(100dvh - <offset> - 20px)`, with a `vh` fallback), and the content area scrolls inside. Mobile is unchanged: it stays full-screen.
10
+ - **Blocked storage no longer breaks the panel.** `typeof localStorage` sat outside the `try`, and when storage is denied (all cookies blocked, sandboxed iframe) merely reading `window.localStorage` throws — the component lost its behaviour and the trigger did nothing. The check now lives inside the guard; the panel works and simply doesn't persist settings.
11
+
12
+ ## 0.7.1
13
+
14
+ ### Patch Changes
15
+
16
+ - Two fixes surfaced while integrating at high text zoom:
17
+
18
+ - **The panel now scrolls inside its `max-height`.** The content area was a flex child without `flex:1 1 auto; min-height:0`, so on short viewports or at high text zoom it pushed the header (the close button) and footer (Reset All) off-screen — the panel became impossible to close. The content now scrolls and the header/footer stay pinned and reachable. This affected the classic panel too, not only scoped ones.
19
+ - **`scope="body"` no longer doubles the text-size zoom.** When the scope is the document root, the global zoom rule (`html:has(body.oks-zoom-N)`) already scales the page; the scoped instance no longer also emits its own `font-size` rule, which compounded to 2.25×. Scoping to a real container was unaffected.
20
+
3
21
  ## 0.7.0
4
22
 
5
23
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -167,7 +167,7 @@ interface PanelController {
167
167
  */
168
168
  declare function bindPanelBehavior(root: ShadowRoot, opts?: BehaviorOptions): PanelController;
169
169
 
170
- 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/* Screen-reader-only: keeps the category headings in the accessibility tree\n (flat scoped layout) while taking them out of the grid flow so they don't\n force a visual row break or occupy a cell. */\n.oks-sr-only {\n position: absolute !important;\n width: 1px; height: 1px;\n margin: 0; padding: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\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";
170
+ 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 flex: 0 0 auto;\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/* flex:1 1 auto + min-height:0 lets the content scroll inside the 90vh panel.\n Without min-height:0 a flex child won't shrink below its content, so the\n header (\u00D7) and footer (Reset) get pushed off-screen on short viewports or at\n high zoom \u2014 the panel becomes impossible to close. */\n.oks-access-content { flex: 1 1 auto; min-height: 0; 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/* Screen-reader-only: keeps the category headings in the accessibility tree\n (flat scoped layout) while taking them out of the grid flow so they don't\n force a visual row break or occupy a cell. */\n.oks-sr-only {\n position: absolute !important;\n width: 1px; height: 1px;\n margin: 0; padding: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\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 flex: 0 0 auto;\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";
171
171
  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\n/* Focus highlight colour is themeable via --oks-focus-color (default #005fcc):\n set it on :root for a dark theme so the highlight stays visible. The\n persistent \"all interactive\" dashed derives a 45% tint from it; the glow can\n be overridden on its own with --oks-focus-glow. */\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 color-mix(in srgb, var(--oks-focus-color, #005fcc) 45%, transparent) !important;\n outline-offset: 2px !important;\n}\nbody.oks-a11y-focus *:focus-visible {\n outline: 3px solid var(--oks-focus-color, #005fcc) !important;\n outline-offset: 3px !important;\n box-shadow: 0 0 0 6px var(--oks-focus-glow, color-mix(in srgb, var(--oks-focus-color, #005fcc) 25%, transparent)) !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";
172
172
  /**
173
173
  * Scoped-mode effect CSS (#scope). Emits only the controls that can be confined
package/dist/index.js CHANGED
@@ -256,29 +256,32 @@ function escapeAttr(s) {
256
256
  function positionRules(position) {
257
257
  switch (position) {
258
258
  case "top-left":
259
- return { wrap: "top: 20px; left: 20px;", panel: "top: 80px; left: 20px;" };
259
+ return { wrap: "top: 20px; left: 20px;", panel: "top: 80px; left: 20px;", reserve: 100 };
260
260
  case "top-center":
261
- return { wrap: "top: 20px; left: 50%; transform: translateX(-50%);", panel: "top: 80px; left: 50%; transform: translateX(-50%);" };
261
+ return { wrap: "top: 20px; left: 50%; transform: translateX(-50%);", panel: "top: 80px; left: 50%; transform: translateX(-50%);", reserve: 100 };
262
262
  case "top-right":
263
- return { wrap: "top: 20px; right: 20px;", panel: "top: 80px; right: 20px;" };
263
+ return { wrap: "top: 20px; right: 20px;", panel: "top: 80px; right: 20px;", reserve: 100 };
264
264
  case "mid-left":
265
- return { wrap: "top: 50%; left: 20px; transform: translateY(-50%);", panel: "top: 50%; left: 90px; transform: translateY(-50%);" };
265
+ return { wrap: "top: 50%; left: 20px; transform: translateY(-50%);", panel: "top: 50%; left: 90px; transform: translateY(-50%);", reserve: 40 };
266
266
  case "mid-center":
267
- return { wrap: "top: 50%; left: 50%; transform: translate(-50%, -50%);", panel: "top: 50%; left: 50%; transform: translate(-50%, -50%);" };
267
+ return { wrap: "top: 50%; left: 50%; transform: translate(-50%, -50%);", panel: "top: 50%; left: 50%; transform: translate(-50%, -50%);", reserve: 40 };
268
268
  case "mid-right":
269
- return { wrap: "top: 50%; right: 20px; transform: translateY(-50%);", panel: "top: 50%; right: 90px; transform: translateY(-50%);" };
269
+ return { wrap: "top: 50%; right: 20px; transform: translateY(-50%);", panel: "top: 50%; right: 90px; transform: translateY(-50%);", reserve: 40 };
270
270
  case "bottom-left":
271
- return { wrap: "bottom: 20px; left: 20px;", panel: "bottom: 100px; left: 20px;" };
271
+ return { wrap: "bottom: 20px; left: 20px;", panel: "bottom: 100px; left: 20px;", reserve: 120 };
272
272
  case "bottom-center":
273
- return { wrap: "bottom: 20px; left: 50%; transform: translateX(-50%);", panel: "bottom: 100px; left: 50%; transform: translateX(-50%);" };
273
+ return { wrap: "bottom: 20px; left: 50%; transform: translateX(-50%);", panel: "bottom: 100px; left: 50%; transform: translateX(-50%);", reserve: 120 };
274
274
  case "bottom-right":
275
- return { wrap: "bottom: 20px; right: 20px;", panel: "bottom: 100px; right: 20px;" };
275
+ return { wrap: "bottom: 20px; right: 20px;", panel: "bottom: 100px; right: 20px;", reserve: 120 };
276
276
  }
277
277
  }
278
+ function panelMaxHeight(reserve) {
279
+ return `max-height: calc(100vh - ${reserve}px); max-height: calc(100dvh - ${reserve}px);`;
280
+ }
278
281
  function positionCss(position, mobile) {
279
282
  const d = positionRules(position);
280
283
  let css = `.oks-access-wrapper { ${d.wrap} }`;
281
- css += `@media (min-width: 769px) { .oks-access-panel { ${d.panel} } }`;
284
+ css += `@media (min-width: 769px) { .oks-access-panel { ${d.panel} ${panelMaxHeight(d.reserve)} } }`;
282
285
  if (mobile && mobile !== position) {
283
286
  const m = positionRules(mobile);
284
287
  css += `@media (max-width: 768px) { .oks-access-wrapper { top: auto; right: auto; bottom: auto; left: auto; transform: none; ${m.wrap} } }`;
@@ -307,8 +310,8 @@ var DEFAULT_STATE = Object.freeze({
307
310
  bigTargets: false
308
311
  });
309
312
  function loadState(key) {
310
- if (typeof localStorage === "undefined") return { ...DEFAULT_STATE };
311
313
  try {
314
+ if (typeof localStorage === "undefined") return { ...DEFAULT_STATE };
312
315
  const raw = localStorage.getItem(key);
313
316
  if (!raw) return { ...DEFAULT_STATE };
314
317
  const parsed = JSON.parse(raw);
@@ -319,8 +322,8 @@ function loadState(key) {
319
322
  }
320
323
  }
321
324
  function saveState(key, state) {
322
- if (typeof localStorage === "undefined") return;
323
325
  try {
326
+ if (typeof localStorage === "undefined") return;
324
327
  const out = {};
325
328
  for (const [k, v] of Object.entries(state)) {
326
329
  if (typeof v === "number" && v > 0) out[k] = v;
@@ -910,6 +913,7 @@ var PANEL_CSS = `
910
913
  }
911
914
  .oks-access-panel.is-open { opacity: 1; pointer-events: all; }
912
915
  .oks-access-header {
916
+ flex: 0 0 auto;
913
917
  padding: 15px 20px;
914
918
  border-bottom: 1px solid #eee;
915
919
  display: flex;
@@ -933,7 +937,11 @@ var PANEL_CSS = `
933
937
  }
934
938
  .oks-access-close:hover { background: #e0e0e0; border-color: #ccc; }
935
939
  .oks-access-close svg { width: 24px; height: 24px; stroke-width: 2.5px; }
936
- .oks-access-content { padding: 0 20px 20px; overflow-y: auto; }
940
+ /* flex:1 1 auto + min-height:0 lets the content scroll inside the 90vh panel.
941
+ Without min-height:0 a flex child won't shrink below its content, so the
942
+ header (\xD7) and footer (Reset) get pushed off-screen on short viewports or at
943
+ high zoom \u2014 the panel becomes impossible to close. */
944
+ .oks-access-content { flex: 1 1 auto; min-height: 0; padding: 0 20px 20px; overflow-y: auto; }
937
945
  .oks-access-title {
938
946
  margin: 10px 0 5px;
939
947
  font-size: 11px;
@@ -1034,6 +1042,7 @@ var PANEL_CSS = `
1034
1042
  .oks-access-opt[data-level="4"] .oks-levels span:nth-child(-n+4) { background: #000; }
1035
1043
  .oks-access-opt:hover .oks-levels span { background: #555; }
1036
1044
  .oks-access-footer {
1045
+ flex: 0 0 auto;
1037
1046
  padding: 12px 20px;
1038
1047
  border-top: 1px solid #eee;
1039
1048
  text-align: center;
@@ -1240,12 +1249,14 @@ body.oks-a11y-bigtargets summary:not(oksigenia-access-panel):not(oksigenia-acces
1240
1249
  }
1241
1250
  `;
1242
1251
  function scopedEffectCss(s) {
1243
- return `
1252
+ const isRoot = s === "body" || s === "html" || s === ":root";
1253
+ const zoom = isRoot ? "" : `
1244
1254
  ${s}.oks-zoom-1 { font-size: 110% !important; }
1245
1255
  ${s}.oks-zoom-2 { font-size: 120% !important; }
1246
1256
  ${s}.oks-zoom-3 { font-size: 135% !important; }
1247
1257
  ${s}.oks-zoom-4 { font-size: 150% !important; }
1248
-
1258
+ `;
1259
+ return `${zoom}
1249
1260
  ${s}.oks-lh-1 * { line-height: 1.6 !important; }
1250
1261
  ${s}.oks-lh-2 * { line-height: 1.9 !important; }
1251
1262
  ${s}.oks-lh-3 * { line-height: 2.2 !important; }