Files
SCOPENET-MC/launcher/src/pages/Home.svelte
T
Claude 1b1bb984bc Restyle to a clean, flat look and rename brand to SCOPENET
- Brand is now SCOPENET everywhere (defaults, UI, installer, docs)
- Launcher: solid backgrounds (no animated orbs/grain), flat Play button
  without gradient/shine/glow, quieter rail with a simple active
  indicator, smaller type scale, Inter as default font, glass off by
  default, restyled sliders
- Panel: same flat treatment; live preview mirrors the new launcher
- New flat scope-reticle logo and app icons
- Calmer default palette
- Fix: lock the shared cargo cache in the Docker build (amd64/arm64 raced)
- Fix: Windows-only clippy lint in sys.rs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
2026-09-28 05:30:19 +00:00

82 lines
4.7 KiB
Svelte

<script lang="ts">
import { SlidersHorizontal, SquareTerminal, Package, MessageCircle, Globe, ShoppingBag, CirclePlay, Tv, Code, AtSign, Link } from '@lucide/svelte';
import PlayButton from '../components/PlayButton.svelte';
import ServerStatus from '../components/ServerStatus.svelte';
import NewsFeed from '../components/NewsFeed.svelte';
import Console from '../components/Console.svelte';
import { activeAccount, app, instances, selectedInstance } from '../lib/store.svelte';
import { loaderName, shortLoaderVersion } from '../lib/format';
import { openUrl } from '../lib/tauri';
const inst = $derived(selectedInstance());
const b = $derived(app.manifest!.branding);
// Lucide ships no brand logos, so each platform gets a recognisable generic icon.
const linkIcon: Record<string, any> = { discord: MessageCircle, website: Globe, store: ShoppingBag, youtube: CirclePlay, twitch: Tv, github: Code, twitter: AtSign };
const hour = new Date().getHours();
const greet = hour < 5 ? 'Late night session' : hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening';
</script>
<div class="home">
{#if inst}
{#key inst.id}
<section class="hero">
<p class="greet muted">{greet}, <strong>{activeAccount()?.username ?? 'player'}</strong></p>
<div class="chips">
<span class="chip">{inst.mc_version}</span>
<span class="chip">{loaderName[inst.loader]} {shortLoaderVersion(inst.mc_version, inst.loader_version)}</span>
{#if inst.file_count}<span class="chip"><Package size={12} /> {inst.file_count} mods & files</span>{/if}
</div>
<h1>{inst.name}</h1>
<p class="desc">{inst.description || b.tagline}</p>
</section>
<section class="actions">
<PlayButton {inst} />
<div class="row tools">
<button class="glass tool" onclick={() => (app.instanceSettings = inst.id)} title="Instance settings"><SlidersHorizontal size={17} /></button>
<button class="glass tool" class:on={app.consoleOpen} onclick={() => (app.consoleOpen = !app.consoleOpen)} title="Game console"><SquareTerminal size={17} /></button>
{#if inst.server && b.features.server_status}<ServerStatus server={inst.server} />{/if}
</div>
</section>
{/key}
{:else}
<section class="hero empty">
<h1>{b.name}</h1>
<p class="desc">{instances().length === 0 ? "Nothing to play yet — your server admin hasn't published any instances you can access." : ''}</p>
</section>
{/if}
<aside class="side">
{#if b.features.news && b.news.length}<NewsFeed news={b.news} />{:else}<div></div>{/if}
{#if b.links.length}
<div class="links">
{#each b.links as l}
{@const Icon = linkIcon[l.icon] ?? Link}
<button class="glass link" onclick={() => openUrl(l.url)} title={l.label}><Icon size={16} /> <span>{l.label}</span></button>
{/each}
</div>
{/if}
</aside>
{#if app.consoleOpen}<Console />{/if}
</div>
<style>
.home { position: relative; height: 100%; display: grid; grid-template-columns: 1fr minmax(17rem, 22rem); grid-template-rows: 1fr auto; gap: 1.5rem 2.5rem; padding: 2rem 2rem 2rem 2.5rem; }
.hero { grid-column: 1; grid-row: 1; align-self: end; display: flex; flex-direction: column; gap: 0.75rem; animation: fade 0.3s ease; max-width: 38rem; padding-bottom: 0.5rem; }
.greet { font-size: 0.88rem; }
.greet strong { color: var(--text); font-weight: 560; }
.chips { display: flex; gap: 0.45rem; flex-wrap: wrap; }
h1 { font-size: clamp(2.2rem, 4.4vw, 3.2rem); line-height: 1.05; font-weight: 700; letter-spacing: -0.025em; }
.desc { font-size: 0.95rem; line-height: 1.6; color: var(--muted); max-width: 32rem; }
.actions { grid-column: 1; grid-row: 2; display: flex; flex-direction: column; gap: 0.8rem; animation: fade 0.3s ease both; }
.tools { flex-wrap: wrap; gap: 0.6rem; }
.tool { width: 2.75rem; height: 2.75rem; padding: 0; border-radius: var(--radius); color: color-mix(in srgb, var(--text) 75%, transparent); background: var(--surface); }
.tool.on { color: var(--text); border-color: var(--line-strong); background: color-mix(in srgb, var(--text) 8%, var(--surface)); }
.side { grid-column: 2; grid-row: 1 / 3; display: flex; flex-direction: column; justify-content: space-between; gap: 1rem; min-height: 0; animation: fade 0.3s ease both; }
.links { display: flex; flex-wrap: wrap; gap: 0.45rem; }
.link { padding: 0.5rem 0.75rem; font-size: 0.82rem; border-radius: var(--radius-sm); flex: 1 1 auto; background: var(--surface); color: color-mix(in srgb, var(--text) 80%, transparent); }
.link:hover { border-color: var(--line-strong); color: var(--text); }
@media (max-width: 1060px) { .home { grid-template-columns: 1fr 16rem; } .link span { display: none; } }
</style>