new socials buttons and stuffses

This commit is contained in:
legop3
2026-02-10 20:02:55 -05:00
parent 1ac46f4a03
commit 42c99410f4
16 changed files with 296 additions and 187 deletions
@@ -1,23 +0,0 @@
import { useSession } from "../context/SessionContext";
import { FaDiscord } from "react-icons/fa";
export default function DiscordInviteButton({ text = "Join our Discord!", className = "" }) {
const { session } = useSession();
const discordInvite = session?.discord?.invite || null;
if (!discordInvite) return null;
return (
<a
href={discordInvite}
target="_blank"
rel="noopener noreferrer"
className={`inline-flex items-center w-full px-0.5 py-0.5 text-sm font-medium text-white rainbow-animate-bg transition justify-center gap-1 rounded-md ${className}`}
// animated rainbow backgound
// className="inline-flex items-center px-3 py-2 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-white rounded hover:from-indigo-600 hover:via-purple-600 hover:to-pink-600 transition"
>
<FaDiscord className="mr-0" />
{text}
</a>
);
}
-21
View File
@@ -1,21 +0,0 @@
import { useSession } from "../context/SessionContext";
import { FaCoffee } from "react-icons/fa";
export default function KoFiButton({ text = "Support me on Ko-fi!", className = "" }) {
const { session } = useSession();
const kofiLink = session?.kofi?.link || null;
if (!kofiLink) return null;
return (
<a
href={kofiLink}
target="_blank"
rel="noopener noreferrer"
className={`inline-flex items-center w-full px-0.5 py-0.5 text-sm font-medium text-white kofi-animate-bg transition justify-center gap-1 rounded-md ${className}`}
>
<FaCoffee className="mr-0" />
{text}
</a>
);
}
+14 -3
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react';
import AuthPanel from './AuthPanel.jsx';
import { useSession } from '../context/SessionContext.jsx';
import DiscordInviteButton from './DiscordInviteButton.jsx';
import SocialButton from './SocialButton.jsx';
import ChatPanel from './ChatPanel.jsx';
import NicknameForm from './NicknameForm.jsx';
@@ -31,6 +31,13 @@ export default function ModeGateOverlay() {
const reason = session?.adminReason?.text || '';
const reasonUpdatedAt = session?.adminReason?.updatedAt || null;
const timezone = session?.timezone || 'UTC';
const discordUrl =
session?.socials?.find((entry) => {
const key = String(entry?.id || entry?.label || '').toLowerCase();
return key === 'discord';
})?.url ||
session?.discord?.invite ||
null;
const restricted = RESTRICTED_MODES.has(mode);
const privileged = mode === 'lockdown' ? LOCKDOWN_ROLES.has(role) : PRIVILEGED_ROLES.has(role);
const [now, setNow] = useState(() => new Date());
@@ -81,8 +88,12 @@ export default function ModeGateOverlay() {
<div className="surface-muted">
<AuthPanel />
</div>
<div className='w-full justify-center items-center'>
<DiscordInviteButton text='Join our Discord server for updates!'/>
<div className="w-full justify-center items-center">
<SocialButton
id="discord"
label="Join our Discord server for updates!"
url={discordUrl}
/>
</div>
You can still use the chat while the server is locked:
{/* set max height of this box */}
+2 -6
View File
@@ -3,8 +3,7 @@ import { useSession } from '../context/SessionContext.jsx';
import { useSettingsNamespace } from '../settings/index.js';
import { useSocket } from '../context/SocketContext.jsx';
import NicknameForm from './NicknameForm.jsx';
import DiscordInviteButton from './DiscordInviteButton.jsx';
import KoFiButton from './KoFiButton.jsx';
import SocialButtonsGrid from './SocialButtonsGrid.jsx';
function roleColors(role) {
switch (role) {
@@ -97,10 +96,7 @@ export default function RawUserPilePanel({
<NicknameForm compact={compact} />
</div>
</div>
<div className="grid gap-0.5 sm:grid-cols-2 md:grid-cols-1">
<DiscordInviteButton />
<KoFiButton />
</div>
<SocialButtonsGrid />
</div>
{!canSetNickname && <p className="text-xs text-slate-500">Spectators cannot set nicknames.</p>}
</div>
+50
View File
@@ -0,0 +1,50 @@
import { FaBook, FaCoffee, FaCrown, FaDiscord, FaLink } from 'react-icons/fa';
const ICONS_BY_ID = {
discord: FaDiscord,
kofi: FaCoffee,
'ko-fi': FaCoffee,
wiki: FaBook,
throne: FaCrown,
};
const VARIANT_BY_ID = {
discord: 'rainbow-animate-bg',
kofi: 'kofi-animate-bg',
'ko-fi': 'kofi-animate-bg',
wiki: 'wiki-animate-bg',
throne: 'throne-animate-bg',
};
function normalizeId(id, label) {
if (typeof id === 'string' && id.trim()) return id.trim().toLowerCase();
if (typeof label === 'string' && label.trim()) {
return label
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '');
}
return '';
}
export default function SocialButton({ id, label, url, className = '' }) {
if (!url) return null;
const key = normalizeId(id, label);
const Icon = ICONS_BY_ID[key] || FaLink;
const variantClass = VARIANT_BY_ID[key] || 'bg-slate-700 hover:bg-slate-600';
const text = label || id || 'Link';
return (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
aria-label={text}
className={`inline-flex items-center w-full px-0.5 py-0.5 text-sm font-medium text-white transition justify-center gap-1 rounded-md ${variantClass} ${className}`}
>
<Icon className="mr-0" />
{text}
</a>
);
}
@@ -0,0 +1,43 @@
import { useSession } from '../context/SessionContext.jsx';
import SocialButton from './SocialButton.jsx';
function normalizeSocials(session) {
const configured = Array.isArray(session?.socials) ? session.socials : null;
if (configured && configured.length) {
return configured;
}
const fallback = [];
if (session?.discord?.invite) {
fallback.push({ id: 'discord', label: 'Discord', url: session.discord.invite });
}
if (session?.kofi?.link) {
fallback.push({ id: 'kofi', label: 'Ko-fi', url: session.kofi.link });
}
return fallback;
}
function normalizeEntry(entry) {
if (!entry || typeof entry !== 'object') return null;
const id = entry.id || entry.service || entry.key || entry.name || null;
const label = entry.label || entry.title || entry.name || id || 'Link';
const url = entry.url || entry.link || entry.href || null;
return url ? { id, label, url } : null;
}
export default function SocialButtonsGrid({ className = '' }) {
const { session } = useSession();
const socials = normalizeSocials(session)
.map(normalizeEntry)
.filter(Boolean)
.slice(0, 4);
if (!socials.length) return null;
return (
<div className={`grid grid-cols-2 grid-rows-2 gap-0.5 ${className}`}>
{socials.map((entry) => (
<SocialButton key={`${entry.id || entry.label}-${entry.url}`} {...entry} />
))}
</div>
);
}
+3 -10
View File
@@ -3,8 +3,7 @@ import { useSession } from '../context/SessionContext.jsx';
import { useSettingsNamespace } from '../settings/index.js';
import { useSocket } from '../context/SocketContext.jsx';
import NicknameForm from './NicknameForm.jsx';
import DiscordInviteButton from './DiscordInviteButton.jsx';
import KoFiButton from './KoFiButton.jsx';
import SocialButtonsGrid from './SocialButtonsGrid.jsx';
export function NicknameEntryPanel({ compact = false }) {
return (
@@ -19,10 +18,7 @@ export function NicknameEntryPanel({ compact = false }) {
export function LinkButtonsPanel() {
return (
<section className="panel-section flex h-full min-h-0 flex-col gap-0.5 text-base">
<div className="grid flex-1 min-h-0 gap-0.5 grid-rows-2">
<DiscordInviteButton className="h-full" />
<KoFiButton className="h-full" />
</div>
<SocialButtonsGrid className="flex-1 min-h-0" />
</section>
);
}
@@ -185,10 +181,7 @@ export default function UserListPanel({
<NicknameForm compact={compact} />
</div>
</div>
<div className="grid gap-0.5 sm:grid-cols-2 md:grid-cols-1">
<DiscordInviteButton />
<KoFiButton />
</div>
<SocialButtonsGrid />
</div>
{!canSetNickname && <p className="text-xs text-slate-500">Spectators cannot set nicknames.</p>}
</div>
+14 -2
View File
@@ -5,7 +5,7 @@ import { useHudMapSetting } from '../hooks/useHudMapSetting.js';
import { useChat } from '../context/ChatContext.jsx';
import { useSession } from '../context/SessionContext.jsx';
import { useSettingsNamespace } from '../settings/index.js';
import DiscordInviteButton from './DiscordInviteButton.jsx';
import SocialButton from './SocialButton.jsx';
const RESTART_DELAY_MS = 2000;
const UNMUTE_RETRY_MS = 3000;
@@ -61,6 +61,7 @@ export default function VideoTile({
isActiveDriver = false,
idleSkipSeconds = null,
}) {
const { session } = useSession();
const videoRef = useRef(null);
const audioRef = useRef(null);
const restartTimer = useRef(null);
@@ -110,6 +111,13 @@ export default function VideoTile({
const overlayMotors = overcurrentMotors.length ? overcurrentMotors : limiterActive ? ['limiter'] : [];
const overlayFill = limiterFill ?? (overcurrentMotors.length ? 1 : 0);
const overlayVisible = Boolean(overlayMotors.length);
const discordUrl =
session?.socials?.find((entry) => {
const key = String(entry?.id || entry?.label || '').toLowerCase();
return key === 'discord';
})?.url ||
session?.discord?.invite ||
null;
useEffect(() => {
if (!debugHud) return;
@@ -471,7 +479,11 @@ export default function VideoTile({
>
<div className="text-center">{qualityNotice}</div>
<div className="pointer-events-auto mt-0">
<DiscordInviteButton text={'Join our Discord server while you wait!'} />
<SocialButton
id="discord"
label="Join our Discord server while you wait!"
url={discordUrl}
/>
</div>
</div>
</div>
+24
View File
@@ -127,6 +127,30 @@ body {
background-size: 800% 800%;
animation: rainbowBG 240s ease infinite;
}
.wiki-animate-bg {
background: linear-gradient(
270deg,
#14b8a6,
#38bdf8,
#22d3ee,
#14b8a6
);
background-size: 700% 700%;
animation: rainbowBG 240s ease infinite;
}
.throne-animate-bg {
background: linear-gradient(
270deg,
#f59e0b,
#fbbf24,
#fde68a,
#f59e0b
);
background-size: 700% 700%;
animation: rainbowBG 240s ease infinite;
}
}
@layer utilities {