<!DOCTYPE html> <head> <title>Open Signature Generator</title> <meta name="Open Signature Generator" /> <meta name="description" content="Generate and verify document signatures for free!" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" /> </head> <body> <div id="title-container"> <h1> Open Signature Generator </h1> </div> <div id="document-container"> <div id="loading-spinner" class="loading">Loading...</div> <img id="signature-image" style="max-width: 100%; margin-bottom: 20px;"> <div id="verification-info"></div> </div> <div class="button-container"></div> <a href="/" class="button">Go to Open Signature Generator!</a> </div> <div id="animated-background"> <svg viewBox="0 0 100 100"></svg> </div> </body> <script> const animatedBackground = document.getElementById('animated-background'); function stopAnimations() { const animations = animatedBackground.querySelectorAll('animate, animateTransform'); animations.forEach(animation => { animation.style.setProperty('animation-play-state', 'paused'); }); } // Modify your initialization code document.addEventListener('DOMContentLoaded', () => { // Extract the signature hash from the URL let url = window.location.href; let hash = url.split('/v/')[1]; let image_url = `/images/${hash}.png`; // Set the image source const signatureImage = document.getElementById('signature-image'); const loadingSpinner = document.getElementById('loading-spinner'); signatureImage.onload = function() { loadingSpinner.style.display = 'none'; signatureImage.style.opacity = 1; }; signatureImage.onerror = function() { loadingSpinner.textContent = 'Error loading signature image'; loadingSpinner.style.display = 'none'; }; signatureImage.src = image_url; // Fetch and display verification data fetch(`/verify/${hash}`) .then(response => response.json()) .then(data => { // Create a Date object from the time int and the timezone const time = new Date(data.time * 1000); const timezone_offset_minutes = data.timezone; let timezone_string = ""; if (timezone_offset_minutes > 0) { timezone_string = "UTC +" + Math.floor(timezone_offset_minutes/60) + ":" + String(Math.floor(timezone_offset_minutes%60)).padStart(2, '0'); } else { timezone_string = "UTC " + Math.floor(timezone_offset_minutes/60) + ":" + String(Math.floor(timezone_offset_minutes%60)).padStart(2, '0'); } if (data.invert) { document.getElementById('signature-image').classList.add('invert'); } const verificationInfo = document.getElementById('verification-info'); verificationInfo.innerHTML = ` <h3>Signature Details</h3> <p><strong>Created:</strong> ${time.toLocaleString()}</p> <p><strong>Creator Information:</strong> <ul> <li><strong>Signed Name:</strong> ${data.name}</li> <li><strong>Signing Browser Timezone:</strong> ${timezone_string}</li> <li><strong>IP Address:</strong> ${data.identity.ip}</li> <li><strong>Approximate Physical Address:</strong> ${data.identity.address}</li> <li><strong>Approximate GPS Location:</strong> ${data.identity.latlon}</li> <li><strong>Internet Service Provider:</strong> ${data.identity.provider}</li> <li><strong>Browser Information:</strong> ${data.identity.useragent}</li> </ul> </p> ${data.message ? `<p><strong>Message:</strong> ${data.message}</p>` : ''} `; verificationInfo.style.color = '#333'; }) .catch(error => { const verificationInfo = document.getElementById('verification-info'); verificationInfo.innerHTML = `<p style="color: red">Error loading signature verification data.</p>`; console.error('Error:', error); }); // Stop animations after 5 minutes to save resources setTimeout(stopAnimations, 300 * 1000); }); </script> <style> body { background-color: #000000; margin: 0; padding: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; min-height: 100vh; font-family: Arial, sans-serif; } #title-container { position: absolute; width: 100%; top: 20px; z-index: 2; text-align: center; } #title-container h1 { font-size: clamp(24px, calc(6vw + 8px), 64px); margin-top: 20px; color: white; text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7); } #document-container { background-color: rgb(236, 236, 234); width: 70%; max-width: 600px; margin: 100px auto 20px auto; padding: 40px; border-radius: 8px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4); position: relative; z-index: 1; overflow: hidden; } #signature-image { border-radius: 1px; border: 1px solid #000000; } .invert { background-color: #000000; } #animated-background { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; overflow: clip; } @property --a { syntax: '<color>'; inherits: false; initial-value: #453f53; } @property --b { syntax: '<color>'; inherits: false; initial-value: #fff; } @property --c { syntax: '<color>'; inherits: false; initial-value: #777; } #animated-background svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: var(--pattern), var(--map, linear-gradient(90deg, #888, #fff)); background-blend-mode: multiply; filter: contrast(5) blur(20px) saturate(35%) brightness(0.4); mix-blend-mode: darken; --pattern: repeating-radial-gradient(circle, var(--a), var(--b), var(--c) 15em); /* We use steps here to limit framerate to reduce CPU usage displaying the animation*/ /* Because it is a slowly changing background, a low framerate does not impact apparent smoothness of the animation */ animation: bganimation 120s forwards steps(1200) infinite; transform: translateX(35%) translateY(75%) scale(4.5) } @keyframes bganimation { 0% { --a: #453f53; --b: #fff; --c: #777; transform: translateX(35%) translateY(75%) scale(4.5)} 33% { --a: #ce8083; --b: #ac8cbd; --c: #3b1c80; transform: rotate(-10deg) scale(4.0,3.5) translateX(15%) translateY(25%)} 66% { --a: #309385; --b: #5aa8fb; --c: #866849; transform: rotate(10deg) scale(4.5,3.5) translateX(25%) translateY(-15%)} 100% { --a: #453f53; --b: #fff; --c: #777; transform: translateX(35%) translateY(75%) scale(4.5)} } .loading { display: flex; justify-content: center; align-items: center; font-size: 3.0em; color: #666; height: 100px; position: relative; } .loading::after { content: ''; width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; animation: spin 1.0s ease-in-out infinite; position: absolute; margin-left: 10px; opacity: 0.6; } @keyframes spin { 0% { transform: rotate(90deg); } 100% { transform: rotate(450deg); } } .button-container { display: flex; justify-content: center; gap: 20px; margin-top: 20px; } .button { padding: 10px 20px; background-color: #1f4e82; color: white; text-decoration: none; border-radius: 5px; transition: background-color 0.3s; } .button:hover { background-color: #095fbf; } #signature-image { border: 1px solid #000000; opacity: 0; transition: opacity 0.5s ease-in-out; } </style>