Skip to content

feat(landing-purchase): Astro Landing/Purchase page + weownchat-design skill - #165

Merged
ncimino merged 4 commits into
WeOwnNetwork:mainfrom
SinachPat:feature/patrick-landing-purchase-astro
Aug 9, 2026
Merged

feat(landing-purchase): Astro Landing/Purchase page + weownchat-design skill#165
ncimino merged 4 commits into
WeOwnNetwork:mainfrom
SinachPat:feature/patrick-landing-purchase-astro

Conversation

@SinachPat

@SinachPat SinachPat commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Rebuild of the WeOwnChat Landing/Purchase page (PRD roadmap item 2) on Astro + Tailwind CSS v4 (static output), replacing #163 (closed) which was built on Next.js. Same page, same content, same design — different, and leaner, foundation.

Why the switch: direct instruction to match the framework used elsewhere in the WeOwn ecosystem. Worth being upfront: a repo-wide search across this specific repository found no existing Astro usage anywhere to match conventions against (no astro.config, no .astro files, nothing in any package.json), so the setup below reflects this project's own reasonable defaults, not an established in-repo pattern.

What's the same as #163

All content, copy, and design carry over unchanged: WeOwnChat branding (not "WeOwn AI" — see #164), the real dashboard's color/type/radius tokens via .claude/skills/weownchat-design/, the "Live preview" section embedding the actual dashboard HTML in an iframe with seeded demo content, the two-tab private/public explainer, pricing with no dollar figure, the FAQ content, everything.

What's different

  • Framework: Astro (static HTML/CSS output) instead of Next.js/React. Interactive bits that were React state are now small, targeted vanilla-JS scripts: the hero's animated chat loop, the nav mobile menu, the live-preview Private/Public tab toggle.
  • FAQ accordion is now zero-JS — native <details name="faq-accordion"> gives the exact same "only one open at a time" behavior the React useState version had, natively. This is incidentally the same collapsible pattern the real dashboard's own .doc-strip CSS already uses.
  • npm audit: 0 findings (was 3 high-severity, nested inside Next.js's own postcss/sharp). Not the reason for the rewrite, but a real side benefit of the newer, smaller dependency tree.
  • Scroll-reveal is hardened, not just ported. The Next.js version hid [data-reveal] content at opacity:0 unconditionally, fully dependent on React + IntersectionObserver to ever make it visible. Porting it surfaced a real gap worth fixing regardless of the rewrite: IntersectionObserver was observed, during testing, to sometimes never fire its callback at all — even for an element plainly sitting in the viewport — in the specific automated browser environment used here. Cause unconfirmed (real browsers are presumably fine; this is an extremely well-supported API), but "unconfirmed" isn't "ruled out," so it's fixed properly: elements are visible by default and only opt into the hidden-then-reveal transition once the script actually runs (.reveal-armed), and a 2.5s timeout force-reveals anything the observer never fires for. Nothing on the page can end up permanently invisible because one animation mechanism didn't fire.

A real, pre-existing bug found and fixed along the way

The repo-root .gitignore's Icon? rule (the classic macOS "ignore the special custom-folder-icon file" entry) turned out to silently match any file starting with "Icon" longer than 5 characters in this specific file's context — not just the intended 5-byte special file. It ate this project's own icon components (IconChat.astro etc.) until the build showed them missing and I traced it back. Reproduced in an isolated sandbox against the real file to confirm before touching it. Fixed with an unambiguous bracket-negation (Icon[^a-zA-Z0-9]), verified to still catch the actual macOS special file (Icon + a literal CR byte) while no longer matching normal source filenames. Full repro/reasoning is in the commit message.

Verification

  • astro check && astro build — 0 errors/warnings/hints, clean static generation.
  • Checked in-browser at desktop, 1440px, and mobile (390px) widths.
  • FAQ exclusive-accordion behavior and mobile nav toggle confirmed via direct DOM inspection (details.open state, aria-expanded), not just visual screenshots.
  • Scroll-reveal safety net confirmed: content reaches opacity:1 either via genuine intersection or the timeout fallback, tested both paths directly.
  • Manual secret/PII grep over all 39 newly-tracked files — clean.

Open follow-ups (tracked in landing-purchase/CHANGELOG.md)

  • Hosting/deployment target still not decided (Astro's static output keeps the Docker Compose + droplet path and a static host equally easy).
  • Page copy/IA hasn't had a second pass against the Branding & Marketing guide's guardrails beyond this first build.
  • No legal/privacy pages, no analytics, no real Stripe/billing wiring beyond a link to billing.weown.dev.

Copilot AI lite review requested due to automatic review settings August 8, 2026 16:33
@SinachPat
SinachPat requested a review from ncimino as a code owner August 8, 2026 16:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Rebuilds the WeOwnChat Landing/Purchase page as a static Astro + Tailwind CSS v4 site (replacing the prior Next.js approach), while preserving the existing content/design and codifying the WeOwnChat design tokens as a reusable .claude skill.

Changes:

  • Adds a new landing-purchase/ Astro project (static output) implementing the landing/purchase page with minimal vanilla-JS enhancements.
  • Implements progressive-enhancement scroll-reveal (IntersectionObserver + timeout fallback) and a demo “Live preview” iframe backed by seeded/mock data.
  • Updates repo .gitignore rules (macOS Icon\r match + track .claude/skills/) and indexes the new project in /CHANGELOG.md.

Reviewed changes

Copilot reviewed 37 out of 39 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
landing-purchase/tsconfig.json Adds Astro strict TS config for the new project.
landing-purchase/src/styles/global.css Defines Tailwind v4 theme tokens and global styles (incl. reveal animations + reduced-motion handling).
landing-purchase/src/scripts/reveal.ts Adds the page-wide scroll-reveal implementation with timeout fallback.
landing-purchase/src/pages/index.astro Composes the landing page from Astro components.
landing-purchase/src/layouts/Layout.astro Provides the base HTML layout and includes global CSS + reveal script.
landing-purchase/src/components/Container.astro Shared layout container primitive.
landing-purchase/src/components/Button.astro Shared button/link component with variants and external-link handling.
landing-purchase/src/components/BrandMark.astro Implements the WeOwn “W” brand mark for nav/footer.
landing-purchase/src/components/Nav.astro Sticky navigation with mobile menu toggle behavior.
landing-purchase/src/components/Hero.astro Hero section with CTA and animated chat mock embed.
landing-purchase/src/components/AnimatedChatMock.astro Vanilla-JS animated chat loop mock with reduced-motion behavior.
landing-purchase/src/components/TrustLabelStrip.astro Renders the “trust-label” strip used near CTAs.
landing-purchase/src/components/ProductShowcase.astro “Live preview” section embedding the demo dashboard iframe + tab toggle UI.
landing-purchase/src/components/HowItWorks.astro “How it works” steps section.
landing-purchase/src/components/TwoTabExplainer.astro Private/Public explainer section with iconography.
landing-purchase/src/components/ValuePillars.astro Value pillars grid with icon components.
landing-purchase/src/components/Pricing.astro Pricing section (intentionally no hard price) + CTAs.
landing-purchase/src/components/FAQ.astro FAQ accordion using native <details>/<summary> with exclusive-open behavior.
landing-purchase/src/components/Footer.astro Footer content, links, and repeated trust-label strip.
landing-purchase/src/components/icons/IconChat.astro Adds inline SVG icon component.
landing-purchase/src/components/icons/IconCheck.astro Adds inline SVG icon component.
landing-purchase/src/components/icons/IconDocument.astro Adds inline SVG icon component.
landing-purchase/src/components/icons/IconGlobe.astro Adds inline SVG icon component.
landing-purchase/src/components/icons/IconKey.astro Adds inline SVG icon component.
landing-purchase/src/components/icons/IconServer.astro Adds inline SVG icon component.
landing-purchase/src/components/icons/IconShield.astro Adds inline SVG icon component.
landing-purchase/src/components/icons/IconUpload.astro Adds inline SVG icon component.
landing-purchase/src/components/icons/IconUsers.astro Adds inline SVG icon component.
landing-purchase/public/_demo/dashboard-preview.html Adds demo dashboard HTML with fetch mocking to seed preview content.
landing-purchase/package.json Adds Astro/Tailwind/TS dependencies and scripts.
landing-purchase/astro.config.mjs Configures Astro for static output + Tailwind Vite plugin.
landing-purchase/README.md Documents purpose, local run instructions, and design-token provenance.
landing-purchase/CHANGELOG.md Adds project-specific changelog for the landing/purchase project.
landing-purchase/.gitignore Adds local ignores for the new Astro project.
CHANGELOG.md Indexes the new project changelog and documents the new landing/purchase project.
.gitignore Fixes Icon ignore rule and adjusts .claude ignore to track .claude/skills/.
.claude/skills/weownchat-design/SKILL.md Adds the WeOwnChat design-system skill and its rules/provenance.
.claude/skills/weownchat-design/references/tokens.md Adds the copied design tokens reference (dashboard + Keycloak sources).
Suppressed comments (1)

landing-purchase/src/components/ProductShowcase.astro:77

  • Setting iframe.contentWindow.location.hash can be lost if a user clicks the tab before the iframe has loaded (the hash is applied to about:blank and then overwritten when src loads). Persist the selected tab and apply it on iframe load, and/or update iframe.src with the hash before load.
    if (iframe.contentWindow) {
      iframe.contentWindow.location.hash = `#${tab}`;
    }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +23 to +26
<body class="antialiased">
<slot />
<script src="../scripts/reveal.ts"></script>
</body>
Comment on lines +42 to +47
<svg viewBox="0 0 24 24" class="h-3 w-3 text-ok" fill="none" stroke="currentColor" stroke-width="2">
<rect x="5" y="11" width="14" height="9" rx="2"></rect>
<path d="M8 11V8a4 4 0 0 1 8 0v3"></path>
</svg>
harborviewcpa.weown.dev/app
</div>
Comment on lines +604 to +606
"/api/embed-domains": { domains: ["harborviewcpa.example.com", "www.harborviewcpa.example.com"], always: [] },
"/api/snippet": { snippet: "<scr" + "ipt src=\"https://harborview.weown.dev/embed/widget.min.js\" data-tenant=\"harborview\"></scr" + "ipt>" },
"/api/threads": { threads: [
…n skill

Rebuild of the WeOwnChat Landing/Purchase page (PRD roadmap item 2) on
Astro + Tailwind CSS v4 (static output), replacing an earlier Next.js
version per direct direction to match the framework used elsewhere in the
WeOwn ecosystem outside this repo — a repo-wide search here found no
existing Astro usage to match conventions against, so this project's setup
choices are its own. `npm run build` (astro check && astro build) is clean;
0 npm audit findings.

Full page: nav (real working mobile menu, vanilla JS), hero (a live,
looping recreation of the real chat UI — vanilla-JS state machine, not a
static mockup), a "Live preview" section embedding the real, unmodified
dashboard HTML in an iframe with seeded fictional demo content
(public/_demo/dashboard-preview.html — see its own header comment for the
maintenance contract), how-it-works, the two-tab (private/public)
explainer, value pillars, pricing (no dollar figure — commercial terms
still draft/unsigned per the PRD), FAQ (native <details name="faq-
accordion">, zero JS, same collapsible pattern the real dashboard's own
.doc-strip already uses), footer.

Design tokens copied verbatim from the real product (dashboard :root +
Keycloak weown login theme), not invented — codified as
.claude/skills/weownchat-design/ so future frontend work has a source of
truth to check against instead of drifting.

Scroll-reveal is hardened, not just ported: content is visible by default
and only opts into the hidden-then-reveal transition once the script runs,
with a 2.5s timeout that force-reveals anything IntersectionObserver never
fires for. Added after testing surfaced the observer sometimes not firing
at all for an element plainly in the viewport, in the specific automated-
browser environment used to test this — cause unconfirmed, fix holds
regardless.

Also fixes a real, pre-existing bug in the repo-root .gitignore: the
classic macOS "Icon?" rule (meant to exclude the special Icon+CR
folder-icon file) was found to silently swallow any file whose name starts
with "Icon" and is longer than 5 characters — it ate this project's own
icon components until caught. Replaced with an unambiguous bracket
negation, verified to still catch the intended file and nothing else.
Copilot AI review requested due to automatic review settings August 8, 2026 17:17
@SinachPat
SinachPat force-pushed the feature/patrick-landing-purchase-astro branch from 58dd61f to b5bbbc2 Compare August 8, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 39 changed files in this pull request and generated no new comments.

Suppressed comments (7)

landing-purchase/src/components/ProductShowcase.astro:27

  • The Public tab button is visually stateful, but it doesn’t expose state to assistive tech. Adding aria-pressed (or proper role="tab" semantics) makes the current selection discoverable to screen readers.
        <button type="button" id="showcase-tab-public" class="rounded-full px-4 py-2 text-sm font-semibold text-text-faint transition-colors hover:text-text-mut">

landing-purchase/src/layouts/Layout.astro:25

  • src/scripts/reveal.ts is being loaded via a plain <script src="../scripts/reveal.ts"> tag. Browsers can’t execute TypeScript syntax, and Astro/Vite won’t bundle the file this way, so this will 404 and/or throw a syntax error and the reveal behavior won’t run. Use Astro.resolve() (bundled) and load as a module instead.
    landing-purchase/src/components/ProductShowcase.astro:46
  • This demo URL uses a *.weown.dev hostname. In a public repo, demo/fixture content should use RFC 2606 example domains to avoid leaking real or potentially-internal topology (even in UI-only strings). Consider switching this to an example.com-style domain (and keep it consistent with the allowlist values in the demo).
            harborviewcpa.weown.dev/app

landing-purchase/public/_demo/dashboard-preview.html:605

  • This demo embed snippet hardcodes a *.weown.dev domain. Since this repository is public, demo/fixture URLs should use RFC 2606 example domains (e.g., example.com) unless the intent is explicitly to publish a production endpoint.
    "/api/snippet": { snippet: "<scr" + "ipt src=\"https://harborview.weown.dev/embed/widget.min.js\" data-tenant=\"harborview\"></scr" + "ipt>" },

landing-purchase/src/components/ProductShowcase.astro:24

  • The Private tab button is visually stateful, but it doesn’t expose state to assistive tech. Adding aria-pressed (or proper role="tab" semantics) makes the current selection discoverable to screen readers.

This issue also appears on line 27 of the same file.

        <button type="button" id="showcase-tab-private" class="rounded-full bg-ok/[0.16] px-4 py-2 text-sm font-semibold text-ok transition-colors">

landing-purchase/src/components/ProductShowcase.astro:78

  • setTab() updates styles but doesn’t update any ARIA state, so assistive tech can’t tell which tab is active after interaction. If you add aria-pressed in the markup, also toggle it here when switching tabs.
  function setTab(tab: "private" | "public") {
    const isPrivate = tab === "private";
    privateTab.className = `rounded-full px-4 py-2 text-sm font-semibold transition-colors ${
      isPrivate ? "bg-ok/[0.16] text-ok" : "text-text-faint hover:text-text-mut"
    }`;

landing-purchase/src/components/Button.astro:31

  • href.startsWith("http") will treat any string beginning with http as external, and will not mark other external schemes (e.g. mailto:) as external. Restrict this to http:///https:// to avoid misclassification.
const isExternal = href.startsWith("http");

Copilot AI review requested due to automatic review settings August 9, 2026 00:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 39 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

landing-purchase/src/components/ProductShowcase.astro:78

  • The Private/Public toggle updates styles but doesn’t expose the selected state to assistive tech. Adding/updating aria-pressed (or tab semantics) makes the toggle state discoverable for screen readers and other AT.
  const iframe = document.getElementById("showcase-iframe") as HTMLIFrameElement;
  const privateTab = document.getElementById("showcase-tab-private")!;
  const publicTab = document.getElementById("showcase-tab-public")!;

landing-purchase/src/components/ProductShowcase.astro:46

  • This demo UI hardcodes a *.weown.dev hostname (harborviewcpa.weown.dev) even though the rest of the demo data already uses example.com. For a public repo (and to avoid implying a real tenant URL pattern), prefer RFC 2606 example domains for all demo hostnames (see §3.0 public-repo precautions).
            harborviewcpa.weown.dev/app

landing-purchase/public/_demo/dashboard-preview.html:605

  • The demo snippet includes a hardcoded harborview.weown.dev script URL. Since this is demo content in a public repo, use an RFC 2606 example domain so it can’t be mistaken for a real endpoint or internal topology (see §3.0 public-repo precautions).
    "/api/snippet": { snippet: "<scr" + "ipt src=\"https://harborview.weown.dev/embed/widget.min.js\" data-tenant=\"harborview\"></scr" + "ipt>" },

</head>
<body class="antialiased">
<slot />
<script src="../scripts/reveal.ts"></script>
Copilot AI review requested due to automatic review settings August 9, 2026 00:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 39 changed files in this pull request and generated no new comments.

Suppressed comments (3)

landing-purchase/src/components/ProductShowcase.astro:46

  • This uses a *.weown.dev hostname in a demo UI. Since this repo is public, it’s safer to use an RFC 2606 example domain for fictional/seeded content to avoid accidentally referencing a real internal/customer subdomain.
            harborviewcpa.weown.dev/app

landing-purchase/public/_demo/dashboard-preview.html:605

  • The demo snippet text includes https://harborview.weown.dev/.... In a public repo, prefer RFC 2606 example domains for seeded/demo hostnames to avoid leaking or implying real internal/customer infrastructure naming.
    "/api/snippet": { snippet: "<scr" + "ipt src=\"https://harborview.weown.dev/embed/widget.min.js\" data-tenant=\"harborview\"></scr" + "ipt>" },

landing-purchase/src/styles/global.css:134

  • The reduced-motion fallback doesn’t override the more-specific [data-reveal].reveal-armed rule, so users with prefers-reduced-motion: reduce can still have reveal-armed sections start at opacity: 0 until JS marks them revealed (or the timeout fires). Override the reveal-armed selector inside the media query (and ideally disable transitions) so reduced-motion users never see hidden content.

Copilot AI review requested due to automatic review settings August 9, 2026 00:51
@ncimino
ncimino merged commit 27a4ba4 into WeOwnNetwork:main Aug 9, 2026
9 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 39 changed files in this pull request and generated no new comments.

Suppressed comments (4)

landing-purchase/src/layouts/Layout.astro:25

  • The page loads a TypeScript file via a plain <script src="../scripts/reveal.ts">, which will try to fetch ../scripts/reveal.ts at runtime (not emitted in dist/) and won’t be treated as an ES module. This will likely break scroll-reveal in the static build.
    landing-purchase/src/components/ProductShowcase.astro:46
  • This visible URL uses a real-looking *.weown.dev hostname. Because this repo is public, demo/placeholder domains should use RFC 2606 example domains to avoid leaking internal topology or implying a real tenant host.
            harborviewcpa.weown.dev/app

landing-purchase/public/_demo/dashboard-preview.html:605

  • The demo embed snippet hardcodes https://harborview.weown.dev/.... Since this repo is public, avoid non-example weown.dev demo hostnames in committed fixtures; use an RFC 2606 example domain for the placeholder snippet URL.
    "/api/snippet": { snippet: "<scr" + "ipt src=\"https://harborview.weown.dev/embed/widget.min.js\" data-tenant=\"harborview\"></scr" + "ipt>" },

landing-purchase/src/components/ProductShowcase.astro:78

  • bg-warn/[0.16] only appears inside this JS template string, so Tailwind may not generate the CSS for it (result: the Public tab can lose its active styling in the built output). Also, the buttons don’t expose their selected state to assistive tech. Consider using an existing opacity already present in templates and set aria-pressed on toggle.
  function setTab(tab: "private" | "public") {
    const isPrivate = tab === "private";
    privateTab.className = `rounded-full px-4 py-2 text-sm font-semibold transition-colors ${
      isPrivate ? "bg-ok/[0.16] text-ok" : "text-text-faint hover:text-text-mut"
    }`;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants