1/** 2 * PressHarbor â Main JS 3 * Sticky nav + FAQ accordion + dock URL animation 4 */ 5 6// Sticky nav background on scroll 7( function () { 8 var nav = document.getElementById( 'nav' ); 9 if ( ! nav ) return; 10 window.addEventListener( 'scroll', function () { 11 nav.classList.toggle( 'scrolled', window.scrollY > 60 ); 12 } ); 13} )(); 14 15// Mobile nav toggle 16( function () { 17 var toggle = document.querySelector( '.nav-toggle' ); 18 if ( ! toggle ) return; 19 toggle.addEventListener( 'click', function () { 20 var links = document.querySelector( '.nav-links' ); 21 var open = links.classList.toggle( 'open' ); 22 toggle.setAttribute( 'aria-expanded', open ? 'true' : 'false' ); 23 } ); 24} )(); 25 26// Capacity calculator (hero diagram) 27( function () { 28 var calc = document.getElementById( 'hd-calculator' ); 29 if ( ! calc ) return; 30 31 // Bundled plans â workers and storage move together 32 var plans = typeof pressharborPlanData !== 'undefined' ? pressharborPlanData.plans : []; 33 if (!plans.length) return; 34 35 var pos = 0; // default: Core 36 37 var burstCheck = document.getElementById( 'hd-burst-check' ); 38 var ctaEl = document.getElementById( 'hd-price-cta' ); 39 var contactUrl = 'https://manage.pressharbor.com/submitticket.php?step=2&deptid=1'; 40 41 function fmt( n ) { return n.toFixed( 2 ) + '%'; } 42 43 function update() { 44 var plan = plans[ pos ]; 45 var burst = burstCheck && burstCheck.checked; 46 47 // Workers track 48 document.getElementById( 'hd-workers-display' ).textContent = plan.workers; 49 document.getElementById( 'hd-workers-fill' ).style.setProperty( '--fill', fmt( plan.pos ) ); 50 calc.querySelectorAll( '[data-track="workers"]' ).forEach( function ( dot, i ) { 51 dot.classList.toggle( 'hd-dot--active', i === pos ); 52 } ); 53 document.getElementById( 'hd-workers-labels' ).querySelectorAll( 'span' ).forEach( function ( s, i ) { 54 s.classList.toggle( 'active', i === pos ); 55 } ); 56 57 // Storage track â mirrors workers 58 document.getElementById( 'hd-storage-display' ).textContent = plan.storage; 59 document.getElementById( 'hd-storage-fill' ).style.setProperty( '--fill', fmt( plan.pos ) ); 60 calc.querySelectorAll( '[data-track="storage"]' ).forEach( function ( dot, i ) { 61 dot.classList.toggle( 'hd-dot--active', i === pos ); 62 } ); 63 document.getElementById( 'hd-storage-labels' ).querySelectorAll( 'span' ).forEach( function ( s, i ) { 64 s.classList.toggle( 'active', i === pos ); 65 } ); 66 67 // Price display 68 document.getElementById( 'hd-plan-name' ).textContent = plan.name + ( burst ? ' + Burst' : '' ); 69 70 var footnote = document.getElementById( 'hd-price-footnote' ); 71 72 if ( burst ) { 73 document.getElementById( 'hd-price-amount' ).textContent = 'Custom'; 74 document.getElementById( 'hd-price-period' ).textContent = ''; 75 if ( ctaEl ) { ctaEl.textContent = 'Let\u2019s Talk'; ctaEl.href = contactUrl; } 76 if ( footnote ) footnote.style.display = 'none'; 77 } else { 78 document.getElementById( 'hd-price-amount' ).textContent = '$' + plan.price; 79 document.getElementById( 'hd-price-period' ).textContent = '/mo'; 80 if ( ctaEl ) { ctaEl.textContent = plan.ctaLabel; ctaEl.href = plan.ctaUrl; } 81 if ( footnote ) footnote.style.display = ''; 82 } 83 } 84 85 calc.addEventListener( 'click', function ( e ) { 86 var dot = e.target.closest( '[data-track]' ); 87 if ( ! dot ) return; 88 pos = parseInt( dot.dataset.step, 10 ); 89 update(); 90 } ); 91 92 if ( burstCheck ) { 93 burstCheck.addEventListener( 'change', update ); 94 } 95} )(); 96 97// Billing toggle (annual / monthly) 98( function () { 99 var toggle = document.getElementById( 'billing-toggle' ); 100 if ( ! toggle ) return; 101 toggle.addEventListener( 'click', function ( e ) { 102 var btn = e.target.closest( '.billing-btn' ); 103 if ( ! btn ) return; 104 var period = btn.dataset.period; 105 toggle.querySelectorAll( '.billing-btn' ).forEach( function ( b ) { 106 b.setAttribute( 'aria-pressed', b === btn ? 'true' : 'false' ); 107 } ); 108 document.querySelectorAll( '.plan-amount[data-annual]' ).forEach( function ( el ) { 109 el.textContent = el.dataset[ period ]; 110 } ); 111 } ); 112} )(); 113 114// FAQ accordion 115document.querySelectorAll( '.faq-question' ).forEach( function ( btn ) { 116 btn.addEventListener( 'click', function () { 117 var item = btn.parentElement; 118 var isOpen = item.classList.contains( 'open' ); 119 document.querySelectorAll( '.faq-item' ).forEach( function ( i ) { 120 i.classList.remove( 'open' ); 121 } ); 122 if ( ! isOpen ) item.classList.add( 'open' ); 123 } ); 124} ); 125 126// Dock URL animation â cycles random IDs then settles 127( function () { 128 var chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; 129 var dockId = document.getElementById( 'dock-id' ); 130 var spinner = document.querySelector( '.dock-spinner' ); 131 if ( ! dockId ) return; 132 var started = false;
133 134 function randomId( len ) { 135 var s = ''; 136 for ( var i = 0; i < len; i++ ) { 137 s += chars[ Math.floor( Math.random() * chars.length ) ]; 138 } 139 return s; 140 } 141 142 function runAnimation() { 143 if ( started ) return; 144 started = true; 145 dockId.classList.add( 'cycling' ); 146 var cycles = 0; 147 var total = 18; 148 var interval = setInterval( function () { 149 dockId.textContent = randomId( 5 ); 150 cycles++; 151 if ( cycles >= total ) { 152 clearInterval( interval ); 153 dockId.textContent = randomId( 5 ); 154 dockId.classList.remove( 'cycling' ); 155 if ( spinner ) spinner.classList.add( 'done' ); 156 } 157 }, 70 ); 158 } 159 160 var evalSection = document.getElementById( 'dock' ) || document.getElementById( 'evaluation' ); 161 if ( evalSection && 'IntersectionObserver' in window ) { 162 var obs = new IntersectionObserver( function ( entries ) { 163 if ( entries[0].isIntersecting ) { 164 setTimeout( runAnimation, 400 ); 165 obs.disconnect(); 166 } 167 }, { threshold: 0.3 } ); 168 obs.observe( evalSection ); 169 } else { 170 setTimeout( runAnimation, 2000 ); 171 } 172} )(); 173
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.