@@ -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