atagen

malice

public
Star0
Watch0
5e9271c9b8f197f3df3835ba5f01a4428aac18c8
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/src/lib/appearance.ts

df3c0f2b3da4atagen2026-10-22 13:07 UTC1
import { writable, type Writable } from 'svelte/store';
df3c0f2b3da4atagen2026-10-22 13:07 UTC2
df3c0f2b3da4atagen2026-10-22 13:07 UTC3
export const MESSAGE_SPACING_MIN = 0;
df3c0f2b3da4atagen2026-10-22 13:07 UTC4
export const MESSAGE_SPACING_MAX = 16;
df3c0f2b3da4atagen2026-10-22 13:07 UTC5
export const MESSAGE_SPACING_DEFAULT = 3;
df3c0f2b3da4atagen2026-10-22 13:07 UTC6
df3c0f2b3da4atagen2026-10-22 13:07 UTC7
function persisted<T>(key: string, fallback: T, valid: (v: unknown) => v is T): Writable<T> {
df3c0f2b3da4atagen2026-10-22 13:07 UTC8
  let initial = fallback;
df3c0f2b3da4atagen2026-10-22 13:07 UTC9
  try {
df3c0f2b3da4atagen2026-10-22 13:07 UTC10
    const raw = localStorage.getItem(key);
df3c0f2b3da4atagen2026-10-22 13:07 UTC11
    if (raw !== null) {
df3c0f2b3da4atagen2026-10-22 13:07 UTC12
      const parsed: unknown = JSON.parse(raw);
df3c0f2b3da4atagen2026-10-22 13:07 UTC13
      if (valid(parsed)) initial = parsed;
df3c0f2b3da4atagen2026-10-22 13:07 UTC14
    }
df3c0f2b3da4atagen2026-10-22 13:07 UTC15
  } catch {}
df3c0f2b3da4atagen2026-10-22 13:07 UTC16
  const store = writable(initial);
df3c0f2b3da4atagen2026-10-22 13:07 UTC17
  store.subscribe((value) => {
df3c0f2b3da4atagen2026-10-22 13:07 UTC18
    try {
df3c0f2b3da4atagen2026-10-22 13:07 UTC19
      localStorage.setItem(key, JSON.stringify(value));
df3c0f2b3da4atagen2026-10-22 13:07 UTC20
    } catch {}
df3c0f2b3da4atagen2026-10-22 13:07 UTC21
  });
df3c0f2b3da4atagen2026-10-22 13:07 UTC22
  return store;
df3c0f2b3da4atagen2026-10-22 13:07 UTC23
}
df3c0f2b3da4atagen2026-10-22 13:07 UTC24
df3c0f2b3da4atagen2026-10-22 13:07 UTC25
const isBool = (v: unknown): v is boolean => typeof v === 'boolean';
df3c0f2b3da4atagen2026-10-22 13:07 UTC26
const isSpacing = (v: unknown): v is number =>
df3c0f2b3da4atagen2026-10-22 13:07 UTC27
  typeof v === 'number' && Number.isFinite(v) && v >= MESSAGE_SPACING_MIN && v <= MESSAGE_SPACING_MAX;
df3c0f2b3da4atagen2026-10-22 13:07 UTC28
df3c0f2b3da4atagen2026-10-22 13:07 UTC29
export const navFolded = persisted('malice.navFolded', false, isBool);
e15025643a20atagen2026-10-24 06:51 UTC30
export const railFolded = persisted('malice.railFolded', false, isBool);
6d6a4c34bdc1atagen2026-10-24 06:51 UTC31
export const markdownEnabled = persisted('malice.markdown', true, isBool);
df3c0f2b3da4atagen2026-10-22 13:07 UTC32
export const messageSpacing = persisted('malice.messageSpacing', MESSAGE_SPACING_DEFAULT, isSpacing);
df3c0f2b3da4atagen2026-10-22 13:07 UTC33
df3c0f2b3da4atagen2026-10-22 13:07 UTC34
export const LINE_HEIGHT_MIN = 1.1;
df3c0f2b3da4atagen2026-10-22 13:07 UTC35
export const LINE_HEIGHT_MAX = 2;
df3c0f2b3da4atagen2026-10-22 13:07 UTC36
export const LINE_HEIGHT_DEFAULT = 1.5;
df3c0f2b3da4atagen2026-10-22 13:07 UTC37
df3c0f2b3da4atagen2026-10-22 13:07 UTC38
const isLineHeight = (v: unknown): v is number =>
df3c0f2b3da4atagen2026-10-22 13:07 UTC39
  typeof v === 'number' && Number.isFinite(v) && v >= LINE_HEIGHT_MIN && v <= LINE_HEIGHT_MAX;
df3c0f2b3da4atagen2026-10-22 13:07 UTC40
df3c0f2b3da4atagen2026-10-22 13:07 UTC41
export const lineHeight = persisted('malice.lineHeight', LINE_HEIGHT_DEFAULT, isLineHeight);
df3c0f2b3da4atagen2026-10-22 13:07 UTC42
705b1e1062c2atagen2026-10-23 09:05 UTC43
const isStringRecord = (v: unknown): v is Record<string, string> =>
705b1e1062c2atagen2026-10-23 09:05 UTC44
  typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every((x) => typeof x === 'string');
705b1e1062c2atagen2026-10-23 09:05 UTC45
705b1e1062c2atagen2026-10-23 09:05 UTC46
export const lastSpaceRooms = persisted<Record<string, string>>('malice.lastSpaceRooms', {}, isStringRecord);
705b1e1062c2atagen2026-10-23 09:05 UTC47
df3c0f2b3da4atagen2026-10-22 13:07 UTC48
export const THEMES = [
df3c0f2b3da4atagen2026-10-22 13:07 UTC49
  { id: 'system', label: 'Match system' },
df3c0f2b3da4atagen2026-10-22 13:07 UTC50
  { id: 'dark', label: 'Dark' },
df3c0f2b3da4atagen2026-10-22 13:07 UTC51
  { id: 'light', label: 'Light' },
df3c0f2b3da4atagen2026-10-22 13:07 UTC52
  { id: 'black', label: 'Black' },
df3c0f2b3da4atagen2026-10-22 13:07 UTC53
  { id: 'nord', label: 'Nord' }
df3c0f2b3da4atagen2026-10-22 13:07 UTC54
] as const;
df3c0f2b3da4atagen2026-10-22 13:07 UTC55
df3c0f2b3da4atagen2026-10-22 13:07 UTC56
export type ThemeId = (typeof THEMES)[number]['id'];
df3c0f2b3da4atagen2026-10-22 13:07 UTC57
df3c0f2b3da4atagen2026-10-22 13:07 UTC58
const isTheme = (v: unknown): v is ThemeId => THEMES.some((t) => t.id === v);
df3c0f2b3da4atagen2026-10-22 13:07 UTC59
const isAccent = (v: unknown): v is string | null => v === null || (typeof v === 'string' && /^#[0-9a-f]{6}$/i.test(v));
df3c0f2b3da4atagen2026-10-22 13:07 UTC60
df3c0f2b3da4atagen2026-10-22 13:07 UTC61
export const theme = persisted<ThemeId>('malice.theme', 'dark', isTheme);
df3c0f2b3da4atagen2026-10-22 13:07 UTC62
export const accent = persisted<string | null>('malice.accent', null, isAccent);
df3c0f2b3da4atagen2026-10-22 13:07 UTC63
df3c0f2b3da4atagen2026-10-22 13:07 UTC64
if (typeof document !== 'undefined') {
df3c0f2b3da4atagen2026-10-22 13:07 UTC65
  const root = document.documentElement;
df3c0f2b3da4atagen2026-10-22 13:07 UTC66
  const prefersLight = window.matchMedia('(prefers-color-scheme: light)');
df3c0f2b3da4atagen2026-10-22 13:07 UTC67
  let current: ThemeId = 'system';
df3c0f2b3da4atagen2026-10-22 13:07 UTC68
  const apply = () => {
df3c0f2b3da4atagen2026-10-22 13:07 UTC69
    root.dataset.theme = current === 'system' ? (prefersLight.matches ? 'light' : 'dark') : current;
df3c0f2b3da4atagen2026-10-22 13:07 UTC70
  };
df3c0f2b3da4atagen2026-10-22 13:07 UTC71
  theme.subscribe((t) => {
df3c0f2b3da4atagen2026-10-22 13:07 UTC72
    current = t;
df3c0f2b3da4atagen2026-10-22 13:07 UTC73
    apply();
df3c0f2b3da4atagen2026-10-22 13:07 UTC74
  });
df3c0f2b3da4atagen2026-10-22 13:07 UTC75
  prefersLight.addEventListener('change', apply);
df3c0f2b3da4atagen2026-10-22 13:07 UTC76
  accent.subscribe((a) => {
df3c0f2b3da4atagen2026-10-22 13:07 UTC77
    if (a) root.style.setProperty('--accent', a);
df3c0f2b3da4atagen2026-10-22 13:07 UTC78
    else root.style.removeProperty('--accent');
df3c0f2b3da4atagen2026-10-22 13:07 UTC79
  });
df3c0f2b3da4atagen2026-10-22 13:07 UTC80
}