Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions components/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,11 @@ <h3>Newsletter</h3>
<p>&copy; 2023 Love Alchemy. All rights reserved. Made with <i class="fas fa-heart" style="color: #ff2e63;"></i> for love</p>
</div>
</footer>

<!-- Back-to-top floating button (site-wide) -->
<button id="backBtn" class="back-btn" title="Back to top" aria-label="Back to top">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M12 19V6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M5 12l7-7 7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
96 changes: 96 additions & 0 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,102 @@ function combineNumbers(num1, num2, supportMaster = true) {
return combined
}

// Back button behavior — added for site-wide quick navigation
;(function() {
function scrollToTopSmooth() {
try {
window.scrollTo({ top: 0, behavior: 'smooth' })
} catch (err) {
window.scrollTo(0, 0)
}
}

function initBackBtn(backBtn) {
if (!backBtn) return

const SHOW_AFTER = 200
function updateBackBtnVisibility() {
const y = window.scrollY || window.pageYOffset
if (y > SHOW_AFTER) {
backBtn.style.opacity = '1'
backBtn.style.pointerEvents = 'auto'
backBtn.style.transform = 'translateY(0) scale(1)'
} else {
backBtn.style.opacity = '0'
backBtn.style.pointerEvents = 'none'
backBtn.style.transform = 'translateY(8px) scale(0.98)'
}
}

// Apply initial inline styles for a smooth transition even if CSS hasn't loaded yet
backBtn.style.transition = 'opacity 0.18s ease, transform 0.18s ease'
backBtn.style.opacity = '0'
backBtn.style.pointerEvents = 'none'
backBtn.style.transform = 'translateY(8px) scale(0.98)'

// Bind scroll listener
window.addEventListener('scroll', () => {
if (typeof window.requestAnimationFrame === 'function') {
window.requestAnimationFrame(updateBackBtnVisibility)
} else {
updateBackBtnVisibility()
}
}, { passive: true })

backBtn.addEventListener('click', (e) => {
e.preventDefault()
e.stopPropagation()
scrollToTopSmooth()
})

// Keyboard support
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape' || e.key === 'Home') {
scrollToTopSmooth()
}
})

// Initial visibility check in case page is already scrolled
updateBackBtnVisibility()
}

// Try to initialize immediately
document.addEventListener('DOMContentLoaded', () => {
const btn = document.getElementById('backBtn')
if (btn) {
initBackBtn(btn)
return
}

// If footer is injected later, observe DOM mutations for the button
const observer = new MutationObserver((mutations, obs) => {
const found = document.getElementById('backBtn')
if (found) {
initBackBtn(found)
obs.disconnect()
}
})

observer.observe(document.body, { childList: true, subtree: true })

// Fallback: short polling for up to 3 seconds
const start = Date.now()
const poll = setInterval(() => {
const found = document.getElementById('backBtn')
if (found) {
clearInterval(poll)
observer.disconnect()
initBackBtn(found)
return
}
if (Date.now() - start > 3000) {
clearInterval(poll)
observer.disconnect()
}
}, 150)
})
})()

function mapToPercent(combined, num1, num2) {

let base
Expand Down
35 changes: 35 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,41 @@ body {
background: rgba(255,255,255,0.03);
color: #fff;
}

/* Floating back button (bottom-right) */
.back-btn {
position: fixed;
right: 18px;
bottom: 18px;
width: 52px;
height: 52px;
border-radius: 50%;
display: inline-grid;
place-items: center;
background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.03));
border: 1px solid rgba(255,255,255,0.08);
color: var(--accent2);
box-shadow: 0 8px 24px rgba(0,0,0,0.36);
cursor: pointer;
z-index: 1200;
transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease;
}

.back-btn svg { color: var(--accent2); }

.back-btn:active { transform: translateY(2px) scale(0.98); }
.back-btn:focus { outline: none; box-shadow: 0 0 0 6px rgba(255,46,99,0.09); }

/* Subtle hide on very small screens to avoid overlapping form controls */
@media (max-width: 360px) {
.back-btn { right: 12px; bottom: 12px; width: 48px; height: 48px; }
}

/* Dark-mode variant */
body.dark-mode .back-btn {
background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
border: 1px solid rgba(255,255,255,0.04);
}
.contact-actions {
display: flex;
gap: 8px;
Expand Down