feat: add Backspace design prototype and rebrand from Opencord

- Add Backspace-design-prototype.html: finalized "Aether Drift" design
  language with warm matte surfaces and subtle frosted glass accents
- Update CLAUDE.md with DESIGN SYSTEM section and remove Discord clone references
- Rename all Opencord references to Backspace across the full codebase
- Archive outdated design experiments and Discord-specific assets
- Add science-backed accessibility fallback (prefers-reduced-transparency)
This commit is contained in:
Jannis Braun
2026-03-01 21:26:57 +01:00
parent aeb38a714a
commit e219229b63
58 changed files with 547 additions and 239 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ export function Embed({ url }: EmbedProps) {
// Simple fetch from our new API
fetch(`/api/utils/metadata?url=${encodeURIComponent(url)}`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('opencord_token')}`
'Authorization': `Bearer ${localStorage.getItem('backspace_token')}`
}
})
.then(res => res.json())
@@ -5,7 +5,7 @@ import { MemoryRouter } from 'react-router-dom';
import { FriendsPage } from './FriendsPage';
import { useSocialStore } from '../../stores/socialStore';
import { useServerStore } from '../../stores/serverStore';
import type { Friend, FriendRequest } from '@opencord/shared';
import type { Friend, FriendRequest } from '@backspace/shared';
// Mock the api module
vi.mock('../../api/client', () => ({
@@ -6,7 +6,7 @@ import { Avatar } from '../ui/Avatar';
import { MemberListToggleButton } from '../layout/MemberListToggleButton';
import { LoadingSpinner } from '../ui/LoadingSpinner';
import { api } from '../../api/client';
import type { Friend, FriendRequest } from '@opencord/shared';
import type { Friend, FriendRequest } from '@backspace/shared';
type Tab = 'online' | 'all' | 'pending' | 'add';
@@ -143,7 +143,7 @@ export function FriendsPage() {
return (
<div className="flex-1 p-8">
<h2 className="text-base font-bold text-discord-text-primary uppercase mb-2">Add Friend</h2>
<p className="text-sm text-discord-text-muted mb-4">You can add friends with their Opencord username.</p>
<p className="text-sm text-discord-text-muted mb-4">You can add friends with their Backspace username.</p>
<form onSubmit={handleAddFriend} className="relative mb-8">
<input
type="text"
@@ -1,5 +1,5 @@
import React, { useMemo, useRef, useEffect } from 'react';
import type { MemberWithUser } from '@opencord/shared';
import type { MemberWithUser } from '@backspace/shared';
import { Avatar } from '../ui/Avatar';
import { useServerStore } from '../../stores/serverStore';
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import type { MessageWithUser } from '@opencord/shared';
import type { MessageWithUser } from '@backspace/shared';
import { MarkdownRenderer } from './MarkdownRenderer';
import { Avatar } from '../ui/Avatar';
import { ContextMenu } from '../ui/ContextMenu';
@@ -4,7 +4,7 @@ import { isDmChannel, useServerStore } from '../../stores/serverStore';
import { wsSend } from '../../hooks/useWebSocket';
import { api } from '../../api/client';
import { MentionPopover } from './MentionPopover';
import type { MemberWithUser } from '@opencord/shared';
import type { MemberWithUser } from '@backspace/shared';
interface MessageInputProps {
channelId: string;
@@ -6,7 +6,7 @@ import { useAuthStore } from '../../stores/authStore';
import { useSocialStore } from '../../stores/socialStore';
import { Avatar } from '../ui/Avatar';
import { LoadingSpinner } from '../ui/LoadingSpinner';
import type { MessageWithUser } from '@opencord/shared';
import type { MessageWithUser } from '@backspace/shared';
const EMPTY_MESSAGES: MessageWithUser[] = [];
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo } from 'react';
import { useSocialStore } from '../../stores/socialStore';
import { useUIStore } from '../../stores/uiStore';
import { Avatar } from '../ui/Avatar';
import type { Friend } from '@opencord/shared';
import type { Friend } from '@backspace/shared';
export function ActivityPanel() {
const friends = useSocialStore((s) => s.friends);
@@ -53,12 +53,12 @@ export function AppLayout() {
// MutationObserver: neutralize rogue LiveKit <audio> elements that bypass our Web Audio pipeline.
// LiveKit can re-attach hidden <audio> elements after .detach(), causing full-volume playback
// that ignores our volume/mute controls. Any <audio> without data-opencord is immediately killed.
// that ignores our volume/mute controls. Any <audio> without data-backspace is immediately killed.
useEffect(() => {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
mutation.addedNodes.forEach((node) => {
if (node instanceof HTMLAudioElement && !node.dataset.opencord) {
if (node instanceof HTMLAudioElement && !node.dataset.backspace) {
node.muted = true;
node.volume = 0;
node.pause();
@@ -210,7 +210,7 @@ export function AppLayout() {
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
<p className="text-discord-text-muted">Loading Opencord...</p>
<p className="text-discord-text-muted">Loading Backspace...</p>
</div>
</div>
);
@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import type { MemberWithUser } from '@opencord/shared';
import type { MemberWithUser } from '@backspace/shared';
import { useServerStore } from '../../stores/serverStore';
import { useUIStore } from '../../stores/uiStore';
import { Avatar } from '../ui/Avatar';
@@ -4,7 +4,7 @@ import { Avatar } from '../ui/Avatar';
import { useUIStore } from '../../stores/uiStore';
import { useServerStore } from '../../stores/serverStore';
import { api } from '../../api/client';
import type { User } from '@opencord/shared';
import type { User } from '@backspace/shared';
export function AddDmMemberModal() {
const [query, setQuery] = useState('');
@@ -5,7 +5,7 @@ import { Avatar } from '../ui/Avatar';
import { useUIStore } from '../../stores/uiStore';
import { useServerStore } from '../../stores/serverStore';
import { api } from '../../api/client';
import type { User } from '@opencord/shared';
import type { User } from '@backspace/shared';
export function NewDmModal() {
const [query, setQuery] = useState('');
@@ -8,7 +8,7 @@ import { Avatar } from '../ui/Avatar';
import { api } from '../../api/client';
import { useNavigate } from 'react-router-dom';
import { hasPermissionBit, PermissionBits } from '../../utils/permissions';
import type { InstanceStreamingLimits } from '@opencord/shared';
import type { InstanceStreamingLimits } from '@backspace/shared';
const VALID_RESOLUTIONS = [540, 720, 1080] as const;
const VALID_FRAMERATES = [30, 45, 60] as const;
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react';
import type { User } from '@opencord/shared';
import type { User } from '@backspace/shared';
import { useUIStore } from '../../stores/uiStore';
interface AvatarProps {
@@ -1,6 +1,6 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import type { User } from '@opencord/shared';
import type { User } from '@backspace/shared';
import { Avatar } from '../ui/Avatar';
import { api } from '../../api/client';
import { useServerStore } from '../../stores/serverStore';
@@ -60,7 +60,7 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
<div className="w-full h-[1px] bg-discord-modifier-accent mb-3" />
<div className="mb-3">
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Opencord Member Since</div>
<div className="text-[12px] font-bold text-discord-text-header uppercase mb-1">Backspace Member Since</div>
<div className="text-[12px] text-discord-text-normal font-medium">
{new Date(user.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
</div>
@@ -49,7 +49,7 @@ function AudioTrackElement({
// The <audio> element is always muted — it serves only as a Chrome
// keep-alive so Chrome continues processing the WebRTC track.
// Real audio output goes through the Web Audio pipeline.
return <audio ref={audioRef} autoPlay playsInline data-opencord="keepalive" />;
return <audio ref={audioRef} autoPlay playsInline data-backspace="keepalive" />;
}
/**