Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ jobs:
- name: Lint app
run: pnpm --filter @reineira-os/modules-app run lint

- name: Lint landing
run: pnpm --filter @reineira-os/modules-landing run lint

build:
name: Build
runs-on: ubuntu-latest
Expand Down
8 changes: 8 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules/
dist/
.next/
out/
coverage/
pnpm-lock.yaml
CHANGELOG.md
**/next-env.d.ts
34 changes: 34 additions & 0 deletions packages/landing/.commitlintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'build',
'ci',
'chore',
'revert',
'Feature', // Allow "Feature" as a type
'feature', // Allow "feature" as a type
'Bugfix',
'bugfix',
'Hotfix',
'hotfix',
'Release',
'release',
],
],
'subject-case': [0], // Disable subject case checking
'subject-empty': [0], // Disable subject empty checking
'type-empty': [0], // Disable type empty checking
'header-max-length': [0], // Disable header max length
},
}
5 changes: 5 additions & 0 deletions packages/landing/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Waitlist submission URL (Google Apps Script)
NEXT_PUBLIC_WAITLIST_URL=https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec

# Fathom Analytics site ID
NEXT_PUBLIC_FATHOM_SITE_ID=YOUR_SITE_ID
42 changes: 42 additions & 0 deletions packages/landing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.next
out
.DS_Store
dist
dist-ssr
coverage
*.local
.eslintcache
.vite_cache

# TypeScript incremental build cache
*.tsbuildinfo

/cypress/videos/
/cypress/screenshots/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Sentry Auth Token
.eslintcache
.env.sentry-build-plugin

# Performance testing
.lighthouseci
10 changes: 10 additions & 0 deletions packages/landing/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"useTabs": false,
"printWidth": 120,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "ignore"
}
40 changes: 40 additions & 0 deletions packages/landing/.stylelintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module.exports = {
plugins: ['stylelint-scss'],
extends: ['stylelint-config-recommended-scss', 'stylelint-config-recommended-vue/scss'],
rules: {
'no-duplicate-selectors': null,
indentation: null,
'no-descending-specificity': null,
'at-rule-empty-line-before': null,
'custom-property-empty-line-before': null,
'declaration-empty-line-before': null,
'max-nesting-depth': null,
'value-keyword-case': null,
'color-hex-length': ['long'],
'color-hex-case': 'lower',
'color-named': 'never',
'declaration-block-single-line-max-declarations': [0],
'selector-max-compound-selectors': 6,
'selector-class-pattern': null,
'media-feature-name-no-unknown': null,
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep'],
},
],
'scss/dollar-variable-colon-space-after': ['always-single-line'],
'scss/at-function-pattern': null,
'scss/at-mixin-pattern': null,
'scss/dollar-variable-pattern': null,
'scss/percent-placeholder-pattern': null,
'scss/at-mixin-argumentless-call-parentheses': 'never',
'scss/no-duplicate-dollar-variables': true,
'property-no-unknown': [
true,
{
ignoreProperties: ['field-sizing'],
},
],
},
}
83 changes: 83 additions & 0 deletions packages/landing/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Landing Template — Conventions

> Generated project conventions. Copied into `<venture>/packages/landing/` on `/scaffold-landing`.

## Page structure is canonical — never change it

This template is a **1:1 clone** of the reference landing (`web-landing-app`). The page set,
component sequence, block roles, markup, and animations are **fixed across every venture**.

**What varies per venture:**

- Token system (`branding.accent` → 9-step scale + hover/bg/border variants; `branding.fontSans`
/ `fontMono`; `branding.borderRadius`; `branding.borderWidth`).
- Text content (every string in every slot of `src/content/site.ts`).
- Image URLs (replace `src: null` → real asset, or keep `null` for `[IMAGE PLACEHOLDER]`).

**What never varies:**

- Set of pages (`/`, `/mobile`, `/business`, `/pricing`, `/blog`, `/contact`, `/privacy`,
`/terms`).
- Order of components on each page.
- Role/goal/markup/animation of each component.
- Component internals (don't edit files in `src/components/*` — edit `site.ts`).

**Missing content** → `lorem(kind)` helper for text, `ImagePlaceholder` for images.
**Excess content in brief** → silently ignore. Data overload hurts conversion.

## Token system

`site.ts` `branding.accent` (hex) →
`src/lib/accent-scale.ts` `accentCssOverrides(hex)` →
`src/app/layout.tsx` injects `<style>` with `--accent*` + `--accent-teal*` (back-compat) →
components consume via `var(--accent)` / `bg-accent-teal` class / etc.

Change accent in one place (`site.branding.accent`). Never edit `globals.css` just to recolor.

## Server / client boundary rules

1. **Context.Provider only from client files.** Prerender fails otherwise.
2. **Phosphor icons are client-only** (`@phosphor-icons/react` uses React context).
3. **Animation hooks from framer-motion are client-only** (`useInView`, `useReducedMotion`, etc.).
4. **Dynamic routes need `generateStaticParams`** for `output: 'export'`. `/blog/[slug]` handles
this with a `_placeholder` fallback when `site.blog.grid.posts` is empty.
5. **Metadata goes in `layout.tsx`, not client pages.**

## Verify before claiming done

```bash
pnpm tsc --noEmit # type errors
pnpm build # prerender errors (catches server/client boundary bugs)
pnpm dev # runtime check — curl localhost:3000/{,mobile,business,pricing,blog,contact,privacy,terms}
```

## Content conventions

- All content lives in `src/content/site.ts` — do not hardcode strings elsewhere.
- `design.ts` only overrides token-level knobs (borderRadius, borderWidth, fonts). Never adjusts
layout or structural behavior.

## Text fit — write for the viewport, not the document

Every UI slot has fixed visual proportions. If source text is too long, **rewrite it shorter and
sharper** — never paste paragraph-length copy from a brief directly into a UI slot.

| Slot | Visual role | Writing rule |
| -------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `hero.title` | `<h1>` ~64 px, centered, 1–2 lines | 4–7 punchy words. Rewrite long headlines to a single bold claim. |
| `hero.subtitle` | Lead text ~22 px, max-w-2xl | One sentence. Two short sentences max. Distil to the strongest idea. |
| CTA labels | Pill button | Verb-first, ≤ 4 words: "Start for free", "See pricing". |
| `eyebrow` | Small uppercase badge | One noun phrase, no verb. |
| Section `title` | `<h2>` ~36–42 px | Tight claim or question, not a description. Rewrite if verbose. |
| Section `subtitle` | Sub-copy below h2 | One sentence. If you have two ideas, pick the stronger one. |
| `items[].title` | Card / feature heading | 2–4 words — a label, not a sentence. |
| `items[].description` | Card body, fixed-height grid | One sentence written as a benefit. All cards in a grid must be visually even — trim the longest to match the others. |
| `steps[].description` | Step body | One sentence. |
| `trustStats items[].value` | Large stat | Compact symbol: `"$1B+"`, `"99.9%"`, `"< 2 s"`. Never prose. |
| `trustStats items[].label` | Stat caption | Short noun phrase, no verb. |
| `faq[].answer` | Accordion body | 2–3 sentences max. Lead with the direct answer. |
| `pricing plan.description` | Plan sub-copy | One sentence, outcome-focused. |
| `pricing plan.features[]` | Checklist line | One short phrase per line. |
| `prose paragraphs[]` | Prose section, each paragraph | One idea per paragraph. Split or trim rather than run on. |

**Test:** read any string you wrote aloud. If it sounds like a document or a README, rewrite it as a UI label.
18 changes: 18 additions & 0 deletions packages/landing/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import nextVitals from 'eslint-config-next/core-web-vitals'
import nextTs from 'eslint-config-next/typescript'

const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
'.next/**',
'out/**',
'build/**',
'next-env.d.ts',
]),
])

export default eslintConfig
6 changes: 6 additions & 0 deletions packages/landing/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
10 changes: 10 additions & 0 deletions packages/landing/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
output: 'export',
images: { unoptimized: true },
trailingSlash: true,
poweredByHeader: false,
}

export default nextConfig
34 changes: 34 additions & 0 deletions packages/landing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@reineira-os/modules-landing",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"typecheck": "tsc --noEmit",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dependencies": {
"@phosphor-icons/react": "^2.1.10",
"framer-motion": "^12.26.2",
"next": "16.0.10",
"react": "19.2.1",
"react-dom": "19.2.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.0.10",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.7.4",
"tailwindcss": "^4",
"typescript": "^5"
},
"license": "MIT"
}
7 changes: 7 additions & 0 deletions packages/landing/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
}

export default config
3 changes: 3 additions & 0 deletions packages/landing/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions packages/landing/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Allow: /

Disallow: /api/
Loading
Loading