@happyvertical/smrt-template-site-static-json 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,30 @@
1
+ # template-site-static-json
2
+
3
+ Scaffold template for static community news sites with JSON data. Used by `smrt gnode create`.
4
+
5
+ ## Exports
6
+
7
+ - Template config with placeholder mappings (site name, location, timezone, coordinates)
8
+ - Template directory path and file path utilities
9
+
10
+ ## Template Contents
11
+
12
+ - `template.config.js` — placeholder definitions and post-generation hooks
13
+ - `template/scripts/init-data.ts` — data initialization script
14
+ - `template/src/site.config.ts` — typed access to `smrt.config.js` site section (`initSiteConfig()`/`getSite()`)
15
+ - `template/src/lib/components/WeatherHeader.svelte` — local weather-bar shim (was previously imported from `@happyvertical/smrt-svelte`, but that package dropped the component before the 0.24.x baseline). The shim renders the caelus-shaped `forecast` payload; consumers should customize for their data layer if they pull a different upstream.
16
+
17
+ ## Key Patterns
18
+
19
+ - **Placeholder system**: variables (site name, location, timezone) replaced during `smrt gnode create`
20
+ - **Post-generation hooks**: run after template files are copied (e.g., init-data.ts)
21
+ - **Dependency injection**: `template/package.json` ships with empty `dependencies`/`devDependencies`; the scaffolder injects entries from `template.config.js` at generation time, so version bumps live in `template.config.js` only.
22
+ - **Workflow scripts**: `workflow:caelus` and `workflow:praeco` are currently stubs that print a clear "upstream package needs a CLI bin" message and exit with status 1. Two independent reasons today's scaffold can't run them:
23
+ 1. The upstream `@happyvertical/caelus` and `@happyvertical/praeco` packages are library-only — they do not declare a `bin` field, so `npx @happyvertical/caelus` / `npx @happyvertical/praeco` exits with "could not determine executable to run."
24
+ 2. The template does not ship a local workflow shim (e.g. `template/src/workflows/caelus.ts`) that would import the upstream package and invoke the workflow from code. A `tsx`-based script would work fine if such a shim existed; the previous baseline pointed at `tsx src/workflows/caelus.ts` and failed only because the shim file was missing, not because tsx requires upstream to ship a bin.
25
+
26
+ Once upstream ships bins, replace the stubs with `npx @happyvertical/caelus --config smrt.config.js` / `npx @happyvertical/praeco --config smrt.config.js` (the scoped names — the unscoped `caelus` is a 404 on the public registry). Alternatively, add a `template/src/workflows/<name>.ts` shim that imports the upstream package's main export and invokes the workflow, then point the script at `tsx src/workflows/<name>.ts`. Until either is in place, callers who need these workflows should write the shim themselves in their generated project.
27
+
28
+ ## Runtime Dependencies
29
+
30
+ Template projects use: `smrt-content`, `smrt-events`, `smrt-places`, `smrt-profiles`, `smrt-ui`.
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Happy Vertical Corporation>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # @happyvertical/smrt-template-site-static-json
2
+
3
+ Template for static community news sites with JSON-based data storage. Generates a SvelteKit static site with weather integration (Caelus) and council meeting scraping (Praeco).
4
+
5
+ ## What This Template Provides
6
+
7
+ - SvelteKit with `adapter-static` for static site generation
8
+ - JSON file-based data storage in `data/`
9
+ - Config-driven site identity, navigation, categories, and theming via `smrt.config.js`
10
+ - Weather forecasts via Caelus (Environment Canada)
11
+ - Council meeting scraping and article generation via Praeco
12
+ - Typed site config helper (`initSiteConfig()`/`getSite()`)
13
+ - Pre-built routes: home, about, contact
14
+ - Markdown utility for content rendering
15
+
16
+ ## Prerequisites
17
+
18
+ - Node.js 18+
19
+ - pnpm (recommended)
20
+ - Gemini API key (for Praeco article generation)
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ smrt gnode create my-town-site --template site-static-json \
26
+ --location "My Town, AB" \
27
+ --lat 53.5 --lon -113.5
28
+
29
+ cd my-town-site
30
+ pnpm install
31
+ cp .env.example .env # Add your GEMINI_API_KEY
32
+ pnpm run init-data # Initialize data directory
33
+ pnpm dev # Start dev server
34
+ ```
35
+
36
+ ## Environment Variables
37
+
38
+ Defined in `.env.example`:
39
+
40
+ | Variable | Required | Description |
41
+ |----------|----------|-------------|
42
+ | `GEMINI_API_KEY` | Yes | Google Gemini API key for Praeco article generation |
43
+ | `PUBLIC_GTM_ID` | No | Google Tag Manager container ID |
44
+ | `CUSTOM_DOMAIN` | No | Custom domain for deployment |
45
+
46
+ ## Configuration
47
+
48
+ Edit `smrt.config.js` to customize. Key sections:
49
+
50
+ - **`site`** -- name, description, location, navigation links, theme colors
51
+ - **`categories`** -- content categories for routing (e.g., `politics/local`)
52
+ - **`modules.praeco`** -- council meeting sources and report configurations
53
+ - **`modules.caelus`** -- weather location (set automatically from `--lat`/`--lon` flags)
54
+
55
+ ## Available Scripts
56
+
57
+ | Script | Description |
58
+ |--------|-------------|
59
+ | `pnpm dev` | Start development server |
60
+ | `pnpm build` | Build static site to `./build` |
61
+ | `pnpm preview` | Preview the built site |
62
+ | `pnpm run init-data` | Initialize JSON data files |
63
+ | `pnpm run workflow:caelus` | Fetch weather data |
64
+ | `pnpm run workflow:praeco` | Scrape council meetings |
65
+ | `pnpm run validate:json` | Validate JSON data files |
66
+ | `pnpm run validate:slugs` | Validate content slugs |
67
+
68
+ ## Template Structure
69
+
70
+ ```
71
+ template/
72
+ ├── .env.example # Environment variables
73
+ ├── .gitignore
74
+ ├── package.json # Scripts and dependency placeholders
75
+ ├── smrt.config.js # Site identity, modules, theme
76
+ ├── svelte.config.js # SvelteKit with adapter-static
77
+ ├── tsconfig.json
78
+ ├── vite.config.ts
79
+ ├── data/ # JSON data storage directory
80
+ ├── scripts/
81
+ │ └── init-data.ts # Data initialization script
82
+ └── src/
83
+ ├── app.css # Global styles
84
+ ├── app.d.ts # SvelteKit type declarations
85
+ ├── app.html # HTML shell
86
+ ├── site.config.ts # Typed config helper (initSiteConfig/getSite)
87
+ ├── lib/
88
+ │ └── utils/
89
+ │ └── markdown.ts # Markdown rendering utility
90
+ └── routes/
91
+ ├── +layout.server.ts # Loads site config for all routes
92
+ ├── +layout.svelte # Root layout
93
+ ├── +page.server.ts # Home page data
94
+ ├── +page.svelte # Home page
95
+ ├── about/ # About page
96
+ └── contact/ # Contact page
97
+ ```
98
+
99
+ ## Placeholder Substitution
100
+
101
+ During `smrt gnode create`, placeholders like `{{SITE_NAME}}`, `{{LOCATION_NAME}}`, `{{LATITUDE}}`, `{{LONGITUDE}}`, and `{{TIMEZONE}}` are replaced from CLI flags. See `template.config.js` for the full mapping.
102
+
103
+ ## Deployment
104
+
105
+ This template uses SvelteKit's static adapter. Build and deploy to any static host:
106
+
107
+ ```bash
108
+ pnpm build
109
+ # Deploy ./build to S3, Netlify, Vercel, Cloudflare Pages, etc.
110
+ ```
111
+
112
+ ## Related
113
+
114
+ - [SMRT Framework](https://github.com/happyvertical/smrt)
115
+ - [SvelteKit](https://kit.svelte.dev/)
package/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Static Site Template
3
+ *
4
+ * Template for community news sites with JSON data storage.
5
+ * Use with: smrt gnode create <name> --template site-static-json
6
+ */
7
+
8
+ export { default as config } from './template.config.js';
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@happyvertical/smrt-template-site-static-json",
3
+ "version": "0.30.0",
4
+ "description": "Static community site template with JSON data storage and SMRT framework",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./template/*": "./template/*"
10
+ },
11
+ "files": [
12
+ "CLAUDE.md",
13
+ "README.md",
14
+ "index.js",
15
+ "template",
16
+ "template.config.js",
17
+ "AGENTS.md"
18
+ ],
19
+ "keywords": [
20
+ "smrt",
21
+ "sveltekit",
22
+ "template",
23
+ "scaffold",
24
+ "community-site",
25
+ "static-site",
26
+ "json"
27
+ ],
28
+ "author": "HappyVertical",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/happyvertical/smrt.git",
33
+ "directory": "packages/template-site-static-json"
34
+ },
35
+ "peerDependencies": {
36
+ "@happyvertical/smrt-core": "0.30.0",
37
+ "@happyvertical/smrt-config": "0.30.0"
38
+ },
39
+ "publishConfig": {
40
+ "registry": "https://registry.npmjs.org",
41
+ "access": "public"
42
+ }
43
+ }
@@ -0,0 +1,8 @@
1
+ # AI Provider (for Praeco article generation)
2
+ GEMINI_API_KEY=your-gemini-api-key
3
+
4
+ # Optional: Google Tag Manager
5
+ # PUBLIC_GTM_ID=GTM-XXXXXX
6
+
7
+ # Optional: Custom domain for deployment
8
+ # CUSTOM_DOMAIN=example.com
@@ -0,0 +1,21 @@
1
+ # SMRT Static JSON Site
2
+
3
+ This project uses SMRT configuration and generated local data for a static site.
4
+ If SMRT objects are added, enable the SMRT Vite plugin so local knowledge
5
+ artifacts are generated in `.smrt/smrt-knowledge.json`.
6
+
7
+ ## Agent Workflow
8
+
9
+ - Treat `AGENTS.md` as the canonical project guidance file.
10
+ - Keep `CLAUDE.md` as the one-line `@AGENTS.md` shim.
11
+ - Use `smrt knowledge:architecture-context --format markdown` or the
12
+ `build-domain-architecture-context` MCP tool when adding SMRT packages.
13
+ - Use `knowledge: { tags, summary, risks }` on domain objects that need
14
+ package-specific review guidance.
15
+
16
+ ## Safety
17
+
18
+ - Do not expose `/__smrt/knowledge` HTTP routes in production without explicit
19
+ admin auth.
20
+ - Keep generated data and `.smrt` artifacts out of source control unless the
21
+ project intentionally publishes a package artifact.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,59 @@
1
+ # {{SITE_NAME}}
2
+
3
+ Local news and community information for {{LOCATION_NAME}}.
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ # Install dependencies
9
+ pnpm install
10
+
11
+ # Initialize data files
12
+ pnpm run init-data
13
+
14
+ # Start development server
15
+ pnpm dev
16
+ ```
17
+
18
+ ## Configuration
19
+
20
+ Edit `smrt.config.js` to configure:
21
+
22
+ - **Site identity**: Name, location, navigation
23
+ - **Data sources**: Council meeting URLs for Praeco
24
+ - **Weather**: Location coordinates for Caelus
25
+
26
+ ## Workflows
27
+
28
+ ```bash
29
+ # Fetch weather data
30
+ pnpm workflow:caelus
31
+
32
+ # Generate articles from council meetings
33
+ pnpm workflow:praeco
34
+ ```
35
+
36
+ ## Deployment
37
+
38
+ ```bash
39
+ # Build static site
40
+ pnpm build
41
+
42
+ # Preview production build
43
+ pnpm preview
44
+ ```
45
+
46
+ Deploy the `build/` directory to any static host (S3, Netlify, Vercel, etc.).
47
+
48
+ ## Project Structure
49
+
50
+ ```
51
+ ├── smrt.config.js # Site configuration
52
+ ├── data/ # JSON data storage
53
+ ├── scripts/ # Utility scripts
54
+ └── src/
55
+ ├── site.config.ts # Config helper
56
+ ├── app.css # Global styles
57
+ ├── routes/ # SvelteKit pages
58
+ └── lib/ # Shared utilities
59
+ ```
@@ -0,0 +1,2 @@
1
+ # This file ensures the data directory exists in git.
2
+ # JSON data files are generated and not committed.
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "{{PACKAGE_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite dev",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11
+ "typecheck": "svelte-kit sync && tsc --noEmit && svelte-check --tsconfig ./tsconfig.json",
12
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
13
+ "init-data": "tsx scripts/init-data.ts",
14
+ "workflow:caelus": "node -e \"console.error('workflow:caelus requires @happyvertical/caelus to ship a CLI bin (tracked upstream). For now invoke the workflow from a script that imports the package directly.'); process.exit(1)\"",
15
+ "workflow:praeco": "node -e \"console.error('workflow:praeco requires @happyvertical/praeco to ship a CLI bin (tracked upstream). For now invoke the workflow from a script that imports the package directly.'); process.exit(1)\"",
16
+ "validate:json": "tsx scripts/validate-json.ts",
17
+ "validate:slugs": "tsx scripts/validate-slugs.ts"
18
+ },
19
+ "devDependencies": {},
20
+ "dependencies": {}
21
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Initialize Data Directory
3
+ *
4
+ * Creates empty JSON files for the site's data storage.
5
+ * Run this after creating a new site: pnpm run init-data
6
+ */
7
+
8
+ import { mkdir, writeFile, access } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+
11
+ const DATA_DIR = join(process.cwd(), 'data');
12
+
13
+ const INITIAL_FILES: Record<string, any[]> = {
14
+ 'contents.json': [],
15
+ 'events.json': [],
16
+ 'places.json': [],
17
+ 'profiles.json': [],
18
+ 'assets.json': [],
19
+ };
20
+
21
+ async function initData() {
22
+ console.log('Initializing data directory...\n');
23
+
24
+ // Ensure data directory exists
25
+ try {
26
+ await access(DATA_DIR);
27
+ console.log(' ✓ data/ directory exists');
28
+ } catch {
29
+ await mkdir(DATA_DIR, { recursive: true });
30
+ console.log(' ✓ Created data/ directory');
31
+ }
32
+
33
+ // Create each data file if it doesn't exist
34
+ for (const [filename, initialData] of Object.entries(INITIAL_FILES)) {
35
+ const filepath = join(DATA_DIR, filename);
36
+ try {
37
+ await access(filepath);
38
+ console.log(` • ${filename} exists, skipping`);
39
+ } catch {
40
+ await writeFile(filepath, JSON.stringify(initialData, null, 2));
41
+ console.log(` ✓ Created ${filename}`);
42
+ }
43
+ }
44
+
45
+ console.log('\n✅ Data initialization complete!');
46
+ console.log('\nNext steps:');
47
+ console.log(' 1. Configure smrt.config.js with your council sources');
48
+ console.log(' 2. Run pnpm workflow:caelus to fetch weather data');
49
+ console.log(' 3. Run pnpm workflow:praeco to generate articles');
50
+ console.log(' 4. Run pnpm dev to start the development server');
51
+ }
52
+
53
+ initData().catch((error) => {
54
+ console.error('Failed to initialize data:', error);
55
+ process.exit(1);
56
+ });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * SMRT Configuration for {{SITE_NAME}}
3
+ *
4
+ * This file configures site identity, navigation, data sources, and workflows.
5
+ */
6
+
7
+ export default {
8
+ // Site identity (used by components for titles, navigation, SEO)
9
+ site: {
10
+ name: '{{SITE_NAME}}',
11
+ shortName: '{{SITE_SHORT_NAME}}',
12
+ description: 'Local news and community information for {{LOCATION_NAME}}',
13
+ url: 'https://{{PACKAGE_NAME}}.com',
14
+ location: {
15
+ name: '{{LOCATION_NAME}}',
16
+ latitude: {{LATITUDE}},
17
+ longitude: {{LONGITUDE}},
18
+ timezone: '{{TIMEZONE}}',
19
+ },
20
+ navigation: {
21
+ primary: [
22
+ { label: 'Politics', href: '/politics' },
23
+ { label: 'Weather', href: '/weather' },
24
+ ],
25
+ footer: [
26
+ { label: 'About', href: '/about' },
27
+ { label: 'Contact', href: '/contact' },
28
+ { label: 'RSS', href: '/rss.xml' },
29
+ ],
30
+ },
31
+ theme: {
32
+ primaryColor: '#1976d2',
33
+ primaryLight: '#e3f2fd',
34
+ primaryDark: '#0d47a1',
35
+ },
36
+ },
37
+
38
+ // Site-wide category definitions (for routing/navigation)
39
+ categories: {
40
+ politics: {
41
+ name: 'Politics',
42
+ children: {
43
+ local: { name: 'Local Government' },
44
+ county: { name: 'County Government' },
45
+ },
46
+ },
47
+ community: {
48
+ name: 'Community',
49
+ children: {
50
+ events: { name: 'Events' },
51
+ },
52
+ },
53
+ },
54
+
55
+ // Agent/developer knowledge defaults
56
+ knowledge: {
57
+ enabled: true,
58
+ api: {
59
+ enabled: false,
60
+ basePath: '/__smrt/knowledge',
61
+ requireAdmin: true,
62
+ includeDocs: false,
63
+ includePrompts: false,
64
+ },
65
+ },
66
+
67
+ // Global package settings
68
+ packages: {
69
+ cli: {
70
+ verbose: true,
71
+ database: {
72
+ type: 'json',
73
+ url: './data',
74
+ },
75
+ },
76
+ ai: {
77
+ defaultProvider: 'claude-cli',
78
+ defaultModel: 'sonnet',
79
+ },
80
+ },
81
+
82
+ // Module-specific configurations
83
+ modules: {
84
+ // Praeco module configuration (meeting scraper)
85
+ praeco: {
86
+ db: {
87
+ type: 'json',
88
+ url: './data',
89
+ writeStrategy: 'immediate',
90
+ },
91
+ ai: {
92
+ type: 'gemini',
93
+ model: 'gemini-2.0-flash-exp',
94
+ apiKey: process.env.GEMINI_API_KEY,
95
+ },
96
+ analyzeOptions: {
97
+ detailLevel: 'comprehensive',
98
+ },
99
+ // Add your council sources here
100
+ sources: [
101
+ // Example:
102
+ // {
103
+ // type: 'documents',
104
+ // council: 'my-council',
105
+ // url: 'https://example.com/meetings/',
106
+ // },
107
+ ],
108
+ // Add your report configurations here
109
+ reports: [
110
+ // Example:
111
+ // {
112
+ // type: 'meeting-recap',
113
+ // council: 'my-council',
114
+ // category: 'politics/local',
115
+ // author: '{{SITE_SHORT_NAME}} Reporter',
116
+ // role: 'You are a professional journalist writing for {{LOCATION_NAME}} residents.',
117
+ // },
118
+ ],
119
+ },
120
+
121
+ // Caelus module configuration (weather)
122
+ caelus: {
123
+ db: {
124
+ type: 'json',
125
+ url: './data',
126
+ writeStrategy: 'immediate',
127
+ clearCache: true,
128
+ },
129
+ provider: 'environment-canada',
130
+ location: {
131
+ name: '{{LOCATION_NAME}}',
132
+ latitude: {{LATITUDE}},
133
+ longitude: {{LONGITUDE}},
134
+ },
135
+ logLevel: 'info',
136
+ },
137
+ },
138
+ };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Global CSS for {{SITE_NAME}}
3
+ * Design tokens as CSS custom properties
4
+ */
5
+
6
+ :root {
7
+ /* Colors - Primary (customize these in smrt.config.js site.theme) */
8
+ --color-primary: #1976d2;
9
+ --color-primary-main: #1976d2;
10
+ --color-primary-light: #e3f2fd;
11
+ --color-primary-dark: #0d47a1;
12
+
13
+ /* Colors - Neutral */
14
+ --color-neutral-white: #ffffff;
15
+ --color-neutral-gray100: #f5f5f5;
16
+ --color-neutral-gray200: #eeeeee;
17
+ --color-neutral-gray300: #e0e0e0;
18
+ --color-neutral-gray400: #bdbdbd;
19
+ --color-neutral-gray500: #9e9e9e;
20
+ --color-neutral-gray600: #757575;
21
+ --color-neutral-gray700: #616161;
22
+ --color-neutral-gray800: #424242;
23
+ --color-neutral-gray900: #212121;
24
+
25
+ /* Colors - Semantic */
26
+ --color-semantic-success: #4caf50;
27
+ --color-semantic-warning: #ff9800;
28
+ --color-semantic-error: #f44336;
29
+ --color-semantic-info: #2196f3;
30
+
31
+ /* Colors - Text */
32
+ --color-text-primary: #333333;
33
+ --color-text-secondary: #666666;
34
+ --color-text-disabled: #9e9e9e;
35
+ --color-text-inverse: #ffffff;
36
+
37
+ /* Spacing */
38
+ --spacing-xs: 0.25rem;
39
+ --spacing-sm: 0.5rem;
40
+ --spacing-md: 1rem;
41
+ --spacing-lg: 1.5rem;
42
+ --spacing-xl: 2rem;
43
+ --spacing-2xl: 3rem;
44
+ --spacing-3xl: 4rem;
45
+
46
+ /* Typography */
47
+ --font-family-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
48
+ --font-family-serif: Georgia, 'Times New Roman', serif;
49
+ --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
50
+
51
+ --font-size-xs: 0.75rem;
52
+ --font-size-sm: 0.875rem;
53
+ --font-size-base: 1rem;
54
+ --font-size-lg: 1.125rem;
55
+ --font-size-xl: 1.25rem;
56
+ --font-size-2xl: 1.5rem;
57
+ --font-size-3xl: 1.875rem;
58
+ --font-size-4xl: 2.25rem;
59
+
60
+ --font-weight-normal: 400;
61
+ --font-weight-medium: 500;
62
+ --font-weight-semibold: 600;
63
+ --font-weight-bold: 700;
64
+
65
+ --line-height-tight: 1.2;
66
+ --line-height-normal: 1.6;
67
+ --line-height-relaxed: 1.8;
68
+
69
+ /* Shadows */
70
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
71
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
72
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
73
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
74
+
75
+ /* Border Radius */
76
+ --radius-sm: 4px;
77
+ --radius-md: 8px;
78
+ --radius-lg: 12px;
79
+ --radius-full: 9999px;
80
+
81
+ /* Transitions */
82
+ --transition-fast: 150ms ease;
83
+ --transition-base: 200ms ease;
84
+ --transition-slow: 300ms ease;
85
+ }
86
+
87
+ /* Reset and base styles */
88
+ *,
89
+ *::before,
90
+ *::after {
91
+ box-sizing: border-box;
92
+ }
93
+
94
+ body {
95
+ margin: 0;
96
+ font-family: var(--font-family-sans);
97
+ font-size: var(--font-size-base);
98
+ line-height: var(--line-height-normal);
99
+ color: var(--color-text-primary);
100
+ background: var(--color-neutral-white);
101
+ -webkit-font-smoothing: antialiased;
102
+ -moz-osx-font-smoothing: grayscale;
103
+ }
104
+
105
+ /* Typography defaults */
106
+ h1, h2, h3, h4, h5, h6 {
107
+ margin: 0;
108
+ font-weight: var(--font-weight-semibold);
109
+ line-height: var(--line-height-tight);
110
+ color: var(--color-text-primary);
111
+ }
112
+
113
+ p {
114
+ margin: 0;
115
+ }
116
+
117
+ a {
118
+ color: var(--color-primary-main);
119
+ text-decoration: none;
120
+ transition: color var(--transition-fast);
121
+ }
122
+
123
+ a:hover {
124
+ color: var(--color-primary-dark);
125
+ }
126
+
127
+ /* Focus styles for accessibility */
128
+ :focus-visible {
129
+ outline: 2px solid var(--color-primary-main);
130
+ outline-offset: 2px;
131
+ }
132
+
133
+ /* Remove default button styles */
134
+ button {
135
+ font-family: inherit;
136
+ font-size: inherit;
137
+ line-height: inherit;
138
+ margin: 0;
139
+ padding: 0;
140
+ border: none;
141
+ background: none;
142
+ cursor: pointer;
143
+ }
144
+
145
+ /* Utility classes */
146
+ .sr-only {
147
+ position: absolute;
148
+ width: 1px;
149
+ height: 1px;
150
+ padding: 0;
151
+ margin: -1px;
152
+ overflow: hidden;
153
+ clip: rect(0, 0, 0, 0);
154
+ white-space: nowrap;
155
+ border-width: 0;
156
+ }