templates/ticket_modal.html.twig line 1

Open in your IDE?
  1. <head>
  2.     <link rel="stylesheet" href="{{ asset('css/ticket-modal.css') }}">
  3. </head>
  4. {% set ticketInfo = has_unscratched_ticket() %}
  5. {% if ticketInfo.exists %}
  6.     <div class="modal fade" id="ticketModal" tabindex="-1" aria-labelledby="ticketModalLabel" aria-hidden="true">
  7.         <div class="modal-dialog modal-dialog-centered">
  8.             <div class="modal-content">
  9.                 <div class="modal-header">
  10.                     <h5 class="modal-title" id="ticketModalLabel">🎁 Vous avez un ticket à gratter ! 🎉</h5>
  11.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  12.                         <span aria-hidden="true">&times;</span>
  13.                     </button>
  14.                 </div>
  15.                 <div class="modal-body">
  16.                     <div class="scratch-card relative border-4 border-gray-300 rounded-lg p-3 w-full h-80 bg-white">
  17.                         <div class="scratch-card-cover-container absolute z-10 top-0 left-0 rounded-lg w-full h-full transition-opacity duration-400">
  18.                             <canvas class="scratch-card-canvas absolute z-20 top-0 left-0 w-full h-full cursor-grab touch-none" width="520" height="520"></canvas>
  19.                             <img class="scratch-card-canvas-render hidden absolute top-0 left-0 w-full h-full bg-transparent transition-opacity duration-200" alt="">
  20.                             <div class="scratch-card-cover flex justify-center items-center absolute top-0 left-0 w-full h-full bg-gray-300 bg-gradient-to-r from-gray-300 via-gray-200 to-gray-100 overflow-hidden">
  21.                                 <svg class="scratch-card-cover-background" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320"><path d="M72.417 85.633a2 2 0 1 0-3.42-2.075l-3.113 5.129a2 2 0 1 0 3.42 2.075l3.113-5.129zm-8.301 13.679a2 2 0 1 0-3.42-2.075l-3.113 5.129a2 2 0 0 0 3.42 2.075l3.113-5.129zm11.997 1.432a2 2 0 0 1-2.747.672l-5.129-3.113a2 2 0 1 1 2.075-3.42l5.129 3.113a2 2 0 0 1 .672 2.748zm-16.425-7.629a2 2 0 1 0 2.075-3.42l-5.129-3.113a2 2 0 1 0-2.075 3.42l5.129 3.113z"/><path fill-rule="evenodd" d="M262.093 121.254a2 2 0 1 0-3.873-1.001l-1.502 5.809a2 2 0 1 0 3.873 1.001l1.502-5.809zm-4.004 15.491a2 2 0 1 0-3.873-1.001l-1.502 5.809a2 2 0 1 0 3.873 1.001l1.502-5.809zm11.9-2.088a2 2 0 0 1-2.437 1.436l-5.809-1.502a2 2 0 1 1 1.001-3.873l5.809 1.502a2 2 0 0 1 1.436 2.437zm-17.927-2.569a2 2 0 0 0 2.437-1.436 2 2 0 0 0-1.436-2.436l-5.809-1.502a2 2 0 0 0-2.437 1.435 2 2 0 0 0 1.436 2.437l5.809 1.502z"/><path d="M196.858 235.528a2 2 0 0 0-2.437-1.435 2 2 0 0 0-1.435 2.437l1.504 5.809a2 2 0 1 0 3.872-1.003l-1.504-5.808zm4.01 15.489a2 2 0 0 0-2.437-1.435 2 2 0 0 0-1.435 2.437l1.504 5.809a2 2 0 1 0 3.872-1.003l-1.504-5.808zm9.396-7.597a2 2 0 0 1-1.435 2.437l-5.809 1.504a2 2 0 1 1-1.002-3.873l5.808-1.503a2 2 0 0 1 2.438 1.435zm-16.924 6.447a2 2 0 1 0-1.003-3.873l-5.808 1.504a2 2 0 1 0 1.002 3.873l5.809-1.504zm-129.604 20.16a2 2 0 1 0 3.032-2.609l-3.913-4.548a2 2 0 1 0-3.032 2.609l3.913 4.548zm14.42-3.173a2 2 0 0 0-2.609-3.032l-4.548 3.914a2 2 0 0 0 2.609 3.032l4.548-3.914zM66.027 277.29a2 2 0 1 0-2.609-3.032l-4.548 3.913a2 2 0 1 0 2.609 3.032l4.548-3.913zm10.965 5.077a2 2 0 0 1-2.82-.212l-3.913-4.548a2 2 0 0 1 3.032-2.609l3.913 4.548a2 2 0 0 1-.212 2.821z"/><g fill-rule="evenodd"><path d="M138.629 74.686a2 2 0 0 1 1.66 2.29l-.787 4.936c-1.184 7.422-10.292 10.36-15.589 5.028-3.163-3.183-8.602-1.12-8.858 3.36l-.325 5.701a2 2 0 1 1-3.994-.228l.326-5.701c.452-7.935 10.086-11.59 15.688-5.952 2.991 3.01 8.134 1.351 8.802-2.839l.787-4.936a2 2 0 0 1 2.29-1.66zm129.754 151.907c-3.652-2.608-8.668.338-8.169 4.798.836 7.469-7.16 12.727-13.686 8.999l-4.34-2.479a2 2 0 0 1 1.984-3.473l4.34 2.479c3.684 2.104 8.199-.864 7.727-5.081-.884-7.899 8-13.117 14.469-8.498l4.647 3.318a2 2 0 1 1-2.325 3.256l-4.647-3.319zM219.458 34.1a2 2 0 0 1 .733-2.732l4.33-2.497c6.51-3.754 14.528 1.471 13.723 8.943-.481 4.462 4.547 7.387 8.189 4.764l4.633-3.338a2 2 0 0 1 2.338 3.246l-4.633 3.338c-6.45 4.646-15.356-.536-14.504-8.439.455-4.219-4.073-7.169-7.748-5.049l-4.33 2.497a2 2 0 0 1-2.731-.733zM43.604 115.569a2 2 0 0 1-1.242 2.541l-4.727 1.623c-4.013 1.378-4.764 6.729-1.285 9.158 6.517 4.551 4.571 14.669-3.169 16.478l-5.56 1.3a2 2 0 0 1-.91-3.895l5.561-1.3c4.37-1.021 5.468-6.734 1.789-9.303-6.162-4.303-4.832-13.78 2.276-16.221l4.727-1.623a2 2 0 0 1 2.541 1.242zM77.095 219.2a2 2 0 0 1 2.043-1.956l4.997.108c7.514.162 11.664 8.786 7.103 14.76-2.723 3.566.061 8.674 4.535 8.318l5.693-.454a2 2 0 1 1 .317 3.988l-5.692.453c-7.923.631-12.855-8.415-8.031-14.733 2.575-3.372.232-8.241-4.01-8.333l-4.997-.108a2 2 0 0 1-1.956-2.043zm-14.771-64.574l-.78-.855.129 1.15a8 8 0 0 1-4.645 8.177l-1.054.478h0l1.134.233a8 8 0 0 1 6.341 6.945l.129 1.15.572-1.006a8 8 0 0 1 8.565-3.885l1.134.232-.78-.854a8 8 0 0 1-1.048-9.346l.572-1.006-1.054.478a8 8 0 0 1-9.212-1.891zm-8.003 5.308c-3.529 1.6-2.948 6.781.848 7.561l1.134.232a4 4 0 0 1 3.171 3.473l.129 1.15c.432 3.85 5.539 4.899 7.453 1.53l.572-1.007a4 4 0 0 1 4.282-1.942l1.134.233c3.796.779 6.371-3.754 3.758-6.616l-.78-.854a4 4 0 0 1-.524-4.673l.572-1.007c1.914-3.369-1.602-7.219-5.13-5.618l-1.054.478a4 4 0 0 1-4.606-.946l-.78-.854c-2.613-2.862-7.361-.708-6.929 3.143l.129 1.15a4 4 0 0 1-2.323 4.089l-1.054.478zM110.956 46.42l1.794-1.614-2.4.251a8 8 0 0 1-8.142-4.707l-.98-2.205-.503 2.36a8 8 0 0 1-6.993 6.289l-2.4.251 2.089 1.208-1.856 3.209 1.856-3.209a8 8 0 0 1 3.82 8.594l-.503 2.36 1.794-1.614a8 8 0 0 1 9.353-.977l2.089 1.208-.98-2.205a8 8 0 0 1 1.961-9.198zm4.469 1.36c2.881-2.591.764-7.355-3.09-6.952l-2.401.251a4 4 0 0 1-4.071-2.354l-.98-2.205c-1.574-3.541-6.759-2.999-7.567.791l-.503 2.36a4 4 0 0 1-3.496 3.144l-2.4.251c-3.854.403-4.941 5.502-1.586 7.441l2.089 1.208a4 4 0 0 1 1.91 4.297l-.503 2.36c-.808 3.79 3.706 6.399 6.587 3.808l1.794-1.614a4 4 0 0 1 4.677-.489l2.089 1.208c3.355 1.939 7.232-1.547 5.658-5.088l-.981-2.205a4 4 0 0 1 .981-4.599l1.794-1.614zm104.847 31.673l.737-4.207-3.069 2.97a8 8 0 0 1-9.312 1.32l-3.773-2.001 1.877 3.836a8 8 0 0 1-1.623 9.264l-3.068 2.97 4.228-.599a8 8 0 0 1 8.309 4.406l1.876 3.837.737-4.207a8 8 0 0 1 6.758-6.541l4.228-.599-3.773-2.001a8 8 0 0 1-4.132-8.448zm4.677-3.517c.668-3.817-3.938-6.259-6.722-3.564l-3.069 2.97a4 4 0 0 1-4.656.66l-3.773-2.001c-3.423-1.815-7.17 1.811-5.467 5.292l1.877 3.836a4 4 0 0 1-.812 4.632l-3.068 2.97c-2.785 2.695-.494 7.378 3.343 6.835l4.228-.599a4 4 0 0 1 4.155 2.203l1.876 3.836c1.703 3.481 6.865 2.75 7.533-1.067l.737-4.207a4 4 0 0 1 3.379-3.27l4.229-.599c3.836-.544 4.736-5.679 1.312-7.494l-3.773-2.001a4 4 0 0 1-2.066-4.224l.737-4.207zM167.501 279.54l1.588-.913-1.821-.195a8 8 0 0 1-6.976-6.307l-.377-1.793-.748 1.672a8 8 0 0 1-8.155 4.685l-1.821-.195 1.358 1.228a8 8 0 0 1 1.936 9.203l-.748 1.672 1.588-.912a8 8 0 0 1 9.351 1.002l1.359 1.229-.377-1.793a8 8 0 0 1 3.843-8.583zm3.581 2.556c3.36-1.931 2.287-7.033-1.566-7.446l-1.822-.195a4 4 0 0 1-3.487-3.154l-.378-1.792c-.797-3.792-5.981-4.348-7.565-.812l-.748 1.672a4 4 0 0 1-4.077 2.343l-1.822-.195c-3.853-.414-5.983 4.345-3.109 6.944l1.359 1.228a4 4 0 0 1 .968 4.602l-.749 1.672c-1.583 3.536 2.284 7.033 5.644 5.103l1.588-.913a4 4 0 0 1 4.675.501l1.359 1.229c2.874 2.599 7.395.001 6.597-3.79l-.377-1.793a4 4 0 0 1 1.922-4.292l1.588-.912zm123.357-94.108l1.83-.08-1.528-1.011a8 8 0 0 1-3.298-8.807l.489-1.765-1.433 1.141a8 8 0 0 1-9.395.414l-1.528-1.01.642 1.715a8 8 0 0 1-2.509 9.064l-1.433 1.141 1.83-.081c3.462-.153 6.629 1.942 7.845 5.187l.642 1.716.489-1.766a8 8 0 0 1 7.357-5.858zm2.006 3.916c3.872-.171 5.262-5.196 2.03-7.333l-1.528-1.01a4 4 0 0 1-1.649-4.404l.489-1.765c1.034-3.735-3.315-6.61-6.346-4.197l-1.433 1.141a4 4 0 0 1-4.698.208l-1.528-1.011c-3.232-2.137-7.311 1.111-5.952 4.739l.642 1.716a4 4 0 0 1-1.255 4.532l-1.433 1.141c-3.031 2.413-1.203 7.296 2.668 7.125l1.83-.081a4 4 0 0 1 3.923 2.594l.642 1.715c1.359 3.629 6.567 3.399 7.601-.335l.489-1.765a4 4 0 0 1 3.678-2.929l1.83-.081z"/><use href="#B"/><use href="#B" x="103" y="30"/><use href="#B" x="-34" y="189"/><use href="#B" x="64" y="126"/><use href="#B" x="-128" y="-2"/><use href="#B" x="-128" y="161"/><use href="#B" x="71" y="219"/></g><path d="M201.469 137.109H177.45a24.65 24.65 0 0 0 6.167-3.906c1.284-1.141 2.317-2.535 3.037-4.095s1.108-3.25 1.143-4.967a13.98 13.98 0 0 0-.982-5.566 13.96 13.96 0 0 0-3.105-4.722 13.98 13.98 0 0 0-4.722-3.106 13.96 13.96 0 0 0-5.566-.981c-1.717.035-3.408.424-4.968 1.143a12.48 12.48 0 0 0-4.095 3.036c-2.885 3.257-4.702 7.5-5.859 11.568-1.147-4.068-2.964-8.301-5.859-11.568a12.48 12.48 0 0 0-4.095-3.036c-1.56-.719-3.251-1.108-4.968-1.143a13.96 13.96 0 0 0-5.566.981 13.98 13.98 0 0 0-4.722 3.106 13.96 13.96 0 0 0-3.105 4.722 13.98 13.98 0 0 0-.982 5.566c.035 1.717.424 3.408 1.143 4.967s1.753 2.954 3.037 4.095a24.65 24.65 0 0 0 6.167 3.906h-24.019a5.86 5.86 0 0 0-4.143 1.717c-1.099 1.098-1.716 2.589-1.716 4.143v15.625a5.86 5.86 0 0 0 5.859 5.859h1.953v33.203a5.86 5.86 0 0 0 5.86 5.86h70.312a5.86 5.86 0 0 0 5.86-5.86v-33.203h1.953a5.86 5.86 0 0 0 5.859-5.859v-15.625c0-1.554-.617-3.045-1.716-4.143a5.86 5.86 0 0 0-4.143-1.717zm-34.18-20.576a8.6 8.6 0 0 1 6.25-2.861h.298c1.345.001 2.676.272 3.915.797s2.36 1.292 3.297 2.257a10.05 10.05 0 0 1 2.159 3.361c.488 1.253.72 2.592.683 3.936-.022 1.183-.287 2.349-.779 3.424s-1.201 2.038-2.083 2.826c-5.903 5.225-16.147 6.451-20.508 6.734.303-4.326 1.524-14.546 6.768-20.474zm-34.18 7.49c-.037-1.344.195-2.683.683-3.936a10.05 10.05 0 0 1 2.159-3.361c.937-.965 2.058-1.733 3.297-2.257s2.57-.796 3.915-.797h.298a8.6 8.6 0 0 1 6.25 2.861c5.229 5.904 6.45 16.148 6.733 20.508-4.34-.283-14.585-1.509-20.507-6.733-.88-.796-1.585-1.765-2.071-2.847a8.6 8.6 0 0 1-.757-3.438zm-19.531 34.571v-15.625c0-.518.206-1.015.572-1.381a1.95 1.95 0 0 1 1.381-.572h41.016v19.531h-41.016c-.518 0-1.015-.206-1.381-.572s-.572-.863-.572-1.381zm7.813 39.062v-33.203h35.156v35.156h-33.203a1.95 1.95 0 0 1-1.953-1.953zm74.218 0a1.95 1.95 0 0 1-1.953 1.953h-33.203v-35.156h35.156v33.203zm7.813-39.062c0 .518-.206 1.015-.572 1.381s-.863.572-1.381.572h-41.016v-19.531h41.016a1.95 1.95 0 0 1 1.381.572c.366.366.572.863.572 1.381v15.625z"/><defs><path id="B" d="M172 57a4 4 0 1 0 0-8 4 4 0 1 0 0 8zm0 4a8 8 0 1 0 0-16 8 8 0 1 0 0 16z"/></defs></svg>
  22.                             </div>
  23.                         </div>
  24.                         <img class="scratch-card-image rounded-lg w-full h-full object-contain filter drop-shadow" src="{{ asset(ticketInfo['ticket'].isWinning ? ticketInfo['ticket'].qrCodePath : 'images/ticket-lost.png') }}" alt="Ticket à gratter">
  25.                     </div>
  26.                     <p class="scratch-card-text text-center mt-4">🎁 Grattez pour découvrir votre surprise !</p>
  27.                 </div>
  28.             </div>
  29.         </div>
  30.     </div>
  31.     <svg width="0" height="0">
  32.         <filter id="remove-black" color-interpolation-filters="sRGB">
  33.             <feColorMatrix
  34.                     type="matrix"
  35.                     values="1 0 0 0 0
  36.                         0 1 0 0 0
  37.                         0 0 1 0 0
  38.                         -1 -1 -1 0 1"
  39.                     result="black-pixels"
  40.             />
  41.             <feComposite in="SourceGraphic" in2="black-pixels" operator="out" />
  42.         </filter>
  43.         <filter id="noise">
  44.             <feTurbulence baseFrequency="0.5"></feTurbulence>
  45.         </filter>
  46.     </svg>
  47.     <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"></script>
  48.     <script>
  49.         document.addEventListener('DOMContentLoaded', function() {
  50.             var modalElement = document.getElementById('ticketModal');
  51.             var modal = new bootstrap.Modal(modalElement);
  52.             $('#ticketModal').on('shown.bs.modal', function () {
  53.                 const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
  54.                 const scratchCardCover = document.querySelector('.scratch-card-cover');
  55.                 const scratchCardCanvasRender = document.querySelector('.scratch-card-canvas-render');
  56.                 const scratchCardCoverContainer = document.querySelector('.scratch-card-cover-container');
  57.                 const scratchCardText = document.querySelector('.scratch-card-text');
  58.                 const scratchCardImage = document.querySelector('.scratch-card-image');
  59.                 const canvas = document.querySelector('canvas');
  60.                 const context = canvas.getContext('2d');
  61.                 let isPointerDown = false;
  62.                 let positionX;
  63.                 let positionY;
  64.                 let clearDetectionTimeout = null;
  65.                 const devicePixelRatio = window.devicePixelRatio || 1;
  66.                 const canvasWidth = canvas.offsetWidth * devicePixelRatio;
  67.                 const canvasHeight = canvas.offsetHeight * devicePixelRatio;
  68.                 canvas.width = canvasWidth;
  69.                 canvas.height = canvasHeight;
  70.                 context.scale(devicePixelRatio, devicePixelRatio);
  71.                 if (isSafari) {
  72.                     canvas.classList.add('hidden');
  73.                 }
  74.                 canvas.addEventListener('pointerdown', (e) => {
  75.                     scratchCardCover.classList.remove('shine');
  76.                     ({ x: positionX, y: positionY } = getPosition(e));
  77.                     clearTimeout(clearDetectionTimeout);
  78.                     canvas.addEventListener('pointermove', plot);
  79.                     window.addEventListener('pointerup', (e) => {
  80.                         canvas.removeEventListener('pointermove', plot);
  81.                         clearDetectionTimeout = setTimeout(() => {
  82.                             checkBlackFillPercentage();
  83.                         }, 500);
  84.                     }, { once: true });
  85.                 });
  86.                 const checkBlackFillPercentage = () => {
  87.                     const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight);
  88.                     const pixelData = imageData.data;
  89.                     let blackPixelCount = 0;
  90.                     for (let i = 0; i < pixelData.length; i += 4) {
  91.                         const red = pixelData[i];
  92.                         const green = pixelData[i + 1];
  93.                         const blue = pixelData[i + 2];
  94.                         const alpha = pixelData[i + 3];
  95.                         if (red === 0 && green === 0 && blue === 0 && alpha === 255) {
  96.                             blackPixelCount++;
  97.                         }
  98.                     }
  99.                     const blackFillPercentage = blackPixelCount * 100 / (canvasWidth * canvasHeight);
  100.                     if (blackFillPercentage >= 20) {
  101.                         fetch('/scratch-ticket', {
  102.                             method: 'POST',
  103.                         })
  104.                             .then(response => response.json())
  105.                             .then(data => {
  106.                                 if (data.success) {
  107.                                     scratchCardCoverContainer.classList.add('clear');
  108.                                     confetti({
  109.                                         particleCount: 100,
  110.                                         spread: 90,
  111.                                         origin: {
  112.                                             y: (scratchCardText.getBoundingClientRect().bottom + 60) / window.innerHeight,
  113.                                         },
  114.                                         zIndex: 9999,
  115.                                     });
  116.                                     scratchCardText.textContent = data.message;
  117.                                     scratchCardImage.classList.add('animate');
  118.                                     scratchCardCoverContainer.addEventListener('transitionend', () => {
  119.                                         scratchCardCoverContainer.classList.add('hidden');
  120.                                     }, { once: true });
  121.                                 } else {
  122.                                     scratchCardText.textContent = 'Aucun ticket à gratter';
  123.                                 }
  124.                             })
  125.                             .catch(error => {
  126.                                 console.error('Error:', error);
  127.                                 scratchCardText.textContent = 'Une erreur est survenue. Veuillez réessayer.';
  128.                             });
  129.                     }
  130.                 }
  131.                 const getPosition = ({ clientX, clientY }) => {
  132.                     const { left, top } = canvas.getBoundingClientRect();
  133.                     return {
  134.                         x: clientX - left,
  135.                         y: clientY - top,
  136.                     };
  137.                 }
  138.                 const plotLine = (context, x1, y1, x2, y2) => {
  139.                     var diffX = Math.abs(x2 - x1);
  140.                     var diffY = Math.abs(y2 - y1);
  141.                     var dist = Math.sqrt(diffX * diffX + diffY * diffY);
  142.                     var step = dist / 50;
  143.                     var i = 0;
  144.                     var t;
  145.                     var x;
  146.                     var y;
  147.                     while (i < dist) {
  148.                         t = Math.min(1, i / dist);
  149.                         x = x1 + (x2 - x1) * t;
  150.                         y = y1 + (y2 - y1) * t;
  151.                         context.beginPath();
  152.                         context.arc(x, y, 16, 0, Math.PI * 2);
  153.                         context.fill();
  154.                         i += step;
  155.                     }
  156.                 }
  157.                 const setImageFromCanvas = () => {
  158.                     canvas.toBlob((blob) => {
  159.                         const url = URL.createObjectURL(blob);
  160.                         previousUrl = scratchCardCanvasRender.src;
  161.                         scratchCardCanvasRender.src = url;
  162.                         if (!previousUrl) {
  163.                             scratchCardCanvasRender.classList.remove('hidden');
  164.                         } else {
  165.                             URL.revokeObjectURL(previousUrl);
  166.                         }
  167.                         previousUrl = url;
  168.                     });
  169.                 }
  170.                 let setImageTimeout = null;
  171.                 const plot = (e) => {
  172.                     const { x, y } = getPosition(e);
  173.                     plotLine(context, positionX, positionY, x, y);
  174.                     positionX = x;
  175.                     positionY = y;
  176.                     if (isSafari) {
  177.                         clearTimeout(setImageTimeout);
  178.                         setImageTimeout = setTimeout(() => {
  179.                             setImageFromCanvas();
  180.                         }, 5);
  181.                     }
  182.                 };
  183.             });
  184.             modal.show();
  185.         });
  186.     </script>
  187. {% endif %}