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-dom": "^18.3.0",
|
||||
"astro": "^5.5.5",
|
||||
"bootstrap": "^5.3.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"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 {
|
||||
error: string | null;
|
||||
error?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
const { error } = Astro.props as Props;
|
||||
const { error, username = "", password = "" } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<Logo />
|
||||
<hr />
|
||||
{
|
||||
error && (
|
||||
<div class="alert alert-danger" style="text-align: center">
|
||||
<Alert type="error" class="text-center">
|
||||
{error}
|
||||
</div>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
<form method="post" action="/login">
|
||||
<div class="form-floating mb-3">
|
||||
<input
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="username">Nom d'utilisateur</label>
|
||||
<Input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="username"
|
||||
name="username"
|
||||
value={username}
|
||||
placeholder="Entrez votre nom d'utilisateur"
|
||||
/>
|
||||
<label class="form-label">Nom d'utilisateur</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="password">Mot de passe</label>
|
||||
<Input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="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>
|
||||
<Button type="submit" variant="primary" class="w-100"> Connexion </Button>
|
||||
</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";
|
||||
|
||||
const req = Astro.request;
|
||||
const isPost = req.method === "POST";
|
||||
type LoginError =
|
||||
| "MISSING_FIELDS"
|
||||
| "INVALID_CREDENTIALS"
|
||||
| "SERVER_ERROR"
|
||||
| "NOT_FOUND";
|
||||
|
||||
let username = "";
|
||||
let password = "";
|
||||
let error = null;
|
||||
const ERROR_MESSAGES: Record<LoginError, string> = {
|
||||
MISSING_FIELDS: "Tous les champs sont requis",
|
||||
INVALID_CREDENTIALS: "Identifiants invalides",
|
||||
NOT_FOUND: "Compte non trouvé",
|
||||
SERVER_ERROR: "Une erreur est survenue",
|
||||
};
|
||||
|
||||
if (isPost) {
|
||||
const form = await req.formData();
|
||||
username = form.get("username")?.toString().trim() ?? "";
|
||||
password = form.get("password")?.toString().trim() ?? "";
|
||||
interface LoginResponse {
|
||||
jwt: string;
|
||||
}
|
||||
|
||||
interface LoginResult {
|
||||
error?: LoginError;
|
||||
jwt?: string;
|
||||
}
|
||||
|
||||
async function handleLogin(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<LoginResult> {
|
||||
if (!username || !password) {
|
||||
error = "Tous les champs sont requis";
|
||||
} else {
|
||||
return { error: "MISSING_FIELDS" };
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.API_URL}/accounts/login`, {
|
||||
method: "POST",
|
||||
|
@ -27,24 +41,45 @@ if (!username || !password) {
|
|||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status === 401) {
|
||||
error = "Identifiants invalides";
|
||||
} else {
|
||||
error = "Une erreur est survenue";
|
||||
return {
|
||||
error:
|
||||
res.status === 401
|
||||
? "INVALID_CREDENTIALS"
|
||||
: res.status === 404
|
||||
? "NOT_FOUND"
|
||||
: "SERVER_ERROR",
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const data = await res.json();
|
||||
Astro.cookies.set("jwt", data.jwt, {
|
||||
|
||||
const data = (await res.json()) as LoginResponse;
|
||||
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: "/",
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
});
|
||||
|
||||
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}>
|
||||
<div
|
||||
class="container-fluid d-flex align-items-center justify-content-center"
|
||||
style="min-height: 100vh;"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<div class="empty-layout">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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;
|
||||
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
// Import des styles
|
||||
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>
|
||||
|
@ -39,7 +45,3 @@ import "../styles/index.css";
|
|||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import "bootstrap/dist/js/bootstrap";
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
---
|
||||
import LoginFormData from "../components/forms/login/LoginFormData.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 />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 {
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4);
|
||||
transition: box-shadow 0.2s ease-in-out;
|
||||
/* Variables globales */
|
||||
:root {
|
||||
/* 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 {
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.8);
|
||||
/* Reset et styles de base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.movieitem {
|
||||
opacity: 0;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.movieitem:hover {
|
||||
animation: 0.2s ease-in-out forwards moviehover;
|
||||
}
|
||||
|
||||
@keyframes moviehover {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
body {
|
||||
background-color: var(--background-dark);
|
||||
color: var(--text-primary);
|
||||
font-family: "Inter", system-ui, sans-serif;
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
|
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