```
✨ feat(templates): [FR] Améliore l'UI et ajoute des traductions pour plus de contenu dynamique.
```
This commit is contained in:
@@ -4,18 +4,18 @@ import '@grafikart/drop-files-element'
|
||||
import {PaymentForm} from './PaymentForm'
|
||||
import * as Sentry from "@sentry/browser";
|
||||
|
||||
// --- CLÉS DE STOCKAGE ET VAPID ---
|
||||
// --- CONFIGURATION ET ETAT ---
|
||||
const VAPID_PUBLIC_KEY = "BKz0kdcsG6kk9KxciPpkfP8kEDAd408inZecij5kBDbQ1ZGZSNwS4KZ8FerC28LFXvgSqpDXtor3ePo0zBCdNqo";
|
||||
const COOKIE_STORAGE_KEY = 'cookies_accepted';
|
||||
let userMenuTimeout; // Pour gérer le délai de fermeture du menu
|
||||
|
||||
// --- MESSAGES ET TRADUCTIONS ---
|
||||
const MESSAGES = {
|
||||
fr: {
|
||||
notificationTitle: "🔔 Activer les notifications",
|
||||
notificationText: "Recevez les nouvelles, les promotions et les événements de l'association.",
|
||||
notificationButton: "Activer",
|
||||
notificationClose: "Fermer la notification",
|
||||
cookieText: 'Ce site utilise uniquement des cookies de <strong>fonctionnement</strong> et de <strong>sécurité</strong> essentiels. Aucune donnée personnelle n\'est collectée.',
|
||||
cookieText: 'Ce site utilise uniquement des cookies de <strong>fonctionnement</strong> et de <strong>sécurité</strong> essentiels.',
|
||||
cookieLink: "Politique de cookies",
|
||||
cookieButton: "Accepter",
|
||||
},
|
||||
@@ -24,7 +24,7 @@ const MESSAGES = {
|
||||
notificationText: "Receive news, promotions, and association events.",
|
||||
notificationButton: "Enable",
|
||||
notificationClose: "Close notification",
|
||||
cookieText: 'This website only uses <strong>functional</strong> and <strong>security</strong> cookies. No personal data is collected.',
|
||||
cookieText: 'This website only uses <strong>functional</strong> and <strong>security</strong> cookies.',
|
||||
cookieLink: "Cookie Policy",
|
||||
cookieButton: "Accept",
|
||||
}
|
||||
@@ -62,12 +62,10 @@ function toggleMenu(button, menu) {
|
||||
* Initialisation de l'UI
|
||||
*/
|
||||
function initializeUI() {
|
||||
// Reset des menus au chargement
|
||||
document.querySelectorAll('#mobile-menu, #userMenuDesktop, #userMenuMobile').forEach(menu => {
|
||||
menu.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Panier Latéral
|
||||
const cartSidebar = document.getElementById('cartSidebar');
|
||||
const cartBackdrop = document.getElementById('cartBackdrop');
|
||||
|
||||
@@ -78,7 +76,6 @@ function initializeUI() {
|
||||
cartSidebar.classList.remove('translate-x-full');
|
||||
cartSidebar.classList.add('translate-x-0');
|
||||
};
|
||||
|
||||
window.closeCart = () => {
|
||||
document.body.style.overflow = '';
|
||||
cartSidebar.classList.remove('translate-x-0');
|
||||
@@ -89,7 +86,6 @@ function initializeUI() {
|
||||
};
|
||||
}
|
||||
|
||||
// Update counters
|
||||
const updateCartDisplay = (count) => {
|
||||
const desktopCounter = document.getElementById('cartCountDesktop');
|
||||
const mobileCounter = document.getElementById('cartCountMobile');
|
||||
@@ -98,20 +94,47 @@ function initializeUI() {
|
||||
};
|
||||
updateCartDisplay(0);
|
||||
|
||||
// Initialisation spécifique au menu utilisateur (Desktop)
|
||||
setupUserMenuHover();
|
||||
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
subscribeAndSave();
|
||||
}
|
||||
}
|
||||
|
||||
// --- LOGIQUE PUSH NOTIFICATIONS ---
|
||||
/**
|
||||
* Gestion du survol du menu utilisateur pour éviter la fermeture brutale
|
||||
*/
|
||||
function setupUserMenuHover() {
|
||||
const btn = document.getElementById('userMenuButtonDesktop');
|
||||
const menu = document.getElementById('userMenuDesktop');
|
||||
|
||||
if (!btn || !menu) return;
|
||||
|
||||
const open = () => {
|
||||
clearTimeout(userMenuTimeout);
|
||||
menu.classList.remove('hidden');
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
userMenuTimeout = setTimeout(() => {
|
||||
menu.classList.add('hidden');
|
||||
}, 200); // Délai de 200ms pour laisser le temps de passer du bouton au menu
|
||||
};
|
||||
|
||||
btn.addEventListener('mouseenter', open);
|
||||
btn.addEventListener('mouseleave', close);
|
||||
menu.addEventListener('mouseenter', open);
|
||||
menu.addEventListener('mouseleave', close);
|
||||
}
|
||||
|
||||
// --- LOGIQUE PUSH & BANNIÈRES (Inchangé) ---
|
||||
async function subscribeAndSave() {
|
||||
if (!('Notification' in window) || Notification.permission !== 'granted') return;
|
||||
if (!('serviceWorker' in navigator)) return;
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
let subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (!subscription) {
|
||||
const applicationServerKey = urlBase64ToUint8Array(VAPID_PUBLIC_KEY);
|
||||
subscription = await registration.pushManager.subscribe({
|
||||
@@ -119,15 +142,12 @@ async function subscribeAndSave() {
|
||||
applicationServerKey: applicationServerKey
|
||||
});
|
||||
}
|
||||
|
||||
await fetch('/notificationSub', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ subscription: subscription.toJSON() })
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Push Error:", error);
|
||||
}
|
||||
} catch (error) { console.error("Push Error:", error); }
|
||||
}
|
||||
|
||||
async function promptForPermissionAndSubscribe() {
|
||||
@@ -135,18 +155,14 @@ async function promptForPermissionAndSubscribe() {
|
||||
try {
|
||||
const permission = await Notification.requestPermission();
|
||||
if (permission === 'granted') await subscribeAndSave();
|
||||
} catch (error) {
|
||||
console.error("Permission Error:", error);
|
||||
}
|
||||
} catch (error) { console.error("Permission Error:", error); }
|
||||
}
|
||||
|
||||
// --- OPTIMISATION PERFORMANCE ---
|
||||
function isPerformanceTestAgent() {
|
||||
const ua = navigator.userAgent;
|
||||
return ua.includes('Lighthouse') || ua.includes('PageSpeed');
|
||||
}
|
||||
|
||||
// --- BANNIÈRES ---
|
||||
function handleNotificationBanner() {
|
||||
if (isPerformanceTestAgent()) return;
|
||||
const BANNER_ID = 'notification-prompt-banner';
|
||||
@@ -157,7 +173,6 @@ function handleNotificationBanner() {
|
||||
const banner = document.createElement('div');
|
||||
banner.id = BANNER_ID;
|
||||
banner.className = `fixed bottom-4 left-4 z-50 p-4 max-w-xs bg-indigo-600 text-white rounded-xl shadow-2xl transition-all duration-500 transform opacity-0 translate-y-full md:left-8 md:bottom-8 border-2 border-black`;
|
||||
|
||||
banner.innerHTML = `
|
||||
<div class="flex items-start justify-between italic">
|
||||
<p class="font-black uppercase text-xs tracking-widest">${M.notificationTitle}</p>
|
||||
@@ -170,13 +185,11 @@ function handleNotificationBanner() {
|
||||
${M.notificationButton}
|
||||
</button>
|
||||
`;
|
||||
|
||||
document.body.appendChild(banner);
|
||||
setTimeout(() => {
|
||||
banner.classList.remove('opacity-0', 'translate-y-full');
|
||||
banner.classList.add('opacity-100', 'translate-y-0');
|
||||
}, 100);
|
||||
|
||||
document.getElementById('closeNotificationBanner').addEventListener('click', () => banner.remove());
|
||||
document.getElementById('activateNotifications').addEventListener('click', async () => {
|
||||
await promptForPermissionAndSubscribe();
|
||||
@@ -189,12 +202,10 @@ function handleCookieBanner() {
|
||||
if (localStorage.getItem(COOKIE_STORAGE_KEY) === 'true') return;
|
||||
const BANNER_ID = 'cookie-banner';
|
||||
if (document.getElementById(BANNER_ID)) return;
|
||||
|
||||
const M = getLanguageMessages();
|
||||
const banner = document.createElement('div');
|
||||
banner.id = BANNER_ID;
|
||||
banner.className = `fixed bottom-4 right-4 z-50 p-6 max-w-sm bg-white border-4 border-black shadow-[10px_10px_0px_rgba(0,0,0,1)] transition-all duration-500 transform opacity-0 translate-y-full`;
|
||||
|
||||
banner.innerHTML = `
|
||||
<p class="text-xs font-bold uppercase italic leading-relaxed text-gray-600">${M.cookieText}</p>
|
||||
<div class="mt-4 flex justify-end items-center gap-4">
|
||||
@@ -204,78 +215,58 @@ function handleCookieBanner() {
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(banner);
|
||||
setTimeout(() => {
|
||||
banner.classList.remove('opacity-0', 'translate-y-full');
|
||||
banner.classList.add('opacity-100', 'translate-y-0');
|
||||
}, 200);
|
||||
|
||||
document.getElementById('acceptCookies').addEventListener('click', () => {
|
||||
localStorage.setItem(COOKIE_STORAGE_KEY, 'true');
|
||||
banner.remove();
|
||||
});
|
||||
}
|
||||
|
||||
// --- DOM READY & TURBO ---
|
||||
// --- BOOTSTRAP ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!customElements.get('payment-don')) {
|
||||
customElements.define('payment-don', PaymentForm, {extends: 'form'});
|
||||
}
|
||||
initializeUI();
|
||||
|
||||
if (!isPerformanceTestAgent()) {
|
||||
handleNotificationBanner();
|
||||
handleCookieBanner();
|
||||
|
||||
// Chatwoot
|
||||
var BASE_URL_WOOT = "https://chat.esy-web.dev";
|
||||
let script = document.createElement('script');
|
||||
script.setAttribute('src', BASE_URL_WOOT + "/packs/js/sdk.js");
|
||||
script.defer = true;
|
||||
document.head.append(script);
|
||||
script.onload = () => {
|
||||
window.chatwootSDK.run({
|
||||
websiteToken: '6uFX3g3qybyvSt3PAQUMgkm4',
|
||||
baseUrl: BASE_URL_WOOT
|
||||
});
|
||||
window.chatwootSDK.run({ websiteToken: '6uFX3g3qybyvSt3PAQUMgkm4', baseUrl: BASE_URL_WOOT });
|
||||
};
|
||||
}
|
||||
|
||||
const env = document.querySelector('meta[name="env"]');
|
||||
if (env && env.getAttribute('content') === "prod") {
|
||||
if ('serviceWorker' in navigator) {
|
||||
if (env && env.getAttribute('content') === "prod" && 'serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('turbo:load', () => {
|
||||
initializeUI();
|
||||
handleNotificationBanner();
|
||||
handleCookieBanner();
|
||||
});
|
||||
|
||||
// --- DÉLÉGATION D'ÉVÉNEMENTS ---
|
||||
document.addEventListener('click', (event) => {
|
||||
const target = event.target;
|
||||
|
||||
// Menu Mobile
|
||||
const mobileMenuButton = target.closest('#mobileMenuButton');
|
||||
if (mobileMenuButton) {
|
||||
const menu = document.getElementById('mobile-menu');
|
||||
toggleMenu(mobileMenuButton, menu);
|
||||
toggleMenu(mobileMenuButton, document.getElementById('mobile-menu'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Panier
|
||||
const openCartBtn = target.closest('#openCartDesktop, #openCartMobile');
|
||||
if (openCartBtn && window.openCart) {
|
||||
event.preventDefault();
|
||||
window.openCart();
|
||||
return;
|
||||
}
|
||||
|
||||
const closeCartBtn = target.closest('#closeCartButton') || target === document.getElementById('cartBackdrop');
|
||||
if (closeCartBtn && window.closeCart) {
|
||||
window.closeCart();
|
||||
|
||||
@@ -9,3 +9,8 @@
|
||||
.epage{
|
||||
color: orangered;
|
||||
}
|
||||
|
||||
#userMenuDesktop {
|
||||
margin-top: -4px; /* Remonte légèrement le menu pour toucher le bouton */
|
||||
padding-top: 10px; /* Ajoute du padding interne pour garder la zone réactive */
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 pb-24 overflow-x-hidden">
|
||||
<div class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 overflow-x-hidden">
|
||||
|
||||
{# --- HEADER HERO --- #}
|
||||
<header class="relative pt-20 pb-24 px-4 overflow-hidden border-b-4 border-gray-900 bg-white">
|
||||
@@ -274,7 +274,7 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.outline-text {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 pb-24 overflow-x-hidden">
|
||||
<div class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 overflow-x-hidden">
|
||||
|
||||
{# --- HEADER --- #}
|
||||
<header class="relative pt-16 pb-20 px-4 border-b-4 border-gray-900 bg-white">
|
||||
@@ -80,7 +80,7 @@
|
||||
<div class="space-y-10">
|
||||
{# Email #}
|
||||
<div class="group">
|
||||
<p class="text-[10px] font-black uppercase text-gray-400 tracking-widest mb-2">// TRANSMISSION_LINE</p>
|
||||
<p class="text-[10px] font-black uppercase text-gray-400 tracking-widest mb-2">// {{ 'contact_info.email'|trans }}</p>
|
||||
<a href="mailto:contact@e-cosplay.fr" class="text-xl font-black text-indigo-600 hover:text-yellow-500 break-all transition-colors">
|
||||
contact@e-cosplay.fr
|
||||
</a>
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
{# Adresse #}
|
||||
<div>
|
||||
<p class="text-[10px] font-black uppercase text-gray-400 tracking-widest mb-2">// BASE_COORDINATES</p>
|
||||
<p class="text-[10px] font-black uppercase text-gray-400 tracking-widest mb-2">// {{ 'contact_info.address'|trans }}</p>
|
||||
<p class="text-xl font-black leading-tight">
|
||||
42 RUE DE SAINT-QUENTIN<br>
|
||||
02800 BEAUTOR, FRANCE
|
||||
@@ -116,22 +116,22 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div class="space-y-2">
|
||||
{{ form_label(form.name, 'contact_form.name.label'|trans, {'label_attr': {'class': 'block text-[10px] font-black uppercase tracking-widest text-gray-400'}}) }}
|
||||
{{ form_widget(form.name, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all', 'placeholder': 'GATOR'}}) }}
|
||||
{{ form_widget(form.name, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all', 'placeholder': 'contact_name_pl'}}) }}
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
{{ form_label(form.surname, 'contact_form.surname.label'|trans, {'label_attr': {'class': 'block text-[10px] font-black uppercase tracking-widest text-gray-400'}}) }}
|
||||
{{ form_widget(form.surname, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all', 'placeholder': 'MARTA'}}) }}
|
||||
{{ form_widget(form.surname, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all', 'placeholder': 'contact_surname_pl'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div class="space-y-2">
|
||||
{{ form_label(form.email, 'form_email_label'|trans, {'label_attr': {'class': 'block text-[10px] font-black uppercase tracking-widest text-gray-400'}}) }}
|
||||
{{ form_widget(form.email, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all', 'placeholder': 'PLAYER@ECOSPLAY.FR'}}) }}
|
||||
{{ form_widget(form.email, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all', 'placeholder': 'contact_email_pl'}}) }}
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
{{ form_label(form.subject, 'contact_form.subject.label'|trans, {'label_attr': {'class': 'block text-[10px] font-black uppercase tracking-widest text-gray-400'}}) }}
|
||||
{{ form_widget(form.subject, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all'}}) }}
|
||||
{{ form_widget(form.subject, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all','placeholder':'contact_email_subject'}}) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
|
||||
<div class="space-y-2">
|
||||
{{ form_label(form.message, 'contact_form.message.label'|trans, {'label_attr': {'class': 'block text-[10px] font-black uppercase tracking-widest text-gray-400'}}) }}
|
||||
{{ form_widget(form.message, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all h-40 resize-none', 'placeholder': 'WRITE YOUR MESSAGE...'}}) }}
|
||||
{{ form_widget(form.message, {'attr': {'class': 'w-full bg-gray-50 border-4 border-gray-900 p-4 font-bold focus:bg-white focus:border-indigo-600 outline-none transition-all h-40 resize-none', 'placeholder': 'contact_email_message'}}) }}
|
||||
</div>
|
||||
|
||||
<div class="pt-6">
|
||||
@@ -164,12 +164,12 @@
|
||||
<div class="mt-20 bg-yellow-500 py-4 overflow-hidden border-y-4 border-gray-900">
|
||||
<div class="whitespace-nowrap flex animate-marquee font-black uppercase italic">
|
||||
{% for i in 1..15 %}
|
||||
<span class="mx-8 text-gray-900">Contact the Team // Support Online // Recruitement Open // Join the Roster //</span>
|
||||
<span class="mx-8 text-gray-900">{{ 'contact_roll'|trans }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes marquee {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 pb-24 overflow-x-hidden">
|
||||
<div class="bg-[#fbfbfb] font-sans text-gray-900 overflow-x-hidden">
|
||||
|
||||
{# --- HEADER --- #}
|
||||
<header class="relative pt-16 pb-20 px-4 border-b-4 border-gray-900 bg-white">
|
||||
@@ -61,12 +61,9 @@
|
||||
{# Badge Date #}
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<p class="text-[10px] font-black uppercase text-gray-400 tracking-[0.2em] mb-1">DATE_ARCHIVE</p>
|
||||
<p class="text-[10px] font-black uppercase text-gray-400 tracking-[0.2em] mb-1">{{ 'doc_ag_at'|trans }}</p>
|
||||
<h3 class="text-3xl font-black uppercase tracking-tighter">{{ ag.agDateAt|date('d/m/Y') }}</h3>
|
||||
</div>
|
||||
<div class="bg-gray-100 border-2 border-gray-900 px-3 py-1 text-xs font-black">
|
||||
VOL_{{ ag.agDateAt|date('Y') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Infos Membres #}
|
||||
@@ -74,11 +71,11 @@
|
||||
<div class="grid grid-cols-2 gap-4 text-[11px] font-bold uppercase tracking-tight">
|
||||
<div>
|
||||
<span class="block text-gray-400 mb-1">{{ 'ag.president_label'|trans }}</span>
|
||||
<span class="text-gray-900">{{ ag.president.civ }} {{ ag.president.surname }}</span>
|
||||
<span class="text-gray-900">{{ ag.president.civ }} {{ ag.president.name }} {{ ag.president.surname }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-gray-400 mb-1">{{ 'ag.secretary_label'|trans }}</span>
|
||||
<span class="text-gray-900">{{ ag.secretaire.civ }} {{ ag.secretaire.surname }}</span>
|
||||
<span class="text-gray-900">{{ ag.secretaire.civ }} {{ ag.secretaire.name }}{{ ag.secretaire.surname }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,23 +106,20 @@
|
||||
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<p class="text-[10px] font-black uppercase text-red-400 tracking-[0.2em] mb-1">CRITICAL_ARCHIVE</p>
|
||||
<p class="text-[10px] font-black uppercase text-red-400 tracking-[0.2em] mb-1">{{ 'doc_ag_at'|trans }}</p>
|
||||
<h3 class="text-3xl font-black uppercase tracking-tighter text-red-600">{{ ag.agDateAt|date('d/m/Y') }}</h3>
|
||||
</div>
|
||||
<div class="bg-red-50 text-red-600 border-2 border-red-600 px-3 py-1 text-xs font-black animate-pulse">
|
||||
ALERT_EXT
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-red-50 border-l-8 border-red-600 p-4 mb-6">
|
||||
<div class="grid grid-cols-2 gap-4 text-[11px] font-bold uppercase tracking-tight">
|
||||
<div>
|
||||
<span class="block text-red-400 mb-1">{{ 'ag.president_label'|trans }}</span>
|
||||
<span class="text-gray-900">{{ ag.president.civ }} {{ ag.president.surname }}</span>
|
||||
<span class="text-gray-900">{{ ag.president.civ }} {{ ag.president.name }} {{ ag.president.surname }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-red-400 mb-1">{{ 'ag.secretary_label'|trans }}</span>
|
||||
<span class="text-gray-900">{{ ag.secretaire.civ }} {{ ag.secretaire.surname }}</span>
|
||||
<span class="text-gray-900">{{ ag.secretaire.civ }} {{ ag.secretaire.name }} {{ ag.president.surname }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -147,13 +141,13 @@
|
||||
<div class="flex whitespace-nowrap animate-marquee italic">
|
||||
{% for i in 1..20 %}
|
||||
<span class="text-white font-black uppercase mx-8 text-xs opacity-40">
|
||||
Official Documentation // Legal Compliance // Transparency // Association Roster //
|
||||
{{ 'doc_roll'|trans }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes marquee {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 pb-24 overflow-x-hidden">
|
||||
<div class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 overflow-x-hidden">
|
||||
|
||||
{# --- HEADER --- #}
|
||||
<header class="relative pt-16 pb-20 px-4 border-b-4 border-gray-900 bg-white">
|
||||
@@ -162,13 +162,13 @@
|
||||
<div class="whitespace-nowrap flex animate-marquee italic">
|
||||
{% for i in 1..10 %}
|
||||
<span class="text-white font-black uppercase mx-8 text-2xl opacity-30">
|
||||
Level Up the Community // Support the Craft // Every Donation Counts // Gear Up //
|
||||
{{ 'dons_roll'|trans }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes marquee {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 pb-24 overflow-x-hidden">
|
||||
<div class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 overflow-x-hidden">
|
||||
|
||||
{# --- HEADER HERO --- #}
|
||||
<header class="relative pt-16 pb-20 px-4 border-b-4 border-gray-900 bg-white">
|
||||
@@ -157,13 +157,13 @@
|
||||
<div class="whitespace-nowrap flex animate-marquee italic">
|
||||
{% for i in 1..10 %}
|
||||
<span class="text-white font-black uppercase mx-8 text-xl opacity-20 tracking-widest">
|
||||
Next Mission // New Event // Stay Tuned // Join the Community //
|
||||
{{ 'event_roll'|trans }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Animation du texte qui défile en bas */
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{% block meta_description %}{{ event.title }}{% endblock %}
|
||||
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_event_details',{id:event.id}) }}" />{% endblock %}
|
||||
|
||||
{% block breadcrumb_schema %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
@@ -34,83 +35,106 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mx-auto p-4 md:p-8 pt-12">
|
||||
<div class="max-w-4xl mx-auto bg-white rounded-2xl shadow-2xl overflow-hidden">
|
||||
<div class="bg-[#fbfbfb] min-h-screen py-12 px-4 italic">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
|
||||
{# BOUTON RETOUR STYLE BRUTALISTE #}
|
||||
<a href="{{ url('app_events') }}" class="inline-block mb-8 px-6 py-2 bg-white border-4 border-gray-900 font-black uppercase tracking-widest text-xs shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
<i class="fas fa-arrow-left mr-2"></i> {{ 'events.details.back_to_list'|trans|default('Retour à la liste') }}
|
||||
</a>
|
||||
|
||||
<article class="bg-white border-8 border-gray-900 shadow-[16px_16px_0px_#4f46e5]">
|
||||
|
||||
{# HEADER AVEC IMAGE / AFFICHE #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 border-b-8 border-gray-900">
|
||||
|
||||
{# Affiche #}
|
||||
<div class="md:col-span-5 bg-gray-200 border-b-8 md:border-b-0 md:border-r-8 border-gray-900 min-h-[400px]">
|
||||
{% set imageUrl = event.eventsFileName ? vich_uploader_asset(event, 'affiche') : null %}
|
||||
|
||||
{# Image / Affiche de l'événement #}
|
||||
{% if imageUrl %}
|
||||
<div class="h-64 sm:h-96 w-full overflow-hidden">
|
||||
<img src="{{ asset(imageUrl) }}"
|
||||
alt="Affiche de l'événement {{ event.title }}"
|
||||
class="w-full h-full object-cover">
|
||||
alt="{{ event.title }}"
|
||||
class="w-full h-full object-cover grayscale hover:grayscale-0 transition-all duration-500">
|
||||
{% else %}
|
||||
<div class="w-full h-full flex items-center justify-center bg-gray-100 font-black text-gray-400 uppercase text-2xl p-10 text-center">
|
||||
Affiche non disponible
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="p-6 sm:p-10">
|
||||
{# Titre et Infos Rapides #}
|
||||
<div class="md:col-span-7 p-8 md:p-12 flex flex-col justify-center bg-white">
|
||||
<div class="inline-block bg-yellow-400 text-gray-900 px-4 py-1 mb-6 border-4 border-gray-900 font-black uppercase tracking-widest text-sm skew-x-[-10deg] self-start">
|
||||
Mission Log // ID: {{ event.id }}
|
||||
</div>
|
||||
|
||||
{# Titre principal #}
|
||||
<h1 class="text-4xl font-extrabold text-gray-900 mb-6 border-b pb-4">
|
||||
<h1 class="text-5xl md:text-7xl font-black uppercase tracking-tighter leading-none mb-8">
|
||||
{{ event.title }}
|
||||
</h1>
|
||||
|
||||
{# Détails clés (Date, Lieu, Organisateur) #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8 border-b pb-6">
|
||||
|
||||
{# Date Block #}
|
||||
<div class="flex items-start">
|
||||
<svg class="w-6 h-6 mr-3 flex-shrink-0 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-indigo-600">{{ 'events.details.date'|trans|default('Date') }}</p>
|
||||
<p class="text-lg font-medium text-gray-800">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="w-10 h-10 bg-indigo-600 text-white flex items-center justify-center border-2 border-gray-900 shadow-[2px_2px_0px_rgba(0,0,0,1)]">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
</span>
|
||||
<span class="text-xl font-black uppercase">
|
||||
{{ event.startAt|date('d/m/Y') }}
|
||||
{% if event.startAt|date('Ymd') != event.endAt|date('Ymd') %}
|
||||
- {{ event.endAt|date('d/m/Y') }}
|
||||
{% else %}
|
||||
- {{ event.endAt|date('H:i') }}
|
||||
<span class="text-indigo-600 ml-2">//</span> {{ event.startAt|date('H:i') }} - {{ event.endAt|date('H:i') }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{# Location Block #}
|
||||
<div class="flex items-start">
|
||||
<svg class="w-6 h-6 mr-3 flex-shrink-0 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-indigo-600">{{ 'events.details.location'|trans|default('Lieu') }}</p>
|
||||
<p class="text-lg font-medium text-gray-800">{{ event.location }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="w-10 h-10 bg-pink-500 text-white flex items-center justify-center border-2 border-gray-900 shadow-[2px_2px_0px_rgba(0,0,0,1)]">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
</span>
|
||||
<span class="text-xl font-black uppercase">{{ event.location }}</span>
|
||||
</div>
|
||||
|
||||
{# Organizer Block #}
|
||||
<div class="flex items-start">
|
||||
<svg class="w-6 h-6 mr-3 flex-shrink-0 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-indigo-600">{{ 'events.details.organizer'|trans|default('Organisateur') }}</p>
|
||||
<p class="text-lg font-medium text-gray-800">{{ event.organizer }}</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="w-10 h-10 bg-yellow-400 text-gray-900 flex items-center justify-center border-2 border-gray-900 shadow-[2px_2px_0px_rgba(0,0,0,1)]">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
</span>
|
||||
<span class="text-lg font-bold uppercase opacity-70">{{ 'events.details.organizer'|trans|default('Organisé par') }} : {{ event.organizer }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Description #}
|
||||
{% if event.description is defined and event.description is not empty %}
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4">
|
||||
{{ 'events.details.description_title'|trans|default('Description') }}
|
||||
{# CORPS DE LA DESCRIPTION #}
|
||||
<div class="p-8 md:p-12 bg-gray-50">
|
||||
<div class="max-w-3xl">
|
||||
<h2 class="text-3xl font-black uppercase tracking-widest mb-8 border-l-8 border-indigo-600 pl-6">
|
||||
{{ 'events.details.description_title'|trans|default('Briefing de mission') }}
|
||||
</h2>
|
||||
<div class="prose max-w-none text-gray-600 leading-relaxed mb-10">
|
||||
{{ event.description|raw }} {# Assuming description is HTML/Markdown content #}
|
||||
|
||||
{% if event.description is defined and event.description is not empty %}
|
||||
<div class="prose prose-xl max-w-none text-gray-800 font-bold leading-relaxed italic">
|
||||
{{ event.description|raw }}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-400 font-black uppercase italic">Aucune information supplémentaire fournie.</p>
|
||||
{% endif %}
|
||||
|
||||
{# Bouton de retour #}
|
||||
<a href="{{ url('app_events') }}" class="inline-flex items-center text-indigo-600 hover:text-indigo-800 transition duration-150 font-medium mt-4">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
{{ 'events.details.back_to_list'|trans|default('Retour à la liste des événements') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# BANDEAU INFÉRIEUR (CTA / PARTAGE) #}
|
||||
<div class="border-t-8 border-gray-900 p-8 flex flex-col md:flex-row items-center justify-between gap-8 bg-white">
|
||||
<div class="text-center md:text-left">
|
||||
<p class="font-black uppercase tracking-tighter text-2xl">Prêt à rejoindre l'aventure ?</p>
|
||||
<p class="font-bold text-gray-500 italic uppercase">Préparez vos crafts et vos équipements.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<button onclick="window.print()" class="px-8 py-4 bg-gray-900 text-white font-black uppercase italic border-4 border-gray-900 shadow-[6px_6px_0px_#4f46e5] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
Imprimer le pass
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="bg-[#fbfbfb] overflow-x-hidden italic font-sans">
|
||||
<div class="bg-[#fbfbfb] overflow-x-hidden italic font-sans">
|
||||
|
||||
{# --- SECTION 1: HERO (COMMAND CENTER) --- #}
|
||||
<section class="relative min-h-[90vh] flex items-center justify-center bg-white border-b-8 border-gray-900 px-4 pt-20 pb-32">
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="flex whitespace-nowrap animate-marquee italic">
|
||||
{% for i in 1..10 %}
|
||||
<span class="text-white font-black uppercase mx-8 text-2xl opacity-80">
|
||||
Crafting Reality // Cosplay Culture // Join the Guild // Artistry & Passion //
|
||||
{{ 'home_roll'|trans }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -174,13 +174,13 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a href="{{ url('app_contact') }}" class="group relative px-16 py-8 bg-yellow-400 text-gray-900 font-black uppercase italic tracking-widest text-2xl border-4 border-gray-900 shadow-[10px_10px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-2 hover:translate-y-2 transition-all">
|
||||
<a href="{{ url('app_recruit') }}" class="group relative px-16 py-8 bg-yellow-400 text-gray-900 font-black uppercase italic tracking-widest text-2xl border-4 border-gray-900 shadow-[10px_10px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-2 hover:translate-y-2 transition-all">
|
||||
{{ 'home_cta.button'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes marquee {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 pb-24 overflow-x-hidden">
|
||||
<div class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 overflow-x-hidden">
|
||||
|
||||
{# --- HEADER --- #}
|
||||
<header class="relative pt-16 pb-20 px-4 border-b-4 border-gray-900 bg-white">
|
||||
@@ -155,10 +155,13 @@
|
||||
{# --- CTA BAS DE PAGE --- #}
|
||||
<div class="mt-32 border-t-4 border-gray-900 bg-yellow-500 py-12 text-center">
|
||||
<p class="text-2xl font-black uppercase italic tracking-tighter mb-4">Envie de rejoindre le roster ?</p>
|
||||
<a href="{{ path('app_recruit') }}" class="inline-block bg-gray-900 text-white px-10 py-4 font-black uppercase italic skew-x-[-10deg] border-2 border-gray-900 hover:bg-indigo-600 transition-colors">
|
||||
{{ 'home_cta.button'|trans }}
|
||||
</a>
|
||||
<a href="{{ path('app_contact') }}" class="inline-block bg-gray-900 text-white px-10 py-4 font-black uppercase italic skew-x-[-10deg] border-2 border-gray-900 hover:bg-indigo-600 transition-colors">
|
||||
Devenir Membre
|
||||
{{ 'home_cta.contact'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 pb-24 overflow-x-hidden">
|
||||
<div class="bg-[#fbfbfb] min-h-screen font-sans text-gray-900 overflow-x-hidden">
|
||||
|
||||
{# --- HEADER : THE ROSTER --- #}
|
||||
<header class="relative pt-16 pb-20 px-4 border-b-4 border-gray-900 bg-white">
|
||||
@@ -150,13 +150,13 @@
|
||||
<div class="whitespace-nowrap flex animate-marquee italic">
|
||||
{% for i in 1..10 %}
|
||||
<span class="text-white font-black uppercase mx-8 text-2xl opacity-20">
|
||||
Showcase your art // Join the Roster // High Tier Cosplay // Community Driven //
|
||||
{{ 'page_roll'|trans }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
<style>
|
||||
@keyframes marquee {
|
||||
0% { transform: translateX(0); }
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{% block meta_description %}{{ 'events.forgot_password'|trans }}{% endblock %}
|
||||
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_forgot_password') }}" />{% endblock %}
|
||||
|
||||
{% block breadcrumb_schema %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
@@ -28,68 +29,87 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-md w-full space-y-8 p-10 bg-white rounded-xl shadow-lg">
|
||||
<div class="min-h-screen flex items-center justify-center bg-[#fbfbfb] py-12 px-4 sm:px-6 lg:px-8 italic">
|
||||
<div class="max-w-md w-full">
|
||||
|
||||
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
{{ 'events.forgot_password'|trans }}
|
||||
{# CONTENEUR NEUBRUTALISTE #}
|
||||
<div class="bg-white border-8 border-gray-900 p-8 md:p-10 shadow-[12px_12px_0px_#4f46e5] relative">
|
||||
|
||||
{# BADGE DE STATUT #}
|
||||
<div class="absolute -top-4 -left-4 bg-pink-500 text-white border-4 border-gray-900 px-4 py-1 font-black text-xs uppercase tracking-widest rotate-[-5deg]">
|
||||
System_Recovery
|
||||
</div>
|
||||
|
||||
<div class="mb-8 mt-4">
|
||||
<h2 class="text-4xl font-black text-gray-900 uppercase tracking-tighter leading-none">
|
||||
{{ 'events.forgot_password'|trans }}<span class="text-pink-500">?</span>
|
||||
</h2>
|
||||
|
||||
<p class="mt-2 text-center text-sm text-gray-600">
|
||||
{{ 'text.enter_email_for_reset'|trans }}
|
||||
<p class="mt-4 text-xs font-bold text-gray-500 uppercase leading-snug tracking-widest">
|
||||
{{ 'text.enter_email_for_reset'|trans }} _
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{# Affichage des messages flash (succès ou erreur) #}
|
||||
{# ALERTES FLASH #}
|
||||
{% for flash_error in app.flashes('reset_password_error') %}
|
||||
<div class="p-4 text-sm text-red-700 bg-red-100 rounded-lg" role="alert">
|
||||
{{ flash_error }}
|
||||
<div class="p-4 mb-6 border-4 border-gray-900 bg-pink-100 text-pink-700 font-black uppercase text-xs shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
||||
<i class="fas fa-terminal mr-2"></i> {{ flash_error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for message in app.flashes('success') %}
|
||||
<div class="p-4 text-sm text-green-700 bg-green-100 rounded-lg" role="alert">
|
||||
{{ message }}
|
||||
<div class="p-4 mb-6 border-4 border-gray-900 bg-green-100 text-green-700 font-black uppercase text-xs shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
||||
<i class="fas fa-check-double mr-2"></i> {{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# FORMULAIRE SYMFONY #}
|
||||
{{ form_start(form, {'attr': {'class': 'space-y-8'}}) }}
|
||||
|
||||
{# Le formulaire Symfony #}
|
||||
{{ form_start(form, {'attr': {'class': 'mt-8 space-y-6'}}) }}
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-black uppercase tracking-widest text-gray-900">
|
||||
{{ 'label.email'|trans }} //
|
||||
</label>
|
||||
|
||||
<div class="rounded-md shadow-sm -space-y-px">
|
||||
{# Champ Email #}
|
||||
<div>
|
||||
{{ form_label(form.email, 'label.email'|trans, {'label_attr': {'class': 'sr-only'}}) }}
|
||||
{{ form_widget(form.email, {
|
||||
'attr': {
|
||||
'class': 'appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm',
|
||||
'placeholder': 'label.email'|trans,
|
||||
'class': 'w-full px-4 py-4 border-4 border-gray-900 text-gray-900 font-black placeholder-gray-400 focus:ring-0 focus:border-pink-500 shadow-[4px_4px_0px_rgba(0,0,0,1)] transition-all',
|
||||
'placeholder': 'votre@email.com',
|
||||
'autocomplete': 'email',
|
||||
'required': 'required'
|
||||
}
|
||||
}) }}
|
||||
|
||||
{# Affichage des erreurs de champ spécifiques #}
|
||||
<div class="text-pink-600 font-black text-[10px] uppercase mt-2">
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Bouton Soumettre #}
|
||||
<div>
|
||||
<button type="submit"
|
||||
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
class="w-full group flex items-center justify-center py-4 px-4 border-4 border-gray-900 text-lg font-black uppercase tracking-tighter text-white bg-gray-900 shadow-[8px_8px_0px_#ec4899] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
{{ 'button.send_reset_link'|trans }}
|
||||
<i class="fas fa-paper-plane ml-3 text-pink-400 group-hover:translate-x-1 transition-transform"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
<div class="text-center text-sm">
|
||||
<a href="{{ path('app_login') }}" class="font-medium text-indigo-600 hover:text-indigo-500">
|
||||
{# NAVIGATION #}
|
||||
<div class="mt-10 pt-6 border-t-4 border-gray-100 flex justify-center">
|
||||
<a href="{{ path('app_login') }}" class="inline-flex items-center text-[11px] font-black uppercase tracking-widest text-gray-400 hover:text-indigo-600 transition-colors">
|
||||
<i class="fas fa-arrow-left mr-2"></i>
|
||||
{{ 'link.back_to_login'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{# DECORATION BACKGROUND (OPTIONNEL) #}
|
||||
<div class="mt-8 flex justify-center gap-4 opacity-20">
|
||||
<div class="w-12 h-2 bg-gray-900"></div>
|
||||
<div class="w-12 h-2 bg-indigo-600"></div>
|
||||
<div class="w-12 h-2 bg-pink-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{% block meta_description %}{{ 'page.login'|trans }}{% endblock %}
|
||||
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_login') }}" />{% endblock %}
|
||||
|
||||
{% block breadcrumb_schema %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
@@ -28,67 +29,82 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-md w-full space-y-8 p-10 bg-white rounded-xl shadow-lg">
|
||||
<div class="min-h-screen flex items-center justify-center bg-[#fbfbfb] py-12 px-4 sm:px-6 lg:px-8 italic">
|
||||
<div class="max-w-md w-full">
|
||||
|
||||
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
{{ 'security.login'|trans }}
|
||||
{# CONTENEUR PRINCIPAL NEUBRUTALISTE #}
|
||||
<div class="bg-white border-8 border-gray-900 p-8 md:p-10 shadow-[12px_12px_0px_rgba(0,0,0,1)] relative overflow-hidden">
|
||||
|
||||
{# DECO CORNER #}
|
||||
<div class="absolute top-0 right-0 bg-yellow-400 border-l-8 border-b-8 border-gray-900 px-4 py-1 font-black text-xs uppercase tracking-tighter">
|
||||
Secure_Link v2.0
|
||||
</div>
|
||||
|
||||
<div class="mb-10">
|
||||
<h2 class="text-5xl font-black text-gray-900 uppercase tracking-tighter leading-none">
|
||||
{{ 'security.login'|trans }}<span class="text-indigo-600">.</span>
|
||||
</h2>
|
||||
<p class="mt-2 text-xs font-bold text-gray-500 uppercase tracking-widest">Identification requise pour continuer</p>
|
||||
</div>
|
||||
|
||||
{# Display error messages if login fails #}
|
||||
{# MESSAGES D'ERREUR ET DE SUCCÈS #}
|
||||
{% if error %}
|
||||
<div class="p-4 mb-4 text-sm text-red-700 bg-red-100 rounded-lg" role="alert">
|
||||
{{ error.messageKey|trans(error.messageData, 'security') }}
|
||||
<div class="p-4 mb-6 border-4 border-gray-900 bg-pink-100 text-pink-700 font-bold uppercase text-xs shadow-[4px_4px_0px_rgba(0,0,0,1)]" role="alert">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i> {{ error.messageKey|trans(error.messageData, 'security') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for message in app.flashes('success') %}
|
||||
<div class="p-4 text-sm text-green-700 bg-green-100 rounded-lg" role="alert">
|
||||
{{ message }}
|
||||
<div class="p-4 mb-6 border-4 border-gray-900 bg-green-100 text-green-700 font-bold uppercase text-xs shadow-[4px_4px_0px_rgba(0,0,0,1)]" role="alert">
|
||||
<i class="fas fa-check-circle mr-2"></i> {{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{# The actual login form #}
|
||||
<form class="mt-8 space-y-6" action="{{ path('app_login') }}" method="post">
|
||||
|
||||
{# FORMULAIRE #}
|
||||
<form action="{{ path('app_login') }}" method="post" class="space-y-6">
|
||||
<input type="hidden" name="remember" value="true">
|
||||
|
||||
{# Username Field (Email) #}
|
||||
<div class="rounded-md shadow-sm -space-y-px">
|
||||
<div class="space-y-4">
|
||||
{# Champ Email #}
|
||||
<div>
|
||||
<label for="username" class="sr-only">{{ 'label.email'|trans }}</label>
|
||||
<label for="username" class="block text-xs font-black uppercase tracking-widest text-gray-900 mb-2">
|
||||
{{ 'label.email'|trans }} _
|
||||
</label>
|
||||
<input id="username" name="_username" type="email" autocomplete="email" required
|
||||
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="{{ 'label.email'|trans }}" value="{{ last_username }}" autofocus>
|
||||
class="w-full px-4 py-4 border-4 border-gray-900 text-gray-900 font-bold placeholder-gray-400 focus:ring-0 focus:border-indigo-600 shadow-[4px_4px_0px_rgba(0,0,0,1)] transition-all"
|
||||
placeholder="admin@e-cosplay.fr" value="{{ last_username }}" autofocus>
|
||||
</div>
|
||||
|
||||
{# Password Field #}
|
||||
{# Champ Mot de passe #}
|
||||
<div>
|
||||
<label for="password" class="sr-only">{{ 'label.password'|trans }}</label>
|
||||
<label for="password" class="block text-xs font-black uppercase tracking-widest text-gray-900 mb-2">
|
||||
{{ 'label.password'|trans }} _
|
||||
</label>
|
||||
<input id="password" name="_password" type="password" autocomplete="current-password" required
|
||||
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="{{ 'label.password'|trans }}">
|
||||
class="w-full px-4 py-4 border-4 border-gray-900 text-gray-900 font-bold placeholder-gray-400 focus:ring-0 focus:border-indigo-600 shadow-[4px_4px_0px_rgba(0,0,0,1)] transition-all"
|
||||
placeholder="••••••••">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Remember Me & Forgot Password (Optional) #}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center justify-between pt-2">
|
||||
<div class="text-sm">
|
||||
<a href="{{ path('app_forgot_password') }}" class="font-medium text-indigo-600 hover:text-indigo-500">
|
||||
<a href="{{ path('app_forgot_password') }}" class="text-[10px] font-black uppercase underline hover:text-indigo-600 decoration-2">
|
||||
{{ 'link.forgot_password'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# CSRF Token (Important for security) #}
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
|
||||
|
||||
{# Submit Button #}
|
||||
<div>
|
||||
{# BOUTON VALIDER #}
|
||||
<button type="submit"
|
||||
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
class="w-full group relative flex justify-center py-4 px-4 border-4 border-gray-900 text-lg font-black uppercase tracking-widest text-white bg-indigo-600 shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
{{ 'button.sign_in'|trans }}
|
||||
<i class="fas fa-chevron-right ml-3 mt-1 group-hover:translate-x-2 transition-transform"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"name": "E-Cosplay"
|
||||
},
|
||||
"copyrightNotice": "E-Cosplay"
|
||||
}}
|
||||
}
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
@@ -54,48 +54,13 @@
|
||||
"@type": "Brand",
|
||||
"name": "E-COSPLAY"
|
||||
},
|
||||
"shippingDetails": {
|
||||
"@type": "OfferShippingDetails",
|
||||
"shippingRate": {
|
||||
"@type": "MonetaryAmount",
|
||||
"value": 6.00,
|
||||
"currency": "EUR"
|
||||
},
|
||||
"shippingDestination": {
|
||||
"@type": "DefinedRegion",
|
||||
"addressCountry": "FR"
|
||||
},
|
||||
"deliveryTime": {
|
||||
"@type": "ShippingDeliveryTime",
|
||||
"handlingTime": {
|
||||
"@type": "QuantitativeValue",
|
||||
"minValue": 0,
|
||||
"maxValue": 1,
|
||||
"unitCode": "DAY"
|
||||
},
|
||||
"transitTime": {
|
||||
"@type": "QuantitativeValue",
|
||||
"minValue": 1,
|
||||
"maxValue": 5,
|
||||
"unitCode": "DAY"
|
||||
}
|
||||
}
|
||||
},
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"url": "{{ app.request.schemeAndHttpHost }}{{ path('app_product_show',{'slug': (product.name|lower|replace({' ': '-'}))~"-"~product.id}) }}",
|
||||
"priceCurrency": "EUR",
|
||||
"price": "{{ product.price }}",
|
||||
"itemCondition": "https://schema.org/{% if product.state == 'new' %}NewCondition{% else %}UsedCondition{% endif %}",
|
||||
"availability": "https://schema.org/InStock",
|
||||
"hasMerchantReturnPolicy": {
|
||||
"@type": "MerchantReturnPolicy",
|
||||
"applicableCountry": "FR",
|
||||
"returnPolicyCategory": "https://schema.org/{% if product.custom %}MerchantReturnNotPermitted {% else %}MerchantReturnFiniteReturnWindow{% endif %}",
|
||||
"merchantReturnDays": {% if product.custom %}0{%else%}14{% endif %},
|
||||
"returnFees": "https://schema.org/{% if product.custom %}ReturnFeesCustomerResponsibility{%else%}ReturnFeesCustomerResponsibility{% endif %}",
|
||||
"returnMethod": "https://schema.org/ReturnByMail"
|
||||
}
|
||||
"availability": "https://schema.org/InStock"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -103,92 +68,108 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mx-auto p-4 md:p-8 pt-12">
|
||||
<h1 class="text-4xl font-extrabold text-gray-900 mb-8 text-center">
|
||||
<div class="bg-[#fbfbfb] min-h-screen py-12 px-4 italic">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
|
||||
{# HEADER DE LA BOUTIQUE #}
|
||||
<header class="mb-16 text-center">
|
||||
<div class="inline-block bg-indigo-600 text-white px-6 py-2 border-4 border-gray-900 font-black uppercase tracking-tighter text-sm mb-4 skew-x-[-12deg] shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
||||
E-Cosplay Market // Avail_
|
||||
</div>
|
||||
<h1 class="text-6xl md:text-8xl font-black uppercase tracking-tighter leading-none mb-6">
|
||||
{{ 'shop.welcome_title'|trans }}
|
||||
</h1>
|
||||
{# --- 2. CONTENU PRINCIPAL / AFFICHAGE DES PRODUITS --- #}
|
||||
<main>
|
||||
<p class="text-xl text-gray-600 mb-8 text-center">
|
||||
<p class="max-w-2xl mx-auto text-xl font-bold uppercase text-gray-500 leading-tight">
|
||||
{{ 'shop.description'|trans }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
|
||||
{# --- GRILLE DE PRODUITS --- #}
|
||||
<main class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{% for product in featuredProducts %}
|
||||
<div class="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-xl transition duration-300 transform hover:-translate-y-1 border border-gray-100 relative">
|
||||
<div class="group relative bg-white border-4 border-gray-900 shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all flex flex-col h-full">
|
||||
|
||||
{# ÉTIQUETTE PROMO (Absolute positioning) #}
|
||||
{# ÉTIQUETTE PROMO / ETAT #}
|
||||
<div class="absolute -top-4 -right-4 z-10 flex flex-col gap-2">
|
||||
{% if product.promo %}
|
||||
<div class="absolute top-2 left-2 bg-red-600 text-white text-xs font-bold px-3 py-1 rounded-full shadow-lg z-10">
|
||||
<div class="bg-pink-500 text-white text-[10px] font-black px-3 py-1 border-2 border-gray-900 uppercase rotate-12">
|
||||
{{ 'shop.tag_promo'|trans }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Image et Étiquette de Préférence (État) #}
|
||||
<div class="relative">
|
||||
<img src="{{ vich_uploader_asset(product,'image') | imagine_filter('webp')}}" alt="Image de {{ product.name }}" class="w-full h-48 object-cover">
|
||||
|
||||
{# Étiquette de préférence (Neuf/Occasion) #}
|
||||
<div class="absolute bottom-0 right-0 bg-gray-900 text-white text-xs font-semibold px-2 py-1 rounded-tl-lg opacity-80">
|
||||
<div class="bg-yellow-400 text-gray-900 text-[10px] font-black px-2 py-1 border-2 border-gray-900 uppercase -rotate-6">
|
||||
{{ ('shop.state_' ~ product.state)|trans }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
{# NOM #}
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2 truncate">
|
||||
{{ product.name }}
|
||||
</h3>
|
||||
{# IMAGE PRODUIT #}
|
||||
<div class="relative h-64 overflow-hidden border-b-4 border-gray-900 bg-gray-100">
|
||||
<img src="{{ vich_uploader_asset(product,'image') | imagine_filter('webp')}}"
|
||||
alt="{{ product.name }}"
|
||||
class="w-full h-full object-cover grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-500">
|
||||
|
||||
{# TAGS SUPPLÉMENTAIRES #}
|
||||
<div class="flex gap-2 mb-3 flex-wrap">
|
||||
{# TAGS FLOTTANTS #}
|
||||
<div class="absolute bottom-2 left-2 flex gap-2">
|
||||
{% if product.handmade %}
|
||||
<span class="text-xs font-medium bg-green-100 text-green-800 px-2 py-0.5 rounded-full">
|
||||
<span class="bg-green-400 text-black text-[9px] font-black border-2 border-black px-2 py-0.5 uppercase">
|
||||
{{ 'shop.tag_handmade'|trans }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{# Exemple de tag "Sur-mesure" basé sur une condition #}
|
||||
{% if product.id == 1 %}
|
||||
<span class="text-xs font-medium bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full">
|
||||
<span class="bg-blue-400 text-black text-[9px] font-black border-2 border-black px-2 py-0.5 uppercase">
|
||||
{{ 'shop.tag_custom'|trans }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# DESCRIPTION COURTE #}
|
||||
<p class="text-sm text-gray-600 mb-4 line-clamp-2" title="{{ product.shortDescription }}">
|
||||
{# INFOS PRODUIT #}
|
||||
<div class="p-6 flex-grow flex flex-col">
|
||||
<h3 class="text-2xl font-black uppercase tracking-tighter leading-none mb-3 truncate">
|
||||
{{ product.name }}
|
||||
</h3>
|
||||
|
||||
<p class="text-xs font-bold text-gray-500 uppercase leading-snug mb-6 line-clamp-3 italic">
|
||||
{{ product.shortDescription }}
|
||||
</p>
|
||||
|
||||
{# PRIX TTC et Bouton #}
|
||||
<div class="flex justify-between items-center pt-3 border-t border-gray-100">
|
||||
<span class="text-2xl font-extrabold text-indigo-600">
|
||||
{{ product.price | number_format(2, ',', ' ') }} € TTC
|
||||
<div class="mt-auto pt-6 border-t-4 border-dashed border-gray-100 flex justify-between items-center">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-black text-gray-400 uppercase tracking-widest">Price_</span>
|
||||
<span class="text-2xl font-black text-indigo-600">
|
||||
{{ product.price | number_format(2, ',', ' ') }}€
|
||||
</span>
|
||||
<a href="{{ path('app_product_show', {'slug': (product.name|lower|replace({' ': '-'}))~"-"~product.id}) }}" class="text-indigo-600 hover:text-indigo-800 text-sm font-semibold inline-flex items-center group">
|
||||
{{ 'shop_more'|trans }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right ml-1 group-hover:translate-x-0.5 transition-transform"><path d="m9 18l6-6-6-6"/></svg>
|
||||
</div>
|
||||
|
||||
<a href="{{ path('app_product_show', {'slug': (product.name|lower|replace({' ': '-'}))~"-"~product.id}) }}"
|
||||
class="w-12 h-12 bg-gray-900 text-white flex items-center justify-center border-2 border-gray-900 shadow-[4px_4px_0px_#ec4899] group-hover:shadow-none group-hover:translate-x-1 group-hover:translate-y-1 transition-all">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</main>
|
||||
|
||||
{# --- NOTE DE VENTE TERMINAL STYLE --- #}
|
||||
<div class="mt-20 p-8 bg-gray-900 border-8 border-indigo-600 shadow-[16px_16px_0px_rgba(79,70,229,0.2)]">
|
||||
<div class="flex flex-col md:flex-row gap-8 items-center">
|
||||
<div class="flex-shrink-0 w-20 h-20 bg-indigo-600 text-white flex items-center justify-center border-4 border-white rotate-3">
|
||||
<i class="fas fa-heart-handshake text-3xl"></i>
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<h3 class="text-2xl font-black text-white uppercase tracking-tighter mb-4 italic">
|
||||
<span class="text-indigo-400">#</span> {{ 'shop.sales_note_title'|trans }}
|
||||
</h3>
|
||||
<div class="text-gray-300 font-bold text-sm leading-relaxed uppercase opacity-90">
|
||||
{{ 'shop.sales_note_main'|trans|raw }}
|
||||
</div>
|
||||
<p class="mt-4 text-[10px] font-black text-indigo-400 uppercase tracking-[0.2em]">
|
||||
>> {{ 'shop.sales_note_details'|trans }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 mb-8 p-6 bg-indigo-50 border-l-4 border-indigo-500 rounded-lg shadow-inner">
|
||||
<h3 class="text-xl font-bold text-indigo-800 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-heart-handshake mr-2"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5"/><path d="M12 20v-6h3a1 1 0 0 1 1 1v4h1a2 2 0 0 1 2 2v0a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v0a2 2 0 0 1 2-2h1.5l1.5-4h2.8l-3.5 6H22"/></svg>
|
||||
{{ 'shop.sales_note_title'|trans }}
|
||||
</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
{{ 'shop.sales_note_main'|trans|raw }}
|
||||
</p>
|
||||
<p class="mt-3 text-sm italic text-gray-600">
|
||||
{{ 'shop.sales_note_details'|trans }}
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}{{'who_page.title'|trans({'%s': city})}}{% endblock %}
|
||||
{% block meta_description %}{{'who_page.description'|trans({'%s': city})}}{% endblock %}
|
||||
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_who',{city:city}) }}" />{% endblock %}
|
||||
{% block canonical_url %}<link rel="canonical" href="{{ url('app_who',{city:city}) }}?lang={{ app.request.locale }}" />{% endblock %}
|
||||
|
||||
{% block breadcrumb_schema %}
|
||||
<script type="application/ld+json">
|
||||
@@ -29,7 +29,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main id="who-city" class="max-w-6xl mx-auto px-4 py-12 font-sans text-gray-800 bg-[#fbfbfb]">
|
||||
<div id="who-city" class="max-w-6xl mx-auto px-4 py-12 font-sans text-gray-800 bg-[#fbfbfb]">
|
||||
|
||||
{# HEADER : STYLE IMPACT ESPORT #}
|
||||
<header class="mb-20 relative py-10">
|
||||
@@ -110,22 +110,21 @@
|
||||
</section>
|
||||
|
||||
{# CTA : BOUTON MASSIVE #}
|
||||
<footer class="mt-32 text-center relative">
|
||||
<div class="absolute inset-0 flex items-center justify-center opacity-[0.03] -z-10">
|
||||
<span class="text-9xl font-black uppercase italic tracking-tighter">JOIN US</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-24 text-center relative">
|
||||
<a href="{{ path('app_contact') }}" class="group relative inline-block">
|
||||
<div class="absolute inset-0 bg-gray-900 translate-x-2 translate-y-2 group-hover:translate-x-0 group-hover:translate-y-0 transition-transform duration-200"></div>
|
||||
<div class="relative bg-indigo-600 text-white px-12 py-5 font-black uppercase italic tracking-widest border-2 border-gray-900 hover:bg-yellow-500 hover:text-gray-900 transition-colors duration-200">
|
||||
{{ 'home_cta.contact'|trans }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{{ path('app_recruit') }}" class="ml-2 group relative inline-block">
|
||||
<div class="absolute inset-0 bg-gray-900 translate-x-2 translate-y-2 group-hover:translate-x-0 group-hover:translate-y-0 transition-transform duration-200"></div>
|
||||
<div class="relative bg-indigo-600 text-white px-12 py-5 font-black uppercase italic tracking-widest border-2 border-gray-900 hover:bg-yellow-500 hover:text-gray-900 transition-colors duration-200">
|
||||
{{ 'home_cta.button'|trans }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="mt-12 text-4xl font-black italic tracking-tighter text-gray-900 opacity-20 uppercase">
|
||||
{{ 'brand_name'|trans }}
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -576,10 +576,6 @@ home_partners.pretitle: "我们的盟友"
|
||||
home_partners.title: "合作协会"
|
||||
home_partners.subtitle: "我们与拥有共同价值观的组织携手合作,以丰富您的体验。"
|
||||
breadcrumb.who: "我们的协会在 %s"
|
||||
who_page.title: "我们的协会在 %s"
|
||||
who_page.description: "发现我们是谁,我们的价值观和我们在 %s 的活动。"
|
||||
who_page.city_pretitle: "我们位于:"
|
||||
who_page.activity_intro: "我们的主要活动包括:"
|
||||
timeline_title: "我们的时间线"
|
||||
event_creation_date: "2025年3月15日"
|
||||
event_creation_text_title: "正式成立"
|
||||
@@ -1038,3 +1034,101 @@ cookie_control_desc: "您可以通过浏览器设置禁用 Cookie,但这可能
|
||||
cookie_cnil_btn: "管理 Cookie (CNIL 指南)"
|
||||
cookie_consent_title: "同意声明"
|
||||
cookie_consent_footer: "继续浏览即表示您同意使用服务运行所必需的 Cookie。"
|
||||
|
||||
# --- 滚动文字 (Rolling Text) ---
|
||||
home_roll: "打破现实 // Cosplay 文化 // 加入公会 // 艺术与激情"
|
||||
page_roll: "展示你的手艺 // 高水平 Cosplay // 社区驱动"
|
||||
event_roll: "下个任务 // 全新活动 // 保持关注 // 加入社区"
|
||||
doc_roll: "官方文档 // 法律合规 // 透明公开"
|
||||
dons_roll: "赋能社区 // 支持匠心工艺 // 每一份捐赠都很重要 // 装备起来"
|
||||
contact_roll: "联系团队 // 在线支持 // 招募开启 // 加入我们的战队"
|
||||
|
||||
# --- 基础页面 & 导航 ---
|
||||
Nous rejoindre: '加入公会'
|
||||
who_page.title: "我们在 %s 的协会"
|
||||
who_page.description: "探索我们是谁、我们的价值观以及我们在 %s 的活动。"
|
||||
who_page.city_pretitle: "我们的所在地:"
|
||||
who_page.activity_intro: "我们的核心活动包括:"
|
||||
home_cta.contact: "联系我们"
|
||||
|
||||
# --- 活动标签 ---
|
||||
concours: "比赛"
|
||||
ouverts: "开放中"
|
||||
craftsmanship: "匠心工艺"
|
||||
acting: "表演"
|
||||
ateliers: "工作坊"
|
||||
coshopital: "Cosplay 医院"
|
||||
reparation: "修复/维修"
|
||||
|
||||
# --- EPAGE 创作者部分 ---
|
||||
epage_cosplay: 'EPAGE - Cosplayer'
|
||||
hero.heading: "创新助你成就梦想"
|
||||
hero.subheading: "探索我们的专业技术如何将你的挑战转化为持续增长的机遇。"
|
||||
hero.cta_main: "探索我们的方案"
|
||||
hero.cta_secondary: "预约咨询"
|
||||
page.title: "EPAGE - Cosplayer"
|
||||
|
||||
creators:
|
||||
title_plural: "见见选择了 Epage 的 Cosplayer。|见见这 %count% 位选择了 Epage 的 Cosplayers。"
|
||||
intro_text: "见见这些选择通过 Epage 管理、资助并分享热情的艺术家们。"
|
||||
social_label: "社交媒体"
|
||||
button: "查看更多"
|
||||
empty_list: "目前这里没有显示的创作者"
|
||||
|
||||
cta_creator:
|
||||
heading: "你是 Cosplayer 吗?"
|
||||
subtext: "想要一个专属页面来展示你的作品并与粉丝互动吗?"
|
||||
button: "探索面向创作者的 Epage"
|
||||
|
||||
# --- 表单 & 注册 ---
|
||||
epage_onboard:
|
||||
name: "姓"
|
||||
surname: "名"
|
||||
email: "电子邮箱"
|
||||
birdth: "出生日期"
|
||||
nameCosplayer: "Cosplayer 艺名 / 昵称"
|
||||
description: "活动描述 (最多 500 字)"
|
||||
linkFacebook: "Facebook 链接 (完整 URL)"
|
||||
linkInstagram: "Instagram 链接 (完整 URL)"
|
||||
linkTiktok: "TikTok 链接 (完整 URL)"
|
||||
linkX: "X (Twitter) 链接 (完整 URL)"
|
||||
useDomain: "使用自定义域名"
|
||||
domain: "期望的域名 (例如: e-cosplay.fr)"
|
||||
avatar: "个人头像"
|
||||
avatar_label: "个人头像最大 100MB - 格式:png, jpg, jpeg, webp"
|
||||
|
||||
onboarding:
|
||||
form:
|
||||
submit_button: "提交完整表单"
|
||||
section4:
|
||||
title: "4. 自定义链接 (EPage)"
|
||||
section3:
|
||||
description: "请添加你主要社交账号的完整链接。"
|
||||
title: "3. 社交媒体链接"
|
||||
title: "EPage 申请表单"
|
||||
section1:
|
||||
title: "1. 个人信息"
|
||||
description: "输入你的联系方式和个人详情。"
|
||||
section2:
|
||||
title: "2. Cosplay 资料"
|
||||
description: "关于你的活动和昵称的详细信息。"
|
||||
|
||||
page_presentation:
|
||||
breadcrumb: "EPAGE - Cosplayer"
|
||||
|
||||
# --- 管理与联系 ---
|
||||
doc_ag_at: "股东大会日期"
|
||||
contact_name_pl: "姓"
|
||||
contact_surname_pl: "名"
|
||||
contact_email_pl: "电子邮箱"
|
||||
contact_email_subject: "主题"
|
||||
contact_email_message: "留言内容"
|
||||
|
||||
# --- 详细标签 ---
|
||||
Prénom: "名"
|
||||
Nom de famille: "姓"
|
||||
Adresse e-mail: "电子邮箱地址"
|
||||
Sujet: "主题"
|
||||
Téléphone (facultatif): "电话 (选填)"
|
||||
Votre message: "你的留言"
|
||||
partner_w: '官方网站'
|
||||
|
||||
@@ -1107,3 +1107,100 @@ cookie_control_desc: "You can block cookies via your browser settings, but this
|
||||
cookie_cnil_btn: "How to control cookies (CNIL)"
|
||||
cookie_consent_title: "Consent"
|
||||
cookie_consent_footer: "By continuing to browse, you accept the use of cookies necessary for the operation of the service."
|
||||
home_roll: "Crafting Reality // Cosplay Culture // Join the Guild // Artistry & Passion"
|
||||
Nous rejoindre: 'Join the guild'
|
||||
|
||||
|
||||
who_page.title: "Our Association in %s"
|
||||
who_page.description: "Discover who we are, our values, and our activities in %s."
|
||||
who_page.city_pretitle: "We are located in:"
|
||||
who_page.activity_intro: "Our flagship activities include:"
|
||||
home_cta.contact: "Contact us"
|
||||
concours: "contests"
|
||||
ouverts: "open"
|
||||
craftsmanship: "craftsmanship" # (Identique en anglais)
|
||||
acting: "acting" # (Identique en anglais)
|
||||
ateliers: "workshops"
|
||||
coshopital: "coshopital" # (Nom propre/concept, se garde tel quel)
|
||||
reparation: "repairs"
|
||||
|
||||
epage_cosplay: 'EPAGE - Cosplayer'
|
||||
hero.heading: "Innovation Powering Your Success"
|
||||
hero.subheading: "Discover how our expertise transforms your challenges into sustainable growth opportunities."
|
||||
hero.cta_main: "Discover Our Solutions"
|
||||
hero.cta_secondary: "Book an Appointment"
|
||||
page.title: "EPAGE - Cosplayer"
|
||||
|
||||
creators:
|
||||
# Pluralisation pour le titre
|
||||
title_plural: "Meet the cosplayer who chose Epage.|Meet the %count% cosplayers who chose Epage."
|
||||
intro_text: "Meet the artists who chose Epage to manage, fund, and share their passion."
|
||||
social_label: "Social Media"
|
||||
button: "View More"
|
||||
empty_list: "No creators are currently listed here"
|
||||
|
||||
cta_creator:
|
||||
heading: "Are you a cosplayer?"
|
||||
subtext: "Want a dedicated page to showcase your cosplays and engage with your fans?"
|
||||
button: "Discover Epage for Creators"
|
||||
|
||||
epage_onboard:
|
||||
name: "Last Name"
|
||||
surname: "First Name"
|
||||
email: "Email"
|
||||
birdth: "Date of Birth"
|
||||
nameCosplayer: "Cosplayer Handle / Stage Name"
|
||||
description: "Activity Description (max 500 characters)"
|
||||
linkFacebook: "Facebook Link (Full URL)"
|
||||
linkInstagram: "Instagram Link (Full URL)"
|
||||
linkTiktok: "TikTok Link (Full URL)"
|
||||
linkX: "X (Twitter) Link (Full URL)"
|
||||
useDomain: "Use a custom domain name"
|
||||
domain: "Desired Domain Name (e.g., e-cosplay.fr)"
|
||||
avatar: "Profile Picture"
|
||||
avatar_label: "Profile picture (max 100MB) - Format: png, jpg, jpeg, webp"
|
||||
|
||||
onboarding:
|
||||
form:
|
||||
submit_button: "Submit Complete Form"
|
||||
section4:
|
||||
title: "4. Custom Link (EPage)"
|
||||
section3:
|
||||
description: "Add full links to your main profiles (Full URL)."
|
||||
title: "3. Social Media Links"
|
||||
title: "EPage Request Form"
|
||||
section1:
|
||||
title: "1. Personal Information"
|
||||
description: "Enter your contact and personal details."
|
||||
section2:
|
||||
title: "2. Cosplay Profile"
|
||||
description: "Details about your activity and handle."
|
||||
|
||||
page_presentation:
|
||||
breadcrumb: "EPAGE - Cosplayer"
|
||||
|
||||
# --- Textes Défilants (Marquee / Roll) ---
|
||||
page_roll: "Showcase Your Craft // High-Level Cosplay // Community Driven"
|
||||
event_roll: "Next Mission // New Event // Stay Tuned // Join the Community"
|
||||
doc_roll: "Official Documentation // Legal Compliance // Transparency"
|
||||
dons_roll: "Empower the Community // Support Craftsmanship // Every Donation Counts // Gear Up"
|
||||
contact_roll: "Contact the Team // Online Support // Recruitment Open // Join Our Squad"
|
||||
|
||||
# --- Administration ---
|
||||
doc_ag_at: "General Assembly Date"
|
||||
|
||||
# --- Champs de Formulaire (Placeholders & Labels) ---
|
||||
contact_name_pl: "Last Name"
|
||||
contact_surname_pl: "First Name"
|
||||
contact_email_pl: "Email"
|
||||
contact_email_subject: "Subject"
|
||||
contact_email_message: "Message"
|
||||
|
||||
# --- Labels détaillés ---
|
||||
Prénom: "First Name"
|
||||
Nom de famille: "Last Name"
|
||||
Adresse e-mail: "Email Address"
|
||||
Sujet: "Subject"
|
||||
Téléphone (facultatif): "Phone (Optional)"
|
||||
Votre message: "Your Message"
|
||||
partner_w: 'Website'
|
||||
|
||||
@@ -1104,3 +1104,39 @@ cookie_control_desc: "Puedes bloquear las cookies a través de los ajustes de tu
|
||||
cookie_cnil_btn: "Controlar las cookies (CNIL)"
|
||||
cookie_consent_title: "Consentimiento"
|
||||
cookie_consent_footer: "Al continuar con tu navegación, aceptas el uso de las cookies necesarias para el funcionamiento del servicio."
|
||||
# --- Textos Desplazables (Marquee / Roll) ---
|
||||
home_roll: "Creando la Realidad // Cultura Cosplay // Únete al Gremio // Arte y Pasión"
|
||||
page_roll: "Destaca tu Artesanía // Cosplay de Alto Nivel // Centrado en la Comunidad"
|
||||
event_roll: "Próxima Misión // Nuevo Evento // Mantente Conectado // Únete a la Comunidad"
|
||||
doc_roll: "Documentación Oficial // Cumplimiento Legal // Transparencia"
|
||||
dons_roll: "Potencia a la Comunidad // Apoya la Artesanía // Cada Donación Cuenta // Equípate"
|
||||
contact_roll: "Contacta al Equipo // Soporte en Línea // Reclutamiento Abierto // Únete a nuestro Squad"
|
||||
|
||||
home_cta.contact: "Contáctanos"
|
||||
|
||||
# --- Etiquetas de Actividad ---
|
||||
concours: "concursos"
|
||||
ouverts: "abiertos"
|
||||
craftsmanship: "craftsmanship" # (Término técnico muy usado, o "artesanía")
|
||||
acting: "acting" # (Término técnico, o "interpretación")
|
||||
ateliers: "talleres"
|
||||
coshopital: "coshopital"
|
||||
reparation: "reparaciones"
|
||||
|
||||
|
||||
# --- Administración y Contacto ---
|
||||
doc_ag_at: "Fecha de la Asamblea General"
|
||||
contact_name_pl: "Apellidos"
|
||||
contact_surname_pl: "Nombre"
|
||||
contact_email_pl: "Correo electrónico"
|
||||
contact_email_subject: "Asunto"
|
||||
contact_email_message: "Mensaje"
|
||||
|
||||
# --- Etiquetas Detalladas ---
|
||||
Prénom: "Nombre"
|
||||
Nom de famille: "Apellidos"
|
||||
Adresse e-mail: "Correo electrónico"
|
||||
Sujet: "Asunto"
|
||||
Téléphone (facultatif): "Teléfono (opcional)"
|
||||
Votre message: "Tu mensaje"
|
||||
partner_w: 'Sitio web'
|
||||
|
||||
@@ -464,6 +464,7 @@ home_activities.diversity_text: "Nous sommes un pilier de soutien pour tous, inc
|
||||
home_cta.title: "Prêt à Partager votre Passion ?"
|
||||
home_cta.subtitle: "Adhérez aujourd'hui et faites partie de l'aventure."
|
||||
home_cta.button: "Adhérer Maintenant"
|
||||
home_cta.contact: "Nous contacter"
|
||||
home_page.description: "Bienvenue dans la communauté e-cosplay ! Votre référence pour les concours, ateliers de craft, et l'entraide. Le cosplay est pour tous, rejoignez notre passion !. Basée dans les Hauts-de-France, et située idéalement proche de Tergnier, Saint-Quentin et Laon"
|
||||
members_description: 'Découvrez les membres actifs de notre association de cosplay ! Rencontrez les bénévoles, juges et organisateurs qui donnent vie à nos événements et activités.'
|
||||
contact_page.description: 'Contactez-nous pour toute question sur le cosplay, les événements ou les partenariats ! Formulaire de contact direct et emails des fondatrices disponibles ici.'
|
||||
@@ -1124,3 +1125,21 @@ cookie_consent_title: "Consentement"
|
||||
cookie_consent_footer: "En continuant votre navigation, vous acceptez l'usage des cookies nécessaires au fonctionnement du service."
|
||||
partner_w: 'Site internet'
|
||||
partner_l: 'Facebook'
|
||||
home_roll: "Créer la réalité // Culture cosplay // Rejoignez l'association // Art et passion"
|
||||
page_roll: "Mettez en valeur votre art // Cosplay de haut niveau // Axé sur la communauté"
|
||||
event_roll: 'Prochaine mission // Nouvel événement // Restez connectés // Rejoignez la communauté'
|
||||
doc_ag_at: "Date de l'assemblée générale"
|
||||
doc_roll: 'Documentation officielle // Conformité légale // Transparence'
|
||||
dons_roll: "Faites progresser la communauté // Soutenez l'artisanat // Chaque don compte // Équipez-vous"
|
||||
contact_roll: "Contacter l'équipe // Assistance en ligne // Recrutement ouvert // Rejoignez notre équipe"
|
||||
contact_name_pl: Nom
|
||||
contact_surname_pl: Prénom
|
||||
contact_email_pl: Email
|
||||
contact_email_subject: Sujet
|
||||
contact_email_message: Message
|
||||
Prénom: Prénom
|
||||
Nom de famille: Nom de famille
|
||||
Adresse e-mail: Adresse e-mail
|
||||
Sujet: Sujet
|
||||
Téléphone (facultatif): Téléphone (facultatif)
|
||||
Votre message: Votre message
|
||||
|
||||
@@ -1104,3 +1104,39 @@ cookie_control_desc: "Sie können Cookies über Ihre Browsereinstellungen blocki
|
||||
cookie_cnil_btn: "Cookies kontrollieren (CNIL)"
|
||||
cookie_consent_title: "Einwilligung"
|
||||
cookie_consent_footer: "Durch die Fortsetzung Ihres Besuchs akzeptieren Sie die Verwendung von Cookies, die für den Betrieb des Dienstes erforderlich sind."
|
||||
# --- Marquee / Roll Texte (Lauftexte) ---
|
||||
home_roll: "Realität erschaffen // Cosplay-Kultur // Tritt der Gilde bei // Kunst & Leidenschaft"
|
||||
page_roll: "Präsentiere dein Handwerk // High-Level Cosplay // Community-Fokus"
|
||||
event_roll: "Nächste Mission // Neues Event // Stay Tuned // Werde Teil der Community"
|
||||
doc_roll: "Offizielle Dokumentation // Rechtliche Konformität // Transparenz"
|
||||
dons_roll: "Stärke die Community // Unterstütze das Handwerk // Jede Spende zählt // Gear Up"
|
||||
contact_roll: "Kontaktiere das Team // Online-Support // Rekrutierung offen // Tritt unserem Squad bei"
|
||||
|
||||
home_cta.contact: "Kontaktiere uns"
|
||||
|
||||
# --- Aktivitäts-Labels ---
|
||||
concours: "Wettbewerbe"
|
||||
ouverts: "Offen"
|
||||
craftsmanship: "Handwerkskunst"
|
||||
acting: "Acting"
|
||||
ateliers: "Workshops"
|
||||
coshopital: "Cos-Hospital"
|
||||
reparation: "Reparaturen"
|
||||
|
||||
|
||||
# --- Verwaltung & Kontakt ---
|
||||
doc_ag_at: "Datum der Generalversammlung"
|
||||
contact_name_pl: "Nachname"
|
||||
contact_surname_pl: "Vorname"
|
||||
contact_email_pl: "E-Mail"
|
||||
contact_email_subject: "Betreff"
|
||||
contact_email_message: "Nachricht"
|
||||
|
||||
# --- Detaillierte Labels ---
|
||||
Prénom: "Vorname"
|
||||
Nom de famille: "Nachname"
|
||||
Adresse e-mail: "E-Mail-Adresse"
|
||||
Sujet: "Betreff"
|
||||
Téléphone (facultatif): "Telefon (optional)"
|
||||
Votre message: "Deine Nachricht"
|
||||
partner_w: 'Webseite'
|
||||
|
||||
Reference in New Issue
Block a user