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 @@
-
-
+
+
+
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.');