rizzo-css 0.0.41 → 0.0.42

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.
Files changed (54) hide show
  1. package/README.md +3 -3
  2. package/bin/rizzo-css.js +55 -15
  3. package/dist/fonts/DMMono/DMMono-Italic.ttf +0 -0
  4. package/dist/fonts/DMMono/DMMono-Regular.ttf +0 -0
  5. package/dist/fonts/DMMono/OFL.txt +93 -0
  6. package/dist/fonts/DMSans/DMSans-Italic-VariableFont_opsz,wght.ttf +0 -0
  7. package/dist/fonts/DMSans/DMSans-VariableFont_opsz,wght.ttf +0 -0
  8. package/dist/fonts/DMSans/OFL.txt +93 -0
  9. package/dist/fonts/DMSans/README.txt +136 -0
  10. package/dist/fonts/Outfit/OFL.txt +93 -0
  11. package/dist/fonts/Outfit/Outfit-VariableFont_wght.ttf +0 -0
  12. package/dist/fonts/Outfit/README.txt +71 -0
  13. package/dist/rizzo.min.css +6 -3
  14. package/package.json +1 -1
  15. package/scaffold/astro/FontSwitcher.astro +221 -0
  16. package/scaffold/astro/Modal.astro +14 -8
  17. package/scaffold/astro/Settings.astro +12 -49
  18. package/scaffold/astro/SoundEffects.astro +49 -0
  19. package/scaffold/astro-core/README-RIZZO.md +1 -1
  20. package/scaffold/astro-core/src/layouts/Layout.astro +1 -1
  21. package/scaffold/config/fonts.ts +12 -4
  22. package/scaffold/svelte/DocsSidebar.svelte +47 -0
  23. package/scaffold/svelte/FontSwitcher.svelte +180 -0
  24. package/scaffold/svelte/Modal.svelte +2 -0
  25. package/scaffold/svelte/Settings.svelte +18 -0
  26. package/scaffold/svelte/SoundEffects.svelte +43 -0
  27. package/scaffold/svelte-core/README-RIZZO.md +1 -1
  28. package/scaffold/vanilla/README-RIZZO.md +1 -1
  29. package/scaffold/vanilla/components/accordion.html +34 -0
  30. package/scaffold/vanilla/components/alert.html +34 -0
  31. package/scaffold/vanilla/components/avatar.html +34 -0
  32. package/scaffold/vanilla/components/badge.html +34 -0
  33. package/scaffold/vanilla/components/breadcrumb.html +34 -0
  34. package/scaffold/vanilla/components/button.html +34 -0
  35. package/scaffold/vanilla/components/cards.html +34 -0
  36. package/scaffold/vanilla/components/copy-to-clipboard.html +34 -0
  37. package/scaffold/vanilla/components/divider.html +34 -0
  38. package/scaffold/vanilla/components/dropdown.html +34 -0
  39. package/scaffold/vanilla/components/forms.html +34 -0
  40. package/scaffold/vanilla/components/icons.html +34 -0
  41. package/scaffold/vanilla/components/index.html +34 -0
  42. package/scaffold/vanilla/components/modal.html +34 -0
  43. package/scaffold/vanilla/components/navbar.html +34 -0
  44. package/scaffold/vanilla/components/pagination.html +34 -0
  45. package/scaffold/vanilla/components/progress-bar.html +34 -0
  46. package/scaffold/vanilla/components/search.html +34 -0
  47. package/scaffold/vanilla/components/settings.html +34 -0
  48. package/scaffold/vanilla/components/spinner.html +34 -0
  49. package/scaffold/vanilla/components/table.html +34 -0
  50. package/scaffold/vanilla/components/tabs.html +34 -0
  51. package/scaffold/vanilla/components/theme-switcher.html +34 -0
  52. package/scaffold/vanilla/components/toast.html +34 -0
  53. package/scaffold/vanilla/components/tooltip.html +34 -0
  54. package/scaffold/vanilla/index.html +34 -0
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  import ThemeSwitcher from './ThemeSwitcher.astro';
3
+ import FontSwitcher from './FontSwitcher.astro';
4
+ import SoundEffects from './SoundEffects.astro';
3
5
  import Close from './icons/Close.astro';
4
- import { FONT_PAIRS, FONT_PAIR_DEFAULT } from '../config/fonts';
5
6
 
6
7
  interface Props {
7
8
  open?: boolean;
@@ -63,34 +64,23 @@ const { open = false } = Astro.props;
63
64
  </div>
64
65
  </section>
65
66
 
66
- <!-- Font (pair: sans + mono) Section -->
67
+ <!-- Font (pair: sans + mono) Section — dropdown like Theme -->
67
68
  <section class="settings__section">
68
69
  <h3 class="settings__section-title">Font</h3>
69
70
  <div class="settings__control">
70
- <label for="font-pair-select" class="settings__label">
71
- <span class="settings__label-text">Font pair (sans + mono)</span>
72
- </label>
73
- <select
74
- id="font-pair-select"
75
- class="form-control"
76
- aria-label="Font pair"
77
- data-font-pair
78
- style="width: 100%;"
79
- >
80
- {FONT_PAIRS.map((pair) => (
81
- <option
82
- value={pair.value}
83
- data-sans={pair.sans}
84
- data-mono={pair.mono}
85
- >
86
- {pair.label}
87
- </option>
88
- ))}
89
- </select>
71
+ <FontSwitcher idPrefix="settings" />
90
72
  <p class="settings__help-text">Body text and code blocks use the selected pair.</p>
91
73
  </div>
92
74
  </section>
93
75
 
76
+ <!-- Sound Section -->
77
+ <section class="settings__section">
78
+ <h3 class="settings__section-title">Sound</h3>
79
+ <div class="settings__control">
80
+ <SoundEffects />
81
+ </div>
82
+ </section>
83
+
94
84
  <!-- Accessibility Section -->
95
85
  <section class="settings__section">
96
86
  <h3 class="settings__section-title">Accessibility</h3>
@@ -178,7 +168,6 @@ const { open = false } = Astro.props;
178
168
  const closeBtn = settings.querySelector('[data-settings-close]');
179
169
  const fontSizeSlider = settings.querySelector('[data-font-size-slider]');
180
170
  const fontSizeValue = settings.querySelector('[data-font-size-value]');
181
- const fontPairSelect = settings.querySelector('[data-font-pair]') as HTMLSelectElement | null;
182
171
  const reducedMotion = settings.querySelector('[data-reduced-motion]');
183
172
  const highContrast = settings.querySelector('[data-high-contrast]');
184
173
  const scrollbarStyleRadios = settings.querySelectorAll('[data-scrollbar-style]');
@@ -195,12 +184,6 @@ const { open = false } = Astro.props;
195
184
  slider.style.setProperty('--slider-progress', `${progress}%`);
196
185
  };
197
186
 
198
- // Apply font pair (sans + mono) to document
199
- const applyFontPair = (sans: string, mono: string) => {
200
- html.style.setProperty('--font-family', sans);
201
- html.style.setProperty('--font-family-mono', mono);
202
- };
203
-
204
187
  // Load saved settings
205
188
  const loadSettings = () => {
206
189
  const savedFontScale = localStorage.getItem('fontSizeScale');
@@ -214,15 +197,6 @@ const { open = false } = Astro.props;
214
197
  updateSliderProgress(fontSizeSlider);
215
198
  }
216
199
 
217
- const savedFontPair = localStorage.getItem('fontPair') || 'geist';
218
- if (fontPairSelect) {
219
- fontPairSelect.value = savedFontPair;
220
- const opt = fontPairSelect.options[fontPairSelect.selectedIndex];
221
- if (opt && opt.dataset.sans && opt.dataset.mono) {
222
- applyFontPair(opt.dataset.sans, opt.dataset.mono);
223
- }
224
- }
225
-
226
200
  const savedReducedMotion = localStorage.getItem('reducedMotion') === 'true';
227
201
  if (reducedMotion) {
228
202
  reducedMotion.checked = savedReducedMotion;
@@ -370,17 +344,6 @@ const { open = false } = Astro.props;
370
344
  });
371
345
  }
372
346
 
373
- // Font pair
374
- if (fontPairSelect) {
375
- fontPairSelect.addEventListener('change', () => {
376
- const opt = fontPairSelect.options[fontPairSelect.selectedIndex];
377
- if (opt && opt.dataset.sans && opt.dataset.mono) {
378
- applyFontPair(opt.dataset.sans, opt.dataset.mono);
379
- localStorage.setItem('fontPair', opt.value);
380
- }
381
- });
382
- }
383
-
384
347
  // Reduced motion
385
348
  if (reducedMotion) {
386
349
  reducedMotion.addEventListener('change', (e) => {
@@ -0,0 +1,49 @@
1
+ ---
2
+ /**
3
+ * Standalone toggle for "Play sound on click". Persists in localStorage (key: soundEffects).
4
+ * The actual click sound is played by a global script in Layout.astro when soundEffects === 'true'.
5
+ * Can be used standalone or inside Settings.
6
+ */
7
+ interface Props {
8
+ /** Optional class for the wrapper (e.g. settings__control). */
9
+ class?: string;
10
+ /** If true, show the help text below the checkbox. */
11
+ showHelp?: boolean;
12
+ }
13
+
14
+ const { class: className = '', showHelp = true } = Astro.props;
15
+ ---
16
+
17
+ <div class:list={[className]} data-sound-effects-wrapper>
18
+ <label class="settings__checkbox-label">
19
+ <input
20
+ type="checkbox"
21
+ class="settings__checkbox"
22
+ data-sound-effects
23
+ aria-label="Play sound on click"
24
+ />
25
+ <span>Play sound on click</span>
26
+ </label>
27
+ {showHelp && (
28
+ <p class="settings__help-text">Short click sound when you interact with buttons and links. Off by default.</p>
29
+ )}
30
+ </div>
31
+
32
+ <script>
33
+ function initSoundEffects() {
34
+ document.querySelectorAll<HTMLInputElement>('[data-sound-effects]').forEach((el) => {
35
+ if (el.hasAttribute('data-sound-effects-inited')) return;
36
+ el.setAttribute('data-sound-effects-inited', 'true');
37
+ el.checked = localStorage.getItem('soundEffects') === 'true';
38
+ el.addEventListener('change', () => {
39
+ localStorage.setItem('soundEffects', el.checked ? 'true' : 'false');
40
+ });
41
+ });
42
+ }
43
+ if (document.readyState === 'loading') {
44
+ document.addEventListener('DOMContentLoaded', initSoundEffects);
45
+ } else {
46
+ initSoundEffects();
47
+ }
48
+ document.addEventListener('astro:page-load', initSoundEffects);
49
+ </script>
@@ -14,7 +14,7 @@
14
14
  Design system · Vanilla · Astro · Svelte
15
15
  ```
16
16
 
17
- Astro project with Rizzo CSS. Scaffolded with `npx rizzo-css init --framework astro --template full` or **manual**. When you choose **Full**, the CLI copies all 29 Rizzo components (Button, Modal, Navbar, Search, Settings, ThemeSwitcher, etc.) into this project so they work together; **Manual** lets you pick which of those to include (all are pre-selected by default).
17
+ Astro project with Rizzo CSS. Scaffolded with `npx rizzo-css init --framework astro --template full` or **manual**. When you choose **Full**, the CLI copies all 31 Rizzo components (Button, Modal, Navbar, Search, Settings, ThemeSwitcher, etc.) into this project so they work together; **Manual** lets you pick which of those to include (all are pre-selected by default).
18
18
 
19
19
  ## Setup
20
20
 
@@ -16,7 +16,7 @@ const { title = '{{TITLE}}' } = Astro.props;
16
16
  (function(){try{
17
17
  var s=localStorage.getItem('theme');var d='{{DEFAULT_DARK}}';var l='{{DEFAULT_LIGHT}}';var initial=document.documentElement.getAttribute('data-theme');var r=!s?((initial&&initial!=='system')?initial:(matchMedia('(prefers-color-scheme: dark)').matches?d:l)):s==='system'?(matchMedia('(prefers-color-scheme: dark)').matches?d:l):s;document.documentElement.setAttribute('data-theme',r);
18
18
  var fs=localStorage.getItem('fontSizeScale');if(fs)document.documentElement.style.setProperty('--font-size-scale',fs);
19
- var fp=localStorage.getItem('fontPair')||'geist';if(fp==='geist'){document.documentElement.style.setProperty('--font-family','var(--font-family-geist-sans)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-geist-mono)');}else if(fp==='inter-jetbrains'){document.documentElement.style.setProperty('--font-family','var(--font-family-inter)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-jetbrains-mono)');}else if(fp==='ibm-plex'){document.documentElement.style.setProperty('--font-family','var(--font-family-ibm-plex-sans)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-ibm-plex-mono)');}else if(fp==='source'){document.documentElement.style.setProperty('--font-family','var(--font-family-source-sans-3)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-source-code-pro)');}
19
+ var fp=localStorage.getItem('fontPair')||'geist';if(fp==='geist'){document.documentElement.style.setProperty('--font-family','var(--font-family-geist-sans)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-geist-mono)');}else if(fp==='inter-jetbrains'){document.documentElement.style.setProperty('--font-family','var(--font-family-inter)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-jetbrains-mono)');}else if(fp==='ibm-plex'){document.documentElement.style.setProperty('--font-family','var(--font-family-ibm-plex-sans)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-ibm-plex-mono)');}else if(fp==='source'){document.documentElement.style.setProperty('--font-family','var(--font-family-source-sans-3)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-source-code-pro)');}else if(fp==='dm'){document.documentElement.style.setProperty('--font-family','var(--font-family-dm-sans)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-dm-mono)');}else if(fp==='outfit-jetbrains'){document.documentElement.style.setProperty('--font-family','var(--font-family-outfit)');document.documentElement.style.setProperty('--font-family-mono','var(--font-family-jetbrains-mono)');}
20
20
  }catch(e){}})();
21
21
  </script>
22
22
  <link rel="stylesheet" href="/css/rizzo.min.css" />
@@ -2,10 +2,6 @@
2
2
  * Font pairs (sans + mono) for the Settings font changer.
3
3
  * Each option sets both --font-family (body/UI) and --font-family-mono (code blocks, pre, kbd).
4
4
  * Used by Settings components and layout flash scripts to apply and persist the chosen pair.
5
- *
6
- * Optional future pairs (add @font-face + vars in variables.css, then add to FONT_PAIRS):
7
- * - dm: DM Sans + DM Mono (OFL; Google Fonts; geometric, friendly)
8
- * - outfit-jetbrains: Outfit + JetBrains Mono (OFL; geometric sans + coding mono)
9
5
  */
10
6
 
11
7
  export interface FontPairEntry {
@@ -44,6 +40,18 @@ export const FONT_PAIRS: FontPairEntry[] = [
44
40
  sans: 'var(--font-family-source-sans-3)',
45
41
  mono: 'var(--font-family-source-code-pro)',
46
42
  },
43
+ {
44
+ value: 'dm',
45
+ label: 'DM Sans + DM Mono',
46
+ sans: 'var(--font-family-dm-sans)',
47
+ mono: 'var(--font-family-dm-mono)',
48
+ },
49
+ {
50
+ value: 'outfit-jetbrains',
51
+ label: 'Outfit + JetBrains Mono',
52
+ sans: 'var(--font-family-outfit)',
53
+ mono: 'var(--font-family-jetbrains-mono)',
54
+ },
47
55
  ];
48
56
 
49
57
  export function getFontPairById(id: string): FontPairEntry | undefined {
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import { DOCS_NAV } from '../../config/docsNav';
3
+
4
+ interface Props {
5
+ /** Current URL pathname (e.g. $page.url.pathname or window.location.pathname). */
6
+ currentPath: string;
7
+ /** Path prefix for framework-specific links (e.g. /docs, /docs/svelte, /docs/vanilla). */
8
+ pathPrefix?: string;
9
+ }
10
+ let { currentPath, pathPrefix = '/docs' }: Props = $props();
11
+
12
+ function fullHref(link: { href: string; frameworkOnly?: boolean }): string {
13
+ const base = link.frameworkOnly ? pathPrefix : '/docs';
14
+ return `${base}/${link.href}`;
15
+ }
16
+
17
+ function isActive(link: { href: string; frameworkOnly?: boolean }): boolean {
18
+ const path = currentPath.replace(/\/$/, '');
19
+ const href = fullHref(link);
20
+ return path === href;
21
+ }
22
+ </script>
23
+
24
+ <aside id="docs-sidebar" class="docs-sidebar" aria-label="Documentation navigation" tabindex="0">
25
+ <nav class="docs-sidebar__nav">
26
+ {#each DOCS_NAV as group}
27
+ <div class="docs-sidebar__group">
28
+ <h2 class="docs-sidebar__group-label">{group.label}</h2>
29
+ <ul class="docs-sidebar__list">
30
+ {#each group.links as link}
31
+ {@const href = fullHref(link)}
32
+ {@const active = isActive(link)}
33
+ <li class="docs-sidebar__item">
34
+ <a
35
+ href={href}
36
+ class="docs-sidebar__link"
37
+ class:docs-sidebar__link--active={active}
38
+ >
39
+ {link.label}
40
+ </a>
41
+ </li>
42
+ {/each}
43
+ </ul>
44
+ </div>
45
+ {/each}
46
+ </nav>
47
+ </aside>
@@ -0,0 +1,180 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import ChevronDown from './icons/ChevronDown.svelte';
4
+ import { FONT_PAIRS, FONT_PAIR_DEFAULT } from '../../config/fonts';
5
+
6
+ interface Props {
7
+ /** Optional prefix for trigger/menu IDs when multiple FontSwitchers exist. */
8
+ idPrefix?: string;
9
+ }
10
+ let { idPrefix = '' }: Props = $props();
11
+
12
+ const triggerId = idPrefix ? `font-pair-trigger-${idPrefix}` : 'font-pair-trigger';
13
+ const menuId = idPrefix ? `font-pair-menu-${idPrefix}` : 'font-pair-menu';
14
+
15
+ let open = $state(false);
16
+ let selectedValue = $state(FONT_PAIR_DEFAULT);
17
+ let previewOption = $state<{ sans: string; mono: string } | null>(null);
18
+ let menuEl: HTMLElement | null = $state(null);
19
+ let triggerEl: HTMLElement | null = $state(null);
20
+
21
+ const selectedPair = $derived(FONT_PAIRS.find((p) => p.value === selectedValue) ?? FONT_PAIRS[0]);
22
+ const label = $derived(selectedPair?.label ?? 'Font');
23
+
24
+ function applyPair(sans: string, mono: string) {
25
+ if (typeof document === 'undefined') return;
26
+ document.documentElement.style.setProperty('--font-family', sans);
27
+ document.documentElement.style.setProperty('--font-family-mono', mono);
28
+ }
29
+
30
+ function selectPair(value: string) {
31
+ const pair = FONT_PAIRS.find((p) => p.value === value);
32
+ if (!pair) return;
33
+ selectedValue = value;
34
+ applyPair(pair.sans, pair.mono);
35
+ localStorage?.setItem('fontPair', value);
36
+ open = false;
37
+ previewOption = null;
38
+ triggerEl?.focus();
39
+ }
40
+
41
+ function setPreview(option: { sans: string; mono: string } | null) {
42
+ previewOption = option;
43
+ }
44
+
45
+ function addClickOutsideListener() {
46
+ const handler = (e: MouseEvent) => {
47
+ const target = e.target as Node;
48
+ if (menuEl && !menuEl.contains(target) && triggerEl && !triggerEl.contains(target)) {
49
+ open = false;
50
+ setPreview(null);
51
+ document.removeEventListener('click', handler);
52
+ }
53
+ };
54
+ setTimeout(() => document.addEventListener('click', handler), 0);
55
+ }
56
+
57
+ onMount(() => {
58
+ try {
59
+ const saved = typeof localStorage !== 'undefined' && localStorage.getItem ? localStorage.getItem('fontPair') || FONT_PAIR_DEFAULT : FONT_PAIR_DEFAULT;
60
+ const pair = FONT_PAIRS.find((p) => p.value === saved);
61
+ if (pair) {
62
+ selectedValue = saved;
63
+ applyPair(pair.sans, pair.mono);
64
+ }
65
+ } catch {
66
+ selectedValue = FONT_PAIR_DEFAULT;
67
+ }
68
+ });
69
+
70
+ function openMenu() {
71
+ open = true;
72
+ setPreview(FONT_PAIRS.find((p) => p.value === selectedValue) ?? null);
73
+ addClickOutsideListener();
74
+ }
75
+
76
+ function handleKeydown(e: KeyboardEvent) {
77
+ if (!open) {
78
+ if (e.key === 'Enter' || e.key === ' ') {
79
+ e.preventDefault();
80
+ openMenu();
81
+ } else if (e.key === 'ArrowDown') {
82
+ e.preventDefault();
83
+ openMenu();
84
+ }
85
+ return;
86
+ }
87
+ if (e.key === 'Escape') {
88
+ e.preventDefault();
89
+ open = false;
90
+ setPreview(null);
91
+ triggerEl?.focus();
92
+ }
93
+ }
94
+ </script>
95
+
96
+ <div class="font-switcher" data-font-switcher>
97
+ <button
98
+ type="button"
99
+ class="font-switcher__trigger"
100
+ aria-expanded={open}
101
+ aria-haspopup="true"
102
+ aria-controls={menuId}
103
+ aria-label="Select font pair"
104
+ id={triggerId}
105
+ bind:this={triggerEl}
106
+ onclick={() => {
107
+ if (open) {
108
+ open = false;
109
+ setPreview(null);
110
+ } else {
111
+ openMenu();
112
+ }
113
+ }}
114
+ onkeydown={handleKeydown}
115
+ >
116
+ <span class="font-switcher__label" data-font-pair-label>{label}</span>
117
+ <ChevronDown class="font-switcher__icon" width={16} height={16} />
118
+ </button>
119
+ <div
120
+ class="font-switcher__menu"
121
+ class:font-switcher__menu--open={open}
122
+ id={menuId}
123
+ role="menu"
124
+ aria-labelledby={triggerId}
125
+ aria-label="Font pair selection"
126
+ aria-orientation="vertical"
127
+ aria-hidden={!open}
128
+ tabindex="-1"
129
+ bind:this={menuEl}
130
+ onmouseleave={() => setPreview(null)}
131
+ >
132
+ <div class="font-switcher__menu-options">
133
+ {#each FONT_PAIRS as pair (pair.value)}
134
+ <div
135
+ class="font-switcher__option"
136
+ class:font-switcher__option--active={selectedValue === pair.value}
137
+ role="menuitemradio"
138
+ aria-checked={selectedValue === pair.value}
139
+ tabindex={open ? 0 : -1}
140
+ data-font-pair-value={pair.value}
141
+ data-font-pair-sans={pair.sans}
142
+ data-font-pair-mono={pair.mono}
143
+ data-font-pair-label={pair.label}
144
+ onmouseenter={() => setPreview({ sans: pair.sans, mono: pair.mono })}
145
+ onfocus={() => setPreview({ sans: pair.sans, mono: pair.mono })}
146
+ onclick={() => selectPair(pair.value)}
147
+ onkeydown={(e) => {
148
+ if (e.key === 'Enter' || e.key === ' ') {
149
+ e.preventDefault();
150
+ selectPair(pair.value);
151
+ }
152
+ }}
153
+ >
154
+ {pair.label}
155
+ </div>
156
+ {/each}
157
+ </div>
158
+ <div
159
+ class="font-switcher__preview"
160
+ data-font-preview
161
+ aria-hidden={!previewOption}
162
+ >
163
+ <div class="font-switcher__preview-title">Preview</div>
164
+ <div
165
+ class="font-switcher__preview-sample"
166
+ data-font-preview-sample
167
+ style={previewOption ? `font-family: ${previewOption.sans}` : ''}
168
+ >
169
+ Aa Bb Cc 012
170
+ </div>
171
+ <div
172
+ class="font-switcher__preview-mono"
173
+ data-font-preview-mono
174
+ style={previewOption ? `font-family: ${previewOption.mono}` : ''}
175
+ >
176
+ code
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
@@ -129,6 +129,7 @@
129
129
  data-modal-overlay
130
130
  aria-hidden={!open}
131
131
  inert={!open ? true : undefined}
132
+ hidden={open ? undefined : true}
132
133
  id="{modalId}-overlay"
133
134
  onclick={handleOverlayClick}
134
135
  role="presentation"
@@ -142,6 +143,7 @@
142
143
  aria-labelledby="{modalId}-title"
143
144
  aria-hidden={!open}
144
145
  inert={!open ? true : undefined}
146
+ hidden={open ? undefined : true}
145
147
  id={modalId}
146
148
  data-modal
147
149
  data-open={open || undefined}
@@ -11,6 +11,7 @@
11
11
  let fontSizeLabel = $state('100%');
12
12
  let fontSizeSlider = $state(1);
13
13
  let fontPairValue = $state(typeof localStorage !== 'undefined' ? localStorage.getItem('fontPair') || FONT_PAIR_DEFAULT : FONT_PAIR_DEFAULT);
14
+ let soundEffects = $state(typeof localStorage !== 'undefined' && localStorage.getItem('soundEffects') === 'true');
14
15
  let reducedMotion = $state(typeof localStorage !== 'undefined' && localStorage.getItem('reducedMotion') === 'true');
15
16
  let highContrast = $state(typeof localStorage !== 'undefined' && localStorage.getItem('highContrast') === 'true');
16
17
  let scrollbarStyle = $state((typeof localStorage !== 'undefined' ? localStorage.getItem('scrollbarStyle') || 'thin' : 'thin') as 'thin' | 'thick' | 'hidden');
@@ -60,12 +61,19 @@
60
61
  fontSizeLabel = `${Math.round(scale * 100)}%`;
61
62
  const savedPair = localStorage?.getItem('fontPair') || FONT_PAIR_DEFAULT;
62
63
  fontPairValue = savedPair;
64
+ soundEffects = localStorage?.getItem('soundEffects') === 'true';
63
65
  reducedMotion = localStorage?.getItem('reducedMotion') === 'true';
64
66
  highContrast = localStorage?.getItem('highContrast') === 'true';
65
67
  scrollbarStyle = (localStorage?.getItem('scrollbarStyle') || 'thin') as 'thin' | 'thick' | 'hidden';
66
68
  }
67
69
  });
68
70
 
71
+ function onSoundEffectsChange(e: Event) {
72
+ const checked = (e.target as HTMLInputElement).checked;
73
+ soundEffects = checked;
74
+ localStorage?.setItem('soundEffects', checked ? 'true' : 'false');
75
+ }
76
+
69
77
  function close() {
70
78
  openInternal = false;
71
79
  }
@@ -166,6 +174,16 @@
166
174
  <p class="settings__help-text">Body text and code blocks use the selected pair.</p>
167
175
  </div>
168
176
  </section>
177
+ <section class="settings__section">
178
+ <h3 class="settings__section-title">Sound</h3>
179
+ <div class="settings__control">
180
+ <label class="settings__checkbox-label">
181
+ <input type="checkbox" class="settings__checkbox" aria-label="Play sound on click" checked={soundEffects} onchange={onSoundEffectsChange} />
182
+ <span>Play sound on click</span>
183
+ </label>
184
+ <p class="settings__help-text">Short click sound when you interact with buttons and links. Off by default.</p>
185
+ </div>
186
+ </section>
169
187
  <section class="settings__section">
170
188
  <h3 class="settings__section-title">Accessibility</h3>
171
189
  <div class="settings__control">
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+
4
+ let soundEffects = $state(false);
5
+
6
+ interface Props {
7
+ showHelp?: boolean;
8
+ }
9
+ let { showHelp = true }: Props = $props();
10
+
11
+ onMount(() => {
12
+ try {
13
+ if (typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function') {
14
+ soundEffects = localStorage.getItem('soundEffects') === 'true';
15
+ }
16
+ } catch {
17
+ soundEffects = false;
18
+ }
19
+ });
20
+
21
+ function onChange(e: Event) {
22
+ const target = e.target as HTMLInputElement;
23
+ if (!target) return;
24
+ soundEffects = target.checked;
25
+ localStorage?.setItem('soundEffects', target.checked ? 'true' : 'false');
26
+ }
27
+ </script>
28
+
29
+ <div data-sound-effects-wrapper>
30
+ <label class="settings__checkbox-label">
31
+ <input
32
+ type="checkbox"
33
+ class="settings__checkbox"
34
+ aria-label="Play sound on click"
35
+ checked={soundEffects}
36
+ onchange={onChange}
37
+ />
38
+ <span>Play sound on click</span>
39
+ </label>
40
+ {#if showHelp}
41
+ <p class="settings__help-text">Short click sound when you interact with buttons and links. Off by default.</p>
42
+ {/if}
43
+ </div>
@@ -14,7 +14,7 @@
14
14
  Design system · Vanilla · Astro · Svelte
15
15
  ```
16
16
 
17
- SvelteKit project with Rizzo CSS. Scaffolded with `npx rizzo-css init --framework svelte --template full` or **manual**. When you choose **Full**, the CLI copies all 29 Rizzo components (Button, Modal, Navbar, Search, Settings, ThemeSwitcher, etc.) into this project so they work together; **Manual** lets you pick which of those to include (all are pre-selected by default).
17
+ SvelteKit project with Rizzo CSS. Scaffolded with `npx rizzo-css init --framework svelte --template full` or **manual**. When you choose **Full**, the CLI copies all 31 Rizzo components (Button, Modal, Navbar, Search, Settings, ThemeSwitcher, etc.) into this project so they work together; **Manual** lets you pick which of those to include (all are pre-selected by default).
18
18
 
19
19
  ## Setup
20
20
 
@@ -27,7 +27,7 @@ If you prefer to load CSS from a CDN instead of the local file, replace the `<li
27
27
  - `<link rel="stylesheet" href="https://unpkg.com/rizzo-css@latest/dist/rizzo.min.css" />`
28
28
  - Or jsDelivr: `https://cdn.jsdelivr.net/npm/rizzo-css@latest/dist/rizzo.min.css`
29
29
 
30
- (Replace `@latest` with a specific version, e.g. `@0.0.39`, in production.)
30
+ (Replace `@latest` with a specific version, e.g. `@0.0.42`, in production.)
31
31
 
32
32
  The CLI replaces placeholders in `index.html` (e.g. `{{DATA_THEME}}`, `{{TITLE}}`) when you run `rizzo-css init`. The theme selected during init is used on first load when you have no saved preference in the browser.
33
33
 
@@ -38,6 +38,12 @@
38
38
  } else if (savedFontPair === 'source') {
39
39
  document.documentElement.style.setProperty('--font-family', 'var(--font-family-source-sans-3)');
40
40
  document.documentElement.style.setProperty('--font-family-mono', 'var(--font-family-source-code-pro)');
41
+ } else if (savedFontPair === 'dm') {
42
+ document.documentElement.style.setProperty('--font-family', 'var(--font-family-dm-sans)');
43
+ document.documentElement.style.setProperty('--font-family-mono', 'var(--font-family-dm-mono)');
44
+ } else if (savedFontPair === 'outfit-jetbrains') {
45
+ document.documentElement.style.setProperty('--font-family', 'var(--font-family-outfit)');
46
+ document.documentElement.style.setProperty('--font-family-mono', 'var(--font-family-jetbrains-mono)');
41
47
  }
42
48
  if (localStorage.getItem('reducedMotion') === 'true') {
43
49
  document.documentElement.classList.add('reduced-motion');
@@ -185,6 +191,8 @@
185
191
  <option value="inter-jetbrains" data-sans="var(--font-family-inter)" data-mono="var(--font-family-jetbrains-mono)">Inter + JetBrains Mono</option>
186
192
  <option value="ibm-plex" data-sans="var(--font-family-ibm-plex-sans)" data-mono="var(--font-family-ibm-plex-mono)">IBM Plex Sans + Mono</option>
187
193
  <option value="source" data-sans="var(--font-family-source-sans-3)" data-mono="var(--font-family-source-code-pro)">Source Sans 3 + Source Code Pro</option>
194
+ <option value="dm" data-sans="var(--font-family-dm-sans)" data-mono="var(--font-family-dm-mono)">DM Sans + DM Mono</option>
195
+ <option value="outfit-jetbrains" data-sans="var(--font-family-outfit)" data-mono="var(--font-family-jetbrains-mono)">Outfit + JetBrains Mono</option>
188
196
  </select>
189
197
  <p class="settings__help-text">Body text and code blocks use the selected pair.</p>
190
198
  </div>
@@ -337,6 +345,19 @@
337
345
 
338
346
 
339
347
 
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
340
361
 
341
362
 
342
363
 
@@ -467,6 +488,19 @@
467
488
 
468
489
 
469
490
 
491
+
492
+
493
+
494
+
495
+
496
+
497
+
498
+
499
+
500
+
501
+
502
+
503
+
470
504
 
471
505
 
472
506