<!DOCTYPE html><html lang="en" data-bs-theme="white"><head> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Live SEO Analyzer</title> <style> .fade-out { opacity: 0; transition: opacity 0.5s ease-out; } input[type="checkbox"]:checked + label { color: #FFEB3B; /* Or any color you want */ } .spin { animation: spin 1s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .display-jsons { /* position: sticky; */ z-index: 4; box-shadow: 0px 15px 10px -8px #000; background: #9b0b00; font-size: 12px; color: #fff; } .display-og-meta { /* position: sticky; */ z-index: 4; box-shadow: 0px 15px 10px -8px #000; background: #9b0b00; font-size: 12px; color: #fff; } .json-toolbar { display: flex; height: auto; background: #ccc; padding: 5px 15px 5px 15px; min-height: auto; max-height: 28.63px; justify-content: space-between; flex-direction: row; flex-wrap: wrap; align-items: stretch; } ul.json-sections { position: relative; white-space: pre; list-style: none; padding: 0px 15px 0px 15px; z-index: -1; } .col-auto.filter-settings { position: fixed; top: 103px; background: #514d35; border-bottom: 1px solid red; padding: 5px 0px 5px 0px !important; left: 2px; right: 0px; min-width: auto; max-width: 250px; width: 100%; } i.bi.bi-caret-down.passed { color: green; font-size: 16px; vertical-align: baseline; fill: green !important; } summary.robots-audit-status-box{ padding: 10px 0px 10px 15px; margin: 0px 0px 10px 0px; background: #000000; color: #fff; } ul.robots { list-style: none; padding: 0px 0px 0px 15px; border-left: 1px dotted #b9b9b9; background: #fffcf2b8; } ul.robots li.robots-rows-data { background: none; color: #000; font-weight: 400; font-size: 12px; padding: 5px 5px 5px 5px; margin: 5px 0px 5px 0px; } pre.view-code-pre { padding: 10px 15px 10px 15px; border: 1px dotted #000; margin: 10px 0px 10px 0px; line-height: .9rem; color: #000; font-size: 12px; font-weight: 500; overflow: auto; background: #fffcf2b8; } summary.robots-view-code-box{ padding: 10px 0px 10px 15px; margin: 0px 0px 10px 0px; background: #000000; color: #fff; } ul.og-meta-code-container { list-style: none; padding: 15px 15px 15px 15px; margin: 0px auto; min-height: auto; /* height: 300px; */ max-height: max-content; overflow-x: hidden; overflow-y: scroll; scrollbar-width: none; } .og-meta-code-view { height: 300px; max-height: 300px; overflow: hidden; min-height: auto; white-space: break-spaces; line-height: 1.1rem; overflow-y: scroll; } .display-og-meta ul { list-style: none; padding: 0px; margin: 0px; display: flex; top: 0; } ul.og-meta-code-container li { background: #292e34; } div#auditSummaryPieChart.collapse.show { background: #000000; width: 190px; position: absolute; top: 103px; right: 300px; height: 190px; z-index: 1; transition: height .35s ease; } div#auditSummaryPieChart.collapsing { background: #000000; width: 190px; position: absolute; top: 103px; right: 300px; height: 0; z-index: 1; transition: height .35s ease; } div#versionHistory.collapse.show { background: #000000; width: 190px; position: absolute; top: 103px; right: 300px; z-index: 1; transition: height .35s ease; } div#versionHistory.collapsing { background: #000000; width: 190px; position: absolute; top: 103px; right: 300px; height: 0; z-index: 1; transition: height .35s ease; } div#parametersDatabase.collapse.show .search-filter { margin: 0px auto !important; padding: 0px 0px 0px 0px; position: absolute; top: 103px; right: 300px; height: auto; max-height: -webkit-fill-available; min-height: 42.52px; transition: top .81s ease; box-shadow: 0px 20px 5px -17px #000; } div#parametersDatabase.collapsing .search-filter { margin: 0px auto !important; padding: 0px 0px 0px 0px; position: absolute; top: 0px; right: 300px; height: auto; max-height: -webkit-fill-available; min-height: 42.52px; transition: top .81s ease; box-shadow: 0px 20px 5px -17px #000; } .search-filter-container { display: flex; gap: 10px; padding: 10px 0px 9px 0px; align-items: center; height: auto; transition: background .51s ease; flex-direction: row; } div#seoAuditMainSettings.collapse.show .seo-audit-main-settings { max-width: 250px !important; min-width: 250px !important; z-index: 1; background: linear-gradient(359deg, #2d291e, #212529); position: fixed; top: 0px; overflow-x: hidden; overflow-y: scroll; scrollbar-width: thin; left: 0; bottom: 10px; color: #e3dc9c; transition: height 1.51s ease-in-out; padding: 55px 10px 10px 10px; margin: 103px 0px 0px 0px; max-height: 100%; height: auto; scrollbar-gutter: stable; scrollbar-color: white black; font-size: 12px; white-space: normal; word-break: auto-phrase; box-shadow: 20px 0px 15px -10px #000; border-left: 2px solid #ffffff; border-bottom: 20px solid #ffffff; border-right: 2px solid #fffcf2; display: flex !important; font-weight: 400; flex-direction: column; align-items: stretch; } div#seoAuditMainSettings.collapsing .seo-audit-main-settings { max-width: 250px !important; min-width: 250px !important; z-index: 1; background: linear-gradient(359deg, #2d291e, #212529); position: fixed; top: 0px; overflow-x: hidden; overflow-y: scroll; scrollbar-width: thin; left: 0; bottom: 0; color: #fffcf2; transition: top 1.51s ease; padding: 0px 0px 20px 0px; margin: 103px 0px 0px 0px; max-height: 0px; height: 0px; scrollbar-gutter: stable; scrollbar-color: white black; font-size: 10px; white-space: normal; word-break: auto-phrase; box-shadow: 20px 0px 15px -10px #000; border-left: 2px solid #ffffff; border-bottom: 20px solid #ffffff; text-transform: uppercase; border-right: 2px solid #fffcf2; display: flex !important; align-items: stretch; flex-direction: column; } .offcanvas-parameters-settings { padding: 0px; margin: 56px auto; display: block; } .seo-audit-main-settings.d-flex.justify-content-between { /* display: table-header-group !IMPORTANT; flex-direction: column; flex-wrap: wrap; background: #000; color: #fff; font-size: 14px; font-weight: 500; height: 66%; min-height: 90%; */ } div#seoSetupMessage { display: block; transition: display 2s ease-in-out; } div#buttonFilterResultsAccordionItems { max-width: 30px; text-align: center; font-size: 22px; position: absolute; bottom: 76px; } #audit-console-wrapper { position: fixed; bottom: 0; width: 100%; z-index: 9999; } .audit-console-header { font-size: 12px; padding: 0px 0px 0px 10px; color: yellow; } #audit-console { background: #111; color: white; max-height: 50vh; height: 0; overflow-y: auto; resize: vertical; border-top: 1px solid #16c60c; font-size: 12px; padding: 0; transition: height 0.4s ease, padding 0.3s ease; box-sizing: border-box; } #audit-console.show { height: 30vh; padding: 5px 0 0 0; } #resize-handle { height: 5px; background: #555; cursor: ns-resize; } ul.audit-console-statuses { display: flex; list-style: none; float: right; padding: 0px 10px 0px 5px; margin: 0px; } #audit-log-console li { margin: 4px 0; padding: 2px 6px; } .log-error { color: red; } .log-warning { color: orange; } .log-pass { color: lightgreen; } /* div#seoParametersDatabase.collapse.show { top: 102px; position: fixed; right: 0; min-width: 100% !important; margin: 0px auto; left: 0; width: 100%; max-width: 100%; background: rgb(153 0 0); z-index: 999999999; transition: all .35s ease; } div#seoParametersDatabase.collapsing { top: 102px; position: fixed; right: 0; min-width: 100% !important; margin: 0px auto; left: 0; width: 100%; max-width: 100%; background: rgb(0 0 0); z-index: 999999999; transition: all .35s ease; } */ /* Hamburger icon */ .hamburger { display: inline-block; margin: 0px 5px; z-index: 2; width: 25px; } .seo-params-open {display: inline-block;margin: 0px 5px;/* z-index: 99999999999999999999999999; */width: 25px;position: relative;} .bar1, .bar2, .bar3 { width: 25px; height: 3px; background-color: #000; margin: 6px 0; transition: 0.8s; } /* Rotate to X when open */ .change .bar1 { transform: rotate(-45deg) translate(-9px, 6px); } .change .bar2 { opacity: 0; } .change .bar3 { transform: rotate(45deg) translate(-8px, -8px); } button.btn.btn-sm.btn-gear.text-dark { font-size: 22px; padding: 0px; line-height: 0; margin: 0px 5px; width: 25px; } button.btn.btn-sm.btn-crawl { font-size: 22px; padding: 0px; line-height: 0; margin: 0px 5px; width: 25px; } button.btn.btn-sm.btn-gear.text-dark i.bi.bi-nut-fill:hover { color: #a70000; } .sidenav {width: 0;position: fixed;z-index: 1;top: 103px;left: 0;background-color: #000;overflow-x: hidden;transition: 0.5s;padding-top: 60px;/* right: 0; *//* min-width: 100%; */height: auto;} .sidenav a { padding: 8px 8px 8px 32px; text-decoration: none; font-size: 25px; color: #fff; display: block; transition: 0.3s; } .sidenav a:hover { color: #fff; } @media screen and (max-height: 450px) { .sidenav {padding-top: 15px;} .sidenav a {font-size: 18px;} } button.btn.btn-sm.btn-gear.seo-button { color: #000; font-size: 22px; padding: 0px; line-height: 0; margin: 0px 5px; width: 25px; } button.btn.btn-sm.btn-gear.seo-button.need-setup { color: red; font-size: 22px; padding: 0px; line-height: 0; margin: 0px 5px; width: 25px; } .s-hamburger #sidenavSeo { display: none; } .s-hamburger #sidenavSeo.show { display: block; background: #eee; padding: 10px; } .s-hamburger.change .seobar1 { transform: rotate(-45deg) translate(-5px, 6px); } .s-hamburger.change .seobar2 { opacity: 0; } .s-hamburger.change .seobar3 { transform: rotate(45deg) translate(-5px, -6px); } .line-number { color: #888; display: inline-block; width: 2em; } .prop { color: #ffce54; } .val { color: #a0d911; max-height: 15px; float: left; overflow: hidden; max-width: 80%; } .json-code-container { transition: all 0.3s ease; } .sl { width: 100%; display: inline-flex; text-align: left; flex-direction: row; flex-wrap: wrap; justify-content: flex-start; background: #2f343afa; padding: 1px 0px 1px 5px; line-height: .8rem; margin: 0px 0px 1px 0px; } .bd-placeholder-img { font-size: 1.125rem; text-anchor: middle; -webkit-user-select: none; -moz-user-select: none; user-select: none; } @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } } .b-example-divider { width: 100%; height: 3rem; background-color: rgba(0, 0, 0, .1); border: solid rgba(0, 0, 0, .15); border-width: 1px 0; box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15); } .b-example-vr { flex-shrink: 0; width: 1.5rem; height: 100vh; } .bi { vertical-align: -.125em; fill: currentColor; } .nav-scroller { position: relative; z-index: 2; height: 2.75rem; overflow-y: hidden; } .nav-scroller .nav { display: flex; flex-wrap: nowrap; padding-bottom: 1rem; margin-top: -1px; overflow-x: auto; text-align: center; white-space: nowrap; -webkit-overflow-scrolling: touch; } .btn-bd-primary { --bd-violet-bg: #712cf9; --bd-violet-rgb: 112.520718, 44.062154, 249.437846; --bs-btn-font-weight: 600; --bs-btn-color: var(--bs-white); --bs-btn-bg: var(--bd-violet-bg); --bs-btn-border-color: var(--bd-violet-bg); --bs-btn-hover-color: var(--bs-white); --bs-btn-hover-bg: #6528e0; --bs-btn-hover-border-color: #6528e0; --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb); --bs-btn-active-color: var(--bs-btn-hover-color); --bs-btn-active-bg: #5a23c8; --bs-btn-active-border-color: #5a23c8; } .bd-mode-toggle { z-index: 1500; } .bd-mode-toggle .bi { width: 1em; height: 1em; } .bd-mode-toggle .dropdown-menu .active .bi { display: block !important; } .dropdown-toggle { outline: 0; } .btn-toggle { padding: .25rem .5rem; font-weight: 600; color: var(--bs-emphasis-color); background-color: transparent; } .btn-toggle:hover, .btn-toggle:focus { color: rgba(var(--bs-emphasis-color-rgb), .85); background-color: var(--bs-tertiary-bg); } .btn-toggle::before { width: 1.25em; line-height: 0; content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e"); transition: transform .35s ease; transform-origin: .5em 50%; } [data-bs-theme="dark"] .btn-toggle::before { content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%28255,255,255,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e"); } .btn-toggle[aria-expanded="true"] { color: rgba(var(--bs-emphasis-color-rgb), .85); } .btn-toggle[aria-expanded="true"]::before { transform: rotate(90deg); } .btn-toggle-nav a { padding: .1875rem .5rem; margin-top: .125rem; margin-left: 1.25rem; } .btn-toggle-nav a:hover, .btn-toggle-nav a:focus { background-color: var(--bs-tertiary-bg); } .scrollarea { overflow-y: auto; } button.btn.btn-sm.btn-accordion-right-stats.text-dark { font-size: 14px; padding: 0px; line-height: 2; margin: 0px 0px; width: 14px; } .sidebar-tabs-box { border-top: 5px solid #000; padding: 10px; background: #cccccc7a; box-shadow: 0px 15px 10px -15px #000; } .sidebar-tabs-box p.lead.small { border-bottom: 1px dotted #ccc; padding: 0px 0px 5px 0px; font-size: 14px; font-weight: 400; } h3.sidebar-tabs-title { font-size: 16px; border-bottom: 1px solid #ccc; padding: 0px 0px 10px 0px; margin: 0px 0px 10px 0px; } ul#selectedAuditList.selected-audits-list { list-style: none; padding: 0px 0px 0px 0px; font-size: 14px; font-weight: 400; } ul.json-sidebar-ul { padding: 0px; list-style: none; } ul.json-sidebar-list { padding: 0px; font-size: 14px; line-height: 1.75rem; list-style: none; /* border-left: 0px solid #000; */ height: auto; overflow-x: hidden; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #f5f5f5 #212529; max-height: 380px; } div#jsonLdSidebarContent ul.d-flex.align-items-center { /* display: flex !important; */ font-size: 12px; padding: 0px; flex-wrap: wrap; justify-content: space-evenly; list-style: none; gap: 10px; line-height: 0; align-items: center; flex-direction: column; } ul.load { list-style: none; padding: 10px; font-size: 12px; font-weight: 500; /* line-height: 1rem; */ background: #cccccc70; /* display: block; */ border-bottom: 1px solid #000; } ul.load li { /* display: block; */ /* line-height: 24px; */ /* padding: 0px; */ } ul.load li span { font-size: 12px !important; } ul.load li span.spinner-grow.spinner-grow-sm.text-primary { font-size: 12px !important; transform: scale(0.55) !important; float: left; display: block; max-height: 18px; } body { margin: 0px auto !important; padding: 6.5rem 0rem 0rem 0rem !important; min-height: 80rem; overflow: auto; max-width: 100%; } body.modal-open, body.modal-open.modal.fade.show { padding-right: 0px !important; max-width: 100%; overflow: overlay !important; } body.modal-open .modal-backdrop { opacity: 0 !important; z-index: inherit; overflow: hidden !important; display: none; } div#seoAuditsPar { right: 159px; width: 300px; position: absolute; top: 103px; margin: 0px auto; padding: 13px; background: #000; max-width: 300px; } body.modal-open #versionUpdates.modal.fade.show { background: #000000c9; top: 103px; left: 0px; right: 0px; bottom: 0px; height: 100dvh; } body.modal-open #versionUpdates .modal.fade.show.modal-dialog { margin: 0px 0px 0px 0px !important; } body.modal-open #versionUpdates.modal.show .modal-dialog { background: red !important; padding: 10px; } body.offcanvas.offcanvas-bottom.toolbox-bottom.show{ padding-right: 0 !important; overflow-x: hidden !important; overflow-y: hidden !important; } #auditToolbar { background: #fff; box-shadow: 0px 15px 20px -10px #000;} .toolbar { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; padding: 10px 0px 10px 0px; } main.box { background: #ffc10745; /* width: 100%; */ height: 100%; /* overflow-x: hidden !important; */ padding: 40px 0px 40px 0px; min-height: 75rem; } button { padding: 10px 20px; margin-left: 10px; cursor: pointer; } .score-summary { background: #f0f0f0; padding: 10px; margin-bottom: 20px; border-left: 5px solid #2196f3; } .accordion { border: 1px solid #ccc; margin-bottom: 10px; border-radius: 0px; } .accordion-header { display: flex; justify-content: space-between; padding: 5px 15px 5px 15px !important; cursor: pointer; background: #ffffff; flex-direction: row; position: sticky; top: 0; } .accordion-header.passed { border-left: 4px solid #4caf50; } .accordion-header.warning { border-left: 4px solid #ff9800; } .accordion-header.failed { border-left: 4px solid #f44336; } .accordion-content { display: none; padding: 10px 20px; background: #fff; border-top: 1px solid #ccc; white-space: normal; font-size: 13px; border-radius: 0 0 0px 0px; line-height: normal; font-weight: 500; } .accordion-content.passed { background: #4caf5029; color: #005a04; } .accordion-content.warning { background: #ff980029; color: #704300; } .accordion-content.failed { background: #9b0b0029; color: #600000; } .accordion-content-data { white-space: break-spaces; } .accordion-content ul {padding-left: 20px;margin: 0;list-style: disc;} .accordion-content li {margin-bottom: 10px;line-height: 1.6;background: #f5f5f5;padding: 6px 10px;border-radius: 4px;} .error-message { color: red; font-weight: bold; margin-top: 15px; } .export-buttons { margin-top: 20px; } .brand {color: #f00; } .score-summary-block { display: flex; justify-content: space-between; } /* .score-summary-block div { display: flex; align-items: center; gap: 6px; } */ .score-summary-block div.passed { background: green; color: #fff; padding: 5px 10px; font-weight: 300; } .score-summary-block div.warning { background: #ff9800; color: #000000; padding: 5px 10px; font-weight: 300; } .score-summary-block div.failed { background: #9b0b00; color: #ffffff; padding: 5px 10px; font-weight: 300; } .header.passed::before { display: inline-block; content: "\F270"; vertical-align: -.125em; font-family: 'bootstrap-icons'; fill: #4caf50; color: #4caf50; font-weight: 900; border-right: 2px solid #4caf50; padding: 0px 15px 0px 15px; margin: 0px 10px 0px 0px; font-size: 16px; } .header.warning::before { display: inline-block; content: "\F270"; vertical-align: -.125em; font-family: 'bootstrap-icons'; fill: #ff9800; color: #ff9800; font-weight: 900; border-right: 2px solid #ff9800; padding: 0px 15px 0px 15px; margin: 0px 10px 0px 0px; font-size: 16px; } .header.failed::before { display: inline-block; content: "\F270"; vertical-align: -.125em; font-family: 'bootstrap-icons'; fill: #9b0b00; color: #9b0b00; font-weight: 900; border-right: 2px solid #9b0b00; padding: 0px 15px 0px 15px; margin: 0px 10px 0px 0px; font-size: 16px; } pre { white-space: break-spaces; } svg.header-icons { fill: #fff; } .progress.audit-progress { background: none; height: 8px !important; border: none !important; border-radius: 0 !important; } div#seoAuditParameters { height: 400px; overflow-x: hidden; overflow-y: auto; max-height: 400px; min-height: 400px; border: none; scrollbar-width: thin; scrollbar-color: #fffcf2 #212529; border: 0px !important; border-radius: 0px !important; } .accordion-item.fade.show { border-radius: 0 !important; } .accordion-content-dt { white-space-collapse: pre-line; padding: 0px 0px 0px 0px; } .accordion.accordion-box .accordion-content { height: auto !important; max-height: initial; min-height: 1px; overflow-x: hidden !important; overflow-y: scroll; scrollbar-color: #000000 #fffcf2; scrollbar-gutter: stable; scrollbar-width: none; margin: 0px 0px 0px 0px !important; padding: 0px 0px 0px 0px !important; } .accordion-content-text { position: sticky; top: 0; left: 0; right: 0; width: 100%; padding: 5px 15px 5px 15px; background: #000; color: #fff; } li.single-alt-image { display: flex; gap: 10px; justify-content: space-between; align-items: center; } .accordion-content-text p { margin: 5px 0px 5px 0px; } h3.show-tags { font-size: 14px; font-weight: 500;} span.badge.passed { background: #006200; color: #ffffff; margin: 0px 10px 0px 10px; line-height: normal; border-radius: 0; font-size: 12px !important; font-weight: 500; } span.badge.warning { background: #ff9800; color: #000; margin: 0px 10px 0px 10px; line-height: normal; border-radius: 0; font-size: 12px !important; font-weight: 500; } span.badge.failed { background: #f44336; color: #ffffff; margin: 0px 10px 0px 10px; line-height: normal; border-radius: 0; font-size: 12px !important; font-weight: 500; } h2.seo-parameters { padding: 10px 10px 10px 10px; margin: 0px auto; background: #ccc; font-size: 14px !important; } button.sap.accordion-button.collapsed { padding: 5px 0px 5px 10px; font-weight: 600; font-size: 14px; } .accordion-item.fade.show {border-radius: 0 !important;} div#auditSummary .col { padding: 0px 0px 0px 0px !important; width: 19%; } div#auditSummary .col.alert.alert-primary { background: blue; color: #fff; font-size: 14px; font-weight: 500; border: 2px solid #000; } div#auditSummary .col.alert.alert-success { background: green; color: #fff; font-size: 14px; font-weight: 500; border: 2px solid #000; } div#auditSummary .col.alert.alert-warning { background: #FF9800; color: #000; font-size: 14px; font-weight: 500; border: 2px solid #000; } div#auditSummary .col.alert.alert-danger { background: #a30010; color: #fff; font-size: 14px; font-weight: 500; border: 2px solid #000; } div#auditSummary .col.alert.alert-primary { background: #263238; color: #fff; font-size: 14px; font-weight: 500; border: 2px solid #000; } .accordion-caret i { transition: transform 0.3s ease; } .accordion-header.open .accordion-caret i { transform: rotate(180deg); } #myBtn { display: none; /* Hidden by default */ position: fixed; /* Fixed/sticky position */ bottom: 20px; /* Place the button at the bottom of the page */ right: 30px; /* Place the button 30px from the right */ z-index: 99; /* Make sure it does not overlap */ border: none; /* Remove borders */ outline: none; /* Remove outline */ background-color: red; /* Set a background color */ color: white; /* Text color */ cursor: pointer; /* Add a mouse pointer on hover */ padding: 15px; /* Some padding */ border-radius: 10px; /* Rounded corners */ font-size: 18px; /* Increase font size */ } #myBtn:hover { background-color: #555; /* Add a dark-grey background on hover */ } /* div#auditResultsFilterSection { background: #000000; flex-direction: column; align-items: stretch; padding: 10px 10px 10px 10px; margin: 0px 0px 25px 0px; } */ body .offcanvas.offcanvas-bottom.toolbox-bottom.show { border-top: 10px solid #000; padding: 0px !important; overflow: hidden !important; } .icon-state { transition: opacity 0.3s ease; opacity: 1; } .icon-fade-out { opacity: 0; } .seo-score-box { padding: 10px; border: 1px solid #ddd; border-radius: 6px; background-color: #f9f9f9; margin-top: 10px; } .score-summary-block .progress-bar.bg-success { background: #4caf50 !important; color: #fff; font-weight: 500; border-radius: 0 !important; } .score-summary-block .progress-bar.bg-warning { background: #ff9800 !important; color: #000; font-weight: 500; border-radius: 0 !important; } .score-summary-block .progress-bar.bg-danger { background: #9b0b00 !important; color: #000; font-weight: 500; border-radius: 0 !important; } .col-auto.bg-total { background: #007005 !important; color: #fff; font-weight: 500; border-radius: 0 !important; padding: 5px 10px 5px 10px; max-height: 24px !important; display: block; line-height: 1; } .keyword-results ol { padding-left: 1.2em; margin-top: 0.5em; } .keyword-results li { margin-bottom: 4px; font-size: 14px; } div#auditResultsFilterSection { margin: 20px 0px 0px 0px; display: flex; padding: 0px 100px; border-top: 3px solid #212529; box-shadow: 0px 17px 10px -15px #000; position: fixed; left: 0; right: 0; background: #fff; bottom: 0; flex-direction: column; align-content: stretch; flex-wrap: wrap; align-items: stretch; z-index: 1; } .jsons-code-view { height: 300px; max-height: 300px; overflow: hidden; min-height: auto; white-space: break-spaces; line-height: 1.1rem; overflow-y: scroll; } i.bi.bi-braces.json-icon { float: left; vertical-align: middle !important; width: 30px; text-align: center; background: #000; color: #fff; } .display-jsons ul { list-style: none; padding: 0px; margin: 0px; display: flex; /* position: sticky; */ top: 0; } .accordion-content-dt.display-jsons { position: sticky; top: 0; } .display-jsons ul li { background: none; text-align: center; padding: 0px 0px 0px 0px; margin: 0px auto; } ul.json-code-boxes { list-style: none; padding: 15px 15px 15px 15px; margin: 0px auto; min-height: auto; /* height: 300px; */ max-height: max-content; overflow-x: hidden; overflow-y: scroll; scrollbar-width: none; } </style>
<!-- <script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script> --> <!-- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="assets/js/score-chart.js"></script> --></head><body> <main id="main" class="box"> <div id="masterSEONavigation" class="container-fluid m-0 p-0 fixed-top"> <header> <div class="px-1 py-1 text-bg-dark border-bottom"> <div class="container"> <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start"> <a href="file:///A:/Technical%20SEO%20Audit%20by%20SEO%20Book%20Pro%20Beta%20v0.0.7/index.html" class="d-flex align-items-center my-1 my-lg-0 me-lg-auto text-white text-decoration-none"> <span class="brand-name fw-bold p-1 m-1">SEO Book Pro</span> <span class="version-name">Beta v0.0.1 r-c</span> </a> <ul class="nav col-12 col-lg-auto my-1 justify-content-center my-md-0 text-small" id="navbarSupportedContent"> <li> <a href="#" class="nav-link text-white">SEO </a> </li> <li> <a href="#" class="nav-link text-white">Features </a> </li> <li> <a href="#" class="nav-link text-white">Functions </a> </li> <li> <a href="#" class="nav-link text-white">FAQs </a> </li> <li> <a href="#" class="nav-link text-white">Tools </a> </li> <li> <a href="#" class="nav-link text-white">Support </a> </li> <li> <a href="#" class="nav-link text-white">Videos </a> </li> </ul> </div> </div> </div> <div id="auditToolbar" class="container-fluid"> <div class="container"> <div class="toolbar"> <div class="d-flex gap-1 col-7 col-sm-7 col-md-7"> <div class="col-auto"> <button id="toggle-crawl-settings" class="btn btn-sm btn-crawl" type="button"> <i class="bi bi-filter-circle"></i> </button> <button id="toggle-console" class="btn btn-sm btn-gear seo-button" type="button"> <i class="bi bi-terminal"></i> </button> <button class="btn btn-sm btn-gear seo-button" type="button" data-bs-toggle="collapse" data-bs-target="#seoAuditMainSettings" aria-expanded="false" aria-controls="seoAuditMainSettings"> <i class="bi bi-sliders"></i> </button> <button id="resetAuditBtn" class="btn btn-sm btn-gear"> <i class="bi bi-trash3"></i> </button> </div> <input type="text" class="form-control-sm w-100 lh-0 me-1" list="datalistUrlInputOptions" id="urlInput" placeholder="Enter domain or URL..." aria-label="Run Audit" autocomplete="url"> <datalist id="datalistUrlInputOptions"> <option value="https://semrush.com"> <option value="https://ahrefs.com"> <option value="https://moz.com"> <option value="https://majestic.com"> <option value="https://yoast.com"> <option value="https://rankmath.com"> <option value="https://getbootstrap.com/"> <option value="https://google.com"> <option value="https://bing.com"> <option value="https://yahoo.com"> </datalist> <div class="d-flex gap-0 col-auto" id="auditButtonContainer"> <button class="btn btn-sm btn-gear text-dark" onclick="clearAuditInputSearch()" title="Clear Audit Search Input Field"> <i class="bi bi-arrow-clockwise"></i> </button> <button class="btn btn-sm btn-gear text-dark" onclick="runAudit()" title="Perform Technical SEO and Website Audit"> <i class="bi bi-play-circle"></i> </button> <button id="toggleAllAccordionsBtn" class="btn btn-sm btn-gear text-dark"> <i class="bi bi-caret-down-square-fill icon-state"></i> </button> </div> <!-- Placeholder Button --> </div>
<div class="col-auto gap-1"> <button id="generateReportBtn" class="btn btn-sm btn-gear text-dark"><i class="bi bi-cloud-download"></i></button>
</div> <!-- Stats Data Modal --> <div class="modal fade" id="statsDataModal" tabindex="-1" aria-labelledby="statsDataLabel" aria-hidden="true"> <div class="modal-dialog modal-lg modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="statsDataLabel">Stats Data</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <p>Here you can display stats-related information dynamically.</p> <!-- Dynamic content can go here --> </div> </div> </div> </div>
<!-- Stats Info Modal --> <div class="modal fade" id="statsInfoModal" tabindex="-1" aria-labelledby="statsInfoLabel" aria-hidden="true"> <div class="modal-dialog modal-md modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="statsInfoLabel">Stats Info</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <p>This is detailed info about how audit stats are calculated or interpreted.</p> </div> </div> </div> </div>
<!-- Video Help Modal --> <div class="modal fade" id="statsVideoModal" tabindex="-1" aria-labelledby="statsVideoLabel" aria-hidden="true"> <div class="modal-dialog modal-xl modal-dialog-centered"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="statsVideoLabel">Video Help</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <!-- Example embedded YouTube video --> <div class="ratio ratio-16x9"> <!-- <iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" title="Video Help" allowfullscreen></iframe> --> </div> </div> </div> </div> </div>
<div class="d-flex col-auto text-end"> <button class="btn btn-sm btn-gear text-dark" type="button" data-bs-toggle="collapse" data-bs-target="#auditSummaryPieChart" aria-expanded="false" aria-controls="auditSummaryPieChart"> <i class="bi bi-pie-chart-fill"></i> </button> <button id="seoParametersDatabaseButton" class="btn btn-sm btn-gear text-dark" type="button" data-bs-toggle="collapse" data-bs-target="#seoParametersDatabase" aria-controls="seoParametersDatabase"> <i class="bi bi-collection"></i> </button> <button class="btn btn-sm btn-gear text-dark" type="button" data-bs-toggle="offcanvas" data-bs-target="#toolboxBottom" aria-controls="toolboxBottom"> <i class="bi bi-nut-fill"></i> </button> <button type="button" class="btn btn-sm btn-gear text-dark" data-bs-toggle="collapse" data-bs-target="#seoAuditsPar" aria-controls="seoAuditsPar"> <i class="bi bi-git"></i> </button> <!-- <button id="versionUpdatesButton" type="button" class="btn btn-sm btn-gear text-dark" data-bs-toggle="modal" aria-controls="versionUpdatesButton" data-bs-target="#versionUpdates"> <i class="bi bi-fullscreen-exit"></i> </button> -->
<div id="hamburger" class="hamburger" onclick="toggleNav()"> <div class="bar1"></div> <div class="bar2"></div> <div class="bar3"></div> </div> </div> <!-- Progress Bar --> </div> </div> </div> </header> <div class="progress audit-progress" role="progressbar" aria-label="Animated striped example" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"> <div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated" style="height: 8px; width: 0%"></div> </div> </div> <div class="collapse" id="seoAuditMainSettings"> <div class="seo-audit-main-settings d-flex gap-3 justify-content-between"> <div class="col-auto filter-settings d-flex justify-content-center"> <input type="text" id="filterInputAuditCheckToggles" class="form-control-sm" placeholder="Filter by Audit Parameters Names or Audit Status..." aria-label="Filter by Audit Parameters Names or Audit Status..." onkeyup="filterAuditCheckToggles()"> <button class="btn btn-sm btn-gear text-white" onclick="clearFilterAuditCheckToggles()"> <i class="bi bi-arrow-clockwise"></i> </button> </div> <div class="form-check form-switch toggle-all-audits"> <input class="form-check-input" type="checkbox" role="switch" id="toggleAllChecks"> <label class="form-check-label" for="toggleAllChecks"> <strong>Toggle All Audit Checks</strong> </label> </div> <div class="col-auto p-2 border-top border-warning border-2"> <div id="auditCheckToggles"></div> <!-- Toggle All --> </div> </div> </div> <div class="collapse" id="auditSummaryPieChart"> <div class="audit-pie-chart"> <!-- <canvas id="auditPieChart" class="mb-1"></canvas> --> </div> </div> <!-- <div class="collapse" id="seoParametersDatabase"> <div class="parameters-database"> <div class="search-filter"> <div class="search-filter-container w-100"> <div class="col-auto pe-1 text-white">800+</div> <div class="col p-0 m-0"> <input type="text" id="searchAccordion" class="form-control form-control-sm" placeholder="Search SEO Parameters..." onkeyup="filterAccordion()"> </div> <div class="col-auto"> <button class="btn btn-sm btn-dark" onclick="clearAccordionSearch()"> <i class="bi bi-arrow-clockwise"></i> </button> <button class="btn btn-sm btn-dark" data-bs-toggle="collapse" href="#parametersDatabasef" role="button" aria-expanded="false" aria-controls="parametersDatabasef"> <i class="bi bi-list"></i> </button> </div> </div> </div> </div> </div> --> <div class="collapse" id="seoAuditsPar"> <div class="parameters-database"> <div class="search-filter"> <div class="search-filter-container w-100"> <div class="col-auto pe-1 text-white">800+</div> <div class="col p-0 m-0"> <input type="text" id="searchAccordion" class="form-control form-control-sm" placeholder="Search SEO Parameters..." onkeyup="filterAccordion()"> </div> <div class="col-auto"> <button class="btn btn-sm btn-dark" onclick="clearAccordionSearch()"> <i class="bi bi-arrow-clockwise"></i> </button> <button class="btn btn-sm btn-dark" data-bs-toggle="collapse" role="button" data-bs-target="#asdf" aria-expanded="false" aria-controls="asdf"> <i class="bi bi-list"></i> </button> </div> </div> </div> </div> <div class="list-parameters collapse" style="width: auto;" id="asdf"> <h2 class="seo-parameters">SEO Audit Parameters</h2> <div class="accordion" id="seoAuditParameters"> <!-- SEO Title -->
<!-- SEO Title --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#seo-title" aria-expanded="false" aria-controls="seo-title"> SEO Title </button> </h2> <div id="seo-title" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>SEO Title:</strong> Appears in SERPs as the clickable headline; should be unique and keyword-optimized. </div> </div> </div>
<!-- SEO Description --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#seo-description" aria-expanded="false" aria-controls="seo-description"> SEO Description </button> </h2> <div id="seo-description" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>SEO Description:</strong> The meta description tag summarizes page content in search engine results. </div> </div> </div>
<!-- H1 Heading --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#h1-heading" aria-expanded="false" aria-controls="h1-heading"> H1 Heading </button> </h2> <div id="h1-heading" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>H1 Heading:</strong> The primary heading that introduces the content of the page. </div> </div> </div>
<!-- Paragraphs --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#paragraphs" aria-expanded="false" aria-controls="paragraphs"> Paragraphs </button> </h2> <div id="paragraphs" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>Paragraphs:</strong> Blocks of readable text optimized with relevant keywords and structure. </div> </div> </div>
<!-- Spans --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#spans" aria-expanded="false" aria-controls="spans"> Spans </button> </h2> <div id="spans" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>Spans:</strong> Inline text containers, often used for styling and keyword emphasis. </div> </div> </div>
<!-- Lists (UL/LI) --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#lists" aria-expanded="false" aria-controls="lists"> Lists (UL/LI) </button> </h2> <div id="lists" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>Lists (UL/LI):</strong> Useful for scannable content and featured snippet eligibility. </div> </div> </div>
<!-- Internal Links --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#internal-links" aria-expanded="false" aria-controls="internal-links"> Internal Links </button> </h2> <div id="internal-links" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>Internal Links:</strong> Hyperlinks that point to pages within the same domain. </div> </div> </div>
<!-- External Links --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#external-links" aria-expanded="false" aria-controls="external-links"> External Links </button> </h2> <div id="external-links" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>External Links:</strong> Hyperlinks pointing to different domains or external websites. </div> </div> </div>
<!-- HTTP Links --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#http-links" aria-expanded="false" aria-controls="http-links"> HTTP Links </button> </h2> <div id="http-links" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>HTTP Links:</strong> Non-secure links (http://) that should be updated to HTTPS. </div> </div> </div>
<!-- Image ALT Attributes --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#image-alt" aria-expanded="false" aria-controls="image-alt"> Image ALT Attributes </button> </h2> <div id="image-alt" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>Image ALT Attributes:</strong> Textual descriptions for images that improve accessibility and image SEO. </div> </div> </div>
<!-- Canonical Tag --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#canonical" aria-expanded="false" aria-controls="canonical"> Canonical Tag </button> </h2> <div id="canonical" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>Canonical Tag:</strong> Specifies the preferred version of a web page to avoid duplicate content. </div> </div> </div>
<!-- OpenGraph Meta --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#opengraph" aria-expanded="false" aria-controls="opengraph"> OpenGraph Meta </button> </h2> <div id="opengraph" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>OpenGraph Meta:</strong> Tags that control how URLs are displayed when shared on social platforms. </div> </div> </div>
<!-- Noindex Meta --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#noindex" aria-expanded="false" aria-controls="noindex"> Noindex Meta </button> </h2> <div id="noindex" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>Noindex Meta:</strong> Prevents pages from being indexed by search engines. </div> </div> </div>
<!-- Schema Meta Data --> <div class="accordion-item"> <h2 class="accordion-header p-1"> <button class="sap accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#schema" aria-expanded="false" aria-controls="schema"> Schema Meta Data </button> </h2> <div id="schema" class="accordion-collapse collapse fade" data-bs-parent="#seoAuditParameters"> <div class="accordion-body"> <strong>Schema Meta Data:</strong> Structured data markup that enhances search engine understanding of content. </div> </div>
</div>
</div> </div>
</div> <div class="container-fluid"> <div class="container"> <div class="row d-flex"> <div class="col-sm-9 p-0"> <h1 class="fs-5 pb-3">Free Technical SEO and Website Audit Dashboard Beta v0.0.1 r-c</h1> <!-- <button id="generateReportBtn" class="btn btn-primary">📄 Build Report</button> <div id="reportLoader" style="display:none;">Generating report...</div> --> <!-- <div id="auditSummary" class="grid text-center"> --> <!-- <div class="result-completion fs-4 p-1" id="auditCompletionResults"></div> --> <div class="result" id="auditResultsFilterSection"></div> <!-- <div class="result p-2" id="generalAuditResultsSection"></div> --> <div class="result p-2" id="auditResults"></div> <!-- Placeholder Button --> </div> <div class="col-sm-3 px-3 pe-0"> <div id="sideSelectedAudits"> <h5>Running Audits:</h5> <ul id="selectedAuditList" class="selected-audits-list"></ul> </div> <div id="auditError"></div> <div id="needSeoSetup"></div> <div id="alertValidUrl" class="col-12 alert alert-danger d-none mb-2 mt-2"></div> <div class="result pb-2 mb-2 pt-2 mt-2" id="auditResultsBox"></div> <div id="jsonLdSidebarDisplay"></div> </div> <!--<div id="auditError"></div> <div class="jumbotron sidebar-tabs-box" id="coreFunctions"> <ul class="nav nav-pills mb-3 d-flex justify-content-between" id="pills-tab" role="tablist"> <li class="nav-item" role="presentation"> <button class="btn btn-sm btn-gear text-dark" id="coreFunctionsDatabase-tab" data-bs-toggle="pill" data-bs-target="#coreFunctionsDatabase" type="button" role="tab" aria-controls="coreFunctionsDatabase" aria-selected="true"> <i class="bi bi-database"></i> </button> </li> <li class="nav-item" role="presentation"> <button class="btn btn-sm btn-gear text-dark" id="coreFunctionsCodeRowsProgrammingLanguages-tab" data-bs-toggle="pill" data-bs-target="#coreFunctionsCodeRowsProgrammingLanguages" type="button" role="tab" aria-controls="coreFunctionsCodeRowsProgrammingLanguages" aria-selected="true"> <i class="bi bi-code-square"></i> </button> </li> <li class="nav-item" role="presentation"> <button class="btn btn-sm btn-gear text-dark" id="pills-profile-tab" data-bs-toggle="pill" data-bs-target="#pills-profile" type="button" role="tab" aria-controls="pills-profile" aria-selected="false"> <i class="bi bi-filetype-js"></i> </button> </li> <li class="nav-item" role="presentation"> <button class="btn btn-sm btn-gear text-dark" id="pills-contact-tab" data-bs-toggle="pill" data-bs-target="#pills-contact" type="button" role="tab" aria-controls="pills-contact" aria-selected="false"> <i class="bi bi-filetype-css"></i> </button> </li> <li class="nav-item" role="presentation"> <button class="btn btn-sm btn-gear text-dark" id="pills-disabled-tab" data-bs-toggle="pill" data-bs-target="#pills-disabled" type="button" role="tab" aria-controls="pills-disabled" aria-selected="false"> <i class="bi bi-filetype-html"></i> </button> </li> <li class="nav-item" role="presentation"> <button class="btn btn-sm btn-gear text-dark" id="pills-disabled-tab" data-bs-toggle="pill" data-bs-target="#pills-disabled" type="button" role="tab" aria-controls="pills-disabled" aria-selected="false"> <i class="bi bi-filetype-json"></i> </button> </li> </ul> <div class="tab-content" id="pills-tabContent"> <div class="tab-pane fade show active" id="coreFunctionsDatabase" role="tabpanel" aria-labelledby="coreFunctionsDatabase-tab" tabindex="0"> <div class="list-group"> <h3 class="sidebar-tabs-title">Database (All Scripts Available)</h3> <p class="lead small">The full set of database scripts is available and maintained for deployment, migration, and rollback.</p> <p class="lead small">SQL scripts include table creation, indexes, constraints, seed data, and stored procedures where applicable.</p> <p class="lead small">Designed to ensure data integrity and scalability.</p> </div></div><div class="tab-pane fade" id="coreFunctionsCodeRowsProgrammingLanguages" role="tabpanel" aria-labelledby="coreFunctionsCodeRowsProgrammingLanguages-tab" tabindex="0"><div class="list-group"><h3 class="sidebar-tabs-title">Code Rows & Programming Languages</h3><span>This project includes a modular codebase written in multiple languages:</span><p class="lead small">HTML for structure (≈ X lines)</p><p class="lead small">CSS for styling and responsiveness (≈ Y lines)</p><p class="lead small">JavaScript for interactivity (≈ Z lines)</p><p class="lead small">JSON for data structuring (≈ W lines)</p><p class="lead small">Row counts are approximations based on source files and dynamically loaded modules</p></div></div><div class="tab-pane fade" id="pills-contact" role="tabpanel" aria-labelledby="pills-contact-tab" tabindex="0"><div class="list-group"><h3 class="sidebar-tabs-title">Database (All Scripts Available)</h3><p class="lead small">The full set of database scripts is available and maintained for deployment, migration, and rollback.</p><p class="lead small">SQL scripts include table creation, indexes, constraints, seed data, and stored procedures where applicable.</p><p class="lead small">Designed to ensure data integrity and scalability.</p></div></div><div class="tab-pane fade" id="pills-disabled" role="tabpanel" aria-labelledby="pills-disabled-tab" tabindex="0"><div class="list-group"><h3 class="sidebar-tabs-title">Database (All Scripts Available)</h3><p class="lead small">The full set of database scripts is available and maintained for deployment, migration, and rollback.</p><p class="lead small">SQL scripts include table creation, indexes, constraints, seed data, and stored procedures where applicable.</p><p class="lead small">Designed to ensure data integrity and scalability.</p></div></div><div class="tab-pane fade" id="pills-disabled" role="tabpanel" aria-labelledby="pills-disabled-tab" tabindex="0"><div class="list-group"><h3 class="sidebar-tabs-title">Database (All Scripts Available)</h3><p class="lead small">The full set of database scripts is available and maintained for deployment, migration, and rollback.</p><p class="lead small">SQL scripts include table creation, indexes, constraints, seed data, and stored procedures where applicable.</p><p class="lead small">Designed to ensure data integrity and scalability.</p></div></div><div class="tab-pane fade" id="pills-disabled" role="tabpanel" aria-labelledby="pills-disabled-tab" tabindex="0"><div class="list-group"><h3 class="sidebar-tabs-title">Database (All Scripts Available)</h3><p class="lead small">The full set of database scripts is available and maintained for deployment, migration, and rollback.</p><p class="lead small">SQL scripts include table creation, indexes, constraints, seed data, and stored procedures where applicable.</p><p class="lead small">Designed to ensure data integrity and scalability.</p></div></div></div></div><div class="col-auto"><div id="needSeoSetup"></div></div><div class="col-auto"><div id="alertValidUrl" class="col-12 alert alert-danger d-none mb-2 mt-2"></div></div><div class="result pb-2 mb-2 pt-2 mt-2" id="auditResultsBox"></div></div> --></div></div></div></main><div class="container-fluid audit-footer mt-5 pt-5 border-top border-dark border-5"> <footer class="main-footer"> <div class="row"> <div class="col-6 col-md-2 mb-3"> <h3>Section</h3> <ul class="nav flex-column"> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">Home</a> </li> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">Features</a> </li> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">Pricing</a> </li> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">FAQs</a> </li> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">About</a> </li> </ul> </div> <div class="col-6 col-md-2 mb-3"> <h3>Section</h3> <ul class="nav flex-column"> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">Home</a> </li> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">Features</a> </li> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">Pricing</a> </li> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">FAQs</a> </li> <li class="nav-item mb-2"> <a href="#" class="nav-link p-0">About</a> </li> </ul> </div> <div class="col-6 col-md-2 mb-3"> <h3>Resources/Vendors</h3> <ul class="nav flex-column"> <li class="nav-item mb-2"> <a href="#proxies" class="nav-link p-0">Proxies</a> </li> <li class="nav-item mb-2"> <a href="#frameworks" class="nav-link p-0">Frameworks</a> </li> <li class="nav-item mb-2"> <a href="#ai" class="nav-link p-0">Ai</a> </li> <li class="nav-item mb-2"> <a href="#javaScript" class="nav-link p-0">JavaScript</a> </li> <li class="nav-item mb-2"> <a href="#vendors" class="nav-link p-0">Vendors</a> </li> </ul> </div> <div class="col-md-5 offset-md-1 mb-3"> <form> <h3>Subscribe to our newsletter</h3> <p>Monthly digest of what's new and exciting from us.</p> <div class="d-flex flex-column flex-sm-row w-100 gap-2"> <label for="newsletter1" class="visually-hidden">Email address</label> <input id="newsletter1" type="email" class="form-control" placeholder="Email address"> <button class="btn btn-primary" type="button">Subscribe</button> </div> </form> </div> </div> </footer></div><!-- Audit Console --><div id="audit-console-wrapper"> <div id="audit-console"> <span class="audit-console-header">SEO Book Pro Audit Log Console</span> <ul class="audit-console-statuses"> <li class="audit-console-failed">❌</li> <li class="audit-console-warning">⚠️</li> <li class="audit-console-valid">✅</li> </ul> <div id="resize-handle"></div> <div class="col-sm-8"> <ul id="audit-log-console"></ul> </div> <div class="col-sm-4"> <div id="needSeoSetup"></div> </div> </div></div><div class="container mx-auto d-flex flex-column flex-sm-row justify-content-between p-2 m-0 border-top border-danger border-2"> <p>Copyright © 2025 SEO Book Pro . All rights reserved.</p><button id="generateReportBtn" class="btn btn-primary">📄 Build Report</button> <div id="reportLoader" style="display:none;">Generating report...</div> <ul class="list-unstyled d-flex"> <li class="ms-2"> <a class="socials" href="#" aria-label="Facebook"> <i class="bi bi-facebook"></i> </a> </li> <li class="ms-2"> <a class="socials" href="#" aria-label="Facebook Messenger"> <i class="bi bi-messenger"></i> </a> </li> <li class="ms-2"> <a class="socials" href="#" aria-label="Google"> <i class="bi bi-google"></i> </a> </li> <li class="ms-2"> <a class="socials" href="#" aria-label="Linkedin"> <i class="bi bi-linkedin"></i> </a> </li> <li class="ms-2"> <a class="socials" href="#" aria-label="Twitter X"></a> <i class="bi bi-twitter-x"></i> </li> <li class="ms-2"> <a class="socials" href="#" aria-label="Youtube"> <i class="bi bi-youtube"></i> </a> </li> <li class="ms-2"> <a class="socials" href="#" aria-label="Github"> <i class="bi bi-github"></i> </a> </li> </ul></div><button class="btn btn-sm btn-gear text-dark" onclick="topFunction()" id="myBtn" title="Go to top"> <i class="bi bi-caret-up-square"></i></button><div class="offcanvas offcanvas-bottom toolbox-bottom" tabindex="-1" id="toolboxBottom" aria-labelledby="toolboxBottomLabel"> <div class="offcanvas-header"> <h5 class="offcanvas-title" id="toolboxBottomLabel">Offcanvas bottom</h5> <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button> </div> <div class="offcanvas-body small"> <div class="container text-center"> <div class="row row-cols-2 row-cols-lg-10"> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> <div class="col"> <div class="p-0">Row column</div> </div> </div> </div> </div></div><div id="mySidenav" class="sidenav"> <div class="flex-shrink-0 p-3" style="width: 280px;"> <a href="/" class="d-flex align-items-center pb-3 mb-3 link-body-emphasis text-decoration-none border-bottom"> <svg class="bi pe-none me-2" width="30" height="24" aria-hidden="true"> <use xlink:href="#bootstrap"></use> </svg> <span class="fs-5 fw-semibold">Get started</span> </a> <ul class="list-unstyled ps-0"> <li class="mb-1"> <button class="btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed" data-bs-toggle="collapse" data-bs-target="#gettingStartedGuide" aria-expanded="false">Getting Started Guide </button> <div class="collapse" id="gettingStartedGuide" style=""> <ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small"> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Overview</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Updates</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Reports</a> </li> </ul> </div> </li> <li class="mb-1"> <button class="btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed" data-bs-toggle="collapse" data-bs-target="#technicalSeoCollapse" aria-expanded="false">Technical SEO </button> <div class="collapse" id="technicalSeoCollapse"> <ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small"> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Overview</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Weekly</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Monthly</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Annually</a> </li> </ul> </div> </li> <li class="mb-1"> <button class="btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed" data-bs-toggle="collapse" data-bs-target="#seoFunction" aria-expanded="false">Functions </button> <div class="collapse" id="seoFunction"> <ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small"> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">New</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Processed</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Shipped</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Returned</a> </li> </ul> </div> </li> <li class="border-top my-3"></li> <li class="mb-1"> <button class="btn btn-toggle d-inline-flex align-items-center rounded border-0 collapsed" data-bs-toggle="collapse" data-bs-target="#seoFeatures" aria-expanded="false">Features </button> <div class="collapse" id="seoFeatures"> <ul class="btn-toggle-nav list-unstyled fw-normal pb-1 small"> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">New...</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Profile</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Settings</a> </li> <li> <a href="#" class="link-body-emphasis d-inline-flex text-decoration-none rounded">Sign out</a> </li> </ul> </div> </li> </ul> </div></div><script>
// Get the buttonlet mybutton = document.getElementById("myBtn");
// When the user scrolls down 20px from the top of the document, show the buttonwindow.onscroll = function() { scrollFunction()}
function scrollFunction() { if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { mybutton.style.display = "block"; } else { mybutton.style.display = "none"; }}
// When the user clicks on the button, scroll to the top of the documentfunction topFunction() { document.body.scrollTop = 0; document.documentElement.scrollTop = 0;}</script><!-- Include All SEO Audit JavaScript Functions in Separate Files and Keep the Order to Not Break the functionality -->
<script> // async function fetchWithFallback(url) { // const proxies = [ // // `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`, // // `https://thingproxy.freeboard.io/fetch/${url}` // `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`, // `https://thingproxy.freeboard.io/fetch/${url}`, // `https://api.codetabs.com/v1/proxy?quest=${encodeURIComponent(url)}`, // `https://yacdn.org/proxy/${url}`, // `https://corsproxy.io/?${encodeURIComponent(url)}`, // // `https://api.scraperapi.com?api_key=YOUR_API_KEY&url=${encodeURIComponent(url)}`, // Requires API key // `https://proxymirror.com/api/proxy?url=${encodeURIComponent(url)}` // Example placeholder // // ]; // // for (const proxy of proxies) { // try { // const response = await fetch(proxy); // if (!response.ok) continue; // const data = await response.json(); // if (data.contents) return data.contents; // } catch (e) { // console.warn('Proxy failed:', proxy, e); // } // } // throw new Error("All proxies failed"); // } async function fetchWithFallback(url) { const proxies = [ `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`, `https://thingproxy.freeboard.io/fetch/${url}`, `https://api.codetabs.com/v1/proxy?quest=${encodeURIComponent(url)}`, `https://yacdn.org/proxy/${url}`, `https://corsproxy.io/?${encodeURIComponent(url)}`, `https://proxymirror.com/api/proxy?url=${encodeURIComponent(url)}` ];
for (const proxy of proxies) { try { const response = await fetch(proxy); if (!response.ok) continue; const data = await response.json(); if (data.contents) return data.contents; } catch (e) { console.warn('Proxy failed:', proxy, e); logMessage('Proxy failed:'); } }
throw new Error("All proxies failed"); }
async function fetchUntilSuccess(url, delay = 10000) { while (true) { try { console.log(`🔄 Attempting to fetch: ${url}`); logMessage(`🔄 Attempting to fetch`); const content = await fetchWithFallback(url); console.log(`✅ Fetch succeeded.`); logMessage(`✅ Fetch succeeded.`); return content; } catch (error) { console.message(` ❌ All proxies failed. Retrying in ${delay / 1000} seconds... `); logMessage(`❌ All proxies failed.Retrying...`); await new Promise(res => setTimeout(res, delay)); } } } const getMetaTag = (doc, selector) => { const el = doc.querySelector(selector); if (!el) return ''; return el.getAttribute('content') || el.content || ''; };
let latestResults = [];
const tests = [ 'Header Status', 'Meta Charset', 'HTML Lang', 'Meta Viewport', 'Link Profile', 'Favicon Link', 'Preconnect Google Fonts', 'Shortlink Link', 'EditURI Link', 'API Link', 'Hreflang Link', 'RSS Link', 'JavaScript Type', 'Empty Meta Tags', 'Meta Title Tag', 'Meta Description', 'Top Keywords', 'H1 Headings', 'H2 Headings', 'H3 Headings', 'H4 Headings', 'H5 Headings', 'H6 Headings', 'Paragraphs', 'Spans', 'Unorered UL/LI List', 'Internal Links', 'External Links', 'HTTP Links', 'Image ALT Attributes', 'Canonical Tag', 'OpenGraph Meta', 'Robots Meta Tag', 'JSON-LD Schema Markup', 'Technologies Detected', 'Google PageSpeed Score', 'XML Sitemap Index and URLs', 'robots.txt Check' ];
// // Function to render switches // function renderAuditSwitches(testNames) { // const container = document.getElementById('auditCheckToggles'); // container.innerHTML = '';
// testNames.forEach((name, index) => { // const id = `auditCheck_${index}`; // container.innerHTML += ` // <div class="form-check form-switch"> // <input class="form-check-input audit-toggle" type="checkbox" role="switch" id="${id}" data-check="${name}" value="${id}"> // <label class="form-check-label audit-toggle" for="${id}">${name}</label> // </div> // `; // });
// // Add toggle-all logic // document.getElementById('toggleAllChecks').addEventListener('change', function () { // const checked = this.checked; // document.querySelectorAll('#auditCheckToggles .form-check-input').forEach(sw => { // sw.checked = checked; // }); // });
// // Add per-toggle sync logic // setupIndividualSwitchSync(); // } // // Sync toggle-all state if a single toggle is changed // function setupIndividualSwitchSync() { // const allSwitches = document.querySelectorAll('#auditCheckToggles .form-check-input'); // allSwitches.forEach(sw => { // sw.addEventListener('change', () => { // const allChecked = Array.from(allSwitches).every(input => input.checked); // document.getElementById('toggleAllChecks').checked = allChecked; // }); // }); // }
// Function to render switches function renderAuditSwitches(testNames) { const container = document.getElementById('auditCheckToggles'); container.innerHTML = ''; testNames.forEach((name, index) => { const id = `auditCheck_${index}`; container.innerHTML += ` <div class="form-check form-switch"> <input class="form-check-input audit-toggle" type="checkbox" role="switch" id="${id}" data-check="${name}" value="${id}"> <label class="form-check-label audit-toggle" for="${id}">${name}</label> </div> `; }); // Add toggle-all logic document.getElementById('toggleAllChecks').addEventListener('change', function () { const checked = this.checked; document.querySelectorAll('#auditCheckToggles .form-check-input').forEach(sw => { sw.checked = checked; }); }); // Add per-toggle sync logic setupIndividualSwitchSync(); }
// Sync toggle-all state if a single toggle is changed function setupIndividualSwitchSync() { const allSwitches = document.querySelectorAll('#auditCheckToggles .form-check-input'); allSwitches.forEach(sw => { sw.addEventListener('change', () => { const allChecked = Array.from(allSwitches).every(input => input.checked); document.getElementById('toggleAllChecks').checked = allChecked; }); }); } // Call once on load renderAuditSwitches(tests); const limits = { 'Header Status': 1, 'Meta Charset': 1, 'HTML Lang': 1, 'Meta Viewport': 1, 'Link Profile': 1, 'Favicon Link': 1, 'Preconnect Google Fonts': 1, 'Shortlink Link': 0, 'EditURI Link': 0, 'API Link': 0, 'Hreflang Link': 1, 'RSS Link': 0, 'JavaScript Type': 5, 'Empty Meta Tags': 0, 'Meta Title Tag': 60, 'Meta Description': 160, 'Top Keywords': 30, 'H1 Headings': 70, 'H2 Headings': 90, 'H3 Headings': 90, 'H4 Headings': 90, 'H5 Headings': 90, 'H6 Headings': 90, 'Paragraphs': 300, 'Spans': 300, 'Unorered UL/LI List': 100, 'Internal Links': 150, 'External Links': 150, 'HTTP Links': 50, 'Image ALT Attributes': 100, 'Canonical Tag': 1, 'OpenGraph Meta': 10, 'Robots Meta Tag': 1, 'JSON-LD Schema Markup': 20, 'Technologies Detected': 1, 'Google PageSpeed Score': 1, 'XML Sitemap Index and URLs': 1, 'robots.txt Check': 1 }; const auditItems = [ 'Header Status', 'Meta Charset', 'HTML Lang', 'Meta Viewport', 'Link Profile', 'Favicon Link', 'Preconnect Google Fonts', 'Shortlink Link', 'EditURI Link', 'API Link', 'Hreflang Link', 'RSS Link', 'JavaScript Type', 'Empty Meta Tags', 'Meta Title Tag', 'Meta Description', 'Top Keywords', 'H1 Headings', 'H2 Headings', 'H3 Headings', 'H4 Headings', 'H5 Headings', 'H6 Headings', 'Paragraphs', 'Spans', 'Unorered UL/LI List', 'Internal Links', 'External Links', 'HTTP Links', 'Image ALT Attributes', 'Canonical Tag', 'OpenGraph Meta', 'Robots Meta Tag', 'JSON-LD Schema Markup', 'Technologies Detected', 'Google PageSpeed Score', 'XML Sitemap Index and URLs', 'robots.txt Check' ]; const toggleBtn = document.getElementById('toggleAllAccordionsBtn'); const auditContainer = document.getElementById('auditResults');
toggleBtn.addEventListener('click', () => { const accordions = auditContainer.querySelectorAll('.accordion-box'); const anyOpen = Array.from(accordions).some(acc => acc.querySelector('.accordion-content')?.style.display === 'block');
accordions.forEach(acc => { const content = acc.querySelector('.accordion-content'); const header = acc.querySelector('.accordion-header'); if (content && header) { const isOpen = content.style.display === 'block'; if (anyOpen && isOpen) { toggleAccordion(header); // Close } else if (!anyOpen && !isOpen) { toggleAccordion(header); // Open } } });
swapToggleIcon(anyOpen); });
function swapToggleIcon(anyOpen) { const icon = toggleBtn.querySelector('i'); icon.classList.add('icon-fade-out'); setTimeout(() => { icon.className = `bi icon-state ${anyOpen ? 'bi-caret-down-square-fill' : 'bi-x-lg'}`; icon.classList.remove('icon-fade-out'); }, 150); }
function toggleAccordion(headerEl) { const content = headerEl.nextElementSibling; const isOpen = headerEl.classList.toggle('open'); content.style.display = isOpen ? 'block' : 'none'; } // Replace button with loader
// Restore button // function restoreButton() { // const buttonHTML = ` // <button class="btn btn-sm btn-dark text-white lh-1 me-1" onclick="clearAuditInputSearch()" title="Clear Audit Search Input Field"><i class="bi bi-x-lg"></i></button> // <button class="btn btn-sm btn-dark text-white lh-1 me-1" onclick="runAudit()" title="Perform Technical SEO and Website Audit"><i class="bi bi-play"></i></button> // `; // document.getElementById('auditButtonContainer').innerHTML = buttonHTML; // }
// Sequential text animation // function runAuditWithAnimation() { // loadBadKeywords(); // // const box = document.getElementById('auditResultsBox'); // const bar = document.getElementById('progressBar'); // box.innerHTML = ''; // bar.style.width = '0%'; // let i = 0; // // } window.auditLogs = [];
function logMessage(type, message) { window.auditLogs.push({ type, message });
const logConsole = document.getElementById('audit-log-console'); if (logConsole) { const li = document.createElement('li'); li.textContent = `${type.toUpperCase()}: ${message}`; li.className = `log-${type}`; logConsole.appendChild(li); } }
// Toggle open/close console document.getElementById('toggle-console').addEventListener('click', () => { document.getElementById('audit-console').classList.toggle('show'); } );
function validateUrlInput(url) { const alertValid = document.getElementById('alertValidUrl');
if (!url || !isValidHttpUrl(url)) { alertValid.textContent = 'Please enter a valid URL.'; alertValid.classList.remove('d-none'); return false; }
alertValid.classList.add('d-none'); return true; } // Helper function to validate HTTP/HTTPS URLs function isValidHttpUrl(string) { try { const url = new URL(string); return url.protocol === "http:" || url.protocol === "https:"; } catch (_) { return false; } } function updateSelectedAuditList() { const selected = Array.from(document.querySelectorAll('#auditCheckToggles .form-check-input:checked')); const ul = document.getElementById('selectedAuditList'); ul.innerHTML = '';
selected.forEach(input => { const label = document.querySelector(`label[for="${input.id}"]`); if (label) { const li = document.createElement('li'); li.textContent = label.textContent.trim(); ul.appendChild(li); } } ); }
// Call once on load renderAuditSwitches(tests); document.querySelectorAll('#auditCheckToggles .form-check-input').forEach(input => { input.addEventListener('change', updateSelectedAuditList); } ); async function runAudit() { let auditItems = [];
// const selectedTests = Array.from(document.querySelectorAll('.audit-toggle:checked')).map(cb => cb.value); const selectedTests = Array.from(document.querySelectorAll('.audit-toggle:checked')).map(cb => ({ key: cb.value, label: cb.dataset.check })); if (selectedTests.length === 0) { document.getElementById('needSeoSetup').innerHTML = ` <div id="seoSetupMessage" class="col-12 alert alert-danger mb-2 mt-2"> ⚠️ Please select at least one audit check to run. Open Main SEO Settings <button class="btn btn-sm btn-gear seo-button" type="button" data-bs-toggle="collapse" data-bs-target="#seoAuditMainSettings" aria-expanded="false" aria-controls="seoAuditMainSettings"> Open Main SEO Settings </button> </div> `;
const setupBtn = document.querySelector('.seo-button'); if (setupBtn) setupBtn.classList.add('need-setup'), setupBtn.classList.remove('text-dark');
return; // stop the audit } else { document.getElementById('needSeoSetup').innerHTML = ''; const setupBtn = document.querySelector('.seo-button'); if (setupBtn) setupBtn.classList.remove('need-setup'), setupBtn.classList.add('text-dark');
} auditItems = (selectedTests); // ✅ Sync globally index = 0; showNextItem(selectedTests); // ✅ Show only selected const url = document.getElementById('urlInput').value.trim(); if (!validateUrlInput(url)) return;
const parsedSchemas = []; // ✅ Declare once here const latestResults = []; // Loader for sidebar const sidebarDiv = document.getElementById('jsonLdSidebarDisplay');
const filterAuditResults = document.getElementById('auditResultsFilterSection'); const scoreSummary = document.getElementById('scoreSummary'); const resultDivItems = document.getElementById('auditResultsBox'); progressBar.style.width = '0%'; auditResults.style.opacity = 0; resultDivItems.style.display = 'block'; filterAuditResults.style.opacity = 0; filterAuditResults.style.display = 'none'; setTimeout( () => { //resultDiv.style.transition = "opacity 2s"; //resultDiv.style.opacity = 1; progressBar.style.width = '100%'; progressBar.setAttribute('aria-valuenow', 100); auditResults.style.transition = "opacity 2s"; auditResults.style.opacity = 1; filterAuditResults.style.transition = "opacity 2s"; filterAuditResults.style.opacity = 1; filterAuditResults.style.display = 'flex'; // displayNavAverageSeoScoreResults.style.display = 'flex'; // displayNavAverageSeoScoreResults.style.opacity = '1'; // displayNavAverageSeoScoreResults.style.transition = "opacity 2s"; //displayGeneralAccordionResults.style.transition = "opacity 2s"; //displayGeneralAccordionResults.style.opacity = 1; } , 500);
function showNextItem(selectedTests) { if (index < selectedTests.length) { const label = selectedTests[index].label;
const text = ` <div class="alert alert-success p-2" role="alert"> <h3 class="alert-heading show-tags">Be patient, we are getting info for you!</h3> <p class="small mb-0">${label} <span class="badge"><i class="bi bi-code"></i></span></p> <div class="d-flex align-items-center"> <strong role="status">Loading...</strong> <div class="spinner-border ms-auto" aria-hidden="true"></div> </div> </div> `;
setTimeout( () => { resultDivItems.innerHTML = text; resultDivItems.style.transition = "opacity 1s"; resultDivItems.style.opacity = 1;
const progress = Math.min(((index + 1) / selectedTests.length) * 100, 100); progressBar.style.width = progress + '%'; progressBar.setAttribute('aria-valuenow', progress);
index++; setTimeout( () => { showNextItem(selectedTests); } , 500); } , 500); } else { setTimeout( () => { resultDivItems.style.display = 'none'; resultDivItems.style.transition = "display 6s"; resultDivItems.style.opacity = 1; } , 500); } }
auditResults.innerHTML = ''; latestResults.length = 0;
let passed = 0, warning = 0, failed = 0, total = 0; total = tests.length;
try { const htmlString = await fetchUntilSuccess(url); // Keep retrying every 10s until successful const parser = new DOMParser(); const doc = parser.parseFromString(htmlString, 'text/html'); function getTextFromTags(doc) { const selectors = ['title', 'meta[name="description"]', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'ul li', 'img'];
let fullText = '';
selectors.forEach(selector => { const elements = doc.querySelectorAll(selector); elements.forEach(el => { if (selector === 'meta[name="description"]') { fullText += ' ' + (el.getAttribute('content') || ''); } else if (selector === 'img') { fullText += ' ' + (el.getAttribute('alt') || ''); } else { fullText += ' ' + (el.textContent || ''); } } ); } );
return fullText; } function getTopKeywords(text, topN=30) { const words = text.toLowerCase().replace(/[^\w\s]/g, '')// Remove punctuation .split(/\s+/).filter(word => word.length > 2 && !stopWords.includes(word)); // Filter short/common words
const frequency = {}; for (const word of words) { frequency[word] = (frequency[word] || 0) + 1; }
const sorted = Object.entries(frequency).sort( (a, b) => b[1] - a[1]).slice(0, topN);
return sorted; } const stopWords = ['the', 'and', 'you', 'for', 'are', 'but', 'with', 'this', 'that', 'was', 'your', 'have', 'not', 'from', 'they', 'all', 'can', 'will', 'has', 'more', 'one', 'their', 'about', 'when', 'what', 'which', 'how']; const auditTextContent = getTextFromTags(doc); const topKeywords = getTopKeywords(auditTextContent); const badKeyWordsDatabase = ['click here', 'free', 'buy now', 'cheap', 'guaranteed', 'limited time', 'best', 'amazing', 'act now', 'instant', 'winner', 'earn money', 'credit card', 'risk free', 'easy', 'double your', 'extra cash', 'urgent', 'win', 'miracle', 'income', 'trial', 'special promotion', 'cash bonus', 'unsubscribe', 'discount', 'congratulations', 'no obligation', 'fast cash', 'work from home', 'order now', 'be your own boss', 'save big', 'apply now', 'satisfaction guaranteed', 'lose weight', 'free info', 'get paid', 'buy direct', 'unsecured debt', 'online biz opportunity', 'no catch', 'get it now', 'you are a winner', 'promise', 'pre-approved', 'luxury', 'hidden charges', 'bargain', 'extra income']; const checks = { 'Header Status': async () => { let status = 'Unknown'; let statusText = ''; let redirectChain = []; let responseHeaders = ''; let finalURL = url;
try { const response = await fetch(url, { method: 'GET', redirect: 'manual' }); let currentResponse = response; let currentURL = url; let redirectCount = 0;
// Follow redirects manually to track the chain while (currentResponse.status >= 300 && currentResponse.status < 400 && redirectCount < 10) { const location = currentResponse.headers.get('Location'); if (!location) break; const nextURL = new URL(location, currentURL).href; redirectChain.push({ status: currentResponse.status, url: currentURL }); currentURL = nextURL; currentResponse = await fetch(currentURL, { method: 'GET', redirect: 'manual' }); redirectCount++; }
finalURL = currentURL; status = currentResponse.status; statusText = currentResponse.statusText || '';
// Capture response headers responseHeaders = [...currentResponse.headers.entries()].map(([key, val]) => `<li><strong>${key}:</strong> ${val}</li>`).join('');
// Add final response redirectChain.push({ status, url: finalURL });
} catch (err) { console.log(`Failed to fetch ${url}`, err); status = 'Fetch Failed'; statusText = err.message; }
const displayText = ` <ul> <li><strong>Input URL:</strong> <a href="${url}" target="_blank">${url}</a></li> <li><strong>Final URL:</strong> <a href="${finalURL}" target="_blank">${finalURL}</a></li> <li><strong>Final Status:</strong> ${status} ${statusText}</li> </ul> ${redirectChain.length > 1 ? ` <details> <summary>Redirect Chain (${redirectChain.length - 1} redirect${redirectChain.length > 2 ? 's' : ''})</summary> <ul> ${redirectChain.map(r => `<li>${r.status} → <a href="${r.url}" target="_blank">${r.url}</a></li>`).join('')} </ul> </details>` : '' } <details> <summary>View Response Headers</summary> <ul>${responseHeaders}</ul> </details> `;
const valid = status === 200; return [null, 1, displayText, displayText, valid]; }, 'Meta Charset': () => { const el = doc.querySelector('meta[charset]'); const charset = el?.getAttribute('charset')?.trim() || ''; const count = el ? 1 : 0;
const valid = charset.toLowerCase() === 'utf-8'; if (!charset) { logMessage('error', 'No Meta Charset Tag Found.'); } else if (!valid) { logMessage('warning', `Meta Charset should be UTF-8, found: ${charset}`); } else { logMessage('pass', `Meta Charset is valid (UTF-8).`); }
let displayText = ''; if (!charset) { displayText = `<ul><li>❌ No Meta Charset Tag Found.</li></ul>`; } else { displayText = `<ul>`; displayText += `<li>🏷️ <strong>Meta Charset:</strong><br><em>${charset}</em></li>`; displayText += `<li>🔢 <strong>Length:</strong> ${count} characters</li>`; if (!valid) { displayText += `<li>⚠️ Charset should be <strong>UTF-8</strong>, found: <strong>${charset}</strong></li>`; } displayText += `</ul>`; }
return [el, count, charset, displayText, valid]; }, 'HTML Lang': () => { const el = doc.querySelector('html'); const lang = el?.getAttribute('lang')?.trim() || ''; const prefix = el?.getAttribute('prefix')?.trim() || ''; const count = lang.length;
const validLang = lang === 'en-US'; const validPrefix = prefix === 'og: https://ogp.me/ns#';
if (!lang) { logMessage('error', 'No Lang attribute Found.'); } else if (!validLang) { logMessage('warning', `Invalid Lang attribute be UTF-8, found: ${lang}`); } else { logMessage('pass', `Lang attribute is valid ${lang}.`); }
let displayText = `<ul>`; if (!lang || !validLang) { displayText += `<li>❌ Invalid or missing 'lang' attribute. Expected: 'en-US', found: ${lang || 'None'}</li>`; } if (prefix && !validPrefix) { displayText += `<li>⚠️ Invalid 'prefix' attribute. Expected: 'og: https://ogp.me/ns#', found: ${prefix}</li>`; } displayText += `</ul>`;
return [el, count, lang, prefix, displayText, validLang && validPrefix]; }, 'Meta Viewport': () => { const el = doc.querySelector('meta[name="viewport"]'); const content = el?.getAttribute('content')?.trim() || ''; const count = el ? 1 : 0; const valid = content && content.includes('width=device-width') && content.includes('initial-scale=1');
if (!el) { logMessage('error', 'No Meta Viewport Tag Found.'); } else if (!valid) { logMessage('warning', `The Meta Viewport tag should include 'width=device-width' and 'initial-scale=1'`); } else { logMessage('pass', `Meta Viewport valid.`); }
let displayText = ''; if (!el) { displayText = `<ul><li>❌ No Meta Viewport Tag Found.</li></ul>`; } else { displayText = `<ul>`; displayText += `<li>📄 <strong>Meta Viewport:</strong><br><em>${content}</em></li>`; if (!valid) { displayText += `<li>⚠️ The Meta Viewport tag should include 'width=device-width' and 'initial-scale=1'.</li>`; } displayText += `</ul>`; }
return [el, count, content, displayText, valid]; }, 'Link Profile': () => { const el = doc.querySelector('link[rel="profile"]'); const href = el?.getAttribute('href')?.trim() || ''; const count = els.length; const valid = href === 'https://gmpg.org/xfn/11';
let displayText = ''; if (!el) { displayText = `<ul><li>❌ No Link Profile Tag Found.</li></ul>`; } else { displayText = `<ul>`; displayText += `<li>📄 <strong>Link Profile:</strong><br><em>${href}</em></li>`; if (!valid) { displayText += `<li>⚠️ Invalid href in Link Profile. Expected: 'https://gmpg.org/xfn/11', found: ${href}</li>`; } displayText += `</ul>`; }
return [el, count, href, displayText, valid]; }, 'Favicon Link': () => { const results = [];
const checks = [{ selector: 'link[rel="shortcut icon"][href$="favicon.ico"], link[rel="icon"][href$="favicon.ico"]', label: 'favicon.ico', }, { selector: 'link[rel="apple-touch-icon"][href$="apple-touch-icon.png"]', label: 'apple-touch-icon.png', }, { selector: 'link[rel="icon"][href$="favicon-32x32.png"][sizes="32x32"][type="image/png"]', label: 'favicon-32x32.png', }, { selector: 'link[rel="icon"][href$="favicon-16x16.png"][sizes="16x16"][type="image/png"]', label: 'favicon-16x16.png', }];
let count = 0; let valid = true; let els = [];
for (const check of checks) { const el = doc.querySelector(check.selector); if (el) { els.push(el); results.push(`<li>✅ Found ${check.label}</li>`); count++; } else { results.push(`<li>⚠️ Missing ${check.label}</li>`); valid = false; } }
const displayText = `<ul>${results.join('')}</ul>`;
return [els, count, '', displayText, valid]; }, 'Preconnect Google Fonts': () => { const el1 = doc.querySelector('link[rel="preconnect"][href="https://fonts.googleapis.com"]'); const el2 = doc.querySelector('link[rel="preconnect"][href="https://fonts.gstatic.com"][crossorigin]'); const valid = el1 && el2; const count = els.length; let displayText = ''; if (!valid) { displayText = `<ul><li>⚠️ Missing preconnect for Google Fonts (https://fonts.googleapis.com and https://fonts.gstatic.com).</li></ul>`; } else { displayText = `<ul><li>✅ Preconnect links for Google Fonts are correctly implemented.</li></ul>`; }
return [el1 && el2, count, displayText, valid]; }, 'Shortlink Link': () => { const els = doc.querySelectorAll('link[rel="shortlink"][href=""]'); const count = els.length;
let displayText = ''; if (count > 0) { displayText = `<ul><li>⚠️ Found ${count} empty shortlink links. These should be removed or corrected.</li></ul>`; } else { displayText = `<ul><li>✅ No empty shortlink links found.</li></ul>`; }
return [els, count, displayText, count === 0]; }, 'EditURI Link': () => { const els = doc.querySelectorAll('link[rel="EditURI"][type="application/rsd+xml"]'); const count = els.length;
let displayText = ''; if (count > 0) { displayText = `<ul><li>⚠️ Found ${count} EditURI links. These should be removed.</li></ul>`; } else { displayText = `<ul><li>✅ No EditURI links found.</li></ul>`; }
return [els, count, displayText, count === 0]; }, 'API Link': () => { const els = doc.querySelectorAll('link[rel="https://api.w.org/"]'); const count = els.length;
let displayText = ''; if (count > 0) { displayText = `<ul><li>⚠️ Found ${count} API links. These should be removed unless specifically needed.</li></ul>`; } else { displayText = `<ul><li>✅ No API links found.</li></ul>`; }
return [els, count, displayText, count === 0]; }, 'Hreflang Link': () => { const els = doc.querySelectorAll('link[rel="alternate"][hreflang]'); const count = els.length;
let displayText = ''; if (count > 0) { displayText = `<ul><li>⚠️ Found ${count} hreflang link tags. Please ensure they are correctly configured.</li></ul>`; } else { displayText = `<ul><li>✅ No hreflang link tags found.</li></ul>`; }
return [els, count, displayText, count === 0]; }, 'RSS Link': () => { const els = doc.querySelectorAll('link[rel="alternate"][type="application/rss+xml"]'); const count = els.length;
let displayText = ''; if (count > 0) { displayText = `<ul><li>⚠️ Found ${count} RSS link tags. These should be removed or adjusted if not in use.</li></ul>`; } else { displayText = `<ul><li>✅ No RSS links found.</li></ul>`; }
return [els, count, displayText, count === 0]; }, 'JavaScript Type': () => { const els = doc.querySelectorAll('script[type="text/javascript"]'); const count = els.length;
let displayText = ''; if (count > 0) { displayText = `<ul><li>⚠️ Found <strong>${count}</strong> script tags with type='text/javascript'. It is recommended to remove the type attribute for modern browsers.</li></ul>`; } else { displayText = `<ul><li>✅ No 'text/javascript' type attributes found in script tags.</li></ul>`; }
return [els, count, displayText, count === 0]; }, 'Empty Meta Tags': () => { const els = doc.querySelectorAll('meta[name=""][content=""]'); const count = els.length;
let displayText = ''; if (count > 0) { displayText = `<ul><li>⚠️ Found empty meta tags:</li>`; els.forEach(el => { displayText += `<li>📄 <strong>Meta Tag:</strong> ${el.outerHTML}</li>`; } ); displayText += `</ul>`; } else { displayText = `<ul><li>✅ No empty meta tags found.</li></ul>`; }
return [els, count, displayText, count === 0]; }, 'Meta Title Tag': () => { const el = doc.querySelector('title'); const title = el?.textContent?.trim() || ''; const count = title.length;
const brandMatch = title.split(/[-|>]/).pop().trim(); const hasBrand = brandMatch && title.includes(brandMatch);
const badWords = checkBadKeywords(title); const valid = !!title && count <= limits['Meta Title Tag (MetaTT)'] && badWords.length === 0 && hasBrand;
if (!title) { logMessage('error', 'No Meta Title Tag (MetaTT) Found.'); } else if (!valid) { logMessage('warning', `Title is too long (limit: ${limits['Meta Title Tag (MetaTT)']} characters)`); } else { logMessage('pass', `Meta Title Tag (MetaTT) Found`); }
let displayText = ''; if (!title) { displayText = `<ul><li>❌ No title found.</li></ul>`; } else { displayText = `<ul>`; displayText += `<li>🏷️ <strong>Title:</strong><br><em>${title}</em></li>`; displayText += `<li>🔢 <strong>Length:</strong> ${count} characters</li>`; if (count > limits['Meta Title Tag (MetaTT)']) { displayText += `<li>⚠️ Title is too long (limit: ${limits['Meta Title Tag (MetaTT)']} characters)</li>`; } if (badWords.length > 0) { displayText += `<li>🚫 Contains bad keywords: <strong>${badWords.join(', ')}</strong></li>`; } if (!hasBrand) { displayText += `<li>❗ Brand "<strong>${brandMatch}</strong>" not found in title</li>`; } displayText += `</ul>`; }
return [el, count, title, displayText, valid]; }, 'Meta Description': () => { const el = doc.querySelector('meta[name="description"]'); const description = el?.getAttribute('content')?.trim() || ''; const count = description.length;
const titleText = (doc.querySelector('title')?.textContent || '').trim(); const brandMatch = titleText.split(/[-|>]/).pop().trim(); const hasBrand = brandMatch && description.includes(brandMatch);
const badWords = checkBadKeywords(description); const valid = !!description && count <= limits['Meta Description'] && badWords.length === 0 && hasBrand;
let displayText = ''; if (!description) { displayText = `<ul><li>❌ No Meta Description (MetaD) Found.</li></ul>`; logMessage('error', `❌ No Meta Description (MetaD) Found`); } else { displayText = `<ul>`; displayText += `<li>📄 <strong>Meta Description (MetaD):</strong><br><em>${description}</em></li>`; displayText += `<li>🔢 <strong>Length:</strong> ${count} characters</li>`; if (count > limits['Meta Description']) { displayText += `<li>⚠️ Description is too long (limit: ${limits['Meta Description']} characters)</li>`; logMessage('warning', `⚠️ Description is too long (limit: ${limits['Meta Description']} characters)`); } if (badWords.length > 0) { displayText += `<li>🚫 Contains bad keywords: <strong>${badWords.join(', ')}</strong></li>`; logMessage('warning', `🚫 Contains bad keywords: <strong>${badWords.join(', ')}</strong>`); } if (!hasBrand) { displayText += `<li>❗ Brand "<strong>${brandMatch}</strong>" not found in the Meta Description (MetaD)</li>`; logMessage('error', `❗ Brand "<strong>${brandMatch}</strong>" not found in the Meta Description (MetaD)`); } displayText += `</ul>`; }
return [el, count, description, displayText, valid]; }, 'Top Keywords': () => { const elements = [];
// Gather text content from relevant tags const selectors = ['title', 'meta[name="description"]', 'h1,h2,h3,h4,h5,h6', 'p', 'span', 'ul li', 'img[alt]']; let allTextContent = '';
selectors.forEach(sel => { const nodes = doc.querySelectorAll(sel); elements.push(...nodes);
nodes.forEach(node => { if (node.tagName === 'META') { allTextContent += ' ' + node.getAttribute('content') || ''; } else if (node.tagName === 'IMG') { allTextContent += ' ' + node.getAttribute('alt') || ''; } else { allTextContent += ' ' + node.textContent; } } ); } );
// Keyword frequency count const wordCounts = {}; const words = allTextContent.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).filter(w => w.length > 2); // Filter short/stop words here if needed
words.forEach(word => { wordCounts[word] = (wordCounts[word] || 0) + 1; } );
const sortedKeywords = Object.entries(wordCounts).sort( (a, b) => b[1] - a[1]).slice(0, 30);
const count = sortedKeywords.length; const content = sortedKeywords.map( ([word,freq]) => `${word}: ${freq}`).join('\n'); const valid = count > 0;
let htmlOutput = '<ol class="keyword-results">'; sortedKeywords.forEach( ([word,freq]) => { htmlOutput += `<li><strong>${word}</strong>: ${freq}</li>`; } ); htmlOutput += '</ol>';
return [elements, count, content, htmlOutput, valid]; }, 'H1 Headings': () => { const els = doc.querySelectorAll('h1'); const count = els.length; const content = Array.from(els).map(e => e.textContent.trim()).filter(Boolean);
const limit = limits['H1 Heading']; // Defined elsewhere in your config let displayText = '<ul>';
if (!count) { displayText += `<li>❌ No H1 tags found.</li>`; logMessage(`🔴 No H1 tags found.`); } else { content.forEach( (text, i) => { const words = text.toLowerCase().split(/\s+/); const hasStopWord = words.some(word => stopWords.includes(word)); const hasBadWord = words.some(word => badKeyWordsDatabase.includes(word));
const statusIcon = hasBadWord ? 'Errors 🔴' : hasStopWord ? 'Issues 🟠' : 'Ok 🟢'; const statusText = hasBadWord ? 'Contains banned or harmful keywords' : hasStopWord ? 'Contains common/stop keywords' : 'Clean – no issues detected'; logMessage(`🟠 H1 Issues found.`); displayText += ` <li> <strong>H1 ${i + 1}</strong> <span>${text}</span><br> <span>Status: ${statusIcon} ${statusText}</span> </li> `; } ); }
displayText += `<li><strong>Total H1 tags:</strong> ${count}</li></ul>`;
const valid = count > 0 && content.every(h => h.length <= limit);
return [els, count, content.join('\n'), displayText, valid]; }, 'H2 Headings': () => { const els = doc.querySelectorAll('h2'); const count = els.length; const content = Array.from(els).map(e => e.textContent.trim()).filter(Boolean); const limit = limits['H2 Headings'];
const results = content.map(text => { const length = text.length; const isValid = length <= limit; return { text, length, isValid, }; } );
const valid = results.every(r => r.isValid);
let displayText = ''; if (!count) { displayText = `<ul><li>❌ No <i class="bi bi-type-h2"></i> Headings found.</li></ul>`; } else { displayText = `<ul>`; results.forEach( (r, i) => { const status = r.isValid ? '✅' : '❌'; displayText += `<li>${status} <strong><i class="bi bi-type-h2"></i> #${i + 1}:</strong> ${r.text} <span class="text-muted">(${r.length} chars)</span></li>`; } ); if (!valid) { displayText += `<li>⚠️ One or more <i class="bi bi-type-h2"></i> headings exceed the limit of ${limit} characters.</li>`; } displayText += `<li><strong>Total <i class="bi bi-type-h2"></i> Headings:</strong> ${count}</li>`; displayText += `</ul>`; }
return [els.length > 0 ? els : null, count, content.join('\n'), displayText, valid]; }, 'H3 Headings': () => { const els = doc.querySelectorAll('h3'); const count = els.length; const content = Array.from(els).map(e => e.textContent.trim()); const valid = content.every(h => h.length <= limits['H3 Headings']); return [els.length > 0 ? els : null, count, content.join('\n'), valid]; }, 'H4 Headings': () => { const els = doc.querySelectorAll('h4'); const count = els.length; const content = Array.from(els).map(e => e.textContent.trim()); const valid = content.every(h => h.length <= limits['H4 Headings']); return [els.length > 0 ? els : null, count, content.join('\n'), valid]; }, 'H5 Headings': () => { const els = doc.querySelectorAll('h5'); const count = els.length; const content = Array.from(els).map(e => e.textContent.trim()); const valid = content.every(h => h.length <= limits['H5 Headings']); return [els.length > 0 ? els : null, count, content.join('\n'), valid]; }, 'H6 Headings': () => { const els = doc.querySelectorAll('h6'); const count = els.length; const content = Array.from(els).map(e => e.textContent.trim()); const valid = content.every(h => h.length <= limits['H6 Headings']); return [els.length > 0 ? els : null, count, content.join('\n'), valid]; }, 'Paragraphs': () => { const els = doc.querySelectorAll('p'); const count = els.length; const content = Array.from(els).map(e => e.textContent.trim()).filter(Boolean); const valid = count > 0;
let displayText = ''; if (!count) { displayText = `<ul><li>❌ No paragraphs found.</li></ul>`; } else { displayText = `<ul>`; content.forEach( (text, i) => { displayText += `<li><i class="bi bi-paragraph"></i> <strong>Paragraph ${i + 1}:</strong><br><em>${text}</em></li>`; } ); displayText += `<li><strong><i class="bi bi-paragraph"></i> Total paragraphs:</strong> ${count}</li>`; displayText += `</ul>`; }
return [els, count, content.join('\n'), displayText, valid]; }, 'Unorered UL/LI List': () => { const els = doc.querySelectorAll('ul li'); const count = els.length; const content = Array.from(els).map(e => e.textContent.trim()).filter(Boolean); const valid = count > 0;
let displayText = ''; if (!count) { displayText = `<ul><li>❌ No <i class="bi bi-list-ul"></i> <code><ul><li></code> items found.</li></ul>`; } else { displayText = `<ul>`; content.forEach( (text, i) => { displayText += `<li><i class="bi bi-list-ul"></i> <strong>List item ${i + 1}:</strong> ${text}</li>`; } ); displayText += `<li><strong><i class="bi bi-list-ul"></i> Total list items:</strong> ${count}</li>`; displayText += `</ul>`; }
return [els, count, content.join('\n'), displayText, valid]; }, 'Internal Links': async () => { const hostname = new URL(url).hostname; const els = doc.querySelectorAll('a[href^="/"], a[href*="' + hostname + '"]'); const limit = limits['Internal Links']; const count = els.length; let modalTableRows = '';
const results = await Promise.all(Array.from(els).map(async e => { const href = e.getAttribute('href'); const fullUrl = href.startsWith('/') ? new URL(href,url).href : href;
let metaTitle = 'N/A' , metaDesc = 'N/A' , robots = 'N/A'; let h1 = 'N/A' , h2 = 'N/A' , h3 = 'N/A' , status = 'Fetch failed';
try { const html = await fetchUntilSuccess(fullUrl); const parser = new DOMParser(); const tempDoc = parser.parseFromString(html, 'text/html');
status = '200 OK'; metaTitle = tempDoc.querySelector('title')?.innerText || 'Not found'; metaDesc = tempDoc.querySelector('meta[name="description"]')?.content || 'Not found'; robots = tempDoc.querySelector('meta[name="robots"]')?.content || 'Not found'; h1 = tempDoc.querySelector('h1')?.innerText || 'Not found'; h2 = tempDoc.querySelector('h2')?.innerText || 'Not found'; h3 = tempDoc.querySelector('h3')?.innerText || 'Not found';
} catch (err) { console.warn(`Failed to fetch ${fullUrl}`, err); }
modalTableRows += `<tr> <td><a href="${fullUrl}" target="_blank">${fullUrl}</a></td> <td>${status}</td> <td>${metaTitle}</td> <td>${metaDesc}</td> <td>${robots}</td> <td>${h1}</td> <td>${h2}</td> <td>${h3}</td> </tr>`;
return { text: e.textContent.trim(), href: fullUrl, status, metaTitle, metaDesc, robots, h1, h2, h3 }; } ));
// Add modal button and structure const modalId = 'internalLinksModal'; const modalTriggerBtn = `<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#${modalId}">View Table of Internal Links</button>`; const modalHTML = ` <div class="modal fade" id="${modalId}" tabindex="-1" aria-labelledby="${modalId}Label" aria-hidden="true"> <div class="modal-dialog modal-xl modal-dialog-scrollable"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="${modalId}Label">Internal Links Table</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <div class="table-responsive"> <table class="table table-bordered table-sm"> <thead> <tr> <th>Href</th> <th>Status</th> <th>Meta Title</th> <th>Meta Description</th> <th>Meta Robots</th> <th>H1</th> <th>H2</th> <th>H3</th> </tr> </thead> <tbody> ${modalTableRows} </tbody> </table> </div> </div> </div> </div> </div>`;
const displayText = ` <span>Total Internal Links on the Page: <strong>${results.length}</strong></span><br> ${modalTriggerBtn} <details> <summary>View Internal Links List</summary> <ul> ${results.map(r => ` <li> ${r.text} <a href="${r.href}" target="_blank">${r.href}</a> <button class="btn btn-sm btn-link" onclick="this.nextElementSibling.hidden = !this.nextElementSibling.hidden"> <i class="bi bi-chevron-down"></i> </button> <div hidden> <ul> <li><strong>Status:</strong> ${r.status}</li> <li><strong>Meta Title:</strong> ${r.metaTitle}</li> <li><strong>Meta Description:</strong> ${r.metaDesc}</li> <li><strong>Meta Robots:</strong> ${r.robots}</li> <li><strong>H1:</strong> ${r.h1}</li> <li><strong>H2:</strong> ${r.h2}</li> <li><strong>H3:</strong> ${r.h3}</li> </ul> </div> </li> `).join('')} </ul> </details> ${modalHTML} `;
const valid = results.length <= limit; return [els.length > 0 ? els : null, count, displayText, valid]; }, 'External Links': () => { const hostname = new URL(url).hostname; const els = doc.querySelectorAll('a[href^="http"]'); const externalLinks = Array.from(els).filter(el => !el.href.includes(hostname)); const limit = limits['External Links']; const count = externalLinks.length; const displayText = ` <span>Total External Links: <strong>${externalLinks.length}</strong></span> <details> <summary>View Links</summary> <ul> ${externalLinks.map(link => `<li><a href="${link.href}" target="_blank">${link.textContent}</a></li>`).join('')} </ul> </details> `;
const valid = externalLinks.length <= limit; return [els, count, externalLinks.length > 0 ? externalLinks : null, displayText, valid]; }, 'OpenGraph Meta': () => { const els = doc.querySelectorAll('meta[property^="og:"]'); const count = els.length; const content = Array.from(els).map((e, i) => `<div class="sl"><span class="line-number">${i + 1}</span> <span class="prop">${e.getAttribute('property')}</span>: <span class="val">${e.getAttribute('content')}</span></div>` ); const valid = count > 0;
const displayText = ` <div class="display-og-meta"> <ul> <li onclick="copyOgMeta(this)">📋 <strong style="cursor: pointer;">Copy</strong> <i class="bi bi-copy ms-1"></i></li> <li onclick="toggleOgCode(this)">🔽 <strong style="cursor: pointer;">Toggle Code View</strong></li> </ul> </div> <ul class="og-meta-code-container p-2 bg-dark text-light border rounded small" style="font-family: monospace;white-space: pre-wrap;height: auto;overflow: auto;resize: vertical;max-height: 800px;"><li><div class="og-meta-code-view"> ${content.join('<br>')} </li></ul></div> `;
return [els, count, content.map(line => line.replace(/<[^>]*>?/gm, '')).join('\n'), displayText, valid]; }, 'Robots Meta Tag': () => { const el = doc.querySelector('meta[name="robots"]'); const count = el ? 1 : 0; const content = el ? el.getAttribute('content') : 'No meta robots tag'; const valid = !/noindex/i.test(content || ''); return [el, count, content, valid]; }, 'Canonical Tag': () => { const el = doc.querySelector('link[rel="canonical"]'); const count = el ? 1 : 0; const content = el ? el.href : 'Missing canonical tag'; const valid = !!el; return [el, count, content, valid]; }, 'Image ALT Attributes': () => { const els = doc.querySelectorAll('img');
const content = Array.from(els).map(e => ({ src: e.getAttribute('src') || '', alt: e.getAttribute('alt') || '(no alt)' })); const count = content.length; const valid = content.every(img => img.alt && img.alt.length > 0);
let displayText = ''; if (!count) { displayText = `<ul><li>❌ No images found.</li></ul>`; } else { displayText = `<ul>`; displayText += `<li><i class="bi bi-image"></i> <strong>Total images:</strong> ${count}</li>`; content.forEach( (img, i) => { displayText += ` <li class="single-alt-image">
<span class="col-auto"><img class="image-alt-icons" src="${img.src}" width="48" height="48" alt="${img.alt}" title="${img.alt}"/></span>
<span class="col-auto"><i class="bi bi-image"></i> ${i + 1}:</span> <span class="col-auto"><i class="bi bi-link-45deg"></i> <code>${img.src}</code></span> <span class="col-auto"><i class="bi bi-file-richtext"></i>${img.alt}</span>
</li>`; } ); if (!valid) { displayText += `<li>⚠️ Some Images are with Missing Alt <code>Text</code> text.</li>`; } displayText += `</ul>`; }
return [els.length > 0 ? els : null, count, content.map(c => c.alt).join('\n'), displayText, valid]; }, 'JSON-LD Schema Markup': () => { const els = doc.querySelectorAll('script[type="application/ld+json"]'); const count = els.length;
const parsedSchemas = []; const content = Array.from(els).map(e => { try { const parsed = JSON.parse(e.textContent); parsedSchemas.push(parsed); return JSON.stringify(parsed, null, 2); } catch { return 'Invalid JSON-LD'; } } );
const valid = count > 0 && content.every(c => c !== 'Invalid JSON-LD');
let displayText = '';
if (!count) { displayText = `<ul><li>❌ No JSON-LD found.</li></ul>`; } else { displayText = ` <div class="display-jsons"> <ul> <li>📋 <strong>Copy</strong> <i class="bi bi-copy"></i></li> <li>🧪 <strong>Check Validation</strong></li> <li><i class="bi bi-filetype-json"></i> <a href="https://validator.schema.org/" target="_blank">Schema.org Validator</a></li> <li><i class="bi bi-google"></i> <a href="https://search.google.com/test/rich-results" target="_blank">Google Rich Results Test</a></li> </ul> </div> <ul class="json-code-boxes">`;
content.forEach( (text, i) => { displayText += ` <li> <i class="bi bi-braces json-icon"></i> <strong>JSON-LD ${i + 1}:</strong><br> <div class="jsons-code-view">${text}</div> </li>`; } );
// 🧠 Display simplified summary of types and keys displayText += `<details><summary>🧩 Detected Schema Types and Properties:</summary><li><br>`; // displayText += `<div id="jsonLdSidebarDisplay" class="existingSchemasBlock"><ul>`;
const extractSchemas = (item) => { if (Array.isArray(item)) { item.forEach(extractSchemas); } else if (typeof item === 'object' && item !== null) { const type = item['@type'] || 'Thing'; displayText += '<div class="jsondatadisplaybox">'; displayText += `<li><strong>Type:</strong> ${type}</li>`; const properties = Object.keys(item).filter(k => k !== '@type' && k !== '@context'); if (properties.length) { displayText += `<li>Properties<ul>`; properties.forEach(prop => { const value = item[prop]; let datatype = typeof value; if (Array.isArray(value)) datatype = 'Array'; else if (value && typeof value === 'object' && '@type'in value) datatype = 'Nested Type';
displayText += `<li class="prop-value">${prop}</li>`;
if (datatype === 'Nested Type' || datatype === 'Array') { extractSchemas(value); // Recurse into nested schema } } ); displayText += `</ul></li>`; } } } ;
parsedSchemas.forEach(extractSchemas);
displayText += `</ul></div></li>`; displayText += '</div>'; displayText += '</details>'; displayText += `</ul>`; }
return [els, count, content.join('\n\n'), displayText, valid]; }, 'HTTP Links': () => { const httpLinks = doc.querySelectorAll('a[href^="http://"]'); return [`Found: ${httpLinks.length}`, httpLinks.length === 0]; }, 'Technologies Detected': () => { const htmlContent = doc.documentElement.outerHTML.toLowerCase(); const detections = [];
// Basic CMS/framework detection if (htmlContent.includes('wp-content') || htmlContent.includes('wordpress')) detections.push('WordPress'); if (htmlContent.includes('shopify')) detections.push('Shopify'); if (htmlContent.includes('drupal')) detections.push('Drupal'); if (htmlContent.includes('joomla')) detections.push('Joomla'); if (htmlContent.includes('react')) detections.push('React.js'); if (htmlContent.includes('vue')) detections.push('Vue.js'); if (htmlContent.includes('angular')) detections.push('Angular'); if (htmlContent.includes('next')) detections.push('Next.js'); if (htmlContent.includes('svelte')) detections.push('Svelte');
// CDN & library checks if (htmlContent.includes('cloudflare')) detections.push('Cloudflare CDN'); if (htmlContent.includes('cdn.jsdelivr.net')) detections.push('jsDelivr CDN'); if (htmlContent.includes('cdnjs.cloudflare.com')) detections.push('CDNJS'); if (htmlContent.includes('stackpath.bootstrapcdn.com')) detections.push('Bootstrap CDN');
const count = detections.length; const valid = count > 0; const content = detections.join(', '); const displayText = count ? `<ul>${detections.map(d => `<li>🧩 ${d}</li>`).join('')}</ul>` : '<ul><li>ℹ️ No detectable technologies found.</li></ul>';
return [[], count, content, displayText, valid]; }, 'XML Sitemap Index and URLs': async () => { const sitemapIndexUrl = new URL('/sitemap_index.xml',url).href;
async function parseXML(xmlString) { return new window.DOMParser().parseFromString(xmlString, 'application/xml'); }
async function fetchXMLAndParse(url) { const xmlText = await fetchUntilSuccess(url); return await parseXML(xmlText); }
let sitemapUrls = []; let displayText = ''; let valid = false; let errors = [];
try { const indexDoc = await fetchXMLAndParse(sitemapIndexUrl); sitemapUrls = Array.from(indexDoc.querySelectorAll('sitemap > loc')).map(el => el.textContent.trim());
displayText = `<h4>Total Sitemaps Found: ${sitemapUrls.length}</h4><ul class="sitemap-index-list">`;
for (const [i,sitemapUrl] of sitemapUrls.entries()) { await new Promise(res => setTimeout(res, 1000)); // delay between each sitemap fetch try { const sitemapDoc = await fetchXMLAndParse(sitemapUrl); const urls = Array.from(sitemapDoc.querySelectorAll('url'));
const urlList = urls.map(u => { const loc = u.querySelector('loc')?.textContent || 'N/A'; const lastmod = u.querySelector('lastmod')?.textContent || 'N/A'; const image = u.querySelector('image\\:loc')?.textContent || 'No image';
return `<li> <a href="${loc}" target="_blank">${loc}</a><br> <small>Last modified: ${lastmod} | Image: ${image}</small> </li>`; } ).join('');
displayText += ` <li> <details> <summary>Sitemap ${i + 1}: <a href="${sitemapUrl}" target="_blank">${sitemapUrl}</a> <i class="bi bi-chevron-down"></i></summary> <ul>${urlList}</ul> </details> </li>`; } catch (err) { const errorMsg = `❌ Error parsing sitemap: ${sitemapUrl} — ${err.message}`; errors.push(errorMsg); displayText += `<li>${errorMsg}</li>`; console.error(errorMsg); } }
displayText += `</ul>`; valid = sitemapUrls.length > 0; } catch (err) { const errorMsg = `❌ Error fetching index sitemap: ${sitemapIndexUrl} — ${err.message}`; errors.push(errorMsg); console.error(errorMsg); displayText = `<ul><li>${errorMsg}</li></ul>`; }
if (errors.length > 0) { displayText = `<div class="audit-errors"> <h5>Errors Encountered:</h5> <ul>${errors.map(e => `<li>${e}</li>`).join('')}</ul> </div>` + displayText; }
return [sitemapUrls, sitemapUrls.length, displayText, valid]; }, 'robots.txt Check': async () => { const robotsUrl = new URL('/robots.txt',url).href; // const possibleSitemapPaths = ['/sitemap.xml', '/sitemap_index.xml', '/sitemap-index.xml', '/wp-sitemap.xml']; // const possibleSitemapUrls = possibleSitemapPaths.map(path => new URL(path,url).href); const sitemapRegexes = [/sitemap(-|_)?index\.xml$/i, /sitemap(-|_)?\d+(-|_)?index\.xml$/i, /wp-sitemap\.xml$/i, /sitemap\.xml$/i, /sitemaps?(-|_)?\d+(-|_)?sitemap\.xml$/i, /\/.*\/sitemap.*\.xml$/i // for nested folders like /folder/sitemap.xml ];
let displayText = ''; let valid = false;
try { const content = await fetchUntilSuccess(robotsUrl); const lines = content.split('\n').map(line => line.trim()).filter(line => line);
const agents = []; const allows = []; const disallows = []; const sitemaps = [];
lines.forEach(line => { const colonIndex = line.indexOf(':'); if (colonIndex === -1) return; const directive = line.slice(0, colonIndex).trim().toLowerCase(); const value = line.slice(colonIndex + 1).trim(); switch (directive) { case 'user-agent': agents.push(value); break; case 'allow': allows.push(value); break; case 'disallow': disallows.push(value); break; case 'sitemap': sitemaps.push(value); break; } } );
const matchingSitemap = sitemaps.find(s => { return sitemapRegexes.some(regex => regex.test(s)); } );
valid = !!matchingSitemap;
// const matchingSitemap = sitemaps.find(s => possibleSitemapUrls.includes(s)); // valid = !!matchingSitemap;
displayText = ` <div class="robots-info">
${valid ? `<p class="text-success">✅ A Valid XML Sitemap Declared in robots.txt</p>` : `<p class="text-danger">❌ None of the expected XML Sitemap URLs are declared in robots.txt</p>`} <details> <summary class="robots-audit-status-box">Robots.txt Audit Status</summary> <ul class="robots"> <li class="robots-rows-data"><strong>User-Agents:</strong> ${agents.length ? agents.join('<br>') : 'None'}</li> <li class="robots-rows-data"><strong>Allows:</strong> ${allows.length ? allows.join('<br>') : 'None'}</li> <li class="robots-rows-data"><strong>Disallows:</strong> ${disallows.length ? disallows.join('<br>') : 'None'}</li> <li class="robots-rows-data"><strong>Sitemap:</strong><br> ${sitemaps.length ? sitemaps.join('<br>') : 'None'}</li> </ul> </details> <hr> <strong>Robots.txt URL:</strong> <a href="${robotsUrl}" target="_blank">${robotsUrl}</a> <hr> <details> <summary class="robots-view-code-box">View Full robots.txt Content</summary> <pre class="view-code-pre">${content}</pre> </details> </div> `; } catch (error) { displayText = `<ul><li>❌ Failed to fetch robots.txt: ${error.message}</li></ul>`; console.error(`Error fetching robots.txt: ${robotsUrl} — ${err.message}`); displayText = `<ul><li>❌ Failed to fetch or parse robots.txt Page: ${err.message}</li></ul>`; }
return [true, 1, displayText, valid]; } }
checks['Google PageSpeed Score'] = async () => { const mobileScore = await fetchPageSpeedScore(url, 'AIzaSyCgj3SJFQDSoLT473it2G4dbzernnXbOtU', 'mobile'); const desktopScore = await fetchPageSpeedScore(url, 'AIzaSyCgj3SJFQDSoLT473it2G4dbzernnXbOtU', 'desktop');
const count = 2; const content = `Mobile: ${mobileScore}\nDesktop: ${desktopScore}`; const valid = mobileScore >= 50 && desktopScore >= 50;
const htmlOutput = ` <ul> <li>📱 <strong>Mobile Score:</strong> ${mobileScore}</li> <li>💻 <strong>Desktop Score:</strong> ${desktopScore}</li> </ul> <div class="row d-flex align-items-start"> <div class="col-sm-12"><strong>Google PageSpeed Score:</strong></div>
<div class="col-sm-6 gap-1 d-flex align-items-center"> <div class="flex-shrink-1 fs-6 lh-1"><i class="bi bi-phone"></i></div> <div class="badge text-bg-secondary" style="height: 20px;">${mobileScore}%</div> <div class="progress progress w-100" style="height: 20px;"> <div class="progress-bar ${mobileScore >= 90 ? 'bg-success' : mobileScore >= 50 ? 'bg-warning' : 'bg-danger'}" style="width: ${mobileScore}%" role="progressbar" aria-valuenow="${mobileScore}" aria-valuemin="0" aria-valuemax="100"> ${mobileScore}% </div> </div> </div>
<div class="col-sm-6 gap-1 d-flex align-items-center"> <div class="flex-shrink-1 fs-6 lh-1"><i class="bi bi-laptop"></i></div> <div class="badge text-bg-secondary" style="height: 20px;">${desktopScore}%</div> <div class="progress progress w-100" style="height: 20px;"> <div class="progress-bar ${desktopScore >= 90 ? 'bg-success' : desktopScore >= 50 ? 'bg-warning' : 'bg-danger'}" style="width: ${desktopScore}%" role="progressbar" aria-valuenow="${desktopScore}" aria-valuemin="0" aria-valuemax="100"> ${desktopScore}% </div> </div> </div> </div> `;
return [[], count, content, htmlOutput, valid]; }; progressBar.style.width = '100%'; const selectedTests = Array.from(document.querySelectorAll('#auditCheckToggles .form-check-input')).filter(input => input.checked).map(input => input.dataset.check);
// JSON-LD extraction const ldScripts = doc.querySelectorAll('script[type="application/ld+json"]'); ldScripts.forEach(e => { try { const parsed = JSON.parse(e.textContent); parsedSchemas.push(parsed); } catch { } });
for (const test of selectedTests) { //const fn = checks[test]; const fn = checks[test]; if (!fn) continue;
let el, count = 0, text = '', displayText = '', isValid = false;
try { //const result = fn(); const result = await fn(); // <-- now awaits [el,count,text,displayText,isValid] = result.length === 5 ? result : [result[0], result[1], result[2], result[2], result[3]]; // fallback for old format } catch (e) { text = 'Error checking ' + test; displayText = text; isValid = false; }
const t = typeof text === 'string' ? text : ''; const headerClass = isValid ? 'passed' : t.includes('No ') || t.includes('Missing') || t.includes('Error') ? 'failed' : 'warning';
if (headerClass === 'passed') passed++; else if (headerClass === 'warning') warning++; else failed++;
let extraInfo = ''; if ((/^H[1-6]/.test(test) || ['Header Status', 'Meta Charset', 'HTML Lang', 'Meta Viewport', 'Link Profile', 'Favicon Link', 'Preconnect Google Fonts', 'Shortlink Link', 'EditURI Link', 'API Link', 'Hreflang Link', 'RSS Link', 'JavaScript Type', 'Empty Meta Tags', 'Meta Title Tag', 'Meta Description', 'Top Keywords', 'Paragraphs', 'Spans', 'Unorered UL/LI List', 'Internal Links', 'External Links', 'HTTP Links', 'Image ALT Attributes', 'Canonical Tag', 'OpenGraph Meta', 'Robots Meta Tag', 'JSON-LD Schema Markup', 'Technologies Detected', 'Google PageSpeed Score', 'XML Sitemap Index and URLs', 'robots.txt Check'].includes(test)) && count) { extraInfo = `<p><strong>Number of ${test} found:</strong> ${count}</p>`; }
const accordionItem = ` <div class="container p-0 m-0" id="filterSeoAuditResultsData"> <div class="row d-flex results-audit-box"> <div class="col-auto gap-1"> <button class="btn btn-sm btn-accordion-right-stats text-dark" onclick="openStatsData(this)" title="Stats Data"> <i class="bi bi-bar-chart-fill"></i> </button> <button class="btn btn-sm btn-accordion-right-stats text-dark" onclick="openStatsInfoData()" title="Stats Info Data"> <i class="bi bi-info-circle"></i> </button> <button class="btn btn-sm btn-accordion-right-stats text-dark" onclick="openStatsVideoHelp()" title="Stats Video Help"> <i class="bi bi-youtube"></i> </button> </div> <div class="col-10 p-0">
<div class="accordion accordion-box ${headerClass}" id="accordionAuditResults">
<div class="accordion-header ${headerClass}" onclick="toggleAccordion(this)">
<strong class="checks-title me-1">${test}<span class="badge ${headerClass}">${count}</span></strong> <div class="text-end"> <span class="header ${headerClass}"></span> <span class="accordion-caret"><i class="bi bi-caret-down ${headerClass}"></i></span> </div>
</div>
<div class="accordion-content ${headerClass}">
<div class="accordion-content-text">${extraInfo}</div>
<div class="accordion-content-dt">${displayText}</div>
</div>
</div>
</div>
</div> </div>
`; auditResults.innerHTML += accordionItem; latestResults.push({ test, status: headerClass.toUpperCase(), details: text });
} if (parsedSchemas.length > 0) { displayParsedJsonLdSidebar(parsedSchemas); // ✅ now this will work }
// ✅ Trigger schema sidebar after audit is done setTimeout(() => { displayParsedJsonLdSidebar(parsedSchemas); // Custom function below }, 800); // 3 second loader
// calculateSeoScore({ total, passed, warning, failed }); // renderPendingKeywordCharts(); const filterResultsAccordionItems = ` <div id="buttonFilterResultsAccordionItems" onclick="auditResultsOpenClose()" class="filter-res-bottom"><i class="bi bi-caret-down-square-fill"></i></div> <div class="d-flex justify-content-center"> <div class="row col-sm-8 d-flex justify-content-start"> <div id="averageSeoScore" class="col-sm-4 px-0"></div> <div id="googlePageSpeedScore" class="col-sm-7"></div> </div> <div class="col-sm-4 gap-0 d-flex"> <div class="col-sm-12 d-flex align-items-center"> <input type="text" id="searchAuditResults"class="form-control" placeholder="Filter by Audit Parameters Names or Audit Status..." aria-label="Filter by Audit Parameters Names or Audit Status..." onkeyup="filterAuditResults()"> <button class="btn btn-sm btn-gear text-dark" onclick="clearAuditResultsSearch()"> <i class="bi bi-arrow-clockwise"></i> </button> </div> <!-- <div class="col-2 d-flex justify-content-center align-items-center"> <button class="btn btn-sm btn-gear text-dark" onclick="exportToCSV()" title="Export Your SEO Audit in CSV File"><i class="bi bi-filetype-csv"></i></button> <button class="btn btn-sm btn-gear text-dark" onclick="exportToPDF()" title="Export Your SEO Audit in PDF File"><i class="bi bi-filetype-pdf"></i></button> </div>--> </div> </div> `; filterAuditResults.innerHTML += filterResultsAccordionItems;
total = passed + warning + failed; calculateSeoScore({ total, passed, warning, failed });
// Fetch and display PageSpeed scores const mobileScore = await fetchPageSpeedScore(url, 'AIzaSyCgj3SJFQDSoLT473it2G4dbzernnXbOtU', 'mobile'); const desktopScore = await fetchPageSpeedScore(url, 'AIzaSyCgj3SJFQDSoLT473it2G4dbzernnXbOtU', 'desktop');
} catch (e) { console.error(e); auditResults.innerHTML = `<p class="error-message">Error fetching or analyzing the page: ${e.message}</p>`; filterAuditResults.innerHTML = `<p class="error-message">Error fetching or analyzing the page: ${e.message}</p>`; document.getElementById('auditError').innerHTML = ` <div class="alert alert-danger"> ❌ Could not fetch the page even after retries. Please try again later. </div> `;
} }
function showSidebarLoader() { document.getElementById('jsonLdSidebarDisplay').innerHTML = ` <ul class="load"> <li><span role="status" aria-hidden="true" class="spinner-grow spinner-grow-sm text-primary pe-2"></span> Be patient, we are getting info for you!</li> <li><span role="status" aria-hidden="true" class="spinner-grow spinner-grow-sm text-primary pe-2"></span> Loading Audit Data...</li> </ul> `; } function displayParsedJsonLdSidebar(schemas) { showSidebarLoader();
setTimeout(() => { const wrapper = document.getElementById('jsonLdSidebarDisplay'); let html = '<ul class="json-sidebar-list">';
function extract(item) { if (Array.isArray(item)) { item.forEach(extract); } else if (typeof item === 'object' && item !== null) { const type = item['@type'] || 'Thing'; html += `<li><strong>@type:</strong> ${type}</li>`; const props = Object.keys(item).filter(k => k !== '@type' && k !== '@context'); if (props.length) { html += `<details><summary>Properties:</summary><li><ul class="json-sidebar-ul">`; props.forEach(prop => { const value = item[prop]; let datatype = typeof value; if (Array.isArray(value)) datatype = 'Array'; else if (value && typeof value === 'object' && '@type' in value) datatype = 'Nested Type'; html += `<li class="prop-value">${prop}</li>`; if (datatype === 'Nested Type' || datatype === 'Array') { extract(value); } }); html += `</ul></li></details>`; } } }
schemas.forEach(extract); html += '</ul>';
// Replace loader with parsed content, fade-in wrapper.innerHTML = `<div style="display: none;" id="jsonLdSidebarContent">${html}</div>`; $('#jsonLdSidebarContent').fadeIn(1000); // jQuery fadeIn }, 2000); }
// function displayParsedJsonLdSidebar(schemas) { // const sidebar = document.getElementById('jsonLdSidebarDisplay'); // sidebar.innerHTML = ` // <div class="text-center my-3" id="jsonLdLoader"> // <i class="bi bi-arrow-repeat spin"></i> Loading structured data... // </div> // `; // clear loader
// if (!schemas.length) { // sidebar.innerHTML = '<p>No JSON-LD found.</p>'; // return; // }
// setTimeout(() => { // const wrapper = document.getElementById('jsonLdSidebarDisplay'); // let html = '<ul>'; // schemas.forEach(item => { // const types = Array.isArray(item['@type']) ? item['@type'] : [item['@type'] || 'Thing']; // html += '<li>@type(s):<ul>'; // types.forEach(t => html += `<li>${t}</li>`); // html += '</ul></li>';
// const props = Object.keys(item).filter(k => k !== '@type' && k !== '@context'); // if (props.length) { // html += '<li>Properties:<ul>'; // props.forEach(p => html += `<li>${p}</li>`); // html += '</ul></li>'; // } // }); // html += '</ul>'; // // Replace loader with parsed content, fade-in // wrapper.innerHTML = `<div style="display: none;" id="jsonLdSidebarContent">${html}</div>`;
// sidebar.innerHTML = html; // sidebar.style.opacity = 0; // sidebar.style.transition = 'opacity 1s'; // requestAnimationFrame(() => sidebar.style.opacity = 1); // $('#jsonLdSidebarContent').fadeIn(500); // jQuery fadeIn // }, 3000); // }
document.getElementById('resetAuditBtn').addEventListener('click', () => { // Uncheck all audit checkboxes document.querySelectorAll('#auditCheckToggles .form-check-input').forEach(cb => cb.checked = false);
// Clear the URL input // document.getElementById('auditPieChart').value = ''; // Clear the URL input document.getElementById('urlInput').value = '';
const clearSelectedAuditList = document.getElementById('selectedAuditList'); if (clearSelectedAuditList) { clearSelectedAuditList.innerHTML = ''; } // Hide filter section if visible const filterSection = document.getElementById('auditResultsFilterSection'); if (filterSection) { filterSection.style.display = 'none'; }
// Clear results output const auditResults = document.getElementById('auditResults'); if (auditResults) { auditResults.innerHTML = ''; }
// Optionally hide needSeoSetup message const setupMsg = document.getElementById('needSeoSetup'); if (setupMsg) { setupMsg.innerHTML = ''; }
// Reset progress bar if you use one const progressBar = document.getElementById('progressBar'); if (progressBar) { progressBar.style.width = '0%'; progressBar.style.display = 'none'; progressBar.setAttribute('aria-valuenow', '0'); }
console.log('Audit interface reset.'); } ); function calculateSeoScore({total, passed, warning, failed}) { if (total === 0) { document.getElementById('averageSeoScore').innerHTML = `<span class="text-muted">No tests run.</span>`; return; }
const score = Math.round((passed / total) * 100);
let colorClass = 'bg-success'; if (score < 80 && score >= 50) colorClass = 'bg-warning'; else if (score < 50) colorClass = 'bg-danger';
document.getElementById('averageSeoScore').innerHTML = ` <label class="mb-1"><strong>SEO Score:</strong> ${score}%</label> <div class="progress" style="height: 24px;"> <div class="progress-bar ${colorClass}" role="progressbar" style="width: ${score}%;" aria-valuenow="${score}" aria-valuemin="0" aria-valuemax="100"> ${score}% </div> </div> <div class="mt-1"> <small>Total: ${total} | ✅ ${passed} | ⚠️ ${warning} | ❌ ${failed}</small> </div> `;
} async function fetchPageSpeedScore(url, apiKey, strategy = 'mobile') { const apiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&key=${apiKey}&strategy=${strategy}`; try { const response = await fetch(apiUrl); const data = await response.json(); const score = Math.round(data.lighthouseResult.categories.performance.score * 100); return score; } catch (error) { console.error(`Failed to fetch PageSpeed score (${strategy}):`, error); return null; } } function filterAccordion() { const input = document.getElementById('searchAccordion').value.toLowerCase(); const items = document.querySelectorAll('#seoAuditParameters .accordion-item');
items.forEach(item => { const buttonText = item.querySelector('.accordion-button').textContent.toLowerCase(); if (buttonText.includes(input)) { item.style.display = ''; item.classList.add('fade', 'show'); } else { item.style.display = 'none'; item.classList.remove('show'); } }); }
function clearAccordionSearch() { document.getElementById('searchAccordion').value = ''; filterAccordion(); }
function clearAuditInputSearch() { document.getElementById('urlInput').value = ''; document.getElementById('alertValidUrl').style.display = 'none'; //document.getElementById('seoSetupMessage').style.display = 'none'; //document.getElementById('seoSetupMessage').style.transition = 'display 12s ease';
}
// function clearAuditResultsSearch() { // document.getElementById('searchAuditResults').value = ''; // // } function auditResultsOpenClose() { document.getElementById('auditResultsFilterSection').style.display = 'none'; document.getElementById('auditResultsFilterSection').style.transition = 'display .5s ease'; document.getElementById('buttonFilterResultsAccordionItems').style.display = 'block'; document.getElementById('buttonFilterResultsAccordionItems').style.bottom = '0';
} // auditResultsFilterSection // function filterAuditResults() { // const input = document.getElementById('searchAuditResults').value.toLowerCase(); // const items = document.querySelectorAll('#accordionAuditResults .accordion'); // // items.forEach(item => { // const buttonText = item.querySelector('.accordion-header').textContent.toLowerCase(); // if (buttonText.includes(input)) { // item.style.display = ''; // item.classList.add('fade', 'show'); // } else { // item.style.display = 'none'; // item.classList.remove('show'); // } // }); // }
// filtering functions need to match the above structure: function filterAuditResults() { const q = document.getElementById('searchAuditResults').value.toLowerCase(); document.querySelectorAll('#filterSeoAuditResultsData').forEach(box => { const title = box.querySelector('.results-audit-box').textContent.toLowerCase(); box.style.display = title.includes(q) ? '' : 'none'; }); } function clearAuditResultsSearch() { document.getElementById('searchAuditResults').value = ''; filterAuditResults(); } // filtering functions need to match the above structure: id="auditCheckToggles" // function filterAuditCheckToggles() { // const q = document.getElementById('filterInputAuditCheckToggles').value.toLowerCase(); // document.querySelectorAll('#auditCheckToggles').forEach(box => { // const title = box.querySelector('.audit-toggle').textContent.toLowerCase(); // box.style.display = title.includes(q) ? '' : 'none'; // }); // }
function filterAuditCheckToggles() { const input = document.getElementById('filterInputAuditCheckToggles').value.toLowerCase(); const items = document.querySelectorAll('#auditCheckToggles .form-check.form-switch'); // form-check-label audit-toggle items.forEach(item => { const labelText = item.querySelector('.form-check-label.audit-toggle').textContent.toLowerCase(); if (labelText.includes(input)) { item.style.display = ''; item.classList.add('fade', 'show'); } else { item.style.display = 'none'; item.classList.remove('show'); } }); }
function clearFilterAuditCheckToggles() { document.getElementById('filterInputAuditCheckToggles').value = ''; filterAuditCheckToggles(); }
function toggleNav() { const sidenav = document.getElementById("mySidenav"); const hamburger = document.getElementById("hamburger"); if (sidenav.style.width === "250px") { sidenav.style.width = "0"; document.getElementById("main").style.marginLeft = "0"; document.getElementById("main").style.transition = "margin-left .5s"; // document.getElementById("masterSEONavigation").style.left = "0px"; // document.getElementById("masterSEONavigation").style.marginLeft = "0px"; // document.getElementById("masterSEONavigation").style.transition = "margin-left .5s"; hamburger.classList.remove("change"); } else { sidenav.style.width = "250px"; document.getElementById("main").style.marginLeft = "250px"; document.getElementById("main").style.transition = "margin-left .5s;"; // document.getElementById("masterSEONavigation").style.left = "250px"; // document.getElementById("masterSEONavigation").style.marginLeft = "250px"; // document.getElementById("masterSEONavigation").style.transition = "margin-left .5s"; hamburger.classList.add("change"); } }
function openStatsData() { const modal = new bootstrap.Modal(document.getElementById('statsDataModal')); modal.show(); }
function openStatsInfoData() { const modal = new bootstrap.Modal(document.getElementById('statsInfoModal')); modal.show(); }
function openStatsVideoHelp() { const modal = new bootstrap.Modal(document.getElementById('statsVideoModal')); modal.show(); }
document.getElementById('statsDataModal').addEventListener('shown.bs.modal', () => { document.body.style.paddingRight = '0'; }); document.getElementById('statsInfoModal').addEventListener('shown.bs.modal', () => { document.body.style.paddingRight = '0'; }); document.getElementById('statsVideoModal').addEventListener('shown.bs.modal', () => { document.body.style.paddingRight = '0' });
function copyOgMeta(el) { const text = el.closest('.display-jsons').querySelector('.jsons-code-view').textContent; navigator.clipboard.writeText(text).then(() => { el.innerHTML = 'Copied ✅'; setTimeout(() => { el.innerHTML = '📋 <strong style="cursor: pointer;">Copy</strong> <i class="bi bi-copy ms-1"></i>'; }, 2000); }); }
function toggleOgCode(el) { const codeBox = el.closest('.display-jsons').querySelector('.json-code-container'); codeBox.classList.toggle('d-none'); }
// // document.getElementById('generateReportBtn').addEventListener('click', async () => { // const loader = document.getElementById('reportLoader'); // loader.style.display = 'block'; // // const accordionSections = document.querySelectorAll('.accordion-content'); // let reportData = []; // // accordionSections.forEach(section => { // const title = section.closest('.accordion-box')?.querySelector('.checks-title')?.textContent.trim() || ''; // const extra = section.querySelector('.accordion-content-text')?.innerText.trim() || ''; // const details = section.querySelector('.accordion-content-dt')?.innerText.trim() || ''; // reportData.push({ title, extra, details }); // }); // // await new Promise(res => setTimeout(res, 1000)); // simulate delay // // generateCSV(reportData); // generatePDF(reportData); // // loader.style.display = 'none'; // }); // // // CSV Generation // function generateCSV(data) { // const csv = ['Title,Extra Info,Details']; // data.forEach(row => { // csv.push(`"${row.title}","${row.extra.replace(/"/g, '""')}","${row.details.replace(/"/g, '""')}"`); // }); // // const blob = new Blob([csv.join('\n')], { type: 'text/csv' }); // const link = document.createElement('a'); // link.href = URL.createObjectURL(blob); // link.download = 'seo_audit_report.csv'; // link.click(); // } // // // PDF Generation using jsPDF // function generatePDF(data) { // const doc = new jsPDF(); // data.forEach((item, index) => { // doc.text(`Test: ${item.title}`, 10, 10 + index * 30); // doc.text(`Extra Info: ${item.extra}`, 10, 15 + index * 30); // doc.text(`Details: ${item.details}`, 10, 20 + index * 30); // }); // doc.save('seo_audit_report.pdf'); // }
function checkBadKeywords(text) { const matches = []; if (!text) return matches; const t = text.toLowerCase(); badKeyWordsDatabase.forEach(k => { if (t.includes(k)) matches.push(k); }); return matches; } const badKeyWordsDatabase = [ 'click here','free','buy now','cheap','guaranteed','limited time','best','amazing','act now','instant', 'winner','earn money','credit card','risk free','easy','double your','extra cash','urgent','win','miracle', 'income','trial','special promotion','cash bonus','unsubscribe','discount','congratulations','no obligation', 'fast cash','work from home','order now','be your own boss','save big','apply now','satisfaction guaranteed', 'lose weight','free info','get paid','buy direct','unsecured debt','online biz opportunity','no catch','get it now', 'you are a winner','promise','pre-approved','luxury','hidden charges','bargain','extra income' ]; const stopWords = [ 'the', 'and', 'you', 'for', 'are', 'but', 'with', 'this', 'that', 'was', 'your', 'have', 'not', 'from', 'they', 'all', 'can', 'will', 'has', 'more', 'one', 'their', 'about', 'when', 'what', 'which', 'how' ];
</script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> --><!-- Report Modal --><div class="modal fade" id="reportModal" tabindex="-1" aria-labelledby="reportModalLabel" aria-hidden="true"> <div class="modal-dialog modal-xl modal-dialog-scrollable"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="reportModalLabel">Audit Report Preview</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <div id="reportTableContainer" class="table-responsive"></div> </div> <div class="modal-footer"> <button id="downloadCsvBtn" class="btn btn-outline-secondary">⬇️ CSV</button> <button id="downloadPdfBtn" class="btn btn-outline-danger">⬇️ PDF</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> </div> </div> </div></div>
<!-- Load jsPDF for PDF download --><script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script> document.addEventListener('DOMContentLoaded', () => { const generateReportData = () => { const sections = document.querySelectorAll('.accordion-content'); return Array.from(sections).map(section => ({ title: section.closest('.accordion-box')?.querySelector('.checks-title')?.textContent.trim() || '', extra: section.querySelector('.accordion-content-text')?.innerText.trim() || '', details: section.querySelector('.accordion-content-dt')?.innerText.trim() || '' })); };
const generateBtn = document.getElementById('generateReportBtn'); const csvBtn = document.getElementById('downloadCsvBtn'); const pdfBtn = document.getElementById('downloadPdfBtn');
if (generateBtn) { generateBtn.addEventListener('click', () => { const data = generateReportData(); const tableHTML = ` <table class="table table-striped table-bordered"> <thead><tr><th>Test</th><th>Extra Info</th><th>Details</th></tr></thead> <tbody> ${data.map(d => `<tr> <td>${d.title}</td> <td>${d.extra.replace(/\n/g, '<br>')}</td> <td>${d.details.replace(/\n/g, '<br>')}</td> </tr>`).join('')} </tbody> </table> `; document.getElementById('reportTableContainer').innerHTML = tableHTML; const modal = new bootstrap.Modal(document.getElementById('reportModal')); modal.show(); }); }
if (csvBtn) { csvBtn.addEventListener('click', () => { const data = generateReportData(); const csv = ['"Test","Extra Info","Details"']; data.forEach(row => { csv.push(`"${row.title}","${row.extra.replace(/"/g, '""')}","${row.details.replace(/"/g, '""')}"`); }); const blob = new Blob([csv.join('\n')], { type: 'text/csv' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'seo_audit_report.csv'; link.click(); }); }
if (pdfBtn) { pdfBtn.addEventListener('click', () => { const data = generateReportData(); const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'landscape' }); let y = 15; doc.setFontSize(12); data.forEach(row => { if (y > 180) { doc.addPage(); y = 15; } doc.text(`Test: ${row.title}`, 10, y); doc.text(`Extra Info: ${row.extra}`, 10, y + 6); doc.text(`Details: ${row.details}`, 10, y + 12); y += 25; }); doc.save('seo_audit_report.pdf'); }); } });</script>
</body></html>