Skip to content

Port the site from Webflow to Astro; deploy to Firebase Hosting - #411

Merged
eseidel merged 53 commits into
mainfrom
webflow-port
Sep 17, 2026
Merged

eseidel merged 53 commits into
mainfrom
webflow-port

Conversation

@eseidel

@eseidel eseidel commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Ports shorebird.dev off Webflow into this repo (Astro 7 + Tailwind v4) and replaces the GitHub Pages deploy with Firebase Hosting in a dedicated GCP project.

  • PORT_REPORT.md — page-by-page status and accepted differences from Webflow
  • CUTOVER.md — burn-down for leaving Webflow: redirects export, DNS, one-time GCP/WIF setup, cutover day
  • .github/workflows/main.yaml — PR gate, preview channel per PR, deploy on main

Draft until the shorebird-website GCP project, Hosting site, deploy service account and WIF binding exist (CUTOVER.md §2); the preview job will fail at auth until then. Merging deploys nothing user-visible: DNS still points at Webflow until §4.

Raw reference snapshot of the live Webflow site so the port can run
offline. See webflow-export/README.md for layout and the theme model.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
…iner

- global.css: encode the live Webflow variables as CSS custom properties
  (dark by default on :root, light via [data-theme="light"] / .is--mode_1,
  dark re-assert via [data-theme="dark"] / .is--mode_0), responsive type
  and spacing scale, General Sans (500/600 woff2 from the live site), and
  Tailwind v4 @theme tokens (bg-surface-*, text-text-*, border-border,
  brand-*, button-*, text-title-*/body-*, py-section-*, px-container).
  Legacy token names used by pre-port pages keep working as aliases.
- @Utility page-container / page-container-lg (Webflow g_page--container),
  title-*/body-*/label-* text styles, badge-icon, divider.
- New Section.astro (theme/variant/spacing/container props) and
  Container.astro for Phase 2 pages to compose from.
- lib/utils.ts: teach tailwind-merge about the custom font-size tokens so
  `text-title-l text-text-1` keeps both classes.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Ports g_button_primary (gradient pill with hover overlay), g_button_secondary
(1.5px inset-border pill) and a tertiary text-link variant, with `default`
and `small` sizes. Adds ButtonLink (renders a real <a>), ButtonIndicator
(the "View our Demo" live dot) and a `trailing="dot"|"arrow"` prop. Old
shadcn variants/sizes (default, outline, ghost, link, sm, lg, icon) and
GradientOutlineButton still work as aliases.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Sticky 80px (72px mobile) nav with the live link set (Products dropdown with
Code Push / Shorebird CI, Docs, Pricing, Customers, Blog, About), Sign in
and Try for Free CTAs, and the mobile hamburger -> sheet menu. The nav
follows the theme of the section under its bottom edge via
elementsFromPoint + closest('[data-theme]') on scroll (no GSAP).

Logos now use currentColor so they render on both themes; LogoFull is the
exact 123x24 SVG from the live site. sheet.tsx gains opt-in
`overlayClassName` and `hideClose` props for the mobile menu.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Footer: brand column (logo, "Deploy on your schedule", LinkedIn/GitHub/
Discord/RSS circles) plus Product / Resources / Company / Legal columns with
the live links; stacks and centers on mobile. The "Start shipping fast,
today!" band is a separate page section on the live site, so it ships as
cta-band.astro for pages to include above the footer.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
main.astro now loads the live Plausible script (pa-KkCTgCak2h0t6JBTj3Tgm)
and init snippet plus the LinkedIn Insight tag (partner 8654276), preloads
the General Sans woff2 files, applies the dark surface/text tokens on
<body>, and accepts optional `description` and `image` props for the meta
and OG/Twitter tags.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
scripts/import_webflow.py (stdlib only) converts webflow-export/cms/*.json
into content collections, downloads referenced images with curl, and is
idempotent. Blog schema gains intro/readingTime/ogImage/seoTitle/
seoDescription/highlight; success stories gain industry/companySize/
website/intro/seoTitle/seoDescription and highlights becomes optional.
New blog authors (Wesley Peck, Abhishek Doshi, Nick Weatherley,
Mac Carrithers, Brandon DeRosier) with headshots; existing job titles
updated to match the CMS.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
69 posts: 39 keep their hand-written Markdown bodies with frontmatter
regenerated from the CMS; 30 new posts converted from CMS HTML. Five
repo slugs renamed to the Webflow slugs (redirects added in
astro.config.mjs). Body images downloaded to src/assets/blog/<slug>/,
covers to src/assets/blog/covers/, share images to public/blog/og/.

The CMS API exports rich-text code blocks as empty <pre></pre>; the four
affected new posts get their code from the rendered live pages saved in
webflow-export/pages/blog__<slug>.html.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Success stories: 8 keep their bodies with frontmatter extended
(industry, companySize, website, intro, seoTitle, seoDescription); scapia
and vetc are new, with highlights derived from the story text.

src/data/{reviews,logos,team}.json for the homepage and about page, with
avatars, brand logos (SVG) and team headshots downloaded into src/assets.
Asset paths are /src/assets/... strings for use with import.meta.glob.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Rebuild `src/pages/index.astro` and `src/components/home/*` to match the
live shorebird.dev homepage section for section: dark hero with the "home"
Lottie (extracted from the exported .lottie bundle, bitmap inlined) and the
"Trusted by" marquee from logos.json; the "opinionated platform" card with
Code Push / Shorebird CI columns; Benefits; the dark Enterprise-scale card;
Secure and Compliant; How Shorebird can help; a CSS scroll-snap testimonials
row fed by reviews.json; Resources cards on the gradient band; the FAQ
accordion as native <details>; then the shared CTA band and footer.

Copy is verbatim from webflow-export/pages/home.html; images are copied into
src/assets/home. Old home components without a live counterpart (problem,
solution, for-developers, for-teams, pricing-overview, faq-overview,
blog-overview, success-stories-overview, callout, features) are removed.
logos.json drops the extra hand-added PushPress entry so the marquee shows
the 7 CMS logos like the live site (the CMS "Apnaklub" asset is the
PushPress mark).

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Adds /product/code-push built from the homepage patterns plus new
components under src/components/product/ (hero with the Code Push Lottie,
six-card benefits + PushPress quote, the dark "How it works" Arcade demo
card, the four-card overview grid, success-story cards, and the yearly
pricing overview). Reuses the homepage testimonials, resources and FAQ
sections; the FAQ component now takes an optional `faqs` prop.

The Lottie JSON is the Webflow export with its 2048px bitmap mask
downscaled to 512px (2.9MB -> 336KB).

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
/product/shorebird-ci is now just the sunset notice in the hero slot,
followed directly by the footer (no CTA band), matching the live page.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Adds /code-push-guide: title, intro rich text, the ebook cover and a
React download form that posts to the Loops endpoint the Webflow form
used (source=ebook-CP-guide, userGroup=web-download) and then shows the
"Download the guide" link. The PDF is served from public/guides/ and the
1200x630 ebook image is the page's OG image.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
The content import kept the repo's original cover PNGs for posts that
already existed locally, but the live site shows the redesigned (purple)
covers from the CMS `featured-image` field for every post. Download those
for the 38 posts that were still pointing at the old artwork, point their
`cover:` frontmatter at the new files, and drop the orphaned PNGs.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Light page matching the live /blog: "Blog" hero, the highlighted article
(`highlight: true`, newest wins) on the left with the three most recent
posts as a compact list on the right (a horizontal scroller below 992px,
like the live `.c_blog_latest--cms_list`), then the "All articles"
three-column grid of every post newest first (the live page renders all
69 with no pagination), the CTA band and the footer. Card copy is the
post description; same-day posts tie-break the way the live site orders
them. Page title/description come from the live meta tags.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
`src/layouts/blog.astro` now composes `MainLayout` (so it picks up the
shared head/analytics) and matches the live template: light section with
the title, author headshot/name/title, date and the CMS `readingTime` in
an 8-column article, plus a sticky 4-column aside with "In this article"
(`intro`), LinkedIn / X / copy-link share buttons and the "Subscribe to
our newsletter" card, then the CTA band and footer. `seoTitle` /
`seoDescription` / `ogImage` feed the meta tags with fallbacks to the
title / description / `/blog/open-graph.png`. Reading time is no longer
computed from the word count.

Article prose is `@tailwindcss/typography`'s `prose` plus a new
`src/styles/rich-text.css` (`.prose.rich-text`) that overrides headings,
links, lists, blockquotes, inline code, tables, images, iframes and Shiki
code blocks (`#2b2b2b` like Webflow's `.w-code-block`) with the theme
tokens; embeds keep 16:9 and code scrolls horizontally on mobile.

Also: `NewsletterSignupForm` gains a `layout="stacked"` variant (full
width field + primary button) used by the new `newsletter-card.astro`;
`BlogCallout` is restyled with tokens for the layouts that still use it;
the RSS builder moves to `src/lib/blog-feed.ts`, uses the post intro and
is served at both `/rss.xml` and `/blog/rss.xml` (the live footer link).

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Dark "Customers Stories" hero, the 3-up grid of every story newest first
(covers at their natural aspect ratio, name, summary), then the CTA band
and footer, matching the live /success-stories page.

Extract the story card into src/components/success-stories/story-card.astro
and a cover/sort helper in src/lib/success-stories.ts; the product pages'
"Customers stories" grid now uses the same card.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Dark page: publish date, customer name, one-line summary and the cover
above the story rich text (8 columns), with a sticky aside (4 columns)
holding the "In this story" summary, the Industry / Company size /
Website facts and the newsletter card, then the CTA band and footer.

SEO title/description come from the CMS fields with fallbacks; the OG
image is the story cover. The old "Highlights" block is gone from the
layout (the live template has none) but stays in the schema. Also fix a
stray bold marker in the imported Scapia story.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Header with the monthly/yearly toggle, the four plan cards, the trusted-by
marquee and the plan comparison table (single-plan dropdown on mobile),
then the CTA band. The live page has no calculator or FAQ, so those
components are removed.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Hero with the Shorebird/Flutter logos, the "Founded in 2023" intro, the
dark mission card, the team grid from team.json (drafts hidden, order
matching the live CMS), the shared Resources section and the CTA band.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
The live page is the Gem job board in an 800px iframe under the hero
spacer (the board renders the logo, intro and open positions itself), so
embed `config.jobBoardUrl` the same way. Note the board's CSP only allows
https ancestors, so the frame is blank on http://localhost but renders on
the deployed site.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
- `/legal/dpa` moves to `/dpa` (redirect kept); the page links the newer
  DPA PDF from the live site (the 2024 template stays in public/legal so
  old links keep working).
- `/terms/ci` and `/terms/code-push` are gone on the live site: redirect
  them to `/terms`, which now links the PDF terms like the live page. The
  Markdown sources move to `src/legal/` so `/terms/raw` keeps serving them.
- `/privacy` copy matches the live text; headings become h2 like the live
  rich text and the H1/"last updated" line move to the hero.
- `markdown.astro` composes the shared layout: dark hero with the title
  (and optional `subtitle`), rich text in the middle 8 columns, CTA band.
- rich-text.css: nested lists use circle bullets like the live rich text.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Not a page on the live Webflow site, but the blog newsletter form links
it (config.newsletterSubscriptionUrl); give it the legal-page hero and the
stacked newsletter form in a card.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Stdlib-only crawler over the built site: reports any same-site href/src/
srcset/og:image that does not resolve to a file in dist/. Exposed as
`npm run check:links` (run after `npm run build`).

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
The wordmark it references is served locally (public/shorebird-logo-wordmark.png).

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
The old dark cover had the same filename, so the cover swap skipped it.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
The reference export (CMS JSON, rendered pages, CSS, assets, screenshots)
now lives in ../webflow-migration/ — 65MB of read-only material that
should never ship with the site. scripts/import_webflow.py reads it from
there (WEBFLOW_EXPORT overrides). The 69 per-post og:image files are
JPEG q85 (6MB, was 42MB as PNG); JPEG on purpose because social crawlers
don't reliably read WebP/AVIF for share images.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Same copy, bird, and layout as the live utility page. On phones the live
page's float overflows the viewport; ours stacks instead.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
Webflow's 301 rules are not in any export we have and must be pulled
by hand or via the v2 redirects API before DNS moves. Also records the
trailing-slash decision (keep Astro's default) and the Cloudflare
hosting assumptions.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
…robots.txt

Verified via the Webflow API: 15 forms all posting to the same Loops
endpoint our forms use, no email notifications, no webhooks, no
registered scripts; site custom code is Plausible + Unify (head) and
LinkedIn + HubSpot (footer). Live robots.txt only names the sitemap, so
add one pointing at Astro's sitemap-index.xml.

Claude-Session: https://claude.ai/code/session_012ffrD4Bm1fgzhYuSmUeAKz
team.json carried every Webflow "Teams" CMS entry including drafts that
the live site never rendered. Keep only the published members, remove the
draft flag and the unused headshots.
Own GCP project (shorebird-website) so the site's IAM is separate from
production; apex via A records in the existing Cloud DNS zone, so nothing
else on shorebird.dev moves. Cloudflare was considered and rejected: it
needs the whole zone, and its one real advantage (free egress) is
irrelevant at this site's traffic.

- firebase.json / .firebaserc; trailingSlash, immutable cache on /_astro
- src/integrations/firebase.ts copies astro.config.mjs redirects into
  firebase.json at build so they are real 301s; CI fails on drift
- workflow: PR gate + preview channel per PR + deploy on main, WIF auth
  through the org's existing github-actions pool
- drop public/CNAME (GitHub Pages)
- CUTOVER.md / README updated
…tier

The GCP project, Hosting site, deploy service account and a repo-scoped
WIF pool now exist (CUTOVER.md §2). The org pool in code-push-dev is
Tofu-managed and holds production repos, so the website gets its own.

The integration formats firebase.json through Prettier so the build's
regeneration and the format check agree.
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

🐦 Preview of f134350: https://shorebird-website--pr-411-8lydbxt5.web.app (expires in 7 days)

eseidel and others added 4 commits September 15, 2026 15:46
- Drop the /workshops redirect (pointed at a Calendly for workshops we no
  longer run; already a 404 on live) and note that in the announcement post
- Remove /demo (meta-refresh to a personal Calendly), /contact and
  /newsletter-signup: pre-Webflow pages nothing linked to
- Remove the newsletter entirely (footer block on / and the guide page, blog
  and success-story sidebar card, form component, config URL) — it was taken
  off the live site on 2026-09-15. The Code Push guide form stays.
- Fix dead links in posts: updater/issues/new (issues disabled there) →
  shorebird/issues; the RN-vs-Flutter post's /contact → /talk-to-sales
- PORT_REPORT / CUTOVER updated to match
- Mobile menu drawer: drop the 80px top padding above the divider and
  tighten item spacing (gap-8 -> gap-4, no extra gap under Products);
  measured against live at 390px, every element now within 1px
- Navbar no longer swaps light/dark as sections scroll under it. Live
  fixes the theme per page (light on blog pages, dark elsewhere), which
  the existing initialTheme prop already expresses
- /product/shorebird-ci: live's notice is past tense now ("was
  deprecated on August 8, 2026"); match it
Brings in the dependency bumps and CI fixes from #413. Conflict
resolutions:

- package.json: the port's dependency set (without @base-ui-components,
  @radix-ui/react-accordion, lucide-react and react-markdown, which it
  removed) at main's versions, including astro 7. package-lock.json
  regenerated from scratch; 0 audit vulnerabilities.
- .github/workflows/main.yaml: keeps the Firebase build/preview/deploy
  jobs, takes main's actions@v7 and Node 24.x, and moves the deploy job's
  checkout to v7 with the rest.
- README.md / .cspell.yaml: the port's versions, which already drop the
  archival notice and list `webflow`. README now says Astro 7.
- src/pages/jobs.astro: the port's iframe embed replaces the markup main
  fixed for the astro 7 compiler, so that fix is moot here.
- src/content/config.ts moves to src/content.config.ts (astro 7 dropped
  legacy content collections); the port's added schema fields carry over.
- One blog post reformatted by prettier 3.9.6, which wraps `$1,000-2,000`
  style text differently.

Verified: npm ci, allow-scripts, format:check, cspell, build, check:links
and the firebase.json drift check all pass. Built output compared against
the pre-merge branch: all 107 pages identical except smartypants
punctuation on 7 blog pages (`--` now an en dash, one `'ol` apostrophe
now curling the other way).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLGw8ogSPAXVnhTv2fbdee
The lockfile from the merge commit only listed linux optional packages,
so `npm ci` on the macOS runner reported every darwin binary as missing
from the lock file and refused to install.

npm resolves the ideal tree from an existing node_modules when one is
present, and then records only the optional binaries installable on the
current platform. Regenerating with node_modules removed restores all 18
darwin and 18 win32 entries alongside the linux ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLGw8ogSPAXVnhTv2fbdee
@eseidel
eseidel requested a review from dawn-ducky September 16, 2026 20:31
@eseidel
eseidel marked this pull request as ready for review September 16, 2026 20:31
@eseidel

eseidel commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Comments, corrections welcome @easymac

@eseidel
eseidel enabled auto-merge (squash) September 16, 2026 20:33

@easymac easymac left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dug through the code and left some nits, mostly performance related and a couple of things to verify.

If we have the hero Lottie animation working, I think I'm satisfied saying this is plenty good enough to ship.

Comment thread firebase.json Outdated
Comment thread src/layouts/main.astro
Comment thread src/pages/terms/raw.ts
Comment thread src/components/product/code-push-motion.tsx Outdated
Comment thread firebase.json
Comment thread src/components/product/guide-form.tsx
Comment thread src/layouts/blog.astro
Comment thread src/components/ui/navbar.tsx Outdated
- `.well-known/microsoft-identity-association` was silently dropped from the
  deploy twice over: `firebase.json`'s `"**/.*"` ignore excluded it, and
  `actions/upload-artifact` skips hidden files by default. Un-ignore it, set
  `include-hidden-files: true`, and give it an explicit `Content-Type` (it has
  no extension, so Hosting would serve it as octet-stream). CUTOVER has a check
  to confirm it on the preview channel, and the alternative if nobody needs the
  Entra app any more.
- Cache `/fonts/**` for a year as `immutable` instead of the default hour.
  They are preloaded on every page and the filenames are stable; README notes
  that a re-exported face has to be renamed rather than overwritten.
- `/terms/raw` and `/privacy/raw` set `content-type: application/json` on a
  `Response` that a static build throws away. Rename both to `raw.json.ts` so
  the extension carries the type, and 301 the old paths there.
- Add LinkedIn to the vendor list in the privacy policy; the Insight tag ships
  on every page but only Plausible and Posthog were listed.
- Drop `role="menu"`/`role="menuitem"` from the Products dropdown: they promise
  arrow-key roving focus and typeahead we do not implement. It is a `<ul>` of
  links behind a button with `aria-expanded`, so Tab walks it like the rest of
  the nav.
- Require first name and make last name optional in the guide form (Webflow had
  it the other way round, which is the wrong half to require).
- Halve the Code Push hero Lottie's bitmap, 144kB -> 72kB, with a byte-identical
  render: every visible pixel is white and the artwork is entirely in the alpha
  channel, but the export left undefined RGB noise under the transparent
  pixels, which is incompressible. It is a halftone world map, not the soft
  ellipse the comment claimed, so it cannot become a CSS gradient and must not
  be downscaled further.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJaeks4xiq6ZvgZwjcQ2pf

eseidel commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Worked through @easymac's review in f134350. Details are on each thread; summary:

Fixed

  • .well-known was dropped three ways, not one: the ignore glob, upload-artifact skipping hidden files, and — even once deployed — no extension, so Hosting would have served it as application/octet-stream when Entra wants application/json. All three fixed, plus a CUTOVER check to confirm it on the preview channel.
  • /fonts/** cached for a year as immutable (was the 1-hour default). README notes the filenames aren't fingerprinted, so a re-exported face must be renamed, not overwritten.
  • /terms/raw/terms/raw.json with a 301, so the content-type a static build was discarding now comes from the extension. /privacy/raw had the identical bug and got the same treatment.
  • LinkedIn added to the privacy policy's vendor list.
  • role="menu"/role="menuitem" dropped from the Products dropdown in favour of a <ul> of links behind an aria-expanded button.
  • Guide form requires first name, last name now optional (Webflow had it backwards).
  • Code Push hero Lottie: 336 kB → 240 kB (167 kB → 94 kB gzipped), pixel-identical. The bitmap isn't the soft ellipse the comment claimed — it's the halftone world map, entirely alpha-channel, with incompressible RGB noise under the transparent pixels. Flattening that is free. Comment corrected.

Not fixed, deliberately

  • React on blog pages. Real number is ~99 kB gzip (311 kB uncompressed), not 56 kB — the navbar chunk is the smaller half. Worth doing, but removing React from blog pages means also rewriting the mobile sheet off Radix, i.e. changing the nav on every page of the site in an already-approved 483-file PR days before DNS moves. Proposed as the first post-cutover follow-up.

Two things that need a human decision, both raised on their threads:

  • The privacy policy's "We do not use any cookies for advertising, analytics, or tracking purposes" is hard to square with the LinkedIn Insight tag. Pre-existing — the tag is live on Webflow today — but somebody should decide the wording.
  • CUTOVER.md assumes the console reads /privacy/raw and /terms/raw; @easymac thinks nothing does. The redirects mean it's safe either way, but if nobody reads them, both endpoints should just be deleted.

format:check, cspell, check:links and the firebase.json redirect check are green locally. The navbar keyboard behaviour, the form validation, and the Lottie frame-by-frame comparison were verified in headless Chromium rather than assumed.


Generated by Claude Code

@eseidel
eseidel merged commit a951f81 into main Sep 17, 2026
3 checks passed
@eseidel
eseidel deleted the webflow-port branch September 17, 2026 05:38
eseidel pushed a commit that referenced this pull request Sep 17, 2026
Captures @eseidel's note on #411 that the terms need another pass with
our lawyers, so it lives in the burn-down rather than in a review thread
on a merged PR.

Two known gaps, both pre-existing on the live Webflow site rather than
introduced by the port: the Cookies section's claim that we use no
advertising/analytics/tracking cookies sits badly next to the LinkedIn
Insight tag, and the vendor list needs to match whatever we end up
shipping.

Also notes that the existing "Decide on HubSpot chat" item gates this
one. The Astro site does not load HubSpot at all (deliberately, see
PORT_REPORT.md §8), so listing it as a vendor before deciding to port it
back would be wrong in the other direction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJaeks4xiq6ZvgZwjcQ2pf
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