Atom feed for our EEG site

more

Changed files
+494 -19
+494 -19
index.html
···
--header-height: 50px;
--sidebar-width: 80px;
--tab-height: 40px;
}
* {
···
color: var(--text-color);
line-height: 1.5;
overflow-x: hidden;
}
header {
···
.feed-item {
border-left: 3px solid transparent;
-
transition: all 0.3s ease;
}
.feed-item:hover {
border-left-color: var(--accent-color);
-
background-color: rgba(77, 250, 123, 0.03);
}
.references-container {
···
display: inline;
margin-left: 8px;
opacity: 1;
}
.preview-links,
···
border-radius: 4px;
margin-bottom: 8px;
overflow: hidden;
-
transition: background-color 0.2s ease;
display: flex;
align-items: center;
padding: 10px 15px;
}
.link-item:hover {
-
background-color: #1a3028;
border-left-color: var(--accent-color);
}
.link-item-date {
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
···
</style>
</head>
<body>
<header>
<div class="header-container">
<div class="header-left">
···
<script>
document.addEventListener('DOMContentLoaded', async () => {
// Add hover event listeners after DOM content is loaded
function setupHoverEffects() {
// Keep track of the currently active item
···
document.querySelectorAll('.feed-item').forEach(item => {
item.addEventListener('mouseenter', () => {
-
// Close all sections in previously hovered item
-
if (currentHoveredItem && currentHoveredItem !== item) {
-
// Remove this section - we no longer show the full content
-
-
// No need to close preview content now since it's controlled by CSS hover
-
-
// References are now controlled by CSS hover
-
}
-
// Set this as current hovered item
currentHoveredItem = item;
-
-
// Remove this section - we no longer show the full content
-
// Preview content is shown automatically by CSS on hover
});
});
}
···
entriesHTML += `
<article id="${entry.articleId}" class="feed-item" ${dateAttr}>
<div class="feed-item-row">
-
<div class="feed-item-date">${monthNames[month]} ${getDayWithOrdinal(date)}, ${year}</div>
<div class="feed-item-author">${entry.author}</div>
<div class="feed-item-content-wrapper">
<div class="feed-item-title"><a href="${entry.link}" target="_blank">${entry.title}</a></div><div class="feed-item-preview">${entry.contentHtml}</div>
···
}
}
-
// Set up hover effects
setupHoverEffects();
// Process all external links from entries
const linksContainer = document.getElementById('link-items');
const allExternalLinks = [];
···
// Create link item HTML
linksHTML += `
<div class="link-item" data-year="${date.getFullYear()}" data-month="${date.getMonth()}">
-
<div class="link-item-date">${monthNames[date.getMonth()]} ${getLinkDayWithOrdinal(date)}, ${date.getFullYear()}</div>
<div class="link-item-source" title="From: ${link.sourceTitle}">
<a href="${link.sourceLink}" target="_blank" style="color: inherit; text-decoration: none;">
${link.source}
···
--header-height: 50px;
--sidebar-width: 80px;
--tab-height: 40px;
+
--ripple-color: rgba(77, 250, 123, 0.04);
+
--ripple-color-strong: rgba(77, 250, 123, 0.06);
+
--matrix-color: rgba(77, 250, 123, 0.2);
+
--matrix-glow: rgba(77, 250, 123, 0.1);
+
--hover-glow: rgba(77, 250, 123, 0.15);
}
* {
···
color: var(--text-color);
line-height: 1.5;
overflow-x: hidden;
+
position: relative;
+
}
+
+
body::before {
+
content: '';
+
position: fixed;
+
top: 0;
+
left: 0;
+
width: 100%;
+
height: 100%;
+
background: linear-gradient(rgba(10, 23, 15, 0.82), rgba(10, 23, 15, 0.92));
+
z-index: -1;
+
pointer-events: none;
+
}
+
+
#matrix-background {
+
position: fixed;
+
top: 0;
+
left: 0;
+
width: 100%;
+
height: 100%;
+
z-index: -2;
+
opacity: 0.6;
+
pointer-events: none;
}
header {
···
.feed-item {
border-left: 3px solid transparent;
+
transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+
position: relative;
+
overflow: hidden;
+
z-index: 1;
}
.feed-item:hover {
border-left-color: var(--accent-color);
+
background-color: rgba(21, 39, 32, 0.95);
+
}
+
+
.feed-item::before {
+
content: '';
+
position: absolute;
+
top: 0;
+
left: 0;
+
right: 0;
+
bottom: 0;
+
background: radial-gradient(circle at var(--mouse-x, 0%) var(--mouse-y, 0%),
+
rgba(77, 250, 123, 0.06) 0%,
+
rgba(77, 250, 123, 0.04) 30%,
+
rgba(77, 250, 123, 0) 70%);
+
opacity: 0;
+
z-index: 0;
+
transform: scale(0);
+
transition: opacity 0.5s ease, transform 0.7s cubic-bezier(0.19, 1, 0.22, 1);
+
pointer-events: none;
}
.references-container {
···
display: inline;
margin-left: 8px;
opacity: 1;
+
}
+
+
.feed-item:hover::before {
+
opacity: 0.6;
+
transform: scale(1.5);
}
.preview-links,
···
border-radius: 4px;
margin-bottom: 8px;
overflow: hidden;
+
transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
display: flex;
align-items: center;
padding: 10px 15px;
+
position: relative;
}
.link-item:hover {
+
background-color: rgba(21, 39, 32, 0.95);
border-left-color: var(--accent-color);
}
+
.link-item::before {
+
content: '';
+
position: absolute;
+
top: 0;
+
left: 0;
+
right: 0;
+
bottom: 0;
+
background: radial-gradient(circle at var(--mouse-x, 0%) var(--mouse-y, 0%),
+
rgba(77, 250, 123, 0.06) 0%,
+
rgba(77, 250, 123, 0.04) 30%,
+
rgba(77, 250, 123, 0) 70%);
+
opacity: 0;
+
z-index: 0;
+
transform: scale(0);
+
transition: opacity 0.5s ease, transform 0.7s cubic-bezier(0.19, 1, 0.22, 1);
+
pointer-events: none;
+
}
+
+
.link-item:hover::before {
+
opacity: 0.6;
+
transform: scale(1.5);
+
}
+
.link-item-date {
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
···
</style>
</head>
<body>
+
<canvas id="matrix-background"></canvas>
<header>
<div class="header-container">
<div class="header-left">
···
<script>
document.addEventListener('DOMContentLoaded', async () => {
+
// Matrix background effect
+
const canvas = document.getElementById('matrix-background');
+
const ctx = canvas.getContext('2d');
+
+
// Set canvas size to match window
+
function resizeCanvas() {
+
canvas.width = window.innerWidth;
+
canvas.height = window.innerHeight;
+
}
+
resizeCanvas();
+
window.addEventListener('resize', resizeCanvas);
+
+
// Vine/plant-related characters and elements
+
const vineChars = '┃┃│┋┇┊┆╽╿┴┬╵╷└┕┖┗┘┙┚┛╘╙╚╛╯╰╱╲⌠⌡╎▏▕⏐▌▐░▒▓◥◤◢◣⎸⎹│';
+
const leafChars = '☘❀✿❁❃❇❈❉❊❋✣✤✥✦✧✩✪✫✬✭✮✾✿❀❁❂❃❄⚘♠♣⚜⚘☘';
+
const branchChars = '┌┐┘└├┬┴┤┼─┄┈┉┊┋╱╲╳☂⚢⌒~∞≈≋⋆✧✦✫';
+
const fontSize = 14;
+
const columns = Math.floor(canvas.width / fontSize * 0.7); // Fewer columns for sparser vines
+
+
// Drop positions for each column
+
const drops = [];
+
+
// Initialize drops at random positions
+
for (let i = 0; i < columns; i++) {
+
// Random starting position
+
drops[i] = Math.random() * -canvas.height;
+
}
+
+
// Set up column types - some will be vines, some will have leaves
+
const columnTypes = [];
+
for (let i = 0; i < columns; i++) {
+
// 70% of columns are vines, 25% are leaves, 5% are cross-connections
+
const rand = Math.random();
+
if (rand < 0.7) {
+
columnTypes[i] = 'vine';
+
} else if (rand < 0.95) {
+
columnTypes[i] = 'leaf';
+
} else {
+
columnTypes[i] = 'branch';
+
}
+
}
+
+
// Store connections between vines
+
const connections = [];
+
+
// Helper function to find nearby columns
+
function findNearbyColumns(columnIndex, maxDistance = 3) {
+
const nearby = [];
+
for (let i = 0; i < columns; i++) {
+
if (i !== columnIndex && Math.abs(i - columnIndex) <= maxDistance) {
+
nearby.push(i);
+
}
+
}
+
return nearby;
+
}
+
+
// Last time random chars were changed
+
const lastCharChangeTime = [];
+
// The current characters displayed
+
const currentChars = [];
+
// Width/thickness of vines
+
const vineThickness = [];
+
+
for (let i = 0; i < columns; i++) {
+
lastCharChangeTime[i] = [];
+
currentChars[i] = [];
+
+
// Random vine thickness between 1-3
+
vineThickness[i] = Math.floor(Math.random() * 3) + 1;
+
+
for (let j = 0; j < canvas.height / fontSize; j++) {
+
lastCharChangeTime[i][j] = 0;
+
+
if (columnTypes[i] === 'vine') {
+
// Choose vine characters based on position and thickness
+
if (j === 0) {
+
// Top of vine - might be a leaf or flower
+
currentChars[i][j] = Math.random() < 0.6 ?
+
leafChars.charAt(Math.floor(Math.random() * leafChars.length)) :
+
vineChars.charAt(Math.floor(Math.random() * vineChars.length));
+
} else {
+
// Main vine character
+
const vineIndex = Math.min(vineThickness[i] * 3, vineChars.length - 1);
+
currentChars[i][j] = vineChars.charAt(Math.floor(Math.random() * vineIndex));
+
}
+
} else if (columnTypes[i] === 'leaf') {
+
// Leaf character - only at top or occasional spots along the vine
+
if (j === 0 || Math.random() < 0.2) {
+
currentChars[i][j] = leafChars.charAt(Math.floor(Math.random() * leafChars.length));
+
} else {
+
// Connecting vine
+
currentChars[i][j] = vineChars.charAt(Math.floor(Math.random() * 5)); // Thin vine characters
+
}
+
} else if (columnTypes[i] === 'branch') {
+
// This is a branching column - will form connections between vines
+
if (j === 0) {
+
// Top of branch might be a leaf or flower
+
currentChars[i][j] = leafChars.charAt(Math.floor(Math.random() * leafChars.length));
+
} else {
+
// Branch characters - horizontal or diagonal connectors
+
currentChars[i][j] = branchChars.charAt(Math.floor(Math.random() * branchChars.length));
+
}
+
}
+
}
+
}
+
+
// Time when animation started
+
const startTime = Date.now();
+
+
// Track connections between vines
+
const crossConnections = [];
+
+
// Draw the rainforest vine effect
+
function drawVineEffect() {
+
// Semi-transparent background to create fade effect
+
ctx.fillStyle = 'rgba(10, 23, 15, 0.05)';
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+
const now = Date.now();
+
+
// Set font
+
ctx.font = `${fontSize}px 'JetBrains Mono', monospace`;
+
ctx.textAlign = 'center';
+
+
// First, create cross-connections
+
// Create new cross-connections occasionally
+
if (Math.random() < 0.01) {
+
// Find a source vine that's grown enough
+
const sourceIndex = Math.floor(Math.random() * columns);
+
if (drops[sourceIndex] > 100 && columnTypes[sourceIndex] === 'vine') {
+
// Find a nearby column to connect to
+
const nearby = findNearbyColumns(sourceIndex, 3);
+
if (nearby.length > 0) {
+
const targetIndex = nearby[Math.floor(Math.random() * nearby.length)];
+
if (drops[targetIndex] > 80) {
+
// The height should be somewhere between the two vines
+
const sourceHeight = drops[sourceIndex];
+
const targetHeight = drops[targetIndex];
+
const connectionHeight = Math.min(sourceHeight, targetHeight) * 0.8;
+
+
// Create the connection
+
crossConnections.push({
+
source: sourceIndex,
+
target: targetIndex,
+
height: connectionHeight,
+
character: branchChars.charAt(Math.floor(Math.random() * branchChars.length)),
+
created: now
+
});
+
}
+
}
+
}
+
}
+
+
// For each column
+
for (let i = 0; i < columns; i++) {
+
// Calculate current position of this vine
+
const x = i * fontSize * 1.5; // Space vines further apart
+
+
// For each character in this column
+
for (let j = 0; j < Math.ceil(drops[i] / fontSize); j++) {
+
const y = j * fontSize;
+
+
// Skip rendering some characters to create gaps in vines
+
if (Math.random() < 0.05 && j > 3) continue;
+
+
// Calculate age of this character
+
const charAge = now - lastCharChangeTime[i][j];
+
+
// Randomly change some characters over time - slower rate for natural movement
+
if (j === 0 && (Math.random() < 0.005 || charAge > 8000)) {
+
// Top character might change between leaves/flowers
+
if (columnTypes[i] === 'leaf' || Math.random() < 0.6) {
+
currentChars[i][j] = leafChars.charAt(Math.floor(Math.random() * leafChars.length));
+
} else {
+
currentChars[i][j] = vineChars.charAt(Math.floor(Math.random() * vineChars.length));
+
}
+
lastCharChangeTime[i][j] = now;
+
} else if (j > 0 && Math.random() < 0.001) {
+
// Occasionally grow new leaves along the vine
+
if (Math.random() < 0.2) {
+
currentChars[i][j] = leafChars.charAt(Math.floor(Math.random() * leafChars.length));
+
} else {
+
const vineIndex = Math.min(vineThickness[i] * 3, vineChars.length - 1);
+
currentChars[i][j] = vineChars.charAt(Math.floor(Math.random() * vineIndex));
+
}
+
lastCharChangeTime[i][j] = now;
+
}
+
+
// Calculate distance from head of the vine
+
const distanceFromHead = (drops[i] - y);
+
+
// Determine color based on position and type
+
if (j === 0 && (currentChars[i][j] === '❀' || currentChars[i][j] === '✿' ||
+
currentChars[i][j] === '❁' || currentChars[i][j] === '✾')) {
+
// Flowers are more colorful - pinkish
+
ctx.fillStyle = 'rgba(255, 180, 220, 0.9)';
+
ctx.shadowColor = 'rgba(255, 150, 200, 0.6)';
+
ctx.shadowBlur = 5;
+
} else if (currentChars[i][j] === '☘' || leafChars.includes(currentChars[i][j])) {
+
// Leaf characters are brightest with different green
+
ctx.fillStyle = 'rgba(120, 255, 150, 0.9)';
+
ctx.shadowColor = 'rgba(77, 250, 123, 0.5)';
+
ctx.shadowBlur = 3;
+
} else if (distanceFromHead < fontSize) {
+
// Growing tip of vine is brightest
+
ctx.fillStyle = 'rgba(120, 255, 150, 0.9)';
+
ctx.shadowColor = 'rgba(77, 250, 123, 0.5)';
+
ctx.shadowBlur = 5;
+
} else if (distanceFromHead < fontSize * 8) {
+
// Newer part of vine is brighter
+
const opacity = 0.8 - (distanceFromHead / (fontSize * 10));
+
ctx.fillStyle = `rgba(77, 180, 100, ${opacity.toFixed(2)})`;
+
ctx.shadowColor = 'transparent';
+
ctx.shadowBlur = 0;
+
} else {
+
// Older parts of vine are darker
+
const opacity = Math.max(0, 0.4 - (distanceFromHead / (canvas.height * 2)));
+
// Darker green for older vines
+
ctx.fillStyle = `rgba(40, 120, 60, ${opacity.toFixed(2)})`;
+
ctx.shadowColor = 'transparent';
+
ctx.shadowBlur = 0;
+
}
+
+
// Add slight random swaying to vines
+
const swayAmount = Math.sin((now / 2000) + i) * 2; // Gentle swaying effect
+
const adjustedX = x + swayAmount;
+
+
// Draw the character
+
if (y < canvas.height) {
+
// Adjust size for special characters
+
if (leafChars.includes(currentChars[i][j])) {
+
ctx.font = `${fontSize * 1.2}px 'JetBrains Mono', monospace`;
+
ctx.fillText(currentChars[i][j], adjustedX, y);
+
ctx.font = `${fontSize}px 'JetBrains Mono', monospace`; // Reset font
+
} else {
+
ctx.fillText(currentChars[i][j], adjustedX, y);
+
}
+
}
+
}
+
+
// Move the vine down - slower for natural growth
+
drops[i] += fontSize * (0.02 + Math.random() * 0.03);
+
+
// Reset vine when it reaches bottom or randomly (much less frequent)
+
if (drops[i] > canvas.height * 2 || (Math.random() < 0.0005 && drops[i] > canvas.height * 0.6)) {
+
drops[i] = Math.random() * -30;
+
// Maybe change vine type
+
if (Math.random() < 0.3) {
+
columnTypes[i] = Math.random() < 0.7 ? 'vine' : 'leaf';
+
vineThickness[i] = Math.floor(Math.random() * 3) + 1;
+
}
+
}
+
}
+
+
// Draw cross connections between vines
+
crossConnections.forEach((connection, index) => {
+
const sourceX = connection.source * fontSize * 1.5;
+
const targetX = connection.target * fontSize * 1.5;
+
const y = connection.height;
+
const heightIndex = Math.floor(y / fontSize);
+
+
// Calculate a safe display Y - make sure it's within the grown vines
+
const safeY = Math.min(
+
Math.min(drops[connection.source], drops[connection.target]),
+
connection.height
+
);
+
+
// Convert to display coords
+
const displayY = Math.floor(safeY / fontSize) * fontSize;
+
+
// Only draw if connection is within visible area
+
if (displayY < 0 || displayY > canvas.height) return;
+
+
// Connection age effect
+
const age = now - connection.created;
+
const maxAge = 20000; // 20 seconds lifetime for connections
+
+
// Remove old connections
+
if (age > maxAge) {
+
crossConnections.splice(index, 1);
+
return;
+
}
+
+
// Fade in/out effect
+
let opacity = 1.0;
+
if (age < 1000) {
+
// Fade in
+
opacity = age / 1000;
+
} else if (age > maxAge - 2000) {
+
// Fade out
+
opacity = (maxAge - age) / 2000;
+
}
+
+
// Draw connection
+
const connectionWidth = Math.abs(targetX - sourceX);
+
const steps = Math.ceil(connectionWidth / (fontSize * 0.8));
+
+
// Lighter green for branches
+
ctx.fillStyle = `rgba(120, 255, 150, ${opacity.toFixed(2)})`;
+
ctx.shadowColor = 'rgba(77, 250, 123, 0.4)';
+
ctx.shadowBlur = 2;
+
+
// Draw branch character at each step
+
let branchChar;
+
+
if (sourceX < targetX) {
+
// Left to right
+
branchChar = '─';
+
} else {
+
// Right to left
+
branchChar = '─';
+
}
+
+
for (let s = 0; s <= steps; s++) {
+
// Calculate position
+
const progress = s / steps;
+
const stepX = sourceX + (targetX - sourceX) * progress;
+
const wiggle = Math.sin(progress * Math.PI) * 5;
+
+
// Choose appropriate connection character
+
let connChar = branchChar;
+
+
// Special characters for start, middle and end
+
if (s === 0) {
+
connChar = '├';
+
} else if (s === steps) {
+
connChar = '┤';
+
} else if (s === Math.floor(steps/2)) {
+
// Add a leaf or flower in the middle sometimes
+
if (Math.random() < 0.3) {
+
connChar = leafChars.charAt(Math.floor(Math.random() * leafChars.length));
+
} else {
+
connChar = s % 2 === 0 ? '┼' : '┴';
+
}
+
} else {
+
// Occasional decorative elements
+
if (Math.random() < 0.1) {
+
connChar = '·';
+
}
+
}
+
+
ctx.fillText(connChar, stepX, displayY + wiggle);
+
}
+
});
+
+
// Schedule next frame
+
requestAnimationFrame(drawVineEffect);
+
}
+
+
// Start the animation
+
drawVineEffect();
// Add hover event listeners after DOM content is loaded
function setupHoverEffects() {
// Keep track of the currently active item
···
document.querySelectorAll('.feed-item').forEach(item => {
item.addEventListener('mouseenter', () => {
// Set this as current hovered item
currentHoveredItem = item;
+
});
+
+
// Track mouse position for the ripple effect
+
item.addEventListener('mousemove', (e) => {
+
// Get position relative to the element
+
const rect = item.getBoundingClientRect();
+
const x = ((e.clientX - rect.left) / rect.width) * 100;
+
const y = ((e.clientY - rect.top) / rect.height) * 100;
+
// Set custom properties for the radial gradient
+
item.style.setProperty('--mouse-x', `${x}%`);
+
item.style.setProperty('--mouse-y', `${y}%`);
});
});
}
···
entriesHTML += `
<article id="${entry.articleId}" class="feed-item" ${dateAttr}>
<div class="feed-item-row">
+
<div class="feed-item-date">${getDayWithOrdinal(date)} ${shortMonthNames[month]} ${year}</div>
<div class="feed-item-author">${entry.author}</div>
<div class="feed-item-content-wrapper">
<div class="feed-item-title"><a href="${entry.link}" target="_blank">${entry.title}</a></div><div class="feed-item-preview">${entry.contentHtml}</div>
···
}
}
+
// Set up hover effects and ripple animations
setupHoverEffects();
+
// Create a ripple effect that travels across the content area
+
const feedContainer = document.querySelector('.feed-container');
+
feedContainer.addEventListener('mousemove', (e) => {
+
// Ripple between items as mouse moves
+
const items = document.querySelectorAll('.feed-item, .link-item');
+
items.forEach(item => {
+
const rect = item.getBoundingClientRect();
+
const centerX = rect.left + rect.width / 2;
+
const centerY = rect.top + rect.height / 2;
+
+
// Calculate distance from mouse to center of item
+
const dx = e.clientX - centerX;
+
const dy = e.clientY - centerY;
+
const distance = Math.sqrt(dx * dx + dy * dy);
+
+
// Calculate fade based on distance
+
const maxDistance = 400; // max distance for effect
+
const intensity = Math.max(0, 1 - (distance / maxDistance));
+
+
if (intensity > 0.05) {
+
// Extremely subtle glow - minimized for optimal text readability
+
item.style.boxShadow = `0 0 ${intensity * 8}px var(--hover-glow)`;
+
item.style.transform = `scale(${1 + intensity * 0.005})`;
+
item.style.transition = 'box-shadow 0.4s ease-out, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)';
+
} else {
+
item.style.boxShadow = 'none';
+
item.style.transform = 'scale(1)';
+
}
+
});
+
});
+
+
// Add hover tracking for link items too
+
document.querySelectorAll('.link-item').forEach(item => {
+
item.addEventListener('mousemove', (e) => {
+
// Get position relative to the element
+
const rect = item.getBoundingClientRect();
+
const x = ((e.clientX - rect.left) / rect.width) * 100;
+
const y = ((e.clientY - rect.top) / rect.height) * 100;
+
+
// Set custom properties for the radial gradient
+
item.style.setProperty('--mouse-x', `${x}%`);
+
item.style.setProperty('--mouse-y', `${y}%`);
+
});
+
});
+
// Process all external links from entries
const linksContainer = document.getElementById('link-items');
const allExternalLinks = [];
···
// Create link item HTML
linksHTML += `
<div class="link-item" data-year="${date.getFullYear()}" data-month="${date.getMonth()}">
+
<div class="link-item-date">${getLinkDayWithOrdinal(date)} ${shortMonthNames[date.getMonth()]} ${date.getFullYear()}</div>
<div class="link-item-source" title="From: ${link.sourceTitle}">
<a href="${link.sourceLink}" target="_blank" style="color: inherit; text-decoration: none;">
${link.source}