feat(Product): Ajoute les dimensions largeur, hauteur et profondeur au produit.
```
This commit is contained in:
Serreau Jovann
2026-01-27 09:08:14 +01:00
parent 6466947c89
commit 216ef477ac
5 changed files with 276 additions and 150 deletions

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260127075804 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE product ADD dim_h DOUBLE PRECISION DEFAULT NULL');
$this->addSql('ALTER TABLE product ADD dim_p DOUBLE PRECISION DEFAULT NULL');
$this->addSql('ALTER TABLE product RENAME COLUMN installation TO dim_w');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE product DROP dim_h');
$this->addSql('ALTER TABLE product DROP dim_p');
$this->addSql('ALTER TABLE product RENAME COLUMN dim_w TO installation');
}
}

View File

@@ -78,6 +78,15 @@ class Product
#[ORM\OneToMany(targetEntity: ProductDoc::class, mappedBy: 'product')] #[ORM\OneToMany(targetEntity: ProductDoc::class, mappedBy: 'product')]
private Collection $productDocs; private Collection $productDocs;
#[ORM\Column]
private ?float $dimW = null;
#[ORM\Column(nullable: true)]
private ?float $dimH = null;
#[ORM\Column(nullable: true)]
private ?float $dimP = null;
public function __construct() public function __construct()
{ {
$this->devisLines = new ArrayCollection(); $this->devisLines = new ArrayCollection();
@@ -354,4 +363,40 @@ class Product
return $this; return $this;
} }
public function getDimW(): ?float
{
return $this->dimW;
}
public function setDimW(float $dimW): static
{
$this->dimW = $dimW;
return $this;
}
public function getDimH(): ?float
{
return $this->dimH;
}
public function setDimH(?float $dimH): static
{
$this->dimH = $dimH;
return $this;
}
public function getDimP(): ?float
{
return $this->dimP;
}
public function setDimP(?float $dimP): static
{
$this->dimP = $dimP;
return $this;
}
} }

View File

@@ -59,6 +59,22 @@ class ProductType extends AbstractType
'required' => true, 'required' => true,
'html5' => true, 'html5' => true,
]) ])
->add('dimW',NumberType::class,[
'label' => 'Largeur',
'required' => true,
'html5' => true,
])
->add('dimP',NumberType::class,[
'label' => 'Longeur',
'required' => true,
'html5' => true,
])
->add('dimH',NumberType::class,[
'label' => 'Hauteur',
'required' => true,
'html5' => true,
])
->add('imageFile',FileType::class,[ ->add('imageFile',FileType::class,[
'label' => 'Image du produit', 'label' => 'Image du produit',
'required' => false, 'required' => false,

View File

@@ -108,6 +108,48 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{# 02. DIMENSIONS DU PRODUIT #}
<div class="mt-8 backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] p-8 shadow-2xl">
<h3 class="text-lg font-bold text-white mb-6 flex items-center">
<span class="w-8 h-8 bg-blue-600/20 text-blue-500 rounded-lg flex items-center justify-center mr-3 text-[10px] font-black">02</span>
Dimensions de la Structure
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
{# Largeur #}
<div class="relative group">
{{ form_label(form.dimW, 'Largeur (m)', {'label_attr': {'class': 'text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<span class="text-slate-500 text-[10px] font-bold italic">W</span>
</div>
{{ form_widget(form.dimW, {'attr': {'placeholder': '0.00', 'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white font-mono focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 pl-10 pr-5'}}) }}
</div>
</div>
{# Longueur #}
<div class="relative group">
{{ form_label(form.dimP, 'Longueur (m)', {'label_attr': {'class': 'text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<span class="text-slate-500 text-[10px] font-bold italic">L</span>
</div>
{{ form_widget(form.dimP, {'attr': {'placeholder': '0.00', 'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white font-mono focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 pl-10 pr-5'}}) }}
</div>
</div>
{# Hauteur #}
<div class="relative group">
{{ form_label(form.dimH, 'Hauteur (m)', {'label_attr': {'class': 'text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] ml-1 mb-2 block'}}) }}
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<span class="text-slate-500 text-[10px] font-bold italic">H</span>
</div>
{{ form_widget(form.dimH, {'attr': {'placeholder': '0.00', 'class': 'w-full bg-slate-900/50 border-white/5 rounded-2xl text-white font-mono focus:ring-blue-500/20 focus:border-blue-500 transition-all py-3.5 pl-10 pr-5'}}) }}
</div>
</div>
</div>
</div>
</div> </div>
{# COLONNE DROITE : TARIFICATION #} {# COLONNE DROITE : TARIFICATION #}

View File

@@ -8,33 +8,30 @@
Tarif : {{ product.priceDay }}€/jour. Installation et livraison dans l'Aisne (02). Tarif : {{ product.priceDay }}€/jour. Installation et livraison dans l'Aisne (02).
{{ product.description|striptags|slice(0, 130) }}... Réservez votre date ! {{ product.description|striptags|slice(0, 130) }}... Réservez votre date !
{% endblock %} {% endblock %}
{% block jsonld %} {% block jsonld %}
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@context": "https://schema.org/", "@context": "https://schema.org/",
"@type": "Product", "@type": "Product",
"name": "{{ product.name }}", "name": "{{ product.name }}",
"image": [ "image": ["{% if product.imageName %}{{ absolute_url(vich_uploader_asset(product, 'imageFile')) }}{% else %}{{ absolute_url(asset('provider/images/favicon.png')) }}{% endif %}"],
"{% if product.imageName %}{{ absolute_url(vich_uploader_asset(product, 'imageFile')) }}{% else %}{{ absolute_url(asset('provider/images/favicon.png')) }}{% endif %}" "description": "{{ product.description|striptags|slice(0, 160) }}",
], "sku": "{{ product.ref }}",
"description": "{{ product.description|striptags|slice(0, 160) }}", "brand": { "@type": "Brand", "name": "Ludikevent" },
"sku": "{{ product.ref }}", "offers": {
"brand": { "@type": "Offer",
"@type": "Brand", "url": "{{ app.request.uri }}",
"name": "Ludikevent" "priceCurrency": "EUR",
}, "price": "{{ product.priceDay }}",
"offers": { "availability": "https://schema.org/InStock",
"@type": "Offer", "itemCondition": "https://schema.org/UsedCondition",
"url": "{{ app.request.uri }}", "priceValidUntil": "{{ "now"|date_modify("+1 year")|date("Y-m-d") }}"
"priceCurrency": "EUR", }
"price": "{{ product.priceDay }}", }
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/UsedCondition",
"priceValidUntil": "{{ "now"|date_modify("+1 year")|date("Y-m-d") }}"
}
}
</script> </script>
{% endblock %} {% endblock %}
{% block breadcrumb_json %} {% block breadcrumb_json %}
,{ ,{
"@type": "ListItem", "@type": "ListItem",
@@ -50,7 +47,6 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{# --- TRACKING ÉVÉNEMENT --- #}
<utm-event event="view_product" data="{{ product.json }}"></utm-event> <utm-event event="view_product" data="{{ product.json }}"></utm-event>
<div class="min-h-screen bg-white font-sans antialiased"> <div class="min-h-screen bg-white font-sans antialiased">
@@ -81,19 +77,19 @@
{# --- COLONNE GAUCHE : VISUEL --- #} {# --- COLONNE GAUCHE : VISUEL --- #}
<div class="sticky top-24"> <div class="sticky top-24">
<div class="relative overflow-hidden rounded-[4rem] bg-slate-50 aspect-[4/5]"> <div class="relative overflow-hidden rounded-[3rem] md:rounded-[4rem] bg-slate-50 aspect-[4/5] shadow-inner">
{% if product.imageName %} {% if product.imageName %}
<img src="{{ vich_uploader_asset(product,'imageFile') | imagine_filter('webp') }}" <img src="{{ vich_uploader_asset(product,'imageFile') | imagine_filter('webp') }}"
alt="{{ product.name }}" alt="{{ product.name }}"
class="w-full h-full object-contain"> class="w-full h-full object-contain p-4 md:p-8">
{% else %} {% else %}
<div class="h-full flex flex-col items-center justify-center p-12 text-center opacity-50"> <div class="h-full flex flex-col items-center justify-center p-12 text-center opacity-50">
<img src="{{ asset('provider/images/favicon.png') }}" alt="Ludik Event" class="w-75 h-75"> <img src="{{ asset('provider/images/favicon.png') }}" alt="Ludik Event" class="w-48">
</div> </div>
{% endif %} {% endif %}
<div class="absolute top-8 left-8"> <div class="absolute top-6 left-6 md:top-8 md:left-8">
<span class="bg-[#f39e36] text-white px-6 py-2 rounded-2xl text-[10px] font-black uppercase tracking-widest shadow-xl"> <span class="bg-[#f39e36] text-white px-4 py-2 md:px-6 md:py-2 rounded-2xl text-[9px] md:text-[10px] font-black uppercase tracking-widest shadow-xl">
{{ product.category }} {{ product.category }}
</span> </span>
</div> </div>
@@ -102,191 +98,182 @@
{# --- COLONNE DROITE : CONTENU --- #} {# --- COLONNE DROITE : CONTENU --- #}
<div class="flex flex-col h-full py-4"> <div class="flex flex-col h-full py-4">
<div class="mb-12"> <div class="mb-10 md:mb-12">
<span class="text-[12px] font-black text-slate-300 uppercase tracking-[0.4em] mb-4 block italic"> <span class="text-[11px] md:text-[12px] font-black text-slate-300 uppercase tracking-[0.4em] mb-4 block italic text-center md:text-left">
Référence : {{ product.ref }} Référence : {{ product.ref }}
</span> </span>
<h1 class="text-6xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.85] mb-10"> <h1 class="text-5xl md:text-8xl font-black text-slate-900 uppercase italic tracking-tighter leading-[0.9] mb-10 text-center md:text-left">
{{ product.name }} {{ product.name }}
</h1> </h1>
<div class="space-y-6"> <div class="flex flex-col items-center md:items-start space-y-6">
{# Prix principal #} {# Prix principal #}
<div class="flex items-baseline gap-4"> <div class="flex flex-col md:flex-row items-center md:items-baseline gap-2 md:gap-4 text-center md:text-left">
<span class="text-6xl font-black text-[#f39e36] ">{{ product.priceDay }}€</span> <span class="text-6xl md:text-7xl font-black text-[#f39e36] leading-none">{{ product.priceDay }}€</span>
<span class="text-sm font-bold text-slate-400 uppercase tracking-widest italic">La première journée</span> <span class="text-[10px] md:text-sm font-bold text-slate-400 uppercase tracking-widest italic">La première journée</span>
</div> </div>
{# Prix supplémentaire #} {# Grille de badges Tarifs/Caution #}
<div class="flex items-center gap-3 bg-slate-50 self-start px-5 py-3 rounded-2xl border border-slate-100 w-fit"> <div class="flex flex-wrap justify-center md:justify-start gap-3 w-full">
<span class="text-2xl font-black text-slate-900 italic">+ {{ product.priceSup }}€</span> <div class="flex items-center gap-3 bg-slate-50 px-5 py-3 rounded-2xl border border-slate-100 shadow-sm">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest italic">Par journée supplémentaire</span> <span class="text-xl md:text-2xl font-black text-slate-900 italic">+ {{ product.priceSup }}</span>
</div> <span class="text-[9px] font-black text-slate-400 uppercase tracking-widest italic">Jour supplémentaire</span>
<div class="flex items-center gap-3 bg-slate-50 self-start px-5 py-3 rounded-2xl border border-slate-100 w-fit"> </div>
<span class="text-2xl font-black text-slate-900 italic">{{ product.caution }}€</span> <div class="flex items-center gap-3 bg-slate-50 px-5 py-3 rounded-2xl border border-slate-100 shadow-sm">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest italic">LA CAUTION</span> <span class="text-xl md:text-2xl font-black text-slate-900 italic">{{ product.caution }}</span>
<span class="text-[9px] font-black text-slate-400 uppercase tracking-widest italic">LA CAUTION</span>
</div>
</div> </div>
<div class="mt-3 max-w-sm p-4 rounded-2xl bg-white/40 border border-slate-200/60 backdrop-blur-sm shadow-sm"> {# Message Caution Stripe #}
<div class="max-w-sm p-4 rounded-2xl bg-white/40 border border-slate-200/60 backdrop-blur-sm shadow-sm">
<div class="flex gap-3"> <div class="flex gap-3">
<svg class="w-5 h-5 text-blue-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 text-blue-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
</svg> </svg>
<p class="text-[11px] leading-relaxed text-slate-500"> <p class="text-[10px] md:text-[11px] leading-relaxed text-slate-500">
<strong class="text-slate-900 font-bold uppercase tracking-tight block mb-0.5">Caution</strong> <strong class="text-slate-900 font-bold uppercase tracking-tight block mb-0.5">Caution</strong>
Cette somme sera prélevée et pourra être bloquée pour un maximum de <span class="text-slate-900 font-semibold">4 jours</span> selon les politiques de <span class="font-medium text-blue-600">Stripe</span>. Cette somme sera prélevée sur votre compte bancaire et pourra être bloquée pour un maximum de <span class="text-slate-900 font-semibold">4 jours</span> selon les politiques de <span class="font-medium text-blue-600">Stripe</span>. </p>
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{# --- DESCRIPTION --- #} {# --- DESCRIPTION --- #}
<div class="prose prose-slate prose-lg max-w-none mb-12 text-slate-600 leading-relaxed"> <div class="prose prose-slate prose-lg max-w-none mb-12 text-slate-600 leading-relaxed text-center md:text-left">
{{ product.description|raw }} {{ product.description|raw }}
</div> </div>
<div class="border-t border-slate-100 pt-10 mb-12"> {# --- DIMENSIONS (Version Responsive) --- #}
<div class="grid grid-cols-1 gap-8"> {% if product.dimP is not empty and product.dimP != "" %}
{# Badge Âge #} <div class="mb-12">
<div class="bg-slate-50 p-6 rounded-[2rem] border border-slate-100 flex items-center justify-between"> <span class="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-4 md:mb-6 block italic text-center md:text-left">Dimensions de la structure</span>
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Âge conseillé</span> <div class="bg-slate-50 rounded-[2.5rem] md:rounded-[3rem] p-6 md:p-8 border border-slate-100 relative overflow-hidden">
<span class="text-lg font-black text-slate-900 uppercase italic">{{ product.category }}</span> <div class="flex flex-col md:grid md:grid-cols-2 gap-8 md:gap-12 items-center">
</div> {# Visuel Schématique #}
<div class="relative h-32 md:h-48 flex items-center justify-center w-full">
{# SÉLECTEUR DE PÉRIODE --- #} <div class="relative w-28 h-16 md:w-32 md:h-20 bg-blue-600/10 border-2 border-blue-500/20 rounded-lg transform -rotate-12 skew-x-12 flex items-center justify-center">
<div class="bg-blue-50/50 p-8 rounded-[3rem] border border-blue-100 hidden"> <div class="absolute -right-4 -top-6 md:-top-8 h-full border-r-2 border-dashed border-blue-400/40 flex items-center">
<span class="block text-[10px] font-black text-blue-600 uppercase tracking-[0.2em] mb-6 italic text-center">Planifiez votre événement</span> <span class="ml-2 text-[9px] md:text-[10px] font-black text-blue-500 italic">{{ product.dimH }}m</span>
<div class="flex flex-col sm:flex-row gap-4"> </div>
<div class="flex-1"> <div class="absolute -bottom-5 md:-bottom-6 left-0 w-full border-b-2 border-dashed border-blue-400/40 text-center">
<label class="block text-[8px] font-black uppercase text-slate-400 mb-2 ml-4">Date de début</label> <span class="block mt-1 text-[9px] md:text-[10px] font-black text-blue-500 italic">{{ product.dimW }}m</span>
<input type="date" class="w-full bg-white border-none rounded-2xl px-5 py-4 text-sm font-bold text-slate-900 focus:ring-2 focus:ring-blue-600 shadow-sm"> </div>
<div class="absolute -bottom-3 -right-6 md:-right-8 w-16 md:w-20 border-b-2 border-dashed border-blue-400/40 transform rotate-[45deg] text-center">
<span class="block mt-1 text-[9px] md:text-[10px] font-black text-blue-500 italic">{{ product.dimP }}m</span>
</div>
<svg class="w-8 h-8 md:w-12 md:h-12 text-blue-500/20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
</svg>
</div>
</div> </div>
<div class="flex-1"> {# Chiffres #}
<label class="block text-[8px] font-black uppercase text-slate-400 mb-2 ml-4">Date de fin</label> <div class="grid grid-cols-1 gap-3 md:gap-4 w-full">
<input type="date" class="w-full bg-white border-none rounded-2xl px-5 py-4 text-sm font-bold text-slate-900 focus:ring-2 focus:ring-blue-600 shadow-sm"> <div class="flex items-center justify-between border-b border-slate-200/60 pb-3">
<span class="text-[9px] md:text-[10px] font-black text-slate-400 uppercase italic">Largeur</span>
<span class="text-lg md:text-xl font-black text-slate-900 italic">{{ product.dimW }} m</span>
</div>
<div class="flex items-center justify-between border-b border-slate-200/60 pb-3">
<span class="text-[9px] md:text-[10px] font-black text-slate-400 uppercase italic">Profondeur</span>
<span class="text-lg md:text-xl font-black text-slate-900 italic">{{ product.dimP }} m</span>
</div>
<div class="flex items-center justify-between">
<span class="text-[9px] md:text-[10px] font-black text-slate-400 uppercase italic">Hauteur</span>
<span class="text-lg md:text-xl font-black text-slate-900 italic">{{ product.dimH }} m</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endif %}
<div class="border-t border-slate-100 pt-10 mb-12">
<div class="grid grid-cols-1 gap-8">
<div class="bg-slate-50 p-6 rounded-[2rem] border border-slate-100 flex items-center justify-between shadow-sm">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Âge conseillé</span>
<span class="text-lg font-black text-slate-900 uppercase italic">{{ product.category }}</span>
</div>
</div>
</div> </div>
{% if product.category == "2-7 ans" %} {# --- ACTIONS FINALES --- #}
{# --- BLOC CONDITION SPÉCIALE (STYLE ÉPURÉ) --- #} <div class="mt-auto space-y-4">
<div class="mt-auto"> {% if product.category == "2-7 ans" %}
<div class="mb-6 p-6 border-2 border-slate-100 rounded-[2.5rem] text-center"> <div class="p-6 border-2 border-slate-100 rounded-[2.5rem] text-center">
<p class="text-[12px] font-bold text-slate-800 leading-relaxed uppercase tracking-wide">h <p class="text-[11px] font-bold text-slate-800 leading-relaxed uppercase tracking-wide">
La réservation de cette structure est soumise à des conditions spéciales. Structure soumise à des conditions spéciales.
</p>
<p class="mt-2 text-[11px] text-slate-500 font-medium">
Merci de nous contacter pour la réserver.
</p> </p>
</div> </div>
{% endif %}
<a href="{{ path('reservation_contact', {id: product.id}) }}" <a href="{{ path('reservation_contact', {id: product.id}) }}"
class="flex items-center justify-center w-full py-8 bg-slate-900 text-white rounded-[2.5rem] font-black uppercase text-[12px] tracking-[0.3em] hover:bg-[#f39e36] transition-all shadow-2xl hover:scale-[1.02] active:scale-95"> class="flex items-center justify-center w-full py-6 md:py-8 bg-slate-900 text-white rounded-[2.5rem] font-black uppercase text-[11px] md:text-[12px] tracking-[0.3em] hover:bg-[#f39e36] transition-all shadow-2xl hover:scale-[1.02] active:scale-95">
Nous contacter {{ (product.category == "2-7 ans") ? 'Nous contacter' : 'Vérifier la disponibilité' }}
</a> </a>
<p class="text-center mt-6 text-[9px] font-black text-slate-300 uppercase tracking-widest italic"> <p class="text-center text-[9px] font-black text-slate-300 uppercase tracking-widest italic">
Devis gratuit • Ludikevent • Qualité Pro Devis gratuit • Ludikevent • Qualité Pro
</p> </p>
</div> </div>
{% else %}
{# --- ACTION FINALE ORIGINALE --- #}
<div class="mt-auto">
<a href="{{ path('reservation_contact', {id: product.id}) }}"
class="flex items-center justify-center w-full py-8 bg-slate-900 text-white rounded-[2.5rem] font-black uppercase text-[12px] tracking-[0.3em] hover:bg-[#f39e36] transition-all shadow-2xl hover:scale-[1.02] active:scale-95">
Vérifier la disponibilité
</a>
<p class="text-center mt-6 text-[9px] font-black text-slate-300 uppercase tracking-widest italic">
Devis gratuit • Ludikevent • Qualité Pro
</p>
</div>
{% endif %}
</div> </div>
</div> </div>
{# --- DOCUMENTS PUBLICS (NOTICES, PDF) --- #}
{# --- DOCUMENTS PUBLICS --- #}
{% set publicDocs = product.productDocs|filter(doc => doc.isPublic) %} {% set publicDocs = product.productDocs|filter(doc => doc.isPublic) %}
{% if publicDocs|length > 0 %} {% if publicDocs|length > 0 %}
<div class="mt-12 mb-12"> <div class="mt-20">
<span class="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-4 block italic">Ressources techniques</span> <span class="text-[10px] font-black text-slate-400 uppercase tracking-[0.3em] mb-6 block italic text-center md:text-left">Ressources techniques</span>
<div class="space-y-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% for doc in publicDocs %} {% for doc in publicDocs %}
<div class="cta-file"> <a href="{{ vich_uploader_asset(doc, 'docProduct') }}"
<utm-event event="click_pdf_product" data="{{ doc.json }}" ></utm-event> download="{{ doc.name }}.pdf"
<a href="{{ vich_uploader_asset(doc, 'docProduct') }}" class="flex items-center justify-between p-5 bg-white border border-slate-100 rounded-3xl hover:border-blue-500 hover:shadow-lg transition-all group">
download="{{ doc.name }}.pdf" <div class="flex items-center gap-4">
class="flex items-center justify-between p-5 bg-white border border-slate-100 rounded-3xl hover:border-blue-500 hover:shadow-lg transition-all group"> <div class="w-10 h-10 bg-red-50 text-red-500 rounded-xl flex items-center justify-center group-hover:bg-red-500 group-hover:text-white transition-colors">
<div class="flex items-center gap-4"> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>
<div class="w-10 h-10 bg-red-50 text-red-500 rounded-xl flex items-center justify-center group-hover:bg-red-500 group-hover:text-white transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
</svg>
</div>
<div>
<p class="text-[11px] font-black text-slate-900 uppercase italic tracking-wider">{{ doc.name }}</p>
</div>
</div> </div>
<div class="text-slate-300 group-hover:text-blue-600 transition-colors"> <p class="text-[11px] font-black text-slate-900 uppercase italic">{{ doc.name }}</p>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </div>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/> <svg class="w-5 h-5 text-slate-300 group-hover:text-blue-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
</svg> </a>
</div>
</a>
</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
</main> </main>
{# --- SECTION SUGGESTIONS (CROSS-SELLING) --- #} {# --- SUGGESTIONS --- #}
<section class="max-w-7xl mx-auto px-4 py-24 border-t border-slate-100 mt-12"> <section class="max-w-7xl mx-auto px-4 py-24 border-t border-slate-100 mt-12">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-16"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mb-16 text-center md:text-left">
<div> <div>
<span class="text-[10px] font-black text-[#f39e36] uppercase tracking-[0.3em] mb-3 block italic">Vous pourriez aussi aimer</span> <span class="text-[10px] font-black text-[#f39e36] uppercase tracking-[0.3em] mb-3 block italic">Vous pourriez aussi aimer</span>
<h2 class="text-4xl md:text-6xl font-black text-slate-900 uppercase italic tracking-tighter leading-none">D'autres <span class="text-[#f39e36]">idées ?</span></h2> <h2 class="text-4xl md:text-6xl font-black text-slate-900 uppercase italic tracking-tighter leading-none">D'autres <span class="text-[#f39e36]">idées ?</span></h2>
</div> </div>
<a href="{{ path('reservation_catalogue') }}" class="inline-flex items-center gap-2 text-[10px] font-black uppercase tracking-widest border-b-2 border-slate-900 pb-1 hover:text-blue-600 hover:border-blue-600 transition-all"> <a href="{{ path('reservation_catalogue') }}" class="inline-flex items-center justify-center gap-2 text-[10px] font-black uppercase tracking-widest border-b-2 border-slate-900 pb-1 hover:text-blue-600 hover:border-blue-600 transition-all">
Tout le catalogue Tout le catalogue
</a> </a>
</div> </div>
<div class="grid grid-cols-2 lg:grid-cols-4 gap-8"> <div class="grid grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8">
{% for other in otherProducts %} {% for other in otherProducts %}
<div class="group transition-all duration-500"> <a href="{{ path('reservation_product_show', {id: (other.slug)}) }}" class="group block">
<a href="{{ path('reservation_product_show', {id: (other.slug)}) }}" class="block"> <div class="relative overflow-hidden rounded-[2rem] md:rounded-[2.5rem] bg-slate-50 aspect-square mb-6 shadow-sm group-hover:shadow-2xl transition-all duration-700">
<div class="relative overflow-hidden rounded-[2.5rem] bg-slate-50 aspect-square mb-6 shadow-sm group-hover:shadow-2xl transition-all duration-700"> {% if other.imageName %}
{% if other.imageName %} <img src="{{ vich_uploader_asset(other,'imageFile') | imagine_filter('webp') }}" alt="{{ other.name }}" class="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-1000">
<img src="{{ vich_uploader_asset(other,'imageFile') | imagine_filter('webp') }}" {% else %}
alt="{{ other.name }}" <div class="w-full h-full flex items-center justify-center opacity-10"><img src="{{ asset('provider/images/favicon.png') }}" class="w-16"></div>
class="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-1000"> {% endif %}
{% else %} <div class="absolute top-4 right-4 bg-white/95 backdrop-blur-md px-3 py-1.5 rounded-xl shadow-sm">
<div class="w-full h-full flex items-center justify-center opacity-10"> <p class="text-slate-900 font-black text-[11px] md:text-[12px] italic leading-none">{{ other.priceDay }}€</p>
<img src="{{ asset('provider/images/favicon.png') }}" class="w-16">
</div>
{% endif %}
<div class="absolute top-4 right-4 bg-white/95 backdrop-blur-md px-3 py-1.5 rounded-xl shadow-sm">
<p class="text-slate-900 font-black text-[12px] italic leading-none">{{ other.priceDay }}€</p>
</div>
</div> </div>
<div class="px-2"> </div>
<span class="text-[8px] font-black text-slate-300 uppercase tracking-widest mb-1 block italic">{{ other.category }}</span> <div class="px-2 text-center md:text-left">
<h3 class="text-lg font-black text-slate-900 uppercase italic tracking-tighter leading-tight group-hover:text-[#f39e36] transition-colors line-clamp-1"> <span class="text-[8px] font-black text-slate-300 uppercase tracking-widest mb-1 block italic">{{ other.category }}</span>
{{ other.name }} <h3 class="text-md md:text-lg font-black text-slate-900 uppercase italic tracking-tighter leading-tight group-hover:text-[#f39e36] transition-colors line-clamp-1">{{ other.name }}</h3>
</h3> </div>
</div> </a>
</a>
</div>
{% else %}
<p class="col-span-full text-center text-slate-400 italic text-sm py-12">Plus de surprises arrivent bientôt...</p>
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
</div> </div>
{% endblock %} {% endblock %}