309 lines
		
	
	
		
			No EOL
		
	
	
		
			9.6 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			309 lines
		
	
	
		
			No EOL
		
	
	
		
			9.6 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!DOCTYPE html>
 | 
						|
<html lang="en">
 | 
						|
<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" />
 | 
						|
 | 
						|
    <!-- Add critical layout CSS inline to prevent layout shift -->
 | 
						|
    <style>
 | 
						|
        .hidden {
 | 
						|
            display: none !important;
 | 
						|
        }
 | 
						|
        body {
 | 
						|
            margin: 0;
 | 
						|
            padding: 0;
 | 
						|
            display: flex;
 | 
						|
            flex-direction: column;
 | 
						|
            justify-content: center;
 | 
						|
            align-items: center;
 | 
						|
            min-height: 100vh;
 | 
						|
        }
 | 
						|
        #document-container {
 | 
						|
            width: 70%;
 | 
						|
            max-width: 600px;
 | 
						|
            margin: 100px auto 20px auto;
 | 
						|
            padding: 40px;
 | 
						|
        }
 | 
						|
        #title-container {
 | 
						|
            position: absolute;
 | 
						|
            width: 100%;
 | 
						|
            top: 20px;
 | 
						|
            text-align: center;
 | 
						|
        }
 | 
						|
    </style>
 | 
						|
</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>
 | 
						|
</html> |