Enhance frontend styling and accessibility features

- Updated .dockerignore to exclude backup directory with root-only permissions from build context.
- Added new CSS variables for card radius and transition duration in app.css.
- Implemented consistent focus ring styles for interactive elements to improve accessibility.
- Refactored card components across various pages to utilize a unified card style, enhancing visual consistency.
- Improved button styles with touch manipulation support for better user interaction on mobile devices.
This commit is contained in:
2026-03-01 22:19:51 +03:00
parent 225aaa36e7
commit d27eb2651e
9 changed files with 71 additions and 31 deletions

View File

@@ -18,6 +18,9 @@ frontend-nuxt/.output
# Runtime data (mounted at run time, not needed for build) # Runtime data (mounted at run time, not needed for build)
grids grids
# Backup dir often has root-only permissions; exclude from build context
backup
# Misc # Misc
*.log *.log
.env* .env*

View File

@@ -46,6 +46,8 @@
@theme { @theme {
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif; --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, monospace; --font-mono: 'JetBrains Mono', ui-monospace, monospace;
--radius-card: 1rem;
--duration-transition: 200ms;
} }
html, html,
@@ -54,6 +56,41 @@ body,
height: 100%; height: 100%;
} }
/* Consistent focus ring for interactive elements */
@layer components {
.focus-ring:focus-visible {
outline: none;
box-shadow: 0 0 0 2px var(--color-base-100), 0 0 0 4px var(--color-primary);
}
button:focus-visible,
[type="button"]:focus-visible,
[type="submit"]:focus-visible,
[type="reset"]:focus-visible,
a[href]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible {
outline: none;
box-shadow: 0 0 0 2px var(--color-base-100), 0 0 0 4px var(--color-primary);
}
/* Unified card style */
.card-app {
@apply rounded-xl shadow-xl border border-base-300/50 bg-base-100;
}
.card-app.card-bg-base-200 {
@apply bg-base-200;
}
/* Modal box aligned with cards */
.modal-box {
@apply rounded-xl border border-base-300/50;
}
/* Primary button: subtle transition, no per-page scale */
.btn-primary {
@apply transition-[transform,color,background-color] duration-200;
}
}
@keyframes login-card-in { @keyframes login-card-in {
from { from {
opacity: 0; opacity: 0;

View File

@@ -60,7 +60,7 @@
</svg> </svg>
</summary> </summary>
<ul <ul
class="dropdown-content menu bg-base-200 rounded-box z-[1100] mt-2 w-52 border border-base-300/50 shadow-xl p-2" class="dropdown-content menu bg-base-200 rounded-xl z-[1100] mt-2 w-52 border border-base-300/50 shadow-xl p-2"
> >
<li> <li>
<NuxtLink to="/" :class="route.path === '/' ? 'active' : ''" @click="closeDropdown"> <NuxtLink to="/" :class="route.path === '/' ? 'active' : ''" @click="closeDropdown">

View File

@@ -3,7 +3,7 @@
<h1 class="text-2xl font-bold mb-6">Admin</h1> <h1 class="text-2xl font-bold mb-6">Admin</h1>
<template v-if="loading"> <template v-if="loading">
<div class="card bg-base-200 shadow-xl mb-6"> <div class="card card-app card-bg-base-200 mb-6">
<div class="card-body"> <div class="card-body">
<Skeleton class="h-6 w-24 mb-4" /> <Skeleton class="h-6 w-24 mb-4" />
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
@@ -12,7 +12,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card bg-base-200 shadow-xl mb-6"> <div class="card card-app card-bg-base-200 mb-6">
<div class="card-body"> <div class="card-body">
<Skeleton class="h-6 w-20 mb-4" /> <Skeleton class="h-6 w-20 mb-4" />
<div class="overflow-x-auto"> <div class="overflow-x-auto">
@@ -20,7 +20,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card bg-base-200 shadow-xl mb-6"> <div class="card card-app card-bg-base-200 mb-6">
<div class="card-body"> <div class="card-body">
<Skeleton class="h-6 w-16 mb-4" /> <Skeleton class="h-6 w-16 mb-4" />
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
@@ -45,7 +45,7 @@
</div> </div>
</div> </div>
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200"> <div class="card card-app card-bg-base-200 mb-6 transition-all duration-200">
<div class="card-body"> <div class="card-body">
<h2 class="card-title gap-2"> <h2 class="card-title gap-2">
<icons-icon-users /> <icons-icon-users />
@@ -88,7 +88,7 @@
</div> </div>
</div> </div>
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200"> <div class="card card-app card-bg-base-200 mb-6 transition-all duration-200">
<div class="card-body"> <div class="card-body">
<h2 class="card-title gap-2"> <h2 class="card-title gap-2">
<icons-icon-map-pin /> <icons-icon-map-pin />
@@ -145,7 +145,7 @@
</div> </div>
</div> </div>
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200"> <div class="card card-app card-bg-base-200 mb-6 transition-all duration-200">
<div class="card-body"> <div class="card-body">
<h2 class="card-title gap-2"> <h2 class="card-title gap-2">
<icons-icon-settings /> <icons-icon-settings />
@@ -191,7 +191,7 @@
</div> </div>
</div> </div>
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200"> <div class="card card-app card-bg-base-200 mb-6 transition-all duration-200">
<div class="card-body"> <div class="card-body">
<h2 class="card-title gap-2"> <h2 class="card-title gap-2">
<icons-icon-alert-triangle /> <icons-icon-alert-triangle />
@@ -226,8 +226,8 @@
</button> </button>
</form> </form>
</div> </div>
<div class="border-t border-red-500/30 pt-4 mt-1 bg-error/5 rounded-lg p-3 -mx-1"> <div class="border-t border-error/30 pt-4 mt-1 bg-error/5 rounded-lg p-3 -mx-1">
<p class="text-sm font-medium text-error/90 mb-2">Danger zone</p> <p class="text-sm font-medium text-error mb-2">Danger zone</p>
<button class="btn btn-sm btn-error min-h-11 touch-manipulation" :disabled="wiping" @click="confirmWipe"> <button class="btn btn-sm btn-error min-h-11 touch-manipulation" :disabled="wiping" @click="confirmWipe">
<span v-if="wiping" class="loading loading-spinner loading-sm" /> <span v-if="wiping" class="loading loading-spinner loading-sm" />
<span v-else>Wipe all data</span> <span v-else>Wipe all data</span>

View File

@@ -1,11 +1,11 @@
<template> <template>
<div class="container mx-auto p-4 max-w-lg"> <div class="container mx-auto p-4 max-w-2xl min-w-0">
<h1 class="text-2xl font-bold mb-6">Edit map {{ id }}</h1> <h1 class="text-2xl font-bold mb-6">Edit map {{ id }}</h1>
<form v-if="map" @submit.prevent="submit" class="flex flex-col gap-4"> <form v-if="map" @submit.prevent="submit" class="flex flex-col gap-4">
<fieldset class="fieldset"> <fieldset class="fieldset">
<label class="label" for="name">Name</label> <label class="label" for="name">Name</label>
<input id="name" v-model="form.name" type="text" class="input" required /> <input id="name" v-model="form.name" type="text" class="input min-h-11 touch-manipulation" required />
</fieldset> </fieldset>
<fieldset class="fieldset"> <fieldset class="fieldset">
<label class="label cursor-pointer gap-2"> <label class="label cursor-pointer gap-2">
@@ -21,16 +21,16 @@
</fieldset> </fieldset>
<p v-if="error" class="text-error text-sm">{{ error }}</p> <p v-if="error" class="text-error text-sm">{{ error }}</p>
<div class="flex gap-2"> <div class="flex gap-2">
<button type="submit" class="btn btn-primary" :disabled="loading"> <button type="submit" class="btn btn-primary min-h-11 touch-manipulation" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm" /> <span v-if="loading" class="loading loading-spinner loading-sm" />
<span v-else>Save</span> <span v-else>Save</span>
</button> </button>
<NuxtLink to="/admin" class="btn btn-ghost">Back</NuxtLink> <NuxtLink to="/admin" class="btn btn-ghost min-h-11 touch-manipulation">Back</NuxtLink>
</div> </div>
</form> </form>
<template v-else-if="mapsLoaded"> <template v-else-if="mapsLoaded">
<p class="text-base-content/70">Map not found.</p> <p class="text-base-content/70">Map not found.</p>
<NuxtLink to="/admin" class="btn btn-ghost mt-2">Back to Admin</NuxtLink> <NuxtLink to="/admin" class="btn btn-ghost mt-2 min-h-11 touch-manipulation">Back to Admin</NuxtLink>
</template> </template>
<p v-else class="text-base-content/70">Loading</p> <p v-else class="text-base-content/70">Loading</p>
</div> </div>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="container mx-auto p-4 max-w-lg"> <div class="container mx-auto p-4 max-w-2xl min-w-0">
<h1 class="text-2xl font-bold mb-6">{{ isNew ? 'New user' : `Edit ${username}` }}</h1> <h1 class="text-2xl font-bold mb-6">{{ isNew ? 'New user' : `Edit ${username}` }}</h1>
<form @submit.prevent="submit" class="flex flex-col gap-4"> <form @submit.prevent="submit" class="flex flex-col gap-4">
@@ -9,7 +9,7 @@
id="user" id="user"
v-model="form.user" v-model="form.user"
type="text" type="text"
class="input" class="input min-h-11 touch-manipulation"
required required
:readonly="!isNew" :readonly="!isNew"
/> />
@@ -30,15 +30,15 @@
</fieldset> </fieldset>
<p v-if="error" class="text-error text-sm">{{ error }}</p> <p v-if="error" class="text-error text-sm">{{ error }}</p>
<div class="flex gap-2"> <div class="flex gap-2">
<button type="submit" class="btn btn-primary" :disabled="loading"> <button type="submit" class="btn btn-primary min-h-11 touch-manipulation" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm" /> <span v-if="loading" class="loading loading-spinner loading-sm" />
<span v-else>Save</span> <span v-else>Save</span>
</button> </button>
<NuxtLink to="/admin" class="btn btn-ghost">Back</NuxtLink> <NuxtLink to="/admin" class="btn btn-ghost min-h-11 touch-manipulation">Back</NuxtLink>
<button <button
v-if="!isNew" v-if="!isNew"
type="button" type="button"
class="btn btn-error btn-outline ml-auto" class="btn btn-error btn-outline ml-auto min-h-11 touch-manipulation"
:disabled="loading" :disabled="loading"
@click="confirmDelete" @click="confirmDelete"
> >

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-base-200 via-base-300 to-primary/10 p-4 overflow-hidden"> <div class="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-base-200 via-base-300 to-primary/10 p-4 overflow-hidden">
<div class="card w-full max-w-sm bg-base-100 shadow-2xl ring-1 ring-base-300 login-card"> <div class="card card-app w-full max-w-sm login-card">
<div class="card-body"> <div class="card-body">
<h1 class="card-title justify-center text-2xl">HnH Map</h1> <h1 class="card-title justify-center text-2xl">HnH Map</h1>
<p class="text-center text-base-content/70 text-sm">Log in to continue</p> <p class="text-center text-base-content/70 text-sm">Log in to continue</p>
@@ -23,7 +23,7 @@
id="user" id="user"
v-model="user" v-model="user"
type="text" type="text"
class="input" class="input min-h-11 touch-manipulation"
required required
autocomplete="username" autocomplete="username"
/> />
@@ -35,7 +35,7 @@
autocomplete="current-password" autocomplete="current-password"
/> />
<p v-if="error" class="text-error text-sm">{{ error }}</p> <p v-if="error" class="text-error text-sm">{{ error }}</p>
<button type="submit" class="btn btn-primary transition-all duration-200 hover:scale-[1.02]" :disabled="loading"> <button type="submit" class="btn btn-primary min-h-11 touch-manipulation" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm" /> <span v-if="loading" class="loading loading-spinner loading-sm" />
<span v-else>Log in</span> <span v-else>Log in</span>
</button> </button>

View File

@@ -3,7 +3,7 @@
<h1 class="text-2xl font-bold mb-6">Profile</h1> <h1 class="text-2xl font-bold mb-6">Profile</h1>
<!-- User info card --> <!-- User info card -->
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200"> <div class="card card-app card-bg-base-200 mb-6 transition-all duration-200">
<div class="card-body"> <div class="card-body">
<template v-if="initialLoad"> <template v-if="initialLoad">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
@@ -80,7 +80,7 @@
</div> </div>
<!-- Upload tokens --> <!-- Upload tokens -->
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200"> <div class="card card-app card-bg-base-200 mb-6 transition-all duration-200">
<div class="card-body"> <div class="card-body">
<template v-if="initialLoad"> <template v-if="initialLoad">
<Skeleton class="h-6 w-32 mb-2" /> <Skeleton class="h-6 w-32 mb-2" />
@@ -116,14 +116,14 @@
</ul> </ul>
<p v-else class="text-sm mt-2">No tokens yet.</p> <p v-else class="text-sm mt-2">No tokens yet.</p>
<p v-if="tokenError" class="text-error text-sm mt-2">{{ tokenError }}</p> <p v-if="tokenError" class="text-error text-sm mt-2">{{ tokenError }}</p>
<button class="btn btn-primary btn-sm mt-2 min-h-11 touch-manipulation transition-all duration-200 hover:scale-[1.02]" :disabled="loadingTokens" @click="generateToken"> <button class="btn btn-primary btn-sm mt-2 min-h-11 touch-manipulation" :disabled="loadingTokens" @click="generateToken">
{{ loadingTokens ? '' : 'Generate token' }} {{ loadingTokens ? '' : 'Generate token' }}
</button> </button>
</template> </template>
</div> </div>
</div> </div>
<div class="card bg-base-200 shadow-xl mb-6 transition-all duration-200"> <div class="card card-app card-bg-base-200 mb-6 transition-all duration-200">
<div class="card-body"> <div class="card-body">
<h2 class="card-title gap-2"> <h2 class="card-title gap-2">
<icons-icon-settings /> <icons-icon-settings />
@@ -136,7 +136,7 @@
autocomplete="new-password" autocomplete="new-password"
/> />
<p v-if="passMsg" class="text-sm" :class="passOk ? 'text-success' : 'text-error'">{{ passMsg }}</p> <p v-if="passMsg" class="text-sm" :class="passOk ? 'text-success' : 'text-error'">{{ passMsg }}</p>
<button type="submit" class="btn btn-sm min-h-11 touch-manipulation transition-all duration-200 hover:scale-[1.02]" :disabled="loadingPass">Save password</button> <button type="submit" class="btn btn-sm min-h-11 touch-manipulation" :disabled="loadingPass">Save password</button>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="min-h-screen flex flex-col items-center justify-center bg-base-200 p-4"> <div class="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-base-200 via-base-300 to-primary/10 p-4 overflow-hidden">
<div class="card w-full max-w-sm bg-base-100 shadow-xl"> <div class="card card-app w-full max-w-sm login-card">
<div class="card-body"> <div class="card-body">
<h1 class="card-title justify-center">First-time setup</h1> <h1 class="card-title justify-center">First-time setup</h1>
<p class="text-sm text-base-content/80"> <p class="text-sm text-base-content/80">
@@ -15,7 +15,7 @@
autocomplete="current-password" autocomplete="current-password"
/> />
<p v-if="error" class="text-error text-sm">{{ error }}</p> <p v-if="error" class="text-error text-sm">{{ error }}</p>
<button type="submit" class="btn btn-primary" :disabled="loading"> <button type="submit" class="btn btn-primary min-h-11 touch-manipulation" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm" /> <span v-if="loading" class="loading loading-spinner loading-sm" />
<span v-else>Create and log in</span> <span v-else>Create and log in</span>
</button> </button>