diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49db9af..69a1303 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,62 +1,11 @@ # Contributing to WordWise -Thanks for your interest! This is a small project, but contributions are welcome. +Please see [docs/Contributing.md](docs/Contributing.md) for the full contributing guide. -## How to Contribute +## Quick Links +- [Development Setup](docs/Development.md) +- [Architecture](docs/Architecture.md) +- [Deployment](docs/Deployment.md) +- [Testing](docs/Testing.md) -1. **Fork** the repository -2. **Create a feature branch** (`git checkout -b feat/my-change`) -3. **Make your changes** -4. **Test locally** (`npm start`, then open http://localhost:3000) -5. **Commit** with a clear message -6. **Push** and open a Pull Request - -## Development Setup - -```bash -git clone https://github.com/sparshsam/wordwise.git -cd wordwise -npm install -npm start -``` - -The server runs on `http://localhost:3000`. - -## Code Style - -- Keep it simple — no build steps, no frameworks beyond Express -- Use `const`/`let`, async/await, and modern Node.js (18+) -- Format readable code; no auto-formatter required - -## Pull Request Guidelines - -- One change per PR -- Update the README if your change affects usage or setup -- Update CHANGELOG.md under `[Unreleased]` -- Test that the API still returns valid JSON and the page renders - -## Adding Words - -To add curated fallback words, edit `words.json`. Each entry needs: - -```json -{ - "word": "example", - "phonetic": "/ɪɡˈzæmpəl/", - "definition": "a thing serving as a model", - "example": "This is an example sentence.", - "partOfSpeech": "noun" -} -``` - -## Reporting Issues - -Open a GitHub issue with: -- What you expected -- What happened instead -- Steps to reproduce (if applicable) -- Browser/device info (for frontend issues) - -## License - -By contributing, you agree that your contributions will be licensed under the AGPL-3.0 License. +By contributing, you agree that your contributions will be licensed under AGPL-3.0. diff --git a/README.md b/README.md index cd3e381..a455317 100644 --- a/README.md +++ b/README.md @@ -1,268 +1,44 @@ -

- WordWise -

+
+ WordWise +

WordWise

+

A word every visit, rendered huge over a full-screen background.
Built for curiosity, aesthetics, and zero friction.

+
-

WordWise

+[![Live Demo](https://img.shields.io/badge/Live%20Demo-wordwisehiccups.vercel.app-339933?style=for-the-badge&logo=vercel)](https://wordwisehiccups.vercel.app) +[![License: AGPL-3.0](https://img.shields.io/github/license/sparshsam/wordwise?style=for-the-badge&color=blue)](LICENSE) +[![Source Code](https://img.shields.io/badge/Source%20Code-GitHub-181717?style=for-the-badge&logo=github)](https://github.com/sparshsam/wordwise)

- A word every visit, rendered huge over a full-screen background. -
- Built for curiosity, aesthetics, and zero friction. + WordWise hero screenshot

-

- Live Demo → - · - Features - · - How It Works - · - API -

+## Why WordWise -

- Node >=18 - AGPL-3.0 - Last commit - Status -

- ---- - -WordWise is a single-page word-of-the-day app that serves a random English word on every visit — with its definition, phonetic pronunciation, a usage example, and a full-screen nature landscape photo or video background. It started as a hardcoded demo and grew into a polished, deployable product. - -**Maturity:** Maintained. Releases, changelog, and issue tracking are active. See [ARCHITECTURE.md](docs/architecture.md) for system design. - ---- +A calm daily ritual: one word, one landscape, one moment of attention. No streaks, no accounts, no noise. ## Features -### 📖 Word Engine | | Detail | |---|---| -| **Word pool** | 20,000+ common English words (3–12 letters), bundled as a flat file | -| **Curated fallback** | 30 hand-picked words with pre-written definitions — always servable | -| **Dictionary API** | On-the-fly lookups from the [Free Dictionary API](https://dictionaryapi.dev/), cached in memory | -| **Audio** | Pronunciation play button when the dictionary provides an audio file | -| **Safety** | All dictionary text escapes HTML before rendering (XSS protection) | - -### 🖼️ Background Media -| | Detail | -|---|---| -| **Source** | [Pexels API](https://pexels.com/api) — free, no rate limits on the free plan | -| **Content** | Nature landscape photos and videos, searched with `orientation=landscape` | -| **Mix** | 50/50 random between photo and video on each request | -| **Quality** | Highest resolution available (original for photos, UHD for videos) | -| **Fallback** | If one type fails, the other serves as backup | -| **Attribution** | Photographer/videographer name linked to original Pexels page | - -### 🕐 Timezone Bar — Desktop -A thin glass-blur strip across the top of the screen showing the current time in 12 major business timezones: - -| Abbr | City, State, Country | Fun Fact | -|---|---|---| -| **PT** | Los Angeles, California, USA | Home to Silicon Valley and Hollywood | -| **MT** | Denver, Colorado, USA | Only U.S. city to host the Olympics at a mile high | -| **CT** | Chicago, Illinois, USA | Invented the skyscraper and the brownie | -| **ET** | New York, New York, USA | NYSE opens at 9:30 ET — busiest trading hour on Earth | -| **BRT** | São Paulo, SP, Brazil | Largest city in the Americas outside North America | -| **GMT** | London, England, UK | World's timekeeping zero — all timezones measured against it | -| **CET** | Berlin, Germany | DST transition affects most of Europe | -| **GST** | Dubai, UAE | No daylight saving — sunrise barely shifts year-round | -| **PKT** | Karachi, Sindh, Pakistan | One of few countries using a 30-minute offset | -| **IST** | Mumbai, Maharashtra, India | Single timezone for 1.4 billion people | -| **CST** | Beijing, China | Spans 5 geographic timezones but uses only one | -| **JST** | Tokyo, Japan | Timezone unchanged since 1888 | - -- **Hover** any timezone → a smooth tooltip fades in with the city, state/province, country and a fun fact about that timezone -- **Date on the right** → hover for a weird historical fact about that day in history -- Times update every 30 seconds - -### 📱 Mobile Clock -On phones (<768px), the timezone bar hides and a compact clock appears in the top-right corner showing: -- Today's date -- The user's **opposite timezone** time (IST→EST fixed, EST→IST fixed, everyone else gets randomized) - -### 🌐 Cross-Platform -- **iOS Safari**: Dynamic viewport height (`100dvh`), safe area insets for notch phones, `viewport-fit=cover`, touch target minimum 44px, `-webkit-text-size-adjust` -- **Android Chrome**: Overscroll prevention, tap highlight removal, sans-serif font stack -- **Desktop**: Hover-only tooltips, wider timezone bar, keyboard accessible -- **Reduced motion**: `prefers-reduced-motion` disables transitions -- **Responsive layout**: Clamp-based font sizing scales from 3.5rem to 10rem - -### 🛡️ Reliability -- **4-second client fallback**: If the API doesn't respond in 4s, a pre-bundled word renders automatically -- **Server timeouts**: All outbound API calls use `AbortController` with 3–4 second timeouts -- **Graceful degradation**: Failed background → just the word. Failed dictionary → curated word. Everything fails → em-dash -- **3 attempts max** for dictionary lookups before falling back to curated words - -### 📦 Deployment -- One-command Vercel deploy via included `vercel.json` -- CI pipeline (`lint-and-test` check) + Dependabot for npm updates -- Main branch protected — force pushes and deletions blocked, status checks required - -### 🔒 Privacy -- Zero cookies, zero analytics, zero tracking scripts -- No authentication, no user accounts, no data storage -- Outbound requests only to Free Dictionary API and Pexels API -- Full AGPL-3.0 source - ---- - -## How It Works - -``` -Visitor opens page - │ - ▼ -┌─────────────────────────────────────┐ -│ Browser loads index.html │ -│ • Timezone bar renders instantly │ -│ • Mobile clock renders instantly │ -│ • Starts 4s fallback timer │ -│ • Fetches /api/word │ -└──────────┬──────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────┐ -│ Server receives request │ -│ • Checks in-memory cache │ -│ • 50% chance: serve cached word │ -│ (instant, no external calls) │ -│ • Otherwise: │ -│ 1. Pick random word from 20k │ -│ 2. Fetch definition from DictAPI │ -│ 3. Cache the result │ -│ 4. Fetch random Pexels bg (3s) │ -│ • Return JSON to client │ -└──────────┬──────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────┐ -│ Client renders │ -│ • Cancel fallback timer │ -│ • Word in huge centered text │ -│ • Phonetic + definition below │ -│ • Play button if audio exists │ -│ • Full-screen video or photo bg │ -│ • Photographer credit │ -└─────────────────────────────────────┘ -``` - -If the API takes longer than 4 seconds (cold start, slow network), the fallback timer fires and renders a curated word from memory immediately. - ---- - -## API - -### `GET /api/word` - -Returns a random word with definition and background media. - -**Response:** - -```json -{ - "word": "serendipity", - "phonetic": "/ˌsɛrənˈdɪpɪti/", - "definition": "the occurrence of events by chance in a happy way", - "example": "Finding that book was pure serendipity.", - "partOfSpeech": "noun", - "audioUrl": null, - "background": "https://images.pexels.com/photos/...", - "backgroundType": "photo", - "photographer": "Jane Doe", - "photoUrl": "https://pexels.com/photo/..." -} -``` - -| Field | Type | Description | -|---|---|---| -| `word` | string | The word | -| `phonetic` | string | Pronunciation (may be empty) | -| `definition` | string | Short definition | -| `example` | string | Usage example (may be empty) | -| `partOfSpeech` | string | e.g. "noun", "adjective", "verb" | -| `audioUrl` | string\|null | Pronunciation audio URL | -| `background` | string\|null | Full-resolution photo/video URL | -| `backgroundType` | string | `"photo"` or `"video"` | -| `photographer` | string\|null | Creator name | -| `photoUrl` | string\|null | Link to original Pexels page | - ---- - -## Getting Started - -### Prerequisites - -- **Node.js 18+** (native `fetch` support required) -- **npm** - -### Install & Run - -```bash -git clone https://github.com/sparshsam/wordwise.git -cd wordwise -npm install -npm start -``` - -Open http://localhost:3000. - -### Deploy to Vercel +| **Word pool** | 20,000+ common English words (3–12 letters) | +| **Curated fallback words** | 30 hand-picked words with pre-written definitions | +| **Dictionary API with audio** | On-the-fly lookups from the Free Dictionary API, pronunciation play button | +| **Nature photo/video backgrounds** | Full-screen Pexels landscape media, 50/50 random mix | +| **Desktop timezone bar** | 12 major business timezones with hover facts | +| **Mobile compact clock** | Opposite-timezone display on phones | +| **4-second fallback timer** | Guarantees content even on cold starts | +| **Zero analytics/cookies** | No tracking, no accounts, no data storage | +| **Express + Vercel deployment** | One-command deploy, serverless-ready | -```bash -npm i -g vercel -vercel --prod -``` +## Designed For -The `vercel.json` routes all requests to the Express server. +Word lovers, language learners, and anyone who wants a moment of calm every day. -### Environment Variables +## Design Philosophy -| Variable | Required | Default | Purpose | -|---|---|---|---| -| `PORT` | No | `3000` | HTTP server port | -| `PEXELS_API_KEY` | No | Embedded key | Your own Pexels API key for background media | +> A small ritual app — curiosity without accounts, beauty without tracking, learning without pressure. ---- - -## Project Structure - -``` -wordwise/ -├── docs/ -│ └── architecture.md # System architecture and data flow -├── routes/ -│ └── word.js # Word selection, dictionary lookup, Pexels fetch -├── views/ -│ ├── index.html # Main page (inline CSS + JS) -│ └── archive.html # Archive placeholder -├── public/ -│ ├── favicon.svg # SVG favicon -│ ├── favicon.png # PNG fallback favicon -│ └── og-image.png # Open Graph social preview image -├── assets/ -│ ├── screenshot-main.png # README hero screenshot -│ └── screenshot-main.svg # Vector fallback -├── words.json # 30 curated fallback words -├── words.txt # 20,000-word pool (~190 KB) -├── server.js # Express entry point -├── vercel.json # Vercel deployment config -├── package.json -├── LICENSE # AGPL-3.0 -├── CHANGELOG.md -├── CONTRIBUTING.md -├── SECURITY.md -└── .github/ - ├── dependabot.yml - └── workflows/ - └── ci.yml # Lint + smoke-test pipeline -``` - ---- - -## Tech Stack +## Built With | Layer | Technology | |---|---| @@ -271,134 +47,26 @@ wordwise/ | **Dictionary** | [Free Dictionary API](https://dictionaryapi.dev/) | | **Media** | [Pexels API](https://pexels.com/api) | | **Deployment** | Vercel (serverless Node — `@vercel/node`) | -| **CI** | GitHub Actions (syntax check + curl smoke test) | - ---- - -## Security - -- **No user data** is collected, stored, or transmitted in any form -- **No cookies**, no analytics, no tracking scripts of any kind -- **No authentication** — the app is fully read-only; there is nothing to log into -- **Outbound requests** are limited to two services: Free Dictionary API (word definitions) and Pexels API (photo/video URLs) -- **Input handling**: All dictionary content is sanitized through `escapeHtml()` before rendering to prevent XSS from API responses -- **API key**: Embedded Pexels key has read-only access to public assets only - -See [SECURITY.md](SECURITY.md) for the full policy. - ---- - -## Deployment Notes - -- **Platform**: Vercel (primary, tested). Works on any Node.js host — Railway, Render, Fly.io, your own VPS. -- **Cold starts**: First hit after inactivity takes 1–4s (serverless cold start + API calls). The 4s fallback ensures the user always sees something. -- **Memory**: The 20,000-word pool uses ~190 KB on disk. The in-memory cache grows with unique word lookups; expect ~100–300 MB under normal use. -- **Branch protection**: Main branch requires `lint-and-test` CI check to pass. Force pushes and deletions blocked. - ---- - -## Roadmap - -WordWise should remain tiny, fast, and peaceful. The roadmap expands the daily ritual without turning the app into a social network, learning platform, or account system. - -### Completed - -- [x] Random word with dictionary definitions -- [x] Landscape nature photo/video backgrounds -- [x] Audio pronunciation support -- [x] Desktop timezone bar with hover tooltips -- [x] Mobile opposite-timezone display -- [x] Date hover with historical facts -- [x] One-click Vercel deployment - -### Near-Term - -- [ ] **Daily word archive** — browse past generated words with their definitions, media credits, and dates. -- [ ] **Share-once links** — create a stable link for a specific word/background pairing without accounts. -- [ ] **Overlay controls** — user-chosen opacity, contrast, and readability settings saved locally. -- [ ] **Word frequency filter** — common, intermediate, advanced, and rare-word modes. -- [ ] **Better fallback packs** — larger curated offline set with definitions, examples, and parts of speech. -- [ ] **Keyboard shortcuts** — refresh word, play audio, copy word, copy definition, and hide/show UI. - -### Mid-Term - -- [ ] **Self-hosted dictionary mode** — remove dependency on external dictionary calls for fully self-contained deployments. -- [ ] **Local archive mode** — optionally store viewed words in local storage only, never server-side. -- [ ] **Theme packs** — forest, ocean, mountain, desert, rain, night sky, and minimal solid-background modes. -- [ ] **Pronunciation improvements** — clearer audio fallback logic, phonetic formatting, and source attribution. -- [ ] **Multilingual interface** — translate app chrome while keeping English words as the core content. -- [ ] **PWA support** — installable daily-word screen with offline fallback words. - -### Long-Term Vision -- [ ] **Personal lexicon** — a private, local-first collection of favourite words, copied definitions, and saved media moments. -- [ ] **Exportable word journal** — export favourite words as JSON, Markdown, or a printable PDF. -- [ ] **Calm learning mode** — gentle review prompts without streak pressure, points, ads, or dopamine traps. -- [ ] **Public domain dictionary pack** — ship a durable, inspectable dictionary dataset for long-term self-hosting. -- [ ] **Embeddable widget** — tiny iframe/script version for personal websites, blogs, dashboards, and digital gardens. -- [ ] **Open archive format** — portable schema for word, definition, source, media attribution, and timestamp metadata. +## Version Journey -### Optional Base / OpenProof Direction - -Any Web3 idea should be optional and almost invisible. WordWise is not a wallet app. - -Possible proof-oriented features: - -- [ ] **Word moment receipts** — locally generate a small receipt for a word/background/date pairing. -- [ ] **Archive integrity snapshots** — hash a public daily archive and optionally anchor the hash through an OpenProof-style flow. -- [ ] **Creator attribution proofs** — preserve media attribution metadata in export receipts. -- [ ] **Personal lexicon verification** — let users prove a saved word journal has not been altered without publishing the journal itself. - -What should never happen: - -- no token -- no NFT marketplace -- no wallet required to use the app -- no user tracking -- no account-gated vocabulary -- no permanent onchain storage of personal journals - -Base, if used, should be a quiet verification layer for public archives and exported receipts, not the center of the product. - -## Future Philosophy - -WordWise is a small ritual app: one word, one landscape, one moment of attention. - -Its place in the broader ecosystem is calm daily utility: - -- **Curiosity without accounts** — language discovery should not require a profile. -- **Beauty without tracking** — visual polish does not need analytics or surveillance. -- **Learning without pressure** — no streak anxiety, no leaderboards, no productivity theatre. -- **Portability over lock-in** — saved words and archives should be exportable. -- **Proof where useful** — receipts and hashes can preserve archives without exposing private reading habits. - -The ambition is not to become the biggest dictionary app. The ambition is to remain a little window that opens cleanly every day. 🌿 - ---- - -## Limitations - -WordWise is a read-only word display app. It does not: - -- Store user history or preferences server-side -- Provide spaced repetition, quizzes, or learning tools -- Offer user accounts, authentication, or personalization -- Support offline word archives (browser-level caching only) -- Guarantee dictionary availability (depends on Free Dictionary API) -- Provide medical, legal, or academic definitions - -## Contributing - -Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, code style, and pull request guidelines. - -## Citation - -If you use WordWise in research or publications, please cite the repository using the metadata in [CITATION.cff](CITATION.cff). +| Version | Date | Milestone | +|---|---|---| +| v0.12.0 | 2026-06 | Timezone bar | +| v0.10.0 | 2026-05 | Pexels backgrounds | +| v0.8.0 | 2026-04 | Fallback timer | +| v0.5.0 | 2026-03 | Dictionary API | +| v0.1.0 | 2026-01 | MVP | ## License AGPL-3.0. See [LICENSE](LICENSE) for details. ---- +## Open Collection -*Last updated: June 2026* +| Project | Description | +|---|---| +| [OpenPalette](https://github.com/sparshsam/openpalette) | Color palette generator | +| [OpenSend](https://github.com/sparshsam/opensend) | File sharing tool | +| [OpenSprout](https://github.com/sparshsam/opensprout) | Project scaffold | +| [OpenTone](https://github.com/sparshsam/opentone) | Tone analysis | diff --git a/assets/branding/icon.svg b/assets/branding/icon.svg new file mode 100644 index 0000000..bf45d53 --- /dev/null +++ b/assets/branding/icon.svg @@ -0,0 +1,4 @@ + + + W + diff --git a/assets/branding/og-image.png b/assets/branding/og-image.png new file mode 100644 index 0000000..755cb86 Binary files /dev/null and b/assets/branding/og-image.png differ diff --git a/assets/gallery/screenshot-main.svg b/assets/gallery/screenshot-main.svg new file mode 100644 index 0000000..d93d52b --- /dev/null +++ b/assets/gallery/screenshot-main.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + S E R E N D I P I T Y + /ˌsɛrənˈdɪpɪti/ + the occurrence of events by chance in a happy way + WordWise + diff --git a/assets/hero/hero.png b/assets/hero/hero.png new file mode 100644 index 0000000..755cb86 Binary files /dev/null and b/assets/hero/hero.png differ diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 0000000..ee2eb3a --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,61 @@ +# Architecture + +WordWise is a minimal vocabulary application — a single Express.js server that serves a word-of-the-day page, an archive page, and a JSON API. + +See [architecture.md](architecture.md) for the full system architecture and data flow documentation. + +## Overview + +``` +Browser → Express.js Server → views/index.html + → views/archive.html + → /api/word → word data +``` + +## Route Design + +| Path | Method | Description | +|------|--------|-------------| +| `/` | GET | Word of the day HTML page | +| `/archive` | GET | Past words HTML page | +| `/api/word` | GET | Word data as JSON | + +## Key Design Decisions + +1. **No database** — Word data is served from flat files, making deployment trivial. +2. **No authentication** — WordWise has no user accounts, sessions, or personal data. +3. **In-memory cache** — Dictionary results are cached to reduce external API calls. +4. **Vercel-ready** — Configured for serverless deployment via `vercel.json`. + +## Data Flow + +``` +Visitor opens page + │ + ▼ +Browser loads index.html + • Timezone bar renders instantly + • Mobile clock renders instantly + • Starts 4s fallback timer + • Fetches /api/word + │ + ▼ +Server receives request + • Checks in-memory cache + • 50% chance: serve cached word (instant) + • Otherwise: + 1. Pick random word from 20k pool + 2. Fetch definition from Free Dictionary API + 3. Cache the result + 4. Fetch random Pexels background (3s timeout) + • Return JSON to client + │ + ▼ +Client renders + • Cancel fallback timer + • Word in huge centered text + • Phonetic + definition below + • Play button if audio exists + • Full-screen video or photo background + • Photographer credit +``` diff --git a/docs/Contributing.md b/docs/Contributing.md new file mode 100644 index 0000000..76e9417 --- /dev/null +++ b/docs/Contributing.md @@ -0,0 +1,47 @@ +# Contributing to WordWise + +Thanks for your interest! This is a small project, but contributions are welcome. + +See [CONTRIBUTING.md](../CONTRIBUTING.md) at the project root for the full contributing guide and links to related documentation. + +## How to Contribute + +1. **Fork** the repository +2. **Create a feature branch** (`git checkout -b feat/my-change`) +3. **Make your changes** +4. **Test locally** (`npm start`, then open http://localhost:3000) +5. **Commit** with a clear message +6. **Push** and open a Pull Request + +## Quick Links + +- [Development Setup](Development.md) +- [Architecture](Architecture.md) +- [Deployment](Deployment.md) +- [Testing](Testing.md) + +## Adding Words + +To add curated fallback words, edit `words.json`. Each entry needs: + +```json +{ + "word": "example", + "phonetic": "/ɪɡˈzæmpəl/", + "definition": "a thing serving as a model", + "example": "This is an example sentence.", + "partOfSpeech": "noun" +} +``` + +## Reporting Issues + +Open a GitHub issue with: +- What you expected +- What happened instead +- Steps to reproduce (if applicable) +- Browser/device info (for frontend issues) + +## License + +By contributing, you agree that your contributions will be licensed under the AGPL-3.0 License. diff --git a/docs/Deployment.md b/docs/Deployment.md new file mode 100644 index 0000000..2b06ea3 --- /dev/null +++ b/docs/Deployment.md @@ -0,0 +1,42 @@ +# Deployment + +## Local Development + +```bash +npm install +npm start +``` + +The server runs on `http://localhost:3000`. + +## Production (Vercel) + +Deploy with a single command: + +```bash +npm i -g vercel +vercel --prod +``` + +The included `vercel.json` routes all requests to the Express server. No environment variables are required — the application is self-contained. + +## Production (Self-Hosted) + +```bash +npm install +npm start +``` + +For a production Node.js deployment, consider using a process manager: + +```bash +npm install -g pm2 +pm2 start server.js --name wordwise +``` + +## Platform Notes + +- **Primary**: Vercel (tested and recommended) +- **Alternatives**: Railway, Render, Fly.io, any Node.js host +- **Cold starts**: First hit after inactivity takes 1–4s (serverless cold start + API calls). The 4s fallback timer ensures the user always sees something. +- **Memory**: The 20,000-word pool uses ~190 KB on disk. In-memory cache grows with unique word lookups; expect ~100–300 MB under normal use. diff --git a/docs/Development.md b/docs/Development.md new file mode 100644 index 0000000..47298ed --- /dev/null +++ b/docs/Development.md @@ -0,0 +1,77 @@ +# Development + +## Prerequisites + +- **Node.js 18+** (native `fetch` support required) +- **npm** + +## Setup + +```bash +git clone https://github.com/sparshsam/wordwise.git +cd wordwise +npm install +``` + +## Running Locally + +```bash +npm start +``` + +Open http://localhost:3000. + +## Environment Variables + +| Variable | Required | Default | Purpose | +|---|---|---|---| +| `PORT` | No | `3000` | HTTP server port | +| `PEXELS_API_KEY` | No | Embedded key | Your own Pexels API key for background media | + +## Code Style + +- Keep it simple — no build steps, no frameworks beyond Express +- Use `const`/`let`, `async/await`, and modern Node.js (18+) +- Format readable code; no auto-formatter required + +## Project Structure + +``` +wordwise/ +├── docs/ # Documentation +│ ├── Architecture.md +│ ├── Contributing.md +│ ├── Deployment.md +│ ├── Development.md +│ └── Testing.md +├── routes/ +│ └── word.js # Word selection, dictionary lookup, Pexels fetch +├── views/ +│ ├── index.html # Main page (inline CSS + JS) +│ └── archive.html # Archive placeholder +├── public/ +│ ├── favicon.svg # SVG favicon +│ ├── favicon.png # PNG fallback favicon +│ └── og-image.png # Open Graph social preview image +├── assets/ +│ ├── hero/hero.png # README hero screenshot +│ ├── branding/icon.svg # Brand icon +│ ├── branding/og-image.png # OG image asset +│ ├── gallery/ # Screenshot gallery +│ ├── screenshots/ # Legacy screenshots +│ ├── icons/ # App icons +│ └── screenshot-main.svg # Vector fallback +├── words.json # 30 curated fallback words +├── words.txt # 20,000-word pool (~190 KB) +├── server.js # Express entry point +├── vercel.json # Vercel deployment config +├── package.json +├── LICENSE # AGPL-3.0 +├── CHANGELOG.md +├── CONTRIBUTING.md +├── SECURITY.md +└── .github/ + ├── dependabot.yml + └── workflows/ + └── ci.yml # Lint + smoke-test pipeline +``` diff --git a/docs/Testing.md b/docs/Testing.md new file mode 100644 index 0000000..ce6fd47 --- /dev/null +++ b/docs/Testing.md @@ -0,0 +1,43 @@ +# Testing + +WordWise uses a CI pipeline defined in `.github/workflows/ci.yml`. + +## CI Check: `lint-and-test` + +The CI workflow runs on every push and pull request to the `main` branch: + +1. **Checkout** repository +2. **Use Node.js 22** with npm cache +3. **Install dependencies** (`npm ci`) +4. **Check syntax** (`node --check server.js routes/word.js`) +5. **Start server and smoke-test API**: + - Starts the Express server in background + - Hits `/api/word` and validates the JSON response contains `word`, `definition`, and `partOfSpeech` + - Hits `/` and checks the HTML contains "WordWise" + - Shuts down the server + +## Running Tests Locally + +```bash +# Syntax check +node --check server.js routes/word.js + +# Start server +node server.js & + +# Smoke test the API +curl -s http://localhost:3000/api/word | python3 -c " +import sys, json +d = json.load(sys.stdin) +assert 'word' in d +assert 'definition' in d +assert 'partOfSpeech' in d +print(f'OK: {d[\"word\"]}') +" + +# Smoke test the HTML +curl -s http://localhost:3000/ | grep -q 'WordWise' && echo 'HTML OK' + +# Stop the server +kill %1 +```