Add custom page templates for Home, Contact, and Services
- Created a new Contact Page template with a contact form and information section. - Developed a Home Page template featuring a hero section, core capabilities, and pricing details. - Introduced a Services Page template outlining core services and industry solutions. - Added a default page template for standard pages without a custom template. - Implemented a single post template for displaying individual blog posts. - Created a style.css file for theme metadata and styling.
This commit is contained in:
1042
wp-content/themes/ots-signs/assets/css/main.css
Normal file
1042
wp-content/themes/ots-signs/assets/css/main.css
Normal file
File diff suppressed because it is too large
Load Diff
142
wp-content/themes/ots-signs/assets/js/main.js
Normal file
142
wp-content/themes/ots-signs/assets/js/main.js
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* OTS Signs — Main JavaScript
|
||||
*/
|
||||
|
||||
( function () {
|
||||
'use strict';
|
||||
|
||||
// ── Sticky header ─────────────────────────────────────────────────────────
|
||||
const header = document.getElementById( 'site-header' );
|
||||
if ( header ) {
|
||||
const onScroll = () => {
|
||||
header.classList.toggle( 'scrolled', window.scrollY > 40 );
|
||||
};
|
||||
window.addEventListener( 'scroll', onScroll, { passive: true } );
|
||||
onScroll(); // run on init
|
||||
}
|
||||
|
||||
// ── Mobile nav toggle ──────────────────────────────────────────────────────
|
||||
const navToggle = document.getElementById( 'nav-toggle' );
|
||||
const siteNav = document.getElementById( 'site-nav' );
|
||||
if ( navToggle && siteNav ) {
|
||||
navToggle.addEventListener( 'click', () => {
|
||||
const isOpen = siteNav.classList.toggle( 'open' );
|
||||
navToggle.classList.toggle( 'open', isOpen );
|
||||
navToggle.setAttribute( 'aria-expanded', isOpen );
|
||||
document.body.style.overflow = isOpen ? 'hidden' : '';
|
||||
} );
|
||||
|
||||
// Close on nav link click
|
||||
siteNav.querySelectorAll( 'a' ).forEach( link => {
|
||||
link.addEventListener( 'click', () => {
|
||||
siteNav.classList.remove( 'open' );
|
||||
navToggle.classList.remove( 'open' );
|
||||
navToggle.setAttribute( 'aria-expanded', 'false' );
|
||||
document.body.style.overflow = '';
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
// ── Scroll-to-top button ──────────────────────────────────────────────────
|
||||
const scrollTopBtn = document.querySelector( '.scroll-top' );
|
||||
if ( scrollTopBtn ) {
|
||||
window.addEventListener( 'scroll', () => {
|
||||
scrollTopBtn.classList.toggle( 'visible', window.scrollY > 400 );
|
||||
}, { passive: true } );
|
||||
scrollTopBtn.addEventListener( 'click', () => {
|
||||
window.scrollTo( { top: 0, behavior: 'smooth' } );
|
||||
} );
|
||||
}
|
||||
|
||||
// ── Contact form AJAX ─────────────────────────────────────────────────────
|
||||
const contactForm = document.getElementById( 'contact-form' );
|
||||
if ( contactForm ) {
|
||||
const submitBtn = document.getElementById( 'contact-submit' );
|
||||
const btnText = submitBtn ? submitBtn.querySelector( '.btn-text' ) : null;
|
||||
const btnLoading = submitBtn ? submitBtn.querySelector( '.btn-loading' ) : null;
|
||||
const notice = document.getElementById( 'form-notice' );
|
||||
|
||||
contactForm.addEventListener( 'submit', async ( e ) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Basic client-side validation
|
||||
const name = contactForm.querySelector( '[name="name"]' );
|
||||
const email = contactForm.querySelector( '[name="email"]' );
|
||||
const message = contactForm.querySelector( '[name="message"]' );
|
||||
|
||||
if ( ! name.value.trim() || ! email.value.trim() || ! message.value.trim() ) {
|
||||
showNotice( 'error', 'Please fill in all required fields.' );
|
||||
return;
|
||||
}
|
||||
|
||||
// Set loading state
|
||||
if ( submitBtn ) submitBtn.disabled = true;
|
||||
if ( btnText ) btnText.style.display = 'none';
|
||||
if ( btnLoading ) btnLoading.style.display = 'inline';
|
||||
if ( notice ) notice.className = 'form-notice';
|
||||
|
||||
try {
|
||||
const formData = new FormData( contactForm );
|
||||
formData.append( 'action', 'ots_contact' );
|
||||
formData.append( 'nonce', otsAjax.nonce );
|
||||
|
||||
const response = await fetch( otsAjax.url, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin',
|
||||
} );
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if ( data.success ) {
|
||||
showNotice( 'success', data.data.message || 'Message sent successfully!' );
|
||||
contactForm.reset();
|
||||
} else {
|
||||
showNotice( 'error', data.data.message || 'Something went wrong. Please try again.' );
|
||||
}
|
||||
} catch ( err ) {
|
||||
showNotice( 'error', 'Network error. Please check your connection and try again.' );
|
||||
} finally {
|
||||
if ( submitBtn ) submitBtn.disabled = false;
|
||||
if ( btnText ) btnText.style.display = 'inline';
|
||||
if ( btnLoading ) btnLoading.style.display = 'none';
|
||||
}
|
||||
} );
|
||||
|
||||
function showNotice( type, message ) {
|
||||
if ( ! notice ) return;
|
||||
notice.textContent = message;
|
||||
notice.className = 'form-notice ' + type;
|
||||
notice.style.display = 'block';
|
||||
notice.scrollIntoView( { behavior: 'smooth', block: 'nearest' } );
|
||||
}
|
||||
}
|
||||
|
||||
// ── Animate cards on scroll (intersection observer) ──────────────────────
|
||||
if ( 'IntersectionObserver' in window ) {
|
||||
const cards = document.querySelectorAll( '.feature-card, .industry-card, .value-card, .service-industry-card, .pricing-card' );
|
||||
const style = document.createElement( 'style' );
|
||||
style.textContent = `
|
||||
.animate-card { opacity: 0; transform: translateY(24px); transition: opacity 0.5s ease, transform 0.5s ease; }
|
||||
.animate-card.visible { opacity: 1; transform: translateY(0); }
|
||||
`;
|
||||
document.head.appendChild( style );
|
||||
|
||||
cards.forEach( ( card, i ) => {
|
||||
card.classList.add( 'animate-card' );
|
||||
card.style.transitionDelay = ( ( i % 4 ) * 80 ) + 'ms';
|
||||
} );
|
||||
|
||||
const observer = new IntersectionObserver( ( entries ) => {
|
||||
entries.forEach( entry => {
|
||||
if ( entry.isIntersecting ) {
|
||||
entry.target.classList.add( 'visible' );
|
||||
observer.unobserve( entry.target );
|
||||
}
|
||||
} );
|
||||
}, { threshold: 0.1 } );
|
||||
|
||||
cards.forEach( card => observer.observe( card ) );
|
||||
}
|
||||
|
||||
} )();
|
||||
Reference in New Issue
Block a user