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.
- package/README.md +3 -3
- package/bin/rizzo-css.js +55 -15
- package/dist/fonts/DMMono/DMMono-Italic.ttf +0 -0
- package/dist/fonts/DMMono/DMMono-Regular.ttf +0 -0
- package/dist/fonts/DMMono/OFL.txt +93 -0
- package/dist/fonts/DMSans/DMSans-Italic-VariableFont_opsz,wght.ttf +0 -0
- package/dist/fonts/DMSans/DMSans-VariableFont_opsz,wght.ttf +0 -0
- package/dist/fonts/DMSans/OFL.txt +93 -0
- package/dist/fonts/DMSans/README.txt +136 -0
- package/dist/fonts/Outfit/OFL.txt +93 -0
- package/dist/fonts/Outfit/Outfit-VariableFont_wght.ttf +0 -0
- package/dist/fonts/Outfit/README.txt +71 -0
- package/dist/rizzo.min.css +6 -3
- package/package.json +1 -1
- package/scaffold/astro/FontSwitcher.astro +221 -0
- package/scaffold/astro/Modal.astro +14 -8
- package/scaffold/astro/Settings.astro +12 -49
- package/scaffold/astro/SoundEffects.astro +49 -0
- package/scaffold/astro-core/README-RIZZO.md +1 -1
- package/scaffold/astro-core/src/layouts/Layout.astro +1 -1
- package/scaffold/config/fonts.ts +12 -4
- package/scaffold/svelte/DocsSidebar.svelte +47 -0
- package/scaffold/svelte/FontSwitcher.svelte +180 -0
- package/scaffold/svelte/Modal.svelte +2 -0
- package/scaffold/svelte/Settings.svelte +18 -0
- package/scaffold/svelte/SoundEffects.svelte +43 -0
- package/scaffold/svelte-core/README-RIZZO.md +1 -1
- package/scaffold/vanilla/README-RIZZO.md +1 -1
- package/scaffold/vanilla/components/accordion.html +34 -0
- package/scaffold/vanilla/components/alert.html +34 -0
- package/scaffold/vanilla/components/avatar.html +34 -0
- package/scaffold/vanilla/components/badge.html +34 -0
- package/scaffold/vanilla/components/breadcrumb.html +34 -0
- package/scaffold/vanilla/components/button.html +34 -0
- package/scaffold/vanilla/components/cards.html +34 -0
- package/scaffold/vanilla/components/copy-to-clipboard.html +34 -0
- package/scaffold/vanilla/components/divider.html +34 -0
- package/scaffold/vanilla/components/dropdown.html +34 -0
- package/scaffold/vanilla/components/forms.html +34 -0
- package/scaffold/vanilla/components/icons.html +34 -0
- package/scaffold/vanilla/components/index.html +34 -0
- package/scaffold/vanilla/components/modal.html +34 -0
- package/scaffold/vanilla/components/navbar.html +34 -0
- package/scaffold/vanilla/components/pagination.html +34 -0
- package/scaffold/vanilla/components/progress-bar.html +34 -0
- package/scaffold/vanilla/components/search.html +34 -0
- package/scaffold/vanilla/components/settings.html +34 -0
- package/scaffold/vanilla/components/spinner.html +34 -0
- package/scaffold/vanilla/components/table.html +34 -0
- package/scaffold/vanilla/components/tabs.html +34 -0
- package/scaffold/vanilla/components/theme-switcher.html +34 -0
- package/scaffold/vanilla/components/toast.html +34 -0
- package/scaffold/vanilla/components/tooltip.html +34 -0
- 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
|
-
<
|
|
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
|
|
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" />
|
package/scaffold/config/fonts.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|