- 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
82 lines
4.7 KiB
Svelte
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>
|