# Viator Collector — "Hop On Hop Off" Front-End Re-skin **Date:** 2026-09-11 **Status:** Design — pending approval **Author:** Amir Bagheri (with Claude) ## 1. Goal Replace the entire visual front-end of the `viator-collector` WordPress plugin with the new "Hop On Hop Off" design that currently exists as a static mockup in `../new front/`, **without changing any of the plugin's data model, query logic, or WordPress plumbing**. Every dynamic value keeps coming from the `viator_tour` post type, its meta, and its taxonomies exactly as today; only markup, CSS, icons, JS interactions, and decorative assets change. ## 2. Decisions (locked) | Question | Decision | |---|---| | Surfaces to re-skin | All three: listing/archive, single tour, homepage sections | | Header / nav / footer | **Content only** — the WordPress theme (e.g. Astra) keeps providing site header/nav/footer. The plugin re-skins only its content area. | | Listing filter behavior | **Keep server-side** (WP_Query + GET params + server pagination). Only the look changes. No switch to `main.js` client-side filtering. | | Homepage static chrome | **Include it** — plugin also ships hero/booking, FAQ, and testimonials as shortcodes so the full `index.html` look is achievable. | | Old CSS/JS | **Replace** — retire `vc-listing.css`, `vc-single.css`, `viator-collector.css`, `vc-sections.css`, and the Lucide dependency. | ## 3. Source of truth The three canonical mockup pages are: - `new front/index.html` — homepage (hero+booking, destinations rail, feature/bus section, most-liked tours rail, about, testimonials marquee, articles, FAQ) - `new front/tours.html` — listing/archive (hero with horizontal filter bar, tour grid, results count, pager) - `new front/tour.html` — single tour (hero-tour banner, facts, overview, gallery, includes/excludes, good-to-know, cancellation policy, reviews, booking aside) Driven by `new front/css/style.css` (~3096 lines, token-based) and `new front/js/main.js` (~419 lines). **Ignored:** `new front/tourdetailsinfo.html` and `new front/tourdetailscontent.html` are raw Figma/React export scraps (absolute-positioned, Tailwind classNames). They are not part of the design system and will not be used except as visual reference for the booking aside (already fully expressed in `tour.html`). ## 4. Architecture ### 4.1 Scoping strategy (critical) Because the plugin renders inside a WordPress theme, the new design's generic class names (`.container`, `.section`, `.hero`, `.btn`, `.rating`, `.meta`, `.dcard`, `.footer`, `.site-header`…) and its global resets (`*`, `html`, `body`, `ul`, `a`, `button`) **must not leak** into or fight the theme. Approach: - Every plugin surface is wrapped in the existing `#vc-app` scope (already used by `shortcode_listing` and `single-viator_tour.php`). Homepage section shortcodes get wrapped in `#vc-app` too. - The ported stylesheet is transformed so **every rule is prefixed with `#vc-app`** (e.g. `.dcard` → `#vc-app .dcard`). The `:root { --… }` token block stays global (harmless custom properties). - Global resets on `*`/`html`/`body` are dropped or converted to scoped equivalents (`#vc-app`, `#vc-app *`). `scroll-behavior`, `overflow-x:hidden` on `html`/`body` are dropped (theme's concern). - Header/nav/footer CSS (`.site-header`, `.nav`, `.burger`, `.footer`, `.skip-link`) from the mockup is **not** ported — the theme owns those. Exception: the `.footer` "back to top" ring-badge is a reusable widget we may keep for section use; TBD, default = drop. ### 4.2 Asset organization New plugin asset layout: ``` viator-collector/assets/ css/ vc-design.css # ported + scoped style.css (single stylesheet) js/ vc-frontend.js # existing server-side filter behaviors (kept, trimmed) vc-design.js # ported interactions: rails, accordion, marquee, ring-badge img/ (decorative design assets copied from new front/assets: bus-iso.png, bus-open-top.png, tower.png, ellipse-17.png, ellipse-17-2.png, hero.png, hero2.png, hero3.jpg) icons/ sprite.svg # the icon set + #ringPathScroll path def ``` - **Decorative** images (bus, tower, ellipses, hero backdrops) ship with the plugin — they are chrome, not tour data. - **Demo tour images** (`city-*.png`, `liked-*.png`, `tour-*.png`) are NOT shipped; those slots are filled by real tour data (`_vc_primary_image_url`, gallery URLs) via `vc_tour_image_html()`. ### 4.3 Icons The mockup uses an inline SVG sprite: a hidden `` holding `` defs, referenced by ``. It also defines `#ringPathScroll` (the circular text path for the "BOOK NOW" badge). - Ship the sprite as `assets/icons/sprite.svg` and print it once per plugin page via a small helper (`vc_print_icon_sprite()`), guarded so it prints at most once. - Add a helper `vc_icon( $name )` returning ``. - Replace all current `` usages with `vc_icon()`. Lucide enqueue is removed. Icon name mapping (Lucide → sprite): | Lucide | Sprite | |---|---| | map-pin | i-location | | clock | i-duration | | users / user | i-person / i-user | | shield-check | i-shield-check | | calendar-check | i-calendar | | rotate-ccw | i-duration (or reuse) | | clipboard-list | i-grid | | images | i-grid | | star (rating) | i-star | (Exact per-call mapping finalized during implementation; any missing glyph is added to the sprite.) ### 4.4 JavaScript `vc-design.js` (new) ports from `main.js` only what our content uses: - **Card rails** — `.rail` horizontal scroll with `[data-rail-prev/next]` buttons (used by homepage destination/tour rails). - **FAQ accordion** — `[data-accordion]` / `.acc__q`. - **Testimonials marquee** — `[data-marquee]` duplicate-for-loop. - **Ring-badge** — the rotating "BOOK NOW" SVG textPath (needs `#ringPathScroll` in the sprite; animation is CSS). - Hero booking/availability form `preventDefault` demo handlers become real submits to the tour's booking URL / listing action (no console stubs). **Explicitly NOT ported:** `main.js` client-side listing filter/sort/ pager (server-side stays), and header/nav/burger/sticky-header (theme's). `vc-frontend.js` is kept for the server-side listing behaviors it already provides (auto-submit selects/checks, filter drawer on mobile). The noUiSlider price slider is **removed** — the new design uses a Price Range `` → `dest[]` (single value) - Category `` → `price` (values like `0-50`, `50-150`, `150-1000`; already parsed by `get_filter_state()`) - Duration `