mirror of
https://github.com/R0m1k3/TodoFlow.git
synced 2026-10-12 01:38:28 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e759a1a7d5 | ||
|
|
3b5ede5872 | ||
|
|
a635aa29a4 | ||
|
|
fcac504693 | ||
|
|
27d3fb7d50 | ||
|
|
d9c1720523 | ||
|
|
e4a56f2fc6 | ||
|
|
3e6dbb5c76 | ||
|
|
1ca25917d4 | ||
|
|
278f74326c | ||
|
|
8cebc3c2fa |
No files matched your search
@@ -3,7 +3,9 @@ name: Release Tauri App
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
|
# Accept lowercase and uppercase tags, for example v1.1.0.0 or V1.1.0.0.
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
- 'V*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -38,6 +40,39 @@ jobs:
|
|||||||
cd widget
|
cd widget
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
|
- name: Update app version from tag
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Keep Tauri/Cargo on valid SemVer while allowing release tags like V1.1.0.0.
|
||||||
|
RAW_VERSION=$(echo "${{ github.ref_name }}" | sed 's/^[vV]//')
|
||||||
|
APP_VERSION="1.1.0"
|
||||||
|
if [[ "$RAW_VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(\.[0-9]+)?$ ]]; then
|
||||||
|
APP_VERSION="${BASH_REMATCH[1]}"
|
||||||
|
fi
|
||||||
|
echo "Updating app version to $APP_VERSION"
|
||||||
|
node -e "
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const updateJson = (file, update) => {
|
||||||
|
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||||
|
update(data);
|
||||||
|
fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
updateJson('./widget/package.json', (pkg) => {
|
||||||
|
pkg.version = '$APP_VERSION';
|
||||||
|
});
|
||||||
|
|
||||||
|
updateJson('./widget/src-tauri/tauri.conf.json', (config) => {
|
||||||
|
config.package.version = '$APP_VERSION';
|
||||||
|
});
|
||||||
|
|
||||||
|
const cargoFile = './widget/src-tauri/Cargo.toml';
|
||||||
|
const cargo = fs.readFileSync(cargoFile, 'utf8')
|
||||||
|
.replace(/^version = \".*\"$/m, 'version = \"$APP_VERSION\"');
|
||||||
|
fs.writeFileSync(cargoFile, cargo, 'utf8');
|
||||||
|
"
|
||||||
|
|
||||||
- name: Build the Tauri app
|
- name: Build the Tauri app
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
@@ -45,7 +80,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
tagName: ${{ github.ref_name }}
|
tagName: ${{ github.ref_name }}
|
||||||
releaseName: 'TodoFlow ${{ github.ref_name }}'
|
releaseName: 'TodoFlow ${{ github.ref_name }}'
|
||||||
releaseBody: 'Release generated automatically by GitHub Actions.'
|
releaseBody: |
|
||||||
releaseDraft: true
|
Installeur Windows de TodoFlow ${{ github.ref_name }}.
|
||||||
|
|
||||||
|
Telechargez le fichier `.exe` (NSIS) ou `.msi` ci-dessous pour installer l'application.
|
||||||
|
releaseDraft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
projectPath: './widget'
|
projectPath: './widget'
|
||||||
+2
-1
@@ -30,11 +30,12 @@ ENV PYTHONUNBUFFERED=1
|
|||||||
RUN mkdir -p /app/data
|
RUN mkdir -p /app/data
|
||||||
|
|
||||||
# Création d'un utilisateur système non-privilégié pour des raisons de sécurité
|
# Création d'un utilisateur système non-privilégié pour des raisons de sécurité
|
||||||
RUN useradd -u 8888 appuser && chown -R appuser:appuser /app
|
RUN chmod +x /app/entrypoint.sh && useradd -u 8888 appuser && chown -R appuser:appuser /app
|
||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# Lancement d'Uvicorn
|
# Lancement d'Uvicorn
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|
||||||
+253
-2
@@ -62,7 +62,23 @@ Permet à l'Agent IA d'ajouter un nouvel objectif pour l'utilisateur.
|
|||||||
```
|
```
|
||||||
* **Réponse (201 Created) :** Renvoie l'objet créé avec son UUID unique généré par le serveur et son horodatage de création (`created_at`).
|
* **Réponse (201 Created) :** Renvoie l'objet créé avec son UUID unique généré par le serveur et son horodatage de création (`created_at`).
|
||||||
|
|
||||||
### C. Mettre à jour une tâche (PUT `/api/v1/tasks/{id}`)
|
### C. Récupérer les détails d'une tâche (GET `/api/v1/tasks/{id}`)
|
||||||
|
Permet à l'Agent IA d'obtenir les informations complètes d'une tâche spécifique à partir de son UUID.
|
||||||
|
|
||||||
|
* **Réponse (200 OK) :**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "e1a77ff0-2c32-4f37-baef-e68b903f806e",
|
||||||
|
"title": "Acheter du pain",
|
||||||
|
"description": "Prendre une tradition bien cuite",
|
||||||
|
"status": "A faire",
|
||||||
|
"created_at": "2026-05-26T14:15:30Z",
|
||||||
|
"updated_at": "2026-05-26T14:15:30Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* **Réponse (404 Not Found) :** Si aucune tâche ne correspond à l'identifiant fourni.
|
||||||
|
|
||||||
|
### D. Mettre à jour une tâche (PUT `/api/v1/tasks/{id}`)
|
||||||
Permet à l'Agent IA de marquer une tâche comme **"En cours"** ou **"Termine"**, ou d'en modifier le titre/description.
|
Permet à l'Agent IA de marquer une tâche comme **"En cours"** ou **"Termine"**, ou d'en modifier le titre/description.
|
||||||
|
|
||||||
* **Request Body (JSON - Mise à jour partielle acceptée) :**
|
* **Request Body (JSON - Mise à jour partielle acceptée) :**
|
||||||
@@ -73,7 +89,7 @@ Permet à l'Agent IA de marquer une tâche comme **"En cours"** ou **"Termine"**
|
|||||||
```
|
```
|
||||||
* **Réponse (200 OK) :** Renvoie l'objet mis à jour avec son nouvel horodatage `updated_at`.
|
* **Réponse (200 OK) :** Renvoie l'objet mis à jour avec son nouvel horodatage `updated_at`.
|
||||||
|
|
||||||
### D. Supprimer une tâche (DELETE `/api/v1/tasks/{id}`)
|
### E. Supprimer une tâche (DELETE `/api/v1/tasks/{id}`)
|
||||||
Permet à l'Agent IA de nettoyer la liste en supprimant une tâche.
|
Permet à l'Agent IA de nettoyer la liste en supprimant une tâche.
|
||||||
|
|
||||||
* **Réponse (204 No Content) :** Succès de la suppression (aucun corps de réponse).
|
* **Réponse (204 No Content) :** Succès de la suppression (aucun corps de réponse).
|
||||||
@@ -125,6 +141,241 @@ if create_resp.status_code == 201:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🛠️ 5. Définition des Outils pour Agents IA (Function Calling)
|
||||||
|
|
||||||
|
Si vous configurez un agent IA externe utilisant les API d'OpenAI, Anthropic ou Gemini, vous pouvez copier-coller les schémas de fonctions ci-dessous pour lui donner un accès direct et natif à la Todo List.
|
||||||
|
|
||||||
|
### A. Format OpenAI / Gemini (JSON Schema)
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_tasks",
|
||||||
|
"description": "Récupère la liste de toutes les tâches stockées dans TodoFlow. Permet de filtrer optionnellement par statut.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["A faire", "En cours", "Termine"],
|
||||||
|
"description": "Filtre optionnel pour récupérer uniquement les tâches ayant ce statut ('A faire', 'En cours', 'Termine')."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_task",
|
||||||
|
"description": "Récupère les détails précis d'une tâche existante à partir de son UUID.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "L'UUID unique de la tâche à récupérer."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["task_id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "create_task",
|
||||||
|
"description": "Crée une nouvelle tâche dans la Todo list.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Le titre court et descriptif de la tâche (ex: 'Acheter du pain')."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Description détaillée de la tâche ou consignes supplémentaires."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["A faire", "En cours", "Termine"],
|
||||||
|
"default": "A faire",
|
||||||
|
"description": "Le statut initial de la tâche (par défaut 'A faire')."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "update_task",
|
||||||
|
"description": "Met à jour une tâche existante (modifier le titre, la description, ou changer de statut comme passer à 'En cours' ou 'Termine').",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "L'UUID unique de la tâche à modifier."
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Nouveau titre pour la tâche."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Nouvelle description pour la tâche."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["A faire", "En cours", "Termine"],
|
||||||
|
"description": "Nouveau statut pour la tâche."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["task_id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "delete_task",
|
||||||
|
"description": "Supprime définitivement une tâche de la liste à partir de son UUID.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "L'UUID unique de la tâche à supprimer."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["task_id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### B. Format Anthropic / Claude (Messages API Tools)
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "get_tasks",
|
||||||
|
"description": "Récupère la liste de toutes les tâches stockées dans TodoFlow. Permet de filtrer optionnellement par statut.",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["A faire", "En cours", "Termine"],
|
||||||
|
"description": "Filtre optionnel pour récupérer uniquement les tâches ayant ce statut."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get_task",
|
||||||
|
"description": "Récupère les détails précis d'une tâche existante à partir de son UUID.",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "L'UUID unique de la tâche à récupérer."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["task_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "create_task",
|
||||||
|
"description": "Crée une nouvelle tâche dans la Todo list.",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Le titre court et descriptif de la tâche."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Description détaillée de la tâche."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["A faire", "En cours", "Termine"],
|
||||||
|
"default": "A faire",
|
||||||
|
"description": "Le statut initial de la tâche."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "update_task",
|
||||||
|
"description": "Met à jour une tâche existante par son UUID (modifier le titre, la description, ou changer de statut).",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "L'UUID unique de la tâche à modifier."
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Nouveau titre pour la tâche."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Nouvelle description."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["A faire", "En cours", "Termine"],
|
||||||
|
"description": "Nouveau statut."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["task_id"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "delete_task",
|
||||||
|
"description": "Supprime définitivement une tâche de la liste à partir de son UUID.",
|
||||||
|
"input_schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"task_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "L'UUID unique de la tâche à supprimer."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["task_id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 💡 Conseils pour le Prompt System de l'Agent IA
|
## 💡 Conseils pour le Prompt System de l'Agent IA
|
||||||
|
|
||||||
Si vous intégrez cette API à un LLM (comme GPT-4 ou Gemini), vous pouvez ajouter cette consigne dans son **System Prompt** :
|
Si vous intégrez cette API à un LLM (comme GPT-4 ou Gemini), vous pouvez ajouter cette consigne dans son **System Prompt** :
|
||||||
|
|||||||
+259
-1
@@ -1,10 +1,13 @@
|
|||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from fastapi import FastAPI, HTTPException, Depends, Security, status
|
from fastapi import FastAPI, HTTPException, Depends, Security, status
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.security.api_key import APIKeyHeader
|
from fastapi.security.api_key import APIKeyHeader
|
||||||
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
@@ -40,7 +43,7 @@ async def lifespan(app: FastAPI):
|
|||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="TodoFlow API",
|
title="TodoFlow API",
|
||||||
description="API centralisée pour la Todo list, connectée au widget de bureau et pilotable par Agent IA.",
|
description="API centralisée pour la Todo list, connectée au widget de bureau et pilotable par Agent IA.",
|
||||||
version="1.0.0",
|
version="1.1.0",
|
||||||
lifespan=lifespan
|
lifespan=lifespan
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,6 +56,261 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --- DOCUMENTATION ET REDIRECTIONS ---
|
||||||
|
|
||||||
|
@app.get("/", include_in_schema=False)
|
||||||
|
def redirect_root_to_doc():
|
||||||
|
"""Redirige les requêtes de la racine vers la page de documentation."""
|
||||||
|
return RedirectResponse(url="/doc")
|
||||||
|
|
||||||
|
@app.get("/doc", response_class=HTMLResponse, include_in_schema=False)
|
||||||
|
def get_html_documentation():
|
||||||
|
"""
|
||||||
|
Sert une page HTML interactive et magnifiquement stylisée (Glassmorphism)
|
||||||
|
rendant le guide d'intégration pour Agent IA (README_AI_AGENT.md).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Chemin relatif vers la racine du serveur (où se trouve le README)
|
||||||
|
readme_path = "README_AI_AGENT.md"
|
||||||
|
if not os.path.exists(readme_path):
|
||||||
|
# Essayer un autre chemin au cas où
|
||||||
|
readme_path = "/app/README_AI_AGENT.md"
|
||||||
|
|
||||||
|
if not os.path.exists(readme_path):
|
||||||
|
raise FileNotFoundError("README_AI_AGENT.md introuvable.")
|
||||||
|
|
||||||
|
with open(readme_path, "r", encoding="utf-8") as f:
|
||||||
|
markdown_content = f.read()
|
||||||
|
|
||||||
|
escaped_content = json.dumps(markdown_content)
|
||||||
|
|
||||||
|
html_template = f"""<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Documentation TodoFlow API</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<style>
|
||||||
|
:root {{
|
||||||
|
--bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
|
||||||
|
--glass-bg: rgba(30, 41, 59, 0.75);
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.08);
|
||||||
|
--text-main: #f8fafc;
|
||||||
|
--text-muted: #94a3b8;
|
||||||
|
--primary: #6366f1;
|
||||||
|
--primary-glow: rgba(99, 102, 241, 0.15);
|
||||||
|
--accent: #10b981;
|
||||||
|
}}
|
||||||
|
|
||||||
|
body {{
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background: var(--bg-gradient);
|
||||||
|
color: var(--text-main);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.container {{
|
||||||
|
width: 100%;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 40px 20px;
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||||
|
}}
|
||||||
|
|
||||||
|
/* Sleek scrollbar */
|
||||||
|
::-webkit-scrollbar {{
|
||||||
|
width: 8px;
|
||||||
|
}}
|
||||||
|
::-webkit-scrollbar-track {{
|
||||||
|
background: rgba(15, 23, 42, 0.5);
|
||||||
|
}}
|
||||||
|
::-webkit-scrollbar-thumb {{
|
||||||
|
background: rgba(99, 102, 241, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
}}
|
||||||
|
::-webkit-scrollbar-thumb:hover {{
|
||||||
|
background: rgba(99, 102, 241, 0.5);
|
||||||
|
}}
|
||||||
|
|
||||||
|
/* Markdown styling */
|
||||||
|
.markdown-body h1 {{
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #cbd5e1 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body h2 {{
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body h3 {{
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body p {{
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body code {{
|
||||||
|
font-family: 'Fira Code', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
background: rgba(15, 23, 42, 0.6);
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #f43f5e;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body pre {{
|
||||||
|
background: rgba(15, 23, 42, 0.8);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body pre code {{
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
color: #38bdf8;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.92em;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body ul, .markdown-body ol {{
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-left: 24px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.7;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body li {{
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body a {{
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body a:hover {{
|
||||||
|
color: #818cf8;
|
||||||
|
text-decoration: underline;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body blockquote {{
|
||||||
|
border-left: 4px solid var(--primary);
|
||||||
|
background: var(--primary-glow);
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body blockquote p {{
|
||||||
|
margin: 0;
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-style: italic;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.markdown-body hr {{
|
||||||
|
border: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
margin: 32px 0;
|
||||||
|
}}
|
||||||
|
|
||||||
|
/* Premium Header Links */
|
||||||
|
.top-links {{
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.top-link {{
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
color: var(--text-main);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.top-link:hover {{
|
||||||
|
background: var(--primary);
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 15px rgba(99, 102, 241, 0.4);
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="top-links">
|
||||||
|
<a href="/docs" class="top-link">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||||||
|
Swagger UI (API Interactive)
|
||||||
|
</a>
|
||||||
|
<a href="/openapi.json" class="top-link">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
||||||
|
OpenAPI Spec (JSON)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="markdown-body" id="content">Chargement de la documentation...</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const markdownText = {escaped_content};
|
||||||
|
document.getElementById('content').innerHTML = marked.parse(markdownText);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
return HTMLResponse(content=html_template, status_code=200)
|
||||||
|
except Exception as e:
|
||||||
|
return HTMLResponse(
|
||||||
|
content=f"<h1>Erreur</h1><p>Impossible de charger la documentation : {str(e)}</p>",
|
||||||
|
status_code=500
|
||||||
|
)
|
||||||
|
|
||||||
# --- ENDPOINTS API ---
|
# --- ENDPOINTS API ---
|
||||||
|
|
||||||
@app.get(
|
@app.get(
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DEFAULT_API_KEYS_RAW="widget-token-secure-789,ai-agent-token-secure-101"
|
||||||
|
PERSISTED_ENV_FILE="${PERSISTED_ENV_FILE:-/app/data/.env}"
|
||||||
|
APP_ENV_FILE="${APP_ENV_FILE:-/app/.env}"
|
||||||
|
|
||||||
|
read_env_value() {
|
||||||
|
file="$1"
|
||||||
|
name="$2"
|
||||||
|
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -n "s/^[[:space:]]*${name}[[:space:]]*=[[:space:]]*//p" "$file" \
|
||||||
|
| tail -n 1 \
|
||||||
|
| sed 's/^"//; s/"$//; s/^'\''//; s/'\''$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
write_env_value() {
|
||||||
|
file="$1"
|
||||||
|
name="$2"
|
||||||
|
value="$3"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$file")"
|
||||||
|
|
||||||
|
if [ -f "$file" ] && grep -q "^[[:space:]]*${name}[[:space:]]*=" "$file"; then
|
||||||
|
escaped_value=$(printf '%s' "$value" | sed 's/[\/&]/\\&/g')
|
||||||
|
sed -i "s/^[[:space:]]*${name}[[:space:]]*=.*/${name}=${escaped_value}/" "$file"
|
||||||
|
else
|
||||||
|
printf '%s=%s\n' "$name" "$value" >> "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
persisted_api_keys="$(read_env_value "$PERSISTED_ENV_FILE" "API_KEYS_RAW" || true)"
|
||||||
|
app_env_api_keys="$(read_env_value "$APP_ENV_FILE" "API_KEYS_RAW" || true)"
|
||||||
|
current_api_keys="${API_KEYS_RAW:-}"
|
||||||
|
|
||||||
|
if [ -n "$persisted_api_keys" ] && { [ -z "$current_api_keys" ] || [ "$current_api_keys" = "$DEFAULT_API_KEYS_RAW" ]; }; then
|
||||||
|
export API_KEYS_RAW="$persisted_api_keys"
|
||||||
|
elif [ -n "$app_env_api_keys" ] && { [ -z "$current_api_keys" ] || [ "$current_api_keys" = "$DEFAULT_API_KEYS_RAW" ]; }; then
|
||||||
|
export API_KEYS_RAW="$app_env_api_keys"
|
||||||
|
if [ "$app_env_api_keys" != "$DEFAULT_API_KEYS_RAW" ]; then
|
||||||
|
write_env_value "$PERSISTED_ENV_FILE" "API_KEYS_RAW" "$app_env_api_keys"
|
||||||
|
fi
|
||||||
|
elif [ -n "$current_api_keys" ] && [ "$current_api_keys" != "$DEFAULT_API_KEYS_RAW" ]; then
|
||||||
|
write_env_value "$PERSISTED_ENV_FILE" "API_KEYS_RAW" "$current_api_keys"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
@@ -4,10 +4,22 @@
|
|||||||
Développement d'un widget Windows moderne et simple connecté à un serveur centralisé hébergé dans un conteneur Docker. L'API du serveur doit être accessible par un agent IA pour piloter la liste de tâches. Les tâches doivent afficher leur date de création et pouvoir être marquées comme "En cours" ou "Terminé".
|
Développement d'un widget Windows moderne et simple connecté à un serveur centralisé hébergé dans un conteneur Docker. L'API du serveur doit être accessible par un agent IA pour piloter la liste de tâches. Les tâches doivent afficher leur date de création et pouvoir être marquées comme "En cours" ou "Terminé".
|
||||||
|
|
||||||
## Focus Actuel
|
## Focus Actuel
|
||||||
- Phase 3 : Développement du Widget Windows avec Tauri et design Glassmorphism.
|
- Projet stable, fonctionnel et documenté.
|
||||||
- Mise en place du démarrage automatique et de l'option "Toujours au premier plan".
|
|
||||||
|
|
||||||
## Master Plan
|
## Master Plan
|
||||||
|
- [x] **Correction du Bug d'Édition du Widget**
|
||||||
|
- [x] Identifier la cause racine (le polling de 4s écrase le DOM et provoque un blur de l'input)
|
||||||
|
- [x] Modifier `fetchTasks()` dans `widget/src/app.js` pour suspendre la synchronisation durant l'édition
|
||||||
|
- [x] Tester et valider la correction localement
|
||||||
|
- [x] **Synchronisation avec le Dépôt Distant**
|
||||||
|
- [x] Mettre à jour `task.md` pour refléter la demande de l'utilisateur
|
||||||
|
- [x] Effectuer un `git pull` pour appliquer les 20 commits de retard sur `origin/main`
|
||||||
|
- [x] Valider l'état des fichiers locaux et s'assurer que la copie locale est propre et fonctionnelle
|
||||||
|
- [x] **Phase 6 : Mise à Jour de la Documentation pour Agent IA Externe (README)**
|
||||||
|
- [x] Concevoir le plan d'action simplifié (`implementation_plan.md`)
|
||||||
|
- [x] Documenter la route manquante `GET /api/v1/tasks/{task_id}` dans `README_AI_AGENT.md`
|
||||||
|
- [x] Ajouter les schémas d'outils (Function Calling) pour LLMs dans `README_AI_AGENT.md`
|
||||||
|
- [x] Vérifier la cohérence de la documentation avec le code réel de l'API
|
||||||
- [x] **Phase 1 : Conception & Architecture**
|
- [x] **Phase 1 : Conception & Architecture**
|
||||||
- [x] Créer le fichier `task.md`
|
- [x] Créer le fichier `task.md`
|
||||||
- [x] Consulter le sous-agent `architecte` pour la structure de l'API et du conteneur
|
- [x] Consulter le sous-agent `architecte` pour la structure de l'API et du conteneur
|
||||||
@@ -42,3 +54,6 @@ Développement d'un widget Windows moderne et simple connecté à un serveur cen
|
|||||||
## Journal de Progression
|
## Journal de Progression
|
||||||
- **2026-05-26** : Plan validé par l'utilisateur. Ajout des spécifications : configuration de l'URL API dans le widget, création directe de tâches, mode toujours au premier plan, et démarrage automatique. Début du développement du serveur API.
|
- **2026-05-26** : Plan validé par l'utilisateur. Ajout des spécifications : configuration de l'URL API dans le widget, création directe de tâches, mode toujours au premier plan, et démarrage automatique. Début du développement du serveur API.
|
||||||
- **2026-05-26** : Implémentation du serveur API conteneurisé (FastAPI + SQLModel + Docker Compose) et du widget de bureau Tauri (HTML/CSS/JS, drag-region, always-on-top natif, autostart registre Windows, tray icon). Rédaction des tests unitaires et du guide d'intégration pour Agent IA. Début de la validation.
|
- **2026-05-26** : Implémentation du serveur API conteneurisé (FastAPI + SQLModel + Docker Compose) et du widget de bureau Tauri (HTML/CSS/JS, drag-region, always-on-top natif, autostart registre Windows, tray icon). Rédaction des tests unitaires et du guide d'intégration pour Agent IA. Début de la validation.
|
||||||
|
- **2026-05-27** : Récupération des dernières modifications du dépôt distant (`origin/main`). Synchronisation de 20 commits incluant des améliorations de l'interface (style glassmorphic, redimensionnement natif, icônes premium 3D) et des correctifs pour l'intégration Docker/Tauri. La copie locale est désormais à jour.
|
||||||
|
- **2026-05-27** : Mise à jour complète du guide d'intégration `README_AI_AGENT.md`. Ajout de la documentation pour la route `GET /api/v1/tasks/{task_id}` et création de schémas complets d'outils (Function Calling / Tools) pour OpenAI, Gemini et Anthropic Claude pour faciliter l'intégration immédiate d'un agent externe.
|
||||||
|
- **2026-05-27** : Correction du bug d'édition de tâches sur le widget. Ajout d'une condition pour suspendre temporairement le rafraîchissement périodique (polling de 4s) lorsque l'utilisateur édite le titre d'une tâche. Cela évite le rafraîchissement destructif du DOM et la perte de focus.
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "todoflow-widget",
|
"name": "todoflow-widget",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "todoflow"
|
name = "todoflow"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
description = "Sleek Windows TodoFlow desktop widget"
|
description = "Sleek Windows TodoFlow desktop widget"
|
||||||
authors = ["Michael"]
|
authors = ["Michael"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -67,6 +67,32 @@ fn get_autostart() -> Result<bool, String> {
|
|||||||
Ok(val.is_ok())
|
Ok(val.is_ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- GESTION DE LA VISIBILITÉ DE LA FENÊTRE ---
|
||||||
|
|
||||||
|
fn toggle_window_visibility(app: &AppHandle) {
|
||||||
|
// Bascule l'affichage du widget depuis l'icône de la barre d'état.
|
||||||
|
// Comme la fenêtre est configurée avec skipTaskbar:true, l'icône du tray est le
|
||||||
|
// seul point de restauration : il doit donc fonctionner dans tous les états.
|
||||||
|
let window = match app.get_window("main") {
|
||||||
|
Some(w) => w,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Une fenêtre réduite est toujours rapportée comme "visible" par Tauri. On la
|
||||||
|
// considère donc comme masquée afin de la restaurer correctement (et non la re-masquer).
|
||||||
|
let is_minimized = window.is_minimized().unwrap_or(false);
|
||||||
|
let is_visible = window.is_visible().unwrap_or(false);
|
||||||
|
|
||||||
|
if is_visible && !is_minimized {
|
||||||
|
let _ = window.hide();
|
||||||
|
} else {
|
||||||
|
// Restauration robuste : dé-minimiser, ré-afficher, puis ramener au premier plan.
|
||||||
|
let _ = window.unminimize();
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- CONTEXTE ET INITIALISATION ---
|
// --- CONTEXTE ET INITIALISATION ---
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -84,26 +110,14 @@ fn main() {
|
|||||||
.on_system_tray_event(|app, event| match event {
|
.on_system_tray_event(|app, event| match event {
|
||||||
SystemTrayEvent::LeftClick { .. } => {
|
SystemTrayEvent::LeftClick { .. } => {
|
||||||
// Un simple clic gauche sur l'icône de la barre des tâches bascule la visibilité
|
// Un simple clic gauche sur l'icône de la barre des tâches bascule la visibilité
|
||||||
let window = app.get_window("main").unwrap();
|
toggle_window_visibility(app);
|
||||||
if window.is_visible().unwrap() {
|
|
||||||
window.hide().unwrap();
|
|
||||||
} else {
|
|
||||||
window.show().unwrap();
|
|
||||||
window.set_focus().unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
|
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
|
||||||
"quit" => {
|
"quit" => {
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
"toggle" => {
|
"toggle" => {
|
||||||
let window = app.get_window("main").unwrap();
|
toggle_window_visibility(app);
|
||||||
if window.is_visible().unwrap() {
|
|
||||||
window.hide().unwrap();
|
|
||||||
} else {
|
|
||||||
window.show().unwrap();
|
|
||||||
window.set_focus().unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "TodoFlow",
|
"productName": "TodoFlow",
|
||||||
"version": "1.0.0"
|
"version": "1.1.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@@ -58,9 +58,11 @@
|
|||||||
{
|
{
|
||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"height": 520,
|
"height": 520,
|
||||||
"resizable": false,
|
"resizable": true,
|
||||||
"title": "TodoFlow",
|
"title": "TodoFlow",
|
||||||
"width": 340,
|
"width": 340,
|
||||||
|
"minWidth": 280,
|
||||||
|
"minHeight": 400,
|
||||||
"decorations": false,
|
"decorations": false,
|
||||||
"transparent": true,
|
"transparent": true,
|
||||||
"alwaysOnTop": false,
|
"alwaysOnTop": false,
|
||||||
|
|||||||
+9
-1
@@ -88,7 +88,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
// Événements Tauri pour le contrôle de la fenêtre de bureau
|
// Événements Tauri pour le contrôle de la fenêtre de bureau
|
||||||
if (appWindow) {
|
if (appWindow) {
|
||||||
btnMinimize.addEventListener("click", () => appWindow.minimize());
|
// La fenêtre utilise skipTaskbar:true : elle n'apparaît jamais dans la barre des tâches.
|
||||||
|
// Un minimize() classique la rendrait donc irrécupérable. On masque plutôt vers le
|
||||||
|
// system tray, d'où elle peut toujours être restaurée via l'icône de la barre d'état.
|
||||||
|
btnMinimize.addEventListener("click", () => appWindow.hide());
|
||||||
btnClose.addEventListener("click", () => appWindow.hide()); // Masquer vers le tray au clic sur 'Fermer'
|
btnClose.addEventListener("click", () => appWindow.hide()); // Masquer vers le tray au clic sur 'Fermer'
|
||||||
|
|
||||||
// Récupération de l'état réel du démarrage automatique depuis le registre Windows
|
// Récupération de l'état réel du démarrage automatique depuis le registre Windows
|
||||||
@@ -176,6 +179,11 @@ async function saveSettings() {
|
|||||||
// --- APPELS API (CRUD) ---
|
// --- APPELS API (CRUD) ---
|
||||||
|
|
||||||
async function fetchTasks() {
|
async function fetchTasks() {
|
||||||
|
// Suspendre la synchronisation si l'utilisateur est en train d'éditer une tâche pour éviter de perdre le focus et fermer l'édition
|
||||||
|
if (document.querySelector(".task-card.editing")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Récupération asynchrone des tâches avec la clé API
|
// Récupération asynchrone des tâches avec la clé API
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${state.apiUrl}/api/v1/tasks`, {
|
const response = await fetch(`${state.apiUrl}/api/v1/tasks`, {
|
||||||
|
|||||||
+345
-296
@@ -1,37 +1,43 @@
|
|||||||
|
/* --- SYSTÈME DE DESIGN & TOKENS FUTURISTES --- */
|
||||||
:root {
|
:root {
|
||||||
--widget-opacity: 0.70;
|
--widget-opacity: 0.70;
|
||||||
--bg-widget: rgba(18, 22, 33, var(--widget-opacity));
|
--bg-widget: rgba(11, 14, 26, var(--widget-opacity));
|
||||||
--bg-card: rgba(255, 255, 255, 0.03);
|
--bg-card: rgba(255, 255, 255, 0.035);
|
||||||
--bg-card-hover: rgba(255, 255, 255, 0.07);
|
--bg-card-hover: rgba(255, 255, 255, 0.08);
|
||||||
--border-widget: 1px solid rgba(255, 255, 255, 0.09);
|
--border-widget: 1px solid rgba(255, 255, 255, 0.11);
|
||||||
--border-card: 1px solid rgba(255, 255, 255, 0.05);
|
--border-card: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
|
||||||
/* Couleurs thématiques HSL */
|
/* Couleurs de marque HSL vibrantes */
|
||||||
--accent-indigo: hsl(250, 85%, 65%);
|
--accent-indigo: hsl(250, 95%, 68%);
|
||||||
--accent-indigo-glow: rgba(99, 102, 241, 0.3);
|
--accent-indigo-glow: rgba(99, 102, 241, 0.35);
|
||||||
--accent-mint: hsl(165, 80%, 42%);
|
|
||||||
--accent-mint-glow: rgba(16, 185, 129, 0.15);
|
--accent-mint: hsl(165, 90%, 45%);
|
||||||
--accent-amber: hsl(38, 95%, 55%);
|
--accent-mint-glow: rgba(16, 185, 129, 0.2);
|
||||||
--accent-amber-glow: rgba(245, 158, 11, 0.15);
|
|
||||||
|
--accent-amber: hsl(38, 95%, 58%);
|
||||||
|
--accent-amber-glow: rgba(245, 158, 11, 0.2);
|
||||||
|
|
||||||
/* Textes */
|
/* Textes */
|
||||||
--text-primary: hsl(220, 20%, 96%);
|
--text-primary: hsl(220, 20%, 97%);
|
||||||
--text-secondary: hsl(220, 12%, 68%);
|
--text-secondary: hsl(220, 14%, 72%);
|
||||||
--text-muted: rgba(241, 243, 249, 0.45);
|
--text-muted: rgba(241, 243, 249, 0.40);
|
||||||
|
|
||||||
|
/* Gradients */
|
||||||
|
--gradient-primary: linear-gradient(135deg, var(--accent-indigo), var(--accent-mint));
|
||||||
|
--gradient-card-hover: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(16, 185, 129, 0.05));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Réinitialisation de base */
|
/* Réinitialisation moderne */
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
user-select: none; /* Évite la sélection de texte accidentelle pour un widget */
|
user-select: none;
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: transparent; /* Indispensable pour la transparence de la fenêtre Tauri */
|
background: transparent;
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
||||||
color: var(--text-primary);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -39,27 +45,30 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- LE CONTAINER DU WIDGET (GLASSMORPHISM PREMIUM) --- */
|
/* --- LE CONTAINER DU WIDGET (GLASSMORPHISM ABSOLU RESONSIVE) --- */
|
||||||
.todo-widget {
|
.todo-widget {
|
||||||
width: 340px;
|
width: 100%;
|
||||||
height: 520px;
|
height: 100%;
|
||||||
|
min-width: 280px;
|
||||||
|
min-height: 400px;
|
||||||
background: var(--bg-widget);
|
background: var(--bg-widget);
|
||||||
backdrop-filter: blur(30px) saturate(125%);
|
backdrop-filter: blur(40px) saturate(150%);
|
||||||
-webkit-backdrop-filter: blur(30px) saturate(125%);
|
-webkit-backdrop-filter: blur(40px) saturate(150%);
|
||||||
border-radius: 16px;
|
border-radius: 20px;
|
||||||
border: var(--border-widget);
|
border: var(--border-widget);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 8px 32px 0 rgba(0, 0, 0, 0.35),
|
0 12px 40px 0 rgba(0, 0, 0, 0.5),
|
||||||
0 1px 0px 0px rgba(255, 255, 255, 0.12) inset;
|
0 1px 0px 0px rgba(255, 255, 255, 0.15) inset;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
transition: background 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- HEADER DU WIDGET --- */
|
/* --- HEADER DU WIDGET --- */
|
||||||
.widget-header {
|
.widget-header {
|
||||||
padding: 16px 16px 12px 16px;
|
padding: 18px 18px 12px 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -68,7 +77,7 @@ body {
|
|||||||
.header-drag-zone {
|
.header-drag-zone {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
@@ -78,102 +87,108 @@ body {
|
|||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Indicateur d'état connecté à l'API */
|
/* Indicateur d'état connecté lumineux */
|
||||||
.status-indicator {
|
.status-indicator {
|
||||||
width: 8px;
|
width: 9px;
|
||||||
height: 8px;
|
height: 9px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transition: all 0.5s ease;
|
transition: all 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-indicator.connected {
|
.status-indicator.connected {
|
||||||
background-color: var(--accent-mint);
|
background-color: var(--accent-mint);
|
||||||
box-shadow: 0 0 8px var(--accent-mint);
|
box-shadow: 0 0 10px 2px var(--accent-mint);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-indicator.disconnected {
|
.status-indicator.disconnected {
|
||||||
background-color: hsl(0, 80%, 60%);
|
background-color: hsl(0, 85%, 60%);
|
||||||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.5);
|
box-shadow: 0 0 10px 2px rgba(239, 68, 68, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-title {
|
.widget-title {
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
letter-spacing: 0.8px;
|
letter-spacing: 1.5px;
|
||||||
|
background: var(--gradient-primary);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--text-primary);
|
|
||||||
opacity: 0.95;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-right {
|
.header-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
z-index: 10; /* Assure le clic par-dessus la zone de drag */
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Boutons de contrôle style Windows */
|
/* Boutons de contrôle Premium */
|
||||||
.win-btn {
|
.win-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
width: 26px;
|
width: 28px;
|
||||||
height: 26px;
|
height: 28px;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.win-btn:hover {
|
.win-btn:hover {
|
||||||
background: rgba(255, 255, 255, 0.08);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.win-btn.btn-close:hover {
|
.win-btn.btn-close:hover {
|
||||||
background: hsl(0, 75%, 50%);
|
background: hsl(0, 75%, 50%);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
box-shadow: 0 0 12px rgba(239, 68, 68, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- INDICATEUR DE PROGRESSION --- */
|
/* --- INDICATEUR DE PROGRESSION HAUT DE GAMME --- */
|
||||||
.progress-container {
|
.progress-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2px;
|
height: 3px;
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(90deg, var(--accent-indigo), var(--accent-mint));
|
background: var(--gradient-primary);
|
||||||
transition: width 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
box-shadow: 0 0 8px var(--accent-indigo-glow);
|
||||||
|
transition: width 0.5s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- ZONE DE CORPS --- */
|
/* --- ZONE DE CORPS --- */
|
||||||
.widget-body {
|
.widget-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 16px;
|
padding: 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Formulaire d'ajout de tâche */
|
/* Formulaire d'ajout de tâche flottant */
|
||||||
.add-task-form {
|
.add-task-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
background: rgba(0, 0, 0, 0.15);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.03);
|
border: 1px solid rgba(255, 255, 255, 0.04);
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
padding: 4px 6px;
|
padding: 5px 6px;
|
||||||
transition: border-color 0.3s;
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-task-form:focus-within {
|
.add-task-form:focus-within {
|
||||||
border-color: rgba(99, 102, 241, 0.4);
|
border-color: rgba(99, 102, 241, 0.5);
|
||||||
|
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-task-form input {
|
.add-task-form input {
|
||||||
@@ -182,36 +197,37 @@ body {
|
|||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 13px;
|
font-size: 13.5px;
|
||||||
padding: 6px;
|
padding: 8px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-task-form input::placeholder {
|
.add-task-form input::placeholder {
|
||||||
color: rgba(241, 243, 249, 0.35);
|
color: rgba(241, 243, 249, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-add {
|
.btn-add {
|
||||||
background: var(--accent-indigo);
|
background: var(--gradient-primary);
|
||||||
border: none;
|
border: none;
|
||||||
width: 28px;
|
width: 32px;
|
||||||
height: 28px;
|
height: 32px;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
box-shadow: 0 4px 12px var(--accent-indigo-glow);
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-add:hover {
|
.btn-add:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.08);
|
||||||
box-shadow: 0 0 10px var(--accent-indigo-glow);
|
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-add svg {
|
.btn-add svg {
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-add:hover svg {
|
.btn-add:hover svg {
|
||||||
@@ -222,13 +238,13 @@ body {
|
|||||||
.tasks-list {
|
.tasks-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: 2px;
|
padding-right: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Personnalisation Scrollbar */
|
/* Scrollbar futuriste */
|
||||||
.tasks-list::-webkit-scrollbar {
|
.tasks-list::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
@@ -238,37 +254,45 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tasks-list::-webkit-scrollbar-thumb {
|
.tasks-list::-webkit-scrollbar-thumb {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
transition: background 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- CARTE DE TÂCHE INDIVIDUELLE --- */
|
.tasks-list::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- CARTE DE TÂCHE SQUIRCLE GLASS --- */
|
||||||
.task-card {
|
.task-card {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.045);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
border-radius: 12px;
|
border-radius: 14px;
|
||||||
padding: 12px 14px;
|
padding: 14px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 14px;
|
||||||
|
cursor: pointer;
|
||||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
position: relative;
|
position: relative;
|
||||||
opacity: 1;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-card:hover {
|
.task-card:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
background: rgba(255, 255, 255, 0.08);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
border-color: rgba(99, 102, 241, 0.35);
|
border-color: rgba(99, 102, 241, 0.35);
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
box-shadow:
|
||||||
|
0 8px 24px rgba(99, 102, 241, 0.18),
|
||||||
|
0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox personnalisée */
|
/* Checkbox Squircle interactive */
|
||||||
.custom-checkbox {
|
.custom-checkbox {
|
||||||
width: 18px;
|
width: 20px;
|
||||||
height: 18px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
border-radius: 7px;
|
||||||
border: 1.5px solid rgba(255, 255, 255, 0.35);
|
border: 1.8px solid rgba(255, 255, 255, 0.3);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -279,13 +303,15 @@ body {
|
|||||||
|
|
||||||
.custom-checkbox:hover {
|
.custom-checkbox:hover {
|
||||||
border-color: var(--accent-indigo);
|
border-color: var(--accent-indigo);
|
||||||
background: rgba(99, 102, 241, 0.08);
|
background: rgba(99, 102, 241, 0.1);
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox cochée (Terminé) */
|
/* Checkbox Pop */
|
||||||
.custom-checkbox.checked {
|
.custom-checkbox.checked {
|
||||||
background: linear-gradient(135deg, hsl(165, 80%, 42%), hsl(165, 80%, 32%));
|
background: var(--gradient-primary);
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
box-shadow: 0 0 10px var(--accent-indigo-glow);
|
||||||
animation: checkboxPop 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
animation: checkboxPop 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +325,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes checkboxPop {
|
@keyframes checkboxPop {
|
||||||
50% { transform: scale(1.2); }
|
50% { transform: scale(1.15); }
|
||||||
100% { transform: scale(1); }
|
100% { transform: scale(1); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,12 +334,12 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 0; /* Permet le troncage correct du texte */
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-title {
|
.task-title {
|
||||||
font-size: 13.5px;
|
font-size: 13.5px;
|
||||||
font-weight: 500;
|
font-weight: 550;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
transition: color 0.3s;
|
transition: color 0.3s;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -327,16 +353,73 @@ body {
|
|||||||
.task-date {
|
.task-date {
|
||||||
font-size: 9.5px;
|
font-size: 9.5px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
margin-top: 3px;
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- BOUTONS ACTIONS SURVOL (MODIFIER / SUPPRIMER) --- */
|
||||||
|
.task-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-right: 6px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card:hover .task-actions {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-task-action {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-task-action:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
color: var(--text-primary);
|
||||||
|
transform: scale(1.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-task-action.btn-delete:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.15);
|
||||||
|
color: hsl(0, 80%, 65%);
|
||||||
|
box-shadow: 0 0 8px rgba(239, 68, 68, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-task-action svg {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input d'édition premium */
|
||||||
|
.task-edit-input {
|
||||||
|
background: rgba(255, 255, 255, 0.09);
|
||||||
|
border: 1px solid var(--accent-indigo);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: inherit;
|
||||||
|
padding: 3px 8px;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 10px var(--accent-indigo-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- BADGES DE STATUT --- */
|
/* --- BADGES DE STATUT --- */
|
||||||
.status-badge {
|
.status-badge {
|
||||||
font-size: 8.5px;
|
font-size: 8.5px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
padding: 3px 7px;
|
padding: 4px 8px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -344,26 +427,39 @@ body {
|
|||||||
.status-badge.in-progress {
|
.status-badge.in-progress {
|
||||||
background: var(--accent-amber-glow);
|
background: var(--accent-amber-glow);
|
||||||
color: var(--accent-amber);
|
color: var(--accent-amber);
|
||||||
border: 1px solid rgba(245, 158, 11, 0.1);
|
border: 1px solid rgba(245, 158, 11, 0.2);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge.in-progress:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
background: rgba(245, 158, 11, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge.todo {
|
.status-badge.todo {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge.todo:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge.done {
|
.status-badge.done {
|
||||||
background: var(--accent-mint-glow);
|
background: var(--accent-mint-glow);
|
||||||
color: var(--accent-mint);
|
color: var(--accent-mint);
|
||||||
border: 1px solid rgba(16, 185, 129, 0.1);
|
border: 1px solid rgba(16, 185, 129, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- DESIGN DES TÂCHES TERMINÉES (ANIMÉ) --- */
|
/* --- DESIGN DES TÂCHES TERMINÉES (ANIMÉ) --- */
|
||||||
.task-card.completed {
|
.task-card.completed {
|
||||||
opacity: 0.5;
|
opacity: 0.45;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-card.completed .task-title {
|
.task-card.completed .task-title {
|
||||||
@@ -380,14 +476,28 @@ body {
|
|||||||
background: var(--text-muted);
|
background: var(--text-muted);
|
||||||
transform: scaleX(0);
|
transform: scaleX(0);
|
||||||
transform-origin: left;
|
transform-origin: left;
|
||||||
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
|
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-card.completed .task-title::after {
|
.task-card.completed .task-title::after {
|
||||||
transform: scaleX(1);
|
transform: scaleX(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* État vide */
|
/* --- ACCORDION EXPANSION STATE --- */
|
||||||
|
.task-card.expanded {
|
||||||
|
background: rgba(255, 255, 255, 0.09);
|
||||||
|
border-color: rgba(99, 102, 241, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-card.expanded .task-title {
|
||||||
|
white-space: normal;
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: unset;
|
||||||
|
display: block;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* État vide premium */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -400,14 +510,15 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty-state p {
|
.empty-state p {
|
||||||
font-size: 13px;
|
font-size: 13.5px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 6px;
|
||||||
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-subtext {
|
.empty-subtext {
|
||||||
font-size: 10.5px;
|
font-size: 10.5px;
|
||||||
opacity: 0.7;
|
opacity: 0.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- PANNEAU DE PARAMÈTRES (BOTTOM SLIDE) --- */
|
/* --- PANNEAU DE PARAMÈTRES (BOTTOM SLIDE) --- */
|
||||||
@@ -416,18 +527,20 @@ body {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 380px;
|
height: 390px;
|
||||||
background: rgba(15, 18, 28, 0.95);
|
background: rgba(13, 16, 28, 0.96);
|
||||||
|
backdrop-filter: blur(40px);
|
||||||
|
-webkit-backdrop-filter: blur(40px);
|
||||||
border-top: var(--border-widget);
|
border-top: var(--border-widget);
|
||||||
border-radius: 16px 16px 0 0;
|
border-radius: 20px 20px 0 0;
|
||||||
padding: 20px;
|
padding: 22px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-panel.open {
|
.settings-panel.open {
|
||||||
@@ -442,9 +555,12 @@ body {
|
|||||||
|
|
||||||
.settings-header h2 {
|
.settings-header h2 {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.8px;
|
||||||
|
background: var(--gradient-primary);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon-close {
|
.btn-icon-close {
|
||||||
@@ -452,8 +568,8 @@ body {
|
|||||||
border: none;
|
border: none;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 24px;
|
width: 26px;
|
||||||
height: 24px;
|
height: 26px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -480,177 +596,32 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.setting-group label {
|
.setting-group label {
|
||||||
font-size: 11px;
|
font-size: 11.5px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-group input {
|
.setting-group input {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
padding: 8px 10px;
|
padding: 10px 12px;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 12px;
|
font-size: 12.5px;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
transition: border-color 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-group input:focus {
|
.setting-group input:focus {
|
||||||
border-color: var(--accent-indigo);
|
border-color: var(--accent-indigo);
|
||||||
}
|
|
||||||
|
|
||||||
/* Switch Toggles */
|
|
||||||
.setting-toggle {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-label {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-container input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-slider {
|
|
||||||
width: 34px;
|
|
||||||
height: 18px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: 20px;
|
|
||||||
position: relative;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-slider::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
left: 3px;
|
|
||||||
bottom: 3px;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-container input:checked + .switch-slider {
|
|
||||||
background-color: var(--accent-indigo);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-container input:checked + .switch-slider::before {
|
|
||||||
transform: translateX(16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bouton enregistrer */
|
|
||||||
.btn-save {
|
|
||||||
background: var(--accent-indigo);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 8px;
|
|
||||||
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-save:hover {
|
|
||||||
background: hsl(250, 85%, 58%);
|
|
||||||
box-shadow: 0 0 10px var(--accent-indigo-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- BOUTONS ACTIONS DES TÂCHES (MODIFIER / SUPPRIMER) --- */
|
|
||||||
.task-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
margin-right: 8px;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-card:hover .task-actions {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-task-action {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
cursor: pointer;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
border-radius: 6px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-task-action:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-task-action.btn-delete:hover {
|
|
||||||
background: rgba(239, 68, 68, 0.15);
|
|
||||||
color: hsl(0, 80%, 65%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-task-action svg {
|
|
||||||
pointer-events: none; /* Évite d'interférer avec le clic */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style de l'input d'édition en ligne */
|
|
||||||
.task-edit-input {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border: 1px solid var(--accent-indigo);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: inherit;
|
|
||||||
padding: 2px 6px;
|
|
||||||
width: 100%;
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 8px var(--accent-indigo-glow);
|
box-shadow: 0 0 8px var(--accent-indigo-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- ACCORDION / EXPANDED STATE --- */
|
/* Transparence Slider */
|
||||||
.task-card {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-card.expanded {
|
|
||||||
background: var(--bg-card-hover);
|
|
||||||
border-color: rgba(99, 102, 241, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-card.expanded .task-title {
|
|
||||||
white-space: normal;
|
|
||||||
overflow: visible;
|
|
||||||
text-overflow: unset;
|
|
||||||
display: block;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- RANGE SLIDER STYLING --- */
|
|
||||||
.slider-opacity {
|
.slider-opacity {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
@@ -676,23 +647,99 @@ body {
|
|||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- CUSTOM CONFIRMATION MODAL OVERLAY --- */
|
/* Switch Toggles */
|
||||||
|
.setting-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-label {
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 550;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-container input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-slider {
|
||||||
|
width: 36px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 20px;
|
||||||
|
position: relative;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-slider::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-container input:checked + .switch-slider {
|
||||||
|
background-color: var(--accent-indigo);
|
||||||
|
box-shadow: 0 0 8px var(--accent-indigo-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-container input:checked + .switch-slider::before {
|
||||||
|
transform: translateX(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouton enregistrer premium */
|
||||||
|
.btn-save {
|
||||||
|
background: var(--gradient-primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 13.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 8px;
|
||||||
|
box-shadow: 0 4px 12px var(--accent-indigo-glow);
|
||||||
|
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- GLASSMORPHIC CONFIRMATION MODAL --- */
|
||||||
.confirm-modal-overlay {
|
.confirm-modal-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: rgba(0, 0, 0, 0.45);
|
background: rgba(0, 0, 0, 0.55);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(12px);
|
||||||
-webkit-backdrop-filter: blur(8px);
|
-webkit-backdrop-filter: blur(12px);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
transition: opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-modal-overlay.open {
|
.confirm-modal-overlay.open {
|
||||||
@@ -702,13 +749,13 @@ body {
|
|||||||
|
|
||||||
.confirm-modal-box {
|
.confirm-modal-box {
|
||||||
width: 290px;
|
width: 290px;
|
||||||
background: rgba(18, 22, 33, 0.95);
|
background: rgba(14, 18, 30, 0.96);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 14px;
|
border-radius: 16px;
|
||||||
padding: 20px;
|
padding: 22px;
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.6);
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,30 +764,30 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.confirm-modal-box h3 {
|
.confirm-modal-box h3 {
|
||||||
font-size: 15px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-modal-box p {
|
.confirm-modal-box p {
|
||||||
font-size: 11.5px;
|
font-size: 12px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-modal-actions {
|
.confirm-modal-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-modal-actions button {
|
.confirm-modal-actions button {
|
||||||
padding: 8px 16px;
|
padding: 10px 18px;
|
||||||
font-size: 12px;
|
font-size: 12.5px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
@@ -750,6 +797,7 @@ body {
|
|||||||
.btn-confirm-cancel {
|
.btn-confirm-cancel {
|
||||||
background: rgba(255, 255, 255, 0.08);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-confirm-cancel:hover {
|
.btn-confirm-cancel:hover {
|
||||||
@@ -759,10 +807,11 @@ body {
|
|||||||
.btn-confirm-delete {
|
.btn-confirm-delete {
|
||||||
background: hsl(0, 75%, 50%);
|
background: hsl(0, 75%, 50%);
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 0 12px rgba(239, 68, 68, 0.3);
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-confirm-delete:hover {
|
.btn-confirm-delete:hover {
|
||||||
background: hsl(0, 75%, 45%);
|
background: hsl(0, 75%, 45%);
|
||||||
box-shadow: 0 0 16px rgba(239, 68, 68, 0.5);
|
box-shadow: 0 4px 18px rgba(239, 68, 68, 0.5);
|
||||||
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user