Initial import after prompting Claude
This commit is contained in:
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copie et installation des dépendances d'abord (cache Docker efficace)
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copie du reste du code
|
||||
COPY . .
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
72
README.md
Normal file
72
README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Ospow — Site web
|
||||
|
||||
## Lancement rapide
|
||||
|
||||
```bash
|
||||
# 1. Installer les dépendances Python
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 2. Copier et remplir la configuration
|
||||
cp .env.example .env
|
||||
# → éditez .env avec vos infos SMTP
|
||||
|
||||
# 3. Lancer le serveur de développement
|
||||
python app.py
|
||||
# → http://localhost:5000
|
||||
```
|
||||
|
||||
## Ajouter une nouvelle page
|
||||
|
||||
1. Créer `templates/ma_page.html` en commençant par :
|
||||
```html
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Mon titre{% endblock %}
|
||||
{% block content %}
|
||||
...votre contenu...
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
2. Ajouter une route dans `app.py` :
|
||||
```python
|
||||
@app.route("/ma-page")
|
||||
def ma_page():
|
||||
return render_template("ma_page.html")
|
||||
```
|
||||
|
||||
3. *(Optionnel)* Ajouter un lien dans `templates/base.html` dans la `<nav>`.
|
||||
|
||||
## Structure des fichiers
|
||||
|
||||
```
|
||||
ospow/
|
||||
├── app.py ← Serveur Flask (routes + formulaire contact)
|
||||
├── requirements.txt ← Dépendances Python
|
||||
├── .env.example ← Modèle de configuration
|
||||
├── templates/
|
||||
│ ├── base.html ← Header, nav, footer (modifié UNE fois pour toutes)
|
||||
│ ├── index.html ← Page d'accueil
|
||||
│ ├── surveillance.html ← Page Vidéosurveillance
|
||||
│ ← cloud.html ← Page Cloud souverain
|
||||
│ ├── opensource.html ← Page Open-Source
|
||||
│ └── contact.html ← Formulaire de contact
|
||||
└── static/
|
||||
├── css/style.css ← Tous les styles
|
||||
├── js/main.js ← Menu burger + animations
|
||||
└── img/ ← Vos images
|
||||
```
|
||||
|
||||
## Configuration email (formulaire de contact)
|
||||
|
||||
Éditez `.env` :
|
||||
|
||||
| Variable | Description |
|
||||
|-----------------|--------------------------------------------|
|
||||
| `SECRET_KEY` | Clé secrète Flask (changez-la !) |
|
||||
| `MAIL_SERVER` | Serveur SMTP (ex: `smtp.gmail.com`) |
|
||||
| `MAIL_PORT` | Port SMTP (587 pour TLS) |
|
||||
| `MAIL_USERNAME` | Votre adresse email |
|
||||
| `MAIL_PASSWORD` | Mot de passe (ou mot de passe d'app Gmail) |
|
||||
| `CONTACT_EMAIL` | Adresse qui reçoit les messages |
|
||||
|
||||
> **Gmail** : activez la validation en deux étapes puis créez un
|
||||
> "Mot de passe d'application" dans les paramètres de votre compte.
|
||||
138
app.py
Normal file
138
app.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
Point d'entrée principal de l'application Ospow.
|
||||
Chaque route = une page du site.
|
||||
Pour ajouter une page : créer templates/ma_page.html et ajouter une route ici.
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, request, flash, redirect, url_for, Response
|
||||
from flask_mail import Mail, Message
|
||||
from datetime import date
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.environ.get("SECRET_KEY", "changez-moi-en-production")
|
||||
|
||||
# --- Configuration de l'envoi d'emails ---
|
||||
app.config["MAIL_SERVER"] = os.environ.get("MAIL_SERVER", "smtp.gmail.com")
|
||||
app.config["MAIL_PORT"] = int(os.environ.get("MAIL_PORT", 587))
|
||||
app.config["MAIL_USE_TLS"] = True
|
||||
app.config["MAIL_USERNAME"] = os.environ.get("MAIL_USERNAME", "")
|
||||
app.config["MAIL_PASSWORD"] = os.environ.get("MAIL_PASSWORD", "")
|
||||
app.config["MAIL_DEFAULT_SENDER"] = os.environ.get("MAIL_USERNAME", "")
|
||||
|
||||
mail = Mail(app)
|
||||
CONTACT_EMAIL = os.environ.get("CONTACT_EMAIL", "contact@ospow.fr")
|
||||
|
||||
|
||||
# ─── Context global (disponible dans tous les templates) ──────────────────────
|
||||
|
||||
@app.context_processor
|
||||
def inject_globals():
|
||||
return {"current_year": date.today().year}
|
||||
|
||||
|
||||
# ─── Pages ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
|
||||
@app.route("/surveillance")
|
||||
def surveillance():
|
||||
return render_template("surveillance.html")
|
||||
|
||||
@app.route("/cloud")
|
||||
def cloud():
|
||||
return render_template("cloud.html")
|
||||
|
||||
@app.route("/opensource")
|
||||
def opensource():
|
||||
return render_template("opensource.html")
|
||||
|
||||
@app.route("/equipe")
|
||||
def equipe():
|
||||
return render_template("equipe.html")
|
||||
|
||||
@app.route("/contact", methods=["GET", "POST"])
|
||||
def contact():
|
||||
if request.method == "POST":
|
||||
nom = request.form.get("nom", "").strip()
|
||||
email = request.form.get("email", "").strip()
|
||||
sujet = request.form.get("sujet", "").strip()
|
||||
message = request.form.get("message", "").strip()
|
||||
|
||||
# Validation basique
|
||||
errors = []
|
||||
if not nom:
|
||||
errors.append("Le nom est obligatoire.")
|
||||
if not email or "@" not in email:
|
||||
errors.append("Adresse email invalide.")
|
||||
if not message:
|
||||
errors.append("Le message ne peut pas être vide.")
|
||||
|
||||
if errors:
|
||||
for e in errors:
|
||||
flash(e, "error")
|
||||
return render_template("contact.html", form_data=request.form)
|
||||
|
||||
# Envoi de l'email
|
||||
try:
|
||||
msg = Message(
|
||||
subject=f"[Ospow Contact] {sujet or 'Nouveau message'}",
|
||||
recipients=[CONTACT_EMAIL],
|
||||
body=f"De : {nom} <{email}>\n\n{message}",
|
||||
reply_to=email,
|
||||
)
|
||||
mail.send(msg)
|
||||
flash("Votre message a bien été envoyé. Nous vous répondrons rapidement !", "success")
|
||||
return redirect(url_for("contact"))
|
||||
except Exception as exc:
|
||||
app.logger.error("Erreur envoi mail : %s", exc)
|
||||
flash("Une erreur est survenue lors de l'envoi. Réessayez plus tard.", "error")
|
||||
|
||||
return render_template("contact.html", form_data={})
|
||||
|
||||
|
||||
# ─── SEO : sitemap & robots ───────────────────────────────────────────────────
|
||||
|
||||
@app.route("/sitemap.xml")
|
||||
def sitemap():
|
||||
pages = [
|
||||
(url_for("index", _external=True), "weekly", "1.0"),
|
||||
(url_for("surveillance",_external=True), "monthly", "0.9"),
|
||||
(url_for("cloud", _external=True), "monthly", "0.9"),
|
||||
(url_for("opensource", _external=True), "monthly", "0.9"),
|
||||
(url_for("equipe", _external=True), "monthly", "0.7"),
|
||||
(url_for("contact", _external=True), "monthly", "0.6"),
|
||||
]
|
||||
today = date.today().isoformat()
|
||||
lines = ['<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">']
|
||||
for loc, freq, priority in pages:
|
||||
lines.append(
|
||||
f" <url><loc>{loc}</loc>"
|
||||
f"<lastmod>{today}</lastmod>"
|
||||
f"<changefreq>{freq}</changefreq>"
|
||||
f"<priority>{priority}</priority></url>"
|
||||
)
|
||||
lines.append("</urlset>")
|
||||
return Response("\n".join(lines), mimetype="application/xml")
|
||||
|
||||
|
||||
@app.route("/robots.txt")
|
||||
def robots():
|
||||
txt = (
|
||||
"User-agent: *\n"
|
||||
"Allow: /\n"
|
||||
f"Sitemap: {url_for('sitemap', _external=True)}\n"
|
||||
)
|
||||
return Response(txt, mimetype="text/plain")
|
||||
|
||||
|
||||
# ─── Lancement ────────────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", debug=True)
|
||||
12
docker-compose.yaml
Normal file
12
docker-compose.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "5000:5000"
|
||||
# Monte le code source en direct : chaque modification est visible sans rebuild
|
||||
volumes:
|
||||
- .:/app
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
FLASK_ENV: development
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Flask>=3.0
|
||||
Flask-Mail>=0.10
|
||||
python-dotenv>=1.0
|
||||
639
static/css/style.css
Normal file
639
static/css/style.css
Normal file
@@ -0,0 +1,639 @@
|
||||
/* ============================================================
|
||||
OSPOW — Feuille de styles principale
|
||||
============================================================
|
||||
Structure :
|
||||
1. Variables & Reset
|
||||
2. Typographie
|
||||
3. Mise en page (container, sections)
|
||||
4. Header & Navigation
|
||||
5. Hero (page d'accueil)
|
||||
6. Page-hero (sous-pages)
|
||||
7. Cards
|
||||
8. Features
|
||||
9. Footer
|
||||
10. Formulaire de contact
|
||||
11. Composants utilitaires
|
||||
12. Media queries (mobile)
|
||||
============================================================ */
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
1. Variables & Reset
|
||||
────────────────────────────────────────────────────────────── */
|
||||
:root {
|
||||
--color-bg: #F5F7FA;
|
||||
--color-bg-alt: #EBF0F8;
|
||||
--color-surface: #FFFFFF;
|
||||
--color-primary: #0A1628; /* bleu nuit */
|
||||
--color-secondary: #1E3A5F; /* bleu marine */
|
||||
--color-accent: #0EA5E9; /* cyan électrique */
|
||||
--color-accent-dark: #0284C7;
|
||||
--color-text: #1A2332;
|
||||
--color-text-muted: #64748B;
|
||||
--color-border: #D1DCF0;
|
||||
--color-success: #16A34A;
|
||||
--color-error: #DC2626;
|
||||
|
||||
--font-sans: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||
--radius: 0.5rem;
|
||||
--radius-lg: 1rem;
|
||||
--shadow: 0 2px 12px rgba(10,22,40,0.08);
|
||||
--shadow-lg: 0 8px 32px rgba(10,22,40,0.14);
|
||||
--transition: 0.2s ease;
|
||||
|
||||
--container-width: 1100px;
|
||||
--header-height: 64px;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 1rem;
|
||||
line-height: 1.65;
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
img { max-width: 100%; display: block; }
|
||||
a { color: var(--color-accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
ul, ol { padding-left: 1.25rem; }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
2. Typographie
|
||||
────────────────────────────────────────────────────────────── */
|
||||
h1, h2, h3, h4 {
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
h1 { font-size: clamp(2rem, 5vw, 3rem); }
|
||||
h2 { font-size: clamp(1.5rem, 3vw, 2rem); }
|
||||
h3 { font-size: 1.15rem; }
|
||||
|
||||
.accent { color: var(--color-accent); }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
3. Mise en page
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.container {
|
||||
width: min(var(--container-width), 100% - 2rem);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-block: 4rem;
|
||||
}
|
||||
.section--alt {
|
||||
background: var(--color-bg-alt);
|
||||
}
|
||||
.section--cta {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
.section--cta h2,
|
||||
.section--cta p { color: #fff; }
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.section-sub {
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
|
||||
/* Prose (pages de détail) */
|
||||
.prose > * + * { margin-top: 1.5rem; }
|
||||
.prose h2 { margin-top: 2.5rem; }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
4. Header & Navigation
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: var(--color-primary);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
height: var(--header-height);
|
||||
}
|
||||
.header-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Logo */
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: #fff;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo-icon { color: var(--color-accent); font-size: 1.5rem; }
|
||||
.logo-text { color: #fff; }
|
||||
|
||||
/* Nav links */
|
||||
.main-nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
padding: 0;
|
||||
}
|
||||
.main-nav a {
|
||||
color: #C8D8EE;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-radius: var(--radius);
|
||||
transition: background var(--transition), color var(--transition);
|
||||
text-decoration: none;
|
||||
}
|
||||
.main-nav a:hover,
|
||||
.main-nav a.active {
|
||||
background: rgba(14,165,233,0.15);
|
||||
color: #fff;
|
||||
}
|
||||
/* Bouton "Contact" dans la nav */
|
||||
.main-nav li:last-child a {
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.main-nav li:last-child a:hover {
|
||||
background: var(--color-accent-dark);
|
||||
}
|
||||
|
||||
/* Burger (caché par défaut) */
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
.nav-toggle span {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
transition: transform var(--transition), opacity var(--transition);
|
||||
}
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
5. Hero (page d'accueil)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.hero {
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%);
|
||||
color: #fff;
|
||||
padding-block: 5rem 4rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hero-inner {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: 3rem;
|
||||
}
|
||||
.hero h1 { color: #fff; }
|
||||
.hero-lead {
|
||||
color: #C8D8EE;
|
||||
font-size: 1.1rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
max-width: 480px;
|
||||
}
|
||||
.hero-cta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Déco hexagonale (pure CSS) */
|
||||
.hero-visual { position: relative; height: 280px; }
|
||||
.hex {
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 1rem;
|
||||
opacity: 0.15;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.hex--1 { background: var(--color-accent); top: 20px; left: 60px; width: 140px; height: 140px; opacity: 0.2; }
|
||||
.hex--2 { background: #fff; top: 100px; left: 160px; opacity: 0.08; }
|
||||
.hex--3 { background: var(--color-accent); top: 60px; left: 240px; width: 80px; height: 80px; }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
6. Page-hero (sous-pages)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-hero {
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%);
|
||||
color: #fff;
|
||||
padding-block: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
.page-hero h1 { color: #fff; }
|
||||
.page-hero p { color: #C8D8EE; margin-top: 0.5rem; font-size: 1.1rem; }
|
||||
.page-hero-icon { font-size: 3rem; display: block; margin-bottom: 0.5rem; }
|
||||
.page-hero--sm { padding-block: 2rem; }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
7. Cards
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.cards--2col { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
|
||||
.cards--3col { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
|
||||
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.75rem;
|
||||
box-shadow: var(--shadow);
|
||||
transition: transform var(--transition), box-shadow var(--transition);
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.card--sm { padding: 1.25rem; }
|
||||
.card-icon { font-size: 2.5rem; margin-bottom: 0.75rem; }
|
||||
.card h3 { margin-bottom: 0.5rem; }
|
||||
.card p { color: var(--color-text-muted); font-size: 0.95rem; }
|
||||
.card-link {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
8. Features
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.features--2col { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
|
||||
.feature { text-align: center; }
|
||||
.feature-icon { font-size: 2.5rem; display: block; margin-bottom: 0.75rem; }
|
||||
.feature h3 { margin-bottom: 0.25rem; }
|
||||
.feature p { color: var(--color-text-muted); font-size: 0.95rem; }
|
||||
|
||||
/* Steps (ol numéroté stylisé) */
|
||||
.steps {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
counter-reset: step;
|
||||
}
|
||||
.steps li {
|
||||
counter-increment: step;
|
||||
padding: 1rem 1rem 1rem 3.5rem;
|
||||
position: relative;
|
||||
border-left: 2px solid var(--color-border);
|
||||
margin-left: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.steps li::before {
|
||||
content: counter(step);
|
||||
position: absolute;
|
||||
left: -1.2rem;
|
||||
top: 1rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.steps li strong { display: block; margin-bottom: 0.25rem; }
|
||||
.steps li p { color: var(--color-text-muted); font-size: 0.95rem; margin: 0; }
|
||||
|
||||
/* Tech-list */
|
||||
.tech-list { list-style: none; padding: 0; }
|
||||
.tech-list li {
|
||||
padding: 0.6rem 1rem;
|
||||
border-left: 3px solid var(--color-accent);
|
||||
margin-bottom: 0.5rem;
|
||||
background: var(--color-bg-alt);
|
||||
border-radius: 0 var(--radius) var(--radius) 0;
|
||||
}
|
||||
|
||||
/* CTA inline */
|
||||
.cta-block {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.cta-block p { color: #C8D8EE; margin-bottom: 1rem; font-size: 1.05rem; }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
9. Footer
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.site-footer {
|
||||
background: var(--color-primary);
|
||||
color: #C8D8EE;
|
||||
padding-top: 3rem;
|
||||
}
|
||||
.footer-inner {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr;
|
||||
gap: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
.footer-brand .logo-text { color: #fff; }
|
||||
.footer-brand p { color: #8AA3C0; font-size: 0.9rem; margin-top: 0.5rem; }
|
||||
.footer-nav h3,
|
||||
.footer-contact h3 { color: #fff; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.75rem; }
|
||||
.footer-nav ul { list-style: none; padding: 0; }
|
||||
.footer-nav li { margin-bottom: 0.4rem; }
|
||||
.footer-nav a,
|
||||
.footer-contact a { color: #8AA3C0; font-size: 0.9rem; }
|
||||
.footer-nav a:hover,
|
||||
.footer-contact a:hover { color: #fff; text-decoration: none; }
|
||||
.footer-contact p { font-size: 0.9rem; margin-bottom: 0.4rem; }
|
||||
.footer-bottom {
|
||||
border-top: 1px solid rgba(255,255,255,0.08);
|
||||
padding-block: 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
color: #4A637F;
|
||||
}
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
10. Formulaire de contact
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.contact-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 3rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.contact-form-wrapper { background: var(--color-surface); border-radius: var(--radius-lg); padding: 2rem; box-shadow: var(--shadow); }
|
||||
|
||||
.form-group { margin-bottom: 1.25rem; }
|
||||
.form-group label { display: block; font-weight: 600; font-size: 0.9rem; margin-bottom: 0.4rem; color: var(--color-text); }
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 0.6rem 0.85rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
transition: border-color var(--transition), box-shadow var(--transition);
|
||||
}
|
||||
.form-group input:focus,
|
||||
.form-group select:focus,
|
||||
.form-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px rgba(14,165,233,0.15);
|
||||
}
|
||||
.form-group textarea { resize: vertical; }
|
||||
.form-note { font-size: 0.8rem; color: var(--color-text-muted); margin-top: 0.75rem; }
|
||||
|
||||
.contact-info {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 2rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.contact-info h2 { margin-bottom: 0.75rem; font-size: 1.4rem; }
|
||||
.contact-details { list-style: none; padding: 0; margin: 1rem 0; }
|
||||
.contact-details li { display: flex; align-items: flex-start; gap: 0.6rem; margin-bottom: 0.75rem; font-size: 0.95rem; }
|
||||
.contact-icon { font-size: 1.1rem; flex-shrink: 0; }
|
||||
.contact-promise { background: var(--color-bg-alt); border-radius: var(--radius); padding: 1rem; margin-top: 1.5rem; }
|
||||
.contact-promise h3 { font-size: 0.95rem; margin-bottom: 0.4rem; }
|
||||
.contact-promise p { font-size: 0.9rem; color: var(--color-text-muted); line-height: 1.8; }
|
||||
|
||||
/* Flash messages */
|
||||
.flash-messages { margin-block: 1rem; }
|
||||
.flash {
|
||||
padding: 0.8rem 1rem;
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.flash--success { background: #DCFCE7; color: #166534; border: 1px solid #86EFAC; }
|
||||
.flash--error { background: #FEE2E2; color: #991B1B; border: 1px solid #FECACA; }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
11. Boutons
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.65rem 1.5rem;
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: background var(--transition), color var(--transition), border-color var(--transition);
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
.btn--primary {
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
.btn--primary:hover {
|
||||
background: var(--color-accent-dark);
|
||||
border-color: var(--color-accent-dark);
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn--ghost {
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
border-color: rgba(255,255,255,0.4);
|
||||
}
|
||||
.btn--ghost:hover {
|
||||
border-color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn--lg { padding: 0.85rem 2rem; font-size: 1.05rem; }
|
||||
.btn--full { width: 100%; }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
12. Media queries (mobile)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
Valeurs (page équipe)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.values {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.value-item {
|
||||
background: var(--color-bg-alt);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
border-left: 4px solid var(--color-accent);
|
||||
}
|
||||
.value-item span { font-size: 1.8rem; display: block; margin-bottom: 0.5rem; }
|
||||
.value-item p { font-size: 0.95rem; color: var(--color-text-muted); margin: 0; }
|
||||
.value-item strong { color: var(--color-primary); }
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
Team cards (page équipe)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.team {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
.team-card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 2rem;
|
||||
box-shadow: var(--shadow);
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
}
|
||||
.team-avatar { text-align: center; }
|
||||
.avatar-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-secondary);
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.avatar-placeholder.avatar--teal { background: #0D9488; }
|
||||
.avatar-placeholder.avatar--orange { background: #D97706; }
|
||||
.avatar-placeholder.avatar--purple { background: #7C3AED; }
|
||||
|
||||
.team-avatar img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.team-info h3 { margin-bottom: 0.2rem; }
|
||||
.team-role { color: var(--color-accent); font-weight: 600; font-size: 0.9rem; margin-bottom: 0.75rem; }
|
||||
.team-bio { color: var(--color-text-muted); font-size: 0.95rem; margin-bottom: 1rem; }
|
||||
.team-tags { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
||||
.team-tags span {
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 999px;
|
||||
padding: 0.2rem 0.7rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.team-card { grid-template-columns: 1fr; }
|
||||
.team-avatar { margin-bottom: 0.5rem; }
|
||||
}
|
||||
|
||||
|
||||
/* Animation d'apparition au scroll */
|
||||
.fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
transition: opacity 0.4s ease, transform 0.4s ease;
|
||||
}
|
||||
.fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Burger actif → croix */
|
||||
.nav-toggle.is-active span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
|
||||
.nav-toggle.is-active span:nth-child(2) { opacity: 0; }
|
||||
.nav-toggle.is-active span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
12. Media queries (mobile)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
/* Header mobile */
|
||||
.nav-toggle { display: flex; }
|
||||
.main-nav {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: var(--header-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--color-primary);
|
||||
padding: 1rem;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
|
||||
}
|
||||
.main-nav.is-open { display: block; }
|
||||
.main-nav ul { flex-direction: column; gap: 0.25rem; }
|
||||
.main-nav a { display: block; padding: 0.6rem 1rem; }
|
||||
|
||||
/* Hero */
|
||||
.hero-inner { grid-template-columns: 1fr; }
|
||||
.hero-visual { display: none; }
|
||||
|
||||
/* Cards */
|
||||
.cards,
|
||||
.cards--2col,
|
||||
.cards--3col { grid-template-columns: 1fr; }
|
||||
|
||||
/* Footer */
|
||||
.footer-inner { grid-template-columns: 1fr; gap: 1.5rem; }
|
||||
|
||||
/* Contact */
|
||||
.contact-layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
67
static/js/main.js
Normal file
67
static/js/main.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Ospow — JavaScript principal
|
||||
*
|
||||
* Fonctionnalités :
|
||||
* - Menu burger (mobile)
|
||||
* - Fermeture du menu au clic sur un lien
|
||||
* - Marquage actif automatique de la nav sur la page courante
|
||||
*/
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
// ─── Menu burger ───────────────────────────────────────────────────────────
|
||||
const toggle = document.querySelector(".nav-toggle");
|
||||
const nav = document.querySelector(".main-nav");
|
||||
|
||||
if (toggle && nav) {
|
||||
|
||||
toggle.addEventListener("click", function () {
|
||||
const isOpen = nav.classList.toggle("is-open");
|
||||
toggle.setAttribute("aria-expanded", isOpen);
|
||||
toggle.setAttribute("aria-label", isOpen ? "Fermer le menu" : "Ouvrir le menu");
|
||||
|
||||
// Animation du burger → croix
|
||||
toggle.classList.toggle("is-active", isOpen);
|
||||
});
|
||||
|
||||
// Ferme le menu si on clique sur un lien (utile sur mobile)
|
||||
nav.querySelectorAll("a").forEach(function (link) {
|
||||
link.addEventListener("click", function () {
|
||||
nav.classList.remove("is-open");
|
||||
toggle.setAttribute("aria-expanded", "false");
|
||||
toggle.classList.remove("is-active");
|
||||
});
|
||||
});
|
||||
|
||||
// Ferme le menu si on clique en dehors
|
||||
document.addEventListener("click", function (event) {
|
||||
if (!nav.contains(event.target) && !toggle.contains(event.target)) {
|
||||
nav.classList.remove("is-open");
|
||||
toggle.setAttribute("aria-expanded", "false");
|
||||
toggle.classList.remove("is-active");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Animation d'apparition au scroll (optionnel) ──────────────────────────
|
||||
// Utilise IntersectionObserver pour faire apparaître les cards en douceur
|
||||
if ("IntersectionObserver" in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add("visible");
|
||||
observer.unobserve(entry.target); // une seule fois
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
document.querySelectorAll(".card, .feature").forEach(function (el) {
|
||||
el.classList.add("fade-in");
|
||||
observer.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
136
templates/base.html
Normal file
136
templates/base.html
Normal file
@@ -0,0 +1,136 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- ── SEO : titre & description ─────────────────────────────────────────── -->
|
||||
<title>{% block title %}Ospow — Cloud souverain, Vidéosurveillance & Open-Source{% endblock %}</title>
|
||||
<meta name="description" content="{% block meta_description %}Ospow conçoit et déploie des solutions open-source souveraines : vidéosurveillance IP locale, cloud personnel auto-hébergé, déploiement de logiciels libres pour particuliers et entreprises partout en France.{% endblock %}" />
|
||||
<link rel="canonical" href="{{ request.host_url.rstrip('/') }}{{ request.path }}" />
|
||||
|
||||
<!-- ── Open Graph (Facebook, LinkedIn, WhatsApp…) ────────────────────────── -->
|
||||
<meta property="og:site_name" content="Ospow" />
|
||||
<meta property="og:locale" content="fr_FR" />
|
||||
<meta property="og:type" content="{% block og_type %}website{% endblock %}" />
|
||||
<meta property="og:title" content="{{ self.title() }}" />
|
||||
<meta property="og:description" content="{{ self.meta_description() }}" />
|
||||
<meta property="og:url" content="{{ request.url }}" />
|
||||
|
||||
<!-- ── Twitter Card ──────────────────────────────────────────────────────── -->
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content="{{ self.title() }}" />
|
||||
<meta name="twitter:description" content="{{ self.meta_description() }}" />
|
||||
|
||||
<!-- ── Feuille de style ──────────────────────────────────────────────────── -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
|
||||
|
||||
<!-- ── JSON-LD : Organisation (présent sur toutes les pages) ─────────────── -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "Ospow",
|
||||
"url": "{{ request.host_url.rstrip('/') }}",
|
||||
"description": "Collectif d'auto-entrepreneurs spécialisés dans les solutions open-source souveraines : vidéosurveillance IP, cloud personnel auto-hébergé et déploiement de logiciels libres.",
|
||||
"areaServed": "France",
|
||||
"contactPoint": {
|
||||
"@type": "ContactPoint",
|
||||
"email": "contact@ospow.fr",
|
||||
"contactType": "customer service",
|
||||
"availableLanguage": "French"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- ── JSON-LD spécifique à la page (breadcrumb, service, person…) ────────── -->
|
||||
{% block json_ld %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ═══════════════════════════════ HEADER / NAV ═══════════════════════════ -->
|
||||
<header class="site-header">
|
||||
<div class="container header-inner">
|
||||
|
||||
<!-- Logo -->
|
||||
<a href="{{ url_for('index') }}" class="logo" aria-label="Ospow — Accueil">
|
||||
<span class="logo-icon">⬡</span>
|
||||
<span class="logo-text">Ospow</span>
|
||||
</a>
|
||||
|
||||
<!-- Burger (mobile) -->
|
||||
<button class="nav-toggle" aria-label="Ouvrir le menu" aria-expanded="false">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
|
||||
<!-- Navigation principale -->
|
||||
<nav class="main-nav" id="main-nav" aria-label="Navigation principale">
|
||||
<ul>
|
||||
<li><a href="{{ url_for('index') }}" {% if request.endpoint == 'index' %}class="active" aria-current="page"{% endif %}>Accueil</a></li>
|
||||
<li><a href="{{ url_for('surveillance') }}" {% if request.endpoint == 'surveillance' %}class="active" aria-current="page"{% endif %}>Vidéosurveillance</a></li>
|
||||
<li><a href="{{ url_for('cloud') }}" {% if request.endpoint == 'cloud' %}class="active" aria-current="page"{% endif %}>Cloud souverain</a></li>
|
||||
<li><a href="{{ url_for('opensource') }}" {% if request.endpoint == 'opensource' %}class="active" aria-current="page"{% endif %}>Open-Source</a></li>
|
||||
<li><a href="{{ url_for('equipe') }}" {% if request.endpoint == 'equipe' %}class="active" aria-current="page"{% endif %}>L'équipe</a></li>
|
||||
<li><a href="{{ url_for('contact') }}" {% if request.endpoint == 'contact' %}class="active" aria-current="page"{% endif %}>Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ═══════════════════════════════ BANNIÈRE (optionnelle) ════════════════ -->
|
||||
{% block banner %}{% endblock %}
|
||||
|
||||
<!-- ═══════════════════════════════ CONTENU PRINCIPAL ════════════════════ -->
|
||||
<main id="main-content">
|
||||
|
||||
<!-- Messages flash (succès / erreur formulaire) -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="container flash-messages" role="alert">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash--{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
</main>
|
||||
|
||||
<!-- ═══════════════════════════════ FOOTER ════════════════════════════════ -->
|
||||
<footer class="site-footer" aria-label="Pied de page">
|
||||
<div class="container footer-inner">
|
||||
|
||||
<div class="footer-brand">
|
||||
<span class="logo-icon" aria-hidden="true">⬡</span>
|
||||
<span class="logo-text">Ospow</span>
|
||||
<p>Solutions numériques souveraines et open-source.</p>
|
||||
</div>
|
||||
|
||||
<nav class="footer-nav" aria-label="Services">
|
||||
<h3>Services</h3>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('surveillance') }}">Vidéosurveillance</a></li>
|
||||
<li><a href="{{ url_for('cloud') }}">Cloud souverain</a></li>
|
||||
<li><a href="{{ url_for('opensource') }}">Déploiement Open-Source</a></li>
|
||||
<li><a href="{{ url_for('equipe') }}">L'équipe</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="footer-contact">
|
||||
<h3>Contact</h3>
|
||||
<p><a href="{{ url_for('contact') }}">Formulaire de contact</a></p>
|
||||
<p><a href="mailto:contact@ospow.fr">contact@ospow.fr</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© {{ current_year }} Ospow — Tous droits réservés</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
112
templates/cloud.html
Normal file
112
templates/cloud.html
Normal file
@@ -0,0 +1,112 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Cloud Souverain Personnel Auto-hébergé — Nextcloud, Mail, Vaultwarden | Ospow{% endblock %}
|
||||
{% block meta_description %}Déployez votre propre cloud sur votre matériel ou un serveur français : fichiers (Nextcloud), e-mail souverain, agenda CalDAV et gestionnaire de mots de passe Vaultwarden. Vos données ne quittent jamais votre infrastructure.{% endblock %}
|
||||
|
||||
{% block json_ld %}
|
||||
<script type="application/ld+json">
|
||||
[
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{ "@type": "ListItem", "position": 1, "name": "Accueil", "item": "{{ url_for('index', _external=True) }}" },
|
||||
{ "@type": "ListItem", "position": 2, "name": "Cloud souverain", "item": "{{ url_for('cloud', _external=True) }}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Service",
|
||||
"name": "Cloud souverain personnel auto-hébergé",
|
||||
"provider": { "@type": "Organization", "name": "Ospow" },
|
||||
"description": "Déploiement et maintenance d'une infrastructure cloud personnelle : Nextcloud, serveur mail, CalDAV/CardDAV, Vaultwarden. Hébergement chez vous ou sur serveur dédié français.",
|
||||
"areaServed": "France",
|
||||
"serviceType": "Hébergement cloud souverain",
|
||||
"offers": { "@type": "Offer", "availability": "https://schema.org/InStock", "priceCurrency": "EUR" }
|
||||
}
|
||||
]
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block banner %}
|
||||
<div class="page-hero">
|
||||
<div class="container">
|
||||
<span class="page-hero-icon">☁️</span>
|
||||
<h1>Cloud souverain personnel</h1>
|
||||
<p>Google Drive, Dropbox... mais chez vous.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="section">
|
||||
<div class="container prose">
|
||||
|
||||
<h2>Vos données vous appartiennent</h2>
|
||||
<p>
|
||||
Chaque jour, des milliards de fichiers personnels transitent par des
|
||||
serveurs dont vous ne contrôlez pas les conditions d'accès. Ospow vous
|
||||
installe un cloud <strong>hébergé sur votre matériel</strong>, accessible
|
||||
partout, aussi simple que les outils grand public.
|
||||
</p>
|
||||
|
||||
<div class="cards cards--2col">
|
||||
|
||||
<article class="card">
|
||||
<h3>📁 Fichiers & Photos</h3>
|
||||
<p>
|
||||
Synchronisation multi-appareils (PC, Mac, iOS, Android) via
|
||||
Nextcloud. Interface identique à Google Drive, données chez vous.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h3>📧 Email souverain</h3>
|
||||
<p>
|
||||
Serveur mail complet (Stalwart / Mailcow) avec antispam, DKIM, DMARC.
|
||||
Votre propre domaine, hébergé par vous.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h3>📅 Agenda & Contacts</h3>
|
||||
<p>
|
||||
CalDAV / CardDAV intégré à Nextcloud. Compatible avec tous vos
|
||||
appareils sans passer par Google ou Apple.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h3>🔐 Gestion des mots de passe</h3>
|
||||
<p>
|
||||
Déploiement de Vaultwarden (compatible Bitwarden). Un coffre-fort
|
||||
partageable en famille ou en équipe, sur votre serveur.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Options d'hébergement</h2>
|
||||
<div class="features features--2col">
|
||||
<div class="feature">
|
||||
<span class="feature-icon">🏠</span>
|
||||
<h3>Chez vous</h3>
|
||||
<p>Sur un mini-PC ou un NAS dans votre domicile ou locaux. Investissement unique, aucun abonnement cloud.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<span class="feature-icon">🖥️</span>
|
||||
<h3>Serveur dédié</h3>
|
||||
<p>Sur un serveur dédié hébergé en France, choisi par vous. Vous restez propriétaire de vos données.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cta-block">
|
||||
<p>Prêt à reprendre le contrôle de vos données ?</p>
|
||||
<a href="{{ url_for('contact') }}" class="btn btn--primary">Demander un devis</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
102
templates/contact.html
Normal file
102
templates/contact.html
Normal file
@@ -0,0 +1,102 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Contactez Ospow — Devis gratuit & sans engagement{% endblock %}
|
||||
{% block meta_description %}Discutez de votre projet avec l'équipe Ospow : vidéosurveillance IP, cloud souverain ou déploiement open-source. Premier échange gratuit, réponse sous 48 h. Devis clair et sans jargon.{% endblock %}
|
||||
|
||||
{% block json_ld %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ContactPage",
|
||||
"name": "Contactez Ospow",
|
||||
"url": "{{ url_for('contact', _external=True) }}",
|
||||
"description": "Formulaire de contact pour demander un devis ou poser une question à l'équipe Ospow."
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block banner %}
|
||||
<div class="page-hero page-hero--sm">
|
||||
<div class="container">
|
||||
<h1>Contactez-nous</h1>
|
||||
<p>Un premier échange est toujours gratuit et sans engagement.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="section">
|
||||
<div class="container contact-layout">
|
||||
|
||||
<!-- Formulaire -->
|
||||
<div class="contact-form-wrapper">
|
||||
<form method="POST" action="{{ url_for('contact') }}" novalidate class="contact-form">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="nom">Votre nom *</label>
|
||||
<input type="text" id="nom" name="nom"
|
||||
value="{{ form_data.get('nom', '') }}"
|
||||
placeholder="Jean Dupont" required />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Votre adresse email *</label>
|
||||
<input type="email" id="email" name="email"
|
||||
value="{{ form_data.get('email', '') }}"
|
||||
placeholder="jean@exemple.fr" required />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sujet">Sujet</label>
|
||||
<select id="sujet" name="sujet">
|
||||
<option value="" {% if not form_data.get('sujet') %}selected{% endif %}>— Choisissez un sujet —</option>
|
||||
<option value="Vidéosurveillance" {% if form_data.get('sujet') == 'Vidéosurveillance' %}selected{% endif %}>Vidéosurveillance</option>
|
||||
<option value="Cloud souverain" {% if form_data.get('sujet') == 'Cloud souverain' %}selected{% endif %}>Cloud souverain</option>
|
||||
<option value="Déploiement open-source" {% if form_data.get('sujet') == 'Déploiement open-source' %}selected{% endif %}>Déploiement open-source</option>
|
||||
<option value="Autre" {% if form_data.get('sujet') == 'Autre' %}selected{% endif %}>Autre</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="message">Votre message *</label>
|
||||
<textarea id="message" name="message" rows="6"
|
||||
placeholder="Décrivez votre projet ou votre question..." required>{{ form_data.get('message', '') }}</textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary btn--full">Envoyer le message</button>
|
||||
<p class="form-note">* Champs obligatoires. Vos données sont utilisées uniquement pour répondre à votre demande.</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Infos de contact -->
|
||||
<aside class="contact-info">
|
||||
<h2>Ospow</h2>
|
||||
<p>Nous intervenons partout en France, principalement à distance.</p>
|
||||
|
||||
<ul class="contact-details">
|
||||
<li>
|
||||
<span class="contact-icon">✉️</span>
|
||||
<a href="mailto:contact@ospow.fr">contact@ospow.fr</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="contact-icon">⏱️</span>
|
||||
Réponse sous 48 h ouvrées
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="contact-promise">
|
||||
<h3>Notre engagement</h3>
|
||||
<p>
|
||||
Premier échange gratuit & sans engagement.<br>
|
||||
Devis clair et détaillé.<br>
|
||||
Pas de jargon inutile.
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
218
templates/equipe.html
Normal file
218
templates/equipe.html
Normal file
@@ -0,0 +1,218 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}L'équipe Ospow — Collectif d'experts open-source & sécurité informatique{% endblock %}
|
||||
{% block meta_description %}Découvrez le collectif Ospow : des auto-entrepreneurs passionnés d'open-source, forts de 20 ans d'expérience cumulée en sécurité des systèmes, infrastructure Linux et déploiement logiciel. Basés en France.{% endblock %}
|
||||
|
||||
{% block json_ld %}
|
||||
<script type="application/ld+json">
|
||||
[
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{ "@type": "ListItem", "position": 1, "name": "Accueil", "item": "{{ url_for('index', _external=True) }}" },
|
||||
{ "@type": "ListItem", "position": 2, "name": "L'équipe", "item": "{{ url_for('equipe', _external=True) }}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Gérald Colangelo",
|
||||
"jobTitle": "Fondateur — RSSI & Expert infrastructure",
|
||||
"worksFor": { "@type": "Organization", "name": "Ospow" },
|
||||
"knowsAbout": ["GNU/Linux", "Sécurité réseau", "TCP/IP", "Python", "Firewalling"]
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Sofia Martel",
|
||||
"jobTitle": "Administratrice systèmes & Cloud souverain",
|
||||
"worksFor": { "@type": "Organization", "name": "Ospow" },
|
||||
"knowsAbout": ["Proxmox", "Nextcloud", "Ansible", "Docker", "ZFS"]
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Théo Lasserre",
|
||||
"jobTitle": "Expert vidéosurveillance & Domotique",
|
||||
"worksFor": { "@type": "Organization", "name": "Ospow" },
|
||||
"knowsAbout": ["Frigate", "ZoneMinder", "ONVIF", "WireGuard"]
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Camille Fontaine",
|
||||
"jobTitle": "Déploiement applicatif & Formatrice",
|
||||
"worksFor": { "@type": "Organization", "name": "Ospow" },
|
||||
"knowsAbout": ["Gitea", "Mattermost", "Matomo", "Python", "Formation"]
|
||||
}
|
||||
]
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block banner %}
|
||||
<div class="page-hero">
|
||||
<div class="container">
|
||||
<span class="page-hero-icon">🤝</span>
|
||||
<h1>Qui sommes-nous ?</h1>
|
||||
<p>Un collectif passionné, au service de votre souveraineté numérique.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Notre histoire -->
|
||||
<section class="section">
|
||||
<div class="container prose">
|
||||
|
||||
<h2>Le collectif Ospow</h2>
|
||||
<p>
|
||||
Ospow n'est pas une SSII comme les autres. C'est un collectif d'<strong>auto-entrepreneurs
|
||||
passionnés par le logiciel libre</strong>, réunis autour d'une conviction commune :
|
||||
la technologie doit servir ses utilisateurs, pas les enfermer.
|
||||
</p>
|
||||
<p>
|
||||
Chacun d'entre nous cumule plusieurs années d'expérience dans des domaines
|
||||
complémentaires — sécurité des systèmes, infrastructure réseau, administration
|
||||
Linux, développement — acquises dans des contextes exigeants : industrie,
|
||||
défense, conseil IT, startups. Nous avons vu de l'intérieur comment les
|
||||
grandes entreprises gèrent (ou maltraitent) les données de leurs clients.
|
||||
C'est précisément ce qui nous a conduits à choisir une autre voie.
|
||||
</p>
|
||||
<p>
|
||||
Nous intervenons principalement à distance, sur toute la France, avec un
|
||||
engagement simple : <strong>pas de jargon inutile, pas de sur-ingénierie,
|
||||
des solutions qui durent</strong>.
|
||||
</p>
|
||||
|
||||
<div class="values">
|
||||
<div class="value-item">
|
||||
<span>📖</span>
|
||||
<p><strong>Open-source d'abord</strong><br>On n'installe que ce qu'on peut auditer, modifier et redistribuer librement.</p>
|
||||
</div>
|
||||
<div class="value-item">
|
||||
<span>🔒</span>
|
||||
<p><strong>Souveraineté par défaut</strong><br>Vos données restent sous votre contrôle, toujours.</p>
|
||||
</div>
|
||||
<div class="value-item">
|
||||
<span>🤝</span>
|
||||
<p><strong>Collectif, pas entreprise</strong><br>Pas de hiérarchie. Chaque membre est libre, responsable et impliqué.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Core team -->
|
||||
<section class="section section--alt">
|
||||
<div class="container">
|
||||
<h2 class="section-title">La core team</h2>
|
||||
<p class="section-sub">Les visages derrière les terminaux.</p>
|
||||
|
||||
<div class="team">
|
||||
|
||||
<!-- Gérald Colangelo -->
|
||||
<article class="team-card">
|
||||
<div class="team-avatar">
|
||||
{# Remplacez l'URL ci-dessous par une vraie photo (ex: /static/img/gerald.jpg) #}
|
||||
<div class="avatar-placeholder">GC</div>
|
||||
</div>
|
||||
<div class="team-info">
|
||||
<h3>Gérald Colangelo</h3>
|
||||
<p class="team-role">Fondateur — Sécurité & Infrastructure</p>
|
||||
<p class="team-bio">
|
||||
RSSI de formation et de terrain, Gérald cumule plus de vingt ans
|
||||
d'expérience dans les systèmes d'information critiques, avec des
|
||||
passages chez Sopra Steria et ECA Group. Expert GNU/Linux, réseaux
|
||||
TCP/IP et sécurité périmétrique, il a développé une conviction forte :
|
||||
la meilleure sécurité est celle que l'on maîtrise de bout en bout.
|
||||
C'est de là qu'est née Ospow — l'envie de mettre cette expertise au
|
||||
service de ceux qui refusent de sous-traiter leur souveraineté
|
||||
numérique à des tiers opaques.
|
||||
</p>
|
||||
<div class="team-tags">
|
||||
<span>GNU/Linux</span><span>Sécurité réseau</span><span>Python</span><span>TCP/IP</span><span>Firewalling</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Sofia Martel (fictif) -->
|
||||
<article class="team-card">
|
||||
<div class="team-avatar">
|
||||
<div class="avatar-placeholder avatar--teal">SM</div>
|
||||
</div>
|
||||
<div class="team-info">
|
||||
<h3>Sofia Martel</h3>
|
||||
<p class="team-role">Infrastructure & Cloud souverain</p>
|
||||
<p class="team-bio">
|
||||
Administratrice systèmes et DevOps depuis dix ans, Sofia est la
|
||||
référente de l'équipe sur tout ce qui touche à l'auto-hébergement.
|
||||
Nextcloud, Proxmox, ZFS, Kubernetes bare-metal : elle a tout cassé,
|
||||
tout réparé, et en a fait une documentation. Elle croit que le droit
|
||||
à la vie privée devrait être un paramètre par défaut, pas une option payante.
|
||||
</p>
|
||||
<div class="team-tags">
|
||||
<span>Proxmox</span><span>Nextcloud</span><span>Ansible</span><span>Docker</span><span>ZFS</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Théo Lasserre (fictif) -->
|
||||
<article class="team-card">
|
||||
<div class="team-avatar">
|
||||
<div class="avatar-placeholder avatar--orange">TL</div>
|
||||
</div>
|
||||
<div class="team-info">
|
||||
<h3>Théo Lasserre</h3>
|
||||
<p class="team-role">Vidéosurveillance & Domotique</p>
|
||||
<p class="team-bio">
|
||||
Électronicien reconverti dans l'IT, Théo s'est spécialisé dans les
|
||||
systèmes embarqués et la vidéosurveillance IP. Il a déployé des
|
||||
installations Frigate et ZoneMinder dans des contextes allant du
|
||||
logement individuel aux entrepôts industriels. Partisan du matériel
|
||||
réparable, il évite soigneusement les caméras à abonnement obligatoire
|
||||
et les NVR dont le firmware n'est pas auditable.
|
||||
</p>
|
||||
<div class="team-tags">
|
||||
<span>Frigate</span><span>ZoneMinder</span><span>ONVIF</span><span>WireGuard</span><span>Raspberry Pi</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Camille Fontaine (fictif) -->
|
||||
<article class="team-card">
|
||||
<div class="team-avatar">
|
||||
<div class="avatar-placeholder avatar--purple">CF</div>
|
||||
</div>
|
||||
<div class="team-info">
|
||||
<h3>Camille Fontaine</h3>
|
||||
<p class="team-role">Déploiement applicatif & Formation</p>
|
||||
<p class="team-bio">
|
||||
Développeuse fullstack et formatrice occasionnelle, Camille est la
|
||||
personne qui traduit les besoins métier en solutions concrètes.
|
||||
Elle choisit les bons outils open-source parmi les dizaines disponibles,
|
||||
les configure pour qu'ils soient maintenables, puis forme les équipes
|
||||
pour qu'elles gagnent en autonomie. Sa règle d'or : une solution
|
||||
que le client ne comprend pas est une solution ratée.
|
||||
</p>
|
||||
<div class="team-tags">
|
||||
<span>Gitea</span><span>Mattermost</span><span>Matomo</span><span>Python</span><span>Formation</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Rejoindre -->
|
||||
<section class="section section--cta">
|
||||
<div class="container text-center">
|
||||
<h2>Vous partagez nos valeurs ?</h2>
|
||||
<p>Le collectif est ouvert. Si vous êtes passionné d'open-source et que vous voulez contribuer, parlons-en.</p>
|
||||
<a href="{{ url_for('contact') }}" class="btn btn--primary btn--lg">Nous écrire</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
129
templates/index.html
Normal file
129
templates/index.html
Normal file
@@ -0,0 +1,129 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Ospow — Cloud souverain, Vidéosurveillance IP & Open-Source en France{% endblock %}
|
||||
{% block meta_description %}Ospow : collectif d'experts open-source basé en France. Nous déployons votre cloud personnel auto-hébergé, vos systèmes de vidéosurveillance IP et vos solutions logicielles libres. Premier échange gratuit.{% endblock %}
|
||||
|
||||
{% block json_ld %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "Ospow",
|
||||
"url": "{{ request.host_url.rstrip('/') }}",
|
||||
"description": "Collectif open-source spécialisé en cloud souverain, vidéosurveillance IP et déploiement de logiciels libres en France."
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block banner %}
|
||||
<section class="hero">
|
||||
<div class="container hero-inner">
|
||||
<div class="hero-text">
|
||||
<h1>Votre numérique,<br><span class="accent">sous votre contrôle.</span></h1>
|
||||
<p class="hero-lead">
|
||||
Ospow conçoit et déploie des solutions open-source souveraines pour les
|
||||
particuliers et les entreprises qui refusent de confier leurs données aux
|
||||
géants du cloud.
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="{{ url_for('contact') }}" class="btn btn--primary">Demander un devis</a>
|
||||
<a href="#services" class="btn btn--ghost">Nos services</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-visual" aria-hidden="true">
|
||||
<div class="hero-hexgrid">
|
||||
<div class="hex hex--1"></div>
|
||||
<div class="hex hex--2"></div>
|
||||
<div class="hex hex--3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Services -->
|
||||
<section id="services" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Nos solutions</h2>
|
||||
<p class="section-sub">Trois domaines d'expertise, une seule philosophie : vous gardez la main.</p>
|
||||
|
||||
<div class="cards">
|
||||
|
||||
<article class="card">
|
||||
<div class="card-icon">📷</div>
|
||||
<h3>Vidéosurveillance</h3>
|
||||
<p>
|
||||
Installation et gestion de systèmes de surveillance IP sur mesure,
|
||||
avec stockage local ou hébergé chez vous. Pas de cloud tiers, pas
|
||||
d'abonnement imposé.
|
||||
</p>
|
||||
<a href="{{ url_for('surveillance') }}" class="card-link">En savoir plus →</a>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="card-icon">☁️</div>
|
||||
<h3>Cloud souverain personnel</h3>
|
||||
<p>
|
||||
Vos fichiers, contacts, agenda et mails hébergés sur votre propre
|
||||
infrastructure. Reprenez le contrôle de vos données personnelles avec
|
||||
des outils éprouvés.
|
||||
</p>
|
||||
<a href="{{ url_for('cloud') }}" class="card-link">En savoir plus →</a>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="card-icon">⚙️</div>
|
||||
<h3>Déploiement Open-Source</h3>
|
||||
<p>
|
||||
Installation, configuration et maintenance sur mesure de solutions
|
||||
open-source pour votre entreprise. On s'occupe de tout, vous profitez
|
||||
du résultat.
|
||||
</p>
|
||||
<a href="{{ url_for('opensource') }}" class="card-link">En savoir plus →</a>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pourquoi Ospow -->
|
||||
<section class="section section--alt">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Pourquoi Ospow ?</h2>
|
||||
<div class="features">
|
||||
<div class="feature">
|
||||
<span class="feature-icon">🔒</span>
|
||||
<h3>Souveraineté</h3>
|
||||
<p>Vos données ne quittent jamais votre périmètre de confiance.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<span class="feature-icon">🛠️</span>
|
||||
<h3>Sur mesure</h3>
|
||||
<p>Chaque déploiement est adapté à vos besoins réels, sans sur-ingénierie.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<span class="feature-icon">🤝</span>
|
||||
<h3>Accompagnement</h3>
|
||||
<p>Formation, documentation et support : vous n'êtes jamais seul.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<span class="feature-icon">📖</span>
|
||||
<h3>Open-Source</h3>
|
||||
<p>Pas de boîte noire. Tout ce qu'on déploie est auditable et pérenne.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA final -->
|
||||
<section class="section section--cta">
|
||||
<div class="container text-center">
|
||||
<h2>Un projet en tête ?</h2>
|
||||
<p>Parlons-en. Un premier échange est toujours gratuit.</p>
|
||||
<a href="{{ url_for('contact') }}" class="btn btn--primary btn--lg">Nous contacter</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
121
templates/opensource.html
Normal file
121
templates/opensource.html
Normal file
@@ -0,0 +1,121 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Déploiement & Maintenance de Solutions Open-Source sur Mesure | Ospow{% endblock %}
|
||||
{% block meta_description %}Ospow installe et maintient pour vous les meilleurs logiciels open-source : gestion de projet (Gitea, Plane), messagerie (Mattermost), analytics (Matomo), monitoring (Grafana). Formation incluse, sans jargon.{% endblock %}
|
||||
|
||||
{% block json_ld %}
|
||||
<script type="application/ld+json">
|
||||
[
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{ "@type": "ListItem", "position": 1, "name": "Accueil", "item": "{{ url_for('index', _external=True) }}" },
|
||||
{ "@type": "ListItem", "position": 2, "name": "Déploiement Open-Source", "item": "{{ url_for('opensource', _external=True) }}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Service",
|
||||
"name": "Déploiement et maintenance de logiciels open-source",
|
||||
"provider": { "@type": "Organization", "name": "Ospow" },
|
||||
"description": "Installation, configuration, migration et maintenance sur mesure de solutions open-source pour entreprises et associations : outils collaboratifs, analytics, monitoring, e-commerce.",
|
||||
"areaServed": "France",
|
||||
"serviceType": "Déploiement logiciel open-source",
|
||||
"offers": { "@type": "Offer", "availability": "https://schema.org/InStock", "priceCurrency": "EUR" }
|
||||
}
|
||||
]
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block banner %}
|
||||
<div class="page-hero">
|
||||
<div class="container">
|
||||
<span class="page-hero-icon">⚙️</span>
|
||||
<h1>Déploiement Open-Source</h1>
|
||||
<p>Les meilleurs outils du monde libre, installés et maintenus pour vous.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="section">
|
||||
<div class="container prose">
|
||||
|
||||
<h2>L'open-source sans la complexité</h2>
|
||||
<p>
|
||||
Les logiciels open-source sont souvent les plus fiables, les plus sécurisés
|
||||
et les plus évolutifs. Mais les installer et les maintenir demande du temps et
|
||||
des compétences. <strong>Ospow s'en charge</strong>, de l'installation initiale
|
||||
à la maintenance longue durée.
|
||||
</p>
|
||||
|
||||
<h2>Exemples de solutions déployées</h2>
|
||||
<div class="cards cards--3col">
|
||||
|
||||
<article class="card card--sm">
|
||||
<h3>🗂️ Gestion de projet</h3>
|
||||
<p>Plane, Taiga, Gitea — alternatives souveraines à Jira, GitHub, Trello.</p>
|
||||
</article>
|
||||
|
||||
<article class="card card--sm">
|
||||
<h3>💬 Communication</h3>
|
||||
<p>Mattermost, Matrix/Element — messagerie d'équipe hébergée chez vous.</p>
|
||||
</article>
|
||||
|
||||
<article class="card card--sm">
|
||||
<h3>📊 Analytics</h3>
|
||||
<p>Matomo, Plausible — statistiques web sans RGPD headaches.</p>
|
||||
</article>
|
||||
|
||||
<article class="card card--sm">
|
||||
<h3>🔍 Monitoring</h3>
|
||||
<p>Grafana + Prometheus — supervision de vos serveurs et applications.</p>
|
||||
</article>
|
||||
|
||||
<article class="card card--sm">
|
||||
<h3>📝 Wiki & Docs</h3>
|
||||
<p>BookStack, Wiki.js — base de connaissances interne pour votre équipe.</p>
|
||||
</article>
|
||||
|
||||
<article class="card card--sm">
|
||||
<h3>🛒 E-commerce</h3>
|
||||
<p>PrestaShop, WooCommerce — boutique en ligne sans commission plateforme.</p>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Notre méthode</h2>
|
||||
<ol class="steps">
|
||||
<li>
|
||||
<strong>Audit de vos besoins</strong>
|
||||
<p>On commence par comprendre votre activité et vos contraintes avant de proposer quoi que ce soit.</p>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Choix de la solution</strong>
|
||||
<p>On sélectionne ensemble l'outil le plus adapté, en évitant la sur-ingénierie.</p>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Déploiement</strong>
|
||||
<p>Installation sur votre serveur, configuration sécurisée, migration des données existantes.</p>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Formation</strong>
|
||||
<p>On vous montre comment utiliser et administrer votre nouvel outil.</p>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Maintenance</strong>
|
||||
<p>Mises à jour, sauvegardes, supervision — on reste disponibles sur le long terme.</p>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="cta-block">
|
||||
<p>Un outil en particulier vous intéresse ?</p>
|
||||
<a href="{{ url_for('contact') }}" class="btn btn--primary">Parlons de votre projet</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
104
templates/surveillance.html
Normal file
104
templates/surveillance.html
Normal file
@@ -0,0 +1,104 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Vidéosurveillance IP sur mesure — Stockage local sans abonnement | Ospow{% endblock %}
|
||||
{% block meta_description %}Installation de systèmes de vidéosurveillance IP avec stockage local (Frigate, ZoneMinder) : vos images restent sur votre réseau, sans cloud tiers ni abonnement mensuel. Accès à distance sécurisé via VPN.{% endblock %}
|
||||
|
||||
{% block json_ld %}
|
||||
<script type="application/ld+json">
|
||||
[
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{ "@type": "ListItem", "position": 1, "name": "Accueil", "item": "{{ url_for('index', _external=True) }}" },
|
||||
{ "@type": "ListItem", "position": 2, "name": "Vidéosurveillance", "item": "{{ url_for('surveillance', _external=True) }}" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Service",
|
||||
"name": "Vidéosurveillance IP sur mesure",
|
||||
"provider": { "@type": "Organization", "name": "Ospow" },
|
||||
"description": "Installation et configuration de systèmes de vidéosurveillance IP open-source avec stockage local, accès distant chiffré et alertes intelligentes.",
|
||||
"areaServed": "France",
|
||||
"serviceType": "Vidéosurveillance",
|
||||
"offers": { "@type": "Offer", "availability": "https://schema.org/InStock", "priceCurrency": "EUR" }
|
||||
}
|
||||
]
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block banner %}
|
||||
<div class="page-hero">
|
||||
<div class="container">
|
||||
<span class="page-hero-icon">📷</span>
|
||||
<h1>Vidéosurveillance</h1>
|
||||
<p>Des yeux sur votre bien, des données chez vous.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="section">
|
||||
<div class="container prose">
|
||||
|
||||
<h2>Surveillez sans dépendre</h2>
|
||||
<p>
|
||||
La plupart des systèmes de caméras du marché envoient vos flux vidéo vers
|
||||
des serveurs aux États-Unis ou en Asie. Chez Ospow, nous déployons des
|
||||
solutions où <strong>chaque image reste sur votre réseau</strong>.
|
||||
</p>
|
||||
|
||||
<div class="cards cards--2col">
|
||||
|
||||
<article class="card">
|
||||
<h3>📦 Installation clé en main</h3>
|
||||
<p>
|
||||
Choix du matériel, câblage, configuration du NVR (enregistreur réseau)
|
||||
et des caméras IP. Nous intervenons chez vous ou à distance.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h3>🗄️ Stockage local</h3>
|
||||
<p>
|
||||
Enregistrement sur disque dur local ou NAS. Pas d'abonnement mensuel,
|
||||
pas de données chez un tiers.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h3>📱 Accès distant sécurisé</h3>
|
||||
<p>
|
||||
Consultez vos caméras depuis votre téléphone via un tunnel chiffré
|
||||
(WireGuard / VPN). Personne d'autre n'y accède.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h3>🔔 Alertes intelligentes</h3>
|
||||
<p>
|
||||
Détection de mouvement, notifications push, enregistrement à la
|
||||
demande. Tout configuré selon vos habitudes.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Logiciels utilisés</h2>
|
||||
<ul class="tech-list">
|
||||
<li><strong>Frigate</strong> — NVR open-source avec détection d'objets par IA locale</li>
|
||||
<li><strong>MotionEye / MotionEyeOS</strong> — Interface légère pour petites installations</li>
|
||||
<li><strong>ZoneMinder</strong> — Solution complète pour les grandes installations</li>
|
||||
</ul>
|
||||
|
||||
<div class="cta-block">
|
||||
<p>Vous avez un projet de vidéosurveillance ?</p>
|
||||
<a href="{{ url_for('contact') }}" class="btn btn--primary">Discutons-en</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user