refactor(templates): decouple css and javascript from html #45
No files matched your search
@@ -6,262 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Category.Title}}</title>
|
||||
<style>
|
||||
:root {
|
||||
--background-color: #f3f4f6;
|
||||
--text-color: #374151;
|
||||
--container-background: #ffffff;
|
||||
--header-title-color: #1f2937;
|
||||
--header-date-color: #6b7280;
|
||||
--group-title-color: #6b7280;
|
||||
--entry-background: #f9fafb;
|
||||
--entry-border-color: #e5e7eb;
|
||||
--entry-focus-shadow: rgba(59, 130, 246, 0.5);
|
||||
--entry-focus-border: #3b82f6;
|
||||
--entry-summary-color: #1d4ed8;
|
||||
--entry-open-background: #e0f2fe;
|
||||
--entry-open-border: #bfdbfe;
|
||||
--entry-active-shadow: rgba(234, 88, 12, 0.7);
|
||||
--entry-active-border: #ea580c;
|
||||
--entry-active-background: #fff7ed;
|
||||
--entry-meta-text-color: #6b7280;
|
||||
--entry-meta-border-color: #e5e7eb;
|
||||
--entry-content-text-color: #4b5563;
|
||||
--entry-link-color: #3b82f6;
|
||||
--entry-link-focus-background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background-color: #1f2937;
|
||||
--text-color: #d1d5db;
|
||||
--container-background: #111827;
|
||||
--header-title-color: #f9fafb;
|
||||
--header-date-color: #9ca3af;
|
||||
--group-title-color: #9ca3af;
|
||||
--entry-background: #374151;
|
||||
--entry-border-color: #4b5563;
|
||||
--entry-focus-shadow: rgba(96, 165, 250, 0.5);
|
||||
--entry-focus-border: #60a5fa;
|
||||
--entry-summary-color: #93c5fd;
|
||||
--entry-open-background: #374151;
|
||||
--entry-open-border: #4b5563;
|
||||
--entry-active-shadow: rgba(251, 146, 60, 0.7);
|
||||
--entry-active-border: #fb923c;
|
||||
--entry-active-background: #4a2c13;
|
||||
--entry-meta-text-color: #9ca3af;
|
||||
--entry-meta-border-color: #4b5563;
|
||||
--entry-content-text-color: #d1d5db;
|
||||
--entry-link-color: #00ff00;
|
||||
--entry-visited-link-color: #00cc00;
|
||||
--entry-link-focus-background: rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--entry-link-color);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--entry-visited-link-color);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
section.header {
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section.header h1.title {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 700;
|
||||
color: var(--header-title-color);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
section.header .date {
|
||||
font-size: 0.875rem;
|
||||
color: var(--header-date-color);
|
||||
}
|
||||
|
||||
section.summary p {
|
||||
margin: 1rem auto;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--container-background);
|
||||
border: 1px solid var(--entry-border-color);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
h2.group-entries-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
color: var(--group-title-color);
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
section.entry {
|
||||
background-color: var(--entry-background);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid var(--entry-border-color);
|
||||
}
|
||||
|
||||
section.entry:focus-within {
|
||||
box-shadow: 0 0 0 3px var(--entry-focus-shadow);
|
||||
border-color: var(--entry-focus-border);
|
||||
}
|
||||
|
||||
summary.entry {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 450;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--entry-summary-color);
|
||||
text-decoration: none;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
details[open] summary.entry {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: initial;
|
||||
}
|
||||
|
||||
summary.entry:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
section.entry:has(details[open]) {
|
||||
background-color: var(--entry-open-background);
|
||||
border-color: var(--entry-open-border);
|
||||
}
|
||||
|
||||
section.entry.active {
|
||||
box-shadow: 0 0 0 3px var(--entry-active-shadow);
|
||||
border-color: var(--entry-active-border);
|
||||
background-color: var(--entry-active-background);
|
||||
}
|
||||
|
||||
section.entry.viewed:not(.active) {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.entry-meta {
|
||||
filter: grayscale(100%);
|
||||
font-size: 0.875rem;
|
||||
color: var(--entry-meta-text-color);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
border-top: 1px solid var(--entry-meta-border-color);
|
||||
}
|
||||
|
||||
section.entry.active > .entry-meta {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.entry-meta-tab {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-right: 1px solid var(--entry-meta-border-color);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.entry-meta-tab-feed {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.entry-meta-tab:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.entry-meta-tab-feed-title {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.entry-content {
|
||||
font-size: 1rem;
|
||||
color: var(--entry-content-text-color);
|
||||
padding: 1rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.entry-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: var(--text-color);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
a.entry-link {
|
||||
color: var(--entry-link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.entry-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.entry-link:focus {
|
||||
outline: 2px solid var(--entry-link-color);
|
||||
outline-offset: 2px;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--entry-link-focus-background);
|
||||
}
|
||||
|
||||
details.entry {}
|
||||
|
||||
details.entry h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
details summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
details summary>* {
|
||||
display: inline;
|
||||
}
|
||||
{{include "static/style.css"}}
|
||||
|
||||
{{range .FeedIcons}} div.feed-icon-{{.FeedID}} {
|
||||
background-image: url("data:{{.Data}}");
|
||||
@@ -269,7 +14,6 @@
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
{{end}}
|
||||
</style>
|
||||
</head>
|
||||
@@ -334,176 +78,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const entries = Array.from(document.querySelectorAll('section.entry'));
|
||||
let activeEntry = null;
|
||||
let markAsReadTimer = null;
|
||||
let focusTimer = null;
|
||||
let lastScrollY = window.scrollY;
|
||||
|
||||
function getVisibleEntries() {
|
||||
return entries.filter(entry => !entry.classList.contains('entry-hidden'));
|
||||
}
|
||||
|
||||
function markAsRead(entry) {
|
||||
const visibleEntries = getVisibleEntries();
|
||||
const entryIndex = visibleEntries.indexOf(entry);
|
||||
for (let i = 0; i < entryIndex; i++) {
|
||||
visibleEntries[i].classList.add('viewed');
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveEntry(entry, preventScroll = false) {
|
||||
clearTimeout(markAsReadTimer);
|
||||
|
||||
if (activeEntry) {
|
||||
activeEntry.classList.remove('active');
|
||||
}
|
||||
|
||||
activeEntry = entry;
|
||||
|
||||
if (activeEntry) {
|
||||
activeEntry.classList.add('viewed');
|
||||
activeEntry.classList.add('active');
|
||||
if (!preventScroll) {
|
||||
activeEntry.querySelector('summary')?.focus();
|
||||
}
|
||||
|
||||
markAsReadTimer = setTimeout(() => {
|
||||
markAsRead(activeEntry);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function focusEntry(startIndex, direction) {
|
||||
let nextIndex = startIndex;
|
||||
|
||||
const visibleEntries = getVisibleEntries();
|
||||
if (visibleEntries.length === 0) {
|
||||
setActiveEntry(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction === 'next') {
|
||||
nextIndex = Math.min(startIndex + 1, visibleEntries.length - 1);
|
||||
} else if (direction === 'prev') {
|
||||
nextIndex = Math.max(startIndex - 1, 0);
|
||||
}
|
||||
|
||||
const newEntry = visibleEntries[nextIndex];
|
||||
|
||||
if (newEntry) {
|
||||
setActiveEntry(newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
function navigate(direction) {
|
||||
const visibleEntries = getVisibleEntries();
|
||||
if (visibleEntries.length === 0) return;
|
||||
|
||||
const currentIndex = activeEntry ? visibleEntries.indexOf(activeEntry) : -1;
|
||||
|
||||
switch (direction) {
|
||||
case 'next':
|
||||
focusEntry(currentIndex, 'next');
|
||||
break;
|
||||
case 'prev':
|
||||
focusEntry(currentIndex, 'prev');
|
||||
break;
|
||||
case 'first':
|
||||
setActiveEntry(visibleEntries[0]);
|
||||
break;
|
||||
case 'last':
|
||||
setActiveEntry(visibleEntries[visibleEntries.length - 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver((intersectionEntries) => {
|
||||
const scrollDirection = window.scrollY > lastScrollY ? 'down' : 'up';
|
||||
lastScrollY = window.scrollY;
|
||||
|
||||
const activeEntryIsVisible = entries.some(entry => {
|
||||
if (entry === activeEntry) {
|
||||
const rect = entry.getBoundingClientRect();
|
||||
return rect.top >= 0 && rect.bottom <= window.innerHeight;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (activeEntryIsVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
intersectionEntries.forEach(intersectionEntry => {
|
||||
const {
|
||||
target,
|
||||
isIntersecting,
|
||||
boundingClientRect
|
||||
} = intersectionEntry;
|
||||
if (scrollDirection === 'down' && !isIntersecting && boundingClientRect.top < 0) {
|
||||
clearTimeout(focusTimer);
|
||||
focusTimer = setTimeout(() => {
|
||||
const visibleEntries = getVisibleEntries();
|
||||
const nextEntry = visibleEntries[visibleEntries.indexOf(target) + 1];
|
||||
if (nextEntry) {
|
||||
setActiveEntry(nextEntry, true);
|
||||
}
|
||||
}, 450);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.5
|
||||
});
|
||||
|
||||
entries.forEach(entry => observer.observe(entry));
|
||||
|
||||
if (entries.length) {
|
||||
setActiveEntry(getVisibleEntries()[0]);
|
||||
}
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const target = event.target;
|
||||
const entry = target.closest('section.entry');
|
||||
|
||||
if (entry) {
|
||||
setActiveEntry(entry);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (['j', 'k', 'v', 'D', 'U', 'V'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
|
||||
switch (event.key) {
|
||||
case 'j':
|
||||
navigate('next');
|
||||
break;
|
||||
case 'k':
|
||||
navigate('prev');
|
||||
break;
|
||||
case 'U':
|
||||
navigate('last');
|
||||
break;
|
||||
case 'D':
|
||||
navigate('first');
|
||||
break;
|
||||
case 'V':
|
||||
if (activeEntry) {
|
||||
const link = activeEntry.querySelector('a[data-link-internal]');
|
||||
if (link && link.href) window.open(link.href, '_blank');
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
if (activeEntry) {
|
||||
const link = activeEntry.querySelector('a[data-link-external]');
|
||||
if (link && link.href) window.open(link.href, '_blank');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
{{include "static/script.js"}}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed static
|
||||
var staticFS embed.FS
|
||||
|
||||
func FuncMap() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"include": include,
|
||||
}
|
||||
}
|
||||
|
||||
func include(filename string) (any, error) {
|
||||
b, err := fs.ReadFile(staticFS, filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch strings.ToLower(filepath.Ext(filename)) {
|
||||
case ".css":
|
||||
return template.CSS(b), nil
|
||||
case ".js":
|
||||
return template.JS(b), nil
|
||||
default:
|
||||
return template.HTML(b), nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const entries = Array.from(document.querySelectorAll('section.entry'));
|
||||
let activeEntry = null;
|
||||
let markAsReadTimer = null;
|
||||
let focusTimer = null;
|
||||
let lastScrollY = window.scrollY;
|
||||
|
||||
function getVisibleEntries() {
|
||||
return entries.filter(entry => !entry.classList.contains('entry-hidden'));
|
||||
}
|
||||
|
||||
function markAsRead(entry) {
|
||||
const visibleEntries = getVisibleEntries();
|
||||
const entryIndex = visibleEntries.indexOf(entry);
|
||||
for (let i = 0; i < entryIndex; i++) {
|
||||
visibleEntries[i].classList.add('viewed');
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveEntry(entry, preventScroll = false) {
|
||||
clearTimeout(markAsReadTimer);
|
||||
|
||||
if (activeEntry) {
|
||||
activeEntry.classList.remove('active');
|
||||
}
|
||||
|
||||
activeEntry = entry;
|
||||
|
||||
if (activeEntry) {
|
||||
activeEntry.classList.add('viewed');
|
||||
activeEntry.classList.add('active');
|
||||
if (!preventScroll) {
|
||||
activeEntry.querySelector('summary')?.focus();
|
||||
}
|
||||
|
||||
markAsReadTimer = setTimeout(() => {
|
||||
markAsRead(activeEntry);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function focusEntry(startIndex, direction) {
|
||||
let nextIndex = startIndex;
|
||||
|
||||
const visibleEntries = getVisibleEntries();
|
||||
if (visibleEntries.length === 0) {
|
||||
setActiveEntry(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction === 'next') {
|
||||
nextIndex = Math.min(startIndex + 1, visibleEntries.length - 1);
|
||||
} else if (direction === 'prev') {
|
||||
nextIndex = Math.max(startIndex - 1, 0);
|
||||
}
|
||||
|
||||
const newEntry = visibleEntries[nextIndex];
|
||||
|
||||
if (newEntry) {
|
||||
setActiveEntry(newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
function navigate(direction) {
|
||||
const visibleEntries = getVisibleEntries();
|
||||
if (visibleEntries.length === 0) return;
|
||||
|
||||
const currentIndex = activeEntry ? visibleEntries.indexOf(activeEntry) : -1;
|
||||
|
||||
switch (direction) {
|
||||
case 'next':
|
||||
focusEntry(currentIndex, 'next');
|
||||
break;
|
||||
case 'prev':
|
||||
focusEntry(currentIndex, 'prev');
|
||||
break;
|
||||
case 'first':
|
||||
setActiveEntry(visibleEntries[0]);
|
||||
break;
|
||||
case 'last':
|
||||
setActiveEntry(visibleEntries[visibleEntries.length - 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver((intersectionEntries) => {
|
||||
const scrollDirection = window.scrollY > lastScrollY ? 'down' : 'up';
|
||||
lastScrollY = window.scrollY;
|
||||
|
||||
const activeEntryIsVisible = entries.some(entry => {
|
||||
if (entry === activeEntry) {
|
||||
const rect = entry.getBoundingClientRect();
|
||||
return rect.top >= 0 && rect.bottom <= window.innerHeight;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (activeEntryIsVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
intersectionEntries.forEach(intersectionEntry => {
|
||||
const {
|
||||
target,
|
||||
isIntersecting,
|
||||
boundingClientRect
|
||||
} = intersectionEntry;
|
||||
if (scrollDirection === 'down' && !isIntersecting && boundingClientRect.top < 0) {
|
||||
clearTimeout(focusTimer);
|
||||
focusTimer = setTimeout(() => {
|
||||
const visibleEntries = getVisibleEntries();
|
||||
const nextEntry = visibleEntries[visibleEntries.indexOf(target) + 1];
|
||||
if (nextEntry) {
|
||||
setActiveEntry(nextEntry, true);
|
||||
}
|
||||
}, 450);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.5
|
||||
});
|
||||
|
||||
entries.forEach(entry => observer.observe(entry));
|
||||
|
||||
if (entries.length) {
|
||||
setActiveEntry(getVisibleEntries()[0]);
|
||||
}
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const target = event.target;
|
||||
const entry = target.closest('section.entry');
|
||||
|
||||
if (entry) {
|
||||
setActiveEntry(entry);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (['j', 'k', 'v', 'D', 'U', 'V'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
|
||||
switch (event.key) {
|
||||
case 'j':
|
||||
navigate('next');
|
||||
break;
|
||||
case 'k':
|
||||
navigate('prev');
|
||||
break;
|
||||
case 'U':
|
||||
navigate('last');
|
||||
break;
|
||||
case 'D':
|
||||
navigate('first');
|
||||
break;
|
||||
case 'V':
|
||||
if (activeEntry) {
|
||||
const link = activeEntry.querySelector('a[data-link-internal]');
|
||||
if (link && link.href) window.open(link.href, '_blank');
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
if (activeEntry) {
|
||||
const link = activeEntry.querySelector('a[data-link-external]');
|
||||
if (link && link.href) window.open(link.href, '_blank');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,256 @@
|
||||
:root {
|
||||
--background-color: #f3f4f6;
|
||||
--text-color: #374151;
|
||||
--container-background: #ffffff;
|
||||
--header-title-color: #1f2937;
|
||||
--header-date-color: #6b7280;
|
||||
--group-title-color: #6b7280;
|
||||
--entry-background: #f9fafb;
|
||||
--entry-border-color: #e5e7eb;
|
||||
--entry-focus-shadow: rgba(59, 130, 246, 0.5);
|
||||
--entry-focus-border: #3b82f6;
|
||||
--entry-summary-color: #1d4ed8;
|
||||
--entry-open-background: #e0f2fe;
|
||||
--entry-open-border: #bfdbfe;
|
||||
--entry-active-shadow: rgba(234, 88, 12, 0.7);
|
||||
--entry-active-border: #ea580c;
|
||||
--entry-active-background: #fff7ed;
|
||||
--entry-meta-text-color: #6b7280;
|
||||
--entry-meta-border-color: #e5e7eb;
|
||||
--entry-content-text-color: #4b5563;
|
||||
--entry-link-color: #3b82f6;
|
||||
--entry-link-focus-background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background-color: #1f2937;
|
||||
--text-color: #d1d5db;
|
||||
--container-background: #111827;
|
||||
--header-title-color: #f9fafb;
|
||||
--header-date-color: #9ca3af;
|
||||
--group-title-color: #9ca3af;
|
||||
--entry-background: #374151;
|
||||
--entry-border-color: #4b5563;
|
||||
--entry-focus-shadow: rgba(96, 165, 250, 0.5);
|
||||
--entry-focus-border: #60a5fa;
|
||||
--entry-summary-color: #93c5fd;
|
||||
--entry-open-background: #374151;
|
||||
--entry-open-border: #4b5563;
|
||||
--entry-active-shadow: rgba(251, 146, 60, 0.7);
|
||||
--entry-active-border: #fb923c;
|
||||
--entry-active-background: #4a2c13;
|
||||
--entry-meta-text-color: #9ca3af;
|
||||
--entry-meta-border-color: #4b5563;
|
||||
--entry-content-text-color: #d1d5db;
|
||||
--entry-link-color: #00ff00;
|
||||
--entry-visited-link-color: #00cc00;
|
||||
--entry-link-focus-background: rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--entry-link-color);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--entry-visited-link-color);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
section.header {
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section.header h1.title {
|
||||
font-size: 1.875rem;
|
||||
font-weight: 700;
|
||||
color: var(--header-title-color);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
section.header .date {
|
||||
font-size: 0.875rem;
|
||||
color: var(--header-date-color);
|
||||
}
|
||||
|
||||
section.summary p {
|
||||
margin: 1rem auto;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--container-background);
|
||||
border: 1px solid var(--entry-border-color);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
h2.group-entries-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
color: var(--group-title-color);
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
section.entry {
|
||||
background-color: var(--entry-background);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid var(--entry-border-color);
|
||||
}
|
||||
|
||||
section.entry:focus-within {
|
||||
box-shadow: 0 0 0 3px var(--entry-focus-shadow);
|
||||
border-color: var(--entry-focus-border);
|
||||
}
|
||||
|
||||
summary.entry {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 450;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--entry-summary-color);
|
||||
text-decoration: none;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
details[open] summary.entry {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: initial;
|
||||
}
|
||||
|
||||
summary.entry:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
section.entry:has(details[open]) {
|
||||
background-color: var(--entry-open-background);
|
||||
border-color: var(--entry-open-border);
|
||||
}
|
||||
|
||||
section.entry.active {
|
||||
box-shadow: 0 0 0 3px var(--entry-active-shadow);
|
||||
border-color: var(--entry-active-border);
|
||||
background-color: var(--entry-active-background);
|
||||
}
|
||||
|
||||
section.entry.viewed:not(.active) {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.entry-meta {
|
||||
filter: grayscale(100%);
|
||||
font-size: 0.875rem;
|
||||
color: var(--entry-meta-text-color);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
border-top: 1px solid var(--entry-meta-border-color);
|
||||
}
|
||||
|
||||
section.entry.active > .entry-meta {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.entry-meta-tab {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-right: 1px solid var(--entry-meta-border-color);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.entry-meta-tab-feed {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.entry-meta-tab:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.entry-meta-tab-feed-title {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.entry-content {
|
||||
font-size: 1rem;
|
||||
color: var(--entry-content-text-color);
|
||||
padding: 1rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.entry-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: var(--text-color);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
a.entry-link {
|
||||
color: var(--entry-link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.entry-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.entry-link:focus {
|
||||
outline: 2px solid var(--entry-link-color);
|
||||
outline-offset: 2px;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--entry-link-focus-background);
|
||||
}
|
||||
|
||||
details.entry {}
|
||||
|
||||
details.entry h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
details summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
details summary > * {
|
||||
display: inline;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func init() {
|
||||
archiveTemplateName := "entries.gohtml"
|
||||
emailTemplateName := "email.gotxt"
|
||||
|
||||
ArchiveTemplate, err = htmlTemplate.New(archiveTemplateName).Funcs(htmlTemplate.FuncMap{
|
||||
ArchiveTemplate, err = htmlTemplate.New(archiveTemplateName).Funcs(FuncMap()).Funcs(htmlTemplate.FuncMap{
|
||||
"htmlEscape": func(s string) htmlTemplate.HTML {
|
||||
return htmlTemplate.HTML(s)
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user