Add LIVE indicator and finalize front page for production - #6
Draft
fsu9913-gif wants to merge 13 commits into
Draft
Add LIVE indicator and finalize front page for production#6fsu9913-gif wants to merge 13 commits into
fsu9913-gif wants to merge 13 commits into
Conversation
Rebuild of the live rent-ruby.com front page per markup review: - Slogans removed; hero leads with address and walk/transit facts - Logo box removed; sticky header carries name, address, phone, email (NAP) - Waiting list intake form wired to new /api/waitlist endpoint with mailto fallback - Amenity rail on the right of the hero and amenity bands at the bottom of page one and top of page two - Call/text and email are large live tel:/mailto: buttons - Ruby accent by default with an in-page ruby/orange comparison toggle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMQkVd9hduxce8PdRMHWwY
…onsolidation-bniwny
Pull the 3875 Ruby St primary photo (converted from AVIF to JPEG) into public/assets and fix the hero image layering so it renders under the scrim gradients. Add a left-side scrim so the headline stays legible over the light facade. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMQkVd9hduxce8PdRMHWwY
Floating chat agent scoped to the Ruby Building plus neighborhood concierge topics: MacArthur BART schedules, Ticketmaster events, restaurants and shopping within ~2 miles, and Oakland 94609 residential parking permit rules, each with links. Quick-ask chips for common questions. Server side: POST /api/wizard backed by Gemini (gemini-2.5-flash) with a building-scoped system prompt, conversation logging to a new wizard_chats table, and lead alerts — new chats, messages containing contact info, and waitlist signups notify via Google Chat webhook and email (Resend), configured through GOOGLE_CHAT_WEBHOOK, WIZARD_ALERT_EMAIL, and RESEND_API_KEY. The widget falls back to scripted concierge answers when the API is unavailable (static hosting). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMQkVd9hduxce8PdRMHWwY
- Added visible "LIVE" badge with pulsing green dot in header - Ensures visitors know this is the production rent-ruby.com site - Removed A/B theme toggle (ruby/orange decision was for review meeting only) - Removed unused orange theme CSS variant Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMQkVd9hduxce8PdRMHWwY
| for (const part of parts) { | ||
| if (/^https?:\/\//.test(part)) { | ||
| const a = document.createElement('a'); | ||
| a.href = part; a.target = '_blank'; a.rel = 'noopener'; |
Comment on lines
+1131
to
+1167
| app.post("/api/wizard", async (req, res) => { | ||
| const { messages, sessionId } = req.body; | ||
| if (!Array.isArray(messages) || messages.length === 0) { | ||
| return res.status(400).json({ error: "messages array is required" }); | ||
| } | ||
| const session = String(sessionId || "anon").slice(0, 64); | ||
| const latest = messages[messages.length - 1]; | ||
| const latestText = String(latest?.text || "").slice(0, 2000); | ||
| const isFirstMessage = !db.prepare("SELECT 1 FROM wizard_chats WHERE session_id = ? LIMIT 1").get(session); | ||
| db.prepare("INSERT INTO wizard_chats (session_id, role, text) VALUES (?, 'user', ?)").run(session, latestText); | ||
| if (isFirstMessage) { | ||
| notifyKeeper("New Wizard of Mosswood chat", `Session ${session}\nFirst question: ${latestText}`); | ||
| } else if (CONTACT_INFO_RE.test(latestText)) { | ||
| notifyKeeper("Wizard chat left contact info", `Session ${session}\nMessage: ${latestText}`); | ||
| } | ||
| const apiKey = process.env.GEMINI_API_KEY; | ||
| if (!apiKey || apiKey === "MY_GEMINI_API_KEY") { | ||
| return res.json({ reply: null, fallback: true }); | ||
| } | ||
| try { | ||
| const { GoogleGenAI } = await import("@google/genai"); | ||
| const ai = new GoogleGenAI({ apiKey }); | ||
| const response = await ai.models.generateContent({ | ||
| model: "gemini-2.5-flash", | ||
| config: { systemInstruction: WIZARD_SYSTEM_PROMPT, maxOutputTokens: 400 }, | ||
| contents: messages.slice(-12).map((m: { role?: string; text?: string }) => ({ | ||
| role: m.role === "model" ? "model" : "user", | ||
| parts: [{ text: String(m.text || "").slice(0, 2000) }], | ||
| })), | ||
| }); | ||
| const reply = response.text || ""; | ||
| db.prepare("INSERT INTO wizard_chats (session_id, role, text) VALUES (?, 'model', ?)").run(session, reply.slice(0, 4000)); | ||
| res.json({ reply }); | ||
| } catch (e) { | ||
| res.json({ reply: null, fallback: true }); | ||
| } | ||
| }); |
| config: { systemInstruction: WIZARD_SYSTEM_PROMPT, maxOutputTokens: 400 }, | ||
| contents: messages.slice(-12).map((m: { role?: string; text?: string }) => ({ | ||
| role: m.role === "model" ? "model" : "user", | ||
| parts: [{ text: String(m.text || "").slice(0, 2000) }], |
Comment on lines
+1169
to
+1172
| app.get("/api/wizard/chats", (req, res) => { | ||
| const rows = db.prepare("SELECT * FROM wizard_chats ORDER BY created_at DESC LIMIT 500").all(); | ||
| res.json(rows); | ||
| }); |
Comment on lines
+1174
to
+1177
| app.get("/api/waitlist", (req, res) => { | ||
| const rows = db.prepare("SELECT * FROM waitlist_signups ORDER BY created_at DESC").all(); | ||
| res.json(rows); | ||
| }); |
Comment on lines
+1179
to
+1193
| app.post("/api/waitlist", (req, res) => { | ||
| const { name, phone, email, desired_move_in, unit_pref, notes } = req.body; | ||
| if (!name || !phone || !email) { | ||
| return res.status(400).json({ error: "name, phone, and email are required" }); | ||
| } | ||
| const result = db.prepare(` | ||
| INSERT INTO waitlist_signups (name, phone, email, desired_move_in, unit_pref, notes) | ||
| VALUES (?, ?, ?, ?, ?, ?) | ||
| `).run(name, phone, email, desired_move_in || null, unit_pref || null, notes || null); | ||
| notifyKeeper( | ||
| "New Ruby waiting list signup", | ||
| `Name: ${name}\nPhone: ${phone}\nEmail: ${email}\nMove-in: ${desired_move_in || "flexible"}\nUnit: ${unit_pref || "no preference"}\nNotes: ${notes || ""}` | ||
| ); | ||
| res.json({ id: result.lastInsertRowid }); | ||
| }); |
Replace Math.random() with crypto.getRandomValues() to address CodeQL's insecure randomness flag. This ensures session IDs use proper cryptographic randomness instead of pseudo-random. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMQkVd9hduxce8PdRMHWwY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds visible "LIVE" indicator to rent-ruby.com header and removes the theme toggle decision interface. The site is now ready for production deployment.
Changes
Visual
The LIVE badge appears in the header with:
Ready for Deployment
This branch includes:
All other work (DMC tenant portal, rent-roll artifacts) deferred indefinitely per latest requirements.
Generated by Claude Code