118118}
119119body .no-scroll {overflow : hidden}
120120a {color : inherit;text-decoration : none}
121+ : focus-visible {outline : 2px solid var (--blue );outline-offset : 3px ;border-radius : 4px }
122+ ::selection {background : color-mix (in srgb, var (--blue ) 22% , transparent);color : var (--ink )}
121123img {max-width : 100% ;height : auto;display : block}
122124button , a , summary {-webkit-tap-highlight-color : transparent}
123125button {font : inherit;cursor : pointer}
195197.button-ghost : hover {background : var (--surface-2 )}
196198
197199/* Hero */
198- .hero {position : relative;overflow : hidden;padding : 70px 0 70px }
200+ .hero {position : relative;overflow : hidden;padding : 70px 0 70px ; min-height : calc ( 100 svh - 74 px ); display : flex; align-items : center }
199201.hero : before {
200202 content : "" ;position : absolute;inset : 0 ;z-index : 0 ;pointer-events : none;
201203 background-image :
249251}
250252[data-theme = "dark" ] .device {background : # 05070a ;box-shadow : var (--shadow )}
251253.device-notch {position : absolute;top : 13px ;left : 50% ;transform : translateX (-50% );width : 120px ;height : 24px ;background : # 0c0f14 ;border-bottom-left-radius : 14px ;border-bottom-right-radius : 14px ;z-index : 3 }
252- .device-screen {width : 100% ;border-radius : 30px ;display : block;background : # 0c0f14 }
254+ .device-screen {width : 100% ;aspect-ratio : 9 / 19 ; object-fit : cover; border-radius : 30px ;display : block;background : # 0c0f14 }
253255.bubble {
254256 position : absolute;display : flex;align-items : center;gap : 9px ;
255257 background : var (--card );border : 1px solid var (--line );
348350.shot .portrait .media-frame img {height : 100% ;object-fit : contain}
349351.shot figcaption {font-size : 13px ;color : var (--muted );margin-top : 10px ;font-weight : 600 }
350352
353+ /* Scroll progress */
354+ .progress {position : fixed;top : 0 ;left : 0 ;height : 3px ;width : 100% ;transform : scaleX (0 );transform-origin : 0 50% ;background : var (--blue );z-index : 60 }
355+
351356/* Config */
352357.config-layout {display : grid;grid-template-columns : .72fr 1.28fr ;gap : 50px ;align-items : start}
353358.config-copy {position : sticky;top : 100px }
579584</ script >
580585</ head >
581586< body >
587+ < div class ="progress " id ="progress " aria-hidden ="true "> </ div >
582588<!-- SVG sprite -->
583589< svg width ="0 " height ="0 " style ="position:absolute " aria-hidden ="true " focusable ="false ">
584590 < symbol id ="i-reply " viewBox ="0 0 24 24 "> < path d ="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z "/> < path d ="M8 10h8M8 13h5 "/> </ symbol >
658664 < div class ="hero-device ">
659665 < div class ="device ">
660666 < div class ="device-notch "> </ div >
661- < img class ="device-screen " src ="assets/showcase .jpg " alt ="InstaReply Bot product showcase on an Android phone " width ="660 " height ="1400 " loading =" eager ">
667+ < img class ="device-screen " src ="assets/dashboard .jpg " alt ="InstaReply Bot dashboard running on an Android phone " width ="429 " height ="900 " fetchpriority =" high ">
662668 </ div >
663669 < div class ="bubble bubble--in "> < svg class ="icon "> < use href ="#i-bell "/> </ svg > New DM detected</ div >
664670 < div class ="bubble bubble--out "> < span class ="dot "> < i > </ i > < i > </ i > < i > </ i > </ span > AI drafting reply</ div >
668674
669675 < section class ="stats " aria-label ="Product summary ">
670676 < div class ="container stats-grid ">
671- < div class ="stat reveal "> < div class ="stat-value "> 0 < span class ="accent "> . </ span > cost </ div > < div class ="stat-label "> Free, open-source APK</ div > </ div >
677+ < div class ="stat reveal "> < div class ="stat-value "> < span class ="accent "> $ </ span > 0 </ div > < div class ="stat-label "> Free, open-source APK</ div > </ div >
672678 < div class ="stat reveal " style ="--d:.08s "> < div class ="stat-value "> 4< span class ="accent "> +</ span > </ div > < div class ="stat-label "> AI providers supported</ div > </ div >
673- < div class ="stat reveal " style ="--d:.16s "> < div class ="stat-value "> 100 < span class =" accent " > % </ span > </ div > < div class ="stat-label "> No Instagram login </ div > </ div >
679+ < div class ="stat reveal " style ="--d:.16s "> < div class ="stat-value "> 0 </ div > < div class ="stat-label "> Passwords required </ div > </ div >
674680 < div class ="stat reveal " style ="--d:.24s "> < div class ="stat-value "> 24< span class ="accent "> /</ span > 7</ div > < div class ="stat-label "> Background automation</ div > </ div >
675681 </ div >
676682 </ section >
@@ -759,7 +765,7 @@ <h2>One clear chain from message to response.</h2>
759765 < li class ="step "> < div class ="step-node "> 05</ div > < div class ="step-body "> < strong > Reply sent</ strong > < span > The available Android reply mechanism sends the text.</ span > </ div > </ li >
760766 < li class ="step "> < div class ="step-node "> 06</ div > < div class ="step-body "> < strong > Logged</ strong > < span > The activity becomes available in the app's history.</ span > </ div > </ li >
761767 </ ol >
762- < div class ="workflow-image reveal " style ="--d:.1s "> < img src ="assets/workflow.jpg " alt ="InstaReply Bot seven-stage workflow infographic " width ="1200 " height ="800 " loading ="lazy "> </ div >
768+ < div class ="workflow-image reveal " style ="--d:.1s "> < img src ="assets/workflow.jpg " alt ="InstaReply Bot seven-stage workflow infographic " width ="1200 " height ="800 " loading ="lazy " decoding =" async " > </ div >
763769 </ div >
764770 </ div >
765771 </ section >
@@ -772,10 +778,10 @@ <h2>See the actual app before you install it.</h2>
772778 < p class ="section-lead "> A Play Store-style screenshot rail using the supplied product screens. Swipe horizontally on smaller devices.</ p >
773779 </ div >
774780 < div class ="screenshot-rail reveal " style ="--d:.06s ">
775- < figure class ="shot portrait "> < div class ="media-frame "> < img src ="assets/dashboard.jpg " alt ="InstaReply Bot dashboard screen " width ="429 " height ="900 " loading ="lazy "> </ div > < figcaption > Dashboard and activity</ figcaption > </ figure >
776- < figure class ="shot portrait "> < div class ="media-frame "> < img src ="assets/rule-engine.jpg " alt ="InstaReply Bot Smart Rule Engine screen " width ="416 " height ="900 " loading ="lazy "> </ div > < figcaption > Smart Rule Engine</ figcaption > </ figure >
777- < figure class ="shot portrait "> < div class ="media-frame "> < img src ="assets/ai-settings.jpg " alt ="InstaReply Bot AI settings screen " width ="416 " height ="900 " loading ="lazy "> </ div > < figcaption > AI settings and providers</ figcaption > </ figure >
778- < figure class ="shot portrait "> < div class ="media-frame "> < img src ="assets/poster.jpg " alt ="InstaReply Bot promotional poster " width ="506 " height ="900 " loading ="lazy "> </ div > < figcaption > Product overview</ figcaption > </ figure >
781+ < figure class ="shot portrait "> < div class ="media-frame "> < img src ="assets/dashboard.jpg " alt ="InstaReply Bot dashboard screen " width ="429 " height ="900 " loading ="lazy " decoding =" async " > </ div > < figcaption > Dashboard and activity</ figcaption > </ figure >
782+ < figure class ="shot portrait "> < div class ="media-frame "> < img src ="assets/rule-engine.jpg " alt ="InstaReply Bot Smart Rule Engine screen " width ="416 " height ="900 " loading ="lazy " decoding =" async " > </ div > < figcaption > Smart Rule Engine</ figcaption > </ figure >
783+ < figure class ="shot portrait "> < div class ="media-frame "> < img src ="assets/ai-settings.jpg " alt ="InstaReply Bot AI settings screen " width ="416 " height ="900 " loading ="lazy " decoding =" async " > </ div > < figcaption > AI settings and providers</ figcaption > </ figure >
784+ < figure class ="shot portrait "> < div class ="media-frame "> < img src ="assets/poster.jpg " alt ="InstaReply Bot promotional poster " width ="506 " height ="900 " loading ="lazy " decoding =" async " > </ div > < figcaption > Product overview</ figcaption > </ figure >
779785 </ div >
780786 </ div >
781787 </ section >
@@ -798,8 +804,8 @@ <h2>Configure the behavior directly in the Android UI.</h2>
798804 </ ul >
799805 </ div >
800806 < div class ="config-shots reveal " style ="--d:.1s ">
801- < figure > < div class ="media-frame "> < img src ="assets/ai-settings.jpg " alt ="Actual InstaReply Bot AI settings screen " width ="416 " height ="900 " loading ="lazy "> </ div > </ figure >
802- < figure > < div class ="media-frame "> < img src ="assets/rule-engine.jpg " alt ="Actual InstaReply Bot rule editor screen " width ="416 " height ="900 " loading ="lazy "> </ div > </ figure >
807+ < figure > < div class ="media-frame "> < img src ="assets/ai-settings.jpg " alt ="Actual InstaReply Bot AI settings screen " width ="416 " height ="900 " loading ="lazy " decoding =" async " > </ div > </ figure >
808+ < figure > < div class ="media-frame "> < img src ="assets/rule-engine.jpg " alt ="Actual InstaReply Bot rule editor screen " width ="416 " height ="900 " loading ="lazy " decoding =" async " > </ div > </ figure >
803809 </ div >
804810 </ div >
805811 </ div >
@@ -936,6 +942,23 @@ <h4>Project</h4>
936942
937943 // Reveal on scroll
938944 const reduce = window . matchMedia ( '(prefers-reduced-motion: reduce)' ) . matches ;
945+
946+ // Scroll progress
947+ const progress = document . getElementById ( 'progress' ) ;
948+ let ticking = false ;
949+ function updateProgress ( ) {
950+ const h = document . documentElement ;
951+ const max = h . scrollHeight - h . clientHeight ;
952+ const p = max > 0 ? ( h . scrollTop || document . body . scrollTop ) / max : 0 ;
953+ progress . style . transform = 'scaleX(' + p . toFixed ( 4 ) + ')' ;
954+ ticking = false ;
955+ }
956+ if ( ! reduce ) {
957+ window . addEventListener ( 'scroll' , ( ) => {
958+ if ( ! ticking ) { ticking = true ; window . requestAnimationFrame ( updateProgress ) ; }
959+ } , { passive :true } ) ;
960+ updateProgress ( ) ;
961+ }
939962 const revealEls = document . querySelectorAll ( '.reveal' ) ;
940963 if ( reduce || ! ( 'IntersectionObserver' in window ) ) {
941964 revealEls . forEach ( el => el . classList . add ( 'in' ) ) ;
0 commit comments