Frontend Guide

4 minutes read

Overview

The frontend is a React 19 SPA built with Vite 7 and TypeScript 5.9. It runs inside an Electron BrowserWindow loaded from file://wwwroot/index.html.

Stack

LibraryPurpose
React 19UI framework
TypeScript 5.9Type safety
Vite 7Build tool
TailwindCSS v4Utility-first CSS
GSAP 3 + @gsap/reactAnimations
Lucide ReactIcons
React Router DOMClient-side routing

Pages

PageFileRouteDescription
Dashboardpages/Dashboard.tsx/Game launch, progress, status
Newspages/News.tsx/newsHytale news feed
Settingspages/Settings.tsx/settingsApp settings
Mod Managerpages/ModManager.tsx/modsBrowse and manage mods

Components

ComponentFileDescription
TitleBarcomponents/TitleBar.tsxFrameless window title bar with controls
Sidebarcomponents/Sidebar.tsxNavigation sidebar with icons
GlassCardcomponents/GlassCard.tsxGlass-morphism card wrapper

UI Primitives

To keep pages consistent and maintainable, prefer the shared primitives in Frontend/src/components/ui/:

Shared Controls (single source of truth)

For most interactive UI (buttons, icon buttons, tab-like segmented pills, scroll areas, and image lightbox), use:

This file intentionally centralizes the “feel” of the app so we don’t end up with many one-off button styles.

What to use

IconButton sizing

IconButton variants

Rule of thumb

Editing controls

Creating a Component

import { ipc } from "../lib/ipc";
import type { Profile } from "../lib/ipc";

interface Props {
  profileId: string;
}

export function ProfileCard({ profileId }: Props) {
  const [profile, setProfile] = useState<Profile | null>(null);

  useEffect(() => {
    ipc.profile.get().then(setProfile);
  }, [profileId]);

  if (!profile) return <div>Loading...</div>;
  return (
    <div
      className="p-4 rounded-xl"
      style={{ backgroundColor: "var(--bg-light)" }}
    >
      {profile.name}
    </div>
  );
}

Theming

All theme colors are CSS custom properties defined in Frontend/src/index.css:

:root {
  --bg-darkest: #0d0d10; /* App background */
  --bg-dark: #14141a; /* Sidebar, title bar */
  --bg-medium: #1c1c26; /* Cards */
  --bg-light: #252533; /* Elevated surfaces */
  --bg-lighter: #2e2e40; /* Borders, scrollbar */
  --text-primary: #f0f0f5; /* Main text */
  --text-secondary: #a0a0b8; /* Secondary text */
  --text-muted: #6b6b80; /* Muted / disabled */
  --accent: #7c5cfc; /* Primary accent (purple) */
  --accent-hover: #6a4ae8; /* Accent hover */
  --success: #4ade80;
  --warning: #fbbf24;
  --error: #f87171;
}

Usage:

Animations (GSAP)

Page transitions and micro-interactions use GSAP:

import { useGSAP } from "@gsap/react";
import gsap from "gsap";

export function MyPage() {
  const containerRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    gsap.from(containerRef.current, {
      opacity: 0,
      y: 20,
      duration: 0.5,
      ease: "power2.out",
    });
  }, []);

  return <div ref={containerRef}>...</div>;
}

IPC Usage

All IPC is accessed through the auto-generated ipc object:

import { ipc } from "../lib/ipc";

// Invoke (request/reply)
const settings = await ipc.settings.get();

// Send (fire-and-forget)
ipc.windowCtl.minimize();

// Event subscription
ipc.game.onProgress((data) => setProgress(data.progress));

// Open URL
ipc.browser.open("https://example.com");

Instance State Resilience

Context Providers

Game state is managed via GameContext:

const { isPlaying, launch, cancel } = useGame();

Add new contexts in Frontend/src/contexts/ for other domain state.

Icons

All icons come from Lucide React

import { Settings, Download, Play } from "lucide-react";

<Settings size={18} style={{ color: "var(--text-secondary)" }} />;