TEST
Colonie de vacances à l'étranger
Où partir en colonie de vacances à l'étranger ? USA, Angleterre, Malte, Grèce, Japon, Espagne, Guadeloupe, Mexique, Portugal,... Partir hors des frontières françaises est l'occasion unique de découvrir de nouvelles cultures, de nouveaux paysages et explorer le monde ! Si vous êtes curieux et plein d'énergie, partez en colonie de vacances à l'étranger pour une expérience que vous n'êtes pas prêt d'oublier !
Une erreur s'est produite lors du traitement du gabarit.
The following has evaluated to null or missing:
==> Titre [in template "20097#20123#40872926" at line 6, column 17]
----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----
----
FTL stack trace ("~" means nesting-related):
- Failed at: ${Titre.getData()} [in template "20097#20123#40872926" at line 6, column 15]
----
1<div id="first">
2 <div class="container custom-container">
3 <div class="row section">
4 <div class="col-md-8 p-3">
5 <div class="text-white">
6 <h1>${Titre.getData()}</h1>
7 <p class="title-desc">${ZoneDeSaisie1x48.getData()}</p>
8 </div>
9 </div>
10 <div id="background-image"></div>
11 <div class="diagonal-divider"></div>
12 </div>
13 </div>
14</div>
15
16<div class="container">
17 <#attempt>
18 <#assign offerLocalService = serviceLocator.findService("com.totemia.commerce.type.offer.service.OfferLocalService")>
19 <#assign produits = offerLocalService.searchProducts(Texteq55r.getData(), themeDisplay) >
20 <div class="row justify-content-center align-items-center mt-4 mb-2">
21 <#if produits?size == 0>
22 <div class="col">
23 <div class="d-flex text-center">
24 <i class="fa fa-exclamation-triangle" style="margin-top: 3px; margin-right: 10px; color: orange;"></i>
25 <h4>Catalogue en cours d'intégration pour cette catégorie</h4>
26 </div>
27
28 <p class="text-center">N'hésitez pas à nous contacter par téléphone au
29 <a href="tel:+33176381092">01 76 38 10 92</a>, par email à
30 <a href="mailto:contact@totemia.com">contact@totemia.com</a>
31 ou à demander un rappel via le formulaire ci-dessous pour bénéficier de nos conseils et vous inscrire sur liste d'attente.
32 </p>
33
34 <div id="recalled-form"></div>
35 </div>
36
37 <img src="https://totemia.com/documents/37901/0/sad_cat.gif/ce486981-2149-275f-3b95-74f21a62e914?t=1710349975702"
38 alt="Sad gif" class="gif-responsive">
39
40 <style>
41 .gif-responsive {
42 max-width: 100%;
43 height: auto;
44 display: block;
45 margin: auto;
46 }
47 </style>
48 <#else>
49 <span class="h3 text-center">Nos séjours coups de cœur </span>
50 <i class="fa fa-heart text-secondary ml-2 mb-2"></i>
51 </#if>
52 </div>
53
54 <div class="row justify-content-center">
55 <#list produits as produit>
56 <#assign productName = produit.getProductName()>
57 <#assign url = "${produit.getUrl()}" >
58 <#assign img = "${produit.getImg()}" >
59 <#assign id = "${produit.getCPDefinitionId()}" >
60 <#assign min = produit.getAgeMin() >
61 <#assign max = produit.getAgeMax() >
62 <#assign address = produit.getAddress() >
63 <#assign cat = produit.getCatName() >
64 <#assign price = produit.getMinPrice()>
65 <#assign promo = produit.getMinPromoPrice()>
66 <#assign isPromo = produit.getMinPrice() != produit.getMinPromoPrice() && produit.getMinPromoPrice() != "0">
67 <#assign csePrice = produit.getCSEPrice()!"">
68 <#assign isCSE = csePrice?has_content && csePrice != "0">
69
70 <%-- Nouveaux champs alignés avec ProductDisplayData --%>
71 <#assign productTag = produit.getProductTag()!"">
72 <#assign isOfferFull = produit.isOfferFull()!false>
73 <#assign durations = produit.getDurations()!"">
74 <#assign activities = produit.getActivities()![]>
75 <#assign accommodations = produit.getAccommodationTypeNames()![]>
76 <#assign hasAccIncluded = produit.isHasAccommodationIncluded()!false>
77
78 <div class="col-sm-6 col-xl-4 mb-3 hover-animate p-3 p-md-2">
79 <div class="card h-100 shadow w-100 card-clickable"
80 data-url="${url}"
81 style="cursor: pointer; border-radius: 20px;">
82
83 <img loading="lazy"
84 class="card-img-top-new overflow-hidden gradient-overlay"
85 src="${img}"
86 alt="${productName}"/>
87
88 <%-- Badge produit (full / lowStock / bestSeller / newOffer) --%>
89 <#if productTag?has_content>
90 <div class="card-img-overlay-top text-left">
91 <span style="margin-right:2px;" class="badge ${productTag}">
92 <#if productTag == "full">
93 Complet
94 <#elseif productTag == "lowStock">
95 Dernières places
96 <#elseif productTag == "bestSeller">
97 Best-seller
98 <#elseif productTag == "newOffer">
99 Nouveau
100 </#if>
101 </span>
102 </div>
103 </#if>
104
105 <!-- Titre et référence -->
106 <div class="px-3 pb-2 card-content">
107 <div class="d-flex flex-wrap" style="margin-bottom: 0.5rem;">
108 <span class="badge title-tag">
109 ${min}-${max} ans
110 </span>
111 <#if durations?has_content>
112 <span class="badge title-tag">
113 ${durations}
114 </span>
115 </#if>
116 <span class="badge title-tag">
117 ${cat}
118 </span>
119 </div>
120
121 <h5 style="letter-spacing: 0;"
122 class="text-decoration-none text-dark mb-0">
123 ${productName}
124 </h5>
125
126 <span class="card-title mb-0 text-decoration-none font-weight-normal text-dark mt-2"
127 style="letter-spacing: 0;">
128 ${address}
129 <#if accommodations?has_content>
130 •
131 <#if !hasAccIncluded>
132 Sans hébergement ou
133 </#if>
134 <#list accommodations as acc>
135 ${acc}<#if acc?has_next> ou </#if>
136 </#list>
137 <#else>
138 • Sans hébergement
139 </#if>
140
141 <#if activities?has_content>
142 <br>
143 <span class="text-xs activities-container mt-2"
144 style="color:#91284D; text-transform: capitalize;">
145 <#list activities as activity>
146 ${activity}<#if activity?has_next> • </#if>
147 </#list>
148 </span>
149 <#elseif produit.getShortDesc()?has_content>
150 <br>
151 <span class="text-xs activities-container mt-2"
152 style="color:#91284D;">
153 ${produit.getShortDesc()}
154 </span>
155 </#if>
156 </span>
157 </div>
158
159 <!-- Prix -->
160 <div class="price-block px-3 pb-3 mt-auto">
161 <div class="d-flex align-items-center justify-content-between">
162 <div class="price-container">
163 <#if isOfferFull>
164 <span class="text-danger font-weight-bold">Complet</span>
165 <#else>
166 <span class="card-text text-muted mb-0">
167 À partir de
168
169 <#if isPromo || isCSE>
170 <span style="text-decoration: line-through;"
171 class="text-dark text-sm ml-1">
172 ${price}€
173 </span>
174
175 <#if isCSE>
176 <span class="h5 promo-price ml-2">
177 ${csePrice}€
178 </span>
179 <#else>
180 <span class="h5 promo-price ml-2">
181 ${promo}€
182 </span>
183 </#if>
184 <#else>
185 <span class="h5 ml-1">
186 ${price}€
187 </span>
188 </#if>
189 </span>
190 </#if>
191 </div>
192
193 <a href="${url}" class="btn-discover">
194 Voir l'offre
195 </a>
196 </div>
197 </div>
198
199 </div>
200 </div>
201
202 </#list>
203 </div>
204 <#recover>
205 </#attempt>
206
207 <#if produits?size != 0>
208 <div class="text-right d-flex align-items-center justify-content-end mb-4">
209 <a class="h3 text-secondary pr-3 mb-0" href="${Texteq55r.getData()}">
210 Découvrir tous les séjours ${Titre.getData()}
211 </a>
212 <i class="fa text-secondary fa-arrow-right"></i>
213 </div>
214 </#if>
215
216 <div class="article mt-5 p-1 p-md-3">
217 ${HTML1yo0.getData()}
218 </div>
219
220 <#if Texte1szp?? && Texte1szp.getData()?has_content>
221 <div class="row justify-content-center">
222 <h2 class="">FAQ ${Titre.getData()}</h2>
223 <div class="accordion">
224 <#list Texte1szp.getData()?eval.faq as item>
225 <div class="accordion-item" itemscope itemprop="mainEntity"
226 itemtype="https://schema.org/Question">
227 <button aria-expanded="false">
228 <h3 itemprop="name" class="accordion-title h4 p-1">${item.question}</h3>
229 <span class="icon" aria-hidden="true"></span>
230 </button>
231 <div itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer"
232 class="accordion-content">
233 <p itemprop="text" class="h5 p-4 font-weight-normal m-0">${item.answer}</p>
234 </div>
235 </div>
236 </#list>
237 </div>
238 </div>
239 </#if>
240</div>
241
242<style>
243 /* ==================== CARDS ==================== */
244
245 .card h5 {
246 display: -webkit-box;
247 -webkit-line-clamp: 2;
248 -webkit-box-orient: vertical;
249 overflow: hidden;
250 text-overflow: ellipsis;
251 min-height: 2.4em;
252 line-height: 1.2em;
253 }
254
255 .activities-container {
256 display: -webkit-box;
257 -webkit-line-clamp: 2;
258 -webkit-box-orient: vertical;
259 overflow: hidden;
260 text-overflow: ellipsis;
261 max-height: 2.8em;
262 line-height: 1.4em;
263 margin-bottom: 6px;
264 }
265
266 .col-sm-6.col-xl-4 {
267 display: flex;
268 }
269
270 .card.h-100 {
271 display: flex;
272 flex-direction: column;
273 }
274
275 .card-content {
276 flex: 1;
277 display: flex;
278 flex-direction: column;
279 }
280
281 .card-img-top-new {
282 height: 190px;
283 object-fit: cover;
284 padding: 5px;
285 border-radius: 20px;
286 }
287
288 .card-img-overlay-top {
289 position: absolute;
290 top: 10px;
291 left: 10px;
292 z-index: 10;
293 }
294
295 /* Badges produit (alignés avec la JSP) */
296 .badge.full { background: #ffab96; color: red; }
297 .badge.lowStock { background: #F7C32E; color: #450A00; }
298 .badge.bestSeller{ background: #00EADF; color: #162F70; }
299 .badge.newOffer { background: #162F70; color: #00EADF; }
300
301 .price-block {
302 margin-top: auto;
303 padding: 5px 0px 0px 0px;
304 background-color: #ffffff;
305 border-radius: 20px;
306 }
307
308 .price-block .d-flex {
309 align-items: center;
310 justify-content: space-between;
311 gap: 15px;
312 }
313
314 .price-container { flex: 1; }
315
316 .promo-price {
317 color: #dc3545;
318 font-weight: 700;
319 }
320
321 .btn-discover {
322 flex-shrink: 0;
323 display: inline-flex;
324 align-items: center;
325 justify-content: center;
326 padding: 12px 8px;
327 border-radius: 16px;
328 background-color: #0C41B8;
329 color: white;
330 font-size: 16px;
331 font-weight: 600;
332 text-decoration: none;
333 transition: background-color 0.3s ease, transform 0.2s ease;
334 white-space: nowrap;
335 }
336
337 .btn-discover:hover {
338 background-color: white;
339 border: 1px solid #0C41B8;
340 color: #0C41B8;
341 transform: translateY(-2px);
342 text-decoration: none;
343 }
344
345 .btn-discover:active { transform: translateY(0); }
346
347 .title-tag {
348 background: white;
349 color: #91284D;
350 padding: 4px 5px;
351 border: 1px solid #91284D;
352 font-size: 10px;
353 margin-right: 4px;
354 margin-bottom: 4px;
355 }
356
357 @media (max-width: 767px) {
358 .badge {
359 font-size: 60%;
360 white-space: normal;
361 }
362 }
363
364 /* ==================== STYLES EXISTANTS ==================== */
365
366 .article img {
367 max-width: -webkit-fill-available;
368 object-fit: cover;
369 }
370
371 #first {
372 background: #1e2c63;
373 position: relative;
374 overflow: hidden;
375 }
376
377 <#if (Image29wz.getData())?? && Image29wz.getData() != "">
378 <#assign backgroundImage = '${Image29wz.getData()}'>
379 <#assign extraStyle = '-4%'>
380 <#else>
381 <#assign backgroundImage = 'https://totemia.com/documents/37901/0/homepage_page_cat.jpg/0fb81309-6f8e-91af-aafe-4c6e44de0ec2?t=1707217228005'>
382 <#assign extraStyle = '0%'>
383 </#if>
384
385 #background-image {
386 background: url('${backgroundImage}');
387 background-size: cover;
388 background-position: right;
389 position: absolute;
390 top: 0;
391 right: ${extraStyle};
392 height: 100%;
393 width: 30%;
394 }
395
396 .section { color: white; }
397
398 .diagonal-divider::before {
399 content: '';
400 position: absolute;
401 top: 0;
402 right: 25%;
403 width: 10%;
404 height: 100%;
405 background-color: #1e2c63;
406 transform: skew(-15deg);
407 z-index: 1;
408 }
409
410 .title-desc { margin-bottom: 0 !important; }
411
412 @media (max-width: 767.98px) {
413 .diagonal-divider { display: none; }
414 #background-image { position: relative; width: 100%; }
415 .title-desc { font-size: 0.7rem; }
416 .section div { text-align: center !important; }
417 }
418
419 .breadcrumb { margin: 0; padding-bottom: 0; }
420
421 /* ==================== ACCORDION FAQ ==================== */
422
423 .accordion .accordion-item { border-bottom: 1px solid #212529; }
424 .accordion .accordion-item button[aria-expanded=true] { border-bottom: 1px solid #212529; }
425
426 .accordion button {
427 position: relative;
428 display: block;
429 text-align: left;
430 width: 100%;
431 padding: 1em 0;
432 font-size: 1.15rem;
433 font-weight: 400;
434 border: none;
435 background: none;
436 outline: none;
437 }
438
439 .accordion button:hover,
440 .accordion button:focus { cursor: pointer; color: #e83e8c; }
441
442 .accordion button:hover::after,
443 .accordion button:focus::after { cursor: pointer; color: #e83e8c; border: 1px solid #e83e8c; }
444
445 .accordion button .accordion-title { padding: 1em 1.5em 1em 0; }
446
447 .accordion button .icon {
448 display: inline-block;
449 position: absolute;
450 top: 18px;
451 right: 0;
452 width: 22px;
453 height: 22px;
454 border: 1px solid;
455 border-radius: 22px;
456 }
457
458 .accordion button .icon::before {
459 display: block;
460 position: absolute;
461 content: "";
462 top: 9px;
463 left: 5px;
464 width: 10px;
465 height: 2px;
466 background: currentColor;
467 }
468
469 .accordion button .icon::after {
470 display: block;
471 position: absolute;
472 content: "";
473 top: 5px;
474 left: 9px;
475 width: 2px;
476 height: 10px;
477 background: currentColor;
478 }
479
480 .accordion button[aria-expanded=true] { color: #212529; }
481 .accordion button[aria-expanded=true] .icon::after { width: 0; }
482
483 .accordion button[aria-expanded=true] + .accordion-content {
484 opacity: 1;
485 max-height: 200em;
486 transition: all 200ms linear;
487 will-change: opacity, max-height;
488 }
489
490 .accordion .accordion-content {
491 opacity: 0;
492 max-height: 0;
493 overflow: hidden;
494 transition: opacity 200ms linear, max-height 200ms linear;
495 will-change: opacity, max-height;
496 }
497
498 .accordion .accordion-content p { font-size: 1rem; margin: 2em 0; }
499</style>
500
501<script>
502 // Card clickable
503 document.addEventListener('DOMContentLoaded', function() {
504 const cards = document.querySelectorAll('.card-clickable');
505 cards.forEach(function(card) {
506 card.addEventListener('click', function(e) {
507 const closestLink = e.target.closest('a, button');
508 if (closestLink) return;
509 const url = this.getAttribute('data-url');
510 if (url) window.location.href = url;
511 });
512 });
513 });
514
515 // Accordion FAQ
516 let items = document.querySelectorAll(".accordion button");
517
518 function toggleAccordion() {
519 const itemToggle = this.getAttribute("aria-expanded");
520 for (i = 0; i < items.length; i++) {
521 items[i].setAttribute("aria-expanded", "false");
522 }
523 if (itemToggle == "false") {
524 this.setAttribute("aria-expanded", "true");
525 }
526 }
527
528 items.forEach((item) => item.addEventListener("click", toggleAccordion));
529
530 // Trustpilot (si utilisé)
531 var element = document.getElementsByClassName("trustpilot-widget");
532 for(var i=0; i<element.length; i++) {
533 if (window.Trustpilot) window.Trustpilot.loadFromElement(element[i]);
534 }
535
536 // HubSpot form
537 function onVariableExist(varName, callback) {
538 const checkExist = setInterval(() => {
539 if (typeof window[varName] !== 'undefined') {
540 clearInterval(checkExist);
541 callback(window[varName]);
542 }
543 }, 100);
544 }
545
546 onVariableExist('hbspt', function(value) {
547 hbspt.forms.create({
548 region: "na1",
549 portalId: "8587022",
550 formId: "dedc835c-9a30-443a-9029-77321fc48d33",
551 target: '#recalled-form',
552 });
553 });
554</script>
Parcourez nos colonies de vacances classées par thème :