← back to Robet Site
auto-save: 2026-06-30T05:28:32 (5 files) — config.json public/app.js public/index.html server.js tests/api.test.mjs
0d21f3b31d1376d71b248866b9c55a028c3fda75 · 2026-06-30 05:28:36 -0700 · Steve Abrams
Files touched
M config.jsonM public/app.jsM public/index.htmlM server.jsM tests/api.test.mjs
Diff
commit 0d21f3b31d1376d71b248866b9c55a028c3fda75
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 30 05:28:36 2026 -0700
auto-save: 2026-06-30T05:28:32 (5 files) — config.json public/app.js public/index.html server.js tests/api.test.mjs
---
config.json | 5 ++---
public/app.js | 13 +++++++++++--
public/index.html | 5 ++---
server.js | 10 +++++-----
tests/api.test.mjs | 7 ++++---
5 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/config.json b/config.json
index 4c97d1d..6567640 100644
--- a/config.json
+++ b/config.json
@@ -11,9 +11,8 @@
"hours": "By appointment — pick a chair below"
},
"scheduling": {
- "owner": "robert@agentabrams.com",
- "embedLoader": "https://venturacorridor.com/embed/appointments.js",
- "bookUrl": "https://venturacorridor.com/book?owner=robert@agentabrams.com"
+ "bookUrl": "",
+ "_note": "Robert's OWN booking link (Calendly / Cal.com / Square Appointments). Standalone — NOT wired to any DW/venturacorridor/dw_unified infra. Empty → the Book button routes to the contact form."
},
"social": []
}
diff --git a/public/app.js b/public/app.js
index 7f591b3..9656c9a 100644
--- a/public/app.js
+++ b/public/app.js
@@ -51,10 +51,19 @@
}
});
- // ── Smart Scheduling: link-first (reliable) — the live /book page does the real booking ──
+ // ── Booking: link to Robert's OWN scheduler (Calendly/Cal/Square). Until set → contact form. ──
const sched = cfg.scheduling || {};
const bookPrimary = $('#bookPrimary');
- if (bookPrimary && sched.bookUrl) bookPrimary.href = sched.bookUrl;
+ if (bookPrimary) {
+ if (sched.bookUrl) {
+ bookPrimary.href = sched.bookUrl;
+ } else {
+ bookPrimary.href = '#contact';
+ bookPrimary.textContent = 'Get in touch to book →';
+ bookPrimary.removeAttribute('target');
+ bookPrimary.removeAttribute('rel');
+ }
+ }
// ── gallery: render real photos from /api/photos, else tasteful placeholders ──
(async () => {
diff --git a/public/index.html b/public/index.html
index 38647f9..d34c298 100644
--- a/public/index.html
+++ b/public/index.html
@@ -6,7 +6,6 @@
<title>ROBERT · Barber</title>
<meta name="description" content="Book a cut with Robert — barber. Pick a time, pick your cut, walk in sharp." />
<link rel="canonical" href="https://robert.agentabrams.com/" />
- <link rel="preconnect" href="https://venturacorridor.com" crossorigin />
<meta name="theme-color" content="#0a0a0a" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
@@ -44,7 +43,7 @@
"name": "Book a cut",
"target": {
"@type": "EntryPoint",
- "urlTemplate": "https://venturacorridor.com/book?owner=robert@agentabrams.com",
+ "urlTemplate": "https://robert.agentabrams.com/#booking",
"actionPlatform": [
"https://schema.org/DesktopWebPlatform",
"https://schema.org/MobileWebPlatform"
@@ -108,7 +107,7 @@ CUT.</h1>
<span class="kicker">/ book a chair</span>
<h2 class="sec-h">Pick a time.</h2>
<p class="mono" style="font-size:18px; max-width:560px; margin-bottom:24px; font-weight:600;">
- Live availability — pick a slot and it lands straight in the chair. Opens the booking page in a new tab.
+ Book your next cut — grab a slot online, or reach out and I'll fit you in.
</p>
<a class="cta" id="bookPrimary" href="#" target="_blank" rel="noopener noreferrer"
style="font-size:28px; padding:20px 40px;">Book your cut →</a>
diff --git a/server.js b/server.js
index 12ec189..d64d190 100644
--- a/server.js
+++ b/server.js
@@ -2,7 +2,7 @@
/*
* robet-site — brutalist marketing site
* • config-driven brand/contact (config.json)
- * • Smart Scheduling booking (embeds venturacorridor.com appointments loader)
+ * • Booking via Robert's own scheduler link (standalone — no DW/venturacorridor infra)
* • /admin API-key entry page (basic-auth) → data/keys.json (gitignored)
*
* Zero external deps beyond express. A tiny inline .env loader avoids dotenv.
@@ -35,12 +35,12 @@ app.use(express.urlencoded({ extended: false }));
// ── security headers (defense-in-depth; nginx also sets some on prod) ──
const CSP = [
"default-src 'self'",
- "script-src 'self' https://venturacorridor.com",
+ "script-src 'self'",
"style-src 'self' 'unsafe-inline'", // inline style="" attrs on the hero/admin
- "img-src 'self' data: https://venturacorridor.com",
+ "img-src 'self' data:",
"font-src 'self'",
- "connect-src 'self' https://venturacorridor.com",
- "frame-src https://venturacorridor.com", // Smart Scheduling booking iframe
+ "connect-src 'self'",
+ "frame-src 'none'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
diff --git a/tests/api.test.mjs b/tests/api.test.mjs
index d6d04d4..e10dec6 100644
--- a/tests/api.test.mjs
+++ b/tests/api.test.mjs
@@ -41,7 +41,7 @@ test('home page renders brand + OG meta', async () => {
test('/api/config exposes brand + scheduling, no secrets', async () => {
const j = await (await fetch(`${BASE}/api/config`)).json();
assert.equal(j.brand, 'ROBERT');
- assert.ok(j.scheduling?.owner, 'scheduling owner present');
+ assert.ok(j.scheduling && 'bookUrl' in j.scheduling, 'scheduling.bookUrl key present (standalone, no DW owner)');
assert.equal(JSON.stringify(j).includes('ADMIN_PASS'), false);
});
@@ -49,7 +49,8 @@ test('security headers present', async () => {
const r = await fetch(`${BASE}/`);
assert.ok(r.headers.get('content-security-policy'), 'CSP set');
assert.equal(r.headers.get('x-content-type-options'), 'nosniff');
- assert.match(r.headers.get('content-security-policy'), /venturacorridor\.com/);
+ assert.match(r.headers.get('content-security-policy'), /default-src 'self'/);
+ assert.match(r.headers.get('content-security-policy'), /frame-src 'none'/); // de-entangled: no 3rd-party embeds
});
test('static SEO assets serve', async () => {
@@ -92,7 +93,7 @@ test('homepage embeds valid JSON-LD with a booking action', async () => {
const j = JSON.parse(m[1]); // throws if invalid → test fails
assert.equal(j['@type'], 'HairSalon');
assert.equal(j.potentialAction['@type'], 'ScheduleAction');
- assert.match(j.potentialAction.target.urlTemplate, /venturacorridor\.com\/book/);
+ assert.match(j.potentialAction.target.urlTemplate, /robert\.agentabrams\.com/);
});
test('malformed JSON body → clean JSON error, not HTML stack', async () => {
← a673808 robert barber v2: gallery (drop photos in public/photos, /ap
·
back to Robet Site
·
de-entangle friend build from DW infra: remove venturacorrid a7ab1ca →