minio #1
19 changed files with 837 additions and 103 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -15,7 +15,6 @@
|
||||||
"@types/react": "^18.3.5",
|
"@types/react": "^18.3.5",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"astro": "^5.5.5",
|
"astro": "^5.5.5",
|
||||||
"bootstrap": "^5.3.3",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
|
|
56
src/components/Logo.astro
Normal file
56
src/components/Logo.astro
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className = "" } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`logo ${className}`}>
|
||||||
|
<div class="logo-icon">
|
||||||
|
<div class="play-icon"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: var(--glass-blur);
|
||||||
|
border-radius: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-icon {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 15px 0 15px 25px;
|
||||||
|
border-color: transparent transparent transparent #ffffff;
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,44 +1,56 @@
|
||||||
---
|
---
|
||||||
|
import Alert from "../../ui/Alert.astro";
|
||||||
|
import Logo from "../../Logo.astro";
|
||||||
|
import Button from "../../ui/Button.astro";
|
||||||
|
import Input from "../../ui/Input.astro";
|
||||||
interface Props {
|
interface Props {
|
||||||
error: string | null;
|
error?: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { error } = Astro.props as Props;
|
const { error, username = "", password = "" } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="card">
|
<Logo />
|
||||||
<div class="card-body">
|
<hr />
|
||||||
{
|
{
|
||||||
error && (
|
error && (
|
||||||
<div class="alert alert-danger" style="text-align: center">
|
<Alert type="error" class="text-center">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</Alert>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<form method="post" action="/login">
|
<form method="post" action="/login">
|
||||||
<div class="form-floating mb-3">
|
<div class="form-group">
|
||||||
<input
|
<label class="form-label" for="username">Nom d'utilisateur</label>
|
||||||
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
|
||||||
id="username"
|
id="username"
|
||||||
name="username"
|
name="username"
|
||||||
|
value={username}
|
||||||
|
placeholder="Entrez votre nom d'utilisateur"
|
||||||
/>
|
/>
|
||||||
<label class="form-label">Nom d'utilisateur</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-floating mb-3">
|
<div class="form-group">
|
||||||
<input
|
<label class="form-label" for="password">Mot de passe</label>
|
||||||
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
class="form-control"
|
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
|
value={password}
|
||||||
|
placeholder="Entrez votre mot de passe"
|
||||||
/>
|
/>
|
||||||
<label class="form-label">Mot de passe</label>
|
|
||||||
</div>
|
|
||||||
<div class="d-grid">
|
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
Connexion
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Button type="submit" variant="primary" class="w-100"> Connexion </Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
<style>
|
||||||
|
h2 {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,22 +1,36 @@
|
||||||
---
|
---
|
||||||
import LoginForm from "./LoginForm.astro";
|
import LoginForm from "./LoginForm.astro";
|
||||||
|
|
||||||
const req = Astro.request;
|
type LoginError =
|
||||||
const isPost = req.method === "POST";
|
| "MISSING_FIELDS"
|
||||||
|
| "INVALID_CREDENTIALS"
|
||||||
|
| "SERVER_ERROR"
|
||||||
|
| "NOT_FOUND";
|
||||||
|
|
||||||
let username = "";
|
const ERROR_MESSAGES: Record<LoginError, string> = {
|
||||||
let password = "";
|
MISSING_FIELDS: "Tous les champs sont requis",
|
||||||
let error = null;
|
INVALID_CREDENTIALS: "Identifiants invalides",
|
||||||
|
NOT_FOUND: "Compte non trouvé",
|
||||||
|
SERVER_ERROR: "Une erreur est survenue",
|
||||||
|
};
|
||||||
|
|
||||||
if (isPost) {
|
interface LoginResponse {
|
||||||
const form = await req.formData();
|
jwt: string;
|
||||||
username = form.get("username")?.toString().trim() ?? "";
|
|
||||||
password = form.get("password")?.toString().trim() ?? "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginResult {
|
||||||
|
error?: LoginError;
|
||||||
|
jwt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogin(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
): Promise<LoginResult> {
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
error = "Tous les champs sont requis";
|
return { error: "MISSING_FIELDS" };
|
||||||
} else {
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${import.meta.env.API_URL}/accounts/login`, {
|
const res = await fetch(`${import.meta.env.API_URL}/accounts/login`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -27,24 +41,45 @@ if (!username || !password) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
if (res.status === 401) {
|
return {
|
||||||
error = "Identifiants invalides";
|
error:
|
||||||
} else {
|
res.status === 401
|
||||||
error = "Une erreur est survenue";
|
? "INVALID_CREDENTIALS"
|
||||||
|
: res.status === 404
|
||||||
|
? "NOT_FOUND"
|
||||||
|
: "SERVER_ERROR",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const data = await res.json();
|
const data = (await res.json()) as LoginResponse;
|
||||||
Astro.cookies.set("jwt", data.jwt, {
|
return { jwt: data.jwt };
|
||||||
|
} catch (err) {
|
||||||
|
return { error: "SERVER_ERROR" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = Astro.request;
|
||||||
|
let error: string | undefined;
|
||||||
|
|
||||||
|
if (req.method === "POST") {
|
||||||
|
const form = await req.formData();
|
||||||
|
const username = form.get("username")?.toString().trim() ?? "";
|
||||||
|
const password = form.get("password")?.toString().trim() ?? "";
|
||||||
|
|
||||||
|
const { error: loginError, jwt } = await handleLogin(username, password);
|
||||||
|
|
||||||
|
if (loginError) {
|
||||||
|
error = ERROR_MESSAGES[loginError];
|
||||||
|
} else if (jwt) {
|
||||||
|
Astro.cookies.set("jwt", jwt, {
|
||||||
path: "/",
|
path: "/",
|
||||||
secure: true,
|
secure: true,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
|
sameSite: "strict",
|
||||||
});
|
});
|
||||||
|
|
||||||
return Astro.redirect("/");
|
return Astro.redirect("/");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
error = "Une erreur est survenue";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
15
src/components/ui/Alert.astro
Normal file
15
src/components/ui/Alert.astro
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
type?: "error" | "warning" | "success";
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type = "error", class: className = "" } = Astro.props;
|
||||||
|
|
||||||
|
const typeClass = `message-${type}`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class:list={["message", typeClass, className]}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
23
src/components/ui/Button.astro
Normal file
23
src/components/ui/Button.astro
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
type?: "button" | "submit" | "reset";
|
||||||
|
variant?: "default" | "primary" | "danger" | "warning";
|
||||||
|
class?: string;
|
||||||
|
fullWidth?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
type = "button",
|
||||||
|
variant = "default",
|
||||||
|
class: className = "",
|
||||||
|
fullWidth = false,
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
const variantClass = variant !== "default" ? `btn-${variant}` : "";
|
||||||
|
const widthClass = fullWidth ? "w-100" : "";
|
||||||
|
---
|
||||||
|
|
||||||
|
<button type={type} class:list={["btn", variantClass, widthClass, className]}>
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
|
30
src/components/ui/Card.astro
Normal file
30
src/components/ui/Card.astro
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
class?: string;
|
||||||
|
padding?: "sm" | "md" | "lg";
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className = "", padding = "md" } = Astro.props;
|
||||||
|
|
||||||
|
const paddingMap = {
|
||||||
|
sm: "1rem",
|
||||||
|
md: "1.5rem",
|
||||||
|
lg: "2rem",
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class:list={["card", className]} style={`padding: ${paddingMap[padding]}`}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card :global(h2) {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
68
src/components/ui/Input.astro
Normal file
68
src/components/ui/Input.astro
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
label?: string;
|
||||||
|
type?: "text" | "password" | "email" | "number";
|
||||||
|
name: string;
|
||||||
|
id?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
value?: string | number;
|
||||||
|
required?: boolean;
|
||||||
|
error?: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
label,
|
||||||
|
type = "text",
|
||||||
|
name,
|
||||||
|
id = name,
|
||||||
|
placeholder = "",
|
||||||
|
value = "",
|
||||||
|
required = false,
|
||||||
|
error,
|
||||||
|
class: className = "",
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class:list={["form-group", className]}>
|
||||||
|
{
|
||||||
|
label && (
|
||||||
|
<label class="form-label" for={id}>
|
||||||
|
{label}
|
||||||
|
{required && <span class="required">*</span>}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
id={id}
|
||||||
|
name={name}
|
||||||
|
class:list={["input", { "input-error": error }]}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={value}
|
||||||
|
required={required}
|
||||||
|
/>
|
||||||
|
{error && <div class="input-error-message">{error}</div>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.required {
|
||||||
|
color: var(--error-color);
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-error {
|
||||||
|
border-color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-error:focus {
|
||||||
|
border-color: var(--error-color);
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 107, 107, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-error-message {
|
||||||
|
color: var(--error-color);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -9,14 +9,17 @@ const { title } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<RootLayout title={title}>
|
<RootLayout title={title}>
|
||||||
<div
|
<div class="empty-layout">
|
||||||
class="container-fluid d-flex align-items-center justify-content-center"
|
|
||||||
style="min-height: 100vh;"
|
|
||||||
>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-auto">
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</RootLayout>
|
</RootLayout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.empty-layout {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -11,8 +11,14 @@ interface Props {
|
||||||
|
|
||||||
const { title } = Astro.props;
|
const { title } = Astro.props;
|
||||||
|
|
||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
// Import des styles
|
||||||
import "../styles/index.css";
|
import "../styles/index.css";
|
||||||
|
import "../styles/grid.css";
|
||||||
|
import "../styles/components/buttons.css";
|
||||||
|
import "../styles/components/cards.css";
|
||||||
|
import "../styles/components/forms.css";
|
||||||
|
import "../styles/components/alerts.css";
|
||||||
|
import "../styles/utils.css";
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
@ -39,7 +45,3 @@ import "../styles/index.css";
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
|
||||||
import "bootstrap/dist/js/bootstrap";
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,8 +1,23 @@
|
||||||
---
|
---
|
||||||
import LoginFormData from "../components/forms/login/LoginFormData.astro";
|
import LoginFormData from "../components/forms/login/LoginFormData.astro";
|
||||||
import EmptyLayout from "../layouts/EmptyLayout.astro";
|
import EmptyLayout from "../layouts/EmptyLayout.astro";
|
||||||
|
import Card from "../components/ui/Card.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<EmptyLayout title="Trepa - Connexion">
|
<EmptyLayout title="Connexion">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-sm-12 col-md-6 col-lg-4">
|
||||||
|
<Card>
|
||||||
<LoginFormData />
|
<LoginFormData />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</EmptyLayout>
|
</EmptyLayout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
24
src/styles/components/alerts.css
Normal file
24
src/styles/components/alerts.css
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/* Styles des messages d'alerte */
|
||||||
|
.message {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-error {
|
||||||
|
background: rgba(255, 107, 107, 0.1);
|
||||||
|
border: 1px solid var(--error-color);
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-warning {
|
||||||
|
background: rgba(255, 217, 61, 0.1);
|
||||||
|
border: 1px solid var(--warning-color);
|
||||||
|
color: var(--warning-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-success {
|
||||||
|
background: rgba(107, 203, 119, 0.1);
|
||||||
|
border: 1px solid var(--success-color);
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
33
src/styles/components/buttons.css
Normal file
33
src/styles/components/buttons.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/* Styles des boutons */
|
||||||
|
.btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-speed);
|
||||||
|
background: var(--background-card);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
backdrop-filter: var(--glass-blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
color: var(--background-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--error-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background: var(--warning-color);
|
||||||
|
color: var(--background-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
8
src/styles/components/cards.css
Normal file
8
src/styles/components/cards.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/* Styles des cartes */
|
||||||
|
.card {
|
||||||
|
background: var(--background-card);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 1.5rem;
|
||||||
|
backdrop-filter: var(--glass-blur);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
27
src/styles/components/forms.css
Normal file
27
src/styles/components/forms.css
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/* Styles des formulaires */
|
||||||
|
.input {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: var(--background-card);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
backdrop-filter: var(--glass-blur);
|
||||||
|
width: 100%;
|
||||||
|
transition: all var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
box-shadow: 0 0 0 2px rgba(134, 227, 206, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
260
src/styles/grid.css
Normal file
260
src/styles/grid.css
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
/* Système de grille */
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: var(--container-padding);
|
||||||
|
padding-left: var(--container-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.container {
|
||||||
|
max-width: var(--breakpoint-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.container {
|
||||||
|
max-width: var(--breakpoint-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.container {
|
||||||
|
max-width: var(--breakpoint-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.container {
|
||||||
|
max-width: var(--breakpoint-xl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-right: calc(-1 * var(--grid-gutter));
|
||||||
|
margin-left: calc(-1 * var(--grid-gutter));
|
||||||
|
}
|
||||||
|
|
||||||
|
[class^="col-"] {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-right: var(--grid-gutter);
|
||||||
|
padding-left: var(--grid-gutter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Colonnes */
|
||||||
|
.col {
|
||||||
|
flex: 1 0 0%;
|
||||||
|
}
|
||||||
|
.col-auto {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Small (sm) */
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.col-sm-1 {
|
||||||
|
flex: 0 0 var(--col-1);
|
||||||
|
max-width: var(--col-1);
|
||||||
|
}
|
||||||
|
.col-sm-2 {
|
||||||
|
flex: 0 0 var(--col-2);
|
||||||
|
max-width: var(--col-2);
|
||||||
|
}
|
||||||
|
.col-sm-3 {
|
||||||
|
flex: 0 0 var(--col-3);
|
||||||
|
max-width: var(--col-3);
|
||||||
|
}
|
||||||
|
.col-sm-4 {
|
||||||
|
flex: 0 0 var(--col-4);
|
||||||
|
max-width: var(--col-4);
|
||||||
|
}
|
||||||
|
.col-sm-5 {
|
||||||
|
flex: 0 0 var(--col-5);
|
||||||
|
max-width: var(--col-5);
|
||||||
|
}
|
||||||
|
.col-sm-6 {
|
||||||
|
flex: 0 0 var(--col-6);
|
||||||
|
max-width: var(--col-6);
|
||||||
|
}
|
||||||
|
.col-sm-7 {
|
||||||
|
flex: 0 0 var(--col-7);
|
||||||
|
max-width: var(--col-7);
|
||||||
|
}
|
||||||
|
.col-sm-8 {
|
||||||
|
flex: 0 0 var(--col-8);
|
||||||
|
max-width: var(--col-8);
|
||||||
|
}
|
||||||
|
.col-sm-9 {
|
||||||
|
flex: 0 0 var(--col-9);
|
||||||
|
max-width: var(--col-9);
|
||||||
|
}
|
||||||
|
.col-sm-10 {
|
||||||
|
flex: 0 0 var(--col-10);
|
||||||
|
max-width: var(--col-10);
|
||||||
|
}
|
||||||
|
.col-sm-11 {
|
||||||
|
flex: 0 0 var(--col-11);
|
||||||
|
max-width: var(--col-11);
|
||||||
|
}
|
||||||
|
.col-sm-12 {
|
||||||
|
flex: 0 0 var(--col-12);
|
||||||
|
max-width: var(--col-12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Medium (md) */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.col-md-1 {
|
||||||
|
flex: 0 0 var(--col-1);
|
||||||
|
max-width: var(--col-1);
|
||||||
|
}
|
||||||
|
.col-md-2 {
|
||||||
|
flex: 0 0 var(--col-2);
|
||||||
|
max-width: var(--col-2);
|
||||||
|
}
|
||||||
|
.col-md-3 {
|
||||||
|
flex: 0 0 var(--col-3);
|
||||||
|
max-width: var(--col-3);
|
||||||
|
}
|
||||||
|
.col-md-4 {
|
||||||
|
flex: 0 0 var(--col-4);
|
||||||
|
max-width: var(--col-4);
|
||||||
|
}
|
||||||
|
.col-md-5 {
|
||||||
|
flex: 0 0 var(--col-5);
|
||||||
|
max-width: var(--col-5);
|
||||||
|
}
|
||||||
|
.col-md-6 {
|
||||||
|
flex: 0 0 var(--col-6);
|
||||||
|
max-width: var(--col-6);
|
||||||
|
}
|
||||||
|
.col-md-7 {
|
||||||
|
flex: 0 0 var(--col-7);
|
||||||
|
max-width: var(--col-7);
|
||||||
|
}
|
||||||
|
.col-md-8 {
|
||||||
|
flex: 0 0 var(--col-8);
|
||||||
|
max-width: var(--col-8);
|
||||||
|
}
|
||||||
|
.col-md-9 {
|
||||||
|
flex: 0 0 var(--col-9);
|
||||||
|
max-width: var(--col-9);
|
||||||
|
}
|
||||||
|
.col-md-10 {
|
||||||
|
flex: 0 0 var(--col-10);
|
||||||
|
max-width: var(--col-10);
|
||||||
|
}
|
||||||
|
.col-md-11 {
|
||||||
|
flex: 0 0 var(--col-11);
|
||||||
|
max-width: var(--col-11);
|
||||||
|
}
|
||||||
|
.col-md-12 {
|
||||||
|
flex: 0 0 var(--col-12);
|
||||||
|
max-width: var(--col-12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Large (lg) */
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.col-lg-1 {
|
||||||
|
flex: 0 0 var(--col-1);
|
||||||
|
max-width: var(--col-1);
|
||||||
|
}
|
||||||
|
.col-lg-2 {
|
||||||
|
flex: 0 0 var(--col-2);
|
||||||
|
max-width: var(--col-2);
|
||||||
|
}
|
||||||
|
.col-lg-3 {
|
||||||
|
flex: 0 0 var(--col-3);
|
||||||
|
max-width: var(--col-3);
|
||||||
|
}
|
||||||
|
.col-lg-4 {
|
||||||
|
flex: 0 0 var(--col-4);
|
||||||
|
max-width: var(--col-4);
|
||||||
|
}
|
||||||
|
.col-lg-5 {
|
||||||
|
flex: 0 0 var(--col-5);
|
||||||
|
max-width: var(--col-5);
|
||||||
|
}
|
||||||
|
.col-lg-6 {
|
||||||
|
flex: 0 0 var(--col-6);
|
||||||
|
max-width: var(--col-6);
|
||||||
|
}
|
||||||
|
.col-lg-7 {
|
||||||
|
flex: 0 0 var(--col-7);
|
||||||
|
max-width: var(--col-7);
|
||||||
|
}
|
||||||
|
.col-lg-8 {
|
||||||
|
flex: 0 0 var(--col-8);
|
||||||
|
max-width: var(--col-8);
|
||||||
|
}
|
||||||
|
.col-lg-9 {
|
||||||
|
flex: 0 0 var(--col-9);
|
||||||
|
max-width: var(--col-9);
|
||||||
|
}
|
||||||
|
.col-lg-10 {
|
||||||
|
flex: 0 0 var(--col-10);
|
||||||
|
max-width: var(--col-10);
|
||||||
|
}
|
||||||
|
.col-lg-11 {
|
||||||
|
flex: 0 0 var(--col-11);
|
||||||
|
max-width: var(--col-11);
|
||||||
|
}
|
||||||
|
.col-lg-12 {
|
||||||
|
flex: 0 0 var(--col-12);
|
||||||
|
max-width: var(--col-12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra Large (xl) */
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.col-xl-1 {
|
||||||
|
flex: 0 0 var(--col-1);
|
||||||
|
max-width: var(--col-1);
|
||||||
|
}
|
||||||
|
.col-xl-2 {
|
||||||
|
flex: 0 0 var(--col-2);
|
||||||
|
max-width: var(--col-2);
|
||||||
|
}
|
||||||
|
.col-xl-3 {
|
||||||
|
flex: 0 0 var(--col-3);
|
||||||
|
max-width: var(--col-3);
|
||||||
|
}
|
||||||
|
.col-xl-4 {
|
||||||
|
flex: 0 0 var(--col-4);
|
||||||
|
max-width: var(--col-4);
|
||||||
|
}
|
||||||
|
.col-xl-5 {
|
||||||
|
flex: 0 0 var(--col-5);
|
||||||
|
max-width: var(--col-5);
|
||||||
|
}
|
||||||
|
.col-xl-6 {
|
||||||
|
flex: 0 0 var(--col-6);
|
||||||
|
max-width: var(--col-6);
|
||||||
|
}
|
||||||
|
.col-xl-7 {
|
||||||
|
flex: 0 0 var(--col-7);
|
||||||
|
max-width: var(--col-7);
|
||||||
|
}
|
||||||
|
.col-xl-8 {
|
||||||
|
flex: 0 0 var(--col-8);
|
||||||
|
max-width: var(--col-8);
|
||||||
|
}
|
||||||
|
.col-xl-9 {
|
||||||
|
flex: 0 0 var(--col-9);
|
||||||
|
max-width: var(--col-9);
|
||||||
|
}
|
||||||
|
.col-xl-10 {
|
||||||
|
flex: 0 0 var(--col-10);
|
||||||
|
max-width: var(--col-10);
|
||||||
|
}
|
||||||
|
.col-xl-11 {
|
||||||
|
flex: 0 0 var(--col-11);
|
||||||
|
max-width: var(--col-11);
|
||||||
|
}
|
||||||
|
.col-xl-12 {
|
||||||
|
flex: 0 0 var(--col-12);
|
||||||
|
max-width: var(--col-12);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,62 @@
|
||||||
.cardshadow {
|
/* Variables globales */
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4);
|
:root {
|
||||||
transition: box-shadow 0.2s ease-in-out;
|
/* Couleurs principales */
|
||||||
|
--primary-gradient: linear-gradient(135deg, #86e3ce 0%, #d6a4e5 100%);
|
||||||
|
--background-dark: #1a1b1e;
|
||||||
|
--background-card: rgba(255, 255, 255, 0.05);
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||||
|
--accent-color: #86e3ce;
|
||||||
|
--error-color: #ff6b6b;
|
||||||
|
--warning-color: #ffd93d;
|
||||||
|
--success-color: #6bcb77;
|
||||||
|
|
||||||
|
/* Effets */
|
||||||
|
--glass-blur: blur(10px);
|
||||||
|
--card-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||||
|
--transition-speed: 0.3s;
|
||||||
|
|
||||||
|
/* Border radius */
|
||||||
|
--radius-sm: 4px;
|
||||||
|
--radius-md: 8px;
|
||||||
|
--radius-lg: 16px;
|
||||||
|
|
||||||
|
/* Grid */
|
||||||
|
--grid-gutter: 1rem;
|
||||||
|
--container-padding: 1rem;
|
||||||
|
|
||||||
|
/* Breakpoints */
|
||||||
|
--breakpoint-sm: 640px;
|
||||||
|
--breakpoint-md: 768px;
|
||||||
|
--breakpoint-lg: 1024px;
|
||||||
|
--breakpoint-xl: 1280px;
|
||||||
|
|
||||||
|
/* Column widths */
|
||||||
|
--col-1: 8.333333%;
|
||||||
|
--col-2: 16.666667%;
|
||||||
|
--col-3: 25%;
|
||||||
|
--col-4: 33.333333%;
|
||||||
|
--col-5: 41.666667%;
|
||||||
|
--col-6: 50%;
|
||||||
|
--col-7: 58.333333%;
|
||||||
|
--col-8: 66.666667%;
|
||||||
|
--col-9: 75%;
|
||||||
|
--col-10: 83.333333%;
|
||||||
|
--col-11: 91.666667%;
|
||||||
|
--col-12: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardshadow:hover {
|
/* Reset et styles de base */
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.8);
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movieitem {
|
body {
|
||||||
opacity: 0;
|
background-color: var(--background-dark);
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
color: var(--text-primary);
|
||||||
color: rgba(255, 255, 255, 1);
|
font-family: "Inter", system-ui, sans-serif;
|
||||||
}
|
line-height: 1.5;
|
||||||
|
min-height: 100vh;
|
||||||
.movieitem:hover {
|
|
||||||
animation: 0.2s ease-in-out forwards moviehover;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes moviehover {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
89
src/styles/utils.css
Normal file
89
src/styles/utils.css
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/* Classes utilitaires */
|
||||||
|
|
||||||
|
/* Flex */
|
||||||
|
.d-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.flex-column {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.justify-content-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.align-items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dimensions */
|
||||||
|
.w-100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.h-100 {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Marges */
|
||||||
|
.mx-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-1 {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-2 {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-3 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-4 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-5 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Séparateur */
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
margin: 2rem 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
var(--text-secondary),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-in {
|
||||||
|
animation: fadeIn 0.5s ease-out;
|
||||||
|
}
|
Loading…
Reference in a new issue