diff --git a/apps/main-app/package.json b/apps/main-app/package.json index b5609f4..e1e69ea 100644 --- a/apps/main-app/package.json +++ b/apps/main-app/package.json @@ -32,6 +32,7 @@ "@radix-ui/react-tabs": "^1.1.13", "@tanstack/react-query": "^5.90.2", "@wisp/atproto-utils": "workspace:*", + "@wisp/css": "workspace:*", "@wisp/constants": "workspace:*", "@wisp/database": "workspace:*", "@wisp/fs-utils": "workspace:*", diff --git a/apps/main-app/public/editor/acceptable-use.html b/apps/main-app/public/editor/acceptable-use.html index 51e4d35..11d2a65 100644 --- a/apps/main-app/public/editor/acceptable-use.html +++ b/apps/main-app/public/editor/acceptable-use.html @@ -8,94 +8,8 @@ + diff --git a/apps/main-app/public/landingpage.html b/apps/main-app/public/landingpage.html index 558d628..f7b6e59 100644 --- a/apps/main-app/public/landingpage.html +++ b/apps/main-app/public/landingpage.html @@ -31,122 +31,9 @@ + diff --git a/apps/main-app/src/index.ts b/apps/main-app/src/index.ts index 1638426..588db95 100644 --- a/apps/main-app/src/index.ts +++ b/apps/main-app/src/index.ts @@ -25,6 +25,7 @@ import { csrfProtection } from './lib/csrf' import { DNSVerificationWorker } from './lib/dns-verification-worker' import { createLogger, logCollector, initializeGrafanaExporters } from '@wisp/observability' import { observabilityMiddleware } from '@wisp/observability/middleware/elysia' +import { css as wispCss } from '@wisp/css' import { promptAdminSetup } from './lib/admin-auth' import { adminRoutes } from './routes/admin' @@ -225,6 +226,11 @@ export const app = new Elysia({ }) : (app) => app ) + .get('/wisp.css', ({ set }) => { + set.headers['Content-Type'] = 'text/css; charset=utf-8' + set.headers['Cache-Control'] = 'public, max-age=86400' + return wispCss + }) .get('/acceptable-use', async ({ set }) => { set.headers['Content-Type'] = 'text/html; charset=utf-8' return await Bun.file('./apps/main-app/public/editor/acceptable-use.html').text() diff --git a/bun.lock b/bun.lock index 484e859..ace9223 100644 --- a/bun.lock +++ b/bun.lock @@ -71,6 +71,7 @@ "@tanstack/react-query": "^5.90.2", "@wisp/atproto-utils": "workspace:*", "@wisp/constants": "workspace:*", + "@wisp/css": "workspace:*", "@wisp/database": "workspace:*", "@wisp/fs-utils": "workspace:*", "@wisp/lexicons": "workspace:*", @@ -156,6 +157,10 @@ "name": "@wisp/constants", "version": "1.0.0", }, + "packages/@wisp/css": { + "name": "@wisp/css", + "version": "1.0.0", + }, "packages/@wisp/database": { "name": "@wisp/database", "version": "1.0.0", @@ -837,6 +842,8 @@ "@wisp/constants": ["@wisp/constants@workspace:packages/@wisp/constants"], + "@wisp/css": ["@wisp/css@workspace:packages/@wisp/css"], + "@wisp/database": ["@wisp/database@workspace:packages/@wisp/database"], "@wisp/fs-utils": ["@wisp/fs-utils@workspace:packages/@wisp/fs-utils"], @@ -889,7 +896,7 @@ "bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="], - "bun-types": ["bun-types@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-qyschsA03Qz+gou+apt6HNl6HnI+sJJLL4wLDke4iugsE6584CMupOtTY1n+2YC9nGVrEKUlTs99jjRLKgWnjQ=="], + "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], diff --git a/packages/@wisp/css/README.md b/packages/@wisp/css/README.md new file mode 100644 index 0000000..b188ca7 --- /dev/null +++ b/packages/@wisp/css/README.md @@ -0,0 +1,143 @@ +# wisp.css + +A minimal, monospace-first CSS stylesheet with automatic light/dark mode. See it live on https://wisp.place or https://sites.wisp.place/wisp.place/wisp.css for a demo. + +## Quick Start + +### CDN +```html + +``` + +### Self-hosted + +Just copy `wisp.css` to your project and link to it: + +```html + +``` + +## Features + +- **Automatic dark mode** via `prefers-color-scheme` +- **CSS custom properties** for easy theming +- **JetBrains Mono** typography +- **Components**: cards, buttons, alerts, forms +- **Utility classes**: spacing, flexbox, text styling +- **Responsive** with mobile-first breakpoints + +## CSS Variables + +All variables use the `--wisp-` prefix. Unprefixed aliases are also available for convenience. + +### Colors + +```css +--wisp-bg /* Main background */ +--wisp-bg-alt /* Alternate background (cards, etc) */ +--wisp-text /* Primary text */ +--wisp-text-muted /* Secondary text */ +--wisp-text-subtle /* Tertiary text */ +--wisp-border /* Strong borders */ +--wisp-border-light /* Light borders */ +--wisp-accent /* Accent/link color */ +--wisp-cta-bg /* Button background */ +--wisp-cta-text /* Button text */ +--wisp-code-bg /* Code block background */ +--wisp-success /* Success state */ +--wisp-danger /* Error/danger state */ +--wisp-warning /* Warning state */ +--wisp-info /* Info state */ +``` + +### Customizing + +Override variables in your own CSS: + +```css +:root { + --wisp-accent: oklch(0.65 0.20 200); /* Blue accent */ +} +``` + +## Components + +### Cards + +```html +
Basic card
+
Success card
+
Danger card
+
Info card
+``` + +### Buttons + +```html + + +CTA Primary +CTA Secondary +``` + +### Alerts + +```html +
Success message
+
Error message
+
Warning message
+
Info message
+``` + +### Forms + +```html + + +``` + +## Utility Classes + +### Spacing + +- `.m-{0-4}` - margin +- `.mt-{0-6}`, `.mb-{0-6}`, `.ml-{0-4}`, `.mr-{0-4}` - directional margin +- `.p-{0-6}` - padding +- `.px-{1-4}`, `.py-{1-4}` - horizontal/vertical padding +- `.gap-{1-6}` - flex/grid gap + +### Flexbox + +- `.flex`, `.inline-flex` +- `.flex-col`, `.flex-wrap` +- `.items-start`, `.items-center`, `.items-end` +- `.justify-start`, `.justify-center`, `.justify-end`, `.justify-between` + +### Text + +- `.text-left`, `.text-center`, `.text-right` +- `.text-sm`, `.text-base`, `.text-lg`, `.text-xl` +- `.font-normal`, `.font-medium`, `.font-semibold`, `.font-bold` +- `.text-muted`, `.text-subtle`, `.text-accent`, `.text-success`, `.text-danger` + +### Display + +- `.hidden`, `.block`, `.inline`, `.inline-block`, `.flex`, `.grid` +- `.hide-mobile` - hidden on screens < 768px +- `.hide-desktop` - hidden on screens >= 769px + +## Layout + +```html +
+ +
+ +
+ +
+``` + +## License + +MIT diff --git a/packages/@wisp/css/index.html b/packages/@wisp/css/index.html new file mode 100644 index 0000000..8f7694d --- /dev/null +++ b/packages/@wisp/css/index.html @@ -0,0 +1,400 @@ + + + + + + wisp.css - Example + + + + + + +
+
+ + +
+
+ +
+ +
+

wisp.css

+

A minimal, monospace-first CSS stylesheet with automatic light/dark mode.

+ +
+ + +
+

Typography

+ +
+

Heading 1

+

Heading 2

+

Heading 3

+

Heading 4

+

This is a paragraph with bold text and a link. The stylesheet uses JetBrains Mono for that clean monospace aesthetic.

+

This is small text for captions or metadata.

+

Here's some inline code in a sentence.

+
// Code block
+const greeting = "Hello, wisp.css!";
+console.log(greeting);
+
+
+ + +
+

Buttons

+ +
+

Standard Buttons

+
+ + + +
+ +

CTA Buttons

+ + +

Button Sizes

+
+ + +
+
+
+ + +
+

Cards

+ +
+

Default Card

+

This is a basic card component with some content inside.

+
+ +
+

Success Card

+

Use this for positive feedback or confirmations.

+
+ +
+

Danger Card

+

Use this for errors or destructive actions.

+
+ +
+

Warning Card

+

Use this for warnings or important notices.

+
+ +
+

Info Card

+

Use this for informational messages.

+
+ +
+
+
💡
+
+

Card with Icon

+

Use card-flex and card-icon for icon layouts.

+
+
+
+
+ + +
+

Alerts

+ +
+ Success! Your changes have been saved. +
+ +
+ Info: This is some helpful information. +
+ +
+ Warning: Please review before continuing. +
+ +
+ Error: Something went wrong. Please try again. +
+
+ + +
+

Forms

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + +
+

Lists

+ +
+

Ordered List

+
    +
  1. First item with automatic numbering
  2. +
  3. Second item in the list
  4. +
  5. Third item completes the set
  6. +
+ +

Unordered List with Bullets

+
    +
  • • Success item
  • +
  • • Danger item
  • +
  • • Warning item
  • +
  • • Accent item
  • +
+
+
+ + +
+

Spinners

+ +
+
+ + Default spinner +
+
+ + Large spinner +
+
+
+ + +
+

Utility Classes

+ +
+

Text Colors

+

text-muted - Secondary text

+

text-subtle - Tertiary text

+

text-accent - Accent color

+

text-success - Success state

+

text-danger - Danger state

+

text-warning - Warning state

+
+ +
+

Text Alignment

+

text-left

+

text-center

+

text-right

+
+ +
+

Flexbox

+
+ justify-between + items-center +
+
+ flex-col + with gap-2 +
+
+ +
+

Spacing

+
p-4 (padding)
+
p-2 with mb-4 (margin-bottom)
+
+
gap-4
+
between
+
items
+
+
+
+ + +
+

Color Palette

+

Colors automatically adapt to light/dark mode via CSS custom properties.

+ +
+
+
bg
+ --wisp-bg
+ +
+
+
bg-alt
+ --wisp-bg-alt
+ +
+
+
text
+ --wisp-text
+ +
+
+
accent
+ --wisp-accent
+ +
+
+
success
+ --wisp-success
+ +
+
+
danger
+ --wisp-danger
+ +
+
+
warning
+ --wisp-warning
+ +
+
+
info
+ --wisp-info
+ +
+
+
+
+ + + + + + diff --git a/packages/@wisp/css/index.ts b/packages/@wisp/css/index.ts new file mode 100644 index 0000000..e8b94ba --- /dev/null +++ b/packages/@wisp/css/index.ts @@ -0,0 +1,6 @@ +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { dirname, join } from 'node:path' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +export const css = readFileSync(join(__dirname, 'wisp.css'), 'utf-8') diff --git a/packages/@wisp/css/package.json b/packages/@wisp/css/package.json new file mode 100644 index 0000000..f259afd --- /dev/null +++ b/packages/@wisp/css/package.json @@ -0,0 +1,25 @@ +{ + "name": "@wisp/css", + "version": "1.0.0", + "description": "A minimal, monospace-first CSS stylesheet with automatic light/dark mode", + "main": "index.ts", + "types": "index.ts", + "style": "wisp.css", + "files": [ + "index.ts", + "wisp.css" + ], + "keywords": [ + "css", + "stylesheet", + "monospace", + "dark-mode", + "minimal" + ], + "author": "nekomimi.pet", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/nekomimi-pet/wisp.place-monorepo" + } +} diff --git a/packages/@wisp/css/wisp.css b/packages/@wisp/css/wisp.css new file mode 100644 index 0000000..455342f --- /dev/null +++ b/packages/@wisp/css/wisp.css @@ -0,0 +1,896 @@ +/** + * wisp.css - A minimal, monospace-first CSS framework + * + * Usage: + * + * + * Requires JetBrains Mono font: + * + * + * + * + * Features: + * - Light/dark mode via prefers-color-scheme + * - CSS custom properties for easy theming + * - Monospace typography with JetBrains Mono + * - Minimal component styles (cards, buttons, alerts) + * - Utility classes for spacing and layout + * + * License: MIT + */ + +/* ========================================================================== + CSS Custom Properties (Theme) + ========================================================================== */ + +:root { + /* Background colors */ + --wisp-bg: oklch(0.92 0.012 35); + --wisp-bg-alt: oklch(0.88 0.01 35); + + /* Text colors */ + --wisp-text: oklch(0.15 0.015 30); + --wisp-text-muted: oklch(0.35 0.02 30); + --wisp-text-subtle: oklch(0.50 0.02 30); + + /* Border colors */ + --wisp-border: oklch(0.30 0.025 35); + --wisp-border-light: oklch(0.65 0.02 30); + + /* Accent colors */ + --wisp-accent: oklch(0.65 0.18 345); + --wisp-accent-muted: oklch(0.75 0.14 345); + + /* CTA/Button colors */ + --wisp-cta-bg: oklch(0.30 0.025 35); + --wisp-cta-text: oklch(0.96 0.008 35); + + /* Semantic colors */ + --wisp-code-bg: oklch(0.95 0.008 35); + --wisp-success: oklch(0.65 0.20 145); + --wisp-danger: oklch(0.60 0.20 25); + --wisp-warning: oklch(0.70 0.15 85); + --wisp-info: oklch(0.60 0.15 240); + + /* Layout */ + --wisp-max-width: 900px; + --wisp-header-max-width: 1100px; + + /* Typography */ + --wisp-font-family: "JetBrains Mono", ui-monospace, monospace; + --wisp-line-height: 1.6; + + /* Spacing scale */ + --wisp-space-1: 0.25rem; + --wisp-space-2: 0.5rem; + --wisp-space-3: 1rem; + --wisp-space-4: 1.5rem; + --wisp-space-5: 2rem; + --wisp-space-6: 3rem; + + /* Border radius */ + --wisp-radius-sm: 0.25rem; + --wisp-radius: 0.375rem; + --wisp-radius-lg: 0.5rem; + + /* Backward-compatible aliases (unprefixed) */ + --bg: var(--wisp-bg); + --bg-alt: var(--wisp-bg-alt); + --text: var(--wisp-text); + --text-muted: var(--wisp-text-muted); + --text-subtle: var(--wisp-text-subtle); + --border: var(--wisp-border); + --border-light: var(--wisp-border-light); + --accent: var(--wisp-accent); + --accent-muted: var(--wisp-accent-muted); + --cta-bg: var(--wisp-cta-bg); + --cta-text: var(--wisp-cta-text); + --code-bg: var(--wisp-code-bg); + --success: var(--wisp-success); + --red: var(--wisp-danger); + --yellow: var(--wisp-warning); + --blue: var(--wisp-info); +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --wisp-bg: oklch(0.23 0.015 285); + --wisp-bg-alt: oklch(0.20 0.015 285); + --wisp-text: oklch(0.90 0.005 285); + --wisp-text-muted: oklch(0.72 0.01 285); + --wisp-text-subtle: oklch(0.55 0.01 285); + --wisp-border: oklch(0.90 0.005 285); + --wisp-border-light: oklch(0.38 0.02 285); + --wisp-accent: oklch(0.85 0.08 5); + --wisp-accent-muted: oklch(0.70 0.10 295); + --wisp-cta-bg: oklch(0.70 0.10 295); + --wisp-cta-text: oklch(0.23 0.015 285); + --wisp-code-bg: oklch(0.28 0.015 285); + --wisp-success: oklch(0.65 0.20 145); + --wisp-danger: oklch(0.65 0.20 25); + --wisp-warning: oklch(0.75 0.15 85); + --wisp-info: oklch(0.65 0.18 240); + + /* Backward-compatible aliases */ + --bg: var(--wisp-bg); + --bg-alt: var(--wisp-bg-alt); + --text: var(--wisp-text); + --text-muted: var(--wisp-text-muted); + --text-subtle: var(--wisp-text-subtle); + --border: var(--wisp-border); + --border-light: var(--wisp-border-light); + --accent: var(--wisp-accent); + --accent-muted: var(--wisp-accent-muted); + --cta-bg: var(--wisp-cta-bg); + --cta-text: var(--wisp-cta-text); + --code-bg: var(--wisp-code-bg); + --success: var(--wisp-success); + --red: var(--wisp-danger); + --yellow: var(--wisp-warning); + --blue: var(--wisp-info); + } +} + +/* Manual dark theme override */ +:root[data-theme="dark"] { + --wisp-bg: oklch(0.23 0.015 285); + --wisp-bg-alt: oklch(0.20 0.015 285); + --wisp-text: oklch(0.90 0.005 285); + --wisp-text-muted: oklch(0.72 0.01 285); + --wisp-text-subtle: oklch(0.55 0.01 285); + --wisp-border: oklch(0.90 0.005 285); + --wisp-border-light: oklch(0.38 0.02 285); + --wisp-accent: oklch(0.85 0.08 5); + --wisp-accent-muted: oklch(0.70 0.10 295); + --wisp-cta-bg: oklch(0.70 0.10 295); + --wisp-cta-text: oklch(0.23 0.015 285); + --wisp-code-bg: oklch(0.28 0.015 285); + --wisp-success: oklch(0.65 0.20 145); + --wisp-danger: oklch(0.65 0.20 25); + --wisp-warning: oklch(0.75 0.15 85); + --wisp-info: oklch(0.65 0.18 240); + + /* Backward-compatible aliases */ + --bg: var(--wisp-bg); + --bg-alt: var(--wisp-bg-alt); + --text: var(--wisp-text); + --text-muted: var(--wisp-text-muted); + --text-subtle: var(--wisp-text-subtle); + --border: var(--wisp-border); + --border-light: var(--wisp-border-light); + --accent: var(--wisp-accent); + --accent-muted: var(--wisp-accent-muted); + --cta-bg: var(--wisp-cta-bg); + --cta-text: var(--wisp-cta-text); + --code-bg: var(--wisp-code-bg); + --success: var(--wisp-success); + --red: var(--wisp-danger); + --yellow: var(--wisp-warning); + --blue: var(--wisp-info); +} + +/* ========================================================================== + Base / Reset + ========================================================================== */ + +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + -webkit-text-size-adjust: 100%; +} + +body { + font-family: var(--wisp-font-family); + background: var(--wisp-bg); + color: var(--wisp-text); + min-height: 100vh; + display: flex; + flex-direction: column; + line-height: var(--wisp-line-height); +} + +/* ========================================================================== + Typography + ========================================================================== */ + +h1, h2, h3, h4, h5, h6 { + color: var(--wisp-text); + font-weight: 700; + line-height: 1.2; +} + +h1 { + font-size: clamp(2rem, 5vw, 2.5rem); + margin-bottom: var(--wisp-space-4); + letter-spacing: -0.03em; +} + +h2 { + font-size: 1.875rem; + margin-bottom: var(--wisp-space-4); + letter-spacing: -0.02em; +} + +h3 { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: var(--wisp-space-3); +} + +h4 { + font-size: 1.25rem; + font-weight: 600; + margin-bottom: var(--wisp-space-2); +} + +p { + color: var(--wisp-text-muted); + margin-bottom: var(--wisp-space-3); + line-height: 1.7; +} + +strong, b { + color: var(--wisp-text); + font-weight: 600; +} + +small { + font-size: 0.875rem; + color: var(--wisp-text-muted); +} + +a { + color: var(--wisp-accent); + text-decoration: none; + transition: opacity 0.15s; +} + +a:hover { + opacity: 0.8; +} + +code { + font-family: var(--wisp-font-family); + background: var(--wisp-code-bg); + padding: 0.125rem 0.375rem; + border-radius: var(--wisp-radius-sm); + font-size: 0.875em; +} + +pre { + background: var(--wisp-code-bg); + padding: var(--wisp-space-3); + border-radius: var(--wisp-radius); + overflow-x: auto; + margin-bottom: var(--wisp-space-3); +} + +pre code { + background: none; + padding: 0; +} + +/* ========================================================================== + Layout + ========================================================================== */ + +.container { + max-width: var(--wisp-max-width); + margin: 0 auto; + padding: var(--wisp-space-6) var(--wisp-space-3); +} + +.container-wide { + max-width: var(--wisp-header-max-width); + margin: 0 auto; + padding: var(--wisp-space-6) var(--wisp-space-3); +} + +/* ========================================================================== + Header + ========================================================================== */ + +header { + position: sticky; + top: 0; + background: var(--wisp-bg); + border-bottom: 1px solid var(--wisp-border-light); + padding: var(--wisp-space-3) var(--wisp-space-5); + z-index: 100; +} + +.header-inner { + max-width: var(--wisp-header-max-width); + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + font-weight: 700; + font-size: 1.125rem; + text-decoration: none; + color: var(--wisp-text); + letter-spacing: -0.02em; + display: flex; + align-items: center; + gap: var(--wisp-space-2); +} + +.logo:hover { + opacity: 1; +} + +.logo img { + width: 2rem; + height: 2rem; +} + +nav { + display: flex; + gap: var(--wisp-space-5); + align-items: center; +} + +nav a { + color: var(--wisp-text-muted); + font-size: 1rem; + transition: color 0.15s; +} + +nav a:hover { + color: var(--wisp-text); + opacity: 1; +} + +/* ========================================================================== + Buttons + ========================================================================== */ + +button, .btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--wisp-space-2); + padding: var(--wisp-space-2) var(--wisp-space-3); + background: var(--wisp-cta-bg); + color: var(--wisp-cta-text); + border: none; + border-radius: var(--wisp-radius); + font-weight: 600; + font-size: 0.875rem; + font-family: var(--wisp-font-family); + cursor: pointer; + transition: opacity 0.15s; + text-decoration: none; +} + +button:hover, .btn:hover { + opacity: 0.9; +} + +button:disabled, .btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +button.outline, .btn.outline { + background: transparent; + color: var(--wisp-text); + border: 1px solid var(--wisp-border-light); +} + +button.outline:hover, .btn.outline:hover { + background: var(--wisp-bg-alt); + opacity: 1; +} + +.btn-lg { + padding: var(--wisp-space-3) var(--wisp-space-5); + font-size: 1rem; +} + +.cta-primary { + background: var(--wisp-cta-bg); + color: var(--wisp-cta-text); + padding: var(--wisp-space-3) var(--wisp-space-5); + border: 2px solid var(--wisp-border); + font-weight: 600; + font-size: 1rem; + text-decoration: none; + text-transform: uppercase; + letter-spacing: 0.05em; + transition: all 0.15s; +} + +.cta-primary:hover { + background: var(--wisp-bg); + color: var(--wisp-text); + opacity: 1; +} + +.cta-secondary { + background: transparent; + color: var(--wisp-text); + padding: var(--wisp-space-3) var(--wisp-space-5); + border: 2px solid var(--wisp-border); + font-weight: 600; + font-size: 1rem; + text-decoration: none; + text-transform: uppercase; + letter-spacing: 0.05em; + transition: all 0.15s; +} + +.cta-secondary:hover { + background: var(--wisp-cta-bg); + color: var(--wisp-cta-text); + opacity: 1; +} + +.nav-cta { + background: var(--wisp-cta-bg); + color: var(--wisp-cta-text); + padding: var(--wisp-space-2) var(--wisp-space-3); + border: 1px solid var(--wisp-border); + font-weight: 600; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; + transition: all 0.15s; +} + +.nav-cta:hover { + background: var(--wisp-bg); + color: var(--wisp-text); + opacity: 1; +} + +/* ========================================================================== + Forms + ========================================================================== */ + +label { + display: block; + margin-bottom: var(--wisp-space-2); + font-size: 0.875rem; + font-weight: 500; + color: var(--wisp-text); +} + +input, textarea, select { + width: 100%; + padding: var(--wisp-space-2) 0.75rem; + background: var(--wisp-bg); + border: 1px solid var(--wisp-border-light); + border-radius: var(--wisp-radius); + color: var(--wisp-text); + font-size: 0.875rem; + font-family: var(--wisp-font-family); +} + +input:focus, textarea:focus, select:focus { + outline: 2px solid var(--wisp-accent); + outline-offset: 2px; +} + +input::placeholder, textarea::placeholder { + color: var(--wisp-text-subtle); +} + +/* ========================================================================== + Cards + ========================================================================== */ + +.card { + background: var(--wisp-bg-alt); + border: 1px solid var(--wisp-border-light); + border-radius: var(--wisp-radius-lg); + padding: var(--wisp-space-4); + margin-bottom: var(--wisp-space-3); +} + +.card.success { + background: color-mix(in oklch, var(--wisp-success) 5%, var(--wisp-bg-alt)); + border-color: color-mix(in oklch, var(--wisp-success) 20%, transparent); +} + +.card.danger { + background: color-mix(in oklch, var(--wisp-danger) 5%, var(--wisp-bg-alt)); + border-color: color-mix(in oklch, var(--wisp-danger) 30%, transparent); + border-width: 2px; +} + +.card.warning { + background: color-mix(in oklch, var(--wisp-warning) 5%, var(--wisp-bg-alt)); + border-color: color-mix(in oklch, var(--wisp-warning) 20%, transparent); +} + +.card.info { + background: color-mix(in oklch, var(--wisp-info) 5%, var(--wisp-bg-alt)); + border-color: color-mix(in oklch, var(--wisp-info) 20%, transparent); +} + +.card.thick { + border-width: 2px; +} + +.card-flex { + display: flex; + gap: var(--wisp-space-3); + align-items: start; +} + +.card-icon { + flex-shrink: 0; + width: 2rem; + height: 2rem; +} + +/* ========================================================================== + Alerts + ========================================================================== */ + +.alert { + padding: var(--wisp-space-3); + border-radius: var(--wisp-radius-lg); + margin-bottom: var(--wisp-space-3); +} + +.alert-success { + background: var(--wisp-code-bg); + border: 1px solid var(--wisp-border-light); + color: var(--wisp-success); +} + +.alert-info { + background: var(--wisp-code-bg); + color: var(--wisp-text-muted); +} + +.alert-warning { + background: var(--wisp-code-bg); + border-left: 4px solid var(--wisp-warning); +} + +.alert-danger { + background: var(--wisp-code-bg); + border-left: 4px solid var(--wisp-danger); +} + +/* ========================================================================== + Lists + ========================================================================== */ + +ul, ol { + list-style: none; + margin: var(--wisp-space-3) 0; +} + +ol { + counter-reset: item; +} + +ol li { + counter-increment: item; +} + +ol li::before { + content: counter(item) "."; + font-weight: 700; + color: var(--wisp-accent); + margin-right: 0.75rem; +} + +.bullet { + flex-shrink: 0; + margin-top: 0.25rem; +} + +.bullet.red, .bullet.danger { color: var(--wisp-danger); } +.bullet.green, .bullet.success { color: var(--wisp-success); } +.bullet.yellow, .bullet.warning { color: var(--wisp-warning); } +.bullet.accent { color: var(--wisp-accent); } + +/* ========================================================================== + Footer + ========================================================================== */ + +footer { + padding: var(--wisp-space-6) var(--wisp-space-5) var(--wisp-space-5); + border-top: 1px solid var(--wisp-border-light); + margin-top: auto; +} + +.footer-inner { + max-width: var(--wisp-header-max-width); + margin: 0 auto; + display: grid; + grid-template-columns: 2fr repeat(3, 1fr); + gap: var(--wisp-space-6); +} + +.footer-brand p:first-child { + font-weight: 700; + margin-bottom: 0.75rem; +} + +.footer-brand p:last-child { + color: var(--wisp-text-muted); + line-height: 1.6; +} + +.footer-col h4 { + font-size: 0.875rem; + color: var(--wisp-text-subtle); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: var(--wisp-space-3); +} + +.footer-col ul { + margin: 0; +} + +.footer-col li { + margin-bottom: var(--wisp-space-2); +} + +.footer-col a { + color: var(--wisp-text-muted); + transition: color 0.15s; +} + +.footer-col a:hover { + color: var(--wisp-text); + opacity: 1; +} + +.footer-bottom { + max-width: var(--wisp-header-max-width); + margin: var(--wisp-space-6) auto 0; + padding-top: var(--wisp-space-5); + border-top: 1px solid var(--wisp-border-light); + display: flex; + justify-content: space-between; + align-items: center; +} + +.footer-bottom p { + color: var(--wisp-text-subtle); + font-size: 0.875rem; +} + +.footer-bottom nav { + gap: var(--wisp-space-4); +} + +.footer-bottom a { + color: var(--wisp-text-subtle); + font-size: 0.875rem; +} + +.footer-bottom a:hover { + color: var(--wisp-text); + opacity: 1; +} + +footer.simple { + background: var(--wisp-bg-alt); + padding: var(--wisp-space-5) var(--wisp-space-3); + text-align: center; + font-size: 0.875rem; + color: var(--wisp-text-muted); +} + +footer.simple p { + margin-bottom: var(--wisp-space-2); +} + +/* ========================================================================== + Spinner + ========================================================================== */ + +.spinner { + display: inline-block; + width: 1rem; + height: 1rem; + border: 2px solid var(--wisp-border-light); + border-top-color: var(--wisp-accent); + border-radius: 50%; + animation: wisp-spin 0.6s linear infinite; +} + +.spinner-lg { + width: 2rem; + height: 2rem; + border-width: 3px; +} + +@keyframes wisp-spin { + to { transform: rotate(360deg); } +} + +/* ========================================================================== + Utility Classes + ========================================================================== */ + +/* Display */ +.hidden { display: none; } +.block { display: block; } +.inline { display: inline; } +.inline-block { display: inline-block; } +.flex { display: flex; } +.inline-flex { display: inline-flex; } +.grid { display: grid; } + +/* Flexbox */ +.flex-col { flex-direction: column; } +.flex-wrap { flex-wrap: wrap; } +.items-start { align-items: flex-start; } +.items-center { align-items: center; } +.items-end { align-items: flex-end; } +.justify-start { justify-content: flex-start; } +.justify-center { justify-content: center; } +.justify-end { justify-content: flex-end; } +.justify-between { justify-content: space-between; } + +/* Gap */ +.gap-1 { gap: var(--wisp-space-1); } +.gap-2 { gap: var(--wisp-space-2); } +.gap-3 { gap: var(--wisp-space-3); } +.gap-4 { gap: var(--wisp-space-4); } +.gap-5 { gap: var(--wisp-space-5); } +.gap-6 { gap: var(--wisp-space-6); } + +/* Margin */ +.m-0 { margin: 0; } +.m-1 { margin: var(--wisp-space-1); } +.m-2 { margin: var(--wisp-space-2); } +.m-3 { margin: var(--wisp-space-3); } +.m-4 { margin: var(--wisp-space-4); } + +.mt-0 { margin-top: 0; } +.mt-1 { margin-top: var(--wisp-space-1); } +.mt-2 { margin-top: var(--wisp-space-2); } +.mt-3 { margin-top: var(--wisp-space-3); } +.mt-4 { margin-top: var(--wisp-space-4); } +.mt-5 { margin-top: var(--wisp-space-5); } +.mt-6 { margin-top: var(--wisp-space-6); } + +.mb-0 { margin-bottom: 0; } +.mb-1 { margin-bottom: var(--wisp-space-1); } +.mb-2 { margin-bottom: var(--wisp-space-2); } +.mb-3 { margin-bottom: var(--wisp-space-3); } +.mb-4 { margin-bottom: var(--wisp-space-4); } +.mb-5 { margin-bottom: var(--wisp-space-5); } +.mb-6 { margin-bottom: var(--wisp-space-6); } + +.ml-0 { margin-left: 0; } +.ml-1 { margin-left: var(--wisp-space-1); } +.ml-2 { margin-left: var(--wisp-space-2); } +.ml-3 { margin-left: var(--wisp-space-3); } +.ml-4 { margin-left: var(--wisp-space-4); } + +.mr-0 { margin-right: 0; } +.mr-1 { margin-right: var(--wisp-space-1); } +.mr-2 { margin-right: var(--wisp-space-2); } +.mr-3 { margin-right: var(--wisp-space-3); } +.mr-4 { margin-right: var(--wisp-space-4); } + +/* Padding */ +.p-0 { padding: 0; } +.p-1 { padding: var(--wisp-space-1); } +.p-2 { padding: var(--wisp-space-2); } +.p-3 { padding: var(--wisp-space-3); } +.p-4 { padding: var(--wisp-space-4); } +.p-5 { padding: var(--wisp-space-5); } +.p-6 { padding: var(--wisp-space-6); } + +.px-1 { padding-left: var(--wisp-space-1); padding-right: var(--wisp-space-1); } +.px-2 { padding-left: var(--wisp-space-2); padding-right: var(--wisp-space-2); } +.px-3 { padding-left: var(--wisp-space-3); padding-right: var(--wisp-space-3); } +.px-4 { padding-left: var(--wisp-space-4); padding-right: var(--wisp-space-4); } + +.py-1 { padding-top: var(--wisp-space-1); padding-bottom: var(--wisp-space-1); } +.py-2 { padding-top: var(--wisp-space-2); padding-bottom: var(--wisp-space-2); } +.py-3 { padding-top: var(--wisp-space-3); padding-bottom: var(--wisp-space-3); } +.py-4 { padding-top: var(--wisp-space-4); padding-bottom: var(--wisp-space-4); } + +/* Width */ +.w-full { width: 100%; } +.w-auto { width: auto; } +.max-w-sm { max-width: 24rem; } +.max-w-md { max-width: 28rem; } +.max-w-lg { max-width: 32rem; } +.max-w-xl { max-width: 36rem; } + +/* Text */ +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } + +.text-sm { font-size: 0.875rem; } +.text-base { font-size: 1rem; } +.text-lg { font-size: 1.125rem; } +.text-xl { font-size: 1.25rem; } + +.font-normal { font-weight: 400; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.font-bold { font-weight: 700; } + +.text-muted { color: var(--wisp-text-muted); } +.text-subtle { color: var(--wisp-text-subtle); } +.text-accent { color: var(--wisp-accent); } +.text-success { color: var(--wisp-success); } +.text-danger { color: var(--wisp-danger); } +.text-warning { color: var(--wisp-warning); } +.text-info { color: var(--wisp-info); } + +/* Background */ +.bg-alt { background: var(--wisp-bg-alt); } +.bg-code { background: var(--wisp-code-bg); } + +/* Border */ +.border { border: 1px solid var(--wisp-border-light); } +.border-2 { border: 2px solid var(--wisp-border-light); } +.rounded { border-radius: var(--wisp-radius); } +.rounded-lg { border-radius: var(--wisp-radius-lg); } + +/* Other */ +.cursor-pointer { cursor: pointer; } +.select-none { user-select: none; } +.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +/* ========================================================================== + Responsive + ========================================================================== */ + +@media (max-width: 768px) { + nav a:not(.nav-cta) { + display: none; + } + + .footer-inner { + grid-template-columns: 1fr 1fr; + } + + .footer-brand { + grid-column: span 2; + } + + .footer-bottom { + flex-direction: column; + gap: var(--wisp-space-3); + text-align: center; + } + + h1 { font-size: 2rem; } + h2 { font-size: 1.5rem; } + h3 { font-size: 1.25rem; } + + .hide-mobile { display: none; } +} + +@media (max-width: 480px) { + .footer-inner { + grid-template-columns: 1fr; + } + + .footer-brand { + grid-column: span 1; + } + + .hide-sm { display: none; } +} + +@media (min-width: 769px) { + .hide-desktop { display: none; } +} diff --git a/packages/@wisp/observability/src/middleware/elysia.ts b/packages/@wisp/observability/src/middleware/elysia.ts index 6594809..fdc193a 100644 --- a/packages/@wisp/observability/src/middleware/elysia.ts +++ b/packages/@wisp/observability/src/middleware/elysia.ts @@ -49,8 +49,9 @@ export function observabilityMiddleware(service: string) { service ) - // Don't log 404 errors - if (statusCode !== 404) { + // Don't log 404 errors or expected auth failures + const isAuthError = error?.message === 'Authentication required' + if (statusCode !== 404 && !isAuthError) { logCollector.error( `Request failed: ${request.method} ${url.pathname}`, service, diff --git a/scripts/codegen.sh b/scripts/codegen.sh index 6aaab3b..4a8d327 100755 --- a/scripts/codegen.sh +++ b/scripts/codegen.sh @@ -7,22 +7,11 @@ ROOT_DIR="$(dirname "$SCRIPT_DIR")" # Parse arguments AUTO_ACCEPT="" if [[ "$1" == "-y" || "$1" == "--yes" ]]; then - AUTO_ACCEPT="yes |" + AUTO_ACCEPT="yes |" fi echo "=== Generating TypeScript lexicons ===" cd "$ROOT_DIR/packages/@wisp/lexicons" eval "$AUTO_ACCEPT npm run codegen" -echo "=== Generating Rust lexicons ===" -echo "Installing jacquard-lexgen..." -cargo install jacquard-lexgen --version 0.9.5 2>/dev/null || true -echo "Running jacquard-codegen..." -echo " Input: $ROOT_DIR/lexicons" -echo " Output: $ROOT_DIR/cli/crates/lexicons/src" -jacquard-codegen -i "$ROOT_DIR/lexicons" -o "$ROOT_DIR/cli/crates/lexicons/src" - -# Add extern crate alloc for the macro to work -sed -i '' '1s/^/extern crate alloc;\n\n/' "$ROOT_DIR/cli/crates/lexicons/src/lib.rs" - echo "=== Done ==="