Skip to content

Commit 37e6e8c

Browse files
committed
feat: add new background effect
1 parent d2c01f5 commit 37e6e8c

1 file changed

Lines changed: 328 additions & 0 deletions

File tree

open-dir.py

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,335 @@ def process_dir(top_dir):
394394
// Create stars
395395
createStars();
396396
</script>
397+
398+
<script>
399+
// ============================================================================
400+
// Configuration
401+
// ============================================================================
402+
403+
const GRADIENTS = {
404+
poly1: {
405+
from: '#0e3158',
406+
to: '#091144',
407+
direction: 'to right',
408+
},
409+
poly2: {
410+
from: '#154984',
411+
to: '#060b2d',
412+
direction: 'to left',
413+
},
414+
poly3: {
415+
from: '#002b33',
416+
to: '#007380',
417+
direction: 'to top',
418+
opacity: 0.2,
419+
},
420+
};
421+
422+
const POLYGON_COUNTS = {
423+
poly1: 10,
424+
poly2: 6,
425+
poly3: 3,
426+
};
427+
428+
const DISTRIBUTION_SETTINGS = {
429+
overflow: 0.3,
430+
disturb: 0.3,
431+
disturbChance: 0.3,
432+
};
433+
434+
const ANIMATION = {
435+
blur: 70, // px
436+
transitionDuration: 3.5, // seconds
437+
};
438+
439+
// ============================================================================
440+
// Configuration (can be modified)
441+
// ============================================================================
442+
443+
let config = {
444+
distribution: 'full',
445+
opacity: 0.2,
446+
hue: 0,
447+
};
448+
449+
// ============================================================================
450+
// Distribution Logic
451+
// ============================================================================
452+
453+
function distributionToLimits(distribution) {
454+
const min = -0.2;
455+
const max = 1.2;
456+
let x = [min, max];
457+
let y = [min, max];
458+
459+
const intersection = (a, b) => [
460+
Math.max(a[0], b[0]),
461+
Math.min(a[1], b[1]),
462+
];
463+
464+
const limits = distribution.split('-');
465+
466+
const limitHandlers = {
467+
topmost: () => { y = intersection(y, [-0.5, 0]); },
468+
top: () => { y = intersection(y, [min, 0.6]); },
469+
bottom: () => { y = intersection(y, [0.4, max]); },
470+
left: () => { x = intersection(x, [min, 0.6]); },
471+
right: () => { x = intersection(x, [0.4, max]); },
472+
xcenter: () => { x = intersection(x, [0.25, 0.75]); },
473+
ycenter: () => { y = intersection(y, [0.25, 0.75]); },
474+
center: () => {
475+
x = intersection(x, [0.25, 0.75]);
476+
y = intersection(y, [0.25, 0.75]);
477+
},
478+
full: () => {
479+
x = intersection(x, [0, 1]);
480+
y = intersection(y, [0, 1]);
481+
},
482+
};
483+
484+
for (const limit of limits) {
485+
if (limitHandlers[limit]) {
486+
limitHandlers[limit]();
487+
}
488+
}
489+
490+
return { x, y };
491+
}
492+
493+
// ============================================================================
494+
// Polygon Generation
495+
// ============================================================================
496+
497+
function distance2([x1, y1], [x2, y2]) {
498+
return (x2 - x1) ** 2 + (y2 - y1) ** 2;
499+
}
500+
501+
class Polygon {
502+
constructor(count, gradientKey) {
503+
this.count = count;
504+
this.gradientKey = gradientKey;
505+
this.points = [];
506+
this.element = null;
507+
this.generatePoints();
508+
}
509+
510+
generatePoints() {
511+
const limits = distributionToLimits(config.distribution);
512+
const { overflow, disturb, disturbChance } = DISTRIBUTION_SETTINGS;
513+
514+
const randomBetween = ([a, b]) => Math.random() * (b - a) + a;
515+
516+
const applyOverflow = (random, overflow) => {
517+
random = random * (1 + overflow * 2) - overflow;
518+
return Math.random() < disturbChance ? random + (Math.random() - 0.5) * disturb : random;
519+
};
520+
521+
const newPoints = Array.from({ length: this.count }, () => [
522+
applyOverflow(randomBetween(limits.x), overflow),
523+
applyOverflow(randomBetween(limits.y), overflow),
524+
]);
525+
526+
if (this.points.length === 0) {
527+
this.points = newPoints;
528+
} else {
529+
const availableNewPoints = new Set(newPoints);
530+
this.points = this.points.map((oldPoint) => {
531+
let minDistance = Infinity;
532+
let closest = null;
533+
534+
for (const newPoint of availableNewPoints) {
535+
const d = distance2(oldPoint, newPoint);
536+
if (d < minDistance) {
537+
minDistance = d;
538+
closest = newPoint;
539+
}
540+
}
541+
542+
if (closest) availableNewPoints.delete(closest);
543+
return closest || oldPoint;
544+
});
545+
}
546+
}
547+
548+
getPolygonString() {
549+
return this.points
550+
.map(([x, y]) => `${x * 100}% ${y * 100}%`)
551+
.join(', ');
552+
}
553+
554+
createElement() {
555+
const div = document.createElement('div');
556+
div.className = 'glow-clip';
557+
div.id = 'glow-' + this.gradientKey;
558+
this.element = div;
559+
return div;
560+
}
561+
562+
updateStyle() {
563+
if (!this.element) return;
564+
565+
const gradient = GRADIENTS[this.gradientKey];
566+
const opacity = gradient.opacity !== undefined ? gradient.opacity : config.opacity;
397567
568+
this.element.style.clipPath = `polygon(${this.getPolygonString()})`;
569+
this.element.style.opacity = opacity;
570+
this.element.style.background = `linear-gradient(${gradient.direction}, ${gradient.from}, ${gradient.to})`;
571+
}
572+
573+
regenerate() {
574+
this.generatePoints();
575+
}
576+
}
577+
578+
// ============================================================================
579+
// Helper Functions
580+
// ============================================================================
581+
582+
function getFullPageHeight() {
583+
const body = document.body;
584+
const html = document.documentElement;
585+
return Math.max(
586+
body.scrollHeight, body.offsetHeight,
587+
html.clientHeight, html.scrollHeight, html.offsetHeight
588+
);
589+
}
590+
591+
// ============================================================================
592+
// Initialize and Create DOM Elements
593+
// ============================================================================
594+
595+
var createGlowEffect = function() {
596+
// Create main container with absolute positioning
597+
const container = document.createElement('div');
598+
container.id = 'glow-container';
599+
600+
function updateContainerHeight() {
601+
const pageHeight = getFullPageHeight();
602+
container.style.cssText = `
603+
position: absolute;
604+
top: 0;
605+
left: 0;
606+
width: 100%;
607+
height: ${pageHeight}px;
608+
z-index: -1;
609+
overflow: hidden;
610+
pointer-events: none;
611+
`;
612+
}
613+
614+
// Set initial height
615+
updateContainerHeight();
616+
617+
// Create background wrapper
618+
const bgWrapper = document.createElement('div');
619+
bgWrapper.id = 'glow-bg-effect';
620+
bgWrapper.className = 'glow-bg';
621+
bgWrapper.setAttribute('aria-hidden', 'true');
622+
bgWrapper.style.cssText = `
623+
width: 100%;
624+
height: 100%;
625+
position: relative;
626+
`;
627+
628+
// Add style for glow-clip elements
629+
const style = document.createElement('style');
630+
style.textContent = `
631+
.glow-clip {
632+
position: absolute;
633+
width: 100%;
634+
height: 100%;
635+
transition: clip-path ${ANIMATION.transitionDuration}s ease-in-out,
636+
opacity ${ANIMATION.transitionDuration}s ease-in-out;
637+
}
638+
`;
639+
document.head.appendChild(style);
640+
641+
// Create polygons
642+
const polygons = {
643+
poly1: new Polygon(POLYGON_COUNTS.poly1, 'poly1'),
644+
poly2: new Polygon(POLYGON_COUNTS.poly2, 'poly2'),
645+
poly3: new Polygon(POLYGON_COUNTS.poly3, 'poly3'),
646+
};
647+
648+
// Create and append polygon elements
649+
Object.values(polygons).forEach(poly => {
650+
bgWrapper.appendChild(poly.createElement());
651+
});
652+
653+
// Append to container
654+
container.appendChild(bgWrapper);
655+
656+
// Append to body
657+
document.body.appendChild(container);
658+
659+
// Update all styles
660+
function updateAllPolygons() {
661+
Object.values(polygons).forEach(poly => poly.updateStyle());
662+
bgWrapper.style.filter = `blur(${ANIMATION.blur}px) hue-rotate(${config.hue}deg)`;
663+
}
664+
665+
function regeneratePolygons() {
666+
Object.values(polygons).forEach(poly => poly.regenerate());
667+
updateAllPolygons();
668+
}
669+
670+
// Initial render
671+
updateAllPolygons();
672+
673+
// Update height on window resize and content changes
674+
window.addEventListener('resize', updateContainerHeight);
675+
676+
// Use ResizeObserver to detect content height changes
677+
if (typeof ResizeObserver !== 'undefined') {
678+
const resizeObserver = new ResizeObserver(() => {
679+
updateContainerHeight();
680+
});
681+
resizeObserver.observe(document.body);
682+
}
683+
684+
// Fallback: periodically check for height changes
685+
setInterval(updateContainerHeight, 1000);
686+
687+
// ============================================================================
688+
// Public API
689+
// ============================================================================
690+
691+
window.glowEffect = {
692+
setDistribution(distribution) {
693+
config.distribution = distribution;
694+
regeneratePolygons();
695+
},
696+
setOpacity(opacity) {
697+
config.opacity = opacity;
698+
updateAllPolygons();
699+
},
700+
setHue(hue) {
701+
config.hue = hue;
702+
updateAllPolygons();
703+
},
704+
regenerate() {
705+
regeneratePolygons();
706+
},
707+
updateHeight() {
708+
updateContainerHeight();
709+
},
710+
startAutoRegenerate(interval = 5000) {
711+
this.autoRegenerateInterval = setInterval(() => {
712+
regeneratePolygons();
713+
}, interval);
714+
},
715+
stopAutoRegenerate() {
716+
if (this.autoRegenerateInterval) {
717+
clearInterval(this.autoRegenerateInterval);
718+
}
719+
}
720+
};
721+
};
722+
723+
createGlowEffect();
724+
</script>
725+
398726
<header>
399727
<h1>"""
400728
f'{path_format}'

0 commit comments

Comments
 (0)