Initial commit: Weedops WordPress theme

Includes front-page, strain archive/single templates, header/footer,
functions.php with custom post types, and base styles.
This commit is contained in:
Alex 2026-03-23 22:54:38 +00:00
commit 8eddfee34b
10 changed files with 1986 additions and 0 deletions

307
archive-strain.php Normal file
View file

@ -0,0 +1,307 @@
<?php
/**
* Archive Strain Template
* Strain catalog page grid layout with filters.
*
* @package Weedops
*/
get_header(); ?>
<main id="main" class="weedops-main">
<div class="strain-archive-wrap">
<!-- Page Header -->
<header class="strain-archive-header">
<h1 class="strain-archive-title">Strain Catalog</h1>
<p class="strain-archive-subtitle">Browse our curated database of cannabis strains. Filter by type, potency, and effects.</p>
</header>
<!-- Filter Bar -->
<div class="strain-filters" id="strainFilters">
<button class="strain-filter-btn active" data-filter="all">All Strains</button>
<button class="strain-filter-btn" data-filter="indica">Indica</button>
<button class="strain-filter-btn" data-filter="sativa">Sativa</button>
<button class="strain-filter-btn" data-filter="hybrid">Hybrid</button>
</div>
<!-- Results Count -->
<div class="strain-results-meta">
<?php
global $wp_query;
$total = $wp_query->found_posts;
?>
<span class="strain-results-count">
<?php echo esc_html( $total ); ?> strain<?php echo $total !== 1 ? 's' : ''; ?> found
</span>
</div>
<!-- Strain Grid -->
<?php if ( have_posts() ) : ?>
<div class="strain-grid" id="strainGrid">
<?php while ( have_posts() ) : the_post(); ?>
<?php
$strain_type = get_post_meta( get_the_ID(), '_strain_type', true );
$thc = get_post_meta( get_the_ID(), '_strain_thc', true );
$cbd = get_post_meta( get_the_ID(), '_strain_cbd', true );
$effects_raw = get_post_meta( get_the_ID(), '_strain_effects', true );
$effects = array_filter( array_map( 'trim', explode( ',', $effects_raw ) ) );
?>
<article
id="strain-<?php the_ID(); ?>"
class="strain-card"
data-type="<?php echo esc_attr( $strain_type ); ?>"
>
<a href="<?php the_permalink(); ?>" class="strain-card__link" tabindex="-1" aria-hidden="true">
<div class="strain-card__image">
<?php if ( has_post_thumbnail() ) : ?>
<?php the_post_thumbnail( 'medium', [ 'class' => 'strain-card__img' ] ); ?>
<?php else : ?>
<div class="strain-card__placeholder">
<span class="strain-card__placeholder-icon">🌿</span>
</div>
<?php endif; ?>
<div class="strain-card__badge strain-card__badge--<?php echo esc_attr( $strain_type ); ?>">
<?php echo esc_html( ucfirst( $strain_type ? $strain_type : 'Unknown' ) ); ?>
</div>
</div>
</a>
<div class="strain-card__body">
<h2 class="strain-card__title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
<div class="strain-card__potency">
<?php if ( $thc !== '' ) : ?>
<div class="strain-card__stat strain-card__stat--thc">
<span class="strain-card__stat-label">THC</span>
<span class="strain-card__stat-value"><?php echo esc_html( $thc ); ?>%</span>
</div>
<?php endif; ?>
<?php if ( $cbd !== '' ) : ?>
<div class="strain-card__stat strain-card__stat--cbd">
<span class="strain-card__stat-label">CBD</span>
<span class="strain-card__stat-value"><?php echo esc_html( $cbd ); ?>%</span>
</div>
<?php endif; ?>
</div>
<?php if ( ! empty( $effects ) ) : ?>
<div class="strain-card__effects">
<?php foreach ( array_slice( $effects, 0, 3 ) as $effect ) : ?>
<span class="strain-card__effect-tag"><?php echo esc_html( $effect ); ?></span>
<?php endforeach; ?>
<?php if ( count( $effects ) > 3 ) : ?>
<span class="strain-card__effect-more">+<?php echo count( $effects ) - 3; ?> more</span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if ( get_the_excerpt() ) : ?>
<p class="strain-card__excerpt"><?php echo esc_html( wp_trim_words( get_the_excerpt(), 15, '…' ) ); ?></p>
<?php endif; ?>
<a href="<?php the_permalink(); ?>" class="strain-card__cta">View Strain </a>
</div>
</article>
<?php endwhile; ?>
</div><!-- .strain-grid -->
<!-- Pagination -->
<div class="strain-pagination">
<?php
echo paginate_links( [
'prev_text' => '← Previous',
'next_text' => 'Next →',
'before_page_number' => '<span class="screen-reader-text">Page </span>',
] );
?>
</div>
<?php else : ?>
<div class="strain-no-results">
<span class="strain-no-results__icon">🔍</span>
<h2>No strains found</h2>
<p>Check back soon we're always adding to the catalog.</p>
</div>
<?php endif; ?>
</div><!-- .strain-archive-wrap -->
</main>
<style>
/* ── Strain Archive / Catalog Styles ─────────────────── */
.strain-archive-wrap { max-width: 1200px; margin: 0 auto; padding: 2rem 1.5rem; }
/* Header */
.strain-archive-header { text-align: center; margin-bottom: 2.5rem; }
.strain-archive-title {
font-size: 2.8rem; font-weight: 900; color: #1a3a1a;
margin: 0 0 .5rem;
background: linear-gradient(135deg, #2e7d32, #66bb6a);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.strain-archive-subtitle { color: #555; font-size: 1.1rem; margin: 0; }
/* Filter Bar */
.strain-filters {
display: flex; gap: .5rem; justify-content: center;
flex-wrap: wrap; margin-bottom: 1.5rem;
}
.strain-filter-btn {
padding: .5rem 1.4rem; border-radius: 999px;
border: 2px solid #a5d6a7; background: #fff;
color: #2e7d32; font-weight: 600; font-size: .9rem;
cursor: pointer; transition: all .2s;
}
.strain-filter-btn:hover,
.strain-filter-btn.active { background: #2e7d32; color: #fff; border-color: #2e7d32; }
/* Results meta */
.strain-results-meta { text-align: right; font-size: .85rem; color: #777; margin-bottom: 1rem; }
/* Grid */
.strain-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
margin-bottom: 2.5rem;
}
/* Card */
.strain-card {
background: #fff;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,.07);
border: 1px solid #e8f5e9;
transition: transform .2s, box-shadow .2s;
display: flex; flex-direction: column;
}
.strain-card:hover { transform: translateY(-4px); box-shadow: 0 8px 28px rgba(46,125,50,.15); }
/* Card Image */
.strain-card__link { display: block; position: relative; }
.strain-card__img { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; }
.strain-card__placeholder {
width: 100%; aspect-ratio: 4/3;
background: linear-gradient(135deg, #1a3a1a 0%, #2d5a2d 100%);
display: flex; align-items: center; justify-content: center;
}
.strain-card__placeholder-icon { font-size: 3rem; }
/* Type Badge on card image */
.strain-card__badge {
position: absolute; top: .8rem; right: .8rem;
padding: .25rem .75rem; border-radius: 999px;
font-size: .7rem; font-weight: 700;
text-transform: uppercase; letter-spacing: .08em;
}
.strain-card__badge--indica { background: #4a148c; color: #e1bee7; }
.strain-card__badge--sativa { background: #e65100; color: #ffe0b2; }
.strain-card__badge--hybrid { background: #1b5e20; color: #c8e6c9; }
/* Card Body */
.strain-card__body { padding: 1.2rem; display: flex; flex-direction: column; flex: 1; }
.strain-card__title { font-size: 1.25rem; font-weight: 800; margin: 0 0 .8rem; }
.strain-card__title a { color: #1a3a1a; text-decoration: none; }
.strain-card__title a:hover { color: #2e7d32; }
/* Potency stats */
.strain-card__potency { display: flex; gap: .8rem; margin-bottom: .8rem; }
.strain-card__stat {
flex: 1; padding: .5rem .7rem; border-radius: 8px;
display: flex; flex-direction: column; align-items: center;
}
.strain-card__stat--thc { background: #e8f5e9; }
.strain-card__stat--cbd { background: #e3f2fd; }
.strain-card__stat-label { font-size: .65rem; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: #666; }
.strain-card__stat-value { font-size: 1.4rem; font-weight: 900; color: #1a3a1a; line-height: 1.1; }
.strain-card__stat--thc .strain-card__stat-value { color: #2e7d32; }
.strain-card__stat--cbd .strain-card__stat-value { color: #0277bd; }
/* Effect tags */
.strain-card__effects { display: flex; flex-wrap: wrap; gap: .3rem; margin-bottom: .8rem; }
.strain-card__effect-tag {
background: #f1f8e9; color: #388e3c;
border: 1px solid #c5e1a5;
border-radius: 999px; padding: .15rem .6rem;
font-size: .75rem; font-weight: 500;
}
.strain-card__effect-more { font-size: .75rem; color: #999; padding: .15rem .4rem; }
/* Excerpt */
.strain-card__excerpt { font-size: .85rem; color: #666; line-height: 1.5; margin: 0 0 1rem; flex: 1; }
/* CTA */
.strain-card__cta {
display: inline-block; margin-top: auto;
color: #2e7d32; font-weight: 700; font-size: .9rem;
text-decoration: none; border-top: 1px solid #e8f5e9;
padding-top: .8rem; transition: color .2s;
}
.strain-card__cta:hover { color: #1b5e20; }
/* Pagination */
.strain-pagination { display: flex; justify-content: center; gap: .5rem; flex-wrap: wrap; }
.strain-pagination .page-numbers {
padding: .5rem 1rem; border-radius: 6px;
border: 1px solid #a5d6a7; color: #2e7d32;
text-decoration: none; font-weight: 600; transition: all .2s;
}
.strain-pagination .page-numbers.current,
.strain-pagination .page-numbers:hover { background: #2e7d32; color: #fff; border-color: #2e7d32; }
/* No Results */
.strain-no-results { text-align: center; padding: 4rem 2rem; color: #777; }
.strain-no-results__icon { font-size: 3rem; display: block; margin-bottom: 1rem; }
.strain-no-results h2 { color: #1a3a1a; }
</style>
<script>
// Simple client-side filter — filters visible cards by strain type without a page reload
(function() {
const btns = document.querySelectorAll('.strain-filter-btn');
const cards = document.querySelectorAll('.strain-card');
const meta = document.querySelector('.strain-results-count');
btns.forEach(function(btn) {
btn.addEventListener('click', function() {
btns.forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
const filter = btn.dataset.filter;
let visible = 0;
cards.forEach(function(card) {
if (filter === 'all' || card.dataset.type === filter) {
card.style.display = '';
visible++;
} else {
card.style.display = 'none';
}
});
if (meta) {
meta.textContent = visible + ' strain' + (visible !== 1 ? 's' : '') + ' found';
}
});
});
})();
</script>
<?php get_footer(); ?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

91
footer.php Normal file
View file

@ -0,0 +1,91 @@
</div><!-- .wo-container (opened in header.php) -->
</main><!-- #main-content -->
<?php // ── Site Footer ───────────────────────────────────────── ?>
<footer class="wo-footer" role="contentinfo">
<div class="wo-container">
<div class="wo-footer__grid">
<?php // Brand column ?>
<div class="wo-footer__brand">
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="wo-logo" rel="home">
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" aria-hidden="true" focusable="false">
<path d="M14 2C8.5 2 4 6.5 4 12c0 3.5 2 6.5 5 8.5L14 26l5-5.5c3-2 5-5 5-8.5C24 6.5 19.5 2 14 2z" fill="#4a9e68"/>
<path d="M14 6c-3.3 0-6 2.7-6 6 0 2 1 3.8 2.5 4.8L14 20l3.5-3.2c1.5-1 2.5-2.8 2.5-4.8 0-3.3-2.7-6-6-6z" fill="#c8a951"/>
</svg>
Weed<span>ops</span>
</a>
<p class="wo-mt-2" style="color:rgba(255,255,255,0.6);font-size:0.85rem;max-width:220px;">
<?php echo esc_html( get_bloginfo( 'description' ) ?: __( 'Cannabis industry product management and compliance platform.', 'weedops' ) ); ?>
</p>
</div>
<?php // Platform column ?>
<div>
<h4><?php esc_html_e( 'Platform', 'weedops' ); ?></h4>
<ul>
<li><a href="<?php echo esc_url( home_url( '/strains' ) ); ?>"><?php esc_html_e( 'Strain Database', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/products' ) ); ?>"><?php esc_html_e( 'Product Catalog', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/dispensaries' ) ); ?>"><?php esc_html_e( 'Dispensaries', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/compliance' ) ); ?>"><?php esc_html_e( 'Compliance', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/inventory' ) ); ?>"><?php esc_html_e( 'Inventory', 'weedops' ); ?></a></li>
</ul>
</div>
<?php // Footer nav widget area or fallback links ?>
<div>
<h4><?php esc_html_e( 'Company', 'weedops' ); ?></h4>
<?php if ( is_active_sidebar( 'footer-col-2' ) ) : ?>
<?php dynamic_sidebar( 'footer-col-2' ); ?>
<?php else : ?>
<ul>
<li><a href="<?php echo esc_url( home_url( '/about' ) ); ?>"><?php esc_html_e( 'About', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/blog' ) ); ?>"><?php esc_html_e( 'Blog', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/contact' ) ); ?>"><?php esc_html_e( 'Contact', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/careers' ) ); ?>"><?php esc_html_e( 'Careers', 'weedops' ); ?></a></li>
</ul>
<?php endif; ?>
</div>
<?php // Legal column ?>
<div>
<h4><?php esc_html_e( 'Legal', 'weedops' ); ?></h4>
<ul>
<li><a href="<?php echo esc_url( home_url( '/privacy-policy' ) ); ?>"><?php esc_html_e( 'Privacy Policy', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/terms' ) ); ?>"><?php esc_html_e( 'Terms of Service', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/age-verification' ) ); ?>"><?php esc_html_e( 'Age Verification', 'weedops' ); ?></a></li>
<li><a href="<?php echo esc_url( home_url( '/compliance-disclosure' ) ); ?>"><?php esc_html_e( 'Compliance Disclosure', 'weedops' ); ?></a></li>
</ul>
</div>
</div><!-- .wo-footer__grid -->
<div class="wo-footer__bottom">
<span>
&copy; <?php echo esc_html( gmdate( 'Y' ) ); ?>
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" style="color:rgba(255,255,255,0.7);">Weedops</a>.
<?php esc_html_e( 'All rights reserved.', 'weedops' ); ?>
</span>
<span style="color:rgba(255,255,255,0.4);font-size:0.75rem;">
<?php esc_html_e( 'For use in jurisdictions where cannabis is legal. Must be 21+.', 'weedops' ); ?>
</span>
<?php
// Optional: footer nav
wp_nav_menu( [
'theme_location' => 'footer',
'container' => false,
'depth' => 1,
'fallback_cb' => false,
'menu_class' => 'wo-footer__nav',
] );
?>
</div><!-- .wo-footer__bottom -->
</div><!-- .wo-container -->
</footer>
<?php wp_footer(); ?>
</body>
</html>

153
front-page.php Normal file
View file

@ -0,0 +1,153 @@
<?php
/**
* Weedops Front Page Template
*
* Full-width hero with photography-style imagery,
* followed by featured sections.
*
* @package Weedops
*/
get_header();
?>
</div><!-- close .wo-container from header -->
</main><!-- close .wo-main from header we'll reopen after hero -->
<?php // ── Hero Section ──────────────────────────────────────── ?>
<section class="wo-hero" role="banner" aria-label="<?php esc_attr_e( 'Welcome to Weedops', 'weedops' ); ?>">
<div class="wo-hero__overlay"></div>
<div class="wo-hero__content">
<span class="wo-hero__eyebrow"><?php esc_html_e( 'Cannabis Intelligence Platform', 'weedops' ); ?></span>
<h1 class="wo-hero__title">
<?php esc_html_e( 'Smarter Product Management for the Cannabis Industry', 'weedops' ); ?>
</h1>
<p class="wo-hero__subtitle">
<?php esc_html_e( 'Compliance tracking, strain databases, and inventory tools built for dispensaries, growers, and brands.', 'weedops' ); ?>
</p>
<div class="wo-hero__actions">
<a href="<?php echo esc_url( home_url( '/strains' ) ); ?>" class="wo-btn wo-btn--primary wo-btn--lg">
<?php esc_html_e( 'Browse Strains', 'weedops' ); ?>
</a>
<a href="<?php echo esc_url( home_url( '/products' ) ); ?>" class="wo-btn wo-btn--ghost wo-btn--lg">
<?php esc_html_e( 'View Products', 'weedops' ); ?>
</a>
</div>
</div>
</section>
<?php // ── Features Grid ─────────────────────────────────────── ?>
<main class="wo-main" id="main-content" role="main">
<div class="wo-container">
<section class="wo-section wo-mt-3">
<div class="wo-text-center wo-mb-3">
<h2><?php esc_html_e( 'Built for Cannabis Professionals', 'weedops' ); ?></h2>
<p class="wo-text-muted" style="max-width:600px;margin:0.75rem auto 0;">
<?php esc_html_e( 'Everything you need to manage products, stay compliant, and grow your operation.', 'weedops' ); ?>
</p>
</div>
<div class="wo-grid wo-grid--3">
<div class="wo-card wo-text-center" style="padding:2rem;">
<div class="wo-feature-icon" style="font-size:2.4rem;margin-bottom:1rem;">&#x1F33F;</div>
<h3 style="font-size:1.15rem;margin-bottom:0.5rem;">
<?php esc_html_e( 'Strain Database', 'weedops' ); ?>
</h3>
<p class="wo-text-muted" style="font-size:0.9rem;">
<?php esc_html_e( 'Comprehensive profiles with THC/CBD levels, terpenes, effects, and grow data.', 'weedops' ); ?>
</p>
</div>
<div class="wo-card wo-text-center" style="padding:2rem;">
<div class="wo-feature-icon" style="font-size:2.4rem;margin-bottom:1rem;">&#x1F4CB;</div>
<h3 style="font-size:1.15rem;margin-bottom:0.5rem;">
<?php esc_html_e( 'Compliance Tracking', 'weedops' ); ?>
</h3>
<p class="wo-text-muted" style="font-size:0.9rem;">
<?php esc_html_e( 'Lab results, COA management, and regulatory status for every product in your catalog.', 'weedops' ); ?>
</p>
</div>
<div class="wo-card wo-text-center" style="padding:2rem;">
<div class="wo-feature-icon" style="font-size:2.4rem;margin-bottom:1rem;">&#x1F4E6;</div>
<h3 style="font-size:1.15rem;margin-bottom:0.5rem;">
<?php esc_html_e( 'Inventory Management', 'weedops' ); ?>
</h3>
<p class="wo-text-muted" style="font-size:0.9rem;">
<?php esc_html_e( 'Real-time stock levels, batch tracking, and automated low-stock alerts.', 'weedops' ); ?>
</p>
</div>
</div>
</section>
<?php // ── Latest Strains ────────────────────────────────────── ?>
<?php
$strains = new WP_Query( [
'post_type' => 'wo_strain',
'posts_per_page' => 6,
'orderby' => 'date',
'order' => 'DESC',
] );
if ( $strains->have_posts() ) :
?>
<section class="wo-section wo-mt-3">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;flex-wrap:wrap;gap:0.5rem;">
<h2><?php esc_html_e( 'Latest Strains', 'weedops' ); ?></h2>
<a href="<?php echo esc_url( home_url( '/strains' ) ); ?>" class="wo-btn wo-btn--outline" style="font-size:0.85rem;">
<?php esc_html_e( 'View All &rarr;', 'weedops' ); ?>
</a>
</div>
<div class="wo-grid wo-grid--3">
<?php while ( $strains->have_posts() ) : $strains->the_post(); ?>
<article <?php post_class( 'wo-card' ); ?>>
<?php if ( has_post_thumbnail() ) : ?>
<a href="<?php the_permalink(); ?>" aria-hidden="true" tabindex="-1">
<?php the_post_thumbnail( 'medium', [
'class' => 'wo-card__img',
'loading' => 'lazy',
'style' => 'width:100%;height:180px;object-fit:cover;border-radius:8px;margin-bottom:1rem;',
] ); ?>
</a>
<?php endif; ?>
<div class="wo-card__meta" style="margin-bottom:0.5rem;">
<?php
$types = get_the_terms( get_the_ID(), 'wo_strain_type' );
if ( $types && ! is_wp_error( $types ) ) {
foreach ( $types as $t ) {
weedops_strain_badge( $t->slug );
}
}
?>
</div>
<h3 style="font-size:1.05rem;margin-bottom:0.4rem;">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
<?php $canna = weedops_get_cannabinoid_str( get_the_ID() ); ?>
<?php if ( $canna ) : ?>
<p style="font-size:0.82rem;font-weight:600;color:var(--wo-green-mid);margin-bottom:0.5rem;">
<?php echo esc_html( $canna ); ?>
</p>
<?php endif; ?>
<a href="<?php the_permalink(); ?>" class="wo-btn wo-btn--outline" style="font-size:0.8rem;padding:0.4rem 0.8rem;">
<?php esc_html_e( 'View Details', 'weedops' ); ?>
</a>
</article>
<?php endwhile; ?>
</div>
</section>
<?php
wp_reset_postdata();
endif;
?>
<?php
get_footer();

192
functions.php Normal file
View file

@ -0,0 +1,192 @@
<?php
/**
* Weedops Theme Functions
*
* Cannabis industry platform theme setup, enqueues, custom post types,
* taxonomies, REST API extensions, and helper utilities.
*
* @package Weedops
* @version 1.0.0
*/
defined( 'ABSPATH' ) || exit;
define( 'WEEDOPS_VERSION', '1.0.0' );
define( 'WEEDOPS_DIR', get_template_directory() );
define( 'WEEDOPS_URI', get_template_directory_uri() );
// ─────────────────────────────────────────────────────────────────────────────
// Theme Setup
// ─────────────────────────────────────────────────────────────────────────────
function weedops_setup(): void {
load_theme_textdomain( 'weedops', WEEDOPS_DIR . '/languages' );
add_theme_support( 'title-tag' );
add_theme_support( 'post-thumbnails' );
add_theme_support( 'html5', [ 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script' ] );
add_theme_support( 'customize-selective-refresh-widgets' );
add_theme_support( 'woocommerce' );
add_theme_support( 'wc-product-gallery-zoom' );
add_theme_support( 'wc-product-gallery-lightbox' );
add_theme_support( 'wc-product-gallery-slider' );
register_nav_menus( [
'primary' => __( 'Primary Navigation', 'weedops' ),
'footer' => __( 'Footer Navigation', 'weedops' ),
'account' => __( 'Account Menu', 'weedops' ),
] );
}
add_action( 'after_setup_theme', 'weedops_setup' );
// ─────────────────────────────────────────────────────────────────────────────
// Scripts & Styles
// ─────────────────────────────────────────────────────────────────────────────
function weedops_enqueue_assets(): void {
// Google Fonts — Inter
wp_enqueue_style(
'weedops-fonts',
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap',
[],
null
);
// Main stylesheet
wp_enqueue_style(
'weedops-style',
get_stylesheet_uri(),
[ 'weedops-fonts' ],
WEEDOPS_VERSION
);
// Main JS bundle
wp_enqueue_script(
'weedops-main',
WEEDOPS_URI . '/assets/js/main.js',
[],
WEEDOPS_VERSION,
true
);
// Pass data to JS
wp_localize_script( 'weedops-main', 'WeedopsData', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'restUrl' => esc_url_raw( rest_url( 'weedops/v1/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'siteUrl' => get_site_url(),
'isLoggedIn'=> is_user_logged_in(),
] );
}
add_action( 'wp_enqueue_scripts', 'weedops_enqueue_assets' );
// ─────────────────────────────────────────────────────────────────────────────
// Custom Post Types, Taxonomies & Meta Fields
// ─────────────────────────────────────────────────────────────────────────────
// Post types, taxonomies, and meta field registration live in their own file.
require_once WEEDOPS_DIR . '/inc/custom-post-types.php';
// ─────────────────────────────────────────────────────────────────────────────
// Widget Areas
// ─────────────────────────────────────────────────────────────────────────────
function weedops_register_sidebars(): void {
$sidebars = [
[ 'id' => 'sidebar-main', 'name' => __( 'Main Sidebar', 'weedops' ) ],
[ 'id' => 'sidebar-catalog', 'name' => __( 'Catalog Sidebar', 'weedops' ) ],
[ 'id' => 'footer-col-1', 'name' => __( 'Footer Column 1', 'weedops' ) ],
[ 'id' => 'footer-col-2', 'name' => __( 'Footer Column 2', 'weedops' ) ],
[ 'id' => 'footer-col-3', 'name' => __( 'Footer Column 3', 'weedops' ) ],
];
foreach ( $sidebars as $s ) {
register_sidebar( [
'id' => $s['id'],
'name' => $s['name'],
'before_widget' => '<div id="%1$s" class="wo-widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h4 class="wo-widget__title">',
'after_title' => '</h4>',
] );
}
}
add_action( 'widgets_init', 'weedops_register_sidebars' );
// ─────────────────────────────────────────────────────────────────────────────
// Helper Functions
// ─────────────────────────────────────────────────────────────────────────────
/**
* Render a strain type badge.
*
* @param string $type indica | sativa | hybrid | cbd
* @param bool $echo Echo or return.
*/
function weedops_strain_badge( string $type, bool $echo = true ): string {
$label = ucfirst( $type );
$class = 'wo-badge wo-badge--' . sanitize_html_class( strtolower( $type ) );
$html = sprintf( '<span class="%s">%s</span>', esc_attr( $class ), esc_html( $label ) );
if ( $echo ) echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return $html;
}
/**
* Return formatted THC/CBD string for a strain.
*
* @param int $post_id
*/
function weedops_get_cannabinoid_str( int $post_id ): string {
$thc = get_post_meta( $post_id, 'wo_thc_percent', true );
$cbd = get_post_meta( $post_id, 'wo_cbd_percent', true );
$parts = [];
if ( $thc !== '' ) $parts[] = 'THC ' . number_format( (float) $thc, 1 ) . '%';
if ( $cbd !== '' ) $parts[] = 'CBD ' . number_format( (float) $cbd, 1 ) . '%';
return implode( ' · ', $parts );
}
/**
* Check if a product has a valid COA (lab test certificate).
*/
function weedops_has_valid_coa( int $post_id ): bool {
$coa = get_post_meta( $post_id, 'wo_coa_url', true );
$tested = get_post_meta( $post_id, 'wo_lab_tested', true );
return ! empty( $coa ) && (bool) $tested;
}
/**
* Age-gate: returns true if the user has confirmed they are 21+.
* Stored in a session cookie.
*/
function weedops_age_verified(): bool {
return ! empty( $_COOKIE['wo_age_verified'] );
}
// ─────────────────────────────────────────────────────────────────────────────
// Age Verification AJAX Handler
// ─────────────────────────────────────────────────────────────────────────────
function weedops_ajax_age_verify(): void {
check_ajax_referer( 'wo_age_verify', 'nonce' );
setcookie( 'wo_age_verified', '1', time() + ( 30 * DAY_IN_SECONDS ), COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
wp_send_json_success( [ 'verified' => true ] );
}
add_action( 'wp_ajax_nopriv_wo_age_verify', 'weedops_ajax_age_verify' );
add_action( 'wp_ajax_wo_age_verify', 'weedops_ajax_age_verify' );
// ─────────────────────────────────────────────────────────────────────────────
// Title Tag
// ─────────────────────────────────────────────────────────────────────────────
function weedops_document_title_parts( array $parts ): array {
$parts['tagline'] = get_bloginfo( 'description' );
return $parts;
}
add_filter( 'document_title_parts', 'weedops_document_title_parts' );
// ─────────────────────────────────────────────────────────────────────────────
// Excerpt length
// ─────────────────────────────────────────────────────────────────────────────
add_filter( 'excerpt_length', fn() => 30 );
add_filter( 'excerpt_more', fn() => '&hellip;' );

91
header.php Normal file
View file

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#1a3d2b">
<link rel="profile" href="https://gmpg.org/xfn/11">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<?php // ── Age-gate compliance notice ─────────────────────────── ?>
<div class="wo-age-notice" role="note" aria-label="<?php esc_attr_e( 'Age compliance notice', 'weedops' ); ?>">
<?php esc_html_e( '21+ only. Cannabis products are for adults. Comply with your local laws.', 'weedops' ); ?>
</div>
<?php // ── Site Header ───────────────────────────────────────── ?>
<header class="wo-header" role="banner">
<div class="wo-container">
<div class="wo-header__inner">
<?php // Logo ?>
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="wo-logo" rel="home">
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" aria-hidden="true" focusable="false">
<path d="M14 2C8.5 2 4 6.5 4 12c0 3.5 2 6.5 5 8.5L14 26l5-5.5c3-2 5-5 5-8.5C24 6.5 19.5 2 14 2z" fill="#4a9e68"/>
<path d="M14 6c-3.3 0-6 2.7-6 6 0 2 1 3.8 2.5 4.8L14 20l3.5-3.2c1.5-1 2.5-2.8 2.5-4.8 0-3.3-2.7-6-6-6z" fill="#c8a951"/>
</svg>
Weed<span>ops</span>
</a>
<?php // Primary navigation ?>
<nav class="wo-nav" id="wo-primary-nav" role="navigation" aria-label="<?php esc_attr_e( 'Primary', 'weedops' ); ?>">
<?php
wp_nav_menu( [
'theme_location' => 'primary',
'menu_class' => '',
'container' => false,
'fallback_cb' => 'weedops_fallback_nav',
] );
?>
</nav>
<?php // Header actions ?>
<div class="wo-header__actions">
<?php if ( is_user_logged_in() ) : ?>
<a href="<?php echo esc_url( get_dashboard_url() ); ?>" class="wo-btn wo-btn--outline" style="font-size:0.82rem;padding:0.45rem 0.9rem;">
<?php esc_html_e( 'Dashboard', 'weedops' ); ?>
</a>
<?php else : ?>
<a href="<?php echo esc_url( wp_login_url() ); ?>" class="wo-btn wo-btn--primary" style="font-size:0.82rem;padding:0.45rem 0.9rem;">
<?php esc_html_e( 'Sign In', 'weedops' ); ?>
</a>
<?php endif; ?>
</div>
<?php // Mobile hamburger ?>
<button
class="wo-nav-toggle"
aria-controls="wo-primary-nav"
aria-expanded="false"
aria-label="<?php esc_attr_e( 'Toggle navigation', 'weedops' ); ?>"
style="display:none;background:none;border:none;cursor:pointer;padding:0.5rem;"
>
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
<rect y="3" width="22" height="2.5" rx="1.25" fill="#fff"/>
<rect y="10" width="22" height="2.5" rx="1.25" fill="#fff"/>
<rect y="17" width="22" height="2.5" rx="1.25" fill="#fff"/>
</svg>
</button>
</div><!-- .wo-header__inner -->
</div><!-- .wo-container -->
</header>
<?php
// Fallback nav when no menu is assigned
function weedops_fallback_nav(): void {
echo '<ul>';
echo '<li><a href="' . esc_url( home_url( '/' ) ) . '">' . esc_html__( 'Home', 'weedops' ) . '</a></li>';
echo '<li><a href="' . esc_url( home_url( '/strains' ) ) . '">' . esc_html__( 'Strains', 'weedops' ) . '</a></li>';
echo '<li><a href="' . esc_url( home_url( '/products' ) ) . '">' . esc_html__( 'Products', 'weedops' ) . '</a></li>';
echo '<li><a href="' . esc_url( home_url( '/dispensaries' ) ) . '">' . esc_html__( 'Dispensaries', 'weedops' ) . '</a></li>';
echo '</ul>';
}
?>
<main class="wo-main" id="main-content" role="main">
<div class="wo-container">

391
inc/custom-post-types.php Normal file
View file

@ -0,0 +1,391 @@
<?php
/**
* Weedops Custom Post Types, Taxonomies & Meta Fields
*
* Registers the 'strain' post type (and related CPTs) with full
* meta support: THC %, CBD %, strain type, effects, and more.
*
* @package Weedops
* @version 1.0.0
*/
defined( 'ABSPATH' ) || exit;
// ─────────────────────────────────────────────────────────────────────────────
// Post Types
// ─────────────────────────────────────────────────────────────────────────────
function weedops_register_post_types(): void {
// ── Strains ──────────────────────────────────────────────────────────────
register_post_type( 'wo_strain', [
'label' => __( 'Strains', 'weedops' ),
'labels' => [
'name' => __( 'Strains', 'weedops' ),
'singular_name' => __( 'Strain', 'weedops' ),
'add_new' => __( 'Add New', 'weedops' ),
'add_new_item' => __( 'Add New Strain', 'weedops' ),
'edit_item' => __( 'Edit Strain', 'weedops' ),
'new_item' => __( 'New Strain', 'weedops' ),
'view_item' => __( 'View Strain', 'weedops' ),
'search_items' => __( 'Search Strains', 'weedops' ),
'not_found' => __( 'No strains found', 'weedops' ),
'not_found_in_trash' => __( 'No strains in trash', 'weedops' ),
'menu_name' => __( 'Strains', 'weedops' ),
],
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_rest' => true,
'rest_base' => 'strains',
'menu_icon' => 'dashicons-palmtree',
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields', 'revisions' ],
'has_archive' => true,
'rewrite' => [ 'slug' => 'strains', 'with_front' => false ],
'capability_type' => 'post',
'map_meta_cap' => true,
] );
// ── Cannabis Products ─────────────────────────────────────────────────────
register_post_type( 'wo_product', [
'label' => __( 'Cannabis Products', 'weedops' ),
'labels' => [
'name' => __( 'Cannabis Products', 'weedops' ),
'singular_name' => __( 'Cannabis Product', 'weedops' ),
'add_new_item' => __( 'Add New Product', 'weedops' ),
'edit_item' => __( 'Edit Product', 'weedops' ),
'not_found' => __( 'No products found', 'weedops' ),
],
'public' => true,
'show_in_rest' => true,
'rest_base' => 'cannabis-products',
'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'custom-fields' ],
'menu_icon' => 'dashicons-archive',
'has_archive' => true,
'rewrite' => [ 'slug' => 'products', 'with_front' => false ],
'capability_type' => 'post',
] );
// ── Compliance Records ────────────────────────────────────────────────────
register_post_type( 'wo_compliance', [
'label' => __( 'Compliance Records', 'weedops' ),
'labels' => [
'name' => __( 'Compliance Records', 'weedops' ),
'singular_name' => __( 'Compliance Record', 'weedops' ),
'add_new_item' => __( 'Add Record', 'weedops' ),
],
'public' => false,
'show_ui' => true,
'show_in_menu' => true,
'show_in_rest' => true,
'rest_base' => 'compliance',
'supports' => [ 'title', 'custom-fields' ],
'menu_icon' => 'dashicons-shield',
'capability_type' => 'post',
] );
// ── Dispensaries / Locations ──────────────────────────────────────────────
register_post_type( 'wo_dispensary', [
'label' => __( 'Dispensaries', 'weedops' ),
'public' => true,
'show_in_rest' => true,
'rest_base' => 'dispensaries',
'supports' => [ 'title', 'editor', 'thumbnail', 'custom-fields' ],
'menu_icon' => 'dashicons-location-alt',
'has_archive' => true,
'rewrite' => [ 'slug' => 'dispensaries', 'with_front' => false ],
] );
}
add_action( 'init', 'weedops_register_post_types' );
// ─────────────────────────────────────────────────────────────────────────────
// Taxonomies
// ─────────────────────────────────────────────────────────────────────────────
function weedops_register_taxonomies(): void {
// ── Strain Type: indica / sativa / hybrid / cbd ───────────────────────────
register_taxonomy( 'wo_strain_type', [ 'wo_strain', 'wo_product' ], [
'label' => __( 'Strain Type', 'weedops' ),
'labels' => [
'name' => __( 'Strain Types', 'weedops' ),
'singular_name' => __( 'Strain Type', 'weedops' ),
'add_new_item' => __( 'Add Strain Type', 'weedops' ),
'search_items' => __( 'Search Types', 'weedops' ),
],
'hierarchical' => true,
'show_in_rest' => true,
'rest_base' => 'strain-types',
'rewrite' => [ 'slug' => 'strain-type' ],
'show_admin_column' => true,
] );
// ── Effects (relaxed, euphoric, creative, …) ──────────────────────────────
register_taxonomy( 'wo_effect', [ 'wo_strain', 'wo_product' ], [
'label' => __( 'Effects', 'weedops' ),
'labels' => [
'name' => __( 'Effects', 'weedops' ),
'singular_name' => __( 'Effect', 'weedops' ),
'add_new_item' => __( 'Add Effect', 'weedops' ),
],
'hierarchical' => false,
'show_in_rest' => true,
'rest_base' => 'effects',
'rewrite' => [ 'slug' => 'effect' ],
'show_admin_column' => true,
] );
// ── Terpenes ──────────────────────────────────────────────────────────────
register_taxonomy( 'wo_terpene', [ 'wo_strain', 'wo_product' ], [
'label' => __( 'Terpenes', 'weedops' ),
'hierarchical' => false,
'show_in_rest' => true,
'rest_base' => 'terpenes',
'rewrite' => [ 'slug' => 'terpene' ],
] );
// ── Product Category (flower / concentrate / edible / topical / …) ────────
register_taxonomy( 'wo_product_cat', [ 'wo_product' ], [
'label' => __( 'Product Category', 'weedops' ),
'hierarchical' => true,
'show_in_rest' => true,
'rest_base' => 'product-categories',
'rewrite' => [ 'slug' => 'product-category' ],
] );
// ── Compliance Status ─────────────────────────────────────────────────────
register_taxonomy( 'wo_compliance_status', [ 'wo_compliance', 'wo_product' ], [
'label' => __( 'Compliance Status', 'weedops' ),
'hierarchical' => true,
'show_in_rest' => true,
'public' => false,
'show_ui' => true,
] );
}
add_action( 'init', 'weedops_register_taxonomies' );
// ─────────────────────────────────────────────────────────────────────────────
// Meta Field Registration (REST API + Gutenberg)
// ─────────────────────────────────────────────────────────────────────────────
function weedops_register_meta_fields(): void {
// Strain meta
$strain_meta = [
'wo_thc_percent' => [ 'type' => 'number', 'label' => 'THC %', 'sanitize' => 'floatval' ],
'wo_cbd_percent' => [ 'type' => 'number', 'label' => 'CBD %', 'sanitize' => 'floatval' ],
'wo_genetics' => [ 'type' => 'string', 'label' => 'Genetics / Lineage', 'sanitize' => 'sanitize_text_field' ],
'wo_flowering_time' => [ 'type' => 'integer', 'label' => 'Flowering Time (days)', 'sanitize' => 'absint' ],
'wo_yield_indoor' => [ 'type' => 'string', 'label' => 'Indoor Yield', 'sanitize' => 'sanitize_text_field' ],
'wo_yield_outdoor' => [ 'type' => 'string', 'label' => 'Outdoor Yield', 'sanitize' => 'sanitize_text_field' ],
'wo_difficulty' => [ 'type' => 'string', 'label' => 'Grow Difficulty', 'sanitize' => 'sanitize_text_field' ],
];
foreach ( $strain_meta as $key => $config ) {
register_post_meta( 'wo_strain', $key, [
'type' => $config['type'],
'single' => true,
'show_in_rest' => true,
'description' => $config['label'],
'sanitize_callback' => $config['sanitize'],
'auth_callback' => fn() => current_user_can( 'edit_posts' ),
] );
}
// Product meta
$product_meta = [
'wo_sku' => [ 'type' => 'string', 'label' => 'SKU / License #', 'sanitize' => 'sanitize_text_field' ],
'wo_batch_id' => [ 'type' => 'string', 'label' => 'Batch ID', 'sanitize' => 'sanitize_text_field' ],
'wo_lab_tested' => [ 'type' => 'boolean', 'label' => 'Lab Tested', 'sanitize' => 'rest_sanitize_boolean' ],
'wo_coa_url' => [ 'type' => 'string', 'label' => 'COA (Certificate of Analysis) URL','sanitize' => 'esc_url_raw' ],
'wo_price_gram' => [ 'type' => 'number', 'label' => 'Price per Gram', 'sanitize' => 'floatval' ],
'wo_inventory_qty' => [ 'type' => 'integer', 'label' => 'Inventory Qty (g)', 'sanitize' => 'absint' ],
'wo_origin_state' => [ 'type' => 'string', 'label' => 'Origin State', 'sanitize' => 'sanitize_text_field' ],
'wo_license_no' => [ 'type' => 'string', 'label' => 'Producer License #', 'sanitize' => 'sanitize_text_field' ],
];
foreach ( $product_meta as $key => $config ) {
register_post_meta( 'wo_product', $key, [
'type' => $config['type'],
'single' => true,
'show_in_rest' => true,
'description' => $config['label'],
'sanitize_callback' => $config['sanitize'],
'auth_callback' => fn() => current_user_can( 'edit_posts' ),
] );
}
}
add_action( 'init', 'weedops_register_meta_fields' );
// ─────────────────────────────────────────────────────────────────────────────
// Admin Meta Box — Strain Details
// ─────────────────────────────────────────────────────────────────────────────
function weedops_add_strain_meta_box(): void {
add_meta_box(
'wo_strain_details',
__( 'Strain Details', 'weedops' ),
'weedops_render_strain_meta_box',
'wo_strain',
'normal',
'high'
);
}
add_action( 'add_meta_boxes', 'weedops_add_strain_meta_box' );
function weedops_render_strain_meta_box( WP_Post $post ): void {
wp_nonce_field( 'wo_save_strain_details', 'wo_strain_nonce' );
$thc = get_post_meta( $post->ID, 'wo_thc_percent', true );
$cbd = get_post_meta( $post->ID, 'wo_cbd_percent', true );
$genetics = get_post_meta( $post->ID, 'wo_genetics', true );
$difficulty = get_post_meta( $post->ID, 'wo_difficulty', true );
// Strain type from taxonomy
$strain_types = get_the_terms( $post->ID, 'wo_strain_type' );
$current_type = ( $strain_types && ! is_wp_error( $strain_types ) )
? $strain_types[0]->slug
: '';
// Effects from taxonomy
$effects_terms = get_terms( [ 'taxonomy' => 'wo_effect', 'hide_empty' => false ] );
$assigned_effects = wp_get_post_terms( $post->ID, 'wo_effect', [ 'fields' => 'ids' ] );
?>
<style>
.wo-metabox-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:16px; }
.wo-metabox-field label { display:block; font-weight:600; margin-bottom:4px; }
.wo-metabox-field input,
.wo-metabox-field select { width:100%; }
.wo-effects-list { columns:3; gap:12px; }
.wo-effects-list label { display:block; font-weight:normal; margin-bottom:4px; }
</style>
<div class="wo-metabox-grid">
<div class="wo-metabox-field">
<label for="wo_thc_percent"><?php esc_html_e( 'THC %', 'weedops' ); ?></label>
<input type="number" id="wo_thc_percent" name="wo_thc_percent"
value="<?php echo esc_attr( $thc ); ?>"
step="0.1" min="0" max="100" placeholder="e.g. 22.5" />
</div>
<div class="wo-metabox-field">
<label for="wo_cbd_percent"><?php esc_html_e( 'CBD %', 'weedops' ); ?></label>
<input type="number" id="wo_cbd_percent" name="wo_cbd_percent"
value="<?php echo esc_attr( $cbd ); ?>"
step="0.1" min="0" max="100" placeholder="e.g. 0.5" />
</div>
<div class="wo-metabox-field">
<label for="wo_strain_type_select"><?php esc_html_e( 'Strain Type', 'weedops' ); ?></label>
<select id="wo_strain_type_select" name="wo_strain_type_select">
<option value=""><?php esc_html_e( '— Select type —', 'weedops' ); ?></option>
<?php foreach ( [ 'indica' => 'Indica', 'sativa' => 'Sativa', 'hybrid' => 'Hybrid', 'cbd' => 'CBD / High-CBD' ] as $slug => $label ) : ?>
<option value="<?php echo esc_attr( $slug ); ?>"
<?php selected( $current_type, $slug ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="wo-metabox-field">
<label for="wo_difficulty"><?php esc_html_e( 'Grow Difficulty', 'weedops' ); ?></label>
<select id="wo_difficulty" name="wo_difficulty">
<option value=""><?php esc_html_e( '— Select —', 'weedops' ); ?></option>
<?php foreach ( [ 'beginner' => 'Beginner', 'intermediate' => 'Intermediate', 'advanced' => 'Advanced' ] as $val => $lbl ) : ?>
<option value="<?php echo esc_attr( $val ); ?>"
<?php selected( $difficulty, $val ); ?>>
<?php echo esc_html( $lbl ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="wo-metabox-field" style="margin-bottom:16px;">
<label for="wo_genetics"><?php esc_html_e( 'Genetics / Lineage', 'weedops' ); ?></label>
<input type="text" id="wo_genetics" name="wo_genetics"
value="<?php echo esc_attr( $genetics ); ?>"
placeholder="e.g. OG Kush × Durban Poison" style="width:100%;" />
</div>
<?php if ( ! is_wp_error( $effects_terms ) && ! empty( $effects_terms ) ) : ?>
<div class="wo-metabox-field">
<label><?php esc_html_e( 'Effects', 'weedops' ); ?></label>
<div class="wo-effects-list">
<?php foreach ( $effects_terms as $effect ) : ?>
<label>
<input type="checkbox" name="wo_effect_terms[]"
value="<?php echo esc_attr( $effect->term_id ); ?>"
<?php checked( in_array( $effect->term_id, (array) $assigned_effects, true ) ); ?> />
<?php echo esc_html( $effect->name ); ?>
</label>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php
}
function weedops_save_strain_meta( int $post_id ): void {
// Bail on autosave / AJAX / wrong post type
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( ! isset( $_POST['wo_strain_nonce'] ) ) return;
if ( ! wp_verify_nonce( sanitize_key( $_POST['wo_strain_nonce'] ), 'wo_save_strain_details' ) ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
if ( get_post_type( $post_id ) !== 'wo_strain' ) return;
// THC %
if ( isset( $_POST['wo_thc_percent'] ) ) {
$thc = (float) $_POST['wo_thc_percent'];
update_post_meta( $post_id, 'wo_thc_percent', $thc );
}
// CBD %
if ( isset( $_POST['wo_cbd_percent'] ) ) {
$cbd = (float) $_POST['wo_cbd_percent'];
update_post_meta( $post_id, 'wo_cbd_percent', $cbd );
}
// Genetics
if ( isset( $_POST['wo_genetics'] ) ) {
update_post_meta( $post_id, 'wo_genetics', sanitize_text_field( wp_unslash( $_POST['wo_genetics'] ) ) );
}
// Difficulty
if ( isset( $_POST['wo_difficulty'] ) ) {
$allowed = [ 'beginner', 'intermediate', 'advanced', '' ];
$diff = in_array( $_POST['wo_difficulty'], $allowed, true ) ? $_POST['wo_difficulty'] : '';
update_post_meta( $post_id, 'wo_difficulty', $diff );
}
// Strain type — set taxonomy term by slug
if ( isset( $_POST['wo_strain_type_select'] ) ) {
$type_slug = sanitize_key( $_POST['wo_strain_type_select'] );
if ( ! empty( $type_slug ) ) {
// Make sure the term exists (create if needed)
$term = get_term_by( 'slug', $type_slug, 'wo_strain_type' );
if ( ! $term ) {
$inserted = wp_insert_term( ucfirst( $type_slug ), 'wo_strain_type', [ 'slug' => $type_slug ] );
$term_id = is_wp_error( $inserted ) ? 0 : $inserted['term_id'];
} else {
$term_id = $term->term_id;
}
if ( $term_id ) {
wp_set_post_terms( $post_id, [ $term_id ], 'wo_strain_type' );
}
} else {
wp_set_post_terms( $post_id, [], 'wo_strain_type' );
}
}
// Effects — array of term IDs
$effect_ids = isset( $_POST['wo_effect_terms'] )
? array_map( 'absint', (array) $_POST['wo_effect_terms'] )
: [];
wp_set_post_terms( $post_id, $effect_ids, 'wo_effect' );
}
add_action( 'save_post_wo_strain', 'weedops_save_strain_meta' );

144
index.php Normal file
View file

@ -0,0 +1,144 @@
<?php
/**
* Weedops Main Template File
*
* Fallback template used when no more specific template matches.
* Also serves as the blog/post archive.
*
* @package Weedops
*/
get_header();
?>
<?php if ( is_home() && ! is_front_page() ) : ?>
<header class="wo-page-header wo-mb-3">
<h1 class="wo-page-title"><?php esc_html_e( 'Latest Posts', 'weedops' ); ?></h1>
</header>
<?php endif; ?>
<?php if ( have_posts() ) : ?>
<div class="wo-grid wo-grid--3">
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class( 'wo-card' ); ?>>
<?php if ( has_post_thumbnail() ) : ?>
<a href="<?php the_permalink(); ?>" class="wo-card__thumb" aria-hidden="true" tabindex="-1">
<?php
the_post_thumbnail( 'medium', [
'class' => 'wo-card__img',
'loading' => 'lazy',
'style' => 'width:100%;height:180px;object-fit:cover;border-radius:8px 8px 0 0;margin:-1.5rem -1.5rem 1rem;width:calc(100% + 3rem);',
] );
?>
</a>
<?php endif; ?>
<?php // Post type / taxonomy badges ?>
<div class="wo-card__meta" style="margin-bottom:0.6rem;">
<?php
$post_type = get_post_type();
if ( 'wo_strain' === $post_type ) {
$types = get_the_terms( get_the_ID(), 'wo_strain_type' );
if ( $types && ! is_wp_error( $types ) ) {
foreach ( $types as $t ) {
weedops_strain_badge( $t->slug );
}
}
} elseif ( 'wo_product' === $post_type ) {
$cats = get_the_terms( get_the_ID(), 'wo_product_cat' );
if ( $cats && ! is_wp_error( $cats ) ) {
echo '<span class="wo-badge" style="background:#1a2e3d;color:#b3d9ff;">';
echo esc_html( $cats[0]->name );
echo '</span>';
}
} else {
echo '<span class="wo-text-muted" style="font-size:0.78rem;">';
echo esc_html( get_the_category_list( ', ' ) );
echo '</span>';
}
?>
</div>
<h2 class="wo-card__title" style="font-size:1.1rem;margin-bottom:0.5rem;">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
<?php // Cannabinoid info for strains/products ?>
<?php if ( in_array( $post_type, [ 'wo_strain', 'wo_product' ], true ) ) : ?>
<?php $canna_str = weedops_get_cannabinoid_str( get_the_ID() ); ?>
<?php if ( $canna_str ) : ?>
<p style="font-size:0.82rem;font-weight:600;color:var(--wo-green-mid);margin-bottom:0.5rem;">
<?php echo esc_html( $canna_str ); ?>
</p>
<?php endif; ?>
<?php endif; ?>
<div class="wo-card__excerpt" style="font-size:0.875rem;color:var(--wo-text-muted);margin-bottom:1rem;">
<?php the_excerpt(); ?>
</div>
<div class="wo-card__footer" style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.5rem;">
<a href="<?php the_permalink(); ?>" class="wo-btn wo-btn--outline" style="font-size:0.8rem;padding:0.4rem 0.8rem;">
<?php esc_html_e( 'View Details', 'weedops' ); ?>
</a>
<?php // COA badge for lab-tested products ?>
<?php if ( 'wo_product' === $post_type && weedops_has_valid_coa( get_the_ID() ) ) : ?>
<span class="wo-compliance-tag wo-compliance-tag--ok">
<?php esc_html_e( 'Lab Tested', 'weedops' ); ?>
</span>
<?php endif; ?>
<?php // Date for blog posts ?>
<?php if ( 'post' === $post_type ) : ?>
<time datetime="<?php echo esc_attr( get_the_date( 'c' ) ); ?>" style="font-size:0.78rem;color:var(--wo-text-muted);">
<?php echo esc_html( get_the_date() ); ?>
</time>
<?php endif; ?>
</div>
</article>
<?php endwhile; ?>
</div><!-- .wo-grid -->
<?php // Pagination ?>
<nav class="wo-pagination wo-mt-3" role="navigation" aria-label="<?php esc_attr_e( 'Posts pagination', 'weedops' ); ?>">
<?php
the_posts_pagination( [
'mid_size' => 2,
'prev_text' => '&larr; ' . __( 'Previous', 'weedops' ),
'next_text' => __( 'Next', 'weedops' ) . ' &rarr;',
'before_page_number' => '<span class="wo-sr-only">' . __( 'Page', 'weedops' ) . ' </span>',
] );
?>
</nav>
<?php else : ?>
<?php // No posts found ?>
<section class="wo-card wo-text-center" style="max-width:560px;margin:4rem auto;">
<svg width="56" height="56" viewBox="0 0 28 28" fill="none" aria-hidden="true" style="margin:0 auto 1rem;display:block;opacity:0.25;">
<path d="M14 2C8.5 2 4 6.5 4 12c0 3.5 2 6.5 5 8.5L14 26l5-5.5c3-2 5-5 5-8.5C24 6.5 19.5 2 14 2z" fill="#1a3d2b"/>
</svg>
<h2 style="font-size:1.3rem;"><?php esc_html_e( 'Nothing found', 'weedops' ); ?></h2>
<p class="wo-text-muted wo-mt-1">
<?php esc_html_e( 'No content matches your query. Try searching or browse our strain database.', 'weedops' ); ?>
</p>
<div class="wo-mt-2">
<?php get_search_form(); ?>
</div>
<div class="wo-mt-2">
<a href="<?php echo esc_url( home_url( '/strains' ) ); ?>" class="wo-btn wo-btn--primary">
<?php esc_html_e( 'Browse Strains', 'weedops' ); ?>
</a>
</div>
</section>
<?php endif; ?>
<?php
get_footer();

224
single-strain.php Normal file
View file

@ -0,0 +1,224 @@
<?php
/**
* Single Strain Template
* Displays individual strain details: THC%, CBD%, type, effects, and product image.
*
* @package Weedops
*/
get_header(); ?>
<main id="main" class="weedops-main">
<?php while ( have_posts() ) : the_post(); ?>
<article id="strain-<?php the_ID(); ?>" <?php post_class( 'strain-single' ); ?>>
<div class="strain-hero">
<div class="strain-hero__image">
<?php if ( has_post_thumbnail() ) : ?>
<?php the_post_thumbnail( 'large', [ 'class' => 'strain-hero__img' ] ); ?>
<?php else : ?>
<div class="strain-hero__placeholder">
<div class="strain-hero__placeholder-inner">
<span class="strain-hero__placeholder-icon">🌿</span>
<span class="strain-hero__placeholder-text">Product Image Coming Soon</span>
</div>
</div>
<?php endif; ?>
</div>
<div class="strain-hero__info">
<div class="strain-badge strain-badge--<?php echo esc_attr( get_post_meta( get_the_ID(), '_strain_type', true ) ); ?>">
<?php
$strain_type = get_post_meta( get_the_ID(), '_strain_type', true );
echo esc_html( ucfirst( $strain_type ? $strain_type : 'Unknown' ) );
?>
</div>
<h1 class="strain-title"><?php the_title(); ?></h1>
<div class="strain-potency">
<?php
$thc = get_post_meta( get_the_ID(), '_strain_thc', true );
$cbd = get_post_meta( get_the_ID(), '_strain_cbd', true );
?>
<?php if ( $thc !== '' ) : ?>
<div class="potency-card potency-card--thc">
<span class="potency-card__label">THC</span>
<span class="potency-card__value"><?php echo esc_html( $thc ); ?>%</span>
<div class="potency-bar">
<div class="potency-bar__fill potency-bar__fill--thc" style="width: <?php echo esc_attr( min( floatval( $thc ), 35 ) / 35 * 100 ); ?>%"></div>
</div>
</div>
<?php endif; ?>
<?php if ( $cbd !== '' ) : ?>
<div class="potency-card potency-card--cbd">
<span class="potency-card__label">CBD</span>
<span class="potency-card__value"><?php echo esc_html( $cbd ); ?>%</span>
<div class="potency-bar">
<div class="potency-bar__fill potency-bar__fill--cbd" style="width: <?php echo esc_attr( min( floatval( $cbd ), 25 ) / 25 * 100 ); ?>%"></div>
</div>
</div>
<?php endif; ?>
</div>
<?php
$effects_raw = get_post_meta( get_the_ID(), '_strain_effects', true );
$effects = array_filter( array_map( 'trim', explode( ',', $effects_raw ) ) );
if ( ! empty( $effects ) ) :
?>
<div class="strain-effects">
<h3 class="strain-effects__heading">Effects</h3>
<div class="strain-effects__tags">
<?php foreach ( $effects as $effect ) : ?>
<span class="effect-tag"><?php echo esc_html( $effect ); ?></span>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php if ( get_the_content() ) : ?>
<div class="strain-description">
<h2 class="strain-description__heading">About This Strain</h2>
<div class="strain-description__content">
<?php the_content(); ?>
</div>
</div>
<?php endif; ?>
<div class="strain-compliance-notice">
<p>⚠️ For use only by adults 21+. Keep out of reach of children. This product has not been evaluated by the FDA.</p>
</div>
<nav class="strain-nav">
<?php
$prev = get_previous_post();
$next = get_next_post();
if ( $prev ) :
?>
<a href="<?php echo esc_url( get_permalink( $prev ) ); ?>" class="strain-nav__link strain-nav__link--prev">
<?php echo esc_html( get_the_title( $prev ) ); ?>
</a>
<?php endif; ?>
<a href="<?php echo esc_url( get_post_type_archive_link( 'strain' ) ); ?>" class="strain-nav__link strain-nav__link--archive">
All Strains
</a>
<?php if ( $next ) : ?>
<a href="<?php echo esc_url( get_permalink( $next ) ); ?>" class="strain-nav__link strain-nav__link--next">
<?php echo esc_html( get_the_title( $next ) ); ?>
</a>
<?php endif; ?>
</nav>
</article>
<?php endwhile; ?>
</main>
<style>
/* ── Single Strain Styles ─────────────────────────────── */
.strain-single { max-width: 1100px; margin: 0 auto; padding: 2rem 1.5rem; }
.strain-hero {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
align-items: start;
margin-bottom: 3rem;
}
@media (max-width: 768px) {
.strain-hero { grid-template-columns: 1fr; gap: 1.5rem; }
}
/* Image / Placeholder */
.strain-hero__img { width: 100%; border-radius: 12px; object-fit: cover; aspect-ratio: 4/3; }
.strain-hero__placeholder {
width: 100%; aspect-ratio: 4/3;
background: linear-gradient(135deg, #1a3a1a 0%, #2d5a2d 100%);
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
border: 2px dashed #4a8c4a;
}
.strain-hero__placeholder-inner { text-align: center; color: #8ec88e; }
.strain-hero__placeholder-icon { font-size: 3rem; display: block; margin-bottom: .5rem; }
.strain-hero__placeholder-text { font-size: .9rem; letter-spacing: .05em; }
/* Badge */
.strain-badge {
display: inline-block;
padding: .3rem .9rem;
border-radius: 999px;
font-size: .75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .08em;
margin-bottom: 1rem;
}
.strain-badge--indica { background: #4a148c; color: #e1bee7; }
.strain-badge--sativa { background: #e65100; color: #ffe0b2; }
.strain-badge--hybrid { background: #1b5e20; color: #c8e6c9; }
/* Title */
.strain-title { font-size: 2.5rem; font-weight: 800; margin: 0 0 1.5rem; color: #1a3a1a; line-height: 1.2; }
/* Potency */
.strain-potency { display: flex; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
.potency-card {
flex: 1; min-width: 120px;
background: #f4f9f4;
border-radius: 10px;
padding: 1rem;
border-left: 4px solid;
}
.potency-card--thc { border-color: #388e3c; }
.potency-card--cbd { border-color: #0288d1; }
.potency-card__label { display: block; font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: #666; }
.potency-card__value { display: block; font-size: 2rem; font-weight: 800; color: #1a3a1a; line-height: 1.1; margin-bottom: .4rem; }
.potency-bar { height: 6px; background: #ddd; border-radius: 3px; overflow: hidden; }
.potency-bar__fill--thc { height: 100%; background: #388e3c; border-radius: 3px; transition: width .4s ease; }
.potency-bar__fill--cbd { height: 100%; background: #0288d1; border-radius: 3px; transition: width .4s ease; }
/* Effects */
.strain-effects__heading { font-size: .85rem; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: #555; margin-bottom: .6rem; }
.strain-effects__tags { display: flex; flex-wrap: wrap; gap: .4rem; }
.effect-tag {
background: #e8f5e9; color: #2e7d32;
border: 1px solid #a5d6a7;
border-radius: 999px;
padding: .25rem .8rem;
font-size: .85rem;
font-weight: 500;
}
/* Description */
.strain-description { background: #f9fafb; border-radius: 12px; padding: 2rem; margin-bottom: 2rem; }
.strain-description__heading { font-size: 1.3rem; font-weight: 700; color: #1a3a1a; margin-top: 0; }
/* Compliance */
.strain-compliance-notice {
background: #fff8e1; border-left: 4px solid #fbc02d;
border-radius: 6px; padding: .8rem 1.2rem;
font-size: .8rem; color: #5d4037;
margin-bottom: 2rem;
}
.strain-compliance-notice p { margin: 0; }
/* Nav */
.strain-nav { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; padding-top: 1.5rem; border-top: 1px solid #e0e0e0; }
.strain-nav__link {
color: #2e7d32; font-weight: 600; text-decoration: none;
padding: .5rem 1rem; border-radius: 6px;
border: 1px solid #a5d6a7;
transition: background .2s;
}
.strain-nav__link:hover { background: #e8f5e9; }
.strain-nav__link--archive { background: #2e7d32; color: #fff; border-color: #2e7d32; }
.strain-nav__link--archive:hover { background: #1b5e20; }
</style>
<?php get_footer(); ?>

393
style.css Normal file
View file

@ -0,0 +1,393 @@
/*
Theme Name: Weedops
Theme URI: https://weedops.site
Author: Weedops Dev Team
Author URI: https://weedops.site
Description: Cannabis industry product management and compliance platform theme. Built for dispensaries, growers, and cannabis brands.
Version: 1.0.0
Requires at least: 6.0
Tested up to: 6.7
Requires PHP: 8.0
License: Proprietary
License URI: https://weedops.site/license
Text Domain: weedops
Tags: cannabis, dispensary, woocommerce, compliance, inventory
*/
/* ============================================================
WEEDOPS THEME Cannabis Industry Platform
Brand: Deep green (#1a3d2b), Gold (#c8a951), Off-white (#f5f0e8)
============================================================ */
:root {
--wo-green-dark: #1a3d2b;
--wo-green-mid: #2e6b47;
--wo-green-light: #4a9e68;
--wo-gold: #c8a951;
--wo-gold-light: #e2c97a;
--wo-cream: #f5f0e8;
--wo-white: #ffffff;
--wo-text-dark: #1c1c1c;
--wo-text-muted: #666666;
--wo-border: #d4cfc6;
--wo-error: #c0392b;
--wo-success: #27ae60;
--wo-font-sans: 'Inter', 'Helvetica Neue', Arial, sans-serif;
--wo-font-serif: 'Georgia', 'Times New Roman', serif;
--wo-font-mono: 'JetBrains Mono', 'Courier New', monospace;
--wo-radius: 6px;
--wo-radius-lg: 12px;
--wo-shadow: 0 2px 8px rgba(0,0,0,0.12);
--wo-shadow-lg: 0 8px 32px rgba(0,0,0,0.18);
--wo-transition: 0.2s ease;
}
/* ── Reset ────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 16px; scroll-behavior: smooth; }
body {
font-family: var(--wo-font-sans);
color: var(--wo-text-dark);
background-color: var(--wo-cream);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
/* ── Typography ───────────────────────────────────────────── */
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
line-height: 1.25;
color: var(--wo-green-dark);
}
h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); }
h2 { font-size: clamp(1.4rem, 3vw, 2.1rem); }
h3 { font-size: clamp(1.1rem, 2.5vw, 1.5rem); }
h4 { font-size: 1.15rem; }
p { margin-bottom: 1rem; }
a { color: var(--wo-green-mid); text-decoration: none; transition: color var(--wo-transition); }
a:hover { color: var(--wo-gold); }
/* ── Layout ───────────────────────────────────────────────── */
.wo-container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 0 1.5rem;
}
.wo-main {
min-height: calc(100vh - 80px - 160px);
padding: 2.5rem 0;
}
.wo-grid {
display: grid;
gap: 1.5rem;
}
.wo-grid--2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.wo-grid--3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.wo-grid--4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
/* ── Header ───────────────────────────────────────────────── */
.wo-header {
background-color: var(--wo-green-dark);
border-bottom: 3px solid var(--wo-gold);
position: sticky;
top: 0;
z-index: 100;
box-shadow: var(--wo-shadow);
}
.wo-header__inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 70px;
}
.wo-logo {
display: flex;
align-items: center;
gap: 0.6rem;
color: var(--wo-white);
font-size: 1.4rem;
font-weight: 800;
letter-spacing: -0.02em;
}
.wo-logo span { color: var(--wo-gold); }
.wo-logo:hover { color: var(--wo-gold-light); }
/* ── Navigation ───────────────────────────────────────────── */
.wo-nav ul {
display: flex;
align-items: center;
gap: 0.25rem;
list-style: none;
}
.wo-nav a {
color: rgba(255,255,255,0.85);
padding: 0.5rem 0.9rem;
border-radius: var(--wo-radius);
font-size: 0.9rem;
font-weight: 500;
transition: background var(--wo-transition), color var(--wo-transition);
}
.wo-nav a:hover,
.wo-nav .current-menu-item > a {
background: rgba(200,169,81,0.2);
color: var(--wo-gold-light);
}
/* ── Buttons ──────────────────────────────────────────────── */
.wo-btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.6rem 1.2rem;
border-radius: var(--wo-radius);
font-size: 0.9rem;
font-weight: 600;
border: 2px solid transparent;
cursor: pointer;
transition: all var(--wo-transition);
text-decoration: none;
line-height: 1;
}
.wo-btn--primary {
background: var(--wo-gold);
color: var(--wo-green-dark);
border-color: var(--wo-gold);
}
.wo-btn--primary:hover {
background: var(--wo-gold-light);
border-color: var(--wo-gold-light);
color: var(--wo-green-dark);
}
.wo-btn--outline {
background: transparent;
color: var(--wo-green-dark);
border-color: var(--wo-green-mid);
}
.wo-btn--outline:hover {
background: var(--wo-green-dark);
color: var(--wo-white);
}
/* ── Cards ────────────────────────────────────────────────── */
.wo-card {
background: var(--wo-white);
border-radius: var(--wo-radius-lg);
border: 1px solid var(--wo-border);
padding: 1.5rem;
box-shadow: var(--wo-shadow);
transition: box-shadow var(--wo-transition), transform var(--wo-transition);
}
.wo-card:hover {
box-shadow: var(--wo-shadow-lg);
transform: translateY(-2px);
}
/* ── Strain / Product badges ──────────────────────────────── */
.wo-badge {
display: inline-flex;
align-items: center;
padding: 0.2rem 0.6rem;
border-radius: 999px;
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.wo-badge--indica { background: #3d1a5e; color: #e8ccff; }
.wo-badge--sativa { background: #1a3d1a; color: #ccffcc; }
.wo-badge--hybrid { background: #3d2e1a; color: #ffe0b2; }
.wo-badge--cbd { background: #1a2e3d; color: #b3d9ff; }
/* ── Compliance tags ──────────────────────────────────────── */
.wo-compliance-tag {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.25rem 0.7rem;
border-radius: var(--wo-radius);
font-size: 0.78rem;
font-weight: 600;
border: 1px solid currentColor;
}
.wo-compliance-tag--ok { color: var(--wo-success); }
.wo-compliance-tag--warning { color: var(--wo-gold); }
.wo-compliance-tag--fail { color: var(--wo-error); }
/* ── Footer ───────────────────────────────────────────────── */
.wo-footer {
background: var(--wo-green-dark);
color: rgba(255,255,255,0.7);
padding: 2.5rem 0 1.5rem;
margin-top: auto;
}
.wo-footer__grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.wo-footer__brand .wo-logo { font-size: 1.2rem; margin-bottom: 0.75rem; }
.wo-footer p { font-size: 0.85rem; line-height: 1.7; }
.wo-footer h4 {
color: var(--wo-gold);
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 0.75rem;
}
.wo-footer ul { list-style: none; }
.wo-footer ul li { margin-bottom: 0.4rem; }
.wo-footer ul a { color: rgba(255,255,255,0.65); font-size: 0.875rem; }
.wo-footer ul a:hover { color: var(--wo-gold-light); }
.wo-footer__bottom {
border-top: 1px solid rgba(255,255,255,0.12);
padding-top: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 0.5rem;
}
/* ── Age-gate / Compliance notice ─────────────────────────── */
.wo-age-notice {
background: var(--wo-green-dark);
color: var(--wo-cream);
text-align: center;
padding: 0.5rem 1rem;
font-size: 0.8rem;
border-bottom: 1px solid var(--wo-gold);
}
/* ── Hero (Photography Style) ────────────────────────────── */
.wo-hero {
position: relative;
min-height: 85vh;
display: flex;
align-items: center;
justify-content: center;
background:
url('assets/img/hero-cannabis.jpg')
center center / cover
no-repeat;
overflow: hidden;
}
.wo-hero__overlay {
position: absolute;
inset: 0;
background: linear-gradient(
160deg,
rgba(26, 61, 43, 0.88) 0%,
rgba(26, 61, 43, 0.55) 50%,
rgba(0, 0, 0, 0.40) 100%
);
z-index: 1;
}
.wo-hero__content {
position: relative;
z-index: 2;
max-width: 720px;
padding: 3rem 2rem;
text-align: center;
}
.wo-hero__eyebrow {
display: inline-block;
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.18em;
color: var(--wo-gold);
margin-bottom: 1.25rem;
padding: 0.35rem 1rem;
border: 1px solid rgba(200, 169, 81, 0.4);
border-radius: 999px;
}
.wo-hero__title {
font-size: clamp(2rem, 5vw, 3.4rem);
font-weight: 800;
line-height: 1.12;
color: var(--wo-white);
margin-bottom: 1.25rem;
letter-spacing: -0.02em;
}
.wo-hero__subtitle {
font-size: clamp(1rem, 2vw, 1.2rem);
color: rgba(255, 255, 255, 0.85);
line-height: 1.7;
margin-bottom: 2rem;
}
.wo-hero__actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.wo-btn--lg {
padding: 0.85rem 1.8rem;
font-size: 1rem;
}
.wo-btn--ghost {
background: transparent;
color: var(--wo-white);
border-color: rgba(255, 255, 255, 0.5);
}
.wo-btn--ghost:hover {
background: rgba(255, 255, 255, 0.12);
border-color: var(--wo-white);
color: var(--wo-white);
}
/* ── Sections ────────────────────────────────────────────── */
.wo-section { padding: 1rem 0; }
/* ── Utilities ────────────────────────────────────────────── */
.wo-text-center { text-align: center; }
.wo-text-muted { color: var(--wo-text-muted); }
.wo-mt-1 { margin-top: 0.5rem; }
.wo-mt-2 { margin-top: 1rem; }
.wo-mt-3 { margin-top: 1.5rem; }
.wo-mb-2 { margin-bottom: 1rem; }
.wo-mb-3 { margin-bottom: 1.5rem; }
.wo-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
/* ── Responsive ───────────────────────────────────────────── */
@media (max-width: 768px) {
.wo-header__inner { height: 60px; }
.wo-nav { display: none; } /* mobile nav handled by JS toggle */
.wo-footer__bottom { flex-direction: column; text-align: center; }
}