templates/vitrine/en/homepage.html.twig line 1

Open in your IDE?
  1. {% extends 'vitrine/en/layout.html.twig' %}
  2. {% block title %}Cathedral Heritage Suite - 8-Guest Rental Strasbourg Center{% endblock title %}
  3. {% block description %}Rent our 83m² suite in the heart of Strasbourg. 3 bedrooms, sleeps 8, 100m from Cathedral. Kitchen fully furnished, 2 bathrooms. Book your stay today!{% endblock description %}
  4. {% block robots %}index,follow{% endblock robots %}
  5. {% block canonical %}
  6. <link rel="canonical" href="https://cathedraleheritage.com" />
  7. <link rel="alternate" hreflang="fr" href="https://cathedraleheritage.com/fr" />
  8. <link rel="alternate" hreflang="en" href="https://cathedraleheritage.com" />
  9. <link rel="alternate" hreflang="x-default" href="https://cathedraleheritage.com" />
  10. {% endblock canonical %}
  11. {% block body %}
  12. <section id="hero" class="text-light no-top no-bottom relative overflow-hidden z-1000">
  13. <div class="abs w-100 abs-centered z-2">
  14. <div class="container">
  15. <div class="spacer-double"></div>
  16. <div class="row g-4 align-items-center justify-content-center">
  17. <div class="col-md-8 text-center">
  18. <h1 class="mb-2" style="color:white;">Welcome to Cathedral Heritage Suite</h1>
  19. <p class="lead mb-3" style="color:white; font-size:22px;">Beautifully appointed apartment accommodating up to 8 guests in Strasbourg's prestigious historical city center</p>
  20. <a class="btn-main btn-line bg-blur fx-slide" href="#about" style="color:white;"><span>Find out more</span></a>
  21. </div>
  22. </div>
  23. </div>
  24. </div>
  25. <div class="vertical-center">
  26. <div class="swiper">
  27. <div class="swiper-wrapper">
  28. <div class="swiper-slide">
  29. <div class="swiper-inner" data-bgimage="url(/images/salon2.jpeg)">
  30. <div class="sw-overlay op-5"></div>
  31. </div>
  32. </div>
  33. <div class="swiper-slide">
  34. <div class="swiper-inner" data-bgimage="url(/images/chambre2.jpeg)">
  35. <div class="sw-overlay op-5"></div>
  36. </div>
  37. </div>
  38. <div class="swiper-slide">
  39. <div class="swiper-inner" data-bgimage="url(/images/noel.jpeg)">
  40. <div class="sw-overlay op-5"></div>
  41. </div>
  42. </div>
  43. </div>
  44. </div>
  45. </div>
  46. <div class="abs w-100 bottom-0 z-2 pb-4 sm-hide">
  47. <div class="container">
  48. <div class="row">
  49. <div class="col-lg-12">
  50. <div class="d-flex justify-content-between">
  51. <div><h6 style="color:white;">Cathedral 100m away</h6></div>
  52. <div><h6 style="color:white;">Museums 200m away</h6></div>
  53. <div><h6 style="color:white;">Petite France 300m away</h6></div>
  54. </div>
  55. </div>
  56. </div>
  57. </div>
  58. </div>
  59. <div class="gradient-edge-bottom op-8"></div>
  60. </section>
  61. <section id="about">
  62. <div class="container">
  63. <div class="row g-4 align-items-end justify-content-between">
  64. <div class="col-lg-5">
  65. <div class="subtitle">About the apartment</div>
  66. <h2>Modern suite in the heart of Strasbourg's Grande Île</h2>
  67. </div>
  68. <div class="col-lg-4">
  69. <p class="mb-4" style="color:#212121;">
  70. Spacious fully equipped appartement ideal for families and groups up to 8 people : 1 master bedroom with en-suite bathroom, one double bedroom, one bedroom with two double bunk beds (sleeps 4), maximum weight of 100kg for upper bunks.
  71. </p>
  72. <p class="mb-4" style="color:#212121;">
  73. Kitchen fully furnished and cozy living-dining room to share great moments together.
  74. </p>
  75. <p class="mb-4" style="color:#212121;">
  76. Amenities: hairdryer, dishwasher, washer-dryer, microwave, oven, ironing board with iron, Nespresso coffee machine, kettle, TV, Wi-Fi and USB outlets for charging your devices.
  77. </p>
  78. </div>
  79. </div>
  80. <div class="spacer-single"></div>
  81. <div class="row g-4">
  82. <div class="col-md-2">
  83. <div class="relative">
  84. <h5>Size</h5>
  85. <div class="d-flex justify-content-start align-items-center" style="padding-top:6px;">
  86. <img src="/images/home.png" width="37" class="me-3" alt="">
  87. <div class="fs-20 fw-600" style="color:#212121;">83 m²</div>
  88. </div>
  89. </div>
  90. </div>
  91. <div class="col-md-2">
  92. <div class="relative">
  93. <h5>Bedrooms</h5>
  94. <div class="d-flex justify-content-start align-items-center" style="padding-top:4px;">
  95. <img src="/images/bed.png" width="37" class="me-3" alt="">
  96. <div class="fs-20 fw-600" style="color:#212121;">3</div>
  97. </div>
  98. </div>
  99. </div>
  100. <div class="col-md-2">
  101. <div class="relative">
  102. <h5>Living Room</h5>
  103. <div class="d-flex justify-content-start align-items-center">
  104. <img src="/images/sofa.png" width="50" class="me-3" alt="">
  105. <div class="fs-20 fw-600" style="color:#212121;">Included</div>
  106. </div>
  107. </div>
  108. </div>
  109. <div class="col-md-2">
  110. <div class="relative">
  111. <h5>Kitchen</h5>
  112. <div class="d-flex justify-content-start align-items-center" style="padding-top:6px;">
  113. <img src="/images/cooking.png" width="37" class="me-3" alt="">
  114. <div class="fs-20 fw-600" style="color:#212121;">Included</div>
  115. </div>
  116. </div>
  117. </div>
  118. <div class="col-md-2">
  119. <div class="relative">
  120. <h5>Bathrooms</h5>
  121. <div class="d-flex justify-content-start align-items-center" style="padding-top:4px;">
  122. <img src="/images/bathroom.png" width="40" class="me-3" alt="">
  123. <div class="fs-20 fw-600" style="color:#212121;">2</div>
  124. </div>
  125. </div>
  126. </div>
  127. <div class="col-md-2">
  128. <div class="relative">
  129. <h5>Guests</h5>
  130. <div class="d-flex justify-content-start align-items-center" style="padding-top:5px;">
  131. <img src="/images/user.png" width="37" class="me-3" alt="">
  132. <div class="fs-20 fw-600" style="color:#212121;">8</div>
  133. </div>
  134. </div>
  135. </div>
  136. </div>
  137. </div>
  138. </section>
  139. <section id="rooms" class="p-0 overflow-hidden">
  140. <div class="container-fluid">
  141. <div class="row">
  142. <div class="col-lg-12">
  143. <div class="owl-custom-nav menu-float" data-target="#room-carousel">
  144. <a class="btn-next"></a>
  145. <a class="btn-prev"></a>
  146. <div id="room-carousel" class="owl-2-cols-center owl-carousel owl-theme">
  147. <div class="item">
  148. <div class="relative">
  149. <div class="overflow-hidden text-light">
  150. <div class="abs h-100 start-0 z-2 p-4">
  151. <h3 style="color:white;">Living Room</h3>
  152. </div>
  153. <img src="/images/salon2.jpeg" class="w-100" alt="Living Room">
  154. </div>
  155. </div>
  156. </div>
  157. <div class="item">
  158. <div class="relative">
  159. <div class="overflow-hidden text-light">
  160. <div class="abs h-100 start-1 z-2 p-4">
  161. <h3 style="color:white;">Bedroom 1</h3>
  162. </div>
  163. <img src="/images/chambre1.jpeg" class="w-100" alt="Bedroom 1">
  164. </div>
  165. </div>
  166. </div>
  167. <div class="item">
  168. <div class="relative">
  169. <div class="overflow-hidden text-light">
  170. <div class="abs h-100 start-2 z-2 p-4">
  171. <h3 style="color:white;">Bedroom 2</h3>
  172. </div>
  173. <img src="/images/chambre2.jpeg" class="w-100" alt="Bedroom 2">
  174. </div>
  175. </div>
  176. </div>
  177. <div class="item">
  178. <div class="relative">
  179. <div class="overflow-hidden text-light">
  180. <div class="abs h-100 start-3 z-2 p-4">
  181. <h3 style="color:white;">Bedroom 3</h3>
  182. </div>
  183. <img src="/images/chambre3.jpeg" class="w-100" alt="Bedroom 3">
  184. </div>
  185. </div>
  186. </div>
  187. <div class="item">
  188. <div class="relative">
  189. <div class="overflow-hidden text-light">
  190. <div class="abs h-100 start-4 z-2 p-4">
  191. <h3 style="color:white;">Kitchen</h3>
  192. </div>
  193. <img src="/images/cuisine.jpeg" class="w-100" alt="Kitchen">
  194. </div>
  195. </div>
  196. </div>
  197. <div class="item">
  198. <div class="relative">
  199. <div class="overflow-hidden text-light">
  200. <div class="abs h-100 start-5 z-2 p-4">
  201. <h3 style="color:white;">Bathroom</h3>
  202. </div>
  203. <img src="/images/salledebain.jpeg" class="w-100" alt="Bathroom 1">
  204. </div>
  205. </div>
  206. </div>
  207. <div class="item">
  208. <div class="relative">
  209. <div class="overflow-hidden text-light">
  210. <div class="abs h-100 start-6 z-2 p-4">
  211. <h3 style="color:white;">Bathroom 2</h3>
  212. </div>
  213. <img src="/images/salledebain2.jpeg" class="w-100" alt="Bathroom 2">
  214. </div>
  215. </div>
  216. </div>
  217. </div>
  218. </div>
  219. </div>
  220. </div>
  221. </div>
  222. </section>
  223. <section id="services" class="overlay-dark-1">
  224. <div class="container">
  225. <div class="row g-4 justify-content-between">
  226. <div class="col-lg-5 relative z-3">
  227. <div class="me-lg-3">
  228. <div class="subtitle wow fadeInUp" data-wow-delay=".0s">Your stay in Strasbourg</div>
  229. <h2 class="wow fadeInUp" data-wow-delay=".2s">Rates and services</h2>
  230. <p style="color:#212121; font-size:18px;">An exceptional suite in the heart of Strasbourg, with optionnal dedicated concierge service to assist you throughout your stay.</p>
  231. <p style="color:#212121; font-size:18px;">Minimum booking of two nights.</p>
  232. <a href="#reservation" class="btn-main" style="color:white;">Booking</a>
  233. </div>
  234. </div>
  235. <div class="col-lg-6" style="color:black;">
  236. <div class="spacer-single spacer-double"></div>
  237. <div class="row">
  238. <div class="col-md-6 wow fadeInUp" data-wow-delay=".2s">
  239. {#
  240. <h5 class=" mb-3">Peak Season</h5>
  241. <p class="fs-500"><small>June 1st to September 30th • November 25th to January 5th</small></p>
  242. <ul class="ul-check fs-500">
  243. <li>€960 per night</li>
  244. <li>10% discount for bookings of 7 nights and up</li>
  245. </ul>
  246. <h5 class="mb-3 mt-4">Off-peak Season</h5>
  247. #}
  248. <ul class="ul-check fs-500">
  249. <li>from €350 per night</li>
  250. <li>10% discount for bookings of 7 nights and up</li>
  251. </ul>
  252. </div>
  253. <div class="col-md-6 wow fadeInUp" data-wow-delay=".4s">
  254. <h5 class="mb-3">Optional services</h5>
  255. <ul class="ul-check fs-500">
  256. <li>One parking space: €24/day or €150/week</li>
  257. <li>Extra cleaning during your stay : 60 euros</li>
  258. <li>Other Extras : Children's workshop, baby amenities, dog policy on request (other pets are unfortunately not allowed in the appartement), Decoration for special occasions, breakfast, Alsatian delicacies, etc</li>
  259. </ul>
  260. <div class="mt-4">
  261. <p class="fs-500">
  262. <strong>On site assistance</strong> <br />
  263. <small>Tuesday to Saturday • 10am-6:45pm</small>
  264. </p>
  265. <p class="fs-500">
  266. <strong>Private concierge subject to availability</strong><br>
  267. <small>Quotation on request</small>
  268. </p>
  269. </div>
  270. </div>
  271. </div>
  272. </div>
  273. </div>
  274. </div>
  275. </section>
  276. <section id="section-gallery" class="relative">
  277. <div class="container relative z-2" id="photos">
  278. <div class="row g-4 gx-5 justify-content-center">
  279. <div class="col-lg-6 text-center">
  280. <h2 class="wow fadeInUp" data-wow-delay=".2s">Apartment Gallery</h2>
  281. <p class="wow fadeInUp" data-wow-delay=".3s" style="color:#212121;">Explore our curated images to discover the residence's various spaces — from the bedrooms and living area to the kitchen, bathroom, and outdoor surroundings.</p>
  282. </div>
  283. </div>
  284. <div class="row">
  285. <div class="col-md-12 text-center">
  286. <ul id="filters" class="wow fadeInUp" data-wow-delay="0s">
  287. <li><a href="#" data-filter="*" class="selected" style="color:#212121;">Show All</a></li>
  288. <li><a href="#" data-filter=".chambre" style="color:#212121;">Bedrooms</a></li>
  289. <li><a href="#" data-filter=".salon" style="color:#212121;">Living Room</a></li>
  290. <li><a href="#" data-filter=".cuisine" style="color:#212121;">Kitchen</a></li>
  291. <li><a href="#" data-filter=".salledebain" style="color:#212121;">Bathroom</a></li>
  292. <li><a href="#" data-filter=".exterieur" style="color:#212121;">Outdoor surroundings</a></li>
  293. </ul>
  294. </div>
  295. </div>
  296. <div id="gallery" class="row g-3 wow fadeInUp" data-wow-delay=".3s">
  297. <div class="col-md-3 col-sm-6 col-12 item chambre">
  298. <a href="images/chambre1.jpeg" class="image-popup d-block hover">
  299. <div class="relative overflow-hidden">
  300. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  301. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  302. <img src="images/chambre1.jpeg" class="w-100 hover-scale-1-2" alt="Bedroom">
  303. </div>
  304. </a>
  305. </div>
  306. <div class="col-md-3 col-sm-6 col-12 item chambre">
  307. <a href="images/chambre2.jpeg" class="image-popup d-block hover">
  308. <div class="relative overflow-hidden">
  309. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  310. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  311. <img src="images/chambre2.jpeg" class="w-100 hover-scale-1-2" alt="Bedroom 2">
  312. </div>
  313. </a>
  314. </div>
  315. <div class="col-md-3 col-sm-6 col-12 item chambre">
  316. <a href="images/chambre3.jpeg" class="image-popup d-block hover">
  317. <div class="relative overflow-hidden">
  318. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  319. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  320. <img src="images/chambre3.jpeg" class="w-100 hover-scale-1-2" alt="Bedroom 3">
  321. </div>
  322. </a>
  323. </div>
  324. <div class="col-md-3 col-sm-6 col-12 item salon">
  325. <a href="images/salon.jpeg" class="image-popup d-block hover">
  326. <div class="relative overflow-hidden">
  327. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  328. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  329. <img src="images/salon.jpeg" class="w-100 hover-scale-1-2" alt="Living Room">
  330. </div>
  331. </a>
  332. </div>
  333. <div class="col-md-3 col-sm-6 col-12 item salon">
  334. <a href="images/salon2.jpeg" class="image-popup d-block hover">
  335. <div class="relative overflow-hidden">
  336. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  337. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  338. <img src="images/salon2.jpeg" class="w-100 hover-scale-1-2" alt="Living Room 2">
  339. </div>
  340. </a>
  341. </div>
  342. <div class="col-md-3 col-sm-6 col-12 item salon">
  343. <a href="images/salon3.jpeg" class="image-popup d-block hover">
  344. <div class="relative overflow-hidden">
  345. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  346. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  347. <img src="images/salon3.jpeg" class="w-100 hover-scale-1-2" alt="Living Room 3">
  348. </div>
  349. </a>
  350. </div>
  351. <div class="col-md-3 col-sm-6 col-12 item salon">
  352. <a href="images/salon4.jpeg" class="image-popup d-block hover">
  353. <div class="relative overflow-hidden">
  354. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  355. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  356. <img src="images/salon4.jpeg" class="w-100 hover-scale-1-2" alt="Living Room 4">
  357. </div>
  358. </a>
  359. </div>
  360. <div class="col-md-3 col-sm-6 col-12 item salon">
  361. <a href="images/salon5.jpeg" class="image-popup d-block hover">
  362. <div class="relative overflow-hidden">
  363. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  364. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  365. <img src="images/salon5.jpeg" class="w-100 hover-scale-1-2" alt="Living Room 5">
  366. </div>
  367. </a>
  368. </div>
  369. <div class="col-md-3 col-sm-6 col-12 item cuisine">
  370. <a href="images/cuisine.jpeg" class="image-popup d-block hover">
  371. <div class="relative overflow-hidden">
  372. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  373. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  374. <img src="images/cuisine.jpeg" class="w-100 hover-scale-1-2" alt="Kitchen">
  375. </div>
  376. </a>
  377. </div>
  378. <div class="col-md-3 col-sm-6 col-12 item salledebain">
  379. <a href="images/salledebain.jpeg" class="image-popup d-block hover">
  380. <div class="relative overflow-hidden">
  381. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  382. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  383. <img src="images/salledebain.jpeg" class="w-100 hover-scale-1-2" alt="Bathroom">
  384. </div>
  385. </a>
  386. </div>
  387. <div class="col-md-3 col-sm-6 col-12 item salledebain">
  388. <a href="images/salledebain2.jpeg" class="image-popup d-block hover">
  389. <div class="relative overflow-hidden">
  390. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  391. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  392. <img src="images/salledebain2.jpeg" class="w-100 hover-scale-1-2" alt="Bathroom 2">
  393. </div>
  394. </a>
  395. </div>
  396. <div class="col-md-3 col-sm-6 col-12 item exterieur">
  397. <a href="images/exterieur.jpeg" class="image-popup d-block hover">
  398. <div class="relative overflow-hidden">
  399. <div class="absolute start-0 w-100 hover-op-1 p-5 abs-middle z-2 text-center text-white z-3">View</div>
  400. <div class="absolute start-0 w-100 h-100 overlay-dark-7 hover-op-1 z-2"></div>
  401. <img src="images/exterieur.jpeg" class="w-100 hover-scale-1-2" alt="Exterior">
  402. </div>
  403. </a>
  404. </div>
  405. </div>
  406. </div>
  407. </section>
  408. {# =================================================== #}
  409. {# ============ BOOKING SECTION (refactored) ========= #}
  410. {# =================================================== #}
  411. <section id="reservation" class="text-light">
  412. <div class="container">
  413. <div class="row g-4 justify-content-center">
  414. <div class="col-lg-6 text-center">
  415. <h2 class="wow fadeInUp" data-wow-delay=".2s">Book your stay</h2>
  416. </div>
  417. </div>
  418. <div class="row g-4 justify-content-center">
  419. <div class="col-lg-8">
  420. <div class="booking-card">
  421. {{ form_start(form) }}
  422. {# ----- CALENDAR ----- #}
  423. <div class="calendar-container" style="height:300px;">
  424. <input type="text" id="date_range_picker">
  425. </div>
  426. {# ----- DATES RECAP (just below the calendar) ----- #}
  427. <div class="row g-3 mt-2 dates-recap">
  428. <div class="col-4 text-center">
  429. <div class="recap-label">Arrival</div>
  430. <div class="recap-value" id="info-start">-</div>
  431. </div>
  432. <div class="col-4 text-center">
  433. <div class="recap-label">Departure</div>
  434. <div class="recap-value" id="info-end">-</div>
  435. </div>
  436. <div class="col-4 text-center">
  437. <div class="recap-label">Nights</div>
  438. <div class="recap-value" id="info-nights">-</div>
  439. </div>
  440. </div>
  441. <hr class="booking-divider">
  442. {# ----- COUNTERS ----- #}
  443. <h4 class="booking-title">Select rooms and guests</h4>
  444. <div class="counter-row">
  445. <div class="counter-label">
  446. <div class="counter-title">Bedrooms</div>
  447. <div class="counter-sub">Set by guest count</div>
  448. </div>
  449. <div class="counter-controls">
  450. <button type="button" class="counter-btn" data-action="dec" data-target="rooms">−</button>
  451. <span class="counter-value" id="cnt-rooms">1</span>
  452. <button type="button" class="counter-btn" data-action="inc" data-target="rooms">+</button>
  453. </div>
  454. </div>
  455. <div class="counter-row">
  456. <div class="counter-label">
  457. <div class="counter-title">Adults</div>
  458. </div>
  459. <div class="counter-controls">
  460. <button type="button" class="counter-btn" data-action="dec" data-target="adults">−</button>
  461. <span class="counter-value" id="cnt-adults">2</span>
  462. <button type="button" class="counter-btn" data-action="inc" data-target="adults">+</button>
  463. </div>
  464. </div>
  465. <div class="counter-row">
  466. <div class="counter-label">
  467. <div class="counter-title">Children</div>
  468. <div class="counter-sub">1 - 17 years</div>
  469. </div>
  470. <div class="counter-controls">
  471. <button type="button" class="counter-btn" data-action="dec" data-target="children">−</button>
  472. <span class="counter-value" id="cnt-children">0</span>
  473. <button type="button" class="counter-btn" data-action="inc" data-target="children">+</button>
  474. </div>
  475. </div>
  476. <div id="capacity-warning" class="capacity-warning" style="display:none;"></div>
  477. {# ----- PRICE RESULT ----- #}
  478. <div id="results" class="mt-3"></div>
  479. <hr class="booking-divider">
  480. {# ----- PERSONAL INFO ----- #}
  481. <div class="row g-3">
  482. <div class="col-md-6">
  483. <label class="booking-label">Last name</label>
  484. {{ form_widget(form.lastname) }}
  485. </div>
  486. <div class="col-md-6">
  487. <label class="booking-label">First name</label>
  488. {{ form_widget(form.name) }}
  489. </div>
  490. <div class="col-md-12">
  491. <label class="booking-label">Email address</label>
  492. {{ form_widget(form.email) }}
  493. </div>
  494. <div class="col-md-12">
  495. <label class="booking-label">Message</label>
  496. {{ form_widget(form.message) }}
  497. </div>
  498. </div>
  499. {# ----- T&C + SUBMIT ----- #}
  500. <div class="text-center mt-4 cgv-block">
  501. <input type="checkbox" id="acceptCGV" /> I accept the <a href="/terms" target="_blank" style="color:#212121; text-decoration:underline;">terms and conditions of sale</a>
  502. </div>
  503. <div class="text-center mt-3">
  504. <button type="submit" name="submit" value="1" class="btn-main" style="color:white;">Send booking request</button>
  505. </div>
  506. {# Hidden fields #}
  507. {{ form_widget(form.start) }}
  508. {{ form_widget(form.end) }}
  509. {{ form_widget(form.rooms) }}
  510. {{ form_widget(form.adults) }}
  511. {{ form_widget(form.children) }}
  512. {{ form_widget(form.persons) }}
  513. {{ form_rest(form) }}
  514. {{ form_end(form) }}
  515. </div>
  516. </div>
  517. </div>
  518. </div>
  519. </section>
  520. <section id="faq" class="text-light">
  521. <div class="container">
  522. <div class="row g-4 justify-content-center">
  523. <div class="col-lg-6 text-center">
  524. <h2 class="wow fadeInUp" data-wow-delay=".2s">FAQ</h2>
  525. </div>
  526. </div>
  527. <div class="row g-4 justify-content-center">
  528. <div class="col-lg-6 text-left" style="color:black !important;">
  529. <h3 style="margin-top:7px;">How can I book the accommodation?</h3>
  530. <div>You can book directly on our website and soon via partner platforms. Secure payment immediately confirms your reservation.</div>
  531. <h3 style="margin-top:7px;">What are the check-in and check-out times?</h3>
  532. <div>
  533. <ul>
  534. <li>Check-in: from 3:00 PM</li>
  535. <li>Check-out: before 11:00 AM</li>
  536. </ul>
  537. </div>
  538. <h3 style="margin-top:7px;">Is the accommodation fully equipped?</h3>
  539. <div> Yes, the apartment is fully furnished and equipped: kitchen, bedding, towels, appliances, Wi-Fi, and more.</div>
  540. <h3 style="margin-top:7px;">Is parking available?</h3>
  541. <div>Yes, private parking is available as an option at €24 per 24 hours or €150 per week (7 nights).</div>
  542. <h3 style="margin-top:7px;">Is the accommodation suitable for children?</h3>
  543. <div>Yes, we offer some baby amenities (baby cot, high chair, child-friendly tableware) upon request.</div>
  544. <h3 style="margin-top:7px;"> Are pets allowed?</h3>
  545. <div> Please contact us before booking if you wish to bring a pet. Only dogs are accepted, and prior approval is required.</div>
  546. <h3 style="margin-top:7px;">Can I cancel or modify my reservation?</h3>
  547. <div>Yes, our cancellation policy is provided at the time of booking:
  548. <ul>
  549. <li>85% refund if canceled more than 1 month before arrival</li>
  550. <li>50% refund if canceled between 1 month and 15 days before arrival</li>
  551. <li>No refund if canceled less than 15 days before arrival</li>
  552. </ul>
  553. </div>
  554. <h3 style="margin-top:7px;">Is the accommodation accessible by public transport?</h3>
  555. <div>Yes, major transport options are nearby. The tram station "Langstross, Grand'rue" is less than 100 meters from the apartment, and Strasbourg train station is a 10-minute walk.</div>
  556. <h3 style="margin-top:7px;">Are there shops or restaurants nearby?</h3>
  557. <div>Yes, you'll find supermarkets, cafés, restaurants, and tourist activities right at the foot of the building.</div>
  558. <h3 style="margin-top:7px;"> What should I do if there's a problem during my stay?</h3>
  559. <div> We are available 24/7 by email for emergencies or questions about the apartment. You can also call us from 9:00 AM to 8:00 PM at +33 7 61 05 82 43, and we are available on-site on the ground floor of the building from Tuesday to Saturday, 10:00 AM to 6:45 PM.
  560. </div>
  561. </div>
  562. </div>
  563. </div>
  564. </section>
  565. {% endblock body %}
  566. {% block footerjs %}
  567. <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
  568. {# Pricing matrix + simulation URL exposed to JS #}
  569. <script>
  570. window.PRICING_MATRIX = {{ pricingMatrix|json_encode|raw }};
  571. window.SIMULATION_URL = "{{ path('simulation_pricing') }}";
  572. console.log('📋 Off-peak pricing matrix loaded:', window.PRICING_MATRIX);
  573. console.log('🔗 Simulation URL:', window.SIMULATION_URL);
  574. </script>
  575. <script>
  576. // ===========================================================
  577. // Bedrooms / adults / children logic
  578. //
  579. // Physical layout:
  580. // - Bedroom A: double bed → 2 people
  581. // - Bedroom B: double bed → 2 people
  582. // - Bedroom C: bunk beds → 4 people (adults/children mix)
  583. //
  584. // The number of bedrooms is DETERMINED automatically (no choice):
  585. //
  586. // WITHOUT CHILDREN:
  587. // 1-2 ad → 1 br
  588. // 3-4 ad → 2 br
  589. // 5-8 ad → 3 br
  590. //
  591. // WITH CHILDREN:
  592. // 1-2 ad → 2 br (bedroom A + bunk room)
  593. // 3+ ad → 3 br (A + B + bunk room)
  594. //
  595. // Constraints: adults + children ≤ 8, children ≤ 4, adults ≥ 1
  596. // ===========================================================
  597. function roomsForCombo(adults, children) {
  598. if (children > 0) {
  599. return adults <= 2 ? 2 : 3;
  600. }
  601. if (adults <= 2) return 1;
  602. if (adults <= 4) return 2;
  603. return 3;
  604. }
  605. function canAddAdult(state) {
  606. if (state.adults >= 8) return false;
  607. return (state.adults + 1 + state.children) <= 8;
  608. }
  609. function canAddChild(state) {
  610. if (state.children >= 4) return false;
  611. return (state.adults + state.children + 1) <= 8;
  612. }
  613. const state = { rooms: 1, adults: 2, children: 0 };
  614. function updateDisplay() {
  615. // Bedrooms count is recalculated automatically on each update
  616. state.rooms = roomsForCombo(state.adults, state.children);
  617. $('#cnt-rooms').text(state.rooms);
  618. $('#cnt-adults').text(state.adults);
  619. $('#cnt-children').text(state.children);
  620. $('#reservation_form_rooms').val(state.rooms);
  621. $('#reservation_form_adults').val(state.adults);
  622. $('#reservation_form_children').val(state.children);
  623. $('#reservation_form_persons').val(state.adults + state.children);
  624. $('.counter-btn').each(function() {
  625. const target = $(this).data('target');
  626. const action = $(this).data('action');
  627. let disabled = false;
  628. if (target === 'rooms') {
  629. // Bedrooms = always locked (set by adults/children)
  630. disabled = true;
  631. }
  632. if (target === 'adults') {
  633. if (action === 'dec' && state.adults <= 1) disabled = true;
  634. if (action === 'inc' && !canAddAdult(state)) disabled = true;
  635. }
  636. if (target === 'children') {
  637. if (action === 'dec' && state.children <= 0) disabled = true;
  638. if (action === 'inc' && !canAddChild(state)) disabled = true;
  639. }
  640. $(this).prop('disabled', disabled).toggleClass('disabled', disabled);
  641. });
  642. $('#capacity-warning').hide();
  643. }
  644. $(document).on('click', '.counter-btn', function() {
  645. if ($(this).prop('disabled')) return;
  646. const target = $(this).data('target');
  647. const action = $(this).data('action');
  648. // No action on "rooms" (always disabled)
  649. if (target === 'adults') {
  650. if (action === 'inc' && canAddAdult(state)) state.adults++;
  651. if (action === 'dec' && state.adults > 1) state.adults--;
  652. }
  653. if (target === 'children') {
  654. if (action === 'inc' && canAddChild(state)) state.children++;
  655. if (action === 'dec' && state.children > 0) state.children--;
  656. }
  657. updateDisplay();
  658. updateSimulation();
  659. });
  660. function updateSimulation() {
  661. const start = $("#reservation_form_start").val();
  662. const end = $("#reservation_form_end").val();
  663. const persons = state.adults + state.children;
  664. console.log('🔄 updateSimulation', { start, end, rooms: state.rooms, adults: state.adults, children: state.children });
  665. if (!start || !end || persons < 1) {
  666. $("#results").html('');
  667. return;
  668. }
  669. $("#results").html('<div class="loading" style="color:#212121;">Calculating...</div>');
  670. $.post(
  671. window.SIMULATION_URL,
  672. { start: start, end: end, rooms: state.rooms, adults: state.adults, children: state.children }
  673. )
  674. .done(function(data) {
  675. console.log('✅ Simulation OK');
  676. $("#results").html(data);
  677. })
  678. .fail(function(xhr) {
  679. console.error('❌ Simulation KO', xhr.status, xhr.responseText);
  680. $("#results").html('<div class="error" style="color:#a00;">Error calculating price (' + xhr.status + ')</div>');
  681. });
  682. }
  683. $(document).ready(function() {
  684. updateDisplay();
  685. $('button[type="submit"]').prop('disabled', true);
  686. $('#acceptCGV').change(function() {
  687. $('button[type="submit"]').prop('disabled', !$(this).is(':checked'));
  688. });
  689. });
  690. </script>
  691. <script>
  692. // ===========================================================
  693. // Flatpickr calendar
  694. // ===========================================================
  695. $(document).ready(function() {
  696. let bookedRanges = [];
  697. $.ajax({
  698. url: '{{ env('WEBHOOK_CALENDAR_AVAILABILITY') }}',
  699. method: 'POST',
  700. contentType: 'application/json',
  701. data: ''
  702. })
  703. .done(function(data) {
  704. bookedRanges = data.bookings || [];
  705. initDatePicker();
  706. })
  707. .fail(function() { initDatePicker(); });
  708. function parseLocalDate(dateStr) {
  709. const [year, month, day] = dateStr.split('-').map(Number);
  710. return new Date(year, month - 1, day);
  711. }
  712. const isMobile = window.innerWidth <= 900;
  713. function initDatePicker() {
  714. flatpickr("#date_range_picker", {
  715. inline: true,
  716. mode: "range",
  717. minDate: new Date(new Date().setDate(new Date().getDate() + 1)),
  718. dateFormat: "Y-m-d",
  719. showMonths: isMobile ? 1 : 2,
  720. disable: [
  721. function(date) {
  722. const checkDate = new Date(date);
  723. checkDate.setHours(0, 0, 0, 0);
  724. return bookedRanges.some(range => {
  725. const start = parseLocalDate(range.start_date);
  726. const end = parseLocalDate(range.end_date);
  727. return checkDate >= start && checkDate <= end;
  728. });
  729. }
  730. ],
  731. onChange: function(selectedDates, dateStr, instance) {
  732. if (selectedDates.length === 2) {
  733. const start = selectedDates[0];
  734. const end = selectedDates[1];
  735. if (isRangeAvailable(start, end)) {
  736. $('#reservation_form_start').val(formatDate(start)).trigger('change');
  737. $('#reservation_form_end').val(formatDate(end)).trigger('change');
  738. updateSelectedInfo(start, end);
  739. updateSimulation();
  740. } else {
  741. alert('⚠️ This period contains already booked dates.');
  742. instance.clear();
  743. clearSymfonyFields();
  744. hideSelectedInfo();
  745. $("#results").html('');
  746. }
  747. } else {
  748. clearSymfonyFields();
  749. hideSelectedInfo();
  750. $("#results").html('');
  751. }
  752. },
  753. onDayCreate: function(dObj, dStr, fp, dayElem) {
  754. const date = new Date(dayElem.dateObj);
  755. date.setHours(0, 0, 0, 0);
  756. const isBooked = bookedRanges.some(range => {
  757. const start = parseLocalDate(range.start_date);
  758. const end = parseLocalDate(range.end_date);
  759. return date >= start && date <= end;
  760. });
  761. if (isBooked) {
  762. dayElem.classList.add('booked');
  763. dayElem.title = 'Unavailable date (booked)';
  764. } else if (date >= new Date()) {
  765. dayElem.classList.add('available');
  766. }
  767. }
  768. });
  769. }
  770. function isRangeAvailable(startDate, endDate) {
  771. let currentDate = new Date(startDate);
  772. currentDate.setHours(0, 0, 0, 0);
  773. const endCheck = new Date(endDate);
  774. endCheck.setHours(0, 0, 0, 0);
  775. while (currentDate <= endCheck) {
  776. const isBooked = bookedRanges.some(range => {
  777. const start = parseLocalDate(range.start_date);
  778. const end = parseLocalDate(range.end_date);
  779. return currentDate >= start && currentDate <= end;
  780. });
  781. if (isBooked) return false;
  782. currentDate.setDate(currentDate.getDate() + 1);
  783. }
  784. return true;
  785. }
  786. function updateSelectedInfo(start, end) {
  787. const nights = Math.ceil((end - start) / (1000 * 60 * 60 * 24));
  788. $('#info-start').text(start.toLocaleDateString('en-GB'));
  789. $('#info-end').text(end.toLocaleDateString('en-GB'));
  790. $('#info-nights').text(nights + ' night' + (nights > 1 ? 's' : ''));
  791. }
  792. function hideSelectedInfo() {
  793. $('#info-start').text('-');
  794. $('#info-end').text('-');
  795. $('#info-nights').text('-');
  796. }
  797. function clearSymfonyFields() {
  798. $('#reservation_form_start').val('');
  799. $('#reservation_form_end').val('');
  800. }
  801. function formatDate(date) {
  802. return date.getFullYear() + '-' +
  803. String(date.getMonth() + 1).padStart(2, '0') + '-' +
  804. String(date.getDate()).padStart(2, '0');
  805. }
  806. });
  807. </script>
  808. {% endblock footerjs %}
  809. {% block css %}
  810. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
  811. <style>
  812. /* ===========================================================
  813. Booking card (white border, transparent bg, black text)
  814. =========================================================== */
  815. .booking-card {
  816. background: transparent;
  817. border: 2px solid white;
  818. border-radius: 12px;
  819. padding: 32px;
  820. color: #212121;
  821. }
  822. .booking-title {
  823. color: #212121;
  824. font-weight: 700;
  825. margin-bottom: 24px;
  826. }
  827. .booking-divider {
  828. border-top: 1px solid rgba(0,0,0,0.15);
  829. margin: 28px 0;
  830. }
  831. .booking-label {
  832. color: #212121;
  833. font-weight: 500;
  834. margin-bottom: 6px;
  835. display: block;
  836. }
  837. .booking-card input.form-control,
  838. .booking-card textarea.form-control {
  839. background: rgba(255,255,255,0.85) !important;
  840. border: 1px solid rgba(0,0,0,0.2) !important;
  841. color: #212121 !important;
  842. }
  843. .booking-card input.form-control::placeholder,
  844. .booking-card textarea.form-control::placeholder {
  845. color: rgba(0,0,0,0.4) !important;
  846. }
  847. .booking-card input.form-control:focus,
  848. .booking-card textarea.form-control:focus {
  849. border-color: #212121 !important;
  850. box-shadow: 0 0 0 0.2rem rgba(0,0,0,0.08) !important;
  851. }
  852. .cgv-block { color: #212121; }
  853. .cgv-block a { color: #212121; text-decoration: underline; }
  854. .dates-recap { color: #212121; }
  855. .recap-label {
  856. font-size: 13px;
  857. text-transform: uppercase;
  858. letter-spacing: 0.5px;
  859. opacity: 0.7;
  860. }
  861. .recap-value {
  862. font-size: 18px;
  863. font-weight: 600;
  864. margin-top: 4px;
  865. }
  866. .counter-row {
  867. display: flex;
  868. justify-content: space-between;
  869. align-items: center;
  870. padding: 14px 0;
  871. border-bottom: 1px solid rgba(0,0,0,0.1);
  872. }
  873. .counter-row:last-of-type { border-bottom: none; }
  874. .counter-title { font-size: 16px; font-weight: 500; color: #212121; }
  875. .counter-sub { font-size: 12px; color: rgba(0,0,0,0.6); margin-top: 2px; }
  876. .counter-controls { display: flex; align-items: center; gap: 12px; }
  877. .counter-btn {
  878. width: 40px;
  879. height: 40px;
  880. border-radius: 8px;
  881. border: 1.5px solid #212121;
  882. background: transparent;
  883. color: #212121;
  884. font-size: 20px;
  885. font-weight: 600;
  886. line-height: 1;
  887. cursor: pointer;
  888. transition: all 0.15s;
  889. }
  890. .counter-btn:hover:not(.disabled) { background: rgba(0,0,0,0.06); }
  891. .counter-btn.disabled,
  892. .counter-btn:disabled {
  893. border-color: rgba(0,0,0,0.2);
  894. color: rgba(0,0,0,0.25);
  895. cursor: not-allowed;
  896. background: transparent;
  897. }
  898. .counter-value {
  899. min-width: 28px;
  900. text-align: center;
  901. font-size: 16px;
  902. font-weight: 500;
  903. color: #212121;
  904. }
  905. .capacity-warning {
  906. margin-top: 12px;
  907. padding: 10px 14px;
  908. background: rgba(255,180,0,0.18);
  909. border: 1px solid rgba(180,120,0,0.5);
  910. border-radius: 6px;
  911. color: #8a4500;
  912. font-size: 14px;
  913. }
  914. .calendar-container {
  915. margin: 0 0 8px 0;
  916. width: 100%;
  917. display: flex;
  918. justify-content: center;
  919. }
  920. #date_range_picker { display: none; }
  921. .flatpickr-calendar.inline {
  922. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  923. border-radius: 8px;
  924. }
  925. .flatpickr-day.booked {
  926. background-color: #ff4444 !important;
  927. color: white !important;
  928. cursor: not-allowed !important;
  929. position: relative;
  930. }
  931. .flatpickr-day.booked::after {
  932. content: "✕";
  933. position: absolute;
  934. top: 50%; left: 50%;
  935. transform: translate(-50%, -50%);
  936. font-size: 20px;
  937. font-weight: bold;
  938. }
  939. .flatpickr-day.booked:hover { background-color: #ff0000 !important; }
  940. .flatpickr-day.available:hover { background-color: #28a745; color: white; }
  941. @media (max-width: 768px) {
  942. .booking-card { padding: 20px; }
  943. .calendar-container { margin: 0 0 8px 0; padding: 0; }
  944. .flatpickr-calendar.inline { width: 100% !important; max-width: 100% !important; }
  945. .flatpickr-months { padding: 8px 10px; }
  946. .flatpickr-days { width: 100% !important; }
  947. .dayContainer {
  948. width: 100% !important;
  949. min-width: 100% !important;
  950. max-width: 100% !important;
  951. }
  952. .flatpickr-day {
  953. height: 40px !important;
  954. line-height: 40px !important;
  955. flex-basis: 14.2857% !important;
  956. max-width: 14.2857% !important;
  957. }
  958. }
  959. </style>
  960. {% endblock css %}