commit 26e489f0bf06557ff35db57cc49b62645de4b53b Author: sakrecoer Date: Fri Jul 31 00:27:45 2026 +0200 did all this shit without even commiting once ffs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7f4147 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +public +resources +.hugo_build.lock +data diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..25b6752 --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,5 @@ ++++ +date = '{{ .Date }}' +draft = true +title = '{{ replace .File.ContentBaseName "-" " " | title }}' ++++ diff --git a/fetch-and-convert.sh b/fetch-and-convert.sh new file mode 100755 index 0000000..b0b2e7a --- /dev/null +++ b/fetch-and-convert.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Configuration +PUBKEY="setto@basspistol.org" +RELAY="wss://basspistol.org" +DATA_DIR="data" + +# Clean up old JSON files (optional) +# rm -f data/*/*.json + +# Define kind mappings +declare -A KINDS +KINDS[0]="profile/profile" +KINDS[30023]="longform/blog" +KINDS[36787]="music/tracks" +KINDS[20]="pictures/photos" +KINDS[22]="videos/videos" + +# Fetch each kind +for kind in "${!KINDS[@]}"; do + output="${KINDS[$kind]}" + echo "Fetching kind $kind..." + + # Create directory if it doesn't exist + mkdir -p "$DATA_DIR/$(dirname "$output")" + + # Fetch and convert to JSON + nak req -k "$kind" -a "$PUBKEY" "$RELAY" | jq -s '.' > "$DATA_DIR/$output.json" + + # Remove any existing .jsonl file in the same directory + rm -f "$DATA_DIR/$(dirname "$output")"/*.jsonl + + echo "Saved to $DATA_DIR/$output.json" +done + +echo "All data fetched and converted!" +echo "JSONL files have been removed." + +echo "Building website" + +hugo build diff --git a/hugo.toml b/hugo.toml new file mode 100644 index 0000000..7140f3d --- /dev/null +++ b/hugo.toml @@ -0,0 +1,15 @@ +baseURL = "https://setto.basspistol.com" +locale = "en-us" +title = "Settolino Malandrino" +theme = "one-pager" +disableKinds = ["sitemap", "taxonomy", "term"] + +[params] + sitename = "My Site" + description = "Set, Party, Gang εΎ’ setto γ‚»γƒƒγƒˆ" + headerImage = "/images/header.jpg" + +[permalinks] + page = "/:contentbasename/" + + diff --git a/themes/one-pager/assets/css/style.css b/themes/one-pager/assets/css/style.css new file mode 100644 index 0000000..cae7aea --- /dev/null +++ b/themes/one-pager/assets/css/style.css @@ -0,0 +1,1519 @@ +/* Import Poppins font */ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700;900&display=swap'); + +:root { + --bg-primary: #0a0a0a; + --bg-secondary: #1a1a1a; + --text-primary: #f5f0eb; + --text-secondary: #d0c8c0; + --accent-yellow: #ffd700; + --accent-grey: #8a8a8a; + --accent-black: #ffffff; + --border-color: #ffd700; + --shadow-color: rgba(255, 215, 0, 0.1); + --card-bg: #141414; + --nav-bg: rgba(255, 215, 0, 0.1); + --tag-bg: #2a2a2a; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif; + line-height: 1.6; + color: var(--text-primary); + background: var(--bg-primary); + transition: background-color 0.4s ease, color 0.4s ease; +} + + +.main-header { + min-height: 100vh !important; + height: 100vh !important; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 4rem 2rem; + position: relative; + background: var(--bg-primary); + border-bottom: 4px solid var(--accent-yellow); + transition: background-color 0.4s ease; + overflow: hidden; + flex-shrink: 0; +} + +/* Combined overlay: dark gradient + cyberpunk pattern */ +.main-header::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 0; + pointer-events: none; + background: + /* Dark overlay for readability */ + linear-gradient( + to bottom, + rgba(0, 0, 0, 0.5) 0%, + rgba(0, 0, 0, 0.25) 40%, + rgba(0, 0, 0, 0.25) 60%, + rgba(0, 0, 0, 0.5) 100% + ), + /* Cyberpunk pattern */ + repeating-linear-gradient( + 33deg, + transparent, + transparent 50px, + var(--accent-yellow) 50px, + var(--accent-yellow) 51px + ); + opacity: 0.6; +} + +[data-theme="dark"] .main-header::before { + background: + /* Darker overlay for dark mode */ + linear-gradient( + to bottom, + rgba(0, 0, 0, 0.7) 0%, + rgba(0, 0, 0, 0.4) 40%, + rgba(0, 0, 0, 0.4) 60%, + rgba(0, 0, 0, 0.7) 100% + ), + repeating-linear-gradient( + 33deg, + transparent, + transparent 50px, + var(--accent-yellow) 50px, + var(--accent-yellow) 51px + ); + opacity: 0.7; +} + +/* Decorative '>' character */ +.main-header::after { + content: '>'; + position: absolute; + bottom: 40px; + right: 40px; + z-index: 1; + font-family: 'Poppins', sans-serif; + font-weight: 900; + font-size: 4rem; + color: var(--accent-yellow); + opacity: 0.15; + pointer-events: none; +} + +/* Content sits above overlay */ +.header-content { + max-width: 1200px; + margin: 0 auto; + width: 100%; + position: relative; + z-index: 1; +} + +/* Profile layout */ +.header-profile { + display: flex; + align-items: center; + gap: 3rem; + justify-content: center; + flex-wrap: wrap; +} + +/* Profile picture */ +.profile-picture { + width: 180px; + height: 180px; + border-radius: 0; + border: 4px solid var(--accent-yellow); + object-fit: cover; + box-shadow: 12px 12px 0 var(--accent-yellow); + transition: all 0.3s ease; + border-radius: 100%; +} + +.profile-picture:hover { + transform: scale(1.02); + box-shadow: 16px 16px 0 var(--accent-yellow); + +} + +/* Header text container */ +.header-text { + text-align: left; + flex: 1; + min-width: 280px; +} + +/* Main title */ +.main-header h1 { + font-family: 'Poppins', sans-serif; + font-weight: 900; + font-size: 7rem; + letter-spacing: -2px; + line-height: 1.1; + color: var(--accent-yellow); + text-transform: uppercase; + margin-bottom: 0.5rem; + text-shadow: + 0 2px 20px rgba(0, 0, 0, 0.6), + 0 4px 8px rgba(0, 0, 0, 0.4), + 4px 4px 0 var(--accent-grey); + transition: all 0.3s ease; +} + + + +.main-header h1:hover { + text-shadow: + 0 2px 30px rgba(255, 215, 0, 0.3), + 0 4px 8px rgba(0, 0, 0, 0.5), + 6px 6px 0 var(--accent-yellow); + transform: translate(-2px, -2px); +} + +/* About text */ +.profile-about { + font-size: 1.1rem; + font-weight: 700; + margin: 1rem 0 1.5rem 0; + line-height: 1.8; + + color: var(--text-secondary); + border-left: 4px solid var(--accent-yellow); + padding: 0.5rem 1rem 1.5rem 1.5rem; + letter-spacing: 0.5px; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-radius: 0 8px 8px 0; +} + +[data-theme="dark"] .profile-about { + background: rgba(0, 0, 0, 0.4); + color: #f0ece8; +} + +.profile-about code { + background: var(--accent-yellow); + color: var(--bg-primary); + padding: 0.5rem; + + font-weight: 700; + font-size: 1.2rem; + font-family: 'Poppins', sans-serif; + letter-spacing: 1px; +} + +/* Tagline */ +.tagline { + font-size: 2rem; + font-weight: 700; + margin-top: 0.5rem; + letter-spacing: -0.5px; + text-transform: uppercase; + color: var(--text-primary); + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); +} + +[data-theme="dark"] .tagline { + color: #f0ece8; + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6); +} + + + +[data-theme="dark"] .profile-nip05, +[data-theme="dark"] .profile-lud16 { + color: #f0ece8; + background: rgba(0, 0, 0, 0.5); + border-color: var(--accent-yellow); +} + +/* Main Navigation - NO SKEW on text */ +.main-nav { + margin-top: 3rem; + display: flex; + justify-content: center; + gap: 2rem; + flex-wrap: wrap; + position: relative; + z-index: 1; +} + +.main-nav a { + color: var(--text-primary); + text-decoration: none; + padding: 0.8rem 2rem; + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 0.8rem; + letter-spacing: 4px; + text-transform: uppercase; + transition: all 0.3s ease; + border: 2px solid var(--accent-yellow); + background: rgba(0, 0, 0, 0.3); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); +} + +[data-theme="dark"] .main-nav a { + background: rgba(0, 0, 0, 0.5); + color: #f0ece8; +} + +.main-nav a:hover { + transform: scale(1.05); + background: var(--accent-yellow); + color: var(--accent-black); + box-shadow: 8px 8px 0 var(--accent-grey); + backdrop-filter: none; + -webkit-backdrop-filter: none; +} + +.main-nav a span { + display: inline-block; +} + +/* ============ END HEADER STYLES ============ */ + +/* Sticky Navigation - NO SKEW */ +.sticky-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 999; + background: var(--bg-primary); + border-bottom: 3px solid var(--accent-yellow); + padding: 0.8rem 2rem; + transform: translateY(-100%); + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.4s ease; + box-shadow: 0 4px 20px var(--shadow-color); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.sticky-nav.visible { + transform: translateY(0); +} + +.sticky-nav .nav-container { + max-width: 1200px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; + flex-wrap: wrap; +} + +.sticky-nav .nav-brand { + font-family: 'Poppins', sans-serif; + font-weight: 900; + font-size: 1.2rem; + letter-spacing: -1px; + color: var(--text-primary); + text-decoration: none; + text-transform: uppercase; +} + +.sticky-nav .nav-brand span { + color: var(--accent-yellow); +} + +.sticky-nav .nav-links { + display: flex; + gap: 1.5rem; + flex-wrap: wrap; + align-items: center; +} + +.sticky-nav .nav-links a { + color: var(--text-primary); + text-decoration: none; + font-family: 'Poppins', sans-serif; + font-weight: 600; + font-size: 0.75rem; + letter-spacing: 3px; + text-transform: uppercase; + padding: 0.4rem 0.8rem; + transition: all 0.3s ease; + border: 2px solid transparent; +} + +.sticky-nav .nav-links a:hover { + color: var(--accent-yellow); + border-color: var(--accent-yellow); + transform: scale(1.05); +} + +.sticky-nav .nav-links a span { + display: inline-block; +} + +.sticky-nav .nav-links { + color: var(--accent-black); + padding: 0.4rem 1rem; + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 0.7rem; + letter-spacing: 3px; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; +} + +.sticky-nav .nav-links .theme-toggle-small:hover { + transform: scale(1.05); + background: var(--accent-black); + color: var(--accent-yellow); + border-color: var(--accent-yellow); +} + +.sticky-nav .nav-links .theme-toggle-small span { + display: inline-block; +} + + +/* Sections */ +.section { + padding: 5rem 2rem; + max-width: 1200px; + margin: 0 auto; + border-bottom: 2px solid var(--accent-grey); + position: relative; +} + +.section:last-child { + border-bottom: none; +} +.section p { + margin-bottom: 2rem; +} + +.section h2 { + font-family: 'Poppins', sans-serif; + font-weight: 900; + font-size: 2.8rem; + letter-spacing: -2px; + margin-bottom: 2rem; + color: var(--text-primary); + text-align: left; + position: relative; + padding-bottom: 1rem; + text-transform: uppercase; +} + +.section h2::before { + content: '// '; + color: var(--accent-yellow); +} + +.section h2::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 80px; + height: 4px; + background: var(--accent-yellow); +} + +/* Blog Grid - KEEP SKEW ON CARDS */ +.blog-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 2.5rem; +} + +.blog-card { + background: var(--card-bg); + border: 2px solid var(--accent-grey); + overflow: hidden; + transition: all 0.3s ease; + position: relative; + transform: skewX(-2deg); + box-shadow: 8px 8px 0 var(--accent-grey); +} + +.blog-card:hover { + transform: skewX(0deg) translate(-4px, -4px); + box-shadow: 12px 12px 0 var(--accent-yellow); +} + +.blog-header-image { + width: 100%; + height: 220px; + object-fit: cover; + border-bottom: 3px solid var(--accent-yellow); + filter: grayscale(30%); + transition: all 0.3s ease; +} + +.blog-card:hover .blog-header-image { + filter: grayscale(0%); +} + +.blog-content { + padding: 1.8rem; +} + +.blog-content h3 { + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 1.4rem; + letter-spacing: -1px; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.blog-meta { + color: var(--accent-grey); + font-size: 0.8rem; + font-weight: 400; + margin-bottom: 1rem; + letter-spacing: 1px; +} + +.blog-description { + color: var(--text-secondary); + margin-bottom: 1rem; + font-weight: 400; + font-size: 0.95rem; +} + +.blog-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 0.5rem 0 1.2rem 0; +} + +button { + border-radius: 8px; +} + +/* Read More button - NO SKEW */ +.read-more-toggle { + background: var(--accent-yellow); + color: #0a0a0a !important; /* Force black */ + border: 2px solid var(--accent-grey); + padding: 0.6rem 1.5rem; + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 0.7rem; + letter-spacing: 3px; + text-transform: uppercase; + cursor: pointer; + transition: all 0.3s ease; +} + +.read-more-toggle:hover { + background: var(--accent-black); + color: var(--accent-yellow) !important; /* Yellow on hover */ + border-color: var(--accent-yellow); + transform: scale(1.05); +} + +.read-more-toggle span { + display: inline-block; +} + +.blog-full-content { + margin-top: 1.5rem; + padding: 1.5rem; + background: var(--bg-secondary); + border-left: 4px solid var(--accent-yellow); + color: var(--text-secondary); + font-weight: 400; + font-size: 0.95rem; +} + +.blog-full-content img { + max-width: 100%; + height: auto; + margin: 0.5rem 0; + border: 2px solid var(--accent-grey); +} + +/* Music Grid - KEEP SKEW ON CARDS */ +.music-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 2.5rem; +} + +.music-card { + background: var(--card-bg); + border: 2px solid var(--accent-grey); + padding: 1.8rem; + transition: all 0.3s ease; + transform: skewX(-2deg); + box-shadow: 8px 8px 0 var(--accent-grey); +} + +.music-card:hover { + transform: skewX(0deg) translate(-4px, -4px); + box-shadow: 12px 12px 0 var(--accent-yellow); +} + +.music-image { + width: 100%; + height: 200px; + object-fit: cover; + border: 2px solid var(--accent-grey); + margin-bottom: 1rem; + filter: grayscale(30%); + transition: all 0.3s ease; +} + +.music-card:hover .music-image { + filter: grayscale(0%); +} + +.music-card h4 { + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 1.3rem; + letter-spacing: -1px; + margin-bottom: 0.3rem; + color: var(--text-primary); +} + +.music-artist { + color: var(--accent-yellow); + font-weight: 600; + font-size: 0.9rem; + letter-spacing: 2px; +} + +.music-album { + color: var(--accent-grey); + font-weight: 400; + font-size: 0.85rem; + margin-bottom: 1rem; +} + +.music-card audio { + width: 100%; + margin: 1rem 0; + border: 2px solid var(--accent-grey); +} + +.music-details { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 0.5rem 0; +} + +.music-details span { + background: var(--tag-bg); + padding: 0.3rem 0.8rem; + font-weight: 600; + font-size: 0.7rem; + letter-spacing: 2px; + color: var(--text-secondary); + border: 1px solid var(--accent-grey); +} + +.music-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 0.5rem 0; +} + +/* Tags - NO SKEW */ +.tag { + background: var(--tag-bg); + padding: 0.3rem 0.8rem; + font-weight: 700; + font-size: 0.65rem; + letter-spacing: 2px; + text-transform: uppercase; + color: var(--text-secondary); + border: 1px solid var(--accent-grey); +} + +.tag span { + display: inline-block; +} + +/* Lyrics button - NO SKEW */ +.lyrics-toggle { + background: var(--accent-yellow); + color: #0a0a0a !important; /* Force black */ + border: 2px solid var(--accent-grey); + padding: 0.6rem 1.5rem; + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 0.7rem; + letter-spacing: 3px; + text-transform: uppercase; + cursor: pointer; + transition: all 0.3s ease; + margin-top: 0.8rem; +} + +.lyrics-toggle:hover { + background: var(--accent-black); + color: #0a0a0a !important; /* Yellow on hover */ + border-color: var(--accent-yellow); + transform: scale(1.05); +} + +.lyrics-toggle span { + display: inline-block; +} + +.lyrics { + margin-top: 1rem; + padding: 1.5rem; + background: var(--bg-secondary); + border-left: 4px solid var(--accent-yellow); + color: var(--text-secondary); + font-weight: 400; + font-size: 0.9rem; + white-space: pre-wrap; +} + +/* Photo Grid - KEEP SKEW ON CARDS */ +.photo-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; +} + +.photo-item { + position: relative; + overflow: hidden; + border: 2px solid var(--accent-grey); + transition: all 0.3s ease; + transform: skewX(-2deg); + box-shadow: 8px 8px 0 var(--accent-grey); + background: var(--card-bg); +} + +.photo-item:hover { + transform: skewX(0deg) translate(-4px, -4px); + box-shadow: 12px 12px 0 var(--accent-yellow); +} + +.photo-item img { + width: 100%; + height: 320px; + object-fit: cover; + cursor: pointer; + filter: grayscale(30%); + transition: all 0.3s ease; + display: block; +} + +.photo-item:hover img { + filter: grayscale(0%); +} + +/* Photo date on main page */ +.photo-date { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 0.8rem 1.2rem; + background: linear-gradient(to top, rgba(0,0,0,0.8), transparent); + color: white; + font-family: 'Poppins', sans-serif; + font-weight: 400; + font-size: 0.75rem; + letter-spacing: 2px; + opacity: 0; + transition: opacity 0.3s ease; +} + +.photo-item:hover .photo-date { + opacity: 1; +} + +/* Video Grid - KEEP SKEW ON CARDS */ +.video-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); + gap: 2.5rem; +} + +.video-item { + background: var(--card-bg); + border: 2px solid var(--accent-grey); + overflow: hidden; + transition: all 0.3s ease; + transform: skewX(-2deg); + box-shadow: 8px 8px 0 var(--accent-grey); +} + +.video-item:hover { + transform: skewX(0deg) translate(-4px, -4px); + box-shadow: 12px 12px 0 var(--accent-yellow); +} + +.video-item video { + width: 100%; + background: var(--accent-black); + display: block; + border-bottom: 3px solid var(--accent-yellow); +} + +.video-info { + padding: 1.5rem; +} + +.video-caption { + color: var(--text-primary); + font-family: 'Poppins', sans-serif; + font-weight: 600; + font-size: 1rem; + margin-bottom: 0.5rem; +} + +.video-meta { + display: flex; + flex-wrap: wrap; + gap: 1rem; + font-weight: 400; + font-size: 0.7rem; + letter-spacing: 2px; + color: var(--accent-grey); +} + +.video-meta span { + background: var(--tag-bg); + padding: 0.3rem 0.8rem; + border: 1px solid var(--accent-grey); +} + +/* Custom Lightbox - NO SKEW */ +.custom-lightbox { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.92); + z-index: 9999; + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + animation: fadeIn 0.3s ease; +} + +.custom-lightbox.active { + display: flex; + align-items: center; + justify-content: center; +} + +.custom-lightbox-content { + max-width: 90%; + max-height: 85vh; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + animation: zoomIn 0.3s ease; +} + +.custom-lightbox-content img { + max-width: 100%; + max-height: 75vh; + object-fit: contain; + border: 3px solid var(--accent-yellow); + box-shadow: 0 0 40px rgba(255, 215, 0, 0.2); +} + +.custom-lightbox-caption { + margin-top: 1rem; + padding: 0.8rem 1.5rem; + background: rgba(0, 0, 0, 0.7); + border-left: 4px solid var(--accent-yellow); + color: white; + font-family: 'Poppins', sans-serif; + font-weight: 400; + font-size: 0.95rem; + text-align: center; + max-width: 80%; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.custom-lightbox-caption .lightbox-meta { + display: block; + font-size: 0.75rem; + color: var(--accent-yellow); + letter-spacing: 2px; + margin-top: 0.3rem; +} + +.custom-lightbox-close { + position: fixed; + top: 20px; + right: 30px; + font-size: 2.5rem; + color: white; + cursor: pointer; + font-family: 'Poppins', sans-serif; + transition: all 0.3s ease; + z-index: 10000; +} + +.custom-lightbox-close:hover { + color: var(--accent-yellow); + transform: scale(1.2); +} + +.custom-lightbox-prev, +.custom-lightbox-next { + position: fixed; + top: 50%; + transform: translateY(-50%); + background: rgba(0, 0, 0, 0.5); + color: white; + border: 2px solid var(--accent-yellow); + padding: 1rem 1.5rem; + font-size: 1.5rem; + cursor: pointer; + font-family: 'Poppins', sans-serif; + transition: all 0.3s ease; + z-index: 10000; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.custom-lightbox-prev { + left: 20px; +} + +.custom-lightbox-next { + right: 20px; +} + +.custom-lightbox-prev:hover, +.custom-lightbox-next:hover { + background: var(--accent-yellow); + color: var(--accent-black); + transform: translateY(-50%) scale(1.1); +} + +.custom-lightbox-counter { + position: fixed; + bottom: 20px; + right: 30px; + color: white; + font-family: 'Poppins', sans-serif; + font-weight: 600; + font-size: 0.8rem; + letter-spacing: 3px; + color: var(--accent-yellow); + z-index: 10000; +} + +/* Responsive sticky nav */ +@media (max-width: 768px) { + .sticky-nav { + padding: 0.6rem 1rem; + } + + .sticky-nav .nav-container { + flex-direction: column; + gap: 0.8rem; + align-items: center; + } + + .sticky-nav .nav-links { + justify-content: center; + gap: 0.8rem; + } + + .sticky-nav .nav-links a { + font-size: 0.65rem; + letter-spacing: 2px; + padding: 0.3rem 0.6rem; + } + + .sticky-nav .nav-brand { + font-size: 1rem; + } +} + +@media (max-width: 480px) { + .sticky-nav .nav-links { + gap: 0.5rem; + } + + .sticky-nav .nav-links a { + font-size: 0.55rem; + letter-spacing: 1px; + padding: 0.2rem 0.5rem; + } +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .main-header h1 { + font-size: 3.5rem; + } + + .section h2 { + font-size: 2.2rem; + } +} + +@media (max-width: 768px) { + .theme-toggle { + top: 10px; + right: 10px; + padding: 8px 16px; + font-size: 0.6rem; + } + + .header-profile { + flex-direction: column; + text-align: center; + gap: 1.5rem; + } + + .header-text { + text-align: center; + } + + .profile-about { + margin-left: auto; + margin-right: auto; + border-left: none; + border-top: 4px solid var(--accent-yellow); + padding: 1rem; + text-align: center; + } + + .profile-links { + justify-content: center; + } + + .main-header h1 { + font-size: 2.8rem; + letter-spacing: -1px; + } + + .main-header h1:hover { + text-shadow: 3px 3px 0 var(--accent-yellow); + } + + .profile-picture { + width: 140px; + height: 140px; + } + + .section { + padding: 3rem 1rem; + } + + .section h2 { + font-size: 1.8rem; + letter-spacing: -1px; + text-align: center; + } + + .section h2::after { + left: 50%; + transform: translateX(-50%); + } + + .blog-grid, + .music-grid, + .photo-grid, + .video-grid { + grid-template-columns: 1fr; + } + + .main-nav { + flex-direction: column; + align-items: center; + gap: 1rem; + } + + .main-nav a { + width: 100%; + max-width: 300px; + text-align: center; + } + + .photo-item img { + height: 250px; + } + + .blog-card, + .music-card, + .photo-item, + .video-item { + transform: skewX(0deg); + box-shadow: 4px 4px 0 var(--accent-grey); + } + + .blog-card:hover, + .music-card:hover, + .photo-item:hover, + .video-item:hover { + transform: translate(-2px, -2px); + box-shadow: 6px 6px 0 var(--accent-yellow); + } + + .custom-lightbox-prev, + .custom-lightbox-next { + padding: 0.8rem 1rem; + font-size: 1rem; + top: 50%; + } + + .custom-lightbox-prev { + left: 10px; + } + + .custom-lightbox-next { + right: 10px; + } + + .custom-lightbox-caption { + font-size: 0.8rem; + max-width: 90%; + } + + .custom-lightbox-close { + top: 10px; + right: 15px; + font-size: 2rem; + } +} + +@media (max-width: 480px) { + .main-header { + padding: 2rem 1rem; + min-height: 90vh; + } + + .main-header h1 { + font-size: 2rem; + letter-spacing: 0px; + } + + .main-header::after { + font-size: 2rem; + bottom: 20px; + right: 20px; + } + + .section h2 { + font-size: 1.4rem; + } + + .profile-picture { + width: 100px; + height: 100px; + } + + .profile-about { + font-size: 0.9rem; + } + + .blog-card, + .music-card, + .photo-item, + .video-item { + transform: skewX(0deg); + box-shadow: 4px 4px 0 var(--accent-grey); + } + + .photo-item img { + height: 200px; + } + + .main-nav a { + font-size: 0.7rem; + padding: 0.6rem 1.5rem; + letter-spacing: 2px; + } +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes zoomIn { + from { transform: scale(0.8); opacity: 0; } + to { transform: scale(1); opacity: 1; } +} + +/* Refresh/Randomize buttons */ +.refresh-button { + background: var(--bg-primary); + color: var(--accent-yellow) !important; /* Force black */ + border: 2px solid var(--accent-yellow); + padding: 0.5rem 1.5rem; + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 0.7rem; + letter-spacing: 3px; + text-transform: uppercase; + cursor: pointer; + transition: all 0.3s ease; +} + +.refresh-button:hover { + transform: scale(1.05); + background: var(--accent-yellow); + color: var(--bg-primary) !important; /* Yellow on hover */ + border-color: var(--accent-yellow); +} + +.refresh-button span { + display: inline-block; +} + +/* ============ CYBERPUNK FOOTER ============ */ + +.site-footer { + background: var(--bg-primary); + border-top: 4px solid var(--accent-yellow); + padding: 3rem 2rem 2rem; + margin-top: 3rem; + position: relative; + overflow: hidden; + transition: background-color 0.4s ease, border-color 0.4s ease; +} + +/* Cyberpunk grid overlay */ +.site-footer::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + repeating-linear-gradient( + 33deg, + transparent, + transparent 50px, + var(--accent-yellow) 50px, + var(--accent-yellow) 51px + ); + opacity: 0.03; + pointer-events: none; + z-index: 0; +} + +/* Top decorative line with scanline effect */ +.site-footer::after { + content: ''; + position: absolute; + top: -2px; + left: 0; + right: 0; + height: 4px; + background: repeating-linear-gradient( + 90deg, + var(--accent-yellow) 0px, + var(--accent-yellow) 20px, + transparent 20px, + transparent 25px + ); + opacity: 0.6; + z-index: 1; +} + +/* Glow line */ +.site-footer .footer-glow { + position: absolute; + top: -4px; + left: 10%; + right: 10%; + height: 2px; + background: var(--accent-yellow); + box-shadow: 0 0 30px var(--accent-yellow); + opacity: 0.3; + z-index: 1; +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-wrap: wrap; + gap: 2rem; + position: relative; + z-index: 1; +} + +/* Left column */ +.footer-left { + display: flex; + flex-direction: column; + gap: 1rem; + flex: 1; + min-width: 200px; +} + +.footer-brand { + font-family: 'Poppins', sans-serif; + font-weight: 900; + font-size: 1.8rem; + letter-spacing: -2px; + color: var(--text-primary); + text-transform: uppercase; + position: relative; + display: inline-block; +} + +.footer-brand .copyleft { + color: var(--accent-yellow); + font-weight: 900; +} + +.footer-brand .year { + font-weight: 300; + color: var(--accent-grey); + font-size: 1.2rem; +} + +.footer-brand::after { + content: ''; + position: absolute; + bottom: -4px; + left: 0; + width: 40px; + height: 3px; + background: var(--accent-yellow); + transform: skewX(-33deg); +} + +.footer-tagline { + font-family: 'Poppins', sans-serif; + font-weight: 300; + font-size: 0.8rem; + color: var(--accent-grey); + letter-spacing: 4px; + text-transform: uppercase; + margin-top: 0.5rem; + opacity: 0.8; +} + +.footer-tagline span { + color: var(--accent-yellow); + font-weight: 600; +} + +.footer-statement { + font-family: 'Poppins', sans-serif; + font-weight: 400; + font-size: 0.85rem; + color: var(--text-secondary); + letter-spacing: 1px; + border-left: 3px solid var(--accent-yellow); + padding-left: 1rem; +} + +/* Right column */ +.footer-right { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.8rem; + flex: 1; + min-width: 200px; +} + +.footer-meta-item { + font-family: 'Poppins', sans-serif; + font-weight: 300; + font-size: 0.75rem; + color: var(--accent-grey); + letter-spacing: 1px; + display: flex; + align-items: center; + gap: 0.5rem; + transition: all 0.3s ease; +} + +.footer-meta-item:hover { + color: var(--text-primary); +} + +.footer-meta-item .icon { + font-size: 1rem; + display: inline-block; + width: 1.5rem; + text-align: center; +} + +.footer-meta-item .label { + color: var(--accent-grey); + font-weight: 400; + text-transform: uppercase; + font-size: 0.6rem; + letter-spacing: 2px; + opacity: 0.6; +} + +.footer-meta-item .value { + font-weight: 500; + color: var(--text-primary); +} + +.footer-meta-item a { + color: var(--accent-yellow); + text-decoration: none; + font-weight: 500; + transition: all 0.3s ease; + border-bottom: 1px solid transparent; + padding-bottom: 1px; +} + +.footer-meta-item a:hover { + color: var(--text-primary); + border-bottom-color: var(--accent-yellow); +} + +/* Bottom bar */ +.footer-bottom { + max-width: 1200px; + margin: 1.5rem auto 0; + padding-top: 1.5rem; + border-top: 1px solid rgba(138, 138, 138, 0.2); + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; + position: relative; + z-index: 1; +} + +.footer-bottom-text { + font-family: 'Poppins', sans-serif; + font-weight: 300; + font-size: 0.6rem; + color: var(--accent-grey); + letter-spacing: 3px; + text-transform: uppercase; + opacity: 0.5; +} + +.footer-bottom-text span { + color: var(--accent-yellow); + opacity: 1; +} + +.footer-social { + display: flex; + gap: 1.5rem; +} + +.footer-social a { + color: var(--accent-grey); + text-decoration: none; + font-family: 'Poppins', sans-serif; + font-weight: 600; + font-size: 0.65rem; + letter-spacing: 2px; + text-transform: uppercase; + transition: all 0.3s ease; + border: 1px solid transparent; + padding: 0.2rem 0.6rem; +} + +.footer-social a:hover { + color: var(--accent-yellow); + border-color: var(--accent-yellow); + transform: scale(1.05); +} + +/* Responsive footer */ +@media (max-width: 768px) { + .site-footer { + padding: 2rem 1.5rem 1.5rem; + } + + .footer-content { + flex-direction: column; + align-items: stretch; + gap: 1.5rem; + } + + .footer-right { + align-items: flex-start; + } + + .footer-brand { + font-size: 1.5rem; + } + + .footer-bottom { + flex-direction: column; + text-align: center; + gap: 0.8rem; + } + + .footer-social { + justify-content: center; + } + + .footer-meta-item { + font-size: 0.65rem; + } +} + +@media (max-width: 480px) { + .site-footer { + padding: 1.5rem 1rem 1rem; + } + + .footer-brand { + font-size: 1.2rem; + letter-spacing: -1px; + } + + .footer-tagline { + font-size: 0.6rem; + letter-spacing: 2px; + } + + .footer-statement { + font-size: 0.7rem; + padding-left: 0.8rem; + } + + .footer-meta-item { + font-size: 0.55rem; + flex-wrap: wrap; + } + + .footer-bottom-text { + font-size: 0.5rem; + letter-spacing: 2px; + } +} + +/* ============ END CYBERPUNK FOOTER ============ */ diff --git a/themes/one-pager/assets/js/scripts.js b/themes/one-pager/assets/js/scripts.js new file mode 100644 index 0000000..f42a1c2 --- /dev/null +++ b/themes/one-pager/assets/js/scripts.js @@ -0,0 +1,870 @@ +document.addEventListener('DOMContentLoaded', function() { + // Sticky Navigation + const stickyNav = document.getElementById('stickyNav'); + const mainHeader = document.querySelector('.main-header'); + const themeToggle = document.getElementById('themeToggle'); + const themeToggleSmall = document.getElementById('themeToggleSmall'); + let lastScrollY = window.scrollY; + let ticking = false; + + // Function to update sticky nav visibility + function updateStickyNav() { + const scrollY = window.scrollY; + const headerHeight = mainHeader ? mainHeader.offsetHeight : 400; + + if (scrollY > headerHeight - 100) { + stickyNav.classList.add('visible'); + if (themeToggle) { + themeToggle.classList.add('hidden'); + } + } else { + stickyNav.classList.remove('visible'); + if (themeToggle) { + themeToggle.classList.remove('hidden'); + } + } + lastScrollY = scrollY; + } + + // Throttled scroll listener + window.addEventListener('scroll', function() { + if (!ticking) { + window.requestAnimationFrame(function() { + updateStickyNav(); + ticking = false; + }); + ticking = true; + } + }); + + // Initial check + setTimeout(updateStickyNav, 100); + + + + + // Read More toggle for blog posts + const readMoreButtons = document.querySelectorAll('.read-more-toggle'); + + readMoreButtons.forEach(button => { + button.addEventListener('click', function() { + const content = this.nextElementSibling; + if (content.style.display === 'none') { + content.style.display = 'block'; + this.innerHTML = 'Read Less'; + } else { + content.style.display = 'none'; + this.innerHTML = 'Read More'; + } + }); + }); + + + // Smooth scrolling for navigation links (both main nav and sticky nav) + document.querySelectorAll('nav a').forEach(link => { + link.addEventListener('click', function(e) { + e.preventDefault(); + const targetId = this.getAttribute('href'); + const targetElement = document.querySelector(targetId); + if (targetElement) { + // Calculate offset for sticky nav + const navHeight = stickyNav.offsetHeight; + const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navHeight - 20; + + window.scrollTo({ + top: targetPosition, + behavior: 'smooth' + }); + } + }); + }); + + // ============ CUSTOM LIGHTBOX ============ + const lightbox = document.getElementById('customLightbox'); + const lightboxImage = document.getElementById('lightboxImage'); + const lightboxCaption = document.getElementById('lightboxCaption'); + const lightboxMeta = document.getElementById('lightboxMeta'); + const lightboxCounter = document.getElementById('lightboxCounter'); + const lightboxClose = document.getElementById('lightboxClose'); + const lightboxPrev = document.getElementById('lightboxPrev'); + const lightboxNext = document.getElementById('lightboxNext'); + + let currentPhotos = []; + let currentIndex = 0; + + // Get all photo items + const photoItems = document.querySelectorAll('.photo-item'); + + // Build photo array + photoItems.forEach(item => { + const image = item.querySelector('img'); + const date = item.dataset.date || ''; + const pubkey = item.dataset.pubkey || ''; + const alt = item.dataset.alt || ''; + + if (image) { + currentPhotos.push({ + url: image.src, + alt: alt, + date: date, + pubkey: pubkey + }); + } + }); + + // Open lightbox + function openLightbox(index) { + if (index < 0) index = currentPhotos.length - 1; + if (index >= currentPhotos.length) index = 0; + currentIndex = index; + + const photo = currentPhotos[currentIndex]; + lightboxImage.src = photo.url; + lightboxImage.alt = photo.alt; + lightboxCaption.textContent = photo.alt; + lightboxMeta.textContent = `πŸ“… ${photo.date} | πŸ–ŠοΈ ${photo.pubkey}`; + lightboxCounter.textContent = `${currentIndex + 1} / ${currentPhotos.length}`; + + lightbox.classList.add('active'); + document.body.style.overflow = 'hidden'; + } + + // Close lightbox + function closeLightbox() { + lightbox.classList.remove('active'); + document.body.style.overflow = ''; + } + + // Navigate + function prevPhoto() { + openLightbox(currentIndex - 1); + } + + function nextPhoto() { + openLightbox(currentIndex + 1); + } + + // Event listeners for photo clicks + photoItems.forEach((item, index) => { + const img = item.querySelector('img'); + if (img) { + img.addEventListener('click', function(e) { + e.preventDefault(); + openLightbox(index); + }); + } + }); + + // Event listeners for lightbox controls + lightboxClose.addEventListener('click', closeLightbox); + lightboxPrev.addEventListener('click', prevPhoto); + lightboxNext.addEventListener('click', nextPhoto); + + // Keyboard controls + document.addEventListener('keydown', function(e) { + if (!lightbox.classList.contains('active')) return; + + if (e.key === 'Escape') { + closeLightbox(); + } else if (e.key === 'ArrowLeft') { + prevPhoto(); + } else if (e.key === 'ArrowRight') { + nextPhoto(); + } + }); + + // Click outside image to close + lightbox.addEventListener('click', function(e) { + if (e.target === lightbox) { + closeLightbox(); + } + }); +}); + +// No external lightbox library needed anymore! +console.log('Custom lightbox initialized successfully!'); + +// Artist filter list +const allowedArtists = [ + 'εΎ’ Setto γ‚»γƒƒγƒˆ', +'Settoshi Tonami', +'Sethy Bowoy' +]; + +// Function to check if a track is by an allowed artist +function isAllowedArtist(artist) { + if (!artist) return false; + // Normalize the artist name for comparison (remove extra spaces, lowercase) + const normalizedArtist = artist.trim().toLowerCase(); + + return allowedArtists.some(allowed => { + // Normalize the allowed artist name too + const normalizedAllowed = allowed.trim().toLowerCase(); + // Check if the artist name contains the allowed artist name or vice versa + return normalizedArtist.includes(normalizedAllowed) || + normalizedAllowed.includes(normalizedArtist); + }); +} + +// Render music tracks (randomized, 6 at a time, filtered by artist) +function renderMusicTracks(tracks, containerId) { + const container = document.getElementById(containerId); + if (!container) return; + + // Filter tracks by artist + const filteredTracks = tracks.filter(track => { + // Extract artist from tags + let artist = ''; + if (track.tags) { + track.tags.forEach(tag => { + if (tag[0] === 'artist') { + artist = tag[1]; + } + }); + } + return isAllowedArtist(artist); + }); + + // Check if we have any filtered tracks + if (filteredTracks.length === 0) { + container.innerHTML = '

No tracks found for the selected artists.

'; + return; + } + + // Shuffle and get first 6 + const shuffled = filteredTracks.sort(() => 0.5 - Math.random()); + const selected = shuffled.slice(0, 6); + + // Clear container + container.innerHTML = ''; + + // Render each track (same as before) + selected.forEach(track => { + // Extract data from tags + let title = '', url = '', image = '', artist = '', album = '', + trackNumber = '', duration = '', released = '', language = '', + tags = [], alt = '', content = ''; + + // Parse tags + if (track.tags) { + track.tags.forEach(tag => { + if (tag[0] === 'title') title = tag[1]; + else if (tag[0] === 'url') url = tag[1]; + else if (tag[0] === 'image') image = tag[1]; + else if (tag[0] === 'artist') artist = tag[1]; + else if (tag[0] === 'album') album = tag[1]; + else if (tag[0] === 'track_number') trackNumber = tag[1]; + else if (tag[0] === 'duration') duration = tag[1]; + else if (tag[0] === 'released') released = tag[1]; + else if (tag[0] === 'language') language = tag[1]; + else if (tag[0] === 't') tags.push(tag[1]); + else if (tag[0] === 'alt') alt = tag[1]; + }); + } + + content = track.content || ''; + + // Create music card HTML + const card = document.createElement('div'); + card.className = 'music-card'; + + let cardHTML = ` + ${image ? `${title}` : ''} +
+

${title || 'Untitled'}

+ ${artist ? `

${artist}

` : ''} + ${album ? `

${album}

` : ''} +
+ ${url ? `` : ''} +
+ ${trackNumber ? `Track ${trackNumber}` : ''} + ${duration ? `${duration}s` : ''} + ${released ? `${released}` : ''} + ${language ? `${language}` : ''} +
+
+ ${tags.map(t => `${t}`).join('')} +
+ ${content ? ` + + + ` : ''} + `; + + card.innerHTML = cardHTML; + container.appendChild(card); + }); + + // Re-initialize event listeners for new buttons + initMusicEventListeners(); +} + +// Initialize music event listeners +function initMusicEventListeners() { + // Lyrics toggle + document.querySelectorAll('.lyrics-toggle').forEach(button => { + button.removeEventListener('click', handleLyricsToggle); + button.addEventListener('click', handleLyricsToggle); + }); +} + +function handleLyricsToggle() { + const lyrics = this.nextElementSibling; + if (lyrics && lyrics.classList.contains('lyrics')) { + if (lyrics.style.display === 'none') { + lyrics.style.display = 'block'; + this.innerHTML = 'Hide Info'; + } else { + lyrics.style.display = 'none'; + this.innerHTML = 'Show Info'; + } + } +} + +// Refresh music (randomize) +function refreshMusic() { + if (window.allMusicData) { + renderMusicTracks(window.allMusicData, 'musicGrid'); + } +} + +// Call this when page loads +document.addEventListener('DOMContentLoaded', function() { + if (window.allMusicData && window.allMusicData.length > 0) { + renderMusicTracks(window.allMusicData, 'musicGrid'); + } +}); + +// ============ CUSTOM BLURHASH DECODER ============ +// Based on the official blurhash implementation, but compact + +const Blurhash = { + // Base83 character set + chars: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+,;<=>?@[]^_`{|}~', + + // Decode base83 to integer + decode83: function(str) { + let value = 0; + for (let i = 0; i < str.length; i++) { + const c = str[i]; + const digit = this.chars.indexOf(c); + if (digit === -1) { + throw new Error('Invalid base83 character: ' + c); + } + value = value * 83 + digit; + } + return value; + }, + + // Decode blurhash to pixel data + decode: function(blurhash, width, height, punch = 1) { + // Parse metadata + const sizeFlag = this.decode83(blurhash[0]); + const numX = (sizeFlag % 9) + 1; + const numY = Math.floor(sizeFlag / 9) + 1; + + // Calculate maximum AC component value + const maxAC = this.decode83(blurhash[1]) + 1; + + // Decode DC component + const dcValue = this.decode83(blurhash.slice(2, 6)); + const dc = this.decodeDC(dcValue); + + // Decode AC components + const ac = []; + let acIndex = 6; + for (let y = 0; y < numY; y++) { + for (let x = 0; x < numX; x++) { + if (x === 0 && y === 0) continue; + const encoded = this.decode83(blurhash.slice(acIndex, acIndex + 2)); + acIndex += 2; + const acValue = this.decodeAC(encoded, maxAC * punch); + ac.push({ x, y, value: acValue }); + } + } + + // Render pixels + const pixels = new Uint8ClampedArray(width * height * 4); + const size = width * height; + + // Pre-calculate cos values for performance + const cosX = []; + const cosY = []; + for (let x = 0; x < width; x++) { + cosX[x] = Math.cos(Math.PI * x / width); + } + for (let y = 0; y < height; y++) { + cosY[y] = Math.cos(Math.PI * y / height); + } + + for (let i = 0; i < size; i++) { + const x = i % width; + const y = Math.floor(i / width); + let r = dc[0]; + let g = dc[1]; + let b = dc[2]; + + for (const acVal of ac) { + const basis = cosX[x] * cosY[y]; + const value = acVal.value; + r += value[0] * basis; + g += value[1] * basis; + b += value[2] * basis; + } + + const index = i * 4; + pixels[index] = this.clamp(r + 128); + pixels[index + 1] = this.clamp(g + 128); + pixels[index + 2] = this.clamp(b + 128); + pixels[index + 3] = 255; + } + + return pixels; + }, + + // Decode DC component + decodeDC: function(value) { + return [ + this.signPow((value >> 16) & 0x3ff, 2), + this.signPow((value >> 8) & 0xff, 2), + this.signPow(value & 0xff, 2) + ]; + }, + + // Decode AC component + decodeAC: function(value, maxVal) { + const quantR = Math.floor(value / (19 * 19)); + const quantG = Math.floor((value / 19) % 19); + const quantB = value % 19; + return [ + this.signPow((quantR - 9) / 9, 2) * maxVal, + this.signPow((quantG - 9) / 9, 2) * maxVal, + this.signPow((quantB - 9) / 9, 2) * maxVal + ]; + }, + + // Sign power function + signPow: function(val, exp) { + return Math.sign(val) * Math.pow(Math.abs(val), exp); + }, + + // Clamp value to 0-255 + clamp: function(value) { + return Math.min(255, Math.max(0, value)); + }, + + // Render blurhash to canvas + renderToCanvas: function(blurhash, canvas, punch = 1) { + if (!blurhash) return false; + + try { + const width = canvas.width; + const height = canvas.height; + const pixels = this.decode(blurhash, width, height, punch); + const ctx = canvas.getContext('2d'); + const imageData = ctx.createImageData(width, height); + imageData.data.set(pixels); + ctx.putImageData(imageData, 0, 0); + return true; + } catch (e) { + console.warn('Failed to decode blurhash:', e); + return false; + } + }, + + // Get blurhash as data URL + toDataURL: function(blurhash, width = 64, height = 64, punch = 1) { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const success = this.renderToCanvas(blurhash, canvas, punch); + return success ? canvas.toDataURL('image/png') : null; + } +}; + +// ============ RANDOMIZED GALLERIES ============ + +// Function to shuffle array (Fisher-Yates) +function shuffleArray(array) { + const shuffled = [...array]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } + return shuffled; +} + +// Render random pictures (6 max) + +function renderRandomPictures(picturesData, containerId) { + const container = document.getElementById(containerId); + if (!container) return; + + // Shuffle and get first 6 + const shuffled = shuffleArray(picturesData); + const selected = shuffled.slice(0, 6); + + // Clear container + container.innerHTML = ''; + + // Render each picture + selected.forEach(picture => { + // Parse imeta tag to get image URL + let imageUrl = ''; + let alt = picture.content || ''; + + if (picture.tags) { + picture.tags.forEach(tag => { + if (tag[0] === 'imeta') { + const imetaStr = tag[1]; + // Extract URL + const urlMatch = imetaStr.match(/url ([^\s]+)/); + if (urlMatch) { + imageUrl = urlMatch[1]; + } + // Extract alt + const altMatch = imetaStr.match(/alt ([^\s]+)/); + if (altMatch) { + alt = altMatch[1]; + } + } + }); + } + + // Create picture item + const item = document.createElement('div'); + item.className = 'photo-item'; + item.dataset.image = imageUrl; + item.dataset.alt = alt; + + const date = new Date(picture.created_at * 1000); + const dateStr = date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + + item.innerHTML = ` + ${imageUrl ? ` + ${alt} + ` : ''} +
+ πŸ“… ${dateStr} +
+ `; + + container.appendChild(item); + }); + + // Re-initialize the custom lightbox for new images + initCustomLightbox(); +} + +// Initialize custom lightbox (call this after any DOM updates) +function initCustomLightbox() { + const lightbox = document.getElementById('customLightbox'); + if (!lightbox) return; + + const lightboxImage = document.getElementById('lightboxImage'); + const lightboxCaption = document.getElementById('lightboxCaption'); + const lightboxMeta = document.getElementById('lightboxMeta'); + const lightboxCounter = document.getElementById('lightboxCounter'); + const lightboxClose = document.getElementById('lightboxClose'); + const lightboxPrev = document.getElementById('lightboxPrev'); + const lightboxNext = document.getElementById('lightboxNext'); + + // Get all photo items + const photoItems = document.querySelectorAll('.photo-item'); + + // Build photo array + let currentPhotos = []; + photoItems.forEach(item => { + const image = item.querySelector('img'); + const date = item.dataset.date || ''; + const pubkey = item.dataset.pubkey || ''; + const alt = item.dataset.alt || ''; + + if (image) { + currentPhotos.push({ + url: image.src, + alt: alt, + date: date, + pubkey: pubkey + }); + } + }); + + let currentIndex = 0; + + // Remove old event listeners by cloning and replacing + const newLightboxImage = lightboxImage.cloneNode(true); + const newLightboxCaption = lightboxCaption.cloneNode(true); + const newLightboxMeta = lightboxMeta.cloneNode(true); + const newLightboxCounter = lightboxCounter.cloneNode(true); + const newLightboxClose = lightboxClose.cloneNode(true); + const newLightboxPrev = lightboxPrev.cloneNode(true); + const newLightboxNext = lightboxNext.cloneNode(true); + + lightboxImage.parentNode.replaceChild(newLightboxImage, lightboxImage); + lightboxCaption.parentNode.replaceChild(newLightboxCaption, lightboxCaption); + lightboxMeta.parentNode.replaceChild(newLightboxMeta, lightboxMeta); + lightboxCounter.parentNode.replaceChild(newLightboxCounter, lightboxCounter); + lightboxClose.parentNode.replaceChild(newLightboxClose, lightboxClose); + lightboxPrev.parentNode.replaceChild(newLightboxPrev, lightboxPrev); + lightboxNext.parentNode.replaceChild(newLightboxNext, lightboxNext); + + // Update references + const newImage = document.getElementById('lightboxImage'); + const newCaption = document.getElementById('lightboxCaption'); + const newMeta = document.getElementById('lightboxMeta'); + const newCounter = document.getElementById('lightboxCounter'); + const newClose = document.getElementById('lightboxClose'); + const newPrev = document.getElementById('lightboxPrev'); + const newNext = document.getElementById('lightboxNext'); + + // Open lightbox + function openLightbox(index) { + if (index < 0) index = currentPhotos.length - 1; + if (index >= currentPhotos.length) index = 0; + currentIndex = index; + + const photo = currentPhotos[currentIndex]; + newImage.src = photo.url; + newImage.alt = photo.alt; + newCaption.textContent = photo.alt; + newMeta.textContent = `πŸ“… ${photo.date} | πŸ–ŠοΈ ${photo.pubkey}`; + newCounter.textContent = `${currentIndex + 1} / ${currentPhotos.length}`; + + lightbox.classList.add('active'); + document.body.style.overflow = 'hidden'; + } + + // Close lightbox + function closeLightbox() { + lightbox.classList.remove('active'); + document.body.style.overflow = ''; + } + + // Navigate + function prevPhoto() { + openLightbox(currentIndex - 1); + } + + function nextPhoto() { + openLightbox(currentIndex + 1); + } + + // Event listeners for photo clicks + photoItems.forEach((item, index) => { + const img = item.querySelector('img'); + if (img) { + // Remove old listener by cloning + const newImg = img.cloneNode(true); + img.parentNode.replaceChild(newImg, img); + newImg.addEventListener('click', function(e) { + e.preventDefault(); + openLightbox(index); + }); + } + }); + + // Event listeners for lightbox controls + newClose.addEventListener('click', closeLightbox); + newPrev.addEventListener('click', prevPhoto); + newNext.addEventListener('click', nextPhoto); + + // Keyboard controls + document.removeEventListener('keydown', handleLightboxKeydown); + document.addEventListener('keydown', handleLightboxKeydown); + + function handleLightboxKeydown(e) { + if (!lightbox.classList.contains('active')) return; + + if (e.key === 'Escape') { + closeLightbox(); + } else if (e.key === 'ArrowLeft') { + prevPhoto(); + } else if (e.key === 'ArrowRight') { + nextPhoto(); + } + } + + // Click outside image to close + lightbox.removeEventListener('click', handleLightboxClick); + lightbox.addEventListener('click', handleLightboxClick); + + function handleLightboxClick(e) { + if (e.target === lightbox) { + closeLightbox(); + } + } +} + +// Update the DOMContentLoaded event listener +document.addEventListener('DOMContentLoaded', function() { + // ... (your existing code for theme toggle, sticky nav, etc.) ... + + // Initialize Music (if you already have this) + if (window.allMusicData && window.allMusicData.length > 0) { + renderMusicTracks(window.allMusicData, 'musicGrid'); + } + + // Initialize Pictures + if (window.allPicturesData && window.allPicturesData.length > 0) { + renderRandomPictures(window.allPicturesData, 'photoGrid'); + } else { + // If no data, still initialize lightbox for static content + initCustomLightbox(); + } + + // Initialize Videos + if (window.allVideosData && window.allVideosData.length > 0) { + renderRandomVideos(window.allVideosData, 'videoGrid'); + } +}); + +// Refresh functions +function refreshMusic() { + if (window.allMusicData) { + renderMusicTracks(window.allMusicData, 'musicGrid'); + } +} + +function refreshPictures() { + if (window.allPicturesData) { + renderRandomPictures(window.allPicturesData, 'photoGrid'); + } +} + +function refreshVideos() { + if (window.allVideosData) { + renderRandomVideos(window.allVideosData, 'videoGrid'); + } +} + +// Render random videos (2 max) +function renderRandomVideos(videosData, containerId) { + const container = document.getElementById(containerId); + if (!container) return; + + // Shuffle and get first 2 + const shuffled = shuffleArray(videosData); + const selected = shuffled.slice(0, 2); + + // Clear container + container.innerHTML = ''; + + // Static poster image + const posterImage = '/images/videoposter.png'; + + + let html = ''; + + selected.forEach(video => { + let videoUrl = ''; + let mimeType = 'video/mp4'; + let alt = video.content || ''; + let dim = ''; + let isVertical = false; + + if (video.tags) { + video.tags.forEach(tag => { + if (tag[0] === 'imeta') { + const imetaStr = tag[1]; + const urlMatch = imetaStr.match(/url ([^\s]+)/); + if (urlMatch) videoUrl = urlMatch[1]; + + const mimeMatch = imetaStr.match(/m ([^\s]+)/); + if (mimeMatch) mimeType = mimeMatch[1]; + + const altMatch = imetaStr.match(/alt ([^\s]+)/); + if (altMatch) alt = altMatch[1]; + + const dimMatch = imetaStr.match(/dim ([^\s]+)/); + if (dimMatch) { + dim = dimMatch[1]; + const [width, height] = dim.split('x').map(Number); + if (height > width) isVertical = true; + } + } + }); + } + + const date = new Date(video.created_at * 1000); + const dateStr = date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + const wrapperStyle = isVertical + ? 'style="position:relative;padding-bottom:177.78%;height:0;overflow:hidden;background:var(--accent-black);"' + : ''; + + const videoStyle = isVertical + ? 'style="position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;"' + : 'style="width:100%;"'; + + html += ` +
+ ${videoUrl ? ` +
+ +
+ ` : ''} +
+

${alt}

+
+ πŸ“… ${dateStr} + ${dim ? `πŸ“ ${dim}` : ''} +
+
+
+ `; + }); + + container.innerHTML = html; +} + +// ============ INITIALIZATION ============ + +document.addEventListener('DOMContentLoaded', function() { + // ... (your existing code for theme toggle, sticky nav, etc.) ... + + // Initialize Music (if you already have this) + if (window.allMusicData && window.allMusicData.length > 0) { + renderMusicTracks(window.allMusicData, 'musicGrid'); + } + + // Initialize Pictures + if (window.allPicturesData && window.allPicturesData.length > 0) { + renderRandomPictures(window.allPicturesData, 'photoGrid'); + } + + // Initialize Videos + if (window.allVideosData && window.allVideosData.length > 0) { + renderRandomVideos(window.allVideosData, 'videoGrid'); + } +}); + +// Refresh functions for randomize buttons +function refreshMusic() { + if (window.allMusicData) { + renderMusicTracks(window.allMusicData, 'musicGrid'); + } +} + +function refreshPictures() { + if (window.allPicturesData) { + renderRandomPictures(window.allPicturesData, 'photoGrid'); + } +} + +function refreshVideos() { + if (window.allVideosData) { + renderRandomVideos(window.allVideosData, 'videoGrid'); + } +} diff --git a/themes/one-pager/layouts/index.html b/themes/one-pager/layouts/index.html new file mode 100644 index 0000000..27f4a9a --- /dev/null +++ b/themes/one-pager/layouts/index.html @@ -0,0 +1,180 @@ + + + + + + + {{ partial "seo.html" . }} + + + + + + + + + + {{ $style := resources.Get "css/style.css" | resources.Minify | fingerprint }} + + + + + + {{ partial "header.html" . }} + +
+ +
+

Now Log

+
+ {{ $blogPosts := slice }} + {{ range $path, $entries := hugo.Data.longform }} + {{ range $entries }} + {{ $blogPosts = $blogPosts | append . }} + {{ end }} + {{ end }} + + {{ $sortedPosts := sort $blogPosts "created_at" "desc" }} + {{ range first 2 $sortedPosts }} + {{ $title := "" }} + {{ $summary := "" }} + {{ $image := "" }} + {{ $tags := slice }} + {{ $d := "" }} + {{ $createdAt := int .created_at }} + + {{ range .tags }} + {{ if eq (index . 0) "title" }} + {{ $title = index . 1 }} + {{ else if eq (index . 0) "summary" }} + {{ $summary = index . 1 }} + {{ else if eq (index . 0) "image" }} + {{ $image = index . 1 }} + {{ else if eq (index . 0) "t" }} + {{ $tags = $tags | append (index . 1) }} + {{ else if eq (index . 0) "d" }} + {{ $d = index . 1 }} + {{ end }} + {{ end }} + +
+ {{ if $image }} + {{ $title }} + {{ end }} +
+

{{ $title }}

+

+ {{ dateFormat "January 2, 2006" (time (int .created_at)) }} +

+ {{ if $summary }} +

{{ $summary }}

+ {{ end }} +
+ {{ range $tags }} + {{ . }} + {{ end }} +
+ + +
+
+ {{ end }} +
+
+ + +
+

Music Traxxx

+
+ +
+
+ {{ $allMusic := slice }} + {{ range $path, $entries := hugo.Data.music }} + {{ range $entries }} + {{ $allMusic = $allMusic | append . }} + {{ end }} + {{ end }} + + {{ $musicData := jsonify $allMusic }} + +
+
+ + +
+

Pix & Doodles

+
+ +
+
+ {{ $allPictures := slice }} + {{ range $path, $entries := hugo.Data.pictures }} + {{ range $entries }} + {{ $allPictures = $allPictures | append . }} + {{ end }} + {{ end }} + + + {{ $picturesData := jsonify $allPictures }} + +
+
+ + +
+

Vidz

+
+ +
+
+ {{ $allVideos := slice }} + {{ range $path, $entries := hugo.Data.videos }} + {{ range $entries }} + {{ $allVideos = $allVideos | append . }} + {{ end }} + {{ end }} + + + {{ $videosData := jsonify $allVideos }} + +
+
+
+ + + {{ partial "footer.html" . }} + + +
+ βœ• + + +
+ +
+ + +
+
+
1 / 1
+
+ + {{ $js := resources.Get "js/scripts.js" | resources.Minify | fingerprint }} + + + diff --git a/themes/one-pager/layouts/partials/footer.html b/themes/one-pager/layouts/partials/footer.html new file mode 100644 index 0000000..584955f --- /dev/null +++ b/themes/one-pager/layouts/partials/footer.html @@ -0,0 +1,55 @@ + diff --git a/themes/one-pager/layouts/partials/header.html b/themes/one-pager/layouts/partials/header.html new file mode 100644 index 0000000..efe909f --- /dev/null +++ b/themes/one-pager/layouts/partials/header.html @@ -0,0 +1,85 @@ +{{ $displayName := "" }} +{{ $name := "" }} +{{ $picture := "" }} +{{ $banner := "" }} +{{ $about := "" }} +{{ $website := "" }} +{{ $nip05 := "" }} +{{ $lud16 := "" }} + +{{ range hugo.Data.profile }} + {{ range . }} + {{ range .tags }} + {{ if eq (index . 0) "display_name" }} + {{ $displayName = index . 1 }} + {{ else if eq (index . 0) "name" }} + {{ $name = index . 1 }} + {{ else if eq (index . 0) "picture" }} + {{ $picture = index . 1 }} + {{ else if eq (index . 0) "banner" }} + {{ $banner = index . 1 }} + {{ else if eq (index . 0) "about" }} + {{ $about = index . 1 }} + {{ else if eq (index . 0) "website" }} + {{ $website = index . 1 }} + {{ else if eq (index . 0) "nip05" }} + {{ $nip05 = index . 1 }} + {{ else if eq (index . 0) "lud16" }} + {{ $lud16 = index . 1 }} + {{ end }} + {{ end }} + {{ end }} +{{ end }} + + +{{ if not $displayName }} + {{ $displayName = $name }} +{{ end }} + + + + + + +
+ + {{ if $banner }} +
+ +
+ {{ end }} + + + + +
+ +
+
+ {{ if $picture }} + {{ $displayName }} + {{ end }} +
+ + {{ if $about }} +

{{ if $displayName }}{{ $displayName }}{{ else }}{{ .Site.Title }}{{ end }}

{{ $about | markdownify }}
+ {{ else }} +
{{ .Site.Params.description }}
+ {{ end }} +
+
+
+ +
diff --git a/themes/one-pager/layouts/partials/seo.html b/themes/one-pager/layouts/partials/seo.html new file mode 100644 index 0000000..b20bcf0 --- /dev/null +++ b/themes/one-pager/layouts/partials/seo.html @@ -0,0 +1,148 @@ + + + +{{/* Get profile data */}} +{{ $displayName := "" }} +{{ $name := "" }} +{{ $picture := "" }} +{{ $about := "" }} + +{{ range hugo.Data.profile }} + {{ range . }} + {{ range .tags }} + {{ if eq (index . 0) "display_name" }} + {{ $displayName = index . 1 }} + {{ else if eq (index . 0) "name" }} + {{ $name = index . 1 }} + {{ else if eq (index . 0) "picture" }} + {{ $picture = index . 1 }} + {{ else if eq (index . 0) "about" }} + {{ $about = index . 1 }} + {{ end }} + {{ end }} + {{ end }} +{{ end }} + +{{/* Fallback if display_name is empty */}} +{{ if not $displayName }} + {{ $displayName = $name }} +{{ end }} + +{{/* If still empty, use site title */}} +{{ if not $displayName }} + {{ $displayName = .Site.Title }} +{{ end }} + +{{/* Clean about text - remove markdown and HTML */}} +{{ $cleanAbout := $about | plainify | htmlUnescape }} +{{ $cleanAbout = trim $cleanAbout " \n\r\t" }} + +{{/* Truncate to 160 chars for SEO */}} +{{ $truncatedAbout := $cleanAbout }} +{{ if gt (len $truncatedAbout) 160 }} + {{ $truncatedAbout = substr $truncatedAbout 0 160 | printf "%s…" }} +{{ end }} + +{{/* Hugo escaping functions */}} +{{ $escapedTitle := $displayName | safeHTMLAttr }} +{{ $escapedDescription := $truncatedAbout | safeHTMLAttr }} +{{ $escapedPicture := $picture | safeURL }} +{{ $escapedSiteTitle := .Site.Title | safeHTMLAttr }} + +{{/* Build time in ISO 8601 format */}} +{{ $buildTime := now.Format "2006-01-02T15:04:05-07:00" }} +{{ $buildTimeRFC3339 := now.Format "2006-01-02T15:04:05Z07:00" }} + +{{/* Hugo version */}} +{{ $hugoVersion := hugo.Version }} + +{{/* Base URL */}} +{{ $baseURL := .Site.BaseURL | safeURL }} +{{ $permalink := .Permalink | safeURL }} + + +{{ $escapedTitle }}{{ if ne $escapedTitle $escapedSiteTitle }} | {{ $escapedSiteTitle }}{{ end }} + + + + + + + + + + + + + + +{{ if $picture }} + +{{ end }} + + + + + + + + + +{{ if $picture }} + + +{{ end }} + + + + + + + + + + + + + + + + + + + + diff --git a/themes/one-pager/static/images/videoposter.png b/themes/one-pager/static/images/videoposter.png new file mode 100644 index 0000000..7c7f8f9 Binary files /dev/null and b/themes/one-pager/static/images/videoposter.png differ diff --git a/video-poster.svg b/video-poster.svg new file mode 100644 index 0000000..b82639c --- /dev/null +++ b/video-poster.svg @@ -0,0 +1,56 @@ + + + +