Skip to content

Commit 9fdaa06

Browse files
Restyle the entire UI
1 parent 82c32bc commit 9fdaa06

11 files changed

Lines changed: 451 additions & 562 deletions

File tree

islanders-client/src/lib/components/Map.svelte

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { onDestroy, onMount } from 'svelte';
2+
import { onMount } from 'svelte';
33
import { Application, Container, Graphics, Point, Assets, FederatedPointerEvent } from 'pixi.js';
44
import { gameState, bindToWorld, sendAction } from '$lib/stores/game.svelte.ts';
55
import {
@@ -40,35 +40,34 @@
4040
let container: HTMLDivElement | undefined;
4141
let parentElement: HTMLElement | null = null;
4242
43+
const currentPlayer: Player | undefined = $derived(
44+
gameState.world?.players.find((player: Player) => player.name === gameState.playerName)
45+
);
46+
4347
const hexSize = 200;
4448
const tileHeight = 348;
4549
const tileWidth = 400;
4650
const lineWidth = 14;
4751
const tilePath = '/img/tilesets/';
4852
const tileStyle = 'realistic';
4953
const assetsToLoad = [
50-
// Tiles
5154
`${tilePath}${tileStyle}/clay.png`,
5255
`${tilePath}${tileStyle}/desert.png`,
5356
`${tilePath}${tileStyle}/grain.png`,
5457
`${tilePath}${tileStyle}/wood.png`,
5558
`${tilePath}${tileStyle}/stone.png`,
5659
`${tilePath}${tileStyle}/wool.png`,
5760
`${tilePath}${tileStyle}/ocean.png`,
58-
// Pieces
5961
'/img/pieces/house.png',
6062
'/img/pieces/city.png',
6163
'/img/pieces/thief.png',
62-
// Special
6364
`${tilePath}shared/scorch-with-thief.png`,
64-
// Harbors
6565
`${tilePath}${tileStyle}/woodharbor.png`,
6666
`${tilePath}${tileStyle}/woolharbor.png`,
6767
`${tilePath}${tileStyle}/grainharbor.png`,
6868
`${tilePath}${tileStyle}/clayharbor.png`,
6969
`${tilePath}${tileStyle}/stoneharbor.png`,
7070
`${tilePath}${tileStyle}/threetooneharbor.png`,
71-
// Numbers
7271
'/img/numbers/2.png',
7372
'/img/numbers/3.png',
7473
'/img/numbers/4.png',
@@ -120,10 +119,6 @@
120119
let latestWorld: World | undefined;
121120
let loadingPromise: Promise<void> | undefined;
122121
123-
const currentPlayer: Player | undefined = $derived(
124-
gameState.world?.players.find((player: Player) => player.name === gameState.playerName)
125-
);
126-
127122
$effect(() => {
128123
updateMap(gameState.world);
129124
});
@@ -135,15 +130,13 @@
135130
isPlayingRoadBuilding = uiState.isPlayingRoadBuilding;
136131
});
137132
138-
// Convert screen coordinates to world coordinates
139133
const toWorld = (screenPoint: { x: number; y: number }): { x: number; y: number } => {
140134
return {
141135
x: (screenPoint.x - panX) / scale,
142136
y: (screenPoint.y - panY) / scale
143137
};
144138
};
145139
146-
// Update world container transform
147140
const updateWorldTransform = () => {
148141
if (!worldContainer) return;
149142
worldContainer.scale.set(scale);
@@ -155,13 +148,21 @@
155148
return;
156149
}
157150
151+
const prevCenterWorld = toWorld({ x: width / 2, y: height / 2 });
152+
158153
const target = parentElement ?? container;
159-
const ratio = window.devicePixelRatio || 1;
160-
height = target.clientHeight / ratio;
161-
width = target.clientWidth / ratio;
162-
container.style.width = `${target.clientWidth}px`;
163-
container.style.height = `${target.clientHeight}px`;
154+
width = target.clientWidth;
155+
height = target.clientHeight;
156+
container.style.width = `${width}px`;
157+
container.style.height = `${height}px`;
158+
164159
app.renderer.resize(width, height);
160+
app.stage.hitArea = app.screen;
161+
worldContainer.hitArea = app.screen;
162+
163+
panX = width / 2 - prevCenterWorld.x * scale;
164+
panY = height / 2 - prevCenterWorld.y * scale;
165+
updateWorldTransform();
165166
};
166167
167168
const dispatchActionClearCursor = async (action: GameAction) => {
@@ -233,14 +234,12 @@
233234
};
234235
235236
const handleClick = (event: FederatedPointerEvent) => {
236-
if (isBuilding !== 'None') {
237+
if (isBuilding !== 'None' || isPlayingRoadBuilding) {
237238
handleBuildClick(event);
238239
} else if (isMovingThief) {
239240
handleThiefClick(event);
240241
} else if (isPlayingKnight) {
241242
handleIsPlayingKnightClick(event);
242-
} else if (isPlayingRoadBuilding) {
243-
handleBuildClick(event);
244243
}
245244
};
246245
@@ -255,8 +254,7 @@
255254
piece.width = dimensions.x;
256255
piece.height = dimensions.y;
257256
piece.tint = tint;
258-
piece.position.x = coord.x;
259-
piece.position.y = coord.y;
257+
piece.position.set(coord.x, coord.y);
260258
piece.anchor.set(0.5);
261259
return piece;
262260
};
@@ -465,9 +463,9 @@
465463
466464
parentElement = container.parentElement;
467465
const target = parentElement ?? container;
468-
const ratio = window.devicePixelRatio || 1;
469-
height = target.clientHeight / ratio;
470-
width = target.clientWidth / ratio;
466+
// Use raw client sizes (renderer resolution will scale for DPR)
467+
height = target.clientHeight;
468+
width = target.clientWidth;
471469
container.style.width = `${target.clientWidth}px`;
472470
container.style.height = `${target.clientHeight}px`;
473471
@@ -498,10 +496,7 @@
498496
stage.addChild(worldContainerInstance);
499497
container?.appendChild(appInstance.canvas);
500498
501-
worldContainerInstance.addChild(tileGraphics);
502-
worldContainerInstance.addChild(lineGraphics);
503-
worldContainerInstance.addChild(pieceGraphics);
504-
worldContainerInstance.addChild(cursorGraphics);
499+
worldContainerInstance.addChild(tileGraphics, lineGraphics, pieceGraphics, cursorGraphics);
505500
506501
// Set initial position (center the view)
507502
panX = width / 2;
@@ -562,8 +557,8 @@
562557
const delta = -event.deltaY;
563558
const zoomIntensity = 0.0005;
564559
const zoomFactor = Math.exp(delta * zoomIntensity);
565-
const minScale = 0.1;
566-
const maxScale = 5;
560+
const minScale = 0.08;
561+
const maxScale = 2;
567562
const targetScale = scale * zoomFactor;
568563
const newScale = Math.min(maxScale, Math.max(minScale, targetScale));
569564

islanders-client/src/lib/components/PlayerSummary.svelte

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,43 @@
55
interface PlayerInformation {
66
player?: Player;
77
isActive?: boolean;
8-
subtitle?: string;
9-
size?: 'sm' | 'md';
10-
className?: string;
8+
resourceEntries: [string, any][];
119
}
1210
13-
const props = $props<PlayerInformation>();
11+
const { player, resourceEntries, isActive }: PlayerInformation = $props();
1412
1513
const defaultColor = '#94a3b8';
1614
17-
const playerName = $derived(props.player?.name ?? '');
18-
const points = $derived(props.player?.points ?? 0);
15+
const playerName = $derived(player?.name ?? '');
16+
const points = $derived(player?.points ?? 0);
1917
const color = $derived(
20-
props.player && playerName ? (getPlayerColorAsHex(playerName) ?? defaultColor) : defaultColor
18+
player && playerName ? (getPlayerColorAsHex(playerName) ?? defaultColor) : defaultColor
2119
);
22-
const size = $derived(props.size ?? 'md');
23-
const subtitle = $derived(props.subtitle ?? (props.isActive ? 'Current turn' : undefined));
2420
</script>
2521

2622
<div
27-
class={`flex items-center gap-3 leading-tight ${
28-
size === 'sm' ? 'text-sm' : 'text-base'
29-
} ${props.isActive ? 'text-white' : 'text-white/90'} ${props.className ?? ''}`.trim()}
23+
class="card card-body w-full gap-4 transition-colors"
24+
class:bg-base-300={isActive}
25+
class:bg-base-200={!isActive}
26+
class:border={isActive}
27+
class:text-base-900={isActive}
28+
class:text-base={!isActive}
3029
>
31-
<span class="h-10 w-2 rounded-full" style={`background-color: ${color}`}></span>
32-
<div class="flex flex-col">
33-
<span class={`font-semibold ${size === 'sm' ? 'text-base' : 'text-lg'}`}>{playerName}</span>
34-
<span class="text-xs opacity-80">Points: {points}</span>
35-
{#if subtitle}
36-
<span class="text-[0.65rem] tracking-wide text-white/70 uppercase">{subtitle}</span>
37-
{/if}
30+
<div class="flex items-center">
31+
<div class="flex w-full flex-row gap-2">
32+
<span class="min-h-full w-2 rounded-full" style={`background-color: ${color}`}></span>
33+
<div class="flex flex-col">
34+
<span class="text-base font-semibold">{playerName}</span>
35+
<span class="text-xs">Points: {points}</span>
36+
</div>
37+
</div>
38+
</div>
39+
40+
<div class="flex w-full flex-row flex-wrap uppercase">
41+
{#each resourceEntries as [resource, amount]}
42+
<span class="badge text-xs">
43+
{resource}: <span>{amount}</span>
44+
</span>
45+
{/each}
3846
</div>
3947
</div>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script lang="ts" context="module">
2+
export type Resources = {
3+
wood: number;
4+
clay: number;
5+
stone: number;
6+
grain: number;
7+
wool: number;
8+
};
9+
</script>
10+
11+
<script lang="ts">
12+
export let resources: Resources;
13+
14+
const baseColors = {
15+
wood: 'amber',
16+
clay: 'red',
17+
stone: 'gray',
18+
grain: 'yellow',
19+
wool: 'green'
20+
} as const;
21+
22+
const background = (type: keyof typeof baseColors) => {
23+
switch (type) {
24+
case 'wood':
25+
return 'bg-amber-500/85';
26+
case 'clay':
27+
return 'bg-red-500/85';
28+
case 'stone':
29+
return 'bg-gray-500/85';
30+
case 'grain':
31+
return 'bg-yellow-500/85';
32+
case 'wool':
33+
return 'bg-green-500/85';
34+
}
35+
};
36+
</script>
37+
38+
<div class="flex w-full min-w-96 gap-1 rounded-md bg-base-200/40 p-1 backdrop-blur-md">
39+
{#each Object.entries(resources) as [key, value]}
40+
<div
41+
class={`flex grow flex-col items-center rounded ${background(key as keyof typeof baseColors)} p-2`}
42+
>
43+
<span class="text-[10px] font-semibold tracking-wide uppercase">{key}</span>
44+
<span class="font-bold">{value}</span>
45+
</div>
46+
{/each}
47+
</div>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<script lang="ts">
2+
type ResourceType = 'wood' | 'clay' | 'stone' | 'grain' | 'wool';
3+
interface ResourcePanelProps {
4+
title: string;
5+
resources: Record<ResourceType, number>;
6+
maxResources?: Record<ResourceType, number>;
7+
resourceColors: Record<ResourceType, string>;
8+
onChange: (resource: ResourceType, next: number) => void;
9+
}
10+
11+
const { title, resources, maxResources, resourceColors, onChange }: ResourcePanelProps = $props();
12+
13+
const clamp = (r: ResourceType, v: number) => {
14+
if (!maxResources) return Math.max(0, v);
15+
const max = maxResources[r] ?? Number.POSITIVE_INFINITY;
16+
return Math.min(Math.max(0, v), max);
17+
};
18+
19+
const handleInput = (r: ResourceType, value: string) => {
20+
const numeric = Number(value);
21+
if (Number.isNaN(numeric)) return;
22+
onChange(r, clamp(r, numeric));
23+
};
24+
const inc = (r: ResourceType) => onChange(r, clamp(r, resources[r] + 1));
25+
const dec = (r: ResourceType) => onChange(r, clamp(r, resources[r] - 1));
26+
</script>
27+
28+
<article class="rounded bg-base-300 p-4">
29+
<h3 class="mb-3 text-sm font-semibold tracking-wide uppercase">{title}</h3>
30+
<div class="space-y-2">
31+
{#each Object.entries(resources) as [resource, count]}
32+
<div class="flex items-center justify-between rounded-lg p-2">
33+
<div class="flex min-w-0 flex-1 items-center gap-2 text-sm capitalize">
34+
<span class={`h-3 w-3 rounded ${resourceColors[resource as ResourceType]}`}></span>
35+
<span class="truncate">{resource}</span>
36+
</div>
37+
<div class="flex items-center gap-2">
38+
<button
39+
class="btn btn-xs"
40+
onclick={() => dec(resource as ResourceType)}
41+
disabled={count <= 0}>-</button
42+
>
43+
<input
44+
class="input input-xs w-20 text-center"
45+
min="0"
46+
{...maxResources ? { max: maxResources[resource as ResourceType] } : {}}
47+
value={count}
48+
oninput={(e) =>
49+
handleInput(resource as ResourceType, (e.target as HTMLInputElement).value)}
50+
/>
51+
<button
52+
class="btn btn-xs"
53+
onclick={() => inc(resource as ResourceType)}
54+
disabled={maxResources && count >= (maxResources[resource as ResourceType] ?? Infinity)}
55+
>+</button
56+
>
57+
</div>
58+
</div>
59+
{/each}
60+
</div>
61+
</article>

0 commit comments

Comments
 (0)