mirror of
https://github.com/mmahdium/TBW.git
synced 2026-08-14 16:22:48 +03:30
UI improvements
This commit is contained in:
@@ -19,12 +19,21 @@ const alreadyAdded = computed(() =>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-sm hover:shadow-md transition rounded-sm w-full h-full">
|
||||
<router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
|
||||
<figure v-motion-fade-visible-once
|
||||
class="overflow-hidden flex items-center justify-center bg-gray-100 aspect-[2/3] rounded-t-sm"
|
||||
<div
|
||||
class="card relative w-full h-full overflow-hidden
|
||||
bg-white/70 backdrop-blur-md border border-gray-200/60
|
||||
shadow-md hover:shadow-xl transition-all duration-300"
|
||||
v-motion-fade-visible-once
|
||||
>
|
||||
<span v-if="props.loading" class="loading loading-ring loading-xl text-primary"></span>
|
||||
<!-- Poster -->
|
||||
<router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
|
||||
<figure
|
||||
class="overflow-hidden flex items-center justify-center aspect-[2/3] bg-gray-50"
|
||||
>
|
||||
<span
|
||||
v-if="props.loading"
|
||||
class="loading loading-ring loading-lg text-primary"
|
||||
></span>
|
||||
|
||||
<div v-else-if="imageLoadFailed" class="flex items-center justify-center">
|
||||
<svg
|
||||
@@ -33,7 +42,7 @@ const alreadyAdded = computed(() =>
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-12 h-12 text-gray-400"
|
||||
class="w-12 h-12 text-gray-300"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -47,7 +56,7 @@ const alreadyAdded = computed(() =>
|
||||
v-show="!props.loading && !imageLoadFailed"
|
||||
:src="props.movie.Poster"
|
||||
:alt="props.movie.Title"
|
||||
class="object-cover w-full h-full"
|
||||
class="object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
|
||||
@load="emit('loaded', props.movie.imdbID)"
|
||||
@error="
|
||||
() => {
|
||||
@@ -59,21 +68,40 @@ const alreadyAdded = computed(() =>
|
||||
</figure>
|
||||
</router-link>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="card-body p-4 flex flex-col">
|
||||
<router-link :to="{ name: 'details', params: { id: props.movie.imdbID } }">
|
||||
<h2 class="card-title text-lg">{{ props.movie.Title }}</h2>
|
||||
<p class="text-sm text-gray-500">{{ props.movie.Year }}</p>
|
||||
<h2
|
||||
class="card-title text-base font-semibold
|
||||
bg-gradient-to-r from-gray-700 to-gray-500
|
||||
bg-clip-text text-transparent"
|
||||
>
|
||||
{{ props.movie.Title }}
|
||||
</h2>
|
||||
<p class="text-sm text-gray-400">{{ props.movie.Year }}</p>
|
||||
</router-link>
|
||||
|
||||
<div class="card-actions justify-end mt-auto">
|
||||
<button
|
||||
v-if="!alreadyAdded"
|
||||
class="btn btn-sm btn-primary"
|
||||
class="btn btn-sm px-4
|
||||
bg-gradient-to-r from-gray-100 to-gray-200
|
||||
border border-gray-300 text-gray-700
|
||||
hover:from-gray-200 hover:to-gray-300 hover:text-gray-900
|
||||
transition"
|
||||
@click="store.addMovie(props.movie)"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button v-else class="btn btn-sm btn-error" @click="store.removeMovie(props.movie.imdbID)">
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-sm px-4
|
||||
bg-gradient-to-r from-red-50 to-red-100
|
||||
border border-red-200 text-red-600
|
||||
hover:from-red-100 hover:to-red-200 hover:text-red-700
|
||||
transition"
|
||||
@click="store.removeMovie(props.movie.imdbID)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useMoviesStore } from '@/stores/movies'
|
||||
const props = defineProps<{ movie: MovieDetailsType | undefined }>()
|
||||
|
||||
const imageLoaded = ref(false)
|
||||
|
||||
const store = useMoviesStore()
|
||||
|
||||
const alreadyAdded = computed(() =>
|
||||
@@ -16,17 +15,26 @@ const alreadyAdded = computed(() =>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ErrorAlert class="" v-if="props.movie?.Error" :message="props.movie.ErrorMessage" />
|
||||
<ErrorAlert v-if="props.movie?.Error" :message="props.movie.ErrorMessage" />
|
||||
|
||||
<div v-else class="hero bg-base-200 min-h-screen rounded-md">
|
||||
<!-- Loading state for hero content -->
|
||||
<div v-if="props.movie === undefined" class="hero-content">
|
||||
<div
|
||||
v-else
|
||||
class="min-h-screen flex items-center justify-center px-6 py-12
|
||||
bg-gradient-to-b from-gray-50 to-white"
|
||||
>
|
||||
<!-- Loading state -->
|
||||
<div v-if="props.movie === undefined" class="flex justify-center items-center">
|
||||
<span class="loading loading-ring loading-lg text-primary"></span>
|
||||
</div>
|
||||
|
||||
<!-- Actual hero content -->
|
||||
<div v-else class="hero bg-base-200 min-h-screen rounded-md" v-motion-fade-visible-once>
|
||||
<div class="hero-content flex-col lg:flex-row gap-12">
|
||||
<!-- Hero content -->
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col lg:flex-row gap-12 items-center max-w-6xl w-full
|
||||
bg-white/70 backdrop-blur-md border border-gray-200/60
|
||||
shadow-md rounded-xl p-8 transition"
|
||||
v-motion-fade-visible-once
|
||||
>
|
||||
<!-- Poster -->
|
||||
<figure class="flex-shrink-0">
|
||||
<span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span>
|
||||
@@ -34,41 +42,62 @@ const alreadyAdded = computed(() =>
|
||||
v-show="imageLoaded"
|
||||
:src="props.movie.Poster"
|
||||
alt="Poster"
|
||||
class="w-full max-w-lg rounded-lg shadow-2xl"
|
||||
class="w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
|
||||
@load="imageLoaded = true"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
<!-- Text -->
|
||||
<div class="max-w-2xl">
|
||||
<h1 class="text-5xl font-bold">
|
||||
<div class="flex-1">
|
||||
<h1 class="text-4xl font-bold text-gray-800 mb-4">
|
||||
{{ props.movie.Title }}
|
||||
<span class="text-gray-500 text-sm">({{ props.movie.Year }})</span>
|
||||
<span class="text-gray-400 text-lg font-normal">({{ props.movie.Year }})</span>
|
||||
</h1>
|
||||
<p class="py-6">{{ props.movie.Plot }}</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="badge badge-dash badge-primary">
|
||||
|
||||
<p class="text-gray-600 leading-relaxed mb-6">{{ props.movie.Plot }}</p>
|
||||
|
||||
<!-- Badges -->
|
||||
<div class="flex flex-wrap gap-2 mb-6">
|
||||
<span
|
||||
class="px-3 py-1 rounded-md text-sm
|
||||
bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||
>
|
||||
Language: {{ props.movie.Language }}
|
||||
</span>
|
||||
<span class="badge badge-dash badge-secondary">
|
||||
<span
|
||||
class="px-3 py-1 rounded-md text-sm
|
||||
bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||
>
|
||||
Country: {{ props.movie.Country }}
|
||||
</span>
|
||||
<span class="badge badge-dash badge-info">
|
||||
<span
|
||||
class="px-3 py-1 rounded-md text-sm
|
||||
bg-gradient-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300"
|
||||
>
|
||||
IMDB Rating: {{ props.movie.imdbRating }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!alreadyAdded" class="card-actions mt-4">
|
||||
<button class="btn -sm btn-primary" @click="store.addMovie(props.movie)">
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="card-actions">
|
||||
<button
|
||||
v-if="!alreadyAdded"
|
||||
class="btn px-6 bg-gradient-to-r from-gray-100 to-gray-200
|
||||
border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300"
|
||||
@click="store.addMovie(props.movie)"
|
||||
>
|
||||
Add to list
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="card-actions mt-4">
|
||||
<button class="btn btn-error" @click="store.removeMovie(props.movie.imdbID)">
|
||||
<button
|
||||
v-else
|
||||
class="btn px-6 bg-gradient-to-r from-red-50 to-red-100
|
||||
border border-red-200 text-red-600 hover:from-red-100 hover:to-red-200"
|
||||
@click="store.removeMovie(props.movie.imdbID)"
|
||||
>
|
||||
Remove from list
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -10,19 +10,24 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): void }>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="props.movies.length === 0" class="text-center text-gray-500 py-12">
|
||||
<p
|
||||
v-if="props.movies.length === 0"
|
||||
class="text-center text-gray-500 py-12 bg-gray-50/60 rounded-lg border border-gray-200"
|
||||
>
|
||||
No movies found.<br />
|
||||
<RouterLink to="/add" class="text-black font-bold">Add a movie</RouterLink>
|
||||
<RouterLink to="/add" class="text-gray-700 font-semibold hover:text-gray-900 transition">
|
||||
Add a movie
|
||||
</RouterLink>
|
||||
</p>
|
||||
|
||||
<ul v-auto-animate
|
||||
<ul
|
||||
v-auto-animate
|
||||
v-else
|
||||
class="grid gap-6 grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"
|
||||
class="grid gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 lg:gap-8"
|
||||
>
|
||||
<li v-for="movie in props.movies" :key="movie.imdbID" v-auto-animate>
|
||||
<MovieCard
|
||||
@@ -33,19 +38,13 @@ const emit = defineEmits<{ (e: 'loaded', id: string): void; (e: 'loadMore'): voi
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
v-if="props.isSearch && props.movies.length > 0"
|
||||
class="flex justify-center mt-8"
|
||||
>
|
||||
<div v-if="props.isSearch && props.movies.length > 0" class="flex justify-center mt-8">
|
||||
<button
|
||||
class="btn btn-outline btn-secondary"
|
||||
class="btn px-6 bg-gradient-to-r from-gray-100 to-gray-200 border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300 hover:text-gray-900 disabled:opacity-50"
|
||||
@click="emit('loadMore')"
|
||||
:disabled="props.loadingMore"
|
||||
>
|
||||
<span
|
||||
v-if="props.loadingMore"
|
||||
class="loading loading-spinner loading-xs mr-2"
|
||||
></span>
|
||||
<span v-if="props.loadingMore" class="loading loading-spinner loading-xs mr-2"></span>
|
||||
Load more
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+44
-13
@@ -1,11 +1,22 @@
|
||||
<template>
|
||||
<div class="navbar bg-base-100 shadow-sm sticky top-0 z-10">
|
||||
<div
|
||||
class="navbar sticky top-0 z-20
|
||||
bg-white/70 backdrop-blur-md border-b border-gray-200/60
|
||||
shadow-sm transition"
|
||||
>
|
||||
<!-- Left -->
|
||||
<div class="navbar-start">
|
||||
<!-- Mobile dropdown -->
|
||||
<div class="dropdown">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden" aria-label="Menu">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="btn btn-ghost lg:hidden"
|
||||
aria-label="Menu"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
class="h-6 w-6 text-gray-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -20,24 +31,44 @@
|
||||
</div>
|
||||
<ul
|
||||
tabindex="-1"
|
||||
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow"
|
||||
class="menu menu-sm dropdown-content
|
||||
mt-3 w-52 p-2 rounded-lg shadow-md
|
||||
bg-white/80 backdrop-blur-md border border-gray-200/60"
|
||||
>
|
||||
<li><RouterLink class="text-xl" to="/">Home</RouterLink></li>
|
||||
<li><RouterLink class="text-xl" to="/list">List</RouterLink></li>
|
||||
<li><RouterLink class="text-xl" to="/add">Add</RouterLink></li>
|
||||
<li><RouterLink class="text-gray-700 hover:text-gray-900" to="/">Home</RouterLink></li>
|
||||
<li><RouterLink class="text-gray-700 hover:text-gray-900" to="/list">List</RouterLink></li>
|
||||
<li><RouterLink class="text-gray-700 hover:text-gray-900" to="/add">Add</RouterLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
<RouterLink class="btn btn-ghost text-xl" to="/">To Vue</RouterLink>
|
||||
|
||||
<!-- Logo -->
|
||||
<RouterLink
|
||||
class="btn btn-ghost normal-case text-2xl font-bold
|
||||
bg-gradient-to-r from-gray-700 to-gray-500 bg-clip-text text-transparent"
|
||||
to="/"
|
||||
>
|
||||
To Vue
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- Center (desktop menu) -->
|
||||
<div class="navbar-center hidden lg:flex">
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
<li><RouterLink class="text-xl" to="/">Home</RouterLink></li>
|
||||
<li><RouterLink class="text-xl" to="/list">List</RouterLink></li>
|
||||
<li><RouterLink class="text-xl" to="/add">Add</RouterLink></li>
|
||||
<ul class="menu menu-horizontal px-1 space-x-4">
|
||||
<li><RouterLink class="text-gray-700 hover:text-gray-900 transition" to="/">Home</RouterLink></li>
|
||||
<li><RouterLink class="text-gray-700 hover:text-gray-900 transition" to="/list">List</RouterLink></li>
|
||||
<li><RouterLink class="text-gray-700 hover:text-gray-900 transition" to="/add">Add</RouterLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Right -->
|
||||
<div class="navbar-end">
|
||||
<RouterLink class="btn" to="/add">Get Started</RouterLink>
|
||||
<RouterLink
|
||||
class="btn px-5 bg-gradient-to-r from-gray-100 to-gray-200
|
||||
border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300"
|
||||
to="/add"
|
||||
>
|
||||
Get Started
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ref } from 'vue'
|
||||
const props = defineProps<{ modelValue: string }>()
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', value: string): void; (e: 'submit'): void }>()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const localQuery = ref(props.modelValue)
|
||||
|
||||
function onSubmit() {
|
||||
@@ -13,30 +12,51 @@ function onSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<div class="join flex justify-center mb-8">
|
||||
<label class="input join-item">
|
||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="2.5"
|
||||
<form @submit.prevent="onSubmit" class="flex justify-center mb-8">
|
||||
<div
|
||||
class="flex items-center w-full max-w-md
|
||||
bg-white/70 backdrop-blur-md border border-gray-200/60
|
||||
shadow-sm hover:shadow-md transition
|
||||
rounded-lg overflow-hidden"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<span class="pl-3 text-gray-400">
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.3-4.3"></path>
|
||||
</g>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<!-- Input -->
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search for a movie"
|
||||
required
|
||||
:value="props.modelValue"
|
||||
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
||||
class="flex-1 px-3 py-2 bg-transparent outline-none text-gray-700 placeholder-gray-400"
|
||||
/>
|
||||
</label>
|
||||
<!-- <button class="btn btn-neutral join-item">Search</button> -->
|
||||
|
||||
<!-- Optional button -->
|
||||
<!--
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 bg-gradient-to-r from-gray-100 to-gray-200
|
||||
border-l border-gray-200 text-gray-600 hover:text-gray-800
|
||||
transition"
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
-->
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
+24
-6
@@ -102,15 +102,24 @@ watch(searchQuery, () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-3xl font-bold text-center mb-8">Add a Movie</h1>
|
||||
<div class="container mx-auto px-4 py-12" v-motion-fade-visible-once>
|
||||
<!-- Title -->
|
||||
<h1
|
||||
class="text-4xl font-extrabold text-center mb-10
|
||||
bg-gradient-to-r from-gray-700 to-gray-500 bg-clip-text text-transparent"
|
||||
>
|
||||
Add a Movie
|
||||
</h1>
|
||||
|
||||
<!-- Search bar -->
|
||||
<SearchBar v-model="searchQuery" @submit="searchMovie" />
|
||||
|
||||
<div v-if="isSearching" class="flex justify-center my-12">
|
||||
<span class="loading loading-ring loading-lg"></span>
|
||||
<!-- Loading spinner -->
|
||||
<div v-if="isSearching" class="flex justify-center my-16">
|
||||
<span class="loading loading-ring loading-lg text-primary"></span>
|
||||
</div>
|
||||
|
||||
<!-- Movie list -->
|
||||
<MovieList
|
||||
v-else-if="movies && movies.length > 0"
|
||||
:movies="movies"
|
||||
@@ -121,10 +130,19 @@ watch(searchQuery, () => {
|
||||
:is-search="true"
|
||||
/>
|
||||
|
||||
<p v-else class="text-center text-gray-500">Search for a movie to add it to your list</p>
|
||||
<!-- Empty state -->
|
||||
<p
|
||||
v-else
|
||||
class="text-center text-gray-500 mt-16
|
||||
bg-gray-50/60 border border-gray-200 rounded-lg py-12"
|
||||
>
|
||||
Search for a movie to add it to your list
|
||||
</p>
|
||||
|
||||
<div class="flex justify-center pt-2">
|
||||
<!-- Error alert -->
|
||||
<div class="flex justify-center pt-4">
|
||||
<ErrorAlert v-if="seachError" :message="seachError" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<MovieDetails :movie="movie" />
|
||||
<MovieDetails :movie="movie" v-motion-fade-visible-once />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+28
-15
@@ -1,40 +1,53 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<h1 class="text-5xl font-bold text-center mb-12">
|
||||
Welcome to <br /><span class="text-6xl font-bold text-center gradient-text"
|
||||
>To Be Watched</span
|
||||
>
|
||||
<div class="container mx-auto px-4 py-16" v-motion-fade-visible-once>
|
||||
<div class="flex flex-col items-center justify-center text-center">
|
||||
<h1 class="text-5xl font-bold mb-8 leading-tight">
|
||||
Welcome to <br />
|
||||
<span class="text-6xl font-extrabold gradient-text">
|
||||
To Be Watched
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-center mb-12">
|
||||
A simple and beautiful movie list app built with Vue 3 and Tailwind CSS (daisyUI).
|
||||
<p class="text-lg text-gray-600 max-w-2xl mb-12">
|
||||
A simple and beautiful movie list app built with Vue 3 and Tailwind CSS (daisyUI).
|
||||
</p>
|
||||
<RouterLink to="/add" class="btn btn-primary btn-lg">Get Started</RouterLink>
|
||||
|
||||
<RouterLink
|
||||
to="/add"
|
||||
class="btn px-8 py-3 text-lg font-medium
|
||||
bg-gradient-to-r from-gray-100 to-gray-200
|
||||
border border-gray-300 text-gray-700
|
||||
hover:from-gray-200 hover:to-gray-300 hover:text-gray-900
|
||||
transition"
|
||||
>
|
||||
Get Started
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.gradient-text {
|
||||
background: linear-gradient(to right, black 0%, red 50%, blue 100%);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#6b7280 0%, /* gray-500 */
|
||||
#818cf8 50%, /* indigo-400 */
|
||||
#22d3ee 100% /* cyan-400 */
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-size: 200% auto;
|
||||
animation: gradient 3s ease infinite alternate;
|
||||
animation: gradientShift 6s ease infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
@keyframes gradientShift {
|
||||
0% {
|
||||
background-position: left center;
|
||||
}
|
||||
100% {
|
||||
background-position: right center;
|
||||
}
|
||||
50% {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,7 +17,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="container mx-auto px-4 py-8" v-motion-fade-visible-once>
|
||||
<MovieList
|
||||
v-auto-animate
|
||||
v-if="store.movieList"
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-center">Not found</h1>
|
||||
<router-link to="/">Home</router-link>
|
||||
<div class="container mx-auto px-4 py-8"></div>
|
||||
<div
|
||||
class="min-h-screen flex flex-col items-center justify-center bg-gradient-to-b from-gray-50 to-white px-4"
|
||||
>
|
||||
<!-- Big 404 -->
|
||||
<h1
|
||||
class="text-8xl font-extrabold mb-4 bg-gradient-to-r from-gray-700 to-gray-500 bg-clip-text text-transparent"
|
||||
>
|
||||
404
|
||||
</h1>
|
||||
|
||||
<!-- Message -->
|
||||
<p class="text-xl text-gray-600 mb-8 text-center">
|
||||
Oops! The page you’re looking for doesn’t exist.
|
||||
</p>
|
||||
|
||||
<!-- Button back home -->
|
||||
<RouterLink
|
||||
to="/"
|
||||
class="btn px-6 py-3 bg-gradient-to-r from-gray-100 to-gray-200 border border-gray-300 text-gray-700 hover:from-gray-200 hover:to-gray-300 hover:text-gray-900 transition"
|
||||
>
|
||||
Back to Home
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user