diff --git a/templates/_layout.html b/templates/_layout.html index 35cbd24..0bac31c 100644 --- a/templates/_layout.html +++ b/templates/_layout.html @@ -32,8 +32,9 @@ - - + + + <!-- ::PAGE_TITLE:: --> diff --git a/website/dynamic-rotation.js b/website/dynamic-rotation.js index 12a373e..f10ecff 100644 --- a/website/dynamic-rotation.js +++ b/website/dynamic-rotation.js @@ -9,7 +9,7 @@ const minRotation = 0.5; const maxRotation = 2.5; const minHeight = 200; - const maxHeight = 700; + const maxHeight = 600; // Linear interpolation between min and max rotation based on height let rotation = maxRotation - ((height - minHeight) / (maxHeight - minHeight)) * (maxRotation - minRotation); diff --git a/website/index.html b/website/index.html index 9d212bd..4fc0c52 100644 --- a/website/index.html +++ b/website/index.html @@ -32,8 +32,9 @@ - - + + + Litruv - Dev/Tech Artist diff --git a/website/main.js b/website/main.js index 6189164..027fad1 100644 --- a/website/main.js +++ b/website/main.js @@ -55,9 +55,40 @@ function createPost(t) { link.href = t.url; link.appendChild(post); + + console.log(`[Main] Created post with ${t.embed.length} images`); + return link; } +// Helper function to add tape to a single image +function addTapeToSingleImage(image, index) { + if (!window.createTapeElement || !window.positionTape) { + console.warn('[Main] Tape functions not available'); + return; + } + + // Determine tape pattern (alternating) + const usePattern1 = index % 2 === 0; + + // Create two tape pieces + const tape1 = window.createTapeElement(index, 1); + const tape2 = window.createTapeElement(index, 2); + + if (usePattern1) { + // Pattern 1: top-left and bottom-right + window.positionTape(tape1, 'top-left', index, 1); + window.positionTape(tape2, 'bottom-right', index, 2); + } else { + // Pattern 2: top-right and bottom-left + window.positionTape(tape1, 'top-right', index, 1); + window.positionTape(tape2, 'bottom-left', index, 2); + } + + image.appendChild(tape1); + image.appendChild(tape2); +} + function filterOriginalPosts(feed) { return feed.filter(item => { const isRepost = item?.reason?.$type === "app.bsky.feed.defs#reasonRepost"; @@ -102,10 +133,22 @@ function getShortestColumn(columns) { function layoutPosts(postElements) { const numCols = getNumColumns(); const columns = createColumns(numCols); - postElements.forEach(postEl => { + postElements.forEach((postEl, index) => { const col = getShortestColumn(columns); + // Set descending z-index - first post has highest z-index + postEl.firstElementChild.style.zIndex = postElements.length - index; col.appendChild(postEl); }); + + // Use refreshTape to avoid duplicate tape on resize/layout + console.log('[Main] Posts laid out, refreshing tape for all images'); + setTimeout(() => { + if (window.refreshTape) { + window.refreshTape(); + } else { + console.error('[Main] refreshTape function not available'); + } + }, 200); } async function fetchPosts() { @@ -131,6 +174,8 @@ async function fetchPosts() { })) || [] })); + console.log(`[Main] Fetched ${posts.length} posts, ${posts.filter(p => p.embed.length > 0).length} have images`); + // Only create post elements once postElementsCache = posts.map(post => createPost(post)); // Preload images @@ -156,6 +201,12 @@ async function fetchPosts() { window.addEventListener("resize", () => { if (postElementsCache) { layoutPosts(postElementsCache); + // Re-add tape after layout changes + if (window.refreshTape) { + setTimeout(() => { + window.refreshTape(); + }, 100); + } } }); diff --git a/website/masonry-smooth.js b/website/masonry-smooth.js index 6a8cc44..4b97b7e 100644 --- a/website/masonry-smooth.js +++ b/website/masonry-smooth.js @@ -13,6 +13,11 @@ if (posts.length === 0 || columns.length === 0) return; + // Assign z-index based on post order (newer posts first) + posts.forEach((post, index) => { + post.style.zIndex = 100 - index; + }); + // Store original positions of posts posts.forEach((post, index) => { const rect = post.getBoundingClientRect(); diff --git a/website/privacy.html b/website/privacy.html index 2f1f215..419529c 100644 --- a/website/privacy.html +++ b/website/privacy.html @@ -66,8 +66,9 @@ - - + + + Privacy Policy - Litruv diff --git a/website/style.css b/website/style.css index f0c905c..4958233 100644 --- a/website/style.css +++ b/website/style.css @@ -150,7 +150,6 @@ social-links a:hover .fab.fa-youtube { transition: none; /* Prevent transitions during layout changes */ will-change: auto; /* Prevent unnecessary layer promotion */ /* Prevent reflow during resize */ - contain: layout; } @media (max-width: 799px) { @@ -160,19 +159,25 @@ social-links a:hover .fab.fa-youtube { transition: none; /* Prevent transitions during layout changes */ /* Remove the min-height that was causing issues */ min-height: auto; - /* Use layout containment to minimize reflow */ - contain: layout style size; + margin-top: 2rem; /* Add top margin to push posts below header */ + margin-right: 0px; + margin-left:0px; + padding-right: 0; /* Remove right padding on mobile */ } .masonry-col { min-width: 0; width: 100%; - /* Prevent content reflow during breakpoint change */ - contain: layout style; /* Hide overflow columns on mobile */ } .masonry-col:not(:first-child) { display: none; } + + /* Ensure posts don't have excessive negative margins on mobile */ + .post { + margin-top: 1rem; + margin-bottom: 1rem; + } } .post { @@ -183,7 +188,7 @@ social-links a:hover .fab.fa-youtube { margin-left: -40px; margin-right: -40px; border: 2px solid var(--post-border); - position: relative; + position: relative; /* Add back for z-index to work */ box-shadow: 3px 3px 0 var(--shadow-light), 6px 6px 0 var(--shadow-mid); transition: transform 0.35s cubic-bezier(0.68, -0.55, 0.27, 1.55), box-shadow 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55), @@ -241,8 +246,44 @@ social-links a:hover .fab.fa-youtube { border: 4px solid var(--post-border); background: var(--avatar-bg); text-align: center; - transform: rotate(1deg); + transform: rotate(0deg); /* Remove rotation from images */ box-shadow: 4px 4px 0 var(--post-border); + display: block; /* Ensure it's a block element */ +} + +/* Ensure image containers can hold positioned tape */ +.image-placeholder, +.image-placeholder img { + position: relative; +} + +/* Force relative positioning on image links (containers) */ +a:has(.image-placeholder) { + position: relative !important; + display: block !important; +} + +/* JavaScript-generated tape styling */ +.tape { + position: absolute; + background: rgba(255, 248, 220, 0.9); + border: 0.5px solid rgba(0, 0, 0, 0.18); + z-index: 100; + pointer-events: none; + box-shadow: 1px 1px 3px rgba(0,0,0,0.3); + background-image: linear-gradient(45deg, + rgba(255, 255, 255, 0.3) 25%, + transparent 25%, + transparent 75%, + rgba(255, 255, 255, 0.3) 75%); + background-size: 3px 3px; + /* Zig-zag mask on top/bottom (long) sides, subtle and thin */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; } .post-actions { diff --git a/website/styles.css b/website/styles.css new file mode 100644 index 0000000..37c47c9 --- /dev/null +++ b/website/styles.css @@ -0,0 +1,11 @@ +.post { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + transition: box-shadow 0.3s ease, transform 0.3s ease; + position: relative; +} + +.post:hover { + box-shadow: 0 12px 35px rgba(0, 0, 0, 0.3); + z-index: 999 !important; + transform: translateY(-4px); +} diff --git a/website/tape-debug.js b/website/tape-debug.js new file mode 100644 index 0000000..db52acd --- /dev/null +++ b/website/tape-debug.js @@ -0,0 +1,283 @@ +class TapeDebugger { + constructor() { + this.enabled = false; + this.debugOverlay = null; + this.debugInfo = null; + this.setupDebugUI(); + } + + setupDebugUI() { + // Create debug toggle button + const debugButton = document.createElement('button'); + debugButton.textContent = 'Debug Tape'; + debugButton.style.cssText = ` + position: fixed; + top: 10px; + right: 10px; + z-index: 10000; /* Increased to ensure button is always visible */ + padding: 10px; + background: #ff4444; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-family: monospace; + font-weight: bold; + `; + debugButton.onclick = () => this.toggleDebug(); + document.body.appendChild(debugButton); + + // Create debug overlay + this.debugOverlay = document.createElement('div'); + this.debugOverlay.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 9999; /* Increased to appear above tape */ + display: none; + `; + document.body.appendChild(this.debugOverlay); + + // Create debug info panel + this.debugInfo = document.createElement('div'); + this.debugInfo.style.cssText = ` + position: fixed; + top: 60px; + right: 10px; + width: 300px; + max-height: 80vh; + overflow-y: auto; + background: rgba(0, 0, 0, 0.9); + color: white; + padding: 15px; + border-radius: 5px; + font-family: monospace; + font-size: 12px; + z-index: 10000; /* Increased to ensure panel is always visible */ + display: none; + `; + document.body.appendChild(this.debugInfo); + } + + toggleDebug() { + this.enabled = !this.enabled; + if (this.enabled) { + this.showDebug(); + } else { + this.hideDebug(); + } + } + + showDebug() { + console.log('[Tape Debug] Enabling visual debugging'); + this.debugOverlay.style.display = 'block'; + this.debugInfo.style.display = 'block'; + this.visualizeAllTape(); + this.updateDebugInfo(); + } + + hideDebug() { + console.log('[Tape Debug] Disabling visual debugging'); + this.debugOverlay.style.display = 'none'; + this.debugInfo.style.display = 'none'; + this.debugOverlay.innerHTML = ''; + } + + visualizeAllTape() { + this.debugOverlay.innerHTML = ''; + const images = document.querySelectorAll('.image-placeholder'); + + images.forEach((image, imageIndex) => { + this.visualizeImageDebug(image, imageIndex); + }); + } + + visualizeImageDebug(image, imageIndex) { + const rect = image.getBoundingClientRect(); + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; + + // Create image outline + const imageOutline = document.createElement('div'); + imageOutline.style.cssText = ` + position: absolute; + left: ${rect.left + scrollLeft}px; + top: ${rect.top + scrollTop}px; + width: ${rect.width}px; + height: ${rect.height}px; + border: 3px solid #00ff00; + background: rgba(0, 255, 0, 0.1); + pointer-events: none; + `; + + // Add image label + const imageLabel = document.createElement('div'); + imageLabel.textContent = `IMG ${imageIndex + 1}`; + imageLabel.style.cssText = ` + position: absolute; + top: -25px; + left: 0; + background: #00ff00; + color: black; + padding: 2px 6px; + font-size: 10px; + font-weight: bold; + `; + imageOutline.appendChild(imageLabel); + + // Visualize tape pieces + const tapeElements = image.querySelectorAll('.tape'); + tapeElements.forEach((tape, tapeIndex) => { + this.visualizeTapeDebug(tape, tapeIndex, rect, scrollTop, scrollLeft, imageIndex); + }); + + this.debugOverlay.appendChild(imageOutline); + } + + visualizeTapeDebug(tape, tapeIndex, imageRect, scrollTop, scrollLeft, imageIndex) { + const tapeRect = tape.getBoundingClientRect(); + + // Create tape outline + const tapeOutline = document.createElement('div'); + tapeOutline.style.cssText = ` + position: absolute; + left: ${tapeRect.left + scrollLeft}px; + top: ${tapeRect.top + scrollTop}px; + width: ${tapeRect.width}px; + height: ${tapeRect.height}px; + border: 2px solid #ff0000; + background: rgba(255, 0, 0, 0.3); + pointer-events: none; + transform: ${tape.style.transform}; + `; + + // Add tape label + const tapeLabel = document.createElement('div'); + tapeLabel.textContent = `T${tapeIndex + 1}`; + tapeLabel.style.cssText = ` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #ff0000; + color: white; + padding: 1px 3px; + font-size: 8px; + font-weight: bold; + `; + tapeOutline.appendChild(tapeLabel); + + // Add positioning info + const positionInfo = document.createElement('div'); + const corner = this.determineTapeCorner(tape); + positionInfo.textContent = corner; + positionInfo.style.cssText = ` + position: absolute; + top: -15px; + left: 0; + background: #ff0000; + color: white; + padding: 1px 3px; + font-size: 8px; + white-space: nowrap; + `; + tapeOutline.appendChild(positionInfo); + + this.debugOverlay.appendChild(tapeOutline); + } + + determineTapeCorner(tape) { + const style = tape.style; + if (style.top && style.left) return 'TOP-LEFT'; + if (style.top && style.right) return 'TOP-RIGHT'; + if (style.bottom && style.left) return 'BOTTOM-LEFT'; + if (style.bottom && style.right) return 'BOTTOM-RIGHT'; + return 'UNKNOWN'; + } + + updateDebugInfo() { + const images = document.querySelectorAll('.image-placeholder'); + const tapeElements = document.querySelectorAll('.tape'); + + let infoHtml = ` +

🎬 Tape Debug Info

+
Images: ${images.length}
+
Tape Elements: ${tapeElements.length}
+
Expected Tape: ${images.length * 2}
+
+ `; + + images.forEach((image, index) => { + const tapes = image.querySelectorAll('.tape'); + const pattern = index % 2 === 0 ? 'Pattern 1 (TL+BR)' : 'Pattern 2 (TR+BL)'; + + infoHtml += ` +
+ Image ${index + 1}:
+ Pattern: ${pattern}
+ Tape Count: ${tapes.length}/2
+ ${tapes.length !== 2 ? '⚠️ Missing tape!' : '✓ Complete'} +
+ `; + }); + + // Add tape details + tapeElements.forEach((tape, index) => { + const corner = this.determineTapeCorner(tape); + const transform = tape.style.transform || 'none'; + const size = `${tape.style.width} x ${tape.style.height}`; + + infoHtml += ` +
+ Tape ${index + 1}: ${corner}
+ Size: ${size}
+ Transform: ${transform} +
+ `; + }); + + this.debugInfo.innerHTML = infoHtml; + } + + // Method to refresh debug visualization when layout changes + refresh() { + if (this.enabled) { + this.visualizeAllTape(); + this.updateDebugInfo(); + } + } +} + +// Initialize debugger +const tapeDebugger = new TapeDebugger(); + +// Add to global scope for easy access +window.tapeDebugger = tapeDebugger; + +// Hook into existing functions to update debug visualization +const originalAddTapeToImages = window.addTapeToImages; +const originalRefreshTape = window.refreshTape; + +if (originalAddTapeToImages) { + window.addTapeToImages = function() { + originalAddTapeToImages.apply(this, arguments); + setTimeout(() => tapeDebugger.refresh(), 100); + }; +} + +if (originalRefreshTape) { + window.refreshTape = function() { + originalRefreshTape.apply(this, arguments); + setTimeout(() => tapeDebugger.refresh(), 100); + }; +} + +// Refresh debug on window resize +window.addEventListener('resize', () => { + setTimeout(() => tapeDebugger.refresh(), 200); +}); + +console.log('[Tape Debug] Visual debugging system loaded. Click "Debug Tape" button to toggle.'); diff --git a/website/tape.js b/website/tape.js new file mode 100644 index 0000000..89512fe --- /dev/null +++ b/website/tape.js @@ -0,0 +1,174 @@ +function addTapeToImages() { + const images = document.querySelectorAll('.image-placeholder'); + console.log(`[Tape] Found ${images.length} images to add tape to`); + + if (images.length === 0) { + console.warn('[Tape] No .image-placeholder elements found'); + return; + } + + images.forEach((image, index) => { + console.log(`[Tape] Processing image ${index + 1}/${images.length}`); + + // Check if image already has tape + const existingTape = image.querySelectorAll('.tape'); + if (existingTape.length > 0) { + console.log(`[Tape] Image ${index + 1} already has tape, skipping`); + return; + } + + // Ensure the image parent has relative positioning + const imageParent = image.parentElement; + if (imageParent && window.getComputedStyle(imageParent).position === 'static') { + imageParent.style.position = 'relative'; + console.log(`[Tape] Set image parent to relative positioning`); + } + + // Create two tape pieces + const tape1 = createTapeElement(index, 1); + const tape2 = createTapeElement(index, 2); + + // Position based on pattern + const usePattern1 = index % 2 === 0; + if (usePattern1) { + positionTape(tape1, 'top-left'); + positionTape(tape2, 'bottom-right'); + } else { + positionTape(tape1, 'top-right'); + positionTape(tape2, 'bottom-left'); + } + + // Add to the image's parent container + imageParent.appendChild(tape1); + imageParent.appendChild(tape2); + + console.log(`[Tape] Added tape to image ${index + 1} parent`); + }); +} + +function createTapeElement(imageIndex, tapeNumber) { + const tape = document.createElement('div'); + tape.className = 'tape'; + + // Even chunkier tape: wider and much taller + const width = 60 + Math.random() * 20; // 60-80px + const height = 40 + Math.random() * 16; // 40-56px + + tape.style.cssText = ` + position: absolute; + width: ${width}px; + height: ${height}px; + background: rgba(255, 248, 220, 0.93); + z-index: 100; + pointer-events: none; + box-shadow: 2px 2px 6px rgba(0,0,0,0.18); + background-image: linear-gradient(45deg, + rgba(255, 255, 255, 0.25) 25%, + transparent 25%, + transparent 75%, + rgba(255, 255, 255, 0.25) 75%); + background-size: 4px 4px; + `; + + console.log(`[Tape] Created tape element ${tapeNumber} for image ${imageIndex + 1}`); + return tape; +} + +function positionTape(tape, corner) { + const randomOffset = () => Math.random() * 8 - 4; // -4 to 4px + const randomRotation = (Math.random() * 20 - 10); // -10 to 10 degrees + const OUT_X = 25; // px, how far out from the left/right + const OUT_Y = 18; // px, how far out from the top/bottom + + switch (corner) { + case 'top-left': + tape.style.top = `${-OUT_Y + randomOffset()}px`; + tape.style.left = `${-OUT_X + randomOffset()}px`; + tape.style.transform = `rotate(${-45 + randomRotation}deg)`; + break; + case 'top-right': + tape.style.top = `${-OUT_Y + randomOffset()}px`; + tape.style.right = `${-OUT_X + randomOffset()}px`; + tape.style.transform = `rotate(${45 + randomRotation}deg)`; + break; + case 'bottom-left': + tape.style.bottom = `${-OUT_Y + randomOffset()}px`; + tape.style.left = `${-OUT_X + randomOffset()}px`; + tape.style.transform = `rotate(${45 + randomRotation}deg)`; + break; + case 'bottom-right': + tape.style.bottom = `${-OUT_Y + randomOffset()}px`; + tape.style.right = `${-OUT_X + randomOffset()}px`; + tape.style.transform = `rotate(${-45 + randomRotation}deg)`; + break; + } + + console.log(`[Tape] Positioned tape at ${corner}:`, { + top: tape.style.top, + bottom: tape.style.bottom, + left: tape.style.left, + right: tape.style.right, + transform: tape.style.transform + }); +} + +function refreshTape() { + console.log('[Tape] Refreshing tape - removing existing tape'); + + // Remove existing tape + const existingTape = document.querySelectorAll('.tape'); + console.log(`[Tape] Found ${existingTape.length} existing tape elements to remove`); + + existingTape.forEach(tape => tape.remove()); + + console.log('[Tape] Re-adding tape to images'); + addTapeToImages(); +} + +// Simple test function +function testSimpleTape() { + console.log('[Tape Test] Creating simple test...'); + + const firstImage = document.querySelector('.image-placeholder'); + if (!firstImage) { + console.error('[Tape Test] No images found'); + return; + } + + const parent = firstImage.parentElement; + parent.style.position = 'relative'; + + const testTape = document.createElement('div'); + testTape.className = 'tape'; + testTape.style.cssText = ` + position: absolute; + top: 10px; + left: 10px; + width: 60px; + height: 30px; + background: red; + border: 2px solid black; + z-index: 999; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + `; + testTape.textContent = 'TEST'; + + parent.appendChild(testTape); + console.log('[Tape Test] Added test tape to first image parent'); + + setTimeout(() => { + testTape.remove(); + console.log('[Tape Test] Removed test tape'); + }, 3000); +} + +// Export functions +window.addTapeToImages = addTapeToImages; +window.refreshTape = refreshTape; +window.testSimpleTape = testSimpleTape; + +console.log('[Tape] Simple tape system loaded. Use window.testSimpleTape() to test.');