Add viator-collector plugin (Hop On Hop Off front-end reskin)
Consolidate the plugin into the TourWebsite repository (its separate nested git repo has been removed). Includes the full new-design reskin: scoped design stylesheet, SVG icon sprite, rebuilt listing/single/section templates, and homepage hero/FAQ/testimonials shortcodes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J4hNL1PSHxXbu94SCenpKj
This commit is contained in:
159
viator-collector/build/scope-css.js
Normal file
159
viator-collector/build/scope-css.js
Normal file
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* scope-css.js — prefix every selector in the "new front" stylesheet with
|
||||
* `#vc-app` so the ported design cannot leak into (or fight) the WordPress
|
||||
* theme. Dependency-free brace-aware parser.
|
||||
*
|
||||
* node build/scope-css.js "../new front/css/style.css" assets/css/vc-design.css
|
||||
*
|
||||
* Rules:
|
||||
* - `:root { ... }` is left untouched (global custom properties).
|
||||
* - `@keyframes` names and step selectors (`0%`, `from`, `to`) are untouched.
|
||||
* - `@font-face` descriptors are untouched.
|
||||
* - `@media` / `@supports` wrappers are kept; their inner rules are scoped.
|
||||
* - Leading `html` / `body` / `*` tokens are mapped into the `#vc-app` scope.
|
||||
* - Every other selector gets a `#vc-app ` prefix.
|
||||
*/
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
|
||||
const SCOPE = '.vc-app';
|
||||
|
||||
function prefixSelector(sel) {
|
||||
sel = sel.trim();
|
||||
if (!sel) return sel;
|
||||
// Leave :root and keyframe-ish untouched (keyframes handled by caller).
|
||||
if (sel === ':root' || sel.startsWith(':root')) return sel;
|
||||
// html -> scope root
|
||||
if (sel === 'html' || sel.startsWith('html ') || sel.startsWith('html.') ||
|
||||
sel.startsWith('html:') || sel.startsWith('html[')) {
|
||||
return SCOPE + sel.slice(4);
|
||||
}
|
||||
// body -> scope root
|
||||
if (sel === 'body' || sel.startsWith('body ') || sel.startsWith('body.') ||
|
||||
sel.startsWith('body:') || sel.startsWith('body[') || sel.startsWith('body>')) {
|
||||
return SCOPE + sel.slice(4);
|
||||
}
|
||||
// Universal selector: `*`, `*::before`, `* + *` ...
|
||||
if (sel === '*' || sel.startsWith('*')) {
|
||||
return SCOPE + ' ' + sel;
|
||||
}
|
||||
return SCOPE + ' ' + sel;
|
||||
}
|
||||
|
||||
function prefixSelectorList(prelude) {
|
||||
return prelude
|
||||
.split(',')
|
||||
.map(function (s) { return prefixSelector(s); })
|
||||
.join(',\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a run of CSS text at a given nesting context.
|
||||
* @param {string} css
|
||||
* @param {boolean} inKeyframes when true, rule preludes are step selectors -> keep as-is
|
||||
*/
|
||||
function transform(css, inKeyframes) {
|
||||
let out = '';
|
||||
let i = 0;
|
||||
const n = css.length;
|
||||
let buf = '';
|
||||
|
||||
while (i < n) {
|
||||
const ch = css[i];
|
||||
|
||||
// Preserve comments verbatim.
|
||||
if (ch === '/' && css[i + 1] === '*') {
|
||||
const end = css.indexOf('*/', i + 2);
|
||||
const stop = end === -1 ? n : end + 2;
|
||||
buf += css.slice(i, stop);
|
||||
i = stop;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '{') {
|
||||
const prelude = buf;
|
||||
buf = '';
|
||||
// Find the matching closing brace for this block.
|
||||
let depth = 1;
|
||||
let j = i + 1;
|
||||
while (j < n && depth > 0) {
|
||||
if (css[j] === '/' && css[j + 1] === '*') {
|
||||
const e = css.indexOf('*/', j + 2);
|
||||
j = e === -1 ? n : e + 2;
|
||||
continue;
|
||||
}
|
||||
if (css[j] === '{') depth++;
|
||||
else if (css[j] === '}') depth--;
|
||||
if (depth === 0) break;
|
||||
j++;
|
||||
}
|
||||
const inner = css.slice(i + 1, j);
|
||||
// Pull any comments out of the prelude and emit them before the rule,
|
||||
// so a comment sitting in front of a selector never gets scoped into it.
|
||||
const split = extractComments(prelude);
|
||||
out += split.comments;
|
||||
const trimmed = split.clean.trim();
|
||||
const lower = trimmed.toLowerCase();
|
||||
|
||||
if (lower.startsWith('@keyframes') || lower.startsWith('@-webkit-keyframes')) {
|
||||
// Keep name; do not scope step selectors inside.
|
||||
out += trimmed + ' {' + inner + '}';
|
||||
} else if (lower.startsWith('@font-face')) {
|
||||
out += trimmed + ' {' + inner + '}';
|
||||
} else if (lower.startsWith('@media') || lower.startsWith('@supports') ||
|
||||
lower.startsWith('@container') || lower.startsWith('@layer')) {
|
||||
// Recurse into the block to scope inner rules.
|
||||
out += trimmed + ' {\n' + transform(inner, false) + '}';
|
||||
} else if (lower.startsWith('@')) {
|
||||
// Any other at-rule with a block: leave prelude, recurse cautiously.
|
||||
out += trimmed + ' {' + transform(inner, inKeyframes) + '}';
|
||||
} else if (inKeyframes) {
|
||||
out += trimmed + ' {' + inner + '}';
|
||||
} else {
|
||||
// Ordinary style rule -> scope the selector list.
|
||||
out += prefixSelectorList(trimmed) + ' {' + inner + '}';
|
||||
}
|
||||
out += '\n\n';
|
||||
i = j + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '}') {
|
||||
// Stray close (end of a recursion slice) — flush and stop-ish.
|
||||
out += buf;
|
||||
buf = '';
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
buf += ch;
|
||||
i++;
|
||||
}
|
||||
out += buf;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Separate comments from the selector prelude. Comments are returned intact
|
||||
// (to be emitted before the rule); `clean` is the comment-free selector text.
|
||||
function extractComments(prelude) {
|
||||
let comments = '';
|
||||
const clean = prelude.replace(/\/\*[\s\S]*?\*\//g, function (c) {
|
||||
comments += c + '\n';
|
||||
return ' ';
|
||||
});
|
||||
return { comments: comments, clean: clean };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const src = process.argv[2] || '../new front/css/style.css';
|
||||
const dest = process.argv[3] || 'assets/css/vc-design.css';
|
||||
const css = fs.readFileSync(src, 'utf8');
|
||||
const header = '/* Generated by build/scope-css.js from ' + src +
|
||||
' — do not edit by hand. */\n\n';
|
||||
const out = header + transform(css, false).replace(/\n{3,}/g, '\n\n');
|
||||
fs.writeFileSync(dest, out, 'utf8');
|
||||
process.stdout.write('Wrote ' + dest + ' (' + out.length + ' bytes)\n');
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user