feat(theme): About, Contact, FAQ, blog list and post pages from new front

- Route pages by address to theme templates (about-us, contact-us, faq, blogs, posts)
- Scoped design CSS (pages.css) + Astra/WordPress fixes (pages-wp.css), page JS
- Contact form emails the new Settings -> Viator Collector -> Contact email
  (fallback: admin email), Reply-To the visitor, honeypot spam trap
- Blog list shows real posts with search, sort and paging; posts use the
  article design with comments as reviews and related articles
- Design images converted to WebP (~15MB PNG -> ~1MB)
- Theme 1.0.10, plugin 1.9.1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 16:20:11 +03:30
parent 930b7e0534
commit f58ed49d07
30 changed files with 5932 additions and 14 deletions

View File

@@ -0,0 +1,129 @@
<?php
/**
* Blog list — "new front/blog.html" with real posts. Search, sort and paging
* are server-side GET parameters (q, sort, pg); see hoho_blog_state().
*/
defined( 'ABSPATH' ) || exit;
get_header();
$hoho_state = hoho_blog_state();
$hoho_query = hoho_blog_query( $hoho_state );
$hoho_base = is_home() ? get_permalink( get_option( 'page_for_posts' ) ) : get_permalink();
$hoho_base = $hoho_base ? $hoho_base : home_url( '/' );
$hoho_max = (int) $hoho_query->max_num_pages;
$hoho_pg = min( $hoho_state['pg'], max( 1, $hoho_max ) );
$hoho_url = function ( $pg ) use ( $hoho_base, $hoho_state ) {
$args = array();
if ( '' !== $hoho_state['q'] ) {
$args['q'] = $hoho_state['q'];
}
if ( 'newest' !== $hoho_state['sort'] ) {
$args['sort'] = $hoho_state['sort'];
}
if ( $pg > 1 ) {
$args['pg'] = $pg;
}
return add_query_arg( $args, $hoho_base ) . '#blog-list';
};
// Page numbers with gaps: 1 … 4 5 6 … 12.
$hoho_range = array();
for ( $n = 1; $n <= $hoho_max; $n++ ) {
if ( $n <= 1 || $n >= $hoho_max || abs( $n - $hoho_pg ) <= 1 ) {
$hoho_range[] = $n;
} elseif ( null !== end( $hoho_range ) ) {
$hoho_range[] = null;
}
}
?>
<main id="main" class="hoho-page">
<section class="hero hero--archive hero--blog">
<div class="hero__media">
<img class="hero__img" src="<?php echo esc_url( hoho_asset( 'img/blogs-hero.webp' ) ); ?>" alt="" fetchpriority="high" />
<div class="hero__scrim" aria-hidden="true"></div>
</div>
<div class="container hero__content">
<p class="pill hero__eyebrow"><?php echo esc_html__( 'Tour & Travel Articles', 'hoponhopoff' ); ?></p>
<h1 class="hero__title"><?php echo esc_html__( 'HOHO Weblog', 'hoponhopoff' ); ?></h1>
<form class="filters filters--blog" id="filters" method="get" action="<?php echo esc_url( $hoho_base ); ?>" role="search">
<div class="filters__row">
<div class="fsearch">
<?php echo hoho_icon( 'search', 'fsearch__icon' ); // phpcs:ignore ?>
<input class="fsearch__input" id="f-q" name="q" type="search" value="<?php echo esc_attr( $hoho_state['q'] ); ?>" placeholder="<?php echo esc_attr__( 'Search Tours, cities ...', 'hoponhopoff' ); ?>" aria-label="<?php echo esc_attr__( 'Search articles', 'hoponhopoff' ); ?>" />
</div>
<div class="filters__sort">
<span class="filters__sort-label">
<?php echo hoho_icon( 'sort' ); // phpcs:ignore ?>
<label for="f-sort"><?php echo esc_html__( 'Sort by', 'hoponhopoff' ); ?></label>
</span>
<div class="fsel fsel--bare">
<select class="fsel__input" id="f-sort" name="sort" data-autosubmit>
<option value="newest" <?php selected( $hoho_state['sort'], 'newest' ); ?>><?php echo esc_html__( 'Newest', 'hoponhopoff' ); ?></option>
<option value="oldest" <?php selected( $hoho_state['sort'], 'oldest' ); ?>><?php echo esc_html__( 'Oldest', 'hoponhopoff' ); ?></option>
<option value="title" <?php selected( $hoho_state['sort'], 'title' ); ?>><?php echo esc_html__( 'Title A–Z', 'hoponhopoff' ); ?></option>
</select>
<?php echo hoho_icon( 'chevron', 'fsel__chevron' ); // phpcs:ignore ?>
</div>
</div>
</div>
</form>
</div>
</section>
<section class="section" id="blog-list">
<div class="container">
<div class="results">
<p class="results__count" role="status">
<?php
/* translators: %s: number of articles. */
echo esc_html( sprintf( _n( '%s article', '%s articles', (int) $hoho_query->found_posts, 'hoponhopoff' ), number_format_i18n( (int) $hoho_query->found_posts ) ) );
?>
</p>
<?php if ( '' !== $hoho_state['q'] ) : ?>
<a class="btn btn--ghost results__reset" href="<?php echo esc_url( $hoho_base ); ?>"><?php echo esc_html__( 'Clear search', 'hoponhopoff' ); ?></a>
<?php endif; ?>
</div>
<?php if ( $hoho_query->have_posts() ) : ?>
<ul class="tgrid" id="tourGrid">
<?php
foreach ( $hoho_query->posts as $hoho_post ) {
echo hoho_article_card( $hoho_post ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
</ul>
<?php else : ?>
<p class="tgrid__empty"><?php echo esc_html__( 'No articles match your search.', 'hoponhopoff' ); ?>
<a class="linkbtn" href="<?php echo esc_url( $hoho_base ); ?>"><?php echo esc_html__( 'Clear the search', 'hoponhopoff' ); ?></a>.</p>
<?php endif; ?>
<?php if ( $hoho_max > 1 ) : ?>
<nav class="pager" aria-label="<?php echo esc_attr__( 'Article list pages', 'hoponhopoff' ); ?>">
<?php if ( $hoho_pg > 1 ) : ?>
<a class="pager__btn pager__btn--icon" href="<?php echo esc_url( $hoho_url( $hoho_pg - 1 ) ); ?>" aria-label="<?php echo esc_attr__( 'Previous page', 'hoponhopoff' ); ?>"><?php echo hoho_icon( 'arrow-left' ); // phpcs:ignore ?></a>
<?php endif; ?>
<span class="pager__pages">
<?php foreach ( $hoho_range as $n ) : ?>
<?php if ( null === $n ) : ?>
<span class="pager__gap" aria-hidden="true">&hellip;</span>
<?php elseif ( $n === $hoho_pg ) : ?>
<span class="pager__num is-current" aria-current="page"><?php echo esc_html( $n ); ?></span>
<?php else : ?>
<a class="pager__num" href="<?php echo esc_url( $hoho_url( $n ) ); ?>"><?php echo esc_html( $n ); ?></a>
<?php endif; ?>
<?php endforeach; ?>
</span>
<?php if ( $hoho_pg < $hoho_max ) : ?>
<a class="pager__btn" href="<?php echo esc_url( $hoho_url( $hoho_pg + 1 ) ); ?>"><span><?php echo esc_html__( 'Next', 'hoponhopoff' ); ?></span><?php echo hoho_icon( 'arrow-right' ); // phpcs:ignore ?></a>
<?php endif; ?>
</nav>
<?php endif; ?>
</div>
</section>
</main>
<?php
get_footer();