You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

343 lines
9.4 KiB

<script lang="ts">
import { onMount } from 'svelte';
type TextSize = 'small' | 'medium' | 'large';
type LineSpacing = 'tight' | 'normal' | 'loose';
type ContentWidth = 'narrow' | 'medium' | 'wide';
let textSize = $state<TextSize>('medium');
let lineSpacing = $state<LineSpacing>('normal');
let contentWidth = $state<ContentWidth>('medium');
let showPreferences = $state(false);
onMount(() => {
loadPreferences();
applyPreferences();
});
function loadPreferences() {
if (typeof window === 'undefined') return;
const savedTextSize = localStorage.getItem('aitherboard_textSize') as TextSize | null;
const savedLineSpacing = localStorage.getItem('aitherboard_lineSpacing') as LineSpacing | null;
const savedContentWidth = localStorage.getItem('aitherboard_contentWidth') as ContentWidth | null;
if (savedTextSize) textSize = savedTextSize;
if (savedLineSpacing) lineSpacing = savedLineSpacing;
if (savedContentWidth) contentWidth = savedContentWidth;
}
function savePreferences() {
if (typeof window === 'undefined') return;
localStorage.setItem('aitherboard_textSize', textSize);
localStorage.setItem('aitherboard_lineSpacing', lineSpacing);
localStorage.setItem('aitherboard_contentWidth', contentWidth);
applyPreferences();
}
function applyPreferences() {
if (typeof document === 'undefined') return;
const root = document.documentElement;
// Text size - set both CSS variable and data attribute
const textSizes = {
small: '14px',
medium: '16px',
large: '18px'
};
root.style.setProperty('--text-size', textSizes[textSize]);
root.setAttribute('data-text-size', textSize);
// Line spacing - set both CSS variable and data attribute
const lineSpacings = {
tight: '1.4',
normal: '1.6',
loose: '1.8'
};
root.style.setProperty('--line-height', lineSpacings[lineSpacing]);
root.setAttribute('data-line-spacing', lineSpacing);
// Content width - set CSS variable and data attribute
const contentWidths = {
narrow: '600px',
medium: '800px',
wide: '1200px'
};
root.style.setProperty('--content-width', contentWidths[contentWidth]);
root.setAttribute('data-content-width', contentWidth);
}
$effect(() => {
savePreferences();
});
</script>
<button
onclick={() => (showPreferences = !showPreferences)}
class="preferences-toggle"
title="User Preferences"
aria-label="User Preferences"
>
Preferences
</button>
{#if showPreferences}
<div
class="preferences-modal"
onclick={(e) => e.target === e.currentTarget && (showPreferences = false)}
onkeydown={(e) => {
if (e.key === 'Escape') {
e.preventDefault();
showPreferences = false;
}
}}
role="dialog"
aria-modal="true"
aria-labelledby="preferences-title"
tabindex="-1"
>
<div class="preferences-content">
<div class="preferences-header">
<h2 id="preferences-title">User Preferences</h2>
<button onclick={() => (showPreferences = false)} class="close-button" aria-label="Close preferences">×</button>
</div>
<div class="preferences-body">
<fieldset class="preference-group">
<legend class="preference-label">Text Size</legend>
<div class="preference-options" role="group" aria-label="Text Size">
<button
onclick={() => (textSize = 'small')}
class="preference-option {textSize === 'small' ? 'active' : ''}"
aria-pressed={textSize === 'small'}
>
Small
</button>
<button
onclick={() => (textSize = 'medium')}
class="preference-option {textSize === 'medium' ? 'active' : ''}"
aria-pressed={textSize === 'medium'}
>
Medium
</button>
<button
onclick={() => (textSize = 'large')}
class="preference-option {textSize === 'large' ? 'active' : ''}"
aria-pressed={textSize === 'large'}
>
Large
</button>
</div>
</fieldset>
<fieldset class="preference-group">
<legend class="preference-label">Line Spacing</legend>
<div class="preference-options" role="group" aria-label="Line Spacing">
<button
onclick={() => (lineSpacing = 'tight')}
class="preference-option {lineSpacing === 'tight' ? 'active' : ''}"
aria-pressed={lineSpacing === 'tight'}
>
Tight
</button>
<button
onclick={() => (lineSpacing = 'normal')}
class="preference-option {lineSpacing === 'normal' ? 'active' : ''}"
aria-pressed={lineSpacing === 'normal'}
>
Normal
</button>
<button
onclick={() => (lineSpacing = 'loose')}
class="preference-option {lineSpacing === 'loose' ? 'active' : ''}"
aria-pressed={lineSpacing === 'loose'}
>
Loose
</button>
</div>
</fieldset>
<fieldset class="preference-group">
<legend class="preference-label">Content Width</legend>
<div class="preference-options" role="group" aria-label="Content Width">
<button
onclick={() => (contentWidth = 'narrow')}
class="preference-option {contentWidth === 'narrow' ? 'active' : ''}"
aria-pressed={contentWidth === 'narrow'}
>
Narrow
</button>
<button
onclick={() => (contentWidth = 'medium')}
class="preference-option {contentWidth === 'medium' ? 'active' : ''}"
aria-pressed={contentWidth === 'medium'}
>
Medium
</button>
<button
onclick={() => (contentWidth = 'wide')}
class="preference-option {contentWidth === 'wide' ? 'active' : ''}"
aria-pressed={contentWidth === 'wide'}
>
Wide
</button>
</div>
</fieldset>
</div>
</div>
</div>
{/if}
<style>
.preferences-toggle {
padding: 0.5rem 1rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1f2937);
cursor: pointer;
font-size: 0.875rem;
}
:global(.dark) .preferences-toggle {
background: var(--fog-dark-post, #1f2937);
border-color: var(--fog-dark-border, #374151);
color: var(--fog-dark-text, #f9fafb);
}
.preferences-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.preferences-content {
background: var(--fog-post, #ffffff);
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 8px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow: auto;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
:global(.dark) .preferences-content {
background: var(--fog-dark-post, #1f2937);
border-color: var(--fog-dark-border, #374151);
}
.preferences-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid var(--fog-border, #e5e7eb);
}
:global(.dark) .preferences-header {
border-bottom-color: var(--fog-dark-border, #374151);
}
.close-button {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: 0;
width: 2rem;
height: 2rem;
}
.preferences-body {
padding: 1rem;
}
.preference-group {
margin-bottom: 1.5rem;
border: none;
padding: 0;
}
.preference-label {
display: block;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--fog-text, #1f2937);
padding: 0;
}
:global(.dark) .preference-label {
color: var(--fog-dark-text, #f9fafb);
}
.preference-options {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
@media (max-width: 768px) {
.preferences-content {
width: 95%;
max-height: 90vh;
}
.preference-options {
gap: 0.375rem;
}
.preference-option {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
}
.preference-option {
padding: 0.5rem 1rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.25rem;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1f2937);
cursor: pointer;
transition: all 0.2s;
}
:global(.dark) .preference-option {
background: var(--fog-dark-post, #1f2937);
border-color: var(--fog-dark-border, #374151);
color: var(--fog-dark-text, #f9fafb);
}
.preference-option:hover {
background: var(--fog-highlight, #f3f4f6);
border-color: var(--fog-accent, #64748b);
}
:global(.dark) .preference-option:hover {
background: var(--fog-dark-highlight, #374151);
}
.preference-option.active {
background: var(--fog-accent, #64748b);
color: var(--fog-text, #475569);
border-color: var(--fog-accent, #64748b);
}
:global(.dark) .preference-option.active {
background: var(--fog-dark-accent, #64748b);
color: var(--fog-dark-text, #cbd5e1);
border-color: var(--fog-dark-accent, #64748b);
}
</style>