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:
Matt Batchelder
2026-02-19 09:10:01 -05:00
commit 9fddd43a2c
21 changed files with 2959 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View 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 ) );
}
} )();