feat(PagesController): Ajoute la vérification de disponibilité du slug.

Ajoute une route pour vérifier la disponibilité d'un slug d'EPage.
Utilise EpageService pour vérifier si le slug est disponible.
Retourne une réponse JSON indiquant la disponibilité et le slug.
```
This commit is contained in:
Serreau Jovann
2025-12-26 13:31:23 +01:00
parent 4f08db7541
commit 80e4eaa907
6 changed files with 467 additions and 442 deletions

2
.env
View File

@@ -56,7 +56,7 @@ VAPID_PK=DsOg7jToRSD-VpNSV1Gt3YAhSwz4l-nqeu7yFvzbSxg
VAPID_PC=BKz0kdcsG6kk9KxciPpkfP8kEDAd408inZecij5kBDbQ1ZGZSNwS4KZ8FerC28LFXvgSqpDXtor3ePo0zBCdNqo
CLOUDFLARE_ZONE_ID=a26d2ecd33d18c984f348eeb060ed5b3
CLOUDFLARE_API_TOKEN=zhpO0bBO8wdogBrDuePbpGzMRrY6pItGhPSmhQ-h
CLOUDFLARE_API_TOKEN=Kq_hpaH_ng-hAeGsJo6KhQb2TxYW1v6lRGE84aOR
MARCHAND_ID=5685183792
###> google/apiclient ###
GOOGLE_API_KEY=

View File

@@ -75,8 +75,8 @@ vich_uploader:
delete_on_update: true
delete_on_remove: true
epage_avatar:
uri_prefix: /epage_avatar/events
upload_destination: '%kernel.project_dir%/public/storage/epage_avatar'
uri_prefix: /storage/epage/
upload_destination: '%kernel.project_dir%/public/storage/epage'
namer: App\VichUploader\Namer\Epage\AvatarNamer # Replaced namer
directory_namer: App\VichUploader\DirectoryNamer\Epage\DirectoryNamer
inject_on_load: true

View File

@@ -5,6 +5,7 @@ namespace App\Controller;
use App\Entity\OnBoaringEpage;
use App\Form\EPageOnboard;
use App\Repository\AbonementsRepository;
use App\Service\Epage\EpageService;
use Cocur\Slugify\Slugify;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -23,6 +24,15 @@ class PagesController extends AbstractController
]);
}
#[Route(path: '/pages/check', name: 'app_pages_check', options: ['sitemap' => false], methods: ['GET','POST'])]
public function check(Request $request,EpageService $epageService): Response
{
$s = new Slugify();
$g = $request->get('q',null);
$slug = $s->slugify($g);
$isAvailable = $epageService->checkDispo($slug);
return $this->json(['available' => $isAvailable, 'slug' => $slug]);
}
#[Route(path: '/pages/discover', name: 'app_pages_discover', options: ['sitemap' => false], methods: ['GET','POST'])]
public function discover(): Response
{

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Service\Epage;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class EpageService
{
const EPAGE_ROOT= ".e-cosplay.fr";
private mixed $zoneId;
private mixed $apiToken;
public function __construct(private readonly HttpClientInterface $httpClient,private readonly TranslatorInterface $translator)
{
// Les variables sont injectées via le conteneur de services (services.yaml)
$this->zoneId = $_ENV['CLOUDFLARE_ZONE_ID'];
$this->apiToken = $_ENV['CLOUDFLARE_API_TOKEN'];
}
public function checkDispo(string $dns): bool
{
$finalDomain = $dns . self::EPAGE_ROOT;
try {
$response = $this->httpClient->request('GET', "https://api.cloudflare.com/client/v4/zones/{$this->zoneId}/dns_records", [
'headers' => [
'Authorization' => 'Bearer ' . $this->apiToken,
'Content-Type' => 'application/json',
],
'query' => [
'name' => $finalDomain, // On filtre directement par le nom complet
],
]);
$data = $response->toArray();
// Cloudflare retourne un tableau 'result'.
// S'il est vide, l'enregistrement n'existe pas, donc c'est disponible (true).
return empty($data['result']);
} catch (\Exception $e) {
// En cas d'erreur API, on considère par sécurité que ce n'est pas disponible
// ou vous pouvez logger l'erreur selon votre politique.
return false;
}
}
public function createDns(string $dns) : array
{
$final = $dns.self::EPAGE_ROOT;
}
public function deleteDns(string $dns) : array
{
$final = $dns.self::EPAGE_ROOT;
}
public function updateDns(string $dns) : array
{
$final = $dns.self::EPAGE_ROOT;
}
public function addCustomDns()
{
}
public function deleteCustomDns()
{
}
}

View File

@@ -5,145 +5,207 @@
{% block canonical_url %}<link rel="canonical" href="{{ url('app_pages_onboaring',{type:app.request.get('abo','M1')}) }}" />{% endblock %}
{% block body %}
<div class="min-h-screen bg-[#f0f0f0] py-12 px-4 italic">
{{ form_start(form) }}
<!-- Conteneur principal de la carte -->
<div class="mt-2 mb-2 w-full max-w-4xl mx-auto mt-2 bg-white shadow-xl rounded-2xl p-6 md:p-10 border border-gray-100">
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold text-gray-800 mb-8 text-center">{{ 'onboarding.form.title'|trans }}</h1>
{# EN-TÊTE DU FORMULAIRE #}
<div class="mb-12 text-center">
<h1 class="inline-block text-4xl md:text-6xl font-black bg-gray-900 text-white p-6 border-8 border-gray-900 shadow-[12px_12px_0px_rgba(239,68,68,1)] uppercase tracking-tighter">
{{ 'onboarding.form.title'|trans }}
</h1>
</div>
<!-- Contenu des Étapes (Formulaire) - Tous affichés en colonnes -->
<div class="space-y-6">
<div class="space-y-12">
<!-- Étape 1 Contenu -->
<div class="form-section">
<div class="p-6 bg-indigo-50 rounded-xl border border-indigo-200 shadow-sm">
<h2 class="text-2xl font-bold text-indigo-700 mb-4 border-b border-indigo-300 pb-2 flex items-center">
<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
{# SECTION 1 : INFORMATIONS PERSONNELLES #}
<div class="bg-white border-8 border-gray-900 p-8 shadow-[12px_12px_0px_#4f46e5]">
<h2 class="text-3xl font-black text-indigo-700 mb-2 uppercase flex items-center tracking-tighter">
<span class="bg-indigo-700 text-white px-3 mr-3 border-4 border-gray-900 shadow-[4px_4px_0px_rgba(0,0,0,1)]">1</span>
{{ 'onboarding.form.section1.title'|trans }}
</h2>
<p class="text-indigo-600 mb-6">{{ 'onboarding.form.section1.description'|trans }}</p>
<p class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-8 border-b-4 border-indigo-100 pb-2">
// {{ 'onboarding.form.section1.description'|trans }}
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 form-input-group">
<!-- Nom -->
<div>
{{ form_row(form.name) }}
</div>
<!-- Prénom -->
<div>
{{ form_row(form.surname) }}
</div>
<!-- Email -->
<div class="md:col-span-1">
{{ form_row(form.email) }}
</div>
<!-- Date de naissance -->
<div class="md:col-span-1">
{{ form_row(form.birdth) }}
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="neubrutal-input">{{ form_row(form.name) }}</div>
<div class="neubrutal-input">{{ form_row(form.surname) }}</div>
<div class="neubrutal-input">{{ form_row(form.email) }}</div>
<div class="neubrutal-input">{{ form_row(form.birdth) }}</div>
</div>
</div>
<!-- Étape 2 Contenu -->
<div class="p-6 bg-yellow-50 rounded-xl border border-yellow-200 shadow-sm">
<h2 class="text-2xl font-bold text-yellow-700 mb-4 border-b border-yellow-300 pb-2 flex items-center">
<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.639a2 2 0 01-1.789-2.894l3.5-7z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 9H2.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 014.737 3h4.636a2 2 0 011.789 2.894l-3.5 7z"></path></svg>
{# SECTION 2 : PROFIL COSPLAY + CHECK DISPO #}
<div class="bg-white border-8 border-gray-900 p-8 shadow-[12px_12px_0px_#eab308]">
<h2 class="text-3xl font-black text-yellow-600 mb-2 uppercase flex items-center tracking-tighter">
<span class="bg-yellow-400 text-black px-3 mr-3 border-4 border-gray-900 shadow-[4px_4px_0px_rgba(0,0,0,1)]">2</span>
{{ 'onboarding.form.section2.title'|trans }}
</h2>
<p class="text-yellow-600 mb-6">{{ 'onboarding.form.section2.description'|trans }}</p>
<!-- Simulation du contenu du formulaire -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 form-input-group">
<div class="md:col-span-2">
<p class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-8 border-b-4 border-yellow-100 pb-2">
// {{ 'onboarding.form.section2.description'|trans }}
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="md:col-span-2 neubrutal-input relative">
{{ form_row(form.nameCosplayer) }}
{# Le feedback JS sera injecté ici #}
</div>
<div class="md:col-span-2">
{{ form_row(form.description)}}
</div>
{{ form_row(form.epage) }}
<div class="md:col-span-2 neubrutal-input">{{ form_row(form.description)}}</div>
<div class="md:col-span-2">{{ form_row(form.epage) }}</div>
</div>
</div>
<!-- Étape 3 Contenu -->
<div class="p-6 bg-green-50 rounded-xl border border-green-200 shadow-sm">
<h2 class="text-2xl font-bold text-green-700 mb-4 border-b border-green-300 pb-2 flex items-center">
<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.886l2.273 2.273.744-.744-2.273-2.273z"></path></svg>
{# SECTION 3 : RÉSEAUX SOCIAUX #}
<div class="bg-white border-8 border-gray-900 p-8 shadow-[12px_12px_0px_#22c55e]">
<h2 class="text-3xl font-black text-green-700 mb-2 uppercase flex items-center tracking-tighter">
<span class="bg-green-500 text-white px-3 mr-3 border-4 border-gray-900 shadow-[4px_4px_0px_rgba(0,0,0,1)]">3</span>
{{ 'onboarding.form.section3.title'|trans }}
</h2>
<p class="text-green-600 mb-6">{{ 'onboarding.form.section3.description'|trans }}</p>
<p class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-8 border-b-4 border-green-100 pb-2">
// {{ 'onboarding.form.section3.description'|trans }}
</p>
<div class="grid grid-cols-2 md:grid-cols-2 gap-6 form-input-group">
<div>
{{ form_row(form.linkFacebook) }}
</div>
<div>
{{ form_row(form.linkInstagram) }}
</div>
<div>
{{ form_row(form.linkTiktok) }}
</div>
<div>
{{ form_row(form.linkX) }}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="neubrutal-input">{{ form_row(form.linkFacebook) }}</div>
<div class="neubrutal-input">{{ form_row(form.linkInstagram) }}</div>
<div class="neubrutal-input">{{ form_row(form.linkTiktok) }}</div>
<div class="neubrutal-input">{{ form_row(form.linkX) }}</div>
</div>
</div>
<!-- Étape 4 Contenu -->
<div class="p-6 bg-blue-50 rounded-xl border border-blue-200 shadow-sm">
<h2 class="text-2xl font-bold text-blue-700 mb-4 border-b border-blue-200 pb-2 flex items-center">
<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.5 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.5-3-9s1.343-9 3-9m-9 9h.01"></path></svg>
{# SECTION 4 : LIEN PERSONNALISÉ #}
<div class="bg-white border-8 border-gray-900 p-8 shadow-[12px_12px_0px_#3b82f6]">
<h2 class="text-3xl font-black text-blue-700 mb-2 uppercase flex items-center tracking-tighter">
<span class="bg-blue-600 text-white px-3 mr-3 border-4 border-gray-900 shadow-[4px_4px_0px_rgba(0,0,0,1)]">4</span>
{{ 'onboarding.form.section4.title'|trans }}
</h2>
<div class="grid grid-cols-1 gap-6 form-input-group">
<!-- Checkbox for Custom Domain (form.useDomain) -->
<div class="mt-4 flex items-center">
<div class="mt-8 space-y-6">
<div class="p-4 bg-blue-50 border-4 border-blue-200 font-bold uppercase text-sm">
{{ form_row(form.useDomain) }}
</div>
<!-- Custom Domain Input (form.domain) - Hidden by default -->
<div id="custom-domain-group" class="hidden transition-all duration-300 pt-4 border-t border-blue-200 mt-4">
<div id="custom-domain-group" class="hidden transition-all duration-300 neubrutal-input p-6 bg-gray-900 text-white border-8 border-gray-900 shadow-[8px_8px_0px_#3b82f6]">
{{ form_row(form.domain) }}
</div>
</div>
</div>
</div>
<!-- Bouton unique de soumission -->
<div class="mt-8 flex justify-center">
<button class="px-8 py-3 text-lg font-semibold rounded-lg text-white bg-indigo-600 hover:bg-indigo-700 transition duration-150 shadow-lg shadow-indigo-200">
{# BOUTON SOUMISSION #}
<div class="mt-16 flex justify-center pb-20">
<button type="submit" class="group relative px-12 py-6 bg-indigo-600 text-white border-8 border-gray-900 text-3xl font-black uppercase tracking-widest shadow-[12px_12px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-2 hover:translate-y-2 transition-all active:scale-95">
{{ 'onboarding.form.submit_button'|trans }}
<i class="fas fa-rocket ml-4 group-hover:-translate-y-2 transition-transform"></i>
</button>
</div>
</div>
{{ form_end(form) }}
</div>
<style>
/* Design Neubrutaliste pour les inputs Symfony */
.neubrutal-input input,
.neubrutal-input textarea,
.neubrutal-input select {
width: 100%;
padding: 1rem;
border: 4px solid #111827 !important;
background: white;
font-weight: 900;
text-transform: uppercase;
box-shadow: 4px 4px 0px #111827;
transition: all 0.2s;
}
.neubrutal-input input:focus {
box-shadow: none;
transform: translate(2px, 2px);
outline: none;
border-color: #4f46e5 !important;
}
.neubrutal-input label {
display: block;
font-weight: 900;
text-transform: uppercase;
font-size: 0.75rem;
margin-bottom: 0.5rem;
letter-spacing: 0.1em;
}
/* Classes pour le Check Pseudo */
.check-success { border-color: #22c55e !important; box-shadow: 4px 4px 0px #15803d !important; }
.check-error { border-color: #ef4444 !important; box-shadow: 4px 4px 0px #b91c1c !important; }
</style>
<script>
document.addEventListener('turbo:load', () => {
// --- LOGIQUE DOMAINE PERSONNALISÉ ---
const useDomainCheckbox = document.getElementById('e_page_onboard_useDomain');
const customDomainGroup = document.getElementById('custom-domain-group');
const customDomainInput = document.getElementById('e_page_onboard_domain');
function toggleDomainFields() {
if (useDomainCheckbox.checked) {
// Show custom domain field
if (useDomainCheckbox && useDomainCheckbox.checked) {
customDomainGroup.classList.remove('hidden');
customDomainInput.setAttribute('required', 'required');
} else {
// Hide custom domain field
} else if (useDomainCheckbox) {
customDomainGroup.classList.add('hidden');
customDomainInput.removeAttribute('required');
customDomainInput.value = ''; // Clear custom domain value
customDomainInput.value = '';
}
}
// Initial state setup (in case of browser refresh retaining checkbox state)
if (useDomainCheckbox) {
toggleDomainFields();
// Event listener for the checkbox
useDomainCheckbox.addEventListener('change', toggleDomainFields);
}
// --- LOGIQUE CHECK PSEUDO (DEBOUNCED) ---
const pseudoInput = document.getElementById('e_page_onboard_nameCosplayer');
let timeout = null;
if (pseudoInput) {
// Création du message de feedback
const feedbackMsg = document.createElement('div');
feedbackMsg.className = 'text-[10px] font-black uppercase mt-2 tracking-widest hidden';
pseudoInput.parentNode.appendChild(feedbackMsg);
pseudoInput.addEventListener('keyup', () => {
const value = pseudoInput.value.trim();
if (value.length < 2) {
resetInputState(pseudoInput, feedbackMsg);
return;
}
clearTimeout(timeout);
timeout = setTimeout(() => {
fetch(`/pages/check?q=${encodeURIComponent(value)}`)
.then(response => response.json())
.then(data => {
if (data.available === true) {
pseudoInput.classList.remove('check-error');
pseudoInput.classList.add('check-success');
feedbackMsg.textContent = "DISPONIBLE // READY TO SYNC";
feedbackMsg.className = 'text-[10px] font-black uppercase mt-2 tracking-widest text-green-600 block';
} else {
pseudoInput.classList.remove('check-success');
pseudoInput.classList.add('check-error');
feedbackMsg.textContent = "NON DISPONIBLE // ALREADY TAKEN";
feedbackMsg.className = 'text-[10px] font-black uppercase mt-2 tracking-widest text-red-600 block';
}
})
.catch(() => console.error("API Check Error"));
}, 500); // 500ms debounce
});
}
function resetInputState(el, msg) {
el.classList.remove('check-success', 'check-error');
msg.classList.add('hidden');
}
});
</script>
{% endblock %}

View File

@@ -1,10 +1,13 @@
{% extends 'base.twig' %}
{# --- METADATA & SCHEMA --- #}
{% block title %}{{'page.presentation.title'|trans}}{% endblock %}
{% block meta_description %}{{'page.presentation.description'|trans}}{% endblock %}
{% block title %}{{ 'page.presentation.title'|trans }}{% endblock %}
{% block meta_description %}{{ 'page.presentation.description'|trans }}{% endblock %}
{% block canonical_url %}
<link rel="canonical" href="{{ url('app_pages') }}" />
{% endblock %}
{% block canonical_url %}<link rel="canonical" href="{{ url('app_pages') }}" />{% endblock %}
{% block breadcrumb_schema %}
<script type="application/ld+json">
{
@@ -15,396 +18,301 @@
"@type": "ListItem",
"position": 1,
"name": "{{ 'breadcrumb.home'|trans }}",
"item": "{{ app.request.schemeAndHttpHost }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "{{ 'page_presentation.breadcrumb'|trans }}",
"item": "{{ app.request.schemeAndHttpHost }}{{ app.request.pathInfo }}"
}
]
}
"item": "{{ app.request.schemeAndHttpHost }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "{{ 'page_presentation.breadcrumb'|trans }}",
"item": "{{ app.request.schemeAndHttpHost }}{{ app.request.pathInfo }}"
}
]
}
</script>
{% endblock %}
{# --- BODY --- #}
{% block body %}
<main class="py-16 bg-gray-100/50 min-h-screen">
{# Wrapper global pour centrer le contenu et gérer le padding mobile #}
<div class="py-16 bg-[#f0f0f0] min-h-screen italic font-bold">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{# Titre Style Moderne/Anime Épuré #}
<h1 class="text-5xl md:text-6xl font-extrabold text-center mb-12 relative
tracking-tight pb-2 text-gray-900">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span> - {{ 'page.presentation.header'|trans }}
<span class="block w-24 h-1 mx-auto mt-2 bg-gradient-to-r from-red-500 to-yellow-500 rounded-full"></span>
{# Titre Neubrutaliste #}
<div class="text-center mb-20">
<h1 class="inline-block text-5xl md:text-7xl font-black bg-white border-8 border-gray-900 p-6 shadow-[12px_12px_0px_rgba(0,0,0,1)] uppercase tracking-tighter">
<span class="text-transparent bg-clip-text bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>
<span class="text-gray-900">// {{ 'page.presentation.header'|trans }}</span>
</h1>
</div>
{# Conteneur des Fonctionnalités #}
<div class="bg-white p-6 sm:p-8 md:p-14 rounded-3xl shadow-[0_20px_40px_-10px_rgba(0,0,0,0.2)] border-t-8 border-yellow-500/80">
<h2 class="text-3xl font-bold text-gray-800 mb-8 text-center">
<div class="bg-white border-8 border-gray-900 p-8 md:p-14 shadow-[16px_16px_0px_rgba(0,0,0,1)] mb-16">
<h2 class="text-4xl font-black text-gray-900 mb-10 uppercase underline decoration-yellow-500 decoration-8 underline-offset-8">
{{ 'page.presentation.subtitle'|trans }}
</h2>
<p class="text-xl text-gray-600 mb-6 leading-relaxed text-center font-medium">
{{ 'page.presentation.intro_paragraph_1'|trans }}<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span> {{ 'page.presentation.intro_paragraph_1_2'|trans }}
<p class="text-2xl text-gray-800 mb-8 leading-tight font-black uppercase tracking-tight">
{{ 'page.presentation.intro_paragraph_1'|trans }}
<span class="bg-yellow-400 px-2 text-black">EPage</span>
{{ 'page.presentation.intro_paragraph_1_2'|trans }}
</p>
{# Clarification du Rôle de l'EPage #}
<p class="text-lg sm:text-xl text-gray-700 mb-10 leading-relaxed text-center font-medium border-l-4 border-yellow-500 pl-4 sm:pl-6 py-3 bg-yellow-50 rounded-lg italic">
{# Alerte Rôle EPage #}
<div class="bg-gray-900 text-white p-6 border-l-[16px] border-red-500 mb-12 shadow-[8px_8px_0px_rgba(239,68,68,0.3)]">
<p class="text-lg md:text-xl font-bold leading-relaxed">
{{ 'page.presentation.intro_paragraph_2'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>',
'strong_tag': '<strong>',
'strong_end_tag': '</strong>',
'warning_tag': '<strong class="text-red-600/90 block mt-2">'
})|raw }}
</p>
{# GRILLE DES FONCTIONNALITÉS (devient 1 colonne sur mobile, 2 sur md, 3 sur lg) #}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-10 text-gray-700">
{# Bloc 1: Visibilité Maximale (Loupe) #}
<div class="p-6 border border-gray-200 rounded-xl hover:shadow-xl transition duration-500 transform hover:-translate-y-1 bg-white">
<div class="flex items-center space-x-4 mb-3">
<span class="text-red-500 text-3xl flex-shrink-0 p-2 bg-red-100 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
</span>
<h3 class="font-extrabold text-xl text-gray-900">{{ 'page.presentation.feature.seo.title'|trans }}</h3>
</div>
<p class="text-lg">{{ 'page.presentation.feature.seo.description'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>'
})|raw }}</p>
</div>
{# Bloc 2: Centralisation de Contenu (Chaîne) #}
<div class="p-6 border border-gray-200 rounded-xl hover:shadow-xl transition duration-500 transform hover:-translate-y-1 bg-white">
<div class="flex items-center space-x-4 mb-3">
<span class="text-yellow-500 text-3xl flex-shrink-0 p-2 bg-yellow-100 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link-2"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3"/><path d="M9 17H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"/><line x1="8" x2="16" y1="12" y2="12"/></svg>
</span>
<h3 class="font-extrabold text-xl text-gray-900">{{ 'page.presentation.feature.nexus.title'|trans }}</h3>
</div>
<p class="text-lg">{{ 'page.presentation.feature.nexus.description'|trans({
'strong_tag': '<strong>',
'strong_end_tag': '</strong>'
})|raw }}</p>
</div>
{# Bloc 3: Annonces & Événements (Calendrier) #}
<div class="p-6 border border-gray-200 rounded-xl hover:shadow-xl transition duration-500 transform hover:-translate-y-1 bg-white">
<div class="flex items-center space-x-4 mb-3">
<span class="text-red-500 text-3xl flex-shrink-0 p-2 bg-red-100 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days"><rect width="18" height="18" x="3" y="4" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg>
</span>
<h3 class="font-extrabold text-xl text-gray-900">{{ 'page.presentation.feature.convention.title'|trans }}</h3>
</div>
<p class="text-lg">{{ 'page.presentation.feature.convention.description'|trans }}</p>
</div>
{# Bloc 4: Intégration E-Cosplay (Cœur) #}
<div class="p-6 border border-gray-200 rounded-xl hover:shadow-xl transition duration-500 transform hover:-translate-y-1 bg-white">
<div class="flex items-center space-x-4 mb-3">
<span class="text-yellow-500 text-3xl flex-shrink-0 p-2 bg-yellow-100 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hand-heart"><path d="M11 14h2a2 2 0 0 0 2-2V7.5l-3.24-3.24a1 1 0 0 0-1.42 0L8 7.5V12a2 2 0 0 0 2 2h2"/><path d="M10 10l.02.02"/><path d="M14 10l.02.02"/><path d="M20 16c0 4.42-3.58 8-8 8s-8-3.58-8-8 8-16 8-16 8 11.58 8 16z"/></svg>
</span>
<h3 class="font-extrabold text-xl text-gray-900">{{ 'page.presentation.feature.approval.title'|trans }}</h3>
</div>
<p class="text-lg">{{ 'page.presentation.feature.approval.description'|trans({
'strong_tag': '<strong>',
'strong_end_tag': '</strong>'
})|raw }}</p>
</div>
{# Bloc 5: Modération et Sécurité (Bouclier) #}
<div class="p-6 border border-gray-200 rounded-xl hover:shadow-xl transition duration-500 transform hover:-translate-y-1 bg-white">
<div class="flex items-center space-x-4 mb-3">
<span class="text-red-500 text-3xl flex-shrink-0 p-2 bg-red-100 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-check"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10"/><path d="m9 12 2 2 4-4"/></svg>
</span>
<h3 class="font-extrabold text-xl text-gray-900">{{ 'page.presentation.feature.security.title'|trans }}</h3>
</div>
<p class="text-lg">
{{ 'page.presentation.feature.security.description'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>',
'strong_tag': '<strong>',
'strong_end_tag': '</strong>'
})|raw }}
</p>
<p class="text-lg mt-2">
{{ 'page.presentation.feature.security.encrypted_data'|trans({
'strong_tag': '<strong>',
'strong_end_tag': '</strong>'
})|raw }}
</p>
<p class="text-lg mt-2">
{{ 'page.presentation.feature.security.data_minimum'|trans }}
</p>
<p class="text-lg mt-2">
{{ 'page.presentation.feature.security.no_fly_inscription'|trans({
'strong_tag': '<strong>',
'strong_end_tag': '</strong>'
'page_span': '<span class="text-yellow-400 uppercase font-black">EPage</span>',
'strong_tag': '<span class="text-white">',
'strong_end_tag': '</span>',
'warning_tag': '<span class="block mt-4 text-red-400 uppercase tracking-widest text-sm underline font-black">'
})|raw }}
</p>
</div>
{# Bloc 6: Formulaire de Contact Sécurisé (Mail Check) #}
<div class="p-6 border border-gray-200 rounded-xl hover:shadow-xl transition duration-500 transform hover:-translate-y-1 bg-white">
<div class="flex items-center space-x-4 mb-3">
<span class="text-yellow-500 text-3xl flex-shrink-0 p-2 bg-yellow-100 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mail-check"><path d="M22 10V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h10"/><path d="m22 10-7.23 6.11a3 3 0 0 1-3.48 0L2 10"/><path d="m16 22 2 2 4-4"/></svg>
</span>
<h3 class="font-extrabold text-xl text-gray-900">{{ 'page.presentation.feature.contact.title'|trans }}</h3>
</div>
<p class="text-lg">
{{ 'page.presentation.feature.contact.description'|trans }}
<br><span class="text-sm mt-2 block text-gray-500 font-medium">{{ 'page.presentation.feature.contact.disclaimer'|trans}}<a target="_blank" class='text-red-500 hover:text-red-600 underline font-bold transition duration-300' href='{{ path('app_rgpd') }}'>{{ 'page.presentation.feature.contact.policy_text'|trans }}</a></span>
</p>
</div>
{# GRILLE DES FONCTIONNALITÉS #}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 text-gray-900">
{# Bloc 7: Personalisation (Palette) #}
<div class="p-6 border border-gray-200 rounded-xl hover:shadow-xl transition duration-500 transform hover:-translate-y-1 bg-white">
<div class="flex items-center space-x-4 mb-3">
<span class="text-red-500 text-3xl flex-shrink-0 p-2 bg-red-100 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-palette"><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="10.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.92 0 1.63-.67 1.95-1.57.17-.46.33-.87.49-1.29.35-.91.78-1.78 1.3-2.58.6-1.14 1.34-2.13 2.22-2.91.88-.78 1.94-1.28 3.12-1.28C21.5 10.5 22 6.5 22 12c0-5.5-4.5-10-10-10z"/></svg>
</span>
<h3 class="font-extrabold text-xl text-gray-900">{{ 'page.presentation.feature.customization.title'|trans }}</h3>
{# Bloc 1: SEO #}
<div class="group p-8 border-4 border-gray-900 bg-white shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-x-1 hover:translate-y-1 hover:shadow-none transition-all duration-200">
<div class="flex items-center mb-4">
<div class="p-3 bg-red-500 border-4 border-gray-900 text-white mr-4 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
</div>
<p class="text-lg">{{ 'page.presentation.feature.customization.description'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>'
<h3 class="font-black text-xl uppercase leading-none">{{ 'page.presentation.feature.seo.title'|trans }}</h3>
</div>
<p class="font-bold text-gray-700 leading-snug">{{ 'page.presentation.feature.seo.description'|trans({
'page_span': '<span class="text-red-600 font-black">EPage</span>'
})|raw }}</p>
</div>
{# Bloc 8: Suivi / Analytics (Activity) #}
<div class="p-6 border border-gray-200 rounded-xl hover:shadow-xl transition duration-500 transform hover:-translate-y-1 bg-white">
<div class="flex items-center space-x-4 mb-3">
<span class="text-yellow-500 text-3xl flex-shrink-0 p-2 bg-yellow-100 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-activity"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
</span>
<h3 class="font-extrabold text-xl text-gray-900">{{ 'page.presentation.feature.analytics.title'|trans }}</h3>
{# Bloc 2: Centralisation #}
<div class="group p-8 border-4 border-gray-900 bg-white shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-x-1 hover:translate-y-1 hover:shadow-none transition-all duration-200">
<div class="flex items-center mb-4">
<div class="p-3 bg-yellow-400 border-4 border-gray-900 text-black mr-4 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3"/><path d="M9 17H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"/><line x1="8" x2="16" y1="12" y2="12"/></svg>
</div>
<p class="text-lg">{{ 'page.presentation.feature.analytics.description'|trans }}</p>
<h3 class="font-black text-xl uppercase leading-none">{{ 'page.presentation.feature.nexus.title'|trans }}</h3>
</div>
<p class="font-bold text-gray-700 leading-snug">{{ 'page.presentation.feature.nexus.description'|trans({
'strong_tag': '<span class="text-black underline decoration-yellow-400 decoration-4">',
'strong_end_tag': '</span>'
})|raw }}</p>
</div>
{# Bloc 3: Événements #}
<div class="group p-8 border-4 border-gray-900 bg-white shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-x-1 hover:translate-y-1 hover:shadow-none transition-all duration-200">
<div class="flex items-center mb-4">
<div class="p-3 bg-red-500 border-4 border-gray-900 text-white mr-4 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="4" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg>
</div>
<h3 class="font-black text-xl uppercase leading-none">{{ 'page.presentation.feature.convention.title'|trans }}</h3>
</div>
<p class="font-bold text-gray-700 leading-snug">{{ 'page.presentation.feature.convention.description'|trans }}</p>
</div>
{# Bloc 4: Intégration #}
<div class="group p-8 border-4 border-gray-900 bg-white shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-x-1 hover:translate-y-1 hover:shadow-none transition-all duration-200">
<div class="flex items-center mb-4">
<div class="p-3 bg-yellow-400 border-4 border-gray-900 text-black mr-4 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M11 14h2a2 2 0 0 0 2-2V7.5l-3.24-3.24a1 1 0 0 0-1.42 0L8 7.5V12a2 2 0 0 0 2 2h2"/><path d="M10 10l.02.02"/><path d="M14 10l.02.02"/><path d="M20 16c0 4.42-3.58 8-8 8s-8-3.58-8-8 8-16 8-16 8 11.58 8 16z"/></svg>
</div>
<h3 class="font-black text-xl uppercase leading-none">{{ 'page.presentation.feature.approval.title'|trans }}</h3>
</div>
<p class="font-bold text-gray-700 leading-snug">{{ 'page.presentation.feature.approval.description'|trans({
'strong_tag': '<span class="text-black underline decoration-red-500 decoration-4">',
'strong_end_tag': '</span>'
})|raw }}</p>
</div>
{# Bloc 5: Sécurité #}
<div class="group p-8 border-4 border-gray-900 bg-white shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-x-1 hover:translate-y-1 hover:shadow-none transition-all duration-200">
<div class="flex items-center mb-4">
<div class="p-3 bg-red-500 border-4 border-gray-900 text-white mr-4 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10"/><path d="m9 12 2 2 4-4"/></svg>
</div>
<h3 class="font-black text-xl uppercase leading-none">{{ 'page.presentation.feature.security.title'|trans }}</h3>
</div>
<div class="font-bold text-gray-700 leading-tight space-y-2">
<p>{{ 'page.presentation.feature.security.description'|trans({'page_span': 'EPage', 'strong_tag': '<b>', 'strong_end_tag': '</b>'})|raw }}</p>
<p class="text-xs uppercase bg-gray-100 p-1">{{ 'page.presentation.feature.security.encrypted_data'|trans({'strong_tag': '', 'strong_end_tag': ''}) }}</p>
</div>
</div>
{# Bloc 6: Contact #}
<div class="group p-8 border-4 border-gray-900 bg-white shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-x-1 hover:translate-y-1 hover:shadow-none transition-all duration-200">
<div class="flex items-center mb-4">
<div class="p-3 bg-yellow-400 border-4 border-gray-900 text-black mr-4 shadow-[4px_4px_0px_rgba(0,0,0,1)]">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M22 10V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h10"/><path d="m22 10-7.23 6.11a3 3 0 0 1-3.48 0L2 10"/><path d="m16 22 2 2 4-4"/></svg>
</div>
<h3 class="font-black text-xl uppercase leading-none">{{ 'page.presentation.feature.contact.title'|trans }}</h3>
</div>
<p class="font-bold text-gray-700 leading-snug">{{ 'page.presentation.feature.contact.description'|trans }}</p>
<a href="{{ path('app_rgpd') }}" class="mt-4 block text-[10px] uppercase font-black text-red-600 hover:underline italic">{{ 'page.presentation.feature.contact.policy_text'|trans }}</a>
</div>
</div>
</div>
{# BLOC : OFFRE SANS ENGAGEMENT #}
<div class="mt-12 p-6 sm:p-8 bg-white border-2 border-red-500 rounded-2xl shadow-xl text-center">
<h4 class="text-xl sm:text-3xl font-extrabold text-gray-900 mb-3 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rocket-launch mr-3 text-red-500"><path d="M10 21h4c1.1 0 2-.9 2-2v-7a2 2 0 0 0-2-2h-4c-1.1 0-2 .9-2 2v7c0 1.1.9 2 2 2z"/><path d="M12 10a2 2 0 0 0 2-2V4a2 2 0 1 0-4 0v4a2 2 0 0 0 2 2z"/><path d="M7 16l-3 3"/><path d="M17 16l3 3"/><path d="M12 21v-2"/></svg>
<div class="mt-12 p-10 bg-white border-8 border-red-500 shadow-[12px_12px_0px_rgba(239,68,68,1)] text-center relative overflow-hidden">
<div class="absolute top-0 left-0 bg-red-500 text-white px-6 py-2 uppercase font-black tracking-widest text-xs rotate-[-2deg] -translate-x-2 translate-y-2 border-4 border-gray-900">
PRO MISSION
</div>
<h4 class="text-4xl font-black text-gray-900 mb-6 uppercase tracking-tighter italic">
{{ 'page.presentation.flexibility.title'|trans }}
</h4>
<p class="text-lg sm:text-xl text-gray-700 leading-relaxed">
<div class="text-2xl font-bold text-gray-800 leading-tight">
{{ 'page.presentation.flexibility.no_commitment'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>',
'strong_tag': '<strong>',
'strong_end_tag': '</strong>'
'page_span': '<span class="text-red-600 uppercase font-black">EPage</span>',
'strong_tag': '<span class="underline decoration-yellow-400 decoration-4 px-1">',
'strong_end_tag': '</span>'
})|raw }}
</p>
<p class="text-base sm:text-lg text-gray-600 mt-2">
{{ 'page.presentation.flexibility.renewal_auto'|trans|raw }}
<strong>{{ 'page.presentation.flexibility.periods_cancel'|trans }}</strong>
</p>
</div>
</div>
{# Appel à l'action pour les étapes #}
<div class="text-center mt-16">
<h3 class="text-2xl sm:text-3xl font-extrabold text-gray-800 mb-5">{{ 'page.presentation.call_to_action'|trans }}</h3>
{# Appel à l'action étapes #}
<div class="text-center mt-24">
<h3 class="text-5xl font-black text-gray-900 uppercase italic tracking-tighter bg-yellow-400 inline-block px-4 border-4 border-gray-900 shadow-[6px_6px_0px_rgba(0,0,0,1)]">
{{ 'page.presentation.call_to_action'|trans }}
</h3>
</div>
{# CONTENEUR DE GRILLE POUR LES 3 BLOCS D'INFORMATION #}
<div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-6 sm:gap-8">
{# BLOC 1: INFO TECHNIQUE NOM DE DOMAINE #}
<div class="p-6 sm:p-8 bg-yellow-50 border border-yellow-300 rounded-xl shadow-md text-gray-700">
<h4 class="text-xl sm:text-2xl font-extrabold text-yellow-700 mb-3 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-code-square mr-2"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="m10 13-2-2 2-2"/><path d="m14 11 2 2-2 2"/></svg>
{# GRILLE INFOS TECHNIQUES #}
<div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-8">
{# Bloc 1: Domaine #}
<div class="p-8 bg-white border-4 border-gray-900 shadow-[8px_8px_0px_rgba(234,179,8,1)]">
<h4 class="text-2xl font-black text-yellow-600 mb-4 uppercase tracking-tighter italic border-b-4 border-gray-100 pb-2">
{{ 'page.presentation.domain_info.title'|trans }}
</h4>
<p class="text-base sm:text-lg leading-relaxed">
<strong>{{ 'page.presentation.domain_info.no_domain_question'|trans }}</strong>
<br>{{ 'page.presentation.domain_info.default_url'|trans({
'url_part': '<code class="bg-gray-200 text-yellow-800 p-1 rounded font-mono text-sm">votre_pseudo.<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">e-cosplay</span>.com</code>'
})|raw }}
</p>
<p class="text-base sm:text-lg leading-relaxed mt-3">
<strong>{{ 'page.presentation.domain_info.has_domain_question'|trans }}</strong>
<br>{{ 'page.presentation.domain_info.cname_guide'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>'
})|raw }}
</p>
<p class="text-base sm:text-lg leading-relaxed mt-3 border-t pt-3 border-yellow-300">
<strong class="text-yellow-700">{{ 'page.presentation.domain_info.purchase_title'|trans }}</strong>
<br>{{ 'page.presentation.domain_info.partner_purchase'|trans({
'partner_strong': '<strong>SARL SITECONSEIL</strong>'
})|raw }}
</p>
<div class="space-y-4 font-bold text-gray-800 text-sm md:text-base uppercase tracking-tight">
<p>{{ 'page.presentation.domain_info.no_domain_question'|trans }}</p>
<code class="block bg-gray-900 text-yellow-400 p-3 border-2 border-gray-900 font-mono text-xs break-all">
votre_pseudo.e-cosplay.com
</code>
<p>{{ 'page.presentation.domain_info.has_domain_question'|trans }}</p>
<p class="text-xs text-gray-500">{{ 'page.presentation.domain_info.cname_guide'|trans({'page_span': 'EPage'})|raw }}</p>
</div>
</div>
{# BLOC 2: INFO LÉGALE COPYRIGHT #}
<div class="p-6 sm:p-8 bg-blue-50 border border-blue-300 rounded-xl shadow-md text-gray-700">
<h4 class="text-xl sm:text-2xl font-extrabold text-blue-700 mb-3 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-gavel mr-2"><path d="m15 11-8 8"/><path d="m14 4 6 6"/><path d="m4 4 6 6"/><path d="M17 17l5 5"/><path d="m15 11-1 1-2 2-2 2"/></svg>
{# Bloc 2: Copyright #}
<div class="p-8 bg-white border-4 border-gray-900 shadow-[8px_8px_0px_rgba(37,99,235,1)]">
<h4 class="text-2xl font-black text-blue-600 mb-4 uppercase tracking-tighter italic border-b-4 border-gray-100 pb-2">
{{ 'page.presentation.copyright_info.title'|trans }}
</h4>
<p class="text-base sm:text-lg leading-relaxed">
<p class="font-bold text-gray-800 leading-snug uppercase text-sm">
{{ 'page.presentation.copyright_info.responsibility'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>',
'strong_tag': '<strong>',
'strong_end_tag': '</strong>',
'red_strong': '<strong class="text-red-600">'
'page_span': 'EPage',
'strong_tag': '<span class="text-blue-600">',
'strong_end_tag': '</span>',
'red_strong': '<span class="text-red-600">'
})|raw }}
</p>
<p class="text-base sm:text-lg leading-relaxed mt-3">
<strong class="text-blue-700">{{ 'page.presentation.copyright_info.help_team'|trans }}</strong>
<br>{{ 'page.presentation.copyright_info.contact_us'|trans }}
</p>
</div>
{# BLOC 3: INFO FONCTIONNALITÉS SUPPLÉMENTAIRES #}
<div class="p-6 sm:p-8 bg-red-50 border border-red-300 rounded-xl shadow-md text-gray-700">
<h4 class="text-xl sm:text-2xl font-extrabold text-red-700 mb-3 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-zap mr-2"><path d="M10 17l6-6-6-6v3H4v6h6z"/></svg>
{# Bloc 3: Add-ons #}
<div class="p-8 bg-white border-4 border-gray-900 shadow-[8px_8px_0px_rgba(220,38,38,1)]">
<h4 class="text-2xl font-black text-red-600 mb-4 uppercase tracking-tighter italic border-b-4 border-gray-100 pb-2">
{{ 'page.presentation.additional_features.title'|trans }}
</h4>
<p class="text-base sm:text-lg leading-relaxed">
<p class="font-bold text-gray-800 leading-snug uppercase text-sm mb-4">
{{ 'page.presentation.additional_features.request_support'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>',
'strong_tag': '<strong>',
'strong_end_tag': '</strong>'
'page_span': 'EPage',
'strong_tag': '<span class="text-red-600">',
'strong_end_tag': '</span>'
})|raw }}
</p>
<p class="text-base sm:text-lg leading-relaxed mt-3">
{{ 'page.presentation.additional_features.siteconseil_referral'|trans({
'partner_strong_cyan': '<strong class="text-cyan-700">SARL SITECONSEIL</strong>'
})|raw }}
<p class="text-[10px] text-gray-400 uppercase font-black tracking-widest border-t-2 border-gray-100 pt-4">
PARTNER_REFERRAL: SARL SITECONSEIL
</p>
</div>
</div>
{# BLOC DE TARIFICATION EPage (CARTE UNIQUE AVEC SÉLECTEUR) - CENTRÉ ET RESPONSIVE #}
<div class="mt-16 text-center" style="display: none">
<h3 class="text-4xl font-extrabold text-gray-800 mb-8">{{ 'page.presentation.pricing.choose_period'|trans }}</h3>
{# BLOC TARIFICATION #}
<div id="pricing-section" class="mt-24 text-center" style="display: none">
<h3 class="text-5xl font-black text-gray-900 mb-10 uppercase tracking-tighter">{{ 'page.presentation.pricing.choose_period'|trans }}</h3>
<div class="max-w-xl mx-auto p-6 sm:p-8 bg-white border-4 border-red-500 rounded-3xl shadow-2xl">
<div class="max-w-2xl mx-auto bg-white border-8 border-gray-900 p-8 md:p-12 shadow-[20px_20px_0px_rgba(0,0,0,1)] relative">
<h4 class="text-3xl font-bold text-gray-900 mb-6">
<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>
<div class="absolute -top-6 left-1/2 -translate-x-1/2 bg-gray-900 text-white px-10 py-2 uppercase font-black text-xl italic skew-x-[-10deg]">
Access Terminal
</div>
<h4 class="text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-yellow-500 to-red-600 mb-10 uppercase italic">
EPage Connect
</h4>
{# SÉLECTEUR DE DURÉE (Utilise flex-wrap pour s'adapter sur mobile) #}
<div class="flex flex-wrap justify-center gap-2 sm:gap-4 mb-8 bg-gray-100 p-2 rounded-xl">
<label class="relative flex-1 cursor-pointer min-w-[70px]">
<input type="radio" name="duration" value="1M" class="peer hidden" checked onchange="updatePrice(this.value)">
<span class="block px-2 sm:px-4 py-2 text-sm sm:text-lg font-semibold text-gray-700 bg-white rounded-lg transition duration-200
peer-checked:bg-red-500 peer-checked:text-white peer-checked:shadow-lg hover:bg-red-100">
{{ 'page.presentation.pricing.period_1m'|trans }}
</span>
</label>
<label class="relative flex-1 cursor-pointer group min-w-[70px]">
<input type="radio" name="duration" value="2M" class="peer hidden" onchange="updatePrice(this.value)">
<span class="block px-2 sm:px-4 py-2 text-sm sm:text-lg font-semibold text-gray-700 bg-white rounded-lg transition duration-200
peer-checked:bg-red-500 peer-checked:text-white peer-checked:shadow-lg hover:bg-red-100">
{{ 'page.presentation.pricing.period_2m'|trans }}
<span class="absolute top-[-10px] right-[-5px] text-[10px] sm:text-xs font-bold text-white bg-yellow-500 px-1.5 py-0.5 rounded-full shadow-md transform -rotate-3 group-hover:rotate-0 transition">{{ 'page.presentation.pricing.save_2e'|trans }}</span>
</span>
</label>
<label class="relative flex-1 cursor-pointer group min-w-[70px]">
<input type="radio" name="duration" value="3M" class="peer hidden" onchange="updatePrice(this.value)">
<span class="block px-2 sm:px-4 py-2 text-sm sm:text-lg font-semibold text-gray-700 bg-white rounded-lg transition duration-200
peer-checked:bg-red-500 peer-checked:text-white peer-checked:shadow-lg hover:bg-red-100">
{{ 'page.presentation.pricing.period_3m'|trans }}
<span class="absolute top-[-10px] right-[-5px] text-[10px] sm:text-xs font-bold text-white bg-yellow-500 px-1.5 py-0.5 rounded-full shadow-md transform -rotate-3 group-hover:rotate-0 transition">{{ 'page.presentation.pricing.save_3e'|trans }}</span>
</span>
</label>
<label class="relative flex-1 cursor-pointer group min-w-[70px]">
<input type="radio" name="duration" value="6M" class="peer hidden" onchange="updatePrice(this.value)">
<span class="block px-2 sm:px-4 py-2 text-sm sm:text-lg font-semibold text-gray-700 bg-white rounded-lg transition duration-200
peer-checked:bg-red-500 peer-checked:text-white peer-checked:shadow-lg hover:bg-red-100">
{{ 'page.presentation.pricing.period_6m'|trans }}
<span class="absolute top-[-10px] right-[-5px] text-[10px] sm:text-xs font-bold text-white bg-yellow-500 px-1.5 py-0.5 rounded-full shadow-md transform -rotate-3 group-hover:rotate-0 transition">{{ 'page.presentation.pricing.save_0e'|trans }}</span>
</span>
</label>
<label class="relative flex-1 cursor-pointer group min-w-[70px]">
<input type="radio" name="duration" value="12M" class="peer hidden" onchange="updatePrice(this.value)">
<span class="block px-2 sm:px-4 py-2 text-sm sm:text-lg font-semibold text-gray-700 bg-white rounded-lg transition duration-200
peer-checked:bg-red-500 peer-checked:text-white peer-checked:shadow-lg hover:bg-red-100">
{{ 'page.presentation.pricing.period_12m'|trans }}
<span class="absolute top-[-10px] right-[-5px] text-[10px] sm:text-xs font-bold text-white bg-red-700 px-1.5 py-0.5 rounded-full shadow-md transform -rotate-3 group-hover:rotate-0 transition">{{ 'page.presentation.pricing.best_deal'|trans }}</span>
{# SÉLECTEUR DE DURÉE #}
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3 mb-10">
{% for period in ['1M', '2M', '3M', '6M', '12M'] %}
<label class="relative cursor-pointer group">
<input type="radio" name="duration" value="{{ period }}" class="peer hidden" {% if period == '1M' %}checked{% endif %} onchange="updatePrice(this.value)">
<span class="flex items-center justify-center py-3 border-4 border-gray-900 font-black uppercase text-xs transition-all peer-checked:bg-red-500 peer-checked:text-white peer-checked:translate-x-1 peer-checked:translate-y-1 peer-checked:shadow-none shadow-[4px_4px_0px_rgba(0,0,0,1)] group-hover:bg-gray-100">
{{ ('page.presentation.pricing.period_' ~ period|lower)|trans }}
</span>
</label>
{% endfor %}
</div>
{# AFFICHAGE DU PRIX DYNAMIQUE #}
<div class="text-5xl sm:text-6xl font-extrabold text-gray-900 my-6">
<span id="current-price">5€</span><span class="text-xl sm:text-2xl text-red-600"> {{ 'page.presentation.pricing.tax_info'|trans }}</span>
{# PRIX DYNAMIQUE #}
<div class="bg-gray-900 text-white p-8 border-4 border-gray-900 shadow-[8px_8px_0px_rgba(234,179,8,1)] mb-8">
<div class="text-7xl font-black tracking-tighter mb-2 italic">
<span id="current-price">5€</span>
</div>
<p class="text-yellow-400 font-black uppercase tracking-widest text-xs">
{{ 'page.presentation.pricing.tax_info'|trans }}
</p>
</div>
<p id="price-per-month" class="text-lg sm:text-xl text-gray-500 mb-8"></p>
<p id="price-per-month" class="text-sm font-black text-gray-500 uppercase tracking-widest mb-10 h-10"></p>
{# BOUTON D'ACTION #}
<button onclick="redirectToCheckout()" class="w-full px-8 py-4 bg-gradient-to-r from-yellow-500 to-red-600 text-white font-extrabold text-xl rounded-xl transition duration-300 transform hover:scale-[1.03] shadow-lg shadow-red-300/50">
<button onclick="redirectToCheckout()" class="w-full py-6 bg-red-600 text-white border-4 border-gray-900 font-black text-2xl uppercase tracking-widest shadow-[10px_10px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-2 hover:translate-y-2 transition-all active:scale-95">
{{ 'page.presentation.pricing.cta_button'|trans }}
</button>
<p class="text-xs sm:text-sm text-gray-500 mt-4 italic">
<p class="text-[9px] text-gray-400 uppercase font-bold mt-6 tracking-widest">
{{ 'page.presentation.pricing.disclaimer'|trans }}
</p>
</div>
</div>
{# BLOC PARTENARIAT SITECONSEIL #}
<div class="mt-20 pt-10 pb-10 bg-white text-gray-900 rounded-3xl shadow-2xl border border-gray-200">
<div class="text-center px-4 sm:px-6">
<h3 class="text-3xl sm:text-4xl font-extrabold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-cyan-600">
{# BLOC PARTENARIAT #}
<div class="mt-32 p-10 bg-white border-8 border-cyan-500 shadow-[12px_12px_0px_rgba(6,182,212,1)] flex flex-col md:flex-row items-center gap-10">
<div class="flex-1 text-left">
<h3 class="text-3xl font-black text-cyan-600 uppercase italic tracking-tighter mb-4">
{{ 'page.presentation.siteconseil_partner.title'|trans }}
</h3>
<p class="text-lg sm:text-xl text-gray-600 mb-6">
<p class="text-gray-800 font-bold uppercase text-sm leading-tight">
{{ 'page.presentation.siteconseil_partner.intro'|trans({
'page_span': '<span class="bg-clip-text text-transparent bg-gradient-to-r from-yellow-500 to-red-600">EPage</span>',
'strong_tag': '<strong>',
'strong_end_tag': '</strong>'
'page_span': '<span class="text-cyan-600 font-black underline">EPage</span>',
'strong_tag': '<b>',
'strong_end_tag': '</b>'
})|raw }}
</p>
<div class="bg-gray-100 p-6 rounded-xl border-l-8 border-cyan-500 inline-block max-w-xl text-gray-900">
<p class="text-xl sm:text-2xl font-bold mb-2">{{ 'page.presentation.siteconseil_partner.recommended'|trans }}</p>
<p class="text-base sm:text-lg text-gray-700">
</div>
<div class="bg-gray-900 text-white p-6 border-4 border-gray-900 shadow-[8px_8px_0px_rgba(6,182,212,0.5)] max-w-sm">
<p class="text-xs font-black text-cyan-400 uppercase mb-4 tracking-widest">{{ 'page.presentation.siteconseil_partner.recommended'|trans }}</p>
<p class="text-sm font-bold mb-6">
{{ 'page.presentation.siteconseil_partner.cms_info'|trans({
'partner_strong': '<strong>SARL SITECONSEIL</strong>',
'cms_strong': '<strong class="text-yellow-600">Esy-Flex</strong>'
'partner_strong': '<span class="text-cyan-400">SARL SITECONSEIL</span>',
'cms_strong': '<span class="text-yellow-400 font-black">Esy-Flex</span>'
})|raw }}
</p>
<p class="text-base sm:text-lg text-gray-700 mt-2">
{{ 'page.presentation.siteconseil_partner.offer_info'|trans }}
</p>
<a href="https://www.siteconseil.fr" target="_blank" class="mt-4 inline-block px-6 sm:px-8 py-3 bg-cyan-500 hover:bg-cyan-600 text-gray-900 font-extrabold text-base sm:text-lg rounded-full transition duration-300 transform hover:scale-105 shadow-lg">
{{ 'page.presentation.siteconseil_partner.discover_button'|trans }}
<!-- Icone de Lien Externe -->
<svg xmlns="http://www.w3.org/2000/svg" class="inline-block ml-2 h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 7h10v10"/><path d="M10 14L16.6 7.4"/></svg>
<a href="https://www.siteconseil.fr" target="_blank" class="block text-center py-3 bg-cyan-500 text-gray-900 font-black uppercase text-xs border-2 border-white hover:bg-white transition-colors">
{{ 'page.presentation.siteconseil_partner.discover_button'|trans }} _
</a>
</div>
</div>
</div>
</div>
{# --- JAVASCRIPT --- #}
<script>
// Données de prix (en Euros HT)
const pricing = {
'1M': { price: 5, label: '5€ / mois' },
'2M': { price: 8, label: '4€ / mois' },
'3M': { price: 12, label: '4€ / mois' },
'6M': { price: 25, label: '4.16€ / mois' },
'12M': { price: 40, label: '3.33€ / mois' }
'1M': {price: 5, label: '5€ / mois'},
'2M': {price: 8, label: '4€ / mois'},
'3M': {price: 12, label: '4€ / mois'},
'6M': {price: 25, label: '4.16€ / mois'},
'12M': {price: 40, label: '3.33€ / mois'}
};
// Translation Keys (In a real Symfony app, these would be loaded from an assets file or context)
const translations = {
'monthly_renewal': '{{ 'page.presentation.js.monthly_renewal'|trans }}',
'annual_billing': '{{ 'page.presentation.js.annual_billing'|trans }}',
@@ -414,59 +322,35 @@
function updatePrice(duration) {
const priceElement = document.getElementById('current-price');
const perMonthElement = document.getElementById('price-per-month');
const data = pricing[duration];
if (data) {
priceElement.textContent = data.price + '€';
if (duration === '1M') {
perMonthElement.textContent = translations.monthly_renewal;
} else if (duration === '12M') {
// Use string replacement for translated text with interpolation
const translatedText = translations.annual_billing.replace(
'%average_price%', `<strong class="text-red-500">${data.label}</strong>`
'%average_price%', `<span class="text-red-600 font-black underline decoration-4 underline-offset-4">${data.label}</span>`
);
perMonthElement.innerHTML = translatedText;
} else {
// Use string replacement for translated text with interpolation
const months = duration.replace('M', '');
const translatedText = translations.periodic_billing
.replace('%duration%', months)
.replace('%average_price%', `<strong class="text-red-500">${data.label}</strong>`);
.replace('%average_price%', `<span class="text-red-600 font-black underline decoration-4 underline-offset-4">${data.label}</span>`);
perMonthElement.innerHTML = translatedText;
}
}
}
// Initialiser le prix au chargement (pour l'abonnement 1M).
// J'utilise turbo:load ou DOMContentLoaded comme fallback.
function initializePrice() {
updatePrice('1M');
}
function redirectToCheckout() {
const selectedDurationInput = document.querySelector('input[name="duration"]:checked');
if (selectedDurationInput) {
const selectedDuration = selectedDurationInput.value;
// --- AJOUT DE LA VALIDATION DE DURÉE ---
if (VALID_DURATIONS.includes(selectedDuration)) {
// Redirection avec le paramètre 'abo' valide
const selectedDuration = selectedDurationInput ? selectedDurationInput.value : '1M';
window.location.href = `/pages/onboaring?abo=${selectedDuration}`;
} else {
// Fallback si la durée sélectionnée n'est pas dans la liste des valides
console.error("Durée d'abonnement sélectionnée non valide:", selectedDuration);
// On peut rediriger vers la page par défaut si l'on ne veut pas bloquer l'utilisateur
window.location.href = `/pages/onboaring?abo=1M`;
}
} else {
// Fallback si rien n'est sélectionné (redirection par défaut vers 1M ou page de découverte)
console.error("Aucune durée d'abonnement sélectionnée. Redirection par défaut.");
window.location.href = `/pages/onboaring?abo=1M`;
}
}
if (typeof window.Turbo !== 'undefined') {
@@ -475,7 +359,5 @@
document.addEventListener('DOMContentLoaded', initializePrice);
}
</script>
</main>
</div>
{% endblock %}