Files
linkbeam/templates/base.html

319 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content="LinkBeam - https://github.com/5mdt/linkbeam" />
<title>{{ .Name }} - LinkBeam</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.png">
<link rel="stylesheet" href="/themes/{{ .Theme }}.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
.theme-toggle {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1000;
width: auto;
}
.theme-label {
display: none;
}
@media (min-width: 768px) {
.theme-label {
display: inline;
font-size: 0.9rem;
font-weight: 500;
}
}
.socials {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
max-width: 600px;
margin: 2rem auto;
padding: 1rem;
}
.socials a,
.socials button {
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
background-color: var(--link-bg);
border: 1px solid var(--border-color);
border-radius: 50%;
color: var(--text-color);
text-decoration: none;
font-size: 1.5rem;
transition: all 0.2s ease;
cursor: pointer;
padding: 0;
}
.socials a:hover,
.socials button:hover {
background-color: var(--link-hover);
transform: scale(1.1);
}
.copy-button.copied {
background-color: var(--link-hover);
}
.section-title {
text-align: center;
font-size: 1.2rem;
font-weight: 600;
margin: 2rem auto 1rem;
color: var(--text-color);
text-transform: capitalize;
}
.copy-feedback {
position: fixed;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
background-color: var(--link-bg);
border: 1px solid var(--border-color);
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: 1000;
}
.copy-feedback.show {
opacity: 1;
}
</style>
</head>
<body>
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
<span id="themeIcon">💡</span>
<span class="theme-label" id="themeLabel">System</span>
</button>
<header>
{{- if .Avatar }}
<img src="{{ .Avatar }}" alt="{{ .Name }}" width="100" height="100" />
{{- end }}
<h1>{{ .Name }}</h1>
<p>{{ .Bio }}</p>
</header>
{{- range .Content }}
{{- if eq .Type "vertical-list-text" }}
{{- range $collectionName, $items := .Collections }}
{{- if $collectionName }}
<h2 class="section-title">{{ $collectionName }}</h2>
{{- end }}
<main>
<ul>
{{- range $items }}
<li>
{{- if .URL }}
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer">
{{- if .Icon }}<i class="{{ .Icon }}"></i> {{- end }}{{ .Title }}
</a>
{{- else if .CopyText }}
<button class="copy-button" onclick="copyToClipboard('{{ .CopyText }}', this)" aria-label="Copy {{ .Title }}">
{{- if .Icon }}<i class="{{ .Icon }}"></i> {{- end }}{{ .Title }}: <code>{{ .CopyText }}</code>
</button>
{{- else if .Text }}
<span>
{{- if .Icon }}<i class="{{ .Icon }}"></i> {{- end }}{{ .Text }}
</span>
{{- end }}
</li>
{{- end }}
</ul>
</main>
{{- end }}
{{- else if eq .Type "horizontal-list-icons" }}
{{- range $collectionName, $items := .Collections }}
{{- if $collectionName }}
<h2 class="section-title">{{ $collectionName }}</h2>
{{- end }}
<div class="socials">
{{- range $items }}
{{- if .URL }}
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" aria-label="{{ .Title }}" title="{{ .Title }}">
{{- if .Icon }}<i class="{{ .Icon }}"></i>{{- end }}
</a>
{{- else if .CopyText }}
<button class="copy-button" onclick="copyToClipboard('{{ .CopyText }}', this)" aria-label="Copy {{ .Title }}" title="{{ .Title }}">
{{- if .Icon }}<i class="{{ .Icon }}"></i>{{- end }}
</button>
{{- end }}
{{- end }}
</div>
{{- end }}
{{- else if eq .Type "vertical-list-images" }}
{{- range $collectionName, $items := .Collections }}
{{- if $collectionName }}
<h2 class="section-title">{{ $collectionName }}</h2>
{{- end }}
<main>
<ul>
{{- range $items }}
<li>
<div class="image-item">
{{- if .Image }}
<img src="{{ .Image }}" alt="{{ .AltText }}" loading="lazy" />
{{- end }}
{{- if .Text }}
<p class="image-caption">{{ .Text }}</p>
{{- end }}
</div>
</li>
{{- end }}
</ul>
</main>
{{- end }}
{{- else if eq .Type "footer" }}
{{- range $collectionName, $items := .Collections }}
<footer>
{{- range $items }}
<p>{{ .Text }}</p>
{{- end }}
</footer>
{{- end }}
{{- end }}
{{- end }}
<div class="copy-feedback" id="copyFeedback">Copied to clipboard!</div>
<script>
// Copy to clipboard functionality with fallback
function copyToClipboard(text, button) {
// Try modern Clipboard API first (requires HTTPS or localhost)
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
showCopySuccess(button);
}).catch(err => {
console.error('Clipboard API failed:', err);
fallbackCopy(text, button);
});
} else {
// Fallback for HTTP or older browsers
fallbackCopy(text, button);
}
}
function fallbackCopy(text, button) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
showCopySuccess(button);
} else {
showCopyError();
}
} catch (err) {
console.error('Fallback copy failed:', err);
showCopyError();
}
document.body.removeChild(textArea);
}
function showCopySuccess(button) {
const feedback = document.getElementById('copyFeedback');
feedback.textContent = 'Copied to clipboard!';
feedback.style.backgroundColor = '';
feedback.classList.add('show');
button.classList.add('copied');
setTimeout(() => {
feedback.classList.remove('show');
button.classList.remove('copied');
}, 2000);
}
function showCopyError() {
const feedback = document.getElementById('copyFeedback');
feedback.textContent = 'Failed to copy. Please select text manually.';
feedback.style.backgroundColor = 'var(--link-hover)';
feedback.classList.add('show');
setTimeout(() => {
feedback.classList.remove('show');
feedback.textContent = 'Copied to clipboard!';
feedback.style.backgroundColor = '';
}, 3000);
}
const THEME_OPTIONS = ['system', 'light', 'dark'];
const THEME_ICONS = { system: '💡', light: '☀️', dark: '🌙' };
const THEME_LABELS = { system: 'System', light: 'Light', dark: 'Dark' };
// Storage with fallback
function getStorage() {
try {
localStorage.setItem('__test__', '1');
localStorage.removeItem('__test__');
return localStorage;
} catch {
return sessionStorage;
}
}
function getFromStorage(key, defaultValue) {
try {
return getStorage().getItem(key) || defaultValue;
} catch {
return defaultValue;
}
}
function saveToStorage(key, value) {
try {
getStorage().setItem(key, value);
} catch {
// Silent fail in private browsing
}
}
function applyTheme(theme) {
// Update HTML attribute
if (theme === 'system') {
document.documentElement.removeAttribute('data-theme');
} else {
document.documentElement.setAttribute('data-theme', theme);
}
// Update UI
document.getElementById('themeIcon').textContent = THEME_ICONS[theme];
document.getElementById('themeLabel').textContent = THEME_LABELS[theme];
// Save preference
saveToStorage('themePreference', theme);
}
function toggleTheme() {
const current = getFromStorage('themePreference', 'system');
const nextIndex = (THEME_OPTIONS.indexOf(current) + 1) % 3;
const next = THEME_OPTIONS[nextIndex];
applyTheme(next);
}
function initTheme() {
const saved = getFromStorage('themePreference', 'system');
applyTheme(saved);
}
// Initialize on load
initTheme();
// Attach event listener
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
</script>
</body>
</html>