All checks were successful
		
		
	
	Docker Build and Publish / build (push) Successful in 7s
				
			
		
			
				
	
	
		
			466 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			466 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!DOCTYPE html>
 | 
						|
<html lang="en">
 | 
						|
<head>
 | 
						|
    <meta charset="UTF-8">
 | 
						|
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
						|
    <title>{{ site_title }}</title>
 | 
						|
    <link rel="preconnect" href="https://fonts.googleapis.com">
 | 
						|
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 | 
						|
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@100..900&display=swap" rel="stylesheet">
 | 
						|
    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
 | 
						|
    <style>
 | 
						|
        body {
 | 
						|
            margin: 0;
 | 
						|
            padding: 0;
 | 
						|
            font-family: Arial, sans-serif;
 | 
						|
            background-color: #f0f0f0;
 | 
						|
            display: flex;
 | 
						|
            flex-direction: row;
 | 
						|
            min-height: 100vh;
 | 
						|
        }
 | 
						|
 | 
						|
        .main-content {
 | 
						|
          flex-grow: 1;
 | 
						|
          padding: 20px;
 | 
						|
          overflow-y: auto;
 | 
						|
        }
 | 
						|
        .grid-container {
 | 
						|
            display: grid;
 | 
						|
            grid-template-columns: repeat(auto-fill, minmax(10px, 1fr));
 | 
						|
            grid-auto-rows: 10px;
 | 
						|
            grid-auto-flow: dense;
 | 
						|
            gap: 15px;
 | 
						|
            max-width: 100%;
 | 
						|
            box-sizing: border-box;
 | 
						|
        }
 | 
						|
        .polaroid {
 | 
						|
            position: relative;
 | 
						|
            background-color: #e8e8ea;
 | 
						|
            box-shadow: 0 0 2px 0 #f4f4f6 inset,
 | 
						|
                1px 5px 5px 0px #99999955;
 | 
						|
            border-radius: 2px;
 | 
						|
            padding: 10px;
 | 
						|
            display: flex;
 | 
						|
            flex-direction: column;
 | 
						|
            align-items: center;
 | 
						|
            transition: background-color 0.3s ease;
 | 
						|
            max-width: 100%;
 | 
						|
            box-sizing: border-box;
 | 
						|
        }
 | 
						|
 | 
						|
        .polaroid img {
 | 
						|
            max-width: 100%;
 | 
						|
            height: auto;
 | 
						|
            display: block;
 | 
						|
            flex-grow: 1;
 | 
						|
            object-fit: contain;
 | 
						|
        }
 | 
						|
 | 
						|
        .date-overlay {
 | 
						|
            position: absolute;
 | 
						|
            bottom: 4rem;
 | 
						|
            font-size: 1rem;
 | 
						|
            right: 1rem;
 | 
						|
            text-align: right;
 | 
						|
            color: #ec5a11;
 | 
						|
            font-family: "Noto Sans Mono", monospace;
 | 
						|
            font-size: 0.7rem;
 | 
						|
            opacity: 0;
 | 
						|
            text-shadow: 0 0 2px #ec5a11;
 | 
						|
            transition: opacity 0.3s ease;
 | 
						|
        }
 | 
						|
 | 
						|
        .polaroid:hover .date-overlay {
 | 
						|
            opacity: 0.8;
 | 
						|
        }
 | 
						|
 | 
						|
        .polaroid .caption {
 | 
						|
            margin-top: 10px;
 | 
						|
            display: block;
 | 
						|
            flex-grow: 0;
 | 
						|
            flex-shrink: 1;
 | 
						|
            flex-basis: auto;
 | 
						|
            align-self: flex-end;
 | 
						|
            order: 0;
 | 
						|
            text-align: right;
 | 
						|
            line-height: 70%;
 | 
						|
            font-size: 0.75rem;
 | 
						|
        }
 | 
						|
 | 
						|
        .noto-sans-mono-font {
 | 
						|
          font-family: "Noto Sans Mono", monospace;
 | 
						|
          font-optical-sizing: auto;
 | 
						|
          font-weight: 400;
 | 
						|
          font-style: normal;
 | 
						|
          font-variation-settings:
 | 
						|
            "wdth" 100;
 | 
						|
        }
 | 
						|
 | 
						|
        .sidebar {
 | 
						|
            width: 20rem;
 | 
						|
            background-color: #f0f0f0;
 | 
						|
            padding: 20px;
 | 
						|
            box-sizing: border-box;
 | 
						|
            position: sticky;
 | 
						|
            top: 0;
 | 
						|
            height: 100vh;
 | 
						|
            overflow-y: auto;
 | 
						|
            display: flex;
 | 
						|
            flex-direction: column;
 | 
						|
            justify-content: space-between;
 | 
						|
        }
 | 
						|
        .sidebar-title {
 | 
						|
            font-size: 1rem;
 | 
						|
            text-align: right;
 | 
						|
        }
 | 
						|
        .sidebar-nav {
 | 
						|
            font-size: 1rem;
 | 
						|
            flex-grow: 1;
 | 
						|
            justify-content: center;
 | 
						|
            text-align: right;
 | 
						|
        }
 | 
						|
        .sidebar-nav ul {
 | 
						|
            list-style-type: none;
 | 
						|
            padding: 0;
 | 
						|
        }
 | 
						|
        .sidebar-nav a {
 | 
						|
            text-decoration: none;
 | 
						|
            color: {{ accent_color }};
 | 
						|
        }
 | 
						|
        .nav-toggle {
 | 
						|
            display: none;
 | 
						|
            cursor: pointer;
 | 
						|
            font-size: 1.5rem;
 | 
						|
            text-align: right;
 | 
						|
        }
 | 
						|
 | 
						|
        @media (max-width: 768px) {
 | 
						|
 | 
						|
 | 
						|
          .main-content {
 | 
						|
            padding: 10px;
 | 
						|
          }
 | 
						|
          .grid-container {
 | 
						|
            display: flex;
 | 
						|
            flex-direction: column;
 | 
						|
            gap: 20px;
 | 
						|
          }
 | 
						|
          .polaroid {
 | 
						|
            width: 100%;
 | 
						|
            max-width: 100vw;
 | 
						|
            height: auto;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        @media (max-width: 1024px) {
 | 
						|
            body {
 | 
						|
                flex-direction: column;
 | 
						|
            }
 | 
						|
            .sidebar {
 | 
						|
                width: 100%;
 | 
						|
                height: auto;
 | 
						|
                position: static;
 | 
						|
                padding: 10px;
 | 
						|
                overflow-y: visible;
 | 
						|
            }
 | 
						|
            .sidebar-nav ul {
 | 
						|
                display: none;
 | 
						|
            }
 | 
						|
            .nav-toggle {
 | 
						|
                display: block;
 | 
						|
            }
 | 
						|
            .sidebar-nav.active ul {
 | 
						|
                display: block;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        .modal {
 | 
						|
            display: none;
 | 
						|
            position: fixed;
 | 
						|
            z-index: 1000;
 | 
						|
            left: 0;
 | 
						|
            top: 0;
 | 
						|
            width: 100%;
 | 
						|
            height: 100%;
 | 
						|
            background-color: rgba(0,0,0,0.5);
 | 
						|
            backdrop-filter: blur(5px);
 | 
						|
        }
 | 
						|
 | 
						|
        .modal-content {
 | 
						|
            background-color: #f0f0f0;
 | 
						|
            margin: 10% auto;
 | 
						|
            padding: 20px;
 | 
						|
            width: 80%;
 | 
						|
            max-width: 600px;
 | 
						|
            border-radius: 8px;
 | 
						|
            position: relative;
 | 
						|
            display: flex;
 | 
						|
            flex-direction: column;
 | 
						|
            gap: 1rem;
 | 
						|
        }
 | 
						|
 | 
						|
        .modal-header {
 | 
						|
            display: flex;
 | 
						|
            align-items: center;
 | 
						|
            gap: 1rem;
 | 
						|
        }
 | 
						|
 | 
						|
        .profile-image {
 | 
						|
            width: 100px;
 | 
						|
            height: 100px;
 | 
						|
            border-radius: 50%;
 | 
						|
            object-fit: cover;
 | 
						|
        }
 | 
						|
 | 
						|
        .profile-info {
 | 
						|
            flex-grow: 1;
 | 
						|
        }
 | 
						|
 | 
						|
        .profile-name {
 | 
						|
            font-size: 1.5rem;
 | 
						|
            margin: 0;
 | 
						|
        }
 | 
						|
 | 
						|
        .profile-location {
 | 
						|
            color: #666;
 | 
						|
            margin: 0;
 | 
						|
        }
 | 
						|
 | 
						|
        .modal-bio {
 | 
						|
            line-height: 1.6;
 | 
						|
        }
 | 
						|
 | 
						|
        .close {
 | 
						|
            position: absolute;
 | 
						|
            right: 20px;
 | 
						|
            top: 20px;
 | 
						|
            font-size: 1.5rem;
 | 
						|
            cursor: pointer;
 | 
						|
            color: #666;
 | 
						|
        }
 | 
						|
 | 
						|
        .close:hover {
 | 
						|
            color: #000;
 | 
						|
        }
 | 
						|
    </style>
 | 
						|
</head>
 | 
						|
<body>
 | 
						|
    <header>
 | 
						|
        <div class="sidebar">
 | 
						|
            <div class="sidebar-nav noto-sans-mono-font">
 | 
						|
                <div class="nav-toggle">☰</div>
 | 
						|
                <nav>
 | 
						|
                    <ul>
 | 
						|
                        <li><a href="/">Home</a></li>
 | 
						|
                        <li><a href="#" id="aboutLink">About</a></li>
 | 
						|
                        <li><hr></li>
 | 
						|
                        <li>Powered by <a href="https://dws.rip">DWS</a></li>
 | 
						|
                    </ul>
 | 
						|
                </nav>
 | 
						|
            </div>
 | 
						|
            <div class="sidebar-title noto-sans-mono-font">
 | 
						|
                <h1>{{ site_title }}</h1>
 | 
						|
            </div>
 | 
						|
        </div>
 | 
						|
    </header>
 | 
						|
 | 
						|
    <div class="main-content">
 | 
						|
      <div class="grid-container" id="polaroid-grid"></div>
 | 
						|
      <div id="loading">Loading more images...</div>
 | 
						|
    </div>
 | 
						|
 | 
						|
    <div id="aboutModal" class="modal">
 | 
						|
        <div class="modal-content">
 | 
						|
            <span class="close">×</span>
 | 
						|
            <div class="modal-header">
 | 
						|
                <img src="{{ about.profile_image }}" alt="{{ about.name }}" class="profile-image">
 | 
						|
                <div class="profile-info">
 | 
						|
                    <h2 class="profile-name">{{ about.name }}</h2>
 | 
						|
                    <p class="profile-location">{{ about.location }}</p>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
            <div class="modal-bio" id="bio-content"></div>
 | 
						|
        </div>
 | 
						|
    </div>
 | 
						|
 | 
						|
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" nonce="{{ nonce }}"></script>
 | 
						|
 | 
						|
    <script nonce="{{ nonce }}">
 | 
						|
        const gridContainer = document.getElementById('polaroid-grid');
 | 
						|
        const loadingIndicator = document.getElementById('loading');
 | 
						|
        const baseSize = 110; // Size of one grid cell in pixels
 | 
						|
        let page = 1;
 | 
						|
        let isLoading = false;
 | 
						|
        let hasMore = true;
 | 
						|
 | 
						|
        function createPolaroid(polaroid) {
 | 
						|
            const polaroidElement = document.createElement('div');
 | 
						|
            polaroidElement.className = 'polaroid';
 | 
						|
            if (polaroid.height > polaroid.width) {
 | 
						|
                polaroidElement.classList.add('vertical');
 | 
						|
            }
 | 
						|
            
 | 
						|
            polaroidElement.style.backgroundColor = `${polaroid.highlightColor}33`;
 | 
						|
            
 | 
						|
            const img = document.createElement('img');
 | 
						|
            img.alt = polaroid.caption;
 | 
						|
            img.setAttribute('data-original-width', polaroid.width);
 | 
						|
            img.setAttribute('data-original-height', polaroid.height);
 | 
						|
            img.setAttribute('data-base-src', polaroid.imgSrc);
 | 
						|
            img.src = polaroid.imgSrc;
 | 
						|
            
 | 
						|
            //img.onload = () => loadOptimalThumbnail(img); // Load optimal thumbnail after initial load
 | 
						|
            
 | 
						|
            const dateOverlay = document.createElement('div');
 | 
						|
            dateOverlay.className = 'date-overlay';
 | 
						|
            dateOverlay.textContent = polaroid.date;
 | 
						|
            
 | 
						|
            const caption = document.createElement('div');
 | 
						|
            caption.className = 'caption noto-sans-mono-font';
 | 
						|
            caption.textContent = polaroid.technicalInfo;
 | 
						|
            
 | 
						|
            polaroidElement.appendChild(img);
 | 
						|
            polaroidElement.appendChild(dateOverlay);
 | 
						|
            polaroidElement.appendChild(caption);
 | 
						|
            
 | 
						|
            return polaroidElement;
 | 
						|
        }
 | 
						|
 | 
						|
        function calculateGridSpan(dimension) {
 | 
						|
            return Math.ceil(dimension / baseSize);
 | 
						|
        }
 | 
						|
 | 
						|
        function positionPolaroid(polaroidElement, polaroid) {
 | 
						|
            const width = calculateGridSpan(polaroid.width + 20); // Add 20px for padding
 | 
						|
            const height = calculateGridSpan(polaroid.height + 40) + 1; // Add 40px for padding and caption
 | 
						|
            
 | 
						|
            polaroidElement.style.gridColumnEnd = `span ${width}`;
 | 
						|
            polaroidElement.style.gridRowEnd = `span ${height}`;
 | 
						|
 | 
						|
        }
 | 
						|
 | 
						|
        function getOptimalThumbnailSize(width, height) {
 | 
						|
            const sizes = [256, 512, 768, 1024, 1536, 2048];
 | 
						|
            const maxDimension = Math.max(width, height);
 | 
						|
            return sizes.find(size => size >= maxDimension) || sizes[sizes.length - 1];
 | 
						|
        }
 | 
						|
 | 
						|
        function loadOptimalThumbnail(img) {
 | 
						|
            // Use a small delay to ensure the image has rendered
 | 
						|
            setTimeout(() => {
 | 
						|
                const containerWidth = img.offsetWidth;
 | 
						|
                const containerHeight = img.offsetHeight;
 | 
						|
                const devicePixelRatio = window.devicePixelRatio || 1;
 | 
						|
                
 | 
						|
                // If dimensions are still 0, use the original image dimensions
 | 
						|
                const width = containerWidth || parseInt(img.getAttribute('data-original-width'));
 | 
						|
                const height = containerHeight || parseInt(img.getAttribute('data-original-height'));
 | 
						|
                
 | 
						|
                const optimalSize = getOptimalThumbnailSize(
 | 
						|
                    width * devicePixelRatio,
 | 
						|
                    height * devicePixelRatio
 | 
						|
                );
 | 
						|
                
 | 
						|
                const newSrc = img.getAttribute('data-base-src').replace('1536_', `${optimalSize}_`);
 | 
						|
                
 | 
						|
                if (newSrc !== img.src) {
 | 
						|
                    const tempImg = new Image();
 | 
						|
                    tempImg.onload = function() {
 | 
						|
                        img.src = newSrc;
 | 
						|
                        img.style.opacity = 1;
 | 
						|
                    };
 | 
						|
                    tempImg.src = newSrc;
 | 
						|
                    img.style.opacity = 0.5;
 | 
						|
                }
 | 
						|
            }, 100); // 100ms delay
 | 
						|
        }
 | 
						|
 | 
						|
        function handleResize() {
 | 
						|
            const polaroids = document.querySelectorAll('.polaroid');
 | 
						|
            polaroids.forEach(polaroid => {
 | 
						|
                const img = polaroid.querySelector('img');
 | 
						|
                const width = parseInt(img.getAttribute('data-original-width'));
 | 
						|
                const height = parseInt(img.getAttribute('data-original-height'));
 | 
						|
                positionPolaroid(polaroid, { width, height });
 | 
						|
                //loadOptimalThumbnail(img);
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        async function loadImages() {
 | 
						|
            if (isLoading || !hasMore) return;
 | 
						|
            
 | 
						|
            isLoading = true;
 | 
						|
            loadingIndicator.style.display = 'block';
 | 
						|
 | 
						|
            try {
 | 
						|
                const response = await fetch(`/api/images?page=${page}`);
 | 
						|
                const data = await response.json();
 | 
						|
 | 
						|
                data.images.forEach(polaroid => {
 | 
						|
                    const polaroidElement = createPolaroid(polaroid);
 | 
						|
                    positionPolaroid(polaroidElement, polaroid);
 | 
						|
                    gridContainer.appendChild(polaroidElement);
 | 
						|
                    // loadOptimalThumbnail is now called in the img.onload event
 | 
						|
                });
 | 
						|
 | 
						|
                hasMore = data.hasMore;
 | 
						|
                page++;
 | 
						|
            } catch (error) {
 | 
						|
                console.error('Error loading images:', error);
 | 
						|
            } finally {
 | 
						|
                isLoading = false;
 | 
						|
                loadingIndicator.style.display = 'none';
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function handleScroll() {
 | 
						|
            if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) {
 | 
						|
                loadImages();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        window.addEventListener('scroll', handleScroll);
 | 
						|
        window.addEventListener('resize', handleResize);
 | 
						|
 | 
						|
        loadImages(); // Initial load
 | 
						|
 | 
						|
        function setupNavToggle() {
 | 
						|
            const navToggle = document.querySelector('.nav-toggle');
 | 
						|
            const sidebarNav = document.querySelector('.sidebar-nav');
 | 
						|
 | 
						|
            navToggle.addEventListener('click', () => {
 | 
						|
                sidebarNav.classList.toggle('active');
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        setupNavToggle();
 | 
						|
 | 
						|
        // Modal functionality
 | 
						|
        const modal = document.getElementById('aboutModal');
 | 
						|
        const aboutLink = document.getElementById('aboutLink');
 | 
						|
        const closeBtn = document.querySelector('.close');
 | 
						|
        const bioContent = document.getElementById('bio-content');
 | 
						|
 | 
						|
        // Render markdown content
 | 
						|
        bioContent.innerHTML = marked.parse(`{{ about.bio | safe }}`);
 | 
						|
 | 
						|
        aboutLink.onclick = function() {
 | 
						|
            modal.style.display = 'block';
 | 
						|
            document.body.style.overflow = 'hidden';
 | 
						|
        }
 | 
						|
 | 
						|
        closeBtn.onclick = function() {
 | 
						|
            modal.style.display = 'none';
 | 
						|
            document.body.style.overflow = 'auto';
 | 
						|
        }
 | 
						|
 | 
						|
        window.onclick = function(event) {
 | 
						|
            if (event.target == modal) {
 | 
						|
                modal.style.display = 'none';
 | 
						|
                document.body.style.overflow = 'auto';
 | 
						|
            }
 | 
						|
        }
 | 
						|
    </script>
 | 
						|
</body>
 | 
						|
</html>
 |