Рефактор игрового поля деревни на онбординге и на /village
This commit is contained in:
parent
c9bf46e309
commit
ff27f664ef
|
|
@ -521,7 +521,7 @@ const handleRegister = async () => {
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
.btn-toggle {
|
.btn-toggle {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 0.7rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,19 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="villageData && villageData.tiles" class="village-container">
|
<div v-if="villageData && villageData.tiles" class="village-container">
|
||||||
<div :class="villageGridWrapperClass" :style="gridWrapperStyle">
|
<div :class="villageGridWrapperClass">
|
||||||
<!-- Empty corner for alignment -->
|
<!-- Empty corner for alignment -->
|
||||||
<div class="empty-corner"></div>
|
<div class="empty-corner"></div>
|
||||||
|
|
||||||
<!-- Column Labels (A, B, C...) -->
|
|
||||||
<div class="col-labels">
|
|
||||||
<div class="col-label" v-for="colLabel in colLabels" :key="colLabel">{{ colLabel }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Row Labels (7, 6, 5...) -->
|
|
||||||
<div class="row-labels">
|
|
||||||
<div class="row-label" v-for="rowLabel in rowLabels" :key="rowLabel">{{ rowLabel }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- The actual grid -->
|
<!-- The actual grid -->
|
||||||
<div class="village-grid" :style="gridStyle">
|
<div class="village-grid" :style="gridStyle">
|
||||||
<div
|
<div
|
||||||
|
|
@ -47,6 +38,10 @@ const props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
selectedTile: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const villageGridWrapperClass = computed(() => ({
|
const villageGridWrapperClass = computed(() => ({
|
||||||
|
|
@ -67,11 +62,7 @@ const gridHeight = computed(() => {
|
||||||
return Math.max(...props.villageData.tiles.map(t => t.y)) + 1;
|
return Math.max(...props.villageData.tiles.map(t => t.y)) + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Dynamic Styles ---
|
|
||||||
const gridWrapperStyle = computed(() => ({
|
|
||||||
'grid-template-columns': `20px repeat(${gridWidth.value}, var(--tile-size))`,
|
|
||||||
'grid-template-rows': `repeat(${gridHeight.value}, var(--tile-size)) 20px`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const gridStyle = computed(() => ({
|
const gridStyle = computed(() => ({
|
||||||
'grid-template-columns': `repeat(${gridWidth.value}, var(--tile-size))`,
|
'grid-template-columns': `repeat(${gridWidth.value}, var(--tile-size))`,
|
||||||
|
|
@ -115,6 +106,7 @@ const tileClasses = (tile) => {
|
||||||
'tile-object': !!tile.object,
|
'tile-object': !!tile.object,
|
||||||
'tile-empty': tile.terrainType === 'EMPTY' && !tile.object,
|
'tile-empty': tile.terrainType === 'EMPTY' && !tile.object,
|
||||||
'tile-clearing': tile.terrainState === 'CLEARING',
|
'tile-clearing': tile.terrainState === 'CLEARING',
|
||||||
|
'selected': props.selectedTile && props.selectedTile.id === tile.id,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -124,9 +116,10 @@ const tileClasses = (tile) => {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 10px;
|
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
--tile-size: clamp(40px, 10vw, 55px);
|
--tile-size: clamp(40px, 10vw, 55px);
|
||||||
|
padding: 0 16px; /* Add padding for mobile */
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.village-grid-wrapper {
|
.village-grid-wrapper {
|
||||||
|
|
@ -136,10 +129,9 @@ const tileClasses = (tile) => {
|
||||||
background-color: var(--container-bg-color);
|
background-color: var(--container-bg-color);
|
||||||
padding: 12px; /* Increased padding */
|
padding: 12px; /* Increased padding */
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
max-width: 100%; /* Ensure it doesn't overflow parent */
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
box-shadow: 0 8px 30px rgba(0,0,0,0.06);
|
box-shadow: 0 8px 30px rgba(0,0,0,0.06);
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-corner, .col-labels, .row-labels {
|
.empty-corner, .col-labels, .row-labels {
|
||||||
|
|
@ -263,35 +255,34 @@ const tileClasses = (tile) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.village-grid-wrapper {
|
/* On mobile, we calculate a tile size that fits the screen.
|
||||||
width: 100%;
|
7 columns (5 tiles + 2 labels) and 6 gaps.
|
||||||
margin: 0;
|
The parent .village-container has 16px padding on each side.
|
||||||
box-sizing: border-box;
|
So, (100vw - 32px - (6 * 4px)) / 7 = tile size
|
||||||
padding-left: 0;
|
We'll use a simplified version with vw units. */
|
||||||
padding-right: 0;
|
.village-container {
|
||||||
position: relative; /* Needed for pseudo-element */
|
--tile-size: clamp(35px, 11vw, 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.village-grid-wrapper::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 20px;
|
|
||||||
background: linear-gradient(to left, rgba(0,0,0,0.05), transparent);
|
|
||||||
pointer-events: none; /* Let clicks pass through */
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.village-grid-wrapper:hover::after {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile {
|
.tile {
|
||||||
aspect-ratio: 1 / 1; /* Keep tiles square */
|
/* aspect-ratio: 1 / 1;
|
||||||
height: auto; /* Let aspect ratio control the height */
|
height: auto; */
|
||||||
|
/* Disabling aspect ratio for now to prevent massive height on narrow screens */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile.tile-empty:hover {
|
||||||
|
background-color: rgba(0,0,0,0.02); /* Reset to non-hover state */
|
||||||
|
border-color: var(--border-color); /* Reset to non-hover state */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile.tile-blocked:hover {
|
||||||
|
transform: translateY(0); /* Reset to non-hover state */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile.tile-object:hover {
|
||||||
|
transform: translateY(0); /* Reset to non-hover state */
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.07); /* Reset to non-hover state */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
|
|
||||||
<!-- Create Habit Form -->
|
<!-- Create Habit Form -->
|
||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
<h2>Создать новую привычку</h2>
|
<h2>Новая привычка</h2>
|
||||||
<form @submit.prevent="createHabit">
|
<form @submit.prevent="createHabit">
|
||||||
<div v-if="error" class="error-message">{{ error }}</div>
|
<div v-if="error" class="error-message">{{ error }}</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,32 +13,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="villageData">
|
<div v-else-if="villageData">
|
||||||
<div class="village-container">
|
<VillageGrid
|
||||||
<div class="village-grid-wrapper"> <!-- This will be the main grid container with labels -->
|
:village-data="villageData"
|
||||||
<div class="empty-corner"></div> <!-- Top-left empty cell -->
|
:selected-tile="selectedTile"
|
||||||
|
@tile-click="selectTile"
|
||||||
<div class="col-labels">
|
/>
|
||||||
<div class="col-label" v-for="colLabel in ['A', 'B', 'C', 'D', 'E']" :key="colLabel">{{ colLabel }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row-labels">
|
|
||||||
<div class="row-label" v-for="rowLabel in ['7', '6', '5', '4', '3', '2', '1']" :key="rowLabel">{{ rowLabel }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="village-grid">
|
|
||||||
<div
|
|
||||||
v-for="tile in villageData.tiles"
|
|
||||||
:key="tile.id"
|
|
||||||
class="tile"
|
|
||||||
:class="[tileClasses(tile), { selected: selectedTile && selectedTile.id === tile.id }]"
|
|
||||||
:style="{ 'grid-column': tile.x + 1, 'grid-row': tile.y + 1 }"
|
|
||||||
@click="selectTile(tile)"
|
|
||||||
>
|
|
||||||
<span class="tile-content">{{ getTileEmoji(tile) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tile Info Overlay -->
|
<!-- Tile Info Overlay -->
|
||||||
<div v-if="selectedTile" class="tile-overlay-backdrop" @click="selectedTile = null">
|
<div v-if="selectedTile" class="tile-overlay-backdrop" @click="selectedTile = null">
|
||||||
|
|
@ -128,35 +107,6 @@ const { data: villageEvents, refresh: refreshEvents } = await useFetch('/api/vil
|
||||||
|
|
||||||
const selectedTile = ref(null);
|
const selectedTile = ref(null);
|
||||||
|
|
||||||
const getTileEmoji = (tile) => {
|
|
||||||
if (tile.terrainState === 'CLEARING') return '⏳';
|
|
||||||
if (tile.object) {
|
|
||||||
switch (tile.object.type) {
|
|
||||||
case 'HOUSE': return '🏠';
|
|
||||||
case 'FIELD': return '🌱';
|
|
||||||
case 'LUMBERJACK': return '🪓';
|
|
||||||
case 'QUARRY': return '⛏️';
|
|
||||||
case 'WELL': return '💧';
|
|
||||||
default: return '❓';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (tile.terrainType) {
|
|
||||||
case 'BLOCKED_TREE': return '🌳';
|
|
||||||
case 'BLOCKED_STONE': return '🪨';
|
|
||||||
case 'EMPTY': return '';
|
|
||||||
default: return '❓';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const tileClasses = (tile) => {
|
|
||||||
return {
|
|
||||||
'tile-blocked': tile.terrainType === 'BLOCKED_TREE' || tile.terrainType === 'BLOCKED_STONE',
|
|
||||||
'tile-object': !!tile.object,
|
|
||||||
'tile-empty': tile.terrainType === 'EMPTY' && !tile.object,
|
|
||||||
'tile-clearing': tile.terrainState === 'CLEARING', // Add this line
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectTile = (tile) => {
|
const selectTile = (tile) => {
|
||||||
selectedTile.value = tile;
|
selectedTile.value = tile;
|
||||||
};
|
};
|
||||||
|
|
@ -333,143 +283,6 @@ const handleAddCoins = () => handleAdminAction('/api/admin/user/add-coins');
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.village-container {
|
|
||||||
max-width: fit-content; /* Changed from width: 100%; display: flex; justify-content: center; */
|
|
||||||
margin: 0 auto; /* Centered horizontally */
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- New Demo-Inspired Tile Styles --- */
|
|
||||||
.tile {
|
|
||||||
width: var(--tile-size);
|
|
||||||
height: var(--tile-size);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
position: relative; /* For selection pseudo-elements */
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.tile-empty {
|
|
||||||
background-color: rgba(0,0,0,0.02);
|
|
||||||
border: 2px dashed var(--border-color);
|
|
||||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.03);
|
|
||||||
}
|
|
||||||
.tile.tile-empty:hover {
|
|
||||||
background-color: rgba(0,0,0,0.04);
|
|
||||||
border-color: var(--primary-color-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.tile-blocked {
|
|
||||||
background-color: #EBE5D7; /* A soft, earthy tone */
|
|
||||||
border: 1px solid #DCD5C6;
|
|
||||||
}
|
|
||||||
.tile.tile-blocked:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.tile-object {
|
|
||||||
background-color: #c8ffcf;
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.07);
|
|
||||||
border: 1px solid #39af50;
|
|
||||||
}
|
|
||||||
.tile.tile-object:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.tile-clearing {
|
|
||||||
background-color: #ffe0b2; /* A soft orange/yellow for "in progress" */
|
|
||||||
border: 1px solid #ffcc80;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.selected {
|
|
||||||
box-shadow: 0 0 0 3px var(--primary-color);
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile-content {
|
|
||||||
font-size: calc(var(--tile-size) * 0.5); /* Make emoji scale with tile */
|
|
||||||
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile.tile-object .tile-content {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Styles for the grid with labels */
|
|
||||||
.village-grid-wrapper {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 20px repeat(5, var(--tile-size));
|
|
||||||
grid-template-rows: repeat(7, var(--tile-size)) 20px;
|
|
||||||
gap: 8px; /* Increased gap */
|
|
||||||
border-radius: 16px; /* Rounded wrapper */
|
|
||||||
background-color: var(--container-bg-color);
|
|
||||||
padding: 12px; /* Increased padding */
|
|
||||||
width: fit-content;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-shadow: 0 8px 30px rgba(0,0,0,0.06);
|
|
||||||
/* Removed overflow-x: auto; and overflow-y: hidden; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-corner, .col-labels, .row-labels {
|
|
||||||
opacity: 0.6;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-corner {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 8;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-labels {
|
|
||||||
grid-column: 2 / span 5;
|
|
||||||
grid-row: 8;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-label {
|
|
||||||
width: var(--tile-size);
|
|
||||||
text-align: center;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-labels {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 1 / span 7;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-label {
|
|
||||||
height: var(--tile-size);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.village-grid {
|
|
||||||
grid-column: 2 / span 5;
|
|
||||||
grid-row: 1 / span 7;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, var(--tile-size));
|
|
||||||
grid-template-rows: repeat(7, var(--tile-size));
|
|
||||||
gap: 8px; /* Increased gap */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Overlay and other styles */
|
/* Overlay and other styles */
|
||||||
|
|
||||||
.tile-overlay-backdrop {
|
.tile-overlay-backdrop {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user