feat: Enhance gallery functionality with lightbox feature and improve icon sprite accessibility
- Added a lightbox feature for gallery images, allowing users to view photos in a modal with navigation controls. - Updated the SVG sprite to include inline styles for better accessibility. - Increased CSS specificity by doubling the `.vc-app` class to ensure styles override Astra theme defaults. - Introduced new shortcodes for bus tours, about section, and articles in the VC_Sections class. - Improved the handling of featured images in the single tour template for sharper display. - Updated version number to 1.8.15.
This commit is contained in:
Binary file not shown.
@@ -421,6 +421,7 @@
|
||||
}
|
||||
|
||||
.hoho .ring-badge--top .ring-badge__text {
|
||||
fill: #808080;
|
||||
font-size: 0.625rem;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
@@ -546,15 +547,26 @@
|
||||
box-shadow: 0 1.25rem 2.5rem rgba(0, 0, 0, 0.18);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-0.375rem);
|
||||
transition: opacity 0.2s var(--ease), transform 0.2s var(--ease), visibility 0.2s;
|
||||
transform: translateY(-0.4rem) scale(0.97);
|
||||
transform-origin: top center;
|
||||
/* Exit: fade/slide out, then flip visibility only once the fade is done, so
|
||||
the close animates instead of snapping. */
|
||||
transition:
|
||||
opacity 0.16s var(--ease),
|
||||
transform 0.16s var(--ease),
|
||||
visibility 0s linear 0.16s;
|
||||
}
|
||||
|
||||
.hoho .nav__item--has-menu:hover .nav__submenu,
|
||||
.hoho .nav__toggle[aria-expanded="true"]+.nav__submenu {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
transform: translateY(0) scale(1);
|
||||
/* Enter: a slightly longer, soft ease-out so it settles in gently. */
|
||||
transition:
|
||||
opacity 0.24s var(--ease),
|
||||
transform 0.28s cubic-bezier(0.16, 1, 0.3, 1),
|
||||
visibility 0s;
|
||||
}
|
||||
|
||||
.hoho .nav__submenu a {
|
||||
@@ -1868,14 +1880,24 @@
|
||||
box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, 0.22);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-0.5rem);
|
||||
transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), visibility 0.25s;
|
||||
transform: translateY(-0.6rem) scale(0.98);
|
||||
transform-origin: top right;
|
||||
/* Exit: fade/slide up, then hide once the fade completes. */
|
||||
transition:
|
||||
opacity 0.18s var(--ease),
|
||||
transform 0.18s var(--ease),
|
||||
visibility 0s linear 0.18s;
|
||||
}
|
||||
|
||||
.hoho .nav.is-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
transform: translateY(0) scale(1);
|
||||
/* Enter: soft ease-out settle. */
|
||||
transition:
|
||||
opacity 0.26s var(--ease),
|
||||
transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
|
||||
visibility 0s;
|
||||
}
|
||||
|
||||
.hoho .nav__list {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg class="sprite" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="sprite" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0;overflow:hidden">
|
||||
<defs>
|
||||
<path id="ringPathScroll" fill="none" d="M50,50 m-39,0 a39,39 0 1,1 78,0 a39,39 0 1,1 -78,0" />
|
||||
</defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@@ -23,10 +23,10 @@ add_action( 'wp_enqueue_scripts', function () {
|
||||
if ( wp_style_is( 'astra-theme-css', 'registered' ) ) {
|
||||
$deps[] = 'astra-theme-css';
|
||||
}
|
||||
wp_enqueue_style( 'hoho-chrome', get_stylesheet_directory_uri() . '/assets/chrome.css', $deps, '1.0.2' );
|
||||
wp_enqueue_style( 'hoho-chrome', get_stylesheet_directory_uri() . '/assets/chrome.css', $deps, '1.0.8' );
|
||||
|
||||
// Header interactions: burger, submenu, sticky, back-to-top, search toggle.
|
||||
wp_enqueue_script( 'hoho-chrome', get_stylesheet_directory_uri() . '/assets/chrome.js', array(), '1.0.2', true );
|
||||
wp_enqueue_script( 'hoho-chrome', get_stylesheet_directory_uri() . '/assets/chrome.js', array(), '1.0.8', true );
|
||||
|
||||
wp_add_inline_style(
|
||||
'hoho-chrome',
|
||||
@@ -40,9 +40,17 @@ add_action( 'wp_enqueue_scripts', function () {
|
||||
'.hoho,.hoho h1,.hoho h2,.hoho h3,.hoho p,.hoho a,.hoho span,.hoho li,.hoho button,.hoho input{font-family:"Plus Jakarta Sans",system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}',
|
||||
// Normalize: stop Astra decorating header/footer links & lists.
|
||||
'.hoho a,.hoho.hoho a{text-decoration:none!important;box-shadow:none!important;}',
|
||||
'.hoho a:not(.btn){color:inherit;}',
|
||||
// …but not ring-badges: they set their own colour on the <a>, and this
|
||||
// inherit rule out-specifies .ring-badge--top, turning the footer
|
||||
// "back to top" text white (inherited) on the light area above it.
|
||||
'.hoho a:not(.btn):not(.ring-badge){color:inherit;}',
|
||||
'.hoho :is(ul,ol){list-style:none!important;margin:0!important;padding:0!important;}',
|
||||
'.hoho li{margin:0!important;}',
|
||||
// Let the footer\'s decorative bits (back-to-top ring, support tab)
|
||||
// spill vertically, but keep the horizontal axis clipped so nothing
|
||||
// causes a mobile sideways scroll. overflow-x:clip (unlike hidden)
|
||||
// does not force overflow-y to auto, so the y-axis stays visible.
|
||||
'.hoho,.hoho .footer{overflow-x:clip;overflow-y:visible;}',
|
||||
// Icon buttons: Astra gives <button> a filled background + its own
|
||||
// colour, which turned the header search icon into an empty dark
|
||||
// circle. Reset them (real .btn buttons keep their styling).
|
||||
@@ -51,9 +59,11 @@ add_action( 'wp_enqueue_scripts', function () {
|
||||
'.hoho button:not(.btn):not(.burger){background:transparent;border:0;color:inherit;}',
|
||||
// Expanding header search field.
|
||||
'.hoho .header__search{display:flex;align-items:center;gap:.25rem;}',
|
||||
'.hoho .header__search-input{width:0;min-width:0;padding:0;border:0;outline:0;opacity:0;height:var(--control-h);border-radius:var(--r-pill);background:rgba(255,255,255,.15);color:#fff;transition:width .25s var(--ease),opacity .2s var(--ease),padding .25s var(--ease);}',
|
||||
// !important beats Astra's input[type=search] styling, which forces a
|
||||
// white field background (and, with a white placeholder, hid the text).
|
||||
'.hoho .header__search-input{width:0;min-width:0;padding:0;border:0!important;outline:0;opacity:0;height:var(--control-h);border-radius:var(--r-pill)!important;background:rgba(255,255,255,.15)!important;color:#fff!important;box-shadow:none!important;transition:width .25s var(--ease),opacity .2s var(--ease),padding .25s var(--ease);}',
|
||||
'.hoho .header__search.is-open .header__search-input{width:min(52vw,15rem);padding:0 1rem;opacity:1;}',
|
||||
'.hoho .header__search-input::placeholder{color:rgba(255,255,255,.7);}',
|
||||
'.hoho .header__search-input::placeholder{color:rgba(255,255,255,.7)!important;opacity:1;}',
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -105,9 +115,11 @@ add_action( 'wp', function () {
|
||||
}
|
||||
add_action( 'astra_header', 'hoho_render_header' );
|
||||
|
||||
if ( function_exists( 'astra_footer_markup' ) ) {
|
||||
remove_action( 'astra_footer', 'astra_footer_markup' );
|
||||
}
|
||||
// Astra renders its footer via the Footer Builder (a class method on the
|
||||
// `astra_footer` hook), so removing the legacy astra_footer_markup() alone
|
||||
// left a second, default footer. Clear every callback on the hook, then add
|
||||
// ours — a clean, full replacement regardless of classic vs builder footer.
|
||||
remove_all_actions( 'astra_footer' );
|
||||
add_action( 'astra_footer', 'hoho_render_footer' );
|
||||
} );
|
||||
|
||||
@@ -146,7 +158,10 @@ function hoho_logo() {
|
||||
* Small helper: inline-icon markup referencing the sprite.
|
||||
*/
|
||||
function hoho_icon( $name, $extra = '' ) {
|
||||
$name = preg_replace( '/[^a-z0-9\-]/', '', 'i-' . ltrim( (string) $name, 'i-' ) );
|
||||
// Strip an existing "i-" prefix (once), then re-add it. ltrim() with a
|
||||
// charlist would eat any leading i/-, turning "instagram" into "nstagram".
|
||||
$slug = preg_replace( '/^i-/', '', (string) $name );
|
||||
$name = preg_replace( '/[^a-z0-9\-]/', '', 'i-' . $slug );
|
||||
$class = 'icon' . ( $extra ? ' ' . $extra : '' );
|
||||
return '<svg class="' . esc_attr( $class ) . '" aria-hidden="true"><use href="#' . esc_attr( $name ) . '"></use></svg>';
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@ $hoho_footer_col = function ( $location ) {
|
||||
<footer class="footer" id="footer">
|
||||
<a class="ring-badge ring-badge--top" href="#top" aria-label="<?php echo esc_attr__( 'Back to top', 'hoponhopoff' ); ?>">
|
||||
<svg class="ring-badge__ring" viewBox="0 0 100 100" aria-hidden="true">
|
||||
<text class="ring-badge__text"><textPath href="#ringPathScroll" startOffset="0">BACK TO TOP · BACK TO TOP · </textPath></text>
|
||||
<text class="ring-badge__text" text-anchor="middle">
|
||||
<textPath href="#ringPathScroll" startOffset="25%"><?php echo esc_html__( 'BACK TO TOP', 'hoponhopoff' ); ?></textPath>
|
||||
<textPath href="#ringPathScroll" startOffset="75%"><?php echo esc_html__( 'BACK TO TOP', 'hoponhopoff' ); ?></textPath>
|
||||
</text>
|
||||
</svg>
|
||||
<span class="ring-badge__dot"><svg class="icon" aria-hidden="true"><use href="#i-arrow-up"></use></svg></span>
|
||||
</a>
|
||||
|
||||
@@ -4,7 +4,7 @@ Theme URI: https://git.kaamyab.dev/vargha/TourWebsite
|
||||
Description: Astra child theme that replaces the Astra header and footer with the "Hop On Hop Off" design. Pairs with the Viator Collector plugin.
|
||||
Author: Amir Bagheri
|
||||
Template: astra
|
||||
Version: 1.0.2
|
||||
Version: 1.0.8
|
||||
Text Domain: hoponhopoff
|
||||
*/
|
||||
|
||||
|
||||
@@ -58,7 +58,15 @@ html {
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family:
|
||||
"Plus Jakarta Sans",
|
||||
system-ui,
|
||||
-apple-system,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: var(--fs-body);
|
||||
line-height: 1.5;
|
||||
color: var(--c-ink);
|
||||
@@ -173,7 +181,10 @@ dd {
|
||||
border-radius: var(--r-pill);
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
transition: transform 0.2s var(--ease), filter 0.2s var(--ease), background 0.2s var(--ease);
|
||||
transition:
|
||||
transform 0.2s var(--ease),
|
||||
filter 0.2s var(--ease),
|
||||
background 0.2s var(--ease);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
@@ -378,7 +389,9 @@ dd {
|
||||
|
||||
.rail:hover .dcard.is-active:not(:hover):not(:focus-within) .ring-badge__dot,
|
||||
.rail:hover .acard.is-active:not(:hover):not(:focus-within) .ring-badge__dot,
|
||||
.bus__grid:hover .tcard.is-active:not(:hover):not(:focus-within) .ring-badge__dot {
|
||||
.bus__grid:hover
|
||||
.tcard.is-active:not(:hover):not(:focus-within)
|
||||
.ring-badge__dot {
|
||||
color: var(--c-white);
|
||||
background: var(--c-glass);
|
||||
}
|
||||
@@ -430,7 +443,6 @@ dd {
|
||||
}
|
||||
|
||||
@keyframes nudge {
|
||||
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
@@ -541,11 +553,14 @@ dd {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-0.375rem);
|
||||
transition: opacity 0.2s var(--ease), transform 0.2s var(--ease), visibility 0.2s;
|
||||
transition:
|
||||
opacity 0.2s var(--ease),
|
||||
transform 0.2s var(--ease),
|
||||
visibility 0.2s;
|
||||
}
|
||||
|
||||
.nav__item--has-menu:hover .nav__submenu,
|
||||
.nav__toggle[aria-expanded="true"]+.nav__submenu {
|
||||
.nav__toggle[aria-expanded="true"] + .nav__submenu {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
@@ -585,7 +600,9 @@ dd {
|
||||
height: 0.125rem;
|
||||
border-radius: 1px;
|
||||
background: var(--c-white);
|
||||
transition: transform 0.25s var(--ease), opacity 0.25s var(--ease);
|
||||
transition:
|
||||
transform 0.25s var(--ease),
|
||||
opacity 0.25s var(--ease);
|
||||
}
|
||||
|
||||
.burger[aria-expanded="true"] span:nth-child(1) {
|
||||
@@ -607,9 +624,9 @@ dd {
|
||||
position: relative;
|
||||
display: grid;
|
||||
align-items: end;
|
||||
min-height: clamp(34rem, 88svh, 56rem);
|
||||
margin: var(--page-inset);
|
||||
padding-block: calc(var(--header-h) + var(--sp-6)) clamp(3rem, 1.5rem + 6vw, 6rem);
|
||||
padding-block: calc(var(--header-h) + var(--sp-6))
|
||||
clamp(3rem, 1.5rem + 6vw, 6rem);
|
||||
border-radius: var(--r-xl);
|
||||
isolation: isolate;
|
||||
}
|
||||
@@ -634,7 +651,11 @@ dd {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(39.91% 77.37% at 60.22% 50%, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.5) 100%),
|
||||
radial-gradient(
|
||||
39.91% 77.37% at 60.22% 50%,
|
||||
rgba(0, 0, 0, 0) 0%,
|
||||
rgba(0, 0, 0, 0.5) 100%
|
||||
),
|
||||
linear-gradient(180deg, rgba(44, 175, 194, 0) 51.6%, var(--c-teal) 100%);
|
||||
}
|
||||
|
||||
@@ -814,24 +835,28 @@ dd {
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-width: none;
|
||||
/* negative margin + padding keeps the card glow from being clipped by the scroller */
|
||||
margin-inline: calc(var(--gap) * -1);
|
||||
padding: var(--sp-4) var(--gap);
|
||||
/* overflow-x:auto forces overflow-y to auto, so the hover glow (a ~3.75rem
|
||||
blur) would be clipped by the scroller. Don't switch to overflow:visible —
|
||||
that kills the horizontal scroll. Instead give the rail enough padding for
|
||||
the glow to paint INSIDE the scroll box, and pull it back with an equal
|
||||
negative margin so the visible spacing is unchanged. Scrolling stays intact. */
|
||||
margin: calc(var(--sp-5) * -1) calc(var(--gap) * -1);
|
||||
padding: 4.5rem var(--gap);
|
||||
}
|
||||
|
||||
.rail::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rail>* {
|
||||
.rail > * {
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.rail--dest>* {
|
||||
.rail--dest > * {
|
||||
flex: 0 0 calc((100% - 4 * var(--gap)) / 5);
|
||||
}
|
||||
|
||||
.rail--art>* {
|
||||
.rail--art > * {
|
||||
flex: 0 0 calc((100% - 2 * var(--gap)) / 3);
|
||||
}
|
||||
|
||||
@@ -870,7 +895,11 @@ dd {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 35%, rgba(0, 0, 0, 0.65) 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0) 35%,
|
||||
rgba(0, 0, 0, 0.65) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* highlighted card — gradients copied from Figma */
|
||||
@@ -884,7 +913,11 @@ dd {
|
||||
.dcard:hover::after,
|
||||
.dcard:focus-within::after {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0) 51.6%, rgba(255, 255, 255, 0.4) 100%),
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0) 51.6%,
|
||||
rgba(255, 255, 255, 0.4) 100%
|
||||
),
|
||||
linear-gradient(0deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)),
|
||||
linear-gradient(180deg, rgba(44, 175, 194, 0) 37.33%, #2cafc2 84.53%);
|
||||
}
|
||||
@@ -897,7 +930,11 @@ dd {
|
||||
|
||||
.rail:hover .dcard.is-active:not(:hover):not(:focus-within)::after,
|
||||
.rail:focus-within .dcard.is-active:not(:hover):not(:focus-within)::after {
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 35%, rgba(0, 0, 0, 0.65) 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0) 35%,
|
||||
rgba(0, 0, 0, 0.65) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.dcard__desc {
|
||||
@@ -907,7 +944,9 @@ dd {
|
||||
top: clamp(2.75rem, 2rem + 2vw, 3.5rem);
|
||||
opacity: 0;
|
||||
transform: translateY(-0.375rem);
|
||||
transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
|
||||
transition:
|
||||
opacity 0.25s var(--ease),
|
||||
transform 0.25s var(--ease);
|
||||
}
|
||||
|
||||
.dcard.is-active .dcard__desc,
|
||||
@@ -918,7 +957,9 @@ dd {
|
||||
}
|
||||
|
||||
.rail:hover .dcard.is-active:not(:hover):not(:focus-within) .dcard__desc,
|
||||
.rail:focus-within .dcard.is-active:not(:hover):not(:focus-within) .dcard__desc {
|
||||
.rail:focus-within
|
||||
.dcard.is-active:not(:hover):not(:focus-within)
|
||||
.dcard__desc {
|
||||
opacity: 0;
|
||||
transform: translateY(-0.375rem);
|
||||
}
|
||||
@@ -1039,7 +1080,8 @@ dd {
|
||||
object-fit: contain;
|
||||
object-position: left;
|
||||
/* Figma tints the blue coach towards the teal brand colour */
|
||||
filter: hue-rotate(-32deg) saturate(1.15) drop-shadow(0 1rem 1.5rem rgba(0, 0, 0, 0.18));
|
||||
filter: hue-rotate(-32deg) saturate(1.15)
|
||||
drop-shadow(0 1rem 1.5rem rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
.promo__note {
|
||||
@@ -1085,7 +1127,12 @@ dd {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0.1) 45%, rgba(0, 0, 0, 0.6) 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.45) 0%,
|
||||
rgba(0, 0, 0, 0.1) 45%,
|
||||
rgba(0, 0, 0, 0.6) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.tcard.is-active,
|
||||
@@ -1098,7 +1145,11 @@ dd {
|
||||
.tcard:hover::after,
|
||||
.tcard:focus-within::after {
|
||||
background:
|
||||
radial-gradient(53.59% 137.34% at 50% 46.41%, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.4) 100%),
|
||||
radial-gradient(
|
||||
53.59% 137.34% at 50% 46.41%,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.4) 100%
|
||||
),
|
||||
linear-gradient(0deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)),
|
||||
linear-gradient(180deg, rgba(44, 175, 194, 0) 37.33%, #2cafc2 84.53%);
|
||||
}
|
||||
@@ -1110,7 +1161,12 @@ dd {
|
||||
|
||||
.bus__grid:hover .tcard.is-active:not(:hover):not(:focus-within)::after,
|
||||
.bus__grid:focus-within .tcard.is-active:not(:hover):not(:focus-within)::after {
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0.1) 45%, rgba(0, 0, 0, 0.6) 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.45) 0%,
|
||||
rgba(0, 0, 0, 0.1) 45%,
|
||||
rgba(0, 0, 0, 0.6) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.tcard__k {
|
||||
@@ -1164,11 +1220,13 @@ dd {
|
||||
right: -34%;
|
||||
top: 42%;
|
||||
height: 28%;
|
||||
background: linear-gradient(90deg,
|
||||
rgba(44, 175, 194, 0) 0%,
|
||||
rgba(44, 175, 194, 0.16) 22%,
|
||||
rgba(44, 175, 194, 0.2) 55%,
|
||||
rgba(44, 175, 194, 0) 100%);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(44, 175, 194, 0) 0%,
|
||||
rgba(44, 175, 194, 0.16) 22%,
|
||||
rgba(44, 175, 194, 0.2) 55%,
|
||||
rgba(44, 175, 194, 0) 100%
|
||||
);
|
||||
/* small blur only — the band keeps its straight parallel edges */
|
||||
filter: blur(2px);
|
||||
/* 25deg == the roof ridge angle, measured off the artwork itself */
|
||||
@@ -1295,8 +1353,20 @@ dd {
|
||||
.marquee {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
|
||||
mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
#000 8%,
|
||||
#000 92%,
|
||||
transparent
|
||||
);
|
||||
mask-image: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
#000 8%,
|
||||
#000 92%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.marquee__route {
|
||||
@@ -1420,14 +1490,23 @@ dd {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.65) 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.35) 0%,
|
||||
rgba(0, 0, 0, 0.05) 40%,
|
||||
rgba(0, 0, 0, 0.65) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.acard.is-active .acard__media::after,
|
||||
.acard:hover .acard__media::after,
|
||||
.acard:focus-within .acard__media::after {
|
||||
background:
|
||||
radial-gradient(53.59% 137.34% at 50% 46.41%, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.4) 100%),
|
||||
radial-gradient(
|
||||
53.59% 137.34% at 50% 46.41%,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.4) 100%
|
||||
),
|
||||
linear-gradient(0deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)),
|
||||
linear-gradient(180deg, rgba(44, 175, 194, 0) 37.33%, #2cafc2 84.53%);
|
||||
}
|
||||
@@ -1446,8 +1525,15 @@ dd {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.rail:hover .acard.is-active:not(:hover):not(:focus-within) .acard__media::after {
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.65) 100%);
|
||||
.rail:hover
|
||||
.acard.is-active:not(:hover):not(:focus-within)
|
||||
.acard__media::after {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.35) 0%,
|
||||
rgba(0, 0, 0, 0.05) 40%,
|
||||
rgba(0, 0, 0, 0.65) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.acard__time {
|
||||
@@ -1559,7 +1645,7 @@ dd {
|
||||
gap: var(--sp-4);
|
||||
}
|
||||
|
||||
.acc+.acc {
|
||||
.acc + .acc {
|
||||
border-top: 1px solid var(--c-line);
|
||||
padding-top: var(--sp-4);
|
||||
}
|
||||
@@ -1613,11 +1699,14 @@ dd {
|
||||
transition: grid-template-rows 0.3s var(--ease);
|
||||
}
|
||||
|
||||
.acc__q[aria-expanded="true"]+.acc__a {
|
||||
/* The button sits inside an <h3>, so .acc__a is the h3's sibling — not the
|
||||
button's. Match on the .acc wrapper's expanded state instead of an adjacent
|
||||
sibling, which never matched through the heading. */
|
||||
.acc:has(.acc__q[aria-expanded="true"]) .acc__a {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.acc__a>* {
|
||||
.acc__a > * {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1634,7 +1723,8 @@ dd {
|
||||
========================================================= */
|
||||
.footer {
|
||||
position: relative;
|
||||
margin: calc(var(--sp-6) + var(--page-inset)) var(--page-inset) var(--page-inset);
|
||||
margin: calc(var(--sp-6) + var(--page-inset)) var(--page-inset)
|
||||
var(--page-inset);
|
||||
padding-block: var(--sp-6) 0;
|
||||
border-radius: var(--r-xl);
|
||||
color: var(--c-white);
|
||||
@@ -1770,7 +1860,11 @@ dd {
|
||||
padding: var(--sp-4);
|
||||
padding-bottom: clamp(2rem, 1rem + 3vw, 3rem);
|
||||
border-radius: var(--r-pill) var(--r-pill) 0 0;
|
||||
background: linear-gradient(180deg, rgba(44, 175, 194, 0.99) 0%, var(--c-teal-20) 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(44, 175, 194, 0.99) 0%,
|
||||
var(--c-teal-20) 100%
|
||||
);
|
||||
box-shadow: 0 0.75rem 8.25rem var(--c-teal);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
backdrop-filter: blur(4px);
|
||||
@@ -1785,7 +1879,7 @@ dd {
|
||||
Responsive
|
||||
========================================================= */
|
||||
@media (max-width: 78rem) {
|
||||
.rail--dest>* {
|
||||
.rail--dest > * {
|
||||
flex: 0 0 calc((100% - 3 * var(--gap)) / 4);
|
||||
}
|
||||
}
|
||||
@@ -1813,7 +1907,7 @@ dd {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.rail--dest>* {
|
||||
.rail--dest > * {
|
||||
flex: 0 0 calc((100% - 2 * var(--gap)) / 3);
|
||||
}
|
||||
|
||||
@@ -1861,7 +1955,10 @@ dd {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-0.5rem);
|
||||
transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), visibility 0.25s;
|
||||
transition:
|
||||
opacity 0.25s var(--ease),
|
||||
transform 0.25s var(--ease),
|
||||
visibility 0.25s;
|
||||
}
|
||||
|
||||
.nav.is-open {
|
||||
@@ -1902,7 +1999,7 @@ dd {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav__toggle[aria-expanded="true"]+.nav__submenu {
|
||||
.nav__toggle[aria-expanded="true"] + .nav__submenu {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
@@ -1915,7 +2012,7 @@ dd {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rail--art>* {
|
||||
.rail--art > * {
|
||||
flex: 0 0 calc((100% - var(--gap)) / 2);
|
||||
}
|
||||
|
||||
@@ -1925,7 +2022,7 @@ dd {
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.rail--dest>* {
|
||||
.rail--dest > * {
|
||||
flex: 0 0 calc((100% - var(--gap)) / 2);
|
||||
}
|
||||
|
||||
@@ -1953,10 +2050,6 @@ dd {
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
.hero {
|
||||
min-height: clamp(30rem, 84svh, 44rem);
|
||||
}
|
||||
|
||||
.booking {
|
||||
padding: var(--sp-2);
|
||||
}
|
||||
@@ -1988,11 +2081,11 @@ dd {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.rail--dest>* {
|
||||
.rail--dest > * {
|
||||
flex: 0 0 min(72%, 15rem);
|
||||
}
|
||||
|
||||
.rail--art>* {
|
||||
.rail--art > * {
|
||||
flex: 0 0 min(85%, 22rem);
|
||||
}
|
||||
|
||||
@@ -2200,20 +2293,23 @@ dd {
|
||||
border-radius: 0.25rem;
|
||||
color: transparent;
|
||||
background: var(--c-white);
|
||||
transition: color 0.2s var(--ease), background-color 0.2s var(--ease), border-color 0.2s var(--ease);
|
||||
transition:
|
||||
color 0.2s var(--ease),
|
||||
background-color 0.2s var(--ease),
|
||||
border-color 0.2s var(--ease);
|
||||
}
|
||||
|
||||
.check:hover .check__box {
|
||||
border-color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.check__input:checked+.check__box {
|
||||
.check__input:checked + .check__box {
|
||||
color: var(--c-white);
|
||||
border-color: var(--c-black);
|
||||
background: var(--c-black);
|
||||
}
|
||||
|
||||
.check__input:focus-visible+.check__box {
|
||||
.check__input:focus-visible + .check__box {
|
||||
outline: 2px solid var(--c-teal);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@@ -2225,7 +2321,7 @@ dd {
|
||||
transition: opacity 0.2s var(--ease);
|
||||
}
|
||||
|
||||
.check__input:checked~.check__text {
|
||||
.check__input:checked ~ .check__text {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -2345,7 +2441,9 @@ dd {
|
||||
line-height: 1.5;
|
||||
text-align: justify;
|
||||
opacity: 0.7;
|
||||
transition: color 0.25s var(--ease), opacity 0.25s var(--ease);
|
||||
transition:
|
||||
color 0.25s var(--ease),
|
||||
opacity 0.25s var(--ease);
|
||||
}
|
||||
|
||||
.titem.is-active .titem__desc,
|
||||
@@ -2357,7 +2455,9 @@ dd {
|
||||
|
||||
/* hovering any card in the grid hands the highlight over to it */
|
||||
.tgrid:hover .titem.is-active:not(:hover):not(:focus-within) .titem__desc,
|
||||
.tgrid:focus-within .titem.is-active:not(:hover):not(:focus-within) .titem__desc {
|
||||
.tgrid:focus-within
|
||||
.titem.is-active:not(:hover):not(:focus-within)
|
||||
.titem__desc {
|
||||
color: inherit;
|
||||
opacity: 0.7;
|
||||
}
|
||||
@@ -2369,11 +2469,17 @@ dd {
|
||||
|
||||
.tgrid:hover .dcard.is-active:not(:hover):not(:focus-within)::after,
|
||||
.tgrid:focus-within .dcard.is-active:not(:hover):not(:focus-within)::after {
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 35%, rgba(0, 0, 0, 0.65) 100%);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0) 35%,
|
||||
rgba(0, 0, 0, 0.65) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.tgrid:hover .dcard.is-active:not(:hover):not(:focus-within) .ring-badge__dot,
|
||||
.tgrid:focus-within .dcard.is-active:not(:hover):not(:focus-within) .ring-badge__dot {
|
||||
.tgrid:focus-within
|
||||
.dcard.is-active:not(:hover):not(:focus-within)
|
||||
.ring-badge__dot {
|
||||
color: var(--c-white);
|
||||
background: var(--c-glass);
|
||||
}
|
||||
@@ -2411,7 +2517,10 @@ dd {
|
||||
color: var(--c-black);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s var(--ease), background-color 0.2s var(--ease), border-color 0.2s var(--ease);
|
||||
transition:
|
||||
color 0.2s var(--ease),
|
||||
background-color 0.2s var(--ease),
|
||||
border-color 0.2s var(--ease);
|
||||
}
|
||||
|
||||
.pager__num {
|
||||
@@ -2530,7 +2639,8 @@ dd {
|
||||
--word: clamp(3rem, 0.4rem + 9.5vw, 11.25rem);
|
||||
min-height: clamp(26rem, 62svh, 38rem);
|
||||
margin: var(--page-inset);
|
||||
padding-block: calc(var(--header-h) + var(--sp-5)) clamp(0.75rem, 0.4rem + 1vw, 1.5rem);
|
||||
padding-block: calc(var(--header-h) + var(--sp-5))
|
||||
clamp(0.75rem, 0.4rem + 1vw, 1.5rem);
|
||||
border-radius: var(--r-xl);
|
||||
isolation: isolate;
|
||||
overflow: clip;
|
||||
@@ -2547,6 +2657,13 @@ dd {
|
||||
border-radius: inherit;
|
||||
object-fit: cover;
|
||||
object-position: center 26%;
|
||||
/* Viator only ships ~720px images, which look soft blown up to a full-width
|
||||
banner. Rather than fight it, lean in: a gentle blur turns the photo into an
|
||||
intentional backdrop (the crisp image lives in the gallery lightbox). The
|
||||
scale hides the blur's feathered edges, which overflow:clip on .hero-tour
|
||||
then trims to the rounded frame. */
|
||||
filter: blur(10px) saturate(1.12);
|
||||
transform: scale(1.12);
|
||||
}
|
||||
|
||||
.hero-tour__sky {
|
||||
@@ -2555,8 +2672,17 @@ dd {
|
||||
z-index: -1;
|
||||
border-radius: inherit;
|
||||
background:
|
||||
radial-gradient(70% 50% at 50% 100%, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0) 70%),
|
||||
linear-gradient(180deg, rgba(27, 147, 166, 0.82) 0%, rgba(44, 175, 194, 0.62) 45%, rgba(111, 208, 221, 0.78) 100%);
|
||||
radial-gradient(
|
||||
70% 50% at 50% 100%,
|
||||
rgba(255, 255, 255, 0.3) 0%,
|
||||
rgba(255, 255, 255, 0) 70%
|
||||
),
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(27, 147, 166, 0.55) 0%,
|
||||
rgba(44, 175, 194, 0.42) 45%,
|
||||
rgba(111, 208, 221, 0.72) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.hero-tour__inner {
|
||||
@@ -2590,7 +2716,7 @@ dd {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.crumbs__list li+li::before {
|
||||
.crumbs__list li + li::before {
|
||||
content: "/";
|
||||
margin-inline-end: var(--sp-1);
|
||||
opacity: 0.5;
|
||||
@@ -2681,7 +2807,7 @@ dd {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.tour__text+.tour__text {
|
||||
.tour__text + .tour__text {
|
||||
margin-block-start: var(--sp-3);
|
||||
}
|
||||
|
||||
@@ -2808,6 +2934,119 @@ dd {
|
||||
border-radius: var(--r-card);
|
||||
}
|
||||
|
||||
.gallery img {
|
||||
cursor: zoom-in;
|
||||
transition: transform 0.25s var(--ease), filter 0.25s var(--ease);
|
||||
}
|
||||
|
||||
.gallery img:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
Gallery lightbox (built by vc-design.js)
|
||||
========================================================= */
|
||||
.vc-lightbox {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: clamp(1rem, 0.5rem + 3vw, 3.5rem);
|
||||
background: rgba(0, 0, 0, 0.86);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
backdrop-filter: blur(6px);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition:
|
||||
opacity 0.25s var(--ease),
|
||||
visibility 0s linear 0.25s;
|
||||
}
|
||||
|
||||
.vc-lightbox.is-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 0.25s var(--ease);
|
||||
}
|
||||
|
||||
.vc-lightbox__img {
|
||||
max-width: min(100%, 78rem);
|
||||
max-height: 84vh;
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: var(--r-card);
|
||||
box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, 0.5);
|
||||
transform: scale(0.96);
|
||||
transition: transform 0.25s var(--ease), opacity 0.15s var(--ease);
|
||||
}
|
||||
|
||||
.vc-lightbox.is-open .vc-lightbox__img {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.vc-lightbox__btn {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: var(--r-pill);
|
||||
color: var(--c-white);
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
backdrop-filter: blur(8px);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s var(--ease), transform 0.2s var(--ease);
|
||||
}
|
||||
|
||||
.vc-lightbox__btn:hover {
|
||||
background: rgba(255, 255, 255, 0.24);
|
||||
}
|
||||
|
||||
.vc-lightbox__close {
|
||||
top: clamp(1rem, 3vw, 2rem);
|
||||
right: clamp(1rem, 3vw, 2rem);
|
||||
}
|
||||
|
||||
.vc-lightbox__prev,
|
||||
.vc-lightbox__next {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.vc-lightbox__prev {
|
||||
left: clamp(0.5rem, 2vw, 2rem);
|
||||
}
|
||||
|
||||
.vc-lightbox__next {
|
||||
right: clamp(0.5rem, 2vw, 2rem);
|
||||
}
|
||||
|
||||
.vc-lightbox__prev:hover,
|
||||
.vc-lightbox__next:hover {
|
||||
transform: translateY(-50%) scale(1.06);
|
||||
}
|
||||
|
||||
.vc-lightbox__count {
|
||||
position: absolute;
|
||||
bottom: clamp(1rem, 3vw, 2rem);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* single image → hide the arrows */
|
||||
.vc-lightbox--single .vc-lightbox__prev,
|
||||
.vc-lightbox--single .vc-lightbox__next,
|
||||
.vc-lightbox--single .vc-lightbox__count {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* --- included / not included --- */
|
||||
.lists {
|
||||
display: grid;
|
||||
|
||||
Binary file not shown.
@@ -16,23 +16,31 @@ listing/single/section assets load only on pages that use them.
|
||||
| `[viator_top_tours]` | Rail of tour cards | `viator_tour` posts |
|
||||
| `[viator_top_attractions]` | Rail of tour cards | `viator_tour` posts |
|
||||
| `[viator_browse_activities]` | Rail of tour cards | `viator_tour` posts |
|
||||
| `[viator_bus_tours]` | "Open Top Bus Tours" promo + 4-up card grid | `viator_tour` posts |
|
||||
| `[viator_about]` | Static "About / Memorable" block + stat counters | Shortcode attributes |
|
||||
| `[viator_articles]` | Rail of latest blog-post cards | `post` posts |
|
||||
| `[viator_faq]` | FAQ accordion | Shortcode `items` / `vc_faq_items` filter |
|
||||
| `[viator_testimonials]` | Testimonials marquee | `vc_testimonials` filter |
|
||||
|
||||
`[viator_hero_slider]` is kept as a **back-compat alias** for `[viator_hero]`.
|
||||
|
||||
A typical home page:
|
||||
Home page matching the design mockup (`new front/index.html`), top to bottom:
|
||||
|
||||
```
|
||||
[viator_hero]
|
||||
[viator_hero image_id="125304"]
|
||||
[viator_top_destinations]
|
||||
[viator_bus_tours image_id="125310"]
|
||||
[viator_top_tours]
|
||||
[viator_top_attractions]
|
||||
[viator_browse_activities]
|
||||
[viator_about image_id="125311"]
|
||||
[viator_testimonials]
|
||||
[viator_articles]
|
||||
[viator_faq]
|
||||
```
|
||||
|
||||
Heavy decorative images (hero photo, open-top bus, isometric bus) are **not
|
||||
bundled** — upload them in **Media** and pass the attachment `image_id` (exactly
|
||||
like `[viator_hero image_id="…"]`). Icons and tiny decorations are inline SVG.
|
||||
|
||||
---
|
||||
|
||||
## `[viator_hero]`
|
||||
@@ -99,6 +107,71 @@ are hidden automatically.
|
||||
|
||||
---
|
||||
|
||||
## `[viator_bus_tours]`
|
||||
|
||||
The "Open Top Bus Tours…" promo block (route line, headline, "See all", a chip,
|
||||
a bus illustration and note) followed by a 4-up grid of tour cards. Selects tours
|
||||
with the **same attributes as the tour rails** (`ids` / `codes` / `destination` /
|
||||
`tag` / `limit` / `orderby`), plus:
|
||||
|
||||
| Attribute | Default | Description |
|
||||
|---|---|---|
|
||||
| `title` | `Open Top<br>Bus Tours…` | Promo headline (`<br>` allowed). |
|
||||
| `note` | *(default copy)* | Paragraph under the headline. |
|
||||
| `chip` | `Very Fun!` | Small accent chip. |
|
||||
| `image_id` | — | **Media attachment ID** of the open-top bus illustration. |
|
||||
| `image` | — | …or a direct image URL (use `image_id` when possible). |
|
||||
|
||||
```
|
||||
[viator_bus_tours image_id="125310" tag="hop-on-hop-off" limit="4"]
|
||||
[viator_bus_tours ids="482,517,690,701"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `[viator_about]`
|
||||
|
||||
Static "About" marketing block: headline with an accent chip, a paragraph, four
|
||||
stat counters and a decorative isometric bus. All copy is editable.
|
||||
|
||||
| Attribute | Default | Description |
|
||||
|---|---|---|
|
||||
| `title` | `Hop On Hop Off` | Headline. |
|
||||
| `highlight` | `Memorable` | Accent chip appended to the headline. |
|
||||
| `text` | *(default copy)* | Paragraph. |
|
||||
| `button_text` | `About us` | Button label. |
|
||||
| `button_url` | *(tours archive)* | Button link. |
|
||||
| `image_id` | — | **Media attachment ID** of the isometric bus illustration. |
|
||||
| `image` | — | …or a direct image URL. |
|
||||
| `stats` | *(4 defaults)* | `25+|Years Experience;;500+|Tours Conducted` — `|` separates number from label, `;;` separates stats. A trailing `+` is styled. |
|
||||
|
||||
```
|
||||
[viator_about image_id="125311" stats="25+|Years Experience;;500+|Tours Conducted;;430+|Tours this Year;;15+|Different Tours"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `[viator_articles]`
|
||||
|
||||
A rail of the latest blog posts styled as the design's "Latest Articles" cards
|
||||
(featured image, relative time, title, excerpt → links to the post).
|
||||
|
||||
| Attribute | Default | Description |
|
||||
|---|---|---|
|
||||
| `title` | `Latest<br>Articles` | Heading (`<br>` allowed). |
|
||||
| `lead` | *(default copy)* | Sub-heading. |
|
||||
| `limit` | `8` | Number of posts. |
|
||||
| `category` | — | Restrict to a category slug. |
|
||||
| `view_all` | *(posts page)* | "See all" link; defaults to the blog page. |
|
||||
|
||||
Cards use each post's **featured image**; set one so the card isn't blank.
|
||||
|
||||
```
|
||||
[viator_articles limit="8" category="travel-tips"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `[viator_faq]`
|
||||
|
||||
FAQ accordion.
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
<svg class="sprite" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="sprite" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0;overflow:hidden">
|
||||
<defs>
|
||||
<path id="ringPathScroll" fill="none" d="M50,50 m-39,0 a39,39 0 1,1 78,0 a39,39 0 1,1 -78,0" />
|
||||
</defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@@ -92,4 +92,57 @@
|
||||
var g = app.querySelector(".gallery, .tour-gallery");
|
||||
if (g) g.scrollIntoView({ behavior: "smooth" });
|
||||
});
|
||||
|
||||
/* ---------- gallery lightbox (zoom / view photos) ---------- */
|
||||
(function () {
|
||||
var imgs = Array.prototype.slice.call(app.querySelectorAll(".gallery img"));
|
||||
if (!imgs.length) return;
|
||||
|
||||
var host = imgs[0].closest(".vc-app") || document.body;
|
||||
var box = document.createElement("div");
|
||||
box.className = "vc-lightbox" + (imgs.length < 2 ? " vc-lightbox--single" : "");
|
||||
box.setAttribute("role", "dialog");
|
||||
box.setAttribute("aria-modal", "true");
|
||||
box.innerHTML =
|
||||
'<button class="vc-lightbox__btn vc-lightbox__prev" type="button" aria-label="Previous"><svg class="icon icon--24" aria-hidden="true"><use href="#i-arrow-left"></use></svg></button>' +
|
||||
'<img class="vc-lightbox__img" alt="">' +
|
||||
'<button class="vc-lightbox__btn vc-lightbox__next" type="button" aria-label="Next"><svg class="icon icon--24" aria-hidden="true"><use href="#i-arrow-right"></use></svg></button>' +
|
||||
'<button class="vc-lightbox__btn vc-lightbox__close" type="button" aria-label="Close"><svg class="icon icon--24" aria-hidden="true"><use href="#i-x"></use></svg></button>' +
|
||||
'<span class="vc-lightbox__count"></span>';
|
||||
host.appendChild(box);
|
||||
|
||||
var bigImg = box.querySelector(".vc-lightbox__img");
|
||||
var count = box.querySelector(".vc-lightbox__count");
|
||||
var idx = 0;
|
||||
|
||||
function show(i) {
|
||||
idx = (i + imgs.length) % imgs.length;
|
||||
bigImg.src = imgs[idx].currentSrc || imgs[idx].src;
|
||||
bigImg.alt = imgs[idx].alt || "";
|
||||
count.textContent = (idx + 1) + " / " + imgs.length;
|
||||
}
|
||||
function open(i) {
|
||||
show(i);
|
||||
box.classList.add("is-open");
|
||||
document.documentElement.style.overflow = "hidden";
|
||||
}
|
||||
function close() {
|
||||
box.classList.remove("is-open");
|
||||
document.documentElement.style.overflow = "";
|
||||
}
|
||||
|
||||
imgs.forEach(function (img, i) {
|
||||
img.addEventListener("click", function () { open(i); });
|
||||
});
|
||||
box.querySelector(".vc-lightbox__prev").addEventListener("click", function () { show(idx - 1); });
|
||||
box.querySelector(".vc-lightbox__next").addEventListener("click", function () { show(idx + 1); });
|
||||
box.querySelector(".vc-lightbox__close").addEventListener("click", close);
|
||||
box.addEventListener("click", function (e) { if (e.target === box) close(); });
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (!box.classList.contains("is-open")) return;
|
||||
if (e.key === "Escape") close();
|
||||
else if (e.key === "ArrowLeft") show(idx - 1);
|
||||
else if (e.key === "ArrowRight") show(idx + 1);
|
||||
});
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
|
||||
const SCOPE = process.env.VC_SCOPE || '.vc-app';
|
||||
// Doubled class (`.vc-app.vc-app`) so every ported rule carries an extra class
|
||||
// of specificity — enough to win against Astra's content selectors (e.g.
|
||||
// `.ast-… .entry-content a`) without touching the markup, which still only
|
||||
// needs a single `class="vc-app"`. Override with VC_SCOPE if ever needed.
|
||||
const SCOPE = process.env.VC_SCOPE || '.vc-app.vc-app';
|
||||
|
||||
function prefixSelector(sel) {
|
||||
sel = sel.trim();
|
||||
|
||||
@@ -11,6 +11,9 @@ class VC_Display {
|
||||
'viator_top_destinations',
|
||||
'viator_top_attractions',
|
||||
'viator_browse_activities',
|
||||
'viator_bus_tours',
|
||||
'viator_about',
|
||||
'viator_articles',
|
||||
'viator_faq',
|
||||
'viator_testimonials',
|
||||
'viator_tour_card',
|
||||
@@ -24,9 +27,12 @@ class VC_Display {
|
||||
);
|
||||
|
||||
public static function init() {
|
||||
// Priority 20 so Astra has already registered/enqueued its stylesheet;
|
||||
// our CSS then loads after it and wins equal-specificity ties.
|
||||
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ), 20 );
|
||||
// Priority 100 so the theme (Astra + child) has already enqueued ALL of
|
||||
// its stylesheets — including the parent style.css and the child's chrome
|
||||
// CSS, which are enqueued by the theme at priority 20 and would otherwise
|
||||
// print AFTER us (plugins run before themes at equal priority) and win
|
||||
// equal-specificity ties. Loading last makes our design win those ties.
|
||||
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ), 100 );
|
||||
add_action( 'template_redirect', array( __CLASS__, 'maybe_enrich_single' ) );
|
||||
add_action( 'pre_get_posts', array( __CLASS__, 'filter_archive_query' ) );
|
||||
add_filter( 'single_template', array( __CLASS__, 'single_template' ) );
|
||||
@@ -146,11 +152,15 @@ class VC_Display {
|
||||
wp_enqueue_style( 'vc-fonts', 'https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;700;800&display=swap', array(), null );
|
||||
|
||||
// One scoped stylesheet (.vc-app) covers listing, single, and homepage
|
||||
// sections — ported from the "Hop On Hop Off" design. Depend on the Astra
|
||||
// theme stylesheet (when present) so our rules load AFTER it and win ties.
|
||||
// sections — ported from the "Hop On Hop Off" design. Declare every theme
|
||||
// stylesheet that could fight ours as a dependency (when present) so our
|
||||
// rules always print AFTER them and win equal-specificity ties. We enqueue
|
||||
// at priority 100, so by now the theme (priority 20) has registered them.
|
||||
$deps = array( 'vc-fonts' );
|
||||
if ( wp_style_is( 'astra-theme-css', 'registered' ) ) {
|
||||
$deps[] = 'astra-theme-css';
|
||||
foreach ( array( 'astra-theme-css', 'astra-parent', 'hoho-chrome' ) as $handle ) {
|
||||
if ( wp_style_is( $handle, 'registered' ) ) {
|
||||
$deps[] = $handle;
|
||||
}
|
||||
}
|
||||
wp_enqueue_style( 'vc-design', VC_PLUGIN_URL . 'assets/css/vc-design.css', $deps, VC_VERSION );
|
||||
wp_add_inline_style( 'vc-design', self::astra_overrides() );
|
||||
@@ -180,42 +190,89 @@ class VC_Display {
|
||||
. '.vc-fullwidth.ast-page-builder-template .site-main>article'
|
||||
. '{padding:0!important;margin:0!important;border:0!important;max-width:none!important;}',
|
||||
'.vc-fullwidth #primary,.vc-fullwidth #content{margin:0!important;padding:0!important;}',
|
||||
// display:block undoes Astra's `.site-content .ast-container{display:flex}`
|
||||
// (≥922px), which otherwise sizes our content to its contents instead
|
||||
// of filling the full-width container.
|
||||
'.vc-fullwidth .site-content .ast-container,.vc-fullwidth .ast-container'
|
||||
. '{max-width:none!important;padding-left:0!important;padding-right:0!important;}',
|
||||
. '{max-width:none!important;padding-left:0!important;padding-right:0!important;display:block!important;}',
|
||||
'.vc-fullwidth .entry-content{margin-block:0!important;}',
|
||||
'.vc-fullwidth .site-content{padding:0!important;}',
|
||||
// Guard against a stray horizontal scrollbar from any full-bleed child.
|
||||
'body.vc-fullwidth{overflow-x:hidden;}',
|
||||
// A gradient stands in when no hero image is set, so the banner is
|
||||
// never blank.
|
||||
'.vc-app .hero__media,.vc-app .hero-tour{background:linear-gradient(120deg,#101317,#2cafc2);}',
|
||||
'.vc-app.vc-app .hero__media,.vc-app.vc-app .hero-tour{background:linear-gradient(120deg,#101317,#2cafc2);}',
|
||||
// Normalize: stop Astra from decorating our components. Its
|
||||
// .entry-content rules (underlined links, list bullets/indent)
|
||||
// out-specify our scoped base, so reset them here (!important beats
|
||||
// e.g. `.ast-single-post .entry-content a`). The .vc-app.vc-app
|
||||
// doubling lifts specificity without touching design class rules.
|
||||
'.vc-app a,.vc-app.vc-app a{text-decoration:none;box-shadow:none;}',
|
||||
'.vc-app :is(ul,ol),.vc-app.vc-app :is(ul,ol){list-style:none!important;margin:0!important;padding:0!important;}',
|
||||
'.vc-app li{margin:0!important;}',
|
||||
// e.g. `.ast-single-post .entry-content a`). Every selector matches
|
||||
// the doubled `.vc-app.vc-app` scope of the main stylesheet so these
|
||||
// patches keep winning against it, not just against the theme.
|
||||
'.vc-app.vc-app a{text-decoration:none;box-shadow:none;}',
|
||||
// No !important on margin/padding: the doubled .vc-app.vc-app scope
|
||||
// already out-specifies Astra's list rules, and !important here was
|
||||
// steamrolling design lists that need their own spacing — e.g. the
|
||||
// rail (<ul class="rail">) whose vertical padding gives the hover glow
|
||||
// room. list-style stays !important (purely cosmetic; prose lists
|
||||
// re-enable bullets via a higher-specificity rule).
|
||||
'.vc-app.vc-app :is(ul,ol){list-style:none!important;margin:0;padding:0;}',
|
||||
'.vc-app.vc-app li{margin:0;}',
|
||||
// …but keep the tour "Overview" prose (the_content) readable.
|
||||
'.vc-app .tour__text a{text-decoration:underline;}',
|
||||
'.vc-app .tour__text ul{list-style:disc!important;margin:1em 0!important;padding-inline-start:1.5em!important;}',
|
||||
'.vc-app .tour__text ol{list-style:decimal!important;margin:1em 0!important;padding-inline-start:1.5em!important;}',
|
||||
'.vc-app .tour__text li{margin:.25em 0!important;}',
|
||||
'.vc-app.vc-app .tour__text a{text-decoration:underline;}',
|
||||
'.vc-app.vc-app .tour__text ul{list-style:disc!important;margin:1em 0!important;padding-inline-start:1.5em!important;}',
|
||||
'.vc-app.vc-app .tour__text ol{list-style:decimal!important;margin:1em 0!important;padding-inline-start:1.5em!important;}',
|
||||
'.vc-app.vc-app .tour__text li{margin:.25em 0!important;}',
|
||||
// Links must inherit the design colour (Astra colours links with its
|
||||
// theme colour, which turned white card titles/destination names into
|
||||
// coloured text). Buttons keep their own colour.
|
||||
'.vc-app a:not(.btn){color:inherit;}',
|
||||
'.vc-app .tour__text a{color:var(--c-teal);}',
|
||||
// …but not ring-badges: they set their own colour on the <a>, and this
|
||||
// inherit rule out-specifies .ring-badge--top/--scroll, which turned
|
||||
// the badge text white (inherited) on light backgrounds.
|
||||
'.vc-app.vc-app a:not(.btn):not(.ring-badge){color:inherit;}',
|
||||
'.vc-app.vc-app .tour__text a{color:var(--c-teal);}',
|
||||
// Real tour titles are far longer than the mockup\'s city names — clamp
|
||||
// them and stop price/duration values wrapping mid-number.
|
||||
'.vc-app .dcard__title{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
'.vc-app .titem__desc{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
'.vc-app .dcard__meta .meta__v{white-space:nowrap;}',
|
||||
'.vc-app.vc-app .dcard__title{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
'.vc-app.vc-app .titem__desc{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
// Real tour descriptions run long; the on-hover card blurb clamps to
|
||||
// three lines with an ellipsis instead of spilling over the card.
|
||||
'.vc-app.vc-app .dcard__desc p{display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}',
|
||||
'.vc-app.vc-app .dcard__meta .meta__v{white-space:nowrap;}',
|
||||
// Astra's page-builder layout centres/limits every entry-content child
|
||||
// (max-width:none; margin:auto), which flattened the hero inset. Our
|
||||
// sections are full-bleed, so re-assert 0 side-margins; !important
|
||||
// beats Astra's 4-class selector. The hero keeps its small inset — the
|
||||
// only section with an intentional side margin.
|
||||
'.vc-app.vc-app>*{max-width:none!important;margin-inline:0!important;}',
|
||||
// Heroes keep their small inset (rounded card look) — .hero on the
|
||||
// home/archive pages and .hero-tour on the single-tour page. Without
|
||||
// this they went full-bleed after the margin-inline:0 reset above.
|
||||
'.vc-app.vc-app>.hero,.vc-app.vc-app>.hero-tour{margin:var(--page-inset)!important;}',
|
||||
// Let vertical decorations (rotating badges, card glow, promo bus, FAQ
|
||||
// watermark) escape, but keep the horizontal axis clipped so a
|
||||
// full-bleed section or decorative element can't cause a mobile
|
||||
// sideways scroll. overflow-x:clip (unlike hidden) does NOT force
|
||||
// overflow-y to auto, so the y-axis stays truly visible.
|
||||
'.vc-app.vc-app{overflow-x:clip;overflow-y:visible;}',
|
||||
// Astra wraps the content in article/.entry-content/.ast-container,
|
||||
// some of which clip overflow — that cut the card hover glow at the
|
||||
// rail edges. Open them up on our pages only (page scroll stays
|
||||
// contained by body.vc-fullwidth{overflow-x:hidden}).
|
||||
'.vc-fullwidth .site-main,.vc-fullwidth #primary,.vc-fullwidth #content,'
|
||||
. '.vc-fullwidth .site-content,.vc-fullwidth .ast-container,'
|
||||
. '.vc-fullwidth .entry-content,.vc-fullwidth .site-main>article'
|
||||
. '{overflow:visible!important;}',
|
||||
// FAQ question buttons must stay flat — Astra gives <button> a shadow.
|
||||
'.vc-app.vc-app .acc__q{box-shadow:none!important;}',
|
||||
// Astra colours every heading on the element itself (h1–h6 and
|
||||
// .entry-content :where(h1..h6)), beating our container-inherited
|
||||
// colours. Make headings inherit again — no !important, so design
|
||||
// classes (.hero__title, the white .tcard titles) keep their own.
|
||||
'.vc-app.vc-app :is(h1,h2,h3,h4,h5,h6){color:inherit;}',
|
||||
// Typography: force the design font over Astra inside our scopes.
|
||||
'.vc-app,.vc-app h1,.vc-app h2,.vc-app h3,.vc-app h4,.vc-app h5,.vc-app h6,'
|
||||
. '.vc-app p,.vc-app a,.vc-app span,.vc-app li,.vc-app button,.vc-app input,'
|
||||
. '.vc-app select,.vc-app textarea,.vc-app blockquote'
|
||||
'.vc-app.vc-app,.vc-app.vc-app h1,.vc-app.vc-app h2,.vc-app.vc-app h3,.vc-app.vc-app h4,.vc-app.vc-app h5,.vc-app.vc-app h6,'
|
||||
. '.vc-app.vc-app p,.vc-app.vc-app a,.vc-app.vc-app span,.vc-app.vc-app li,.vc-app.vc-app button,.vc-app.vc-app input,'
|
||||
. '.vc-app.vc-app select,.vc-app.vc-app textarea,.vc-app.vc-app blockquote'
|
||||
. '{font-family:"Plus Jakarta Sans",system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}',
|
||||
)
|
||||
);
|
||||
|
||||
@@ -15,6 +15,9 @@ class VC_Sections {
|
||||
const TOP_ATTRS = 'viator_top_attractions';
|
||||
const ACTIVITIES = 'viator_browse_activities';
|
||||
const TOP_TOURS = 'viator_top_tours';
|
||||
const BUS_TOURS = 'viator_bus_tours';
|
||||
const ABOUT = 'viator_about';
|
||||
const ARTICLES = 'viator_articles';
|
||||
const FAQ = 'viator_faq';
|
||||
const REVIEWS = 'viator_testimonials';
|
||||
|
||||
@@ -28,6 +31,9 @@ class VC_Sections {
|
||||
add_shortcode( self::TOP_ATTRS, array( __CLASS__, 'top_attractions' ) );
|
||||
add_shortcode( self::ACTIVITIES, array( __CLASS__, 'browse_activities' ) );
|
||||
add_shortcode( self::TOP_TOURS, array( __CLASS__, 'top_tours' ) );
|
||||
add_shortcode( self::BUS_TOURS, array( __CLASS__, 'bus_tours' ) );
|
||||
add_shortcode( self::ABOUT, array( __CLASS__, 'about' ) );
|
||||
add_shortcode( self::ARTICLES, array( __CLASS__, 'articles' ) );
|
||||
add_shortcode( self::FAQ, array( __CLASS__, 'faq' ) );
|
||||
add_shortcode( self::REVIEWS, array( __CLASS__, 'testimonials' ) );
|
||||
}
|
||||
@@ -130,7 +136,7 @@ class VC_Sections {
|
||||
* Open a titled rail section and return its unique rail id. Caller fills the
|
||||
* <ul> with cards and then calls close_rail().
|
||||
*/
|
||||
private static function open_rail( $title, $lead, $view_all_url, $view_all_text ) {
|
||||
private static function open_rail( $title, $lead, $view_all_url, $view_all_text, $rail_class = 'rail--dest' ) {
|
||||
$id = 'vcRail' . ( ++self::$rail_seq );
|
||||
ob_start();
|
||||
?>
|
||||
@@ -150,7 +156,7 @@ class VC_Sections {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="rail rail--dest" id="<?php echo esc_attr( $id ); ?>">
|
||||
<ul class="rail <?php echo esc_attr( $rail_class ); ?>" id="<?php echo esc_attr( $id ); ?>">
|
||||
<?php
|
||||
return array( 'id' => $id, 'head' => ob_get_clean() );
|
||||
}
|
||||
@@ -450,6 +456,299 @@ class VC_Sections {
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* [viator_bus_tours limit="4" tag="hop-on-hop-off" image_id="123"]
|
||||
*
|
||||
* The "Open Top Bus Tours…" promo block + a 4-up grid of tour cards. Backed
|
||||
* by the same curated/filtered tour query as the rails. The bus illustration
|
||||
* is a heavy image, so it comes from the Media Library via image_id (or a
|
||||
* direct image URL) — nothing is bundled with the plugin.
|
||||
*/
|
||||
public static function bus_tours( $atts ) {
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'ids' => '',
|
||||
'codes' => '',
|
||||
'destination' => '',
|
||||
'tag' => '',
|
||||
'limit' => 4,
|
||||
'orderby' => 'rating',
|
||||
'title' => __( 'Open Top<br>Bus Tours…', 'viator-collector' ),
|
||||
'note' => __( 'An open-top hop on hop off bus is the best way to discover the historic and cultural sights of any city.', 'viator-collector' ),
|
||||
'chip' => __( 'Very Fun!', 'viator-collector' ),
|
||||
'image_id' => '', // WordPress media attachment ID for the bus illustration.
|
||||
'image' => '', // …or a direct image URL.
|
||||
),
|
||||
$atts,
|
||||
self::BUS_TOURS
|
||||
);
|
||||
|
||||
$query = self::tour_query( $atts );
|
||||
if ( ! $query->have_posts() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$archive = get_post_type_archive_link( 'viator_tour' );
|
||||
$archive = $archive ? $archive : home_url( '/' );
|
||||
|
||||
$bus_img = '';
|
||||
if ( '' !== $atts['image_id'] ) {
|
||||
// 'full' (uncropped original) — some sites register a hard-cropped
|
||||
// square 'large', which slices decorative illustrations in half.
|
||||
$bus_img = wp_get_attachment_image( absint( $atts['image_id'] ), 'full', false, array( 'alt' => '', 'loading' => 'lazy' ) );
|
||||
} elseif ( '' !== $atts['image'] ) {
|
||||
$bus_img = '<img src="' . esc_url( $atts['image'] ) . '" alt="" loading="lazy">';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="vc-app vc-sections">
|
||||
<section class="section section--tight">
|
||||
<div class="container bus">
|
||||
<div class="promo">
|
||||
<svg class="promo__route" viewBox="0 0 416 526" fill="none" aria-hidden="true" preserveAspectRatio="none">
|
||||
<path d="M-20 470 C 90 470, 60 350, 150 330 S 300 300, 300 210 S 210 120, 300 60 S 430 40, 440 -10" stroke="#2CAFC2" stroke-dasharray="3 3" />
|
||||
</svg>
|
||||
<div class="promo__head">
|
||||
<h2 class="promo__title"><?php echo wp_kses( $atts['title'], array( 'br' => array() ) ); ?></h2>
|
||||
<a class="btn btn--white" href="<?php echo esc_url( $archive ); ?>"><span><?php echo esc_html__( 'See all', 'viator-collector' ); ?></span><?php echo vc_icon( 'arrow-ne' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
|
||||
</div>
|
||||
<?php if ( $atts['chip'] ) : ?><span class="chip chip--accent promo__chip"><?php echo esc_html( $atts['chip'] ); ?></span><?php endif; ?>
|
||||
<?php if ( $bus_img ) : ?><div class="promo__bus" aria-hidden="true"><?php echo $bus_img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div><?php endif; ?>
|
||||
<?php if ( $atts['note'] ) : ?><p class="promo__note"><?php echo esc_html( $atts['note'] ); ?></p><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<ul class="bus__grid">
|
||||
<?php
|
||||
$i = 0;
|
||||
while ( $query->have_posts() ) :
|
||||
$query->the_post();
|
||||
++$i;
|
||||
$pid = get_the_ID();
|
||||
$price = get_post_meta( $pid, '_vc_price_from', true );
|
||||
$currency = get_post_meta( $pid, '_vc_currency', true );
|
||||
$terms = get_the_terms( $pid, 'viator_destination' );
|
||||
$kicker = ( $terms && ! is_wp_error( $terms ) ) ? $terms[0]->name : '';
|
||||
$active = ( 1 === $i );
|
||||
$badge = vc_ring_badge( 'BOOK NOW · BOOK NOW · ', get_permalink( $pid ), 'card' );
|
||||
if ( $active ) {
|
||||
$badge = str_replace( 'ring-badge--card', 'ring-badge--card ring-badge--accent', $badge );
|
||||
}
|
||||
?>
|
||||
<li class="tcard<?php echo $active ? ' is-active' : ''; ?>">
|
||||
<?php echo vc_tour_image_html( $pid, 'tcard__img' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<div class="tcard__top">
|
||||
<?php if ( $kicker ) : ?><span class="tcard__k"><?php echo esc_html( $kicker ); ?></span><?php endif; ?>
|
||||
<h3 class="tcard__title"><a href="<?php echo esc_url( get_permalink( $pid ) ); ?>"><?php echo esc_html( get_the_title( $pid ) ); ?></a></h3>
|
||||
</div>
|
||||
<div class="tcard__bottom">
|
||||
<?php if ( '' !== (string) $price ) : ?>
|
||||
<div class="meta">
|
||||
<span class="meta__k"><?php echo esc_html__( 'Start at', 'viator-collector' ); ?></span>
|
||||
<span class="meta__v"><?php echo esc_html( vc_money( $price, $currency ) ); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php echo $badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
</li>
|
||||
<?php endwhile; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?php
|
||||
wp_reset_postdata();
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* [viator_about image_id="123" title="Hop On Hop Off" highlight="Memorable"
|
||||
* stats="25+|Years Experience;;500+|Tours Conducted"]
|
||||
*
|
||||
* A static "About" marketing block (headline + chip, paragraph, stat counters
|
||||
* and a decorative bus). All copy is editable via attributes; the isometric
|
||||
* bus is a heavy image supplied from the Media Library (image_id / image).
|
||||
*/
|
||||
public static function about( $atts ) {
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'title' => __( 'Hop On Hop Off', 'viator-collector' ),
|
||||
'highlight' => __( 'Memorable', 'viator-collector' ),
|
||||
'text' => __( 'Discover cities the memorable way — hop on and off our open-top buses at every major sight and explore each place at your own pace, with expert local commentary along the way.', 'viator-collector' ),
|
||||
'button_text' => __( 'About us', 'viator-collector' ),
|
||||
'button_url' => '',
|
||||
'image_id' => '',
|
||||
'image' => '',
|
||||
'stats' => '', // "25+|Years Experience;;500+|Tours Conducted;;430+|Tours this Year;;15+|Different Tours"
|
||||
),
|
||||
$atts,
|
||||
self::ABOUT
|
||||
);
|
||||
|
||||
$stats = array();
|
||||
if ( '' !== $atts['stats'] ) {
|
||||
foreach ( explode( ';;', $atts['stats'] ) as $pair ) {
|
||||
$p = array_map( 'trim', explode( '|', $pair, 2 ) );
|
||||
if ( '' !== $p[0] && isset( $p[1] ) ) {
|
||||
$stats[] = array( 'num' => $p[0], 'label' => $p[1] );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( empty( $stats ) ) {
|
||||
$stats = array(
|
||||
array( 'num' => '25+', 'label' => __( 'Years Experience', 'viator-collector' ) ),
|
||||
array( 'num' => '500+', 'label' => __( 'Tours Conducted', 'viator-collector' ) ),
|
||||
array( 'num' => '430+', 'label' => __( 'Tours this Year', 'viator-collector' ) ),
|
||||
array( 'num' => '15+', 'label' => __( 'Different Tours', 'viator-collector' ) ),
|
||||
);
|
||||
}
|
||||
|
||||
$button_url = $atts['button_url'];
|
||||
if ( '' === $button_url ) {
|
||||
$button_url = get_post_type_archive_link( 'viator_tour' );
|
||||
$button_url = $button_url ? $button_url : home_url( '/' );
|
||||
}
|
||||
|
||||
$bus_img = '';
|
||||
if ( '' !== $atts['image_id'] ) {
|
||||
// 'full' (uncropped original) — some sites register a hard-cropped
|
||||
// square 'large', which slices decorative illustrations in half.
|
||||
$bus_img = wp_get_attachment_image( absint( $atts['image_id'] ), 'full', false, array( 'alt' => '', 'loading' => 'lazy' ) );
|
||||
} elseif ( '' !== $atts['image'] ) {
|
||||
$bus_img = '<img src="' . esc_url( $atts['image'] ) . '" alt="" loading="lazy">';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="vc-app vc-sections">
|
||||
<section class="section">
|
||||
<div class="container about">
|
||||
<div class="about__art" aria-hidden="true">
|
||||
<span class="about__glow"></span>
|
||||
<span class="about__trail" aria-hidden="true"></span>
|
||||
<?php if ( $bus_img ) : ?><span class="about__bus"><?php echo $bus_img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span><?php endif; ?>
|
||||
<svg class="about__arcs" viewBox="0 0 480 440" fill="none" aria-hidden="true">
|
||||
<defs>
|
||||
<path id="busArc" pathLength="100" d="M20 336.6V221.6C20 96.6 170 23.4 170 148.4V263.4" />
|
||||
</defs>
|
||||
<g stroke="#FFCC00" stroke-width="9" stroke-linecap="round" fill="none" transform="translate(16 24)">
|
||||
<use href="#busArc" opacity="0.1" stroke-dasharray="62 100" />
|
||||
<use href="#busArc" opacity="0.25" stroke-dasharray="69 100" transform="translate(48 22)" />
|
||||
<use href="#busArc" opacity="0.45" stroke-dasharray="76 100" transform="translate(96 44)" />
|
||||
<use href="#busArc" opacity="0.8" stroke-dasharray="83 100" transform="translate(144 66)" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="about__body">
|
||||
<div class="about__head">
|
||||
<h2 class="about__title"><?php echo esc_html( $atts['title'] ); ?><?php if ( $atts['highlight'] ) : ?><span class="chip chip--accent about__chip"><?php echo esc_html( $atts['highlight'] ); ?></span><?php endif; ?></h2>
|
||||
<a class="btn btn--dark" href="<?php echo esc_url( $button_url ); ?>"><span><?php echo esc_html( $atts['button_text'] ); ?></span><?php echo vc_icon( 'arrow-right' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></a>
|
||||
</div>
|
||||
|
||||
<?php if ( $atts['text'] ) : ?><p class="about__text"><?php echo esc_html( $atts['text'] ); ?></p><?php endif; ?>
|
||||
|
||||
<dl class="stats">
|
||||
<?php
|
||||
foreach ( $stats as $s ) :
|
||||
$num = $s['num'];
|
||||
$suffix = '';
|
||||
if ( preg_match( '/^(.*?)(\+)$/', $num, $m ) ) {
|
||||
$num = $m[1];
|
||||
$suffix = $m[2];
|
||||
}
|
||||
?>
|
||||
<div class="stat">
|
||||
<dt class="stat__num"><?php echo esc_html( $num ); ?><?php if ( $suffix ) : ?><span><?php echo esc_html( $suffix ); ?></span><?php endif; ?></dt>
|
||||
<dd class="stat__label"><?php echo esc_html( $s['label'] ); ?></dd>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* [viator_articles limit="8" category="news"]
|
||||
*
|
||||
* A rail of the latest blog posts, styled as the design's "Latest Articles"
|
||||
* cards. Uses each post's featured image, time and excerpt; links to the post.
|
||||
*/
|
||||
public static function articles( $atts ) {
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'title' => __( 'Latest<br>Articles', 'viator-collector' ),
|
||||
'lead' => __( 'News, tips and stories from our tours.', 'viator-collector' ),
|
||||
'limit' => 8,
|
||||
'category' => '',
|
||||
'view_all' => '',
|
||||
),
|
||||
$atts,
|
||||
self::ARTICLES
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => max( 1, absint( $atts['limit'] ) ),
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
);
|
||||
if ( '' !== $atts['category'] ) {
|
||||
$args['category_name'] = sanitize_title( $atts['category'] );
|
||||
}
|
||||
|
||||
$query = new WP_Query( $args );
|
||||
if ( ! $query->have_posts() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$view_all = $atts['view_all'];
|
||||
if ( '' === $view_all ) {
|
||||
$posts_page = (int) get_option( 'page_for_posts' );
|
||||
$view_all = $posts_page ? get_permalink( $posts_page ) : home_url( '/' );
|
||||
}
|
||||
|
||||
$rail = self::open_rail( $atts['title'], $atts['lead'], $view_all, __( 'See all', 'viator-collector' ), 'rail--art' );
|
||||
|
||||
ob_start();
|
||||
echo $rail['head']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$i = 0;
|
||||
while ( $query->have_posts() ) :
|
||||
$query->the_post();
|
||||
++$i;
|
||||
$pid = get_the_ID();
|
||||
$active = ( 1 === $i );
|
||||
$img = has_post_thumbnail( $pid ) ? get_the_post_thumbnail( $pid, 'large', array( 'class' => 'acard__img', 'loading' => 'lazy', 'alt' => '' ) ) : '';
|
||||
$time = human_time_diff( (int) get_post_time( 'U', true, $pid ), (int) current_time( 'timestamp', true ) );
|
||||
$excerpt = get_the_excerpt( $pid );
|
||||
$badge = vc_ring_badge( 'READ MORE · READ MORE · ', get_permalink( $pid ), 'card' );
|
||||
if ( $active ) {
|
||||
$badge = str_replace( 'ring-badge--card', 'ring-badge--card ring-badge--accent', $badge );
|
||||
}
|
||||
?>
|
||||
<li class="acard<?php echo $active ? ' is-active' : ''; ?>">
|
||||
<div class="acard__media">
|
||||
<?php echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<span class="chip chip--glass acard__time"><?php printf( /* translators: %s: human-readable time difference, e.g. "3 hours". */ esc_html__( '%s ago', 'viator-collector' ), esc_html( $time ) ); ?></span>
|
||||
<div class="acard__overlay">
|
||||
<h3 class="acard__title"><a href="<?php echo esc_url( get_permalink( $pid ) ); ?>"><?php echo esc_html( get_the_title( $pid ) ); ?></a></h3>
|
||||
<?php echo $badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ( $excerpt ) : ?><p class="acard__excerpt"><?php echo esc_html( $excerpt ); ?></p><?php endif; ?>
|
||||
</li>
|
||||
<?php
|
||||
endwhile;
|
||||
echo self::close_rail(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
wp_reset_postdata();
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared tour query for the landing sections. Supports curation (ids /
|
||||
* product codes) and filtering (destination / tag), with a sane fallback.
|
||||
|
||||
@@ -595,7 +595,10 @@ function vc_print_icon_sprite() {
|
||||
* e.g. vc_icon( 'star', 'rating__star' ) => <svg class="icon rating__star">…</svg>
|
||||
*/
|
||||
function vc_icon( $name, $extra_class = '' ) {
|
||||
$name = preg_replace( '/[^a-z0-9\-]/', '', 'i-' . ltrim( (string) $name, 'i-' ) );
|
||||
// Strip an existing "i-" prefix (once), then re-add it. ltrim() with a
|
||||
// charlist would eat any leading i/-, turning "instagram" into "nstagram".
|
||||
$slug = preg_replace( '/^i-/', '', (string) $name );
|
||||
$name = preg_replace( '/[^a-z0-9\-]/', '', 'i-' . $slug );
|
||||
$class = 'icon' . ( $extra_class ? ' ' . $extra_class : '' );
|
||||
return '<svg class="' . esc_attr( $class ) . '" aria-hidden="true"><use href="#' . esc_attr( $name ) . '"></use></svg>';
|
||||
}
|
||||
|
||||
@@ -38,9 +38,18 @@ while ( have_posts() ) :
|
||||
?>
|
||||
<div class="vc-app vc-single">
|
||||
<section class="hero-tour">
|
||||
<?php if ( $primary ) : ?>
|
||||
<?php
|
||||
// Prefer a WordPress featured image (full size, responsive srcset) for a
|
||||
// sharp hero — Viator's own image URLs cap out around 720px and look soft
|
||||
// blown up to a full-width banner. Fall back to the Viator primary image.
|
||||
if ( has_post_thumbnail( $post_id ) ) :
|
||||
echo get_the_post_thumbnail( $post_id, 'full', array( 'class' => 'hero-tour__photo', 'fetchpriority' => 'high', 'alt' => '', 'sizes' => '100vw' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
elseif ( $primary ) :
|
||||
?>
|
||||
<img class="hero-tour__photo" src="<?php echo esc_url( $primary ); ?>" alt="" fetchpriority="high">
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
<div class="hero-tour__sky" aria-hidden="true"></div>
|
||||
<div class="container">
|
||||
<div class="hero-tour__block">
|
||||
|
||||
@@ -4,13 +4,13 @@ defined('ABSPATH') || exit;
|
||||
/**
|
||||
* Plugin Name: Viator Collector
|
||||
* Description: Import Viator tours using the Viator Partner API and display them with native WordPress post types, templates, and shortcodes.
|
||||
* Version: 1.8.4
|
||||
* Version: 1.8.15
|
||||
* Author: Amir Bagheri
|
||||
* License: GPL-2.0-or-later
|
||||
* Text Domain: viator-collector
|
||||
*/
|
||||
|
||||
define('VC_VERSION', '1.8.4');
|
||||
define('VC_VERSION', '1.8.15');
|
||||
define('VC_PLUGIN_FILE', __FILE__);
|
||||
define('VC_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('VC_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
Reference in New Issue
Block a user