feat: background photo system — random Lanzarote landscapes behind CV grid
Dev-only toggle button enables/disables photo backgrounds. Photos are auto-discovered from static/images/backgrounds/ and randomly selected on each page load. Production is unaffected — no button, no photo.
This commit is contained in:
@@ -352,6 +352,19 @@ func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, er
|
||||
}
|
||||
}
|
||||
|
||||
// Scan background photos (dev only)
|
||||
var bgPhotos []string
|
||||
if !isProduction {
|
||||
bgDir := filepath.Join(c.DirStatic, "images", "backgrounds")
|
||||
entries, _ := os.ReadDir(bgDir)
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if !e.IsDir() && (strings.HasSuffix(name, ".jpg") || strings.HasSuffix(name, ".png") || strings.HasSuffix(name, ".webp")) {
|
||||
bgPhotos = append(bgPhotos, "/static/images/backgrounds/"+name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare template data
|
||||
data := map[string]interface{}{
|
||||
"CV": &cv,
|
||||
@@ -366,6 +379,7 @@ func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, er
|
||||
"AlternateEN": "https://juan.andres.morenorub.io/?lang=en",
|
||||
"AlternateES": "https://juan.andres.morenorub.io/?lang=es",
|
||||
"ChatEnabled": h.chatEnabled,
|
||||
"BgPhotos": bgPhotos,
|
||||
}
|
||||
|
||||
return data, nil
|
||||
|
||||
@@ -12,23 +12,25 @@
|
||||
/* Body base */
|
||||
body {
|
||||
background-color: var(--page-bg, #d6d6d6);
|
||||
|
||||
/* OLD PATTERN - Keep for reference (can be restored anytime) */
|
||||
/* background-image:
|
||||
linear-gradient(90deg, rgba(0, 0, 0, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(180deg, rgba(0, 0, 0, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0, 0, 0, 0.02) 1px, transparent 1px),
|
||||
linear-gradient(180deg, rgba(0, 0, 0, 0.02) 1px, transparent 1px);
|
||||
background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px; */
|
||||
|
||||
/* NEW TEST PATTERNS - Theme-specific (woven fabric for light, diagonal grid for dark) */
|
||||
background-image: var(--page-bg-pattern, none);
|
||||
background-size: 40px 40px; /* For dark theme diagonal grid */
|
||||
background-size: 40px 40px;
|
||||
background-attachment: fixed;
|
||||
max-width: 100vw; /* Prevent horizontal overflow */
|
||||
overflow-x: clip; /* Clip prevents horizontal scroll WITHOUT breaking position: sticky */
|
||||
}
|
||||
|
||||
/* Background photo layer — activated via JS in dev mode */
|
||||
body.bg-photo {
|
||||
background-image:
|
||||
var(--page-bg-pattern, none),
|
||||
linear-gradient(var(--page-bg-tint, rgba(214,214,214,0.85)), var(--page-bg-tint, rgba(214,214,214,0.85))),
|
||||
var(--bg-photo-url, none);
|
||||
background-size: auto, auto, cover;
|
||||
background-position: 0 0, 0 0, center;
|
||||
background-repeat: repeat, repeat, no-repeat;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
|
||||
@@ -62,6 +62,9 @@
|
||||
/* Sidebar (for non-clean theme) */
|
||||
--sidebar-bg: #d1d4d2;
|
||||
|
||||
/* Background photo tint — controls how much of the photo shows through */
|
||||
--page-bg-tint: rgba(214, 214, 214, 0.85);
|
||||
|
||||
/* Legacy CV content variables - theme-aware overrides */
|
||||
--text-dark: #1a1a1a; /* Dark text for light background */
|
||||
--text-gray: #333333; /* Secondary text for light background */
|
||||
@@ -116,6 +119,9 @@
|
||||
/* Sidebar (for non-clean theme) - darker than light theme but lighter than main content */
|
||||
--sidebar-bg: #3a3d3e;
|
||||
|
||||
/* Background photo tint — darker overlay for dark theme */
|
||||
--page-bg-tint: rgba(58, 58, 58, 0.85);
|
||||
|
||||
/* Legacy CV content variables - theme-aware overrides */
|
||||
--text-dark: #e0e0e0; /* Light text for dark background */
|
||||
--text-gray: #d0d0d0; /* Secondary text for dark background */
|
||||
@@ -171,6 +177,9 @@
|
||||
/* Sidebar (for non-clean theme) - matches explicit dark theme */
|
||||
--sidebar-bg: #3a3d3e;
|
||||
|
||||
/* Background photo tint — darker overlay for dark theme */
|
||||
--page-bg-tint: rgba(58, 58, 58, 0.85);
|
||||
|
||||
/* Legacy CV content variables - theme-aware overrides */
|
||||
--text-dark: #e0e0e0; /* Light text for dark background */
|
||||
--text-gray: #d0d0d0; /* Secondary text for dark background */
|
||||
|
||||
@@ -124,6 +124,39 @@
|
||||
color: #27ae60; /* Green icon when at bottom */
|
||||
}
|
||||
|
||||
/* Background Photo Toggle (Dev Only - above download button) */
|
||||
.bg-photo-btn {
|
||||
position: fixed;
|
||||
bottom: 30rem;
|
||||
left: 2rem;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: var(--black-bar, #2b2b2b);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
z-index: 999;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.bg-photo-btn:hover {
|
||||
opacity: 1;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
|
||||
background: #8e6b3e !important; /* Earthy brown — Lanzarote vibes */
|
||||
}
|
||||
|
||||
.bg-photo-btn.at-bottom {
|
||||
opacity: 1;
|
||||
background: #8e6b3e !important;
|
||||
}
|
||||
|
||||
/* Download Button (TOP POSITION - now first button after cmd-k removed) */
|
||||
.download-btn {
|
||||
position: fixed;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 413 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 353 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 524 KiB |
@@ -50,6 +50,7 @@
|
||||
{{template "contact-button" .}}
|
||||
{{template "zoom-toggle-button" .}}
|
||||
{{template "shortcuts-button" .}}
|
||||
{{template "bg-photo-toggle" .}}
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- MODALS -->
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
{{define "bg-photo-toggle"}}
|
||||
{{if not .IsProduction}}
|
||||
<!-- Background Photo Toggle (Dev Only) -->
|
||||
<button
|
||||
id="bg-photo-toggle"
|
||||
class="fixed-btn bg-photo-btn no-print has-tooltip"
|
||||
aria-label="Toggle background photo"
|
||||
data-tooltip="Toggle background photo"
|
||||
onclick="toggleBgPhoto()">
|
||||
<iconify-icon icon="mdi:image-outline" width="24" height="24"></iconify-icon>
|
||||
</button>
|
||||
<script>
|
||||
(function() {
|
||||
var photos = [{{range $i, $p := .BgPhotos}}{{if $i}},{{end}}'{{$p}}'{{end}}];
|
||||
var key = 'bg-photo-enabled';
|
||||
var keyIdx = 'bg-photo-index';
|
||||
|
||||
function pickPhoto() {
|
||||
var idx = Math.floor(Math.random() * photos.length);
|
||||
localStorage.setItem(keyIdx, idx);
|
||||
return photos[idx];
|
||||
}
|
||||
|
||||
function applyPhoto(enabled) {
|
||||
var btn = document.getElementById('bg-photo-toggle');
|
||||
var icon = btn ? btn.querySelector('iconify-icon') : null;
|
||||
if (enabled) {
|
||||
var idx = parseInt(localStorage.getItem(keyIdx), 10);
|
||||
var url = (isNaN(idx) || idx >= photos.length) ? pickPhoto() : photos[idx];
|
||||
document.body.style.setProperty('--bg-photo-url', 'url("' + url + '")');
|
||||
document.body.classList.add('bg-photo');
|
||||
if (icon) icon.setAttribute('icon', 'mdi:image');
|
||||
} else {
|
||||
document.body.classList.remove('bg-photo');
|
||||
if (icon) icon.setAttribute('icon', 'mdi:image-outline');
|
||||
}
|
||||
}
|
||||
|
||||
// Init: pick a random photo each load, restore toggle state
|
||||
pickPhoto();
|
||||
var enabled = localStorage.getItem(key) !== 'false'; // default on
|
||||
applyPhoto(enabled);
|
||||
|
||||
window.toggleBgPhoto = function() {
|
||||
var isOn = document.body.classList.contains('bg-photo');
|
||||
localStorage.setItem(key, !isOn);
|
||||
applyPhoto(!isOn);
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user