Compare commits

...

3 commits

Author SHA1 Message Date
6fe263444a Extract authenticated coulomb.social design language via designlang
Session probe lands on /vw_pages (Research). Use main designlang extract
with --cookie-file — pack ignores auth. 39 artifacts under
docs/design-extract; tolerate designlang late summary crash when cores exist.
2026-08-09 01:31:00 +02:00
319dd00465 Add designlang public-shell pack; tighten auth capture checks
Ran authenticated extract against coulomb.social; session metadata
matched anonymous Bubble defaults, so treat docs/design-extract as a
public shell baseline (PROVENANCE.md). Capture script now refuses to
overwrite storage-state.json with thin anonymous sessions.
2026-08-09 01:22:15 +02:00
0bf740017e Add authenticated designlang extract path for coulomb.social
Interactive Playwright session capture (XDG storageState, mode 0600)
plus a designlang pack wrapper that never prints secrets. Document the
intended OpenBao lane tenants/binky/coulomb-social/bubble-member for
later password custody; interactive capture is the default for Bubble.
2026-08-09 01:15:06 +02:00
57 changed files with 6015 additions and 0 deletions

12
.gitignore vendored
View file

@ -3,3 +3,15 @@
.claude/*
!.claude/rules/
!.claude/rules/*.md
# Auth / secrets — never commit (see docs/design-extract-auth.md)
.auth/
**/storage-state.json
**/*cookie*.txt
scripts/node_modules/
scripts/package-lock.json
# Local env overrides
.env
.env.*
!.env.example

137
docs/design-extract-auth.md Normal file
View file

@ -0,0 +1,137 @@
# Design extract — authenticated access to coulomb.social
designlang can crawl authenticated pages via Playwright cookies / storage state:
```text
npx designlang … --cookie-file <path>
```
Supported cookie file formats: JSON, Playwright `storageState`, Netscape `cookies.txt`.
## Threat model
| Artefact | Sensitivity | Where it lives |
|----------|-------------|----------------|
| Bubble member password | **High** — full account | OpenBao only (never Git/chat) |
| Browser `storageState` | **High** — session impersonation | Local XDG path, mode `0600`, gitignored |
| designlang output (tokens, screenshots) | Product UI — ok in repo after review | `docs/design-extract/` |
Do **not** paste passwords, cookies, or storage-state JSON into chat, workplans, or commits.
## Preferred flow (interactive session)
Bubble login may involve MFA, email codes, or CAPTCHA. Prefer a **human-attended** headed browser once, then reuse session state for extracts.
### 1. Capture session as your user
```bash
cd ~/coulomb-social
./scripts/capture-auth-state.sh
```
1. A Chromium window opens on `https://coulomb.social`.
2. Log in as **your** member account and navigate until you see the authenticated UI you care about.
3. Return to the terminal and press **Enter**.
4. Session is written to:
```text
~/.config/coulomb-social/auth/storage-state.json # mode 0600
```
### 2. Run the extract
```bash
./scripts/run-design-extract.sh
# or with extra flags:
./scripts/run-design-extract.sh --depth 5 --full
```
Default output: `docs/design-extract/` (safe to commit after review; no secrets).
### 3. Refresh when the session expires
Re-run `./scripts/capture-auth-state.sh`. There is no long-lived cookie vault unless you complete the OpenBao lane below.
## OpenBao custody (password for re-login / automation)
Use when you need **unattended** re-auth (CI, agent loops) without a headed browser.
| Field | Value |
|-------|--------|
| Mount | `tenants` |
| Path | `tenants/binky/coulomb-social/bubble-member` |
| Fields | `EMAIL`, `PASSWORD` |
| Risk | `high` (member account) |
| Tenant | `binky` (Binky Hedgehog = client #1) |
| Workload | `coulomb-social` |
| Bundle | `bubble-member` |
This follows `ops-warden/wiki/playbooks/tenant-secret-onboarding.md`. **Status today:** path and fields are **declared** for this repo; the CCR, policy, OIDC role, and catalog entry are **not yet applied**. Until then, use interactive capture only.
### Founder provision (after CCR is applied)
```bash
# values only in mode-0600 files — never argv or chat
printf '%s' 'you@example.com' > /tmp/cs-email
printf '%s' '…' > /tmp/cs-pass
chmod 600 /tmp/cs-email /tmp/cs-pass
bao login -method=oidc -path=netkingdom # admin / net-kingdom-admins as required
bao kv put tenants/binky/coulomb-social/bubble-member \
EMAIL=@/tmp/cs-email \
PASSWORD=@/tmp/cs-pass
shred -u /tmp/cs-email /tmp/cs-pass
```
### Worker fetch (never print)
```bash
bao login -method=oidc -path=netkingdom role=<lane>-workload-kv-read
bao kv get -field=EMAIL tenants/binky/coulomb-social/bubble-member > ~/.config/coulomb-social/auth/email
bao kv get -field=PASSWORD tenants/binky/coulomb-social/bubble-member > ~/.config/coulomb-social/auth/password
chmod 600 ~/.config/coulomb-social/auth/email ~/.config/coulomb-social/auth/password
```
Automated login-from-password is optional (`./scripts/capture-auth-state.sh --from-password-files`) and only works if bubble accepts simple email/password without interactive MFA.
## Routing (agents)
```bash
warden route find "coulomb.social bubble member login"
```
Until a catalog id exists, treat this as:
| Need | Owner |
|------|--------|
| Member password | OpenBao `tenants/binky/…` (founder provision) |
| Interactive session | You — `scripts/capture-auth-state.sh` |
| SSH / tunnels | ops-warden / ops-bridge (unrelated) |
**Anti-patterns:** message ops-warden for the password; commit `storage-state.json`; put `EMAIL`/`PASSWORD` in `.env` in the repo.
## designlang auth flags (reference)
**Important:** use the **main** `designlang <url>` command, not `designlang pack`.
The `pack` subcommand ignores `--cookie-file` and crawls anonymously.
```bash
# Prefer the wrapper (probes session, applies cookies, correct out dir):
./scripts/run-design-extract.sh
# Or manually:
npx designlang https://coulomb.social \
--cookie-file ~/.config/coulomb-social/auth/storage-state.json \
-o docs/design-extract \
-n coulomb-social \
--wait 5000 \
--depth 2 \
--interactions
```
Bubble reuses the same `coulomb_*` cookie **names** for guests and members.
A real member session is verified by landing on an app route (e.g. `/vw_pages`)
after load, not by cookie count.
Chrome extension path (optional): log in in desktop Chrome, use designlangs extension for a one-click handoff if you prefer not to use Playwright capture.

View file

@ -0,0 +1,26 @@
# Design extract provenance
| Field | Value |
|-------|--------|
| Source | https://coulomb.social |
| Tool | designlang main extract (not `pack`) |
| Captured | 2026-08-09 |
| Auth | Playwright storageState via `--cookie-file` |
| Session probe | https://coulomb.social/vw_pages (Research \| Coulomb) |
| Pages | 7 analyzed · 3445 elements |
| Primary | `#d08728` |
| designlang exit | 1 (late summary crash after writing core files) |
## Important
`designlang pack` **does not apply cookies**. Always use
`./scripts/run-design-extract.sh` (main extract command).
## Key artifacts
- `coulomb-social-design-language.md` — LLM-oriented full description
- `coulomb-social-DESIGN.md` — agent-native DESIGN.md
- `coulomb-social-design-tokens.json` — DTCG tokens
- `coulomb-social-variables.css` / `coulomb-social-tailwind.config.js`
- `coulomb-social-preview.html` — visual preview
- `coulomb-social-voice.json` — UI chrome (Material icon buttons on Research view)

View file

@ -0,0 +1,115 @@
# You are building UI in the coulomb.social design system.
Source: https://coulomb.social
Extracted by designlang on 2026-08-08.
## Brand at a glance
- title Research | Coulomb
- page intent landing
- material flat
- design grade B
## Colour
- primary #d08728
- neutrals #000000 · #efefef · #616161 · #373737 · #171717 · #ffffff
## Typography
- families Helvetica · Arial · Times New Roman · Inter
- weights 400 · 500 · 600 · 700
- base size 16px
## Spacing
- scale 1px · 24px · 32px · 40px · 60px · 80px · 96px · 320px
## Radii
- scale 4px · 8px · 12px · 15px · 20px
## Motion
- durations 250ms
## Voice
- tone neutral
- pronoun third-person
- headings unknown
- CTA verbs content · favorite · format · filter · add · arrow
## Component anatomy
- button variants: [object Object] · slots: true · true · false
## Accessibility
- WCAG score 100% · failing pairs: 0
## Build rules
1. Use the colours above. **Never invent a new hex.** If you need a
shade between two existing colours, derive it via HSL adjustment
from the closest extracted colour and call out the derivation.
2. Use the extracted typography families. If you need a missing weight,
pick the nearest available weight from the list and note it.
3. Snap spacing values to the scale above. No off-scale paddings or
margins.
4. Snap border radii to the scale above.
5. Match the voice: same tone, same pronoun stance, same heading
style. Reuse the listed CTA verbs.
6. Aim for WCAG AA contrast minimum. When the brand colours fail,
prefer the foreground colour on the background colour rather than
mid-tone neutrals.
7. Reuse component anatomy when it exists — do not invent novel
structures for things the site already has.
## Available context files
designlang wrote these alongside this prompt. Reach for them when
you need ground truth:
- `<host>-design-tokens.json` — DTCG primitive · semantic · composite tokens
- `<host>-tailwind.config.js` — Tailwind v3 config
- `<host>-tailwind-v4.css` — Tailwind v4 `@theme` block
- `<host>-tokens.d.ts` — TypeScript literal-union types
- `<host>-variables.css` — bare CSS custom properties
- `<host>-reset.css` — brand-aware base styles
- `<host>-gradients.css``.grad-N` utility classes
- `<host>-anatomy.tsx` — typed React component scaffolds
- `<host>-shadcn-theme.css` — shadcn/ui theme
- `<host>-theme.js` — React / Vue / Svelte theme object
- `<host>-mcp.json` — MCP server payload (load via stdio)
- `<host>.brand.pdf` — print-ready 13-chapter brand book
When you reference the system in code, prefer importing from these
files over hard-coding values.
## Output expectations
When asked to "build a pricing page" or "make a card" or any UI:
- Produce a single self-contained component file in the appropriate
framework (React / Vue / Svelte — match what the user is using).
- Use Tailwind utility classes wired to the v4 `@theme` if Tailwind
is available; otherwise use the CSS custom properties from
`variables.css`.
- Write the headline copy using the brand voice; do not invent
generic Lorem.
- Annotate any choice where you had to bend the system, with a
one-line `// note:` comment explaining what and why.
## One-line install
```bash
npx designlang coulomb.social
```
Run this against any other URL to extract its system in the same
shape as the one above.
---
Generated by designlang. Re-extract by running `npx designlang coulomb.social`.

View file

@ -0,0 +1,109 @@
---
site: "Research | Coulomb"
url: "https://coulomb.social"
generated_at: "2026-08-08T23:30:22.030Z"
generator: "designlang@12.21.0"
intent: landing
material: flat
tokens:
colors:
primary: "#d08728"
background: "#ffffff"
foreground: "#000000"
typography:
sans: Helvetica
spacing:
base: 4
scale: "[1, 24, 32, 40, 60, 80, 96, 320]"
radii:
sm: 4
md: 8
lg: 15
xl: 20
shadows:
sm: "rgb(170, 170, 170) 2px 2px 4px 0px"
md: "rgba(23, 23, 23, 0.1) 2px 2px 8px 2px"
---
# Overview
A **landing** page (heuristic confidence 0.45), dressed in **flat** material.
> "Research | Coulomb"
The author writes in a **neutral** voice; headings tend to be **unknown** case and **tight**.
Reading order detected on the source: `—`.
Source: <https://coulomb.social>.
# Colors
| role | hex | usage |
|---|---|---|
| primary | `#d08728` | 8 |
| background | `#ffffff` | — |
| foreground | `#000000` | — |
**Neutrals:** `#000000` · `#efefef` · `#616161` · `#373737` · `#171717`
**Total unique colors detected:** 8.
# Typography
**Families**
- `Helvetica` · 1739 uses
- `Arial` · 761 uses
- `Times New Roman` · 450 uses
- `Inter` · 397 uses
# Layout
**Spacing base:** `4px` increments.
**Scale:** `1px` · `24px` · `32px` · `40px` · `60px` · `80px` · `96px` · `320px`
**Layout primitives:** 123 grid containers · 1256 flex containers.
# Elevation and Depth
**Shadow scale**
- `sm``rgb(170, 170, 170) 2px 2px 4px 0px`
- `md``rgba(23, 23, 23, 0.1) 2px 2px 8px 2px`
**Z-index layers:** 35
# Shapes
**Radius scale**
- `sm``4px`
- `md``8px`
- `lg``12px`
- `lg``15px`
- `xl``20px`
# Components
**Detected patterns:** `buttons` · `cards` · `inputs` · `links` · `modals`
**Anatomy**
| kind | variants | sizes | instances |
|---|---|---|---|
| button | — | — | 56 |
# Do's and Don'ts
**Do's**
- Use `content`, `favorite`, `format` as the primary verbs in CTAs — these dominate the source.
- Write headings in **unknown** case, **tight** length.
- Address the reader with the pronoun posture **third-person**.
- Stay inside the **flat** material — match shadow and radius habits.
**Don'ts**
- Don't 4 font families — consider limiting to 2 (heading + body).
- Don't 140 !important rules — prefer specificity over overrides.
- Don't 81% of css is unused — consider purging.
- Don't 6545 duplicate css declarations.
---
_Generated by [designlang](https://github.com/Manavarya09/design-extract) v12.21.0 from <https://coulomb.social>._
_Compatible with the DESIGN.md convention pioneered by [design-extractor.com](https://www.design-extractor.com) — extended with intent, material, voice, anatomy, and library detection._

View file

@ -0,0 +1,15 @@
// Auto-generated by designlang — component anatomy v2.
// Scaffolds. Wire into your token system; not a runtime library.
import * as React from 'react';
export interface ButtonProps {
variant?: 'default';
size?: 'md';
leadingIcon?: React.ReactNode;
children?: React.ReactNode;
}
export function Button({ variant = 'default', size = 'md', ...rest }: ButtonProps) {
return React.createElement('button', { 'data-variant': variant, 'data-size': size, ...rest });
}

View file

@ -0,0 +1,578 @@
# Design Language: Research | Coulomb
> Extracted from `https://coulomb.social` on August 9, 2026
> 3445 elements analyzed across 7 pages
This document describes the complete design language of the website. It is structured for AI/LLM consumption — use it to faithfully recreate the visual design in any framework.
## Color Palette
### Primary Colors
| Role | Hex | RGB | HSL | Usage Count |
|------|-----|-----|-----|-------------|
| Primary | `#d08728` | rgb(208, 135, 40) | hsl(34, 68%, 49%) | 8 |
### Neutral Colors
| Hex | HSL | Usage Count |
|-----|-----|-------------|
| `#000000` | hsl(0, 0%, 0%) | 5304 |
| `#efefef` | hsl(0, 0%, 94%) | 610 |
| `#616161` | hsl(0, 0%, 38%) | 556 |
| `#373737` | hsl(0, 0%, 22%) | 462 |
| `#171717` | hsl(0, 0%, 9%) | 401 |
| `#ffffff` | hsl(0, 0%, 100%) | 242 |
| `#e5e5e5` | hsl(0, 0%, 90%) | 217 |
### Background Colors
Used on large-area elements: `#ffffff`, `#e5e5e5`, `#171717`, `#f5f5f5`
### Text Colors
Text color palette: `#000000`, `#171717`, `#373737`, `#616161`, `#ffffff`
### Full Color Inventory
| Hex | Contexts | Count |
|-----|----------|-------|
| `#000000` | text, border, background | 5304 |
| `#efefef` | background | 610 |
| `#616161` | text, border | 556 |
| `#373737` | text, border | 462 |
| `#171717` | text, border, background | 401 |
| `#ffffff` | background, border, text | 242 |
| `#e5e5e5` | border, background | 217 |
| `#d08728` | background | 8 |
## Typography
### Font Families
- **Helvetica** — used for body (1739 elements)
- **Arial** — used for body (761 elements)
- **Times New Roman** — used for body (450 elements)
- **Inter** — used for body (397 elements)
### Type Scale
| Size (px) | Size (rem) | Weight | Line Height | Letter Spacing | Used On |
|-----------|------------|--------|-------------|----------------|---------|
| 50px | 3.125rem | 400 | 50px | normal | text |
| 42px | 2.625rem | 700 | normal | normal | input |
| 24px | 1.5rem | 600 | 36px | normal | a |
| 18px | 1.125rem | 600 | 18px | normal | div |
| 16px | 1rem | 400 | normal | normal | html, head, meta, title |
| 14px | 0.875rem | 500 | 19.6px | normal | div, a |
| 13.3333px | 0.8333rem | 400 | normal | normal | button, svg, use |
### Font Weights in Use
`400` (3048x), `500` (287x), `600` (102x), `700` (8x)
## Spacing
**Base unit:** 4px
| Token | Value | Rem |
|-------|-------|-----|
| spacing-1 | 1px | 0.0625rem |
| spacing-24 | 24px | 1.5rem |
| spacing-32 | 32px | 2rem |
| spacing-40 | 40px | 2.5rem |
| spacing-60 | 60px | 3.75rem |
| spacing-80 | 80px | 5rem |
| spacing-96 | 96px | 6rem |
| spacing-320 | 320px | 20rem |
## Border Radii
| Label | Value | Count |
|-------|-------|-------|
| sm | 4px | 119 |
| md | 8px | 122 |
| lg | 12px | 110 |
| lg | 15px | 6 |
| xl | 20px | 12 |
## Box Shadows
**sm** — blur: 4px
```css
box-shadow: rgb(170, 170, 170) 2px 2px 4px 0px;
```
**md** — blur: 8px
```css
box-shadow: rgba(23, 23, 23, 0.1) 2px 2px 8px 2px;
```
## CSS Custom Properties
### Colors
```css
--color_primary_default: rgba(2, 5, 211, 1);
--color_primary_default_rgb: 2, 5, 211;
--color_primary_contrast_default: rgba(255, 255, 255, 1);
--color_primary_contrast_default_rgb: 255, 255, 255;
--color_text_default: rgba(9, 23, 71, 1);
--color_text_default_rgb: 9, 23, 71;
--color_surface_default: rgba(255, 255, 255, 1);
--color_surface_default_rgb: 255, 255, 255;
--color_background_default: rgba(255, 255, 255, 0);
--color_background_default_rgb: 255, 255, 255;
--color_destructive_default: rgba(255, 0, 0, 1);
--color_destructive_default_rgb: 255, 0, 0;
--color_success_default: rgba(23, 219, 78, 1);
--color_success_default_rgb: 23, 219, 78;
--color_alert_default: rgba(250, 181, 21, 1);
--color_alert_default_rgb: 250, 181, 21;
--color_danger_default_rgb: rgba(176, 32, 12, 1);
--color_bTRUr_default_rgb: 208,135,40;
--color_bTOCs_default: rgba(55,55,55,1);
--color_bTHZK_default_rgb: 229,229,229;
--color_bTOCs_default_rgb: 55,55,55;
--color_bTINZ_default_rgb: 23,23,23;
--color_bTRUs_default_rgb: 227,230,235;
--color_bTINZ_default: rgba(23,23,23,0.36);
--color_bTRUr_default: rgba(208,135,40,0.1);
--color_bTHZK_default: rgba(229,229,229,1);
--color_bTHfJ_default_rgb: 97,97,97;
--color_bTMap_default: rgba(113,2,139,1);
--color_bTHfJ_default: rgba(97,97,97,1);
--color_bTMap_default_rgb: 113,2,139;
--color_bTRUs_default: rgba(227,230,235,1);
```
### Typography
```css
--font_default: 'Lato';
--font_bTRUx_default: "Inter";
--font_bTRUt_default: "Inter";
```
### Semantic
```css
success: [object Object];
warning: [object Object];
error: [object Object];
info: [object Object];
```
## Transitions & Animations
**Durations:** `0.25s`
### Common Transitions
```css
transition: all;
transition: background 0.25s;
```
### Keyframe Animations
**highlight-in**
```css
@keyframes highlight-in {
0% { background-color: rgb(200, 0, 0); }
}
```
**fa-spin**
```css
@keyframes fa-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(359deg); }
}
```
**opacity-70-25-0-13**
```css
@keyframes opacity-70-25-0-13 {
0% { opacity: 0.25; }
0.01% { opacity: 0.25; }
0.02% { opacity: 1; }
70.01% { opacity: 0.25; }
100% { opacity: 0.25; }
}
```
**opacity-70-25-1-13**
```css
@keyframes opacity-70-25-1-13 {
0% { opacity: 0.25; }
7.70231% { opacity: 0.25; }
7.71231% { opacity: 1; }
77.7023% { opacity: 0.25; }
100% { opacity: 0.25; }
}
```
**opacity-70-25-2-13**
```css
@keyframes opacity-70-25-2-13 {
0% { opacity: 0.25; }
15.3946% { opacity: 0.25; }
15.4046% { opacity: 1; }
85.3946% { opacity: 0.25; }
100% { opacity: 0.25; }
}
```
**opacity-70-25-3-13**
```css
@keyframes opacity-70-25-3-13 {
0% { opacity: 0.25; }
23.0869% { opacity: 0.25; }
23.0969% { opacity: 1; }
93.0869% { opacity: 0.25; }
100% { opacity: 0.25; }
}
```
**opacity-70-25-4-13**
```css
@keyframes opacity-70-25-4-13 {
0% { opacity: 0.258349; }
30.7792% { opacity: 0.25; }
30.7892% { opacity: 1; }
0.779231% { opacity: 0.25; }
100% { opacity: 0.258349; }
}
```
**opacity-70-25-5-13**
```css
@keyframes opacity-70-25-5-13 {
0% { opacity: 0.340766; }
38.4715% { opacity: 0.25; }
38.4815% { opacity: 1; }
8.47154% { opacity: 0.25; }
100% { opacity: 0.340766; }
}
```
**opacity-70-25-6-13**
```css
@keyframes opacity-70-25-6-13 {
0% { opacity: 0.423184; }
46.1638% { opacity: 0.25; }
46.1738% { opacity: 1; }
16.1638% { opacity: 0.25; }
100% { opacity: 0.423184; }
}
```
**opacity-70-25-7-13**
```css
@keyframes opacity-70-25-7-13 {
0% { opacity: 0.505602; }
53.8562% { opacity: 0.25; }
53.8662% { opacity: 1; }
23.8562% { opacity: 0.25; }
100% { opacity: 0.505602; }
}
```
## Component Patterns
Detected UI component patterns and their most common styles:
### Buttons (643 instances)
```css
.button {
background-color: rgb(239, 239, 239);
color: rgb(0, 0, 0);
font-size: 13.3333px;
font-weight: 400;
padding-top: 1px;
padding-right: 6px;
border-radius: 0px;
}
```
### Cards (36 instances)
```css
.card {
background-color: rgb(255, 255, 255);
border-radius: 4px;
box-shadow: rgb(170, 170, 170) 2px 2px 4px 0px;
padding-top: 4px;
padding-right: 4px;
}
```
### Inputs (16 instances)
```css
.input {
color: rgb(23, 23, 23);
border-color: rgb(255, 255, 255);
border-radius: 8px;
font-size: 16px;
padding-top: 2px;
padding-right: 2px;
}
```
### Links (130 instances)
```css
.link {
color: rgb(23, 23, 23);
font-size: 16px;
font-weight: 500;
}
```
### Modals (78 instances)
```css
.modal {
background-color: rgb(255, 255, 255);
border-radius: 12px;
padding-top: 24px;
padding-right: 0px;
max-width: 280px;
}
```
## Component Clusters
Reusable component instances grouped by DOM structure and style similarity:
### Button — 55 instances, 1 variant
**Variant 1** (55 instances)
```css
background: rgba(0, 0, 0, 0);
color: rgb(55, 55, 55);
padding: 0px 0px 0px 0px;
border-radius: 4px;
border: 0px none rgb(55, 55, 55);
font-size: 13.3333px;
font-weight: 400;
```
### Button — 1 instance, 1 variant
**Variant 1** (1 instance)
```css
background: rgb(239, 239, 239);
color: rgb(0, 0, 0);
padding: 1px 6px 1px 6px;
border-radius: 0px;
border: 2px outset rgb(0, 0, 0);
font-size: 13.3333px;
font-weight: 400;
```
## Layout System
**123 grid containers** and **1256 flex containers** detected.
### Container Widths
| Max Width | Padding |
|-----------|---------|
| 1200px | 0px |
| 100% | 0px |
| 992px | 0px |
### Grid Column Patterns
| Columns | Usage Count |
|---------|-------------|
| 1-column | 122x |
| 2-column | 1x |
### Grid Templates
```css
grid-template-columns: 592px 592px;
gap: 16px;
grid-template-columns: 32px;
grid-template-columns: 32px;
grid-template-columns: 32px;
grid-template-columns: 32px;
```
### Flex Patterns
| Direction/Wrap | Count |
|----------------|-------|
| column/nowrap | 470x |
| row/nowrap | 71x |
| row/wrap | 715x |
**Gap values:** `0px 12px`, `0px 20px`, `0px 24px`, `0px 4px`, `0px 8px`, `0px normal`, `12px normal`, `16px`, `18px normal`, `24px`, `24px normal`, `4px normal`, `8px normal`
## Interaction States
### Button States
**""**
```css
/* Focus */
outline: rgb(208, 135, 40) none 3px → rgba(0, 0, 0, 0) dotted 0px;
```
### Link Hover
```css
text-decoration: none → underline;
```
## Accessibility (WCAG 2.1)
**Overall Score: 100%** — 0 passing, 0 failing color pairs
## Design System Score
**Overall: 85/100 (Grade: B)**
| Category | Score |
|----------|-------|
| Color Discipline | 100/100 |
| Typography Consistency | 50/100 |
| Spacing System | 100/100 |
| Shadow Consistency | 100/100 |
| Border Radius Consistency | 90/100 |
| Accessibility | 100/100 |
| CSS Tokenization | 100/100 |
**Strengths:** Tight, disciplined color palette, Well-defined spacing scale, Clean elevation system, Consistent border radii, Strong accessibility compliance, Good CSS variable tokenization
**Issues:**
- 4 font families — consider limiting to 2 (heading + body)
- 140 !important rules — prefer specificity over overrides
- 81% of CSS is unused — consider purging
- 6545 duplicate CSS declarations
## Z-Index Map
**35 unique z-index values** across 3 layers.
| Layer | Range | Elements |
|-------|-------|----------|
| modal | 1503,2105 | div.b.u.b.b.l.e.-.e.l.e.m.e.n.t. .C.u.s.t.o.m.E.l.e.m.e.n.t. .b.a.T.a.K.a.Q.a.X. .f.l.o.a.t.i.n.g.-.g.r.o.u.p. .b.u.b.b.l.e.-.r.-.c.o.n.t.a.i.n.e.r. .f.l.e.x. .c.o.l.u.m.n, div.b.u.b.b.l.e.-.e.l.e.m.e.n.t. .C.u.s.t.o.m.E.l.e.m.e.n.t. .b.a.T.a.K.a.Q.a.X. .f.l.o.a.t.i.n.g.-.g.r.o.u.p. .b.u.b.b.l.e.-.r.-.c.o.n.t.a.i.n.e.r. .f.l.e.x. .c.o.l.u.m.n, div.b.u.b.b.l.e.-.e.l.e.m.e.n.t. .C.u.s.t.o.m.E.l.e.m.e.n.t. .b.a.T.a.K.a.Q.a.X. .f.l.o.a.t.i.n.g.-.g.r.o.u.p. .b.u.b.b.l.e.-.r.-.c.o.n.t.a.i.n.e.r. .f.l.e.x. .c.o.l.u.m.n |
| sticky | 10,30 | div.b.u.b.b.l.e.-.e.l.e.m.e.n.t. .G.r.o.u.p. .b.a.T.a.N.a.I.d. .b.u.b.b.l.e.-.r.-.c.o.n.t.a.i.n.e.r. .f.l.e.x. .r.o.w, div.b.u.b.b.l.e.-.e.l.e.m.e.n.t. .T.e.x.t. .b.a.T.a.N.a.I.q, textarea.b.u.b.b.l.e.-.e.l.e.m.e.n.t. .M.u.l.t.i.L.i.n.e.I.n.p.u.t. .a.1.7.8.6.2.3.1.7.9.1.3.3.1.x.5.4.4.4 |
| base | 0,9 | div.b.u.b.b.l.e.-.r.-.c.o.n.t.a.i.n.e.r. .f.l.e.x. .c.o.l.u.m.n, div.b.u.b.b.l.e.-.r.-.c.o.n.t.a.i.n.e.r. .f.l.e.x. .c.o.l.u.m.n, div.b.u.b.b.l.e.-.r.-.c.o.n.t.a.i.n.e.r. .f.l.e.x. .c.o.l.u.m.n |
## SVG Icons
**7 unique SVG icons** detected. Dominant style: **filled**.
| Size Class | Count |
|------------|-------|
| md | 4 |
| lg | 2 |
| xl | 1 |
**Icon colors:** `rgb(0, 0, 0)`
## Font Files
| Family | Source | Weights | Styles |
|--------|--------|---------|--------|
| FontAwesome | self-hosted | normal | normal |
| Material Icons | self-hosted | 400 | normal |
| Material Icons Outline | self-hosted | 400 | normal |
| Inter | google-fonts | 300, 400, 500, 600, 700 | normal |
**Google Fonts URL:** `https://fonts.googleapis.com/css?family=Inter:300%7CInter:regular%7CInter:500%7CInter:600%7CInter:700`
## Image Style Patterns
| Pattern | Count | Key Styles |
|---------|-------|------------|
| thumbnail | 28 | objectFit: contain, borderRadius: 8px, shape: rounded |
| general | 1 | objectFit: cover, borderRadius: 20px, shape: pill |
**Aspect ratios:** 1:1 (28x), 2:3 (1x)
## Motion Language
**Feel:** mixed · **Scroll-linked:** yes
### Duration Tokens
| name | value | ms |
|---|---|---|
| `sm` | `250ms` | 250 |
## Component Anatomy
### button — 56 instances
**Slots:** label, icon
## Brand Voice
**Tone:** neutral · **Pronoun:** third-person · **Headings:** unknown (tight)
### Top CTA Verbs
- **content** (25)
- **favorite** (25)
- **format** (1)
- **filter** (1)
- **add** (1)
- **arrow** (1)
- **note** (1)
### Button Copy Patterns
- "content_copy" (25×)
- "favorite_border" (25×)
- "format_line_spacing" (1×)
- "filter_list" (1×)
- "add_circle_outline" (1×)
- "arrow_drop_down" (1×)
- "note_alt" (1×)
## Page Intent
**Type:** `landing` (confidence 0.45)
**Description:** A tool to brainstorm, discuss, refine implement and promote ideas.
A social network for building stuff and influencing how stuff is built.
A marketplace for challenges, methods and solutions.
A platfo
## Material Language
**Label:** `flat` (confidence 0)
| Metric | Value |
|--------|-------|
| Avg saturation | 0.101 |
| Shadow profile | soft |
| Avg shadow blur | 0px |
| Max radius | 20px |
| backdrop-filter in use | no |
| Gradients | 0 |
## Imagery Style
**Label:** `mixed` (confidence 0)
**Counts:** total 29, svg 0, icon 1, screenshot-like 0, photo-like 0
**Dominant aspect:** square-ish
**Radius profile on images:** soft
## Quick Start
To recreate this design in a new project:
1. **Install fonts:** Add `Helvetica` from Google Fonts or your font provider
2. **Import CSS variables:** Copy `variables.css` into your project
3. **Tailwind users:** Use the generated `tailwind.config.js` to extend your theme
4. **Design tokens:** Import `design-tokens.json` for tooling integration

View file

@ -0,0 +1,218 @@
{
"$metadata": {
"generator": "designlang",
"version": "7.0.0",
"spec": "https://design-tokens.github.io/community-group/format/",
"source": "https://coulomb.social",
"generatedAt": "2026-08-08T23:30:13.743Z"
},
"primitive": {
"color": {
"brand": {
"primary": {
"$value": "#d08728",
"$type": "color"
}
},
"neutral": {
"n100": {
"$value": "#000000",
"$type": "color"
},
"n200": {
"$value": "#efefef",
"$type": "color"
},
"n300": {
"$value": "#616161",
"$type": "color"
},
"n400": {
"$value": "#373737",
"$type": "color"
},
"n500": {
"$value": "#171717",
"$type": "color"
},
"n600": {
"$value": "#ffffff",
"$type": "color"
},
"n700": {
"$value": "#e5e5e5",
"$type": "color"
}
},
"background": {
"bg0": {
"$value": "#ffffff",
"$type": "color"
},
"bg1": {
"$value": "#e5e5e5",
"$type": "color"
},
"bg2": {
"$value": "#171717",
"$type": "color"
},
"bg3": {
"$value": "#f5f5f5",
"$type": "color"
}
},
"text": {
"text0": {
"$value": "#000000",
"$type": "color"
},
"text1": {
"$value": "#171717",
"$type": "color"
},
"text2": {
"$value": "#373737",
"$type": "color"
},
"text3": {
"$value": "#616161",
"$type": "color"
},
"text4": {
"$value": "#ffffff",
"$type": "color"
}
}
},
"spacing": {
"s0": {
"$value": "1px",
"$type": "dimension"
},
"s1": {
"$value": "24px",
"$type": "dimension"
},
"s2": {
"$value": "32px",
"$type": "dimension"
},
"s3": {
"$value": "40px",
"$type": "dimension"
},
"s4": {
"$value": "60px",
"$type": "dimension"
},
"s5": {
"$value": "80px",
"$type": "dimension"
},
"s6": {
"$value": "96px",
"$type": "dimension"
},
"s7": {
"$value": "320px",
"$type": "dimension"
}
},
"radius": {
"r0": {
"$value": "4px",
"$type": "dimension"
},
"r1": {
"$value": "8px",
"$type": "dimension"
},
"r2": {
"$value": "12px",
"$type": "dimension"
},
"r3": {
"$value": "15px",
"$type": "dimension"
},
"r4": {
"$value": "20px",
"$type": "dimension"
}
},
"shadow": {
"sh0": {
"$value": "rgb(170, 170, 170) 2px 2px 4px 0px",
"$type": "shadow"
},
"sh1": {
"$value": "rgba(23, 23, 23, 0.1) 2px 2px 8px 2px",
"$type": "shadow"
}
},
"fontFamily": {
"f0": {
"$value": "Helvetica",
"$type": "fontFamily"
},
"f1": {
"$value": "Arial",
"$type": "fontFamily"
},
"f2": {
"$value": "Times New Roman",
"$type": "fontFamily"
},
"f3": {
"$value": "Inter",
"$type": "fontFamily"
}
}
},
"semantic": {
"color": {
"action": {
"primary": {
"$value": "{primitive.color.brand.primary}",
"$type": "color"
}
},
"surface": {
"default": {
"$value": "{primitive.color.background.bg0}",
"$type": "color"
}
},
"text": {
"body": {
"$value": "{primitive.color.text.text0}",
"$type": "color"
}
}
},
"typography": {
"body": {
"$value": {
"fontFamily": "Helvetica",
"fontSize": "50px",
"fontWeight": "400",
"lineHeight": "50px"
},
"$type": "typography"
}
},
"radius": {
"control": {
"$value": "{primitive.radius.r0}",
"$type": "dimension"
}
},
"shadow": {
"elevated": {
"$value": "{primitive.shadow.sh0}",
"$type": "shadow"
}
}
}
}

View file

@ -0,0 +1,449 @@
{
"collections": [
{
"name": "Brand",
"modes": [
"light"
],
"variables": [
{
"name": "color/primary",
"type": "COLOR",
"values": {
"light": {
"r": 0.8156862745098039,
"g": 0.5294117647058824,
"b": 0.1568627450980392,
"a": 1
}
}
},
{
"name": "color/neutral/50",
"type": "COLOR",
"values": {
"light": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
}
}
},
{
"name": "color/neutral/100",
"type": "COLOR",
"values": {
"light": {
"r": 0.9372549019607843,
"g": 0.9372549019607843,
"b": 0.9372549019607843,
"a": 1
}
}
},
{
"name": "color/neutral/200",
"type": "COLOR",
"values": {
"light": {
"r": 0.3803921568627451,
"g": 0.3803921568627451,
"b": 0.3803921568627451,
"a": 1
}
}
},
{
"name": "color/neutral/300",
"type": "COLOR",
"values": {
"light": {
"r": 0.21568627450980393,
"g": 0.21568627450980393,
"b": 0.21568627450980393,
"a": 1
}
}
},
{
"name": "color/neutral/400",
"type": "COLOR",
"values": {
"light": {
"r": 0.09019607843137255,
"g": 0.09019607843137255,
"b": 0.09019607843137255,
"a": 1
}
}
},
{
"name": "color/neutral/500",
"type": "COLOR",
"values": {
"light": {
"r": 1,
"g": 1,
"b": 1,
"a": 1
}
}
},
{
"name": "color/neutral/600",
"type": "COLOR",
"values": {
"light": {
"r": 0.8980392156862745,
"g": 0.8980392156862745,
"b": 0.8980392156862745,
"a": 1
}
}
},
{
"name": "color/background/default",
"type": "COLOR",
"values": {
"light": {
"r": 1,
"g": 1,
"b": 1,
"a": 1
}
}
},
{
"name": "color/background/1",
"type": "COLOR",
"values": {
"light": {
"r": 0.8980392156862745,
"g": 0.8980392156862745,
"b": 0.8980392156862745,
"a": 1
}
}
},
{
"name": "color/background/2",
"type": "COLOR",
"values": {
"light": {
"r": 0.09019607843137255,
"g": 0.09019607843137255,
"b": 0.09019607843137255,
"a": 1
}
}
},
{
"name": "color/background/3",
"type": "COLOR",
"values": {
"light": {
"r": 0.9607843137254902,
"g": 0.9607843137254902,
"b": 0.9607843137254902,
"a": 1
}
}
},
{
"name": "color/text/default",
"type": "COLOR",
"values": {
"light": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
}
}
},
{
"name": "color/text/1",
"type": "COLOR",
"values": {
"light": {
"r": 0.09019607843137255,
"g": 0.09019607843137255,
"b": 0.09019607843137255,
"a": 1
}
}
},
{
"name": "color/text/2",
"type": "COLOR",
"values": {
"light": {
"r": 0.21568627450980393,
"g": 0.21568627450980393,
"b": 0.21568627450980393,
"a": 1
}
}
},
{
"name": "color/text/3",
"type": "COLOR",
"values": {
"light": {
"r": 0.3803921568627451,
"g": 0.3803921568627451,
"b": 0.3803921568627451,
"a": 1
}
}
},
{
"name": "color/text/4",
"type": "COLOR",
"values": {
"light": {
"r": 1,
"g": 1,
"b": 1,
"a": 1
}
}
}
]
},
{
"name": "Typography",
"modes": [
"default"
],
"variables": [
{
"name": "font/size/50",
"type": "FLOAT",
"values": {
"default": 50
}
},
{
"name": "font/weight/50",
"type": "FLOAT",
"values": {
"default": 400
}
},
{
"name": "font/lineHeight/50",
"type": "FLOAT",
"values": {
"default": 50
}
},
{
"name": "font/size/42",
"type": "FLOAT",
"values": {
"default": 42
}
},
{
"name": "font/weight/42",
"type": "FLOAT",
"values": {
"default": 700
}
},
{
"name": "font/size/24",
"type": "FLOAT",
"values": {
"default": 24
}
},
{
"name": "font/weight/24",
"type": "FLOAT",
"values": {
"default": 600
}
},
{
"name": "font/lineHeight/24",
"type": "FLOAT",
"values": {
"default": 36
}
},
{
"name": "font/size/18",
"type": "FLOAT",
"values": {
"default": 18
}
},
{
"name": "font/weight/18",
"type": "FLOAT",
"values": {
"default": 600
}
},
{
"name": "font/lineHeight/18",
"type": "FLOAT",
"values": {
"default": 18
}
},
{
"name": "font/size/16",
"type": "FLOAT",
"values": {
"default": 16
}
},
{
"name": "font/weight/16",
"type": "FLOAT",
"values": {
"default": 400
}
},
{
"name": "font/size/14",
"type": "FLOAT",
"values": {
"default": 14
}
},
{
"name": "font/weight/14",
"type": "FLOAT",
"values": {
"default": 500
}
},
{
"name": "font/lineHeight/14",
"type": "FLOAT",
"values": {
"default": 19.6
}
},
{
"name": "font/size/13.3333",
"type": "FLOAT",
"values": {
"default": 13.3333
}
},
{
"name": "font/weight/13.3333",
"type": "FLOAT",
"values": {
"default": 400
}
}
]
},
{
"name": "Spacing",
"modes": [
"default"
],
"variables": [
{
"name": "spacing/1",
"type": "FLOAT",
"values": {
"default": 1
}
},
{
"name": "spacing/24",
"type": "FLOAT",
"values": {
"default": 24
}
},
{
"name": "spacing/32",
"type": "FLOAT",
"values": {
"default": 32
}
},
{
"name": "spacing/40",
"type": "FLOAT",
"values": {
"default": 40
}
},
{
"name": "spacing/60",
"type": "FLOAT",
"values": {
"default": 60
}
},
{
"name": "spacing/80",
"type": "FLOAT",
"values": {
"default": 80
}
},
{
"name": "spacing/96",
"type": "FLOAT",
"values": {
"default": 96
}
},
{
"name": "spacing/320",
"type": "FLOAT",
"values": {
"default": 320
}
},
{
"name": "radius/sm",
"type": "FLOAT",
"values": {
"default": 4
}
},
{
"name": "radius/md",
"type": "FLOAT",
"values": {
"default": 8
}
},
{
"name": "radius/lg",
"type": "FLOAT",
"values": {
"default": 12
}
},
{
"name": "radius/lg",
"type": "FLOAT",
"values": {
"default": 15
}
},
{
"name": "radius/xl",
"type": "FLOAT",
"values": {
"default": 20
}
}
]
}
]
}

View file

@ -0,0 +1,23 @@
{
"flags": [],
"forms": {
"count": 0,
"families": {}
},
"inputTypesSeen": {
"textarea": 10,
"input": 6
},
"modals": [],
"toastLibraries": [],
"loading": {
"skeleton": 0,
"spinner": 0
},
"empty": {
"count": 0
},
"error": {
"count": 0
}
}

View file

@ -0,0 +1,2 @@
/* designlang — no gradients detected on the source page. */
/* Run with --interactions to capture hover backgrounds. */

View file

@ -0,0 +1,7 @@
{
"$schema": "https://designlang.app/schemas/gradients.json",
"source": "https://coulomb.social",
"generated": "2026-08-08T23:30:22.006Z",
"count": 0,
"gradients": []
}

View file

@ -0,0 +1,199 @@
{
"library": "unknown",
"confidence": 0,
"alternates": [],
"stats": {
"count": 55,
"strokeOnly": 0,
"fillOnly": 55,
"mixed": 0,
"avgStrokeWidth": 0,
"gridDistribution": {},
"roundedCapsFraction": 0
},
"signals": [
"fillDominant"
],
"icons": [
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
},
{
"class": "",
"grid": null,
"strokeWidth": null,
"style": "fill"
}
]
}

View file

@ -0,0 +1,24 @@
{
"pageIntent": {
"type": "landing",
"confidence": 0.45,
"path": "/",
"title": "Research | Coulomb",
"description": "A tool to brainstorm, discuss, refine implement and promote ideas.\nA social network for building stuff and influencing how stuff is built.\nA marketplace for challenges, methods and solutions.\nA platfo",
"signals": [
{
"kind": "url",
"type": "landing",
"weight": 0.45,
"detail": "root-path"
}
],
"alternates": [],
"needsSmart": true
},
"sectionRoles": {
"sections": [],
"counts": {},
"readingOrder": []
}
}

View file

@ -0,0 +1,12 @@
{
"library": "unknown",
"confidence": 0,
"evidence": [],
"alternates": [],
"signals": {
"tailwindLike": 0.136,
"radixAttrCount": 0,
"classSampleSize": 467
},
"needsSmart": true
}

View file

@ -0,0 +1,275 @@
{
"colors": {
"all": [
{
"hex": "#000000",
"rgb": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"hsl": {
"h": 0,
"s": 0,
"l": 0
},
"count": 5304,
"contexts": [
"text",
"border",
"background"
]
},
{
"hex": "#efefef",
"rgb": {
"r": 239,
"g": 239,
"b": 239,
"a": 1
},
"hsl": {
"h": 0,
"s": 0,
"l": 94
},
"count": 610,
"contexts": [
"background"
]
},
{
"hex": "#616161",
"rgb": {
"r": 97,
"g": 97,
"b": 97,
"a": 1
},
"hsl": {
"h": 0,
"s": 0,
"l": 38
},
"count": 556,
"contexts": [
"text",
"border"
]
},
{
"hex": "#373737",
"rgb": {
"r": 55,
"g": 55,
"b": 55,
"a": 1
},
"hsl": {
"h": 0,
"s": 0,
"l": 22
},
"count": 462,
"contexts": [
"text",
"border"
]
},
{
"hex": "#171717",
"rgb": {
"r": 23,
"g": 23,
"b": 23,
"a": 1
},
"hsl": {
"h": 0,
"s": 0,
"l": 9
},
"count": 401,
"contexts": [
"text",
"border",
"background"
]
},
{
"hex": "#ffffff",
"rgb": {
"r": 255,
"g": 255,
"b": 255,
"a": 1
},
"hsl": {
"h": 0,
"s": 0,
"l": 100
},
"count": 242,
"contexts": [
"background",
"border",
"text"
]
},
{
"hex": "#e5e5e5",
"rgb": {
"r": 229,
"g": 229,
"b": 229,
"a": 1
},
"hsl": {
"h": 0,
"s": 0,
"l": 90
},
"count": 217,
"contexts": [
"border",
"background"
]
},
{
"hex": "#d08728",
"rgb": {
"r": 208,
"g": 135,
"b": 40,
"a": 1
},
"hsl": {
"h": 34,
"s": 68,
"l": 49
},
"count": 8,
"contexts": [
"background"
]
}
]
},
"regions": [],
"componentClusters": [
{
"kind": "button",
"structuralHash": "button>svg",
"instanceCount": 55,
"variants": [
{
"css": {
"background": "rgba(0, 0, 0, 0)",
"color": "rgb(55, 55, 55)",
"padding": "0px 0px 0px 0px",
"borderRadius": "4px",
"border": "0px none rgb(55, 55, 55)",
"fontSize": "13.3333px",
"fontWeight": "400"
},
"instanceCount": 55
}
]
},
{
"kind": "button",
"structuralHash": "button",
"instanceCount": 1,
"variants": [
{
"css": {
"background": "rgb(239, 239, 239)",
"color": "rgb(0, 0, 0)",
"padding": "1px 6px 1px 6px",
"borderRadius": "0px",
"border": "2px outset rgb(0, 0, 0)",
"fontSize": "13.3333px",
"fontWeight": "400"
},
"instanceCount": 1
}
]
}
],
"accessibility": {
"remediation": []
},
"cssHealth": {
"sheets": [
{
"url": "https://fonts.googleapis.com/css?family=Inter:300%7CInter:regular%7CInter:500%7CInter:600%7CInter:700",
"totalBytes": 11585,
"usedBytes": 0,
"unusedBytes": 11585,
"unusedPercent": 100
},
{
"url": "https://coulomb.social/package/run_css/0591b05f69514365ec03deea8134e9c2ff462a44dd0474f9fb551cb47e38e201/coulomb/live/vw_pages/xfalse/xfalse/run.css",
"totalBytes": 228282,
"usedBytes": 45959,
"unusedBytes": 182323,
"unusedPercent": 80
},
{
"url": "https://coulomb.social/vw_pages?resume=1786231781137x912895168994024000",
"totalBytes": 821,
"usedBytes": 0,
"unusedBytes": 821,
"unusedPercent": 100
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/default.min.css",
"totalBytes": 1144,
"usedBytes": 0,
"unusedBytes": 1144,
"unusedPercent": 100
}
],
"totalBytes": 241832,
"usedBytes": 45959,
"unusedBytes": 195873,
"unusedPercent": 81,
"importantCount": 140,
"duplicates": 6545,
"vendorPrefixes": {
"webkit": 139,
"moz": 436,
"ms": 96,
"o": 34
},
"keyframes": [
{
"name": "highlight-in",
"steps": 1
},
{
"name": "fa-spin",
"steps": 2
}
],
"specificity": {
"max": [
1,
7,
46
],
"average": [
0,
1.84,
0.38
],
"count": 2001
},
"issues": [
"140 !important rules",
"6545 duplicate declarations",
"81% unused CSS"
]
}
}

View file

@ -0,0 +1,16 @@
{
"$description": "Motion tokens extracted by designlang",
"duration": {
"sm": {
"$value": "250ms",
"$type": "duration",
"ms": 250
}
},
"easing": {},
"spring": {},
"$meta": {
"feel": "mixed",
"scrollLinked": true
}
}

View file

@ -0,0 +1,38 @@
/* Motion CSS generated by designlang (motionlang)
* Source: https://coulomb.social
* 2026-08-08T23:30:22.011Z
*
* <link rel="stylesheet" href="./coulomb.social-motion.css">
* <div class="mo-slide-up"></div>
*/
:root {
--duration-sm: 250ms;
--ease-standard: cubic-bezier(0.25, 0.1, 0.25, 1);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes scale-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: none; } }
@keyframes pop { 0% { transform: scale(0.9); } 60% { transform: scale(1.03); } 100% { transform: scale(1); } }
.mo-animate,
[class^="mo-"], [class*=" mo-"] {
animation-duration: var(--duration-sm, 250ms);
animation-timing-function: var(--ease-standard);
animation-fill-mode: both;
}
.mo-fade-in { animation-name: fade-in; }
.mo-slide-up { animation-name: slide-up; }
.mo-scale-in { animation-name: scale-in; }
.mo-pop { animation-name: pop; animation-timing-function: var(--ease-standard); }
@media (prefers-reduced-motion: reduce) {
*, ::before, ::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

View file

@ -0,0 +1,70 @@
// Framer Motion presets — generated by designlang (motionlang)
// Source: https://coulomb.social
// 2026-08-08T23:30:22.009Z
//
// import { transitions, variants } from './coulomb.social-motion.framer';
// <motion.div variants={variants.slideUp} initial="hidden" animate="show"
// transition={transitions.base} />
/** Easing curves extracted from the live page, as Framer cubic-bezier arrays. */
export const easings = {
standard: [0.25, 1, 0.5, 1],
};
/** Duration presets (seconds), extracted from the live page. */
export const durations = {
sm: 0.25,
};
/** Spring presets — pass to a Framer Motion `transition` prop. */
export const springs = {
soft: { type: 'spring', stiffness: 320, damping: 30, mass: 1 },
};
/** Ready-to-spread Framer Motion transition objects. */
export const transitions = {
base: { duration: 0.25, ease: easings.standard },
fast: { duration: 0.125, ease: easings.standard },
slow: { duration: 0.450, ease: easings.standard },
spring: springs.soft,
};
/** Common variants wired to the extracted timing. */
export const variants = {
fade: {
hidden: { opacity: 0 },
show: { opacity: 1, transition: transitions.base },
},
slideUp: {
hidden: { opacity: 0, y: 16 },
show: { opacity: 1, y: 0, transition: transitions.base },
},
scaleIn: {
hidden: { opacity: 0, scale: 0.96 },
show: { opacity: 1, scale: 1, transition: transitions.base },
},
pop: {
hidden: { opacity: 0, scale: 0.9 },
show: { opacity: 1, scale: 1, transition: springs.soft },
},
stagger: {
hidden: {},
show: { transition: { staggerChildren: 0.063 } },
},
};
/** Site uses scroll- or view-timeline. Drop-in `whileInView` props. */
export const inView = {
fadeIn: {
initial: variants.fade.hidden,
whileInView: variants.fade.show,
viewport: { once: true, amount: 0.3 },
},
riseIn: {
initial: variants.slideUp.hidden,
whileInView: variants.slideUp.show,
viewport: { once: true, amount: 0.3 },
},
};
export default { easings, durations, springs, transitions, variants, inView };

View file

@ -0,0 +1,49 @@
// GSAP presets — generated by designlang (motionlang)
// Source: https://coulomb.social
// 2026-08-08T23:30:22.015Z
//
// import gsap from 'gsap';
// import { registerEases, reveals } from './coulomb.social-motion.gsap.js';
// registerEases(gsap); // one-time: registers CustomEases
// reveals.slideUp('.card');
/** CustomEase path data extracted from the live page, keyed by family. */
export const eases = {
standard: 'M0,0 C0.25,0.1 0.25,1 1,1',
};
/** Duration presets (seconds), extracted from the live page. */
export const durations = {
sm: 0.25,
};
/** Register every extracted curve as a named GSAP CustomEase. Call once. */
export function registerEases(gsap) {
const CustomEase = gsap.plugins?.CustomEase || (typeof window !== 'undefined' && window.CustomEase);
if (!CustomEase) {
console.warn('[motion.gsap] CustomEase plugin not registered — call gsap.registerPlugin(CustomEase) first.');
return;
}
for (const [name, path] of Object.entries(eases)) CustomEase.create(name, path);
}
/** gsap.from() reveal helpers. Pass any GSAP target + optional vars override. */
export const reveals = {
fadeIn: (target, vars = {}) => gsap.from(target, { opacity: 0, duration: 0.25, ease: 'standard', ...vars }),
slideUp: (target, vars = {}) => gsap.from(target, { opacity: 0, y: 24, duration: 0.25, ease: 'standard', ...vars }),
slideDown: (target, vars = {}) => gsap.from(target, { opacity: 0, y: -24, duration: 0.25, ease: 'standard', ...vars }),
scaleIn: (target, vars = {}) => gsap.from(target, { opacity: 0, scale: 0.96, duration: 0.25, ease: 'standard', ...vars }),
pop: (target, vars = {}) => gsap.from(target, { scale: 0.9, duration: 0.25, ease: 'standard', ...vars }),
};
/** Site uses scroll-/view-timeline. Batch-reveals elements as they enter. */
/** Requires: gsap.registerPlugin(ScrollTrigger). */
export function revealOnScroll(targets, vars = {}) {
return gsap.utils.toArray(targets).map((el) =>
gsap.from(el, {
opacity: 0, y: 24, duration: 0.25, ease: 'standard', ...vars,
scrollTrigger: { trigger: el, start: 'top 85%', toggleActions: 'play none none reverse' },
}));
}
export default { eases, durations, registerEases, reveals };

View file

@ -0,0 +1,138 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>coulomb.social — motion lab · designlang</title>
<style>
:root {
--bg: #0a0a0c; --fg: #f4f4f5; --fg-2: rgba(244,244,245,.62); --fg-3: rgba(244,244,245,.4);
--line: rgba(255,255,255,.08); --accent: #ef4444;
--mono: ui-monospace, 'SF Mono', Menlo, monospace;
--sans: -apple-system, 'Segoe UI', system-ui, sans-serif;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--fg); font-family: var(--sans); }
.ml-wrap { max-width: 1080px; margin: 0 auto; padding: 56px 24px 96px; }
header.ml-h { margin-bottom: 48px; }
.ml-eyebrow { font-family: var(--mono); font-size: 11px; letter-spacing: .18em;
text-transform: uppercase; color: var(--fg-3); margin: 0 0 12px; }
h1.ml-title { font-size: clamp(34px, 6vw, 60px); letter-spacing: -.03em; margin: 0 0 12px; font-weight: 600; }
.ml-sub { color: var(--fg-2); font-size: 16px; max-width: 60ch; margin: 0; }
h2.ml-section { font-size: 22px; letter-spacing: -.01em; margin: 56px 0 20px; font-weight: 600; }
.ml-section-meta { font-family: var(--mono); font-size: 12px; color: var(--fg-3); margin-left: 8px; font-weight: 400; }
.ml-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
.ml-card { border: 1px solid var(--line); border-radius: 14px; padding: 18px; background: rgba(255,255,255,.02); }
.ml-card-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; }
.ml-name { font-family: var(--mono); font-size: 13px; color: var(--fg); }
.ml-count { font-family: var(--mono); font-size: 11px; color: var(--fg-3); }
.ml-curve { display: block; margin: 0 auto 12px; }
.ml-curve-guide { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 3; }
.ml-curve-path { fill: none; stroke: var(--accent); stroke-width: 2.5; stroke-linecap: round; }
.ml-curve-none { height: 160px; display: flex; align-items: center; justify-content: center;
color: var(--fg-3); font-family: var(--mono); font-size: 12px; }
.ml-raw { display: block; font-family: var(--mono); font-size: 10.5px; color: var(--fg-3);
word-break: break-all; margin-bottom: 14px; }
.ml-demo { height: 8px; border-radius: 999px; background: rgba(255,255,255,.05); position: relative; }
.ml-dot { position: absolute; top: 50%; left: 0; width: 14px; height: 14px; border-radius: 50%;
background: var(--accent); transform: translate(0,-50%);
animation: ml-ride 1.6s infinite alternate; box-shadow: 0 0 12px rgba(239,68,68,.6); }
@keyframes ml-ride { from { left: 0; } to { left: calc(100% - 14px); } }
.ml-durs { display: flex; flex-direction: column; gap: 10px; }
.ml-dur { display: grid; grid-template-columns: 90px 1fr 64px; gap: 14px; align-items: center; }
.ml-dur-name { font-family: var(--mono); font-size: 12px; color: var(--fg-2); }
.ml-dur-track { height: 8px; border-radius: 999px; background: rgba(255,255,255,.05); overflow: hidden; }
.ml-dur-fill { display: block; height: 100%; background: var(--accent); border-radius: 999px;
transform-origin: left; animation: ml-pulse var(--ms) ease-in-out infinite alternate; }
@keyframes ml-pulse { from { opacity: .35; } to { opacity: 1; } }
.ml-dur-ms { font-family: var(--mono); font-size: 12px; color: var(--fg-3); text-align: right; }
.ml-kf { border: 1px solid var(--line); border-radius: 14px; padding: 18px; background: rgba(255,255,255,.02); margin-bottom: 14px; }
.ml-kf-css { font-family: var(--mono); font-size: 11px; color: var(--fg-2); overflow-x: auto; margin: 0; }
.ml-empty { color: var(--fg-3); font-family: var(--mono); font-size: 13px;
border: 1px dashed var(--line); border-radius: 12px; padding: 28px; text-align: center; }
footer.ml-f { margin-top: 72px; padding-top: 24px; border-top: 1px solid var(--line);
font-family: var(--mono); font-size: 11px; color: var(--fg-3); }
footer.ml-f a { color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .ml-dot, .ml-dur-fill { animation: none; } }
</style>
</head>
<body>
<div class="ml-wrap">
<header class="ml-h">
<p class="ml-eyebrow">motionlang · motion lab</p>
<h1 class="ml-title">coulomb.social in motion</h1>
<p class="ml-sub">Every easing curve, duration and keyframe animation designlang read off the live coulomb.social page — drawn, timed and replayed. Generated 2026-08-08.</p>
</header>
<h2 class="ml-section">Easing curves <span class="ml-section-meta">0</span></h2>
<div class="ml-empty">No easing curves detected. Re-run with --interactions to capture hover transitions.</div>
<h2 class="ml-section">Durations <span class="ml-section-meta">1</span></h2>
<div class="ml-durs">
<div class="ml-dur">
<span class="ml-dur-name">sm</span>
<span class="ml-dur-track"><span class="ml-dur-fill" style="--ms:250ms; width:25%"></span></span>
<span class="ml-dur-ms">250ms</span>
</div></div>
<h2 class="ml-section">Keyframe animations <span class="ml-section-meta">15</span></h2>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes highlight-in</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes fa-spin</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-0-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-1-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-2-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-3-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-4-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-5-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-6-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-7-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-8-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<article class="ml-kf">
<div class="ml-card-head"><span class="ml-name">@keyframes opacity-70-25-9-13</span></div>
<pre class="ml-kf-css"></pre>
</article>
<footer class="ml-f">
Generated by designlang · motionlang · <a href="https://designlang.app">designlang.app</a> · re-extract with <code>npx designlang coulomb.social --motion</code>
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,58 @@
// Motion One presets — generated by designlang (motionlang)
// Source: https://coulomb.social
// 2026-08-08T23:30:22.010Z
//
// import { animate } from 'motion';
// import { animations, easings, durations } from './coulomb.social-motion.one.js';
//
// animations.fadeIn('#hero');
// animations.slideUp('.card', { delay: 0.1 });
/** Easing curves extracted from the live page, as Motion One cubic-bezier arrays. */
export const easings = {
standard: [0.25, 0.1, 0.25, 1],
};
/** Duration presets (seconds), extracted from the live page. */
export const durations = {
sm: 0.25,
};
/** Motion One spring options — pass as the `type: 'spring'` block of an animate() call. */
export const springs = {
soft: { type: 'spring', stiffness: 320, damping: 30, mass: 1 },
};
/** No on-page @keyframes were attached to elements; emit empty for shape parity. */
export const keyframes = {};
/** Animate-on-call helpers. Each takes a Motion One target + optional override. */
import { animate } from 'motion';
const _t = { duration: 0.25, easing: easings.standard };
export const animations = {
fadeIn: (target, opts = {}) =>
animate(target, { opacity: [0, 1] }, { ..._t, ...opts }),
fadeOut: (target, opts = {}) =>
animate(target, { opacity: [1, 0] }, { ..._t, ...opts }),
slideUp: (target, opts = {}) =>
animate(target, { opacity: [0, 1], y: [16, 0] }, { ..._t, ...opts }),
slideDown: (target, opts = {}) =>
animate(target, { opacity: [0, 1], y: [-16, 0] }, { ..._t, ...opts }),
scaleIn: (target, opts = {}) =>
animate(target, { opacity: [0, 1], scale: [0.96, 1] }, { ..._t, ...opts }),
pop: (target, opts = {}) =>
animate(target, { scale: [0.9, 1] }, { ...springs.soft, ...opts }),
};
/** Site uses scroll- or view-timeline. Helpers wired to Motion One's `inView` / `scroll`. */
import { inView, scroll } from 'motion';
export const enterOnView = (target, variant = animations.slideUp) =>
inView(target, (el) => { variant(el.target); });
export const parallaxY = (target, distance = 80) =>
scroll(animate(target, { y: [0, -distance] }, { easing: 'linear' }));
export default { easings, durations, springs, keyframes, animations };

View file

@ -0,0 +1,67 @@
// Tailwind motion preset — generated by designlang (motionlang)
// Source: https://coulomb.social
// 2026-08-08T23:30:22.014Z
//
// Merge `extend` into your tailwind.config theme.extend, e.g.:
// const motion = require('./coulomb.social-motion.tailwind');
// module.exports = { theme: { extend: { ...motion.extend } } };
//
// Then: <div class="animate-slide-up" /> or `duration-md ease-spring`.
const extend = {
"transitionDuration": {
"sm": "250ms"
},
"transitionTimingFunction": {
"standard": "cubic-bezier(0.25, 0.1, 0.25, 1)"
},
"keyframes": {
"fade-in": {
"0%": {
"opacity": "0"
},
"100%": {
"opacity": "1"
}
},
"slide-up": {
"0%": {
"opacity": "0",
"transform": "translateY(16px)"
},
"100%": {
"opacity": "1",
"transform": "none"
}
},
"scale-in": {
"0%": {
"opacity": "0",
"transform": "scale(0.96)"
},
"100%": {
"opacity": "1",
"transform": "none"
}
},
"pop": {
"0%": {
"transform": "scale(0.9)"
},
"60%": {
"transform": "scale(1.03)"
},
"100%": {
"transform": "scale(1)"
}
}
},
"animation": {
"fade-in": "fade-in 250ms var(--tw-ease-standard, cubic-bezier(0.25, 0.1, 0.25, 1)) both",
"slide-up": "slide-up 250ms var(--tw-ease-standard, cubic-bezier(0.25, 0.1, 0.25, 1)) both",
"scale-in": "scale-in 250ms var(--tw-ease-standard, cubic-bezier(0.25, 0.1, 0.25, 1)) both",
"pop": "pop 250ms var(--tw-ease-standard, cubic-bezier(0.25, 0.1, 0.25, 1)) both"
}
};
module.exports = { extend };

View file

@ -0,0 +1,44 @@
// Web Animations API presets — generated by designlang (motionlang)
// Source: https://coulomb.social
// 2026-08-08T23:30:22.016Z
//
// Zero-dependency. Runs on the browser-native Element.animate().
// import { animations } from './coulomb.social-motion.waapi.js';
// animations.fadeIn(document.querySelector('#hero'));
// await animations.slideUp(card, { delay: 80 }).finished;
/** Easing curves extracted from the live page, as WAAPI easing strings. */
export const easings = {
standard: 'cubic-bezier(0.25, 0.1, 0.25, 1)',
};
/** Duration presets (milliseconds), extracted from the live page. */
export const durations = {
sm: 250,
};
/** No on-page @keyframes were attached to elements; emit empty for shape parity. */
export const keyframes = {};
/** Honours the user's reduced-motion preference. */
export const prefersReducedMotion = () =>
typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
const _timing = { duration: 250, easing: easings.standard, fill: 'both' };
function _animate(el, frames, opts = {}) {
const timing = { ..._timing, ...opts };
if (prefersReducedMotion()) timing.duration = 0;
return el.animate(frames, timing);
}
/** Animate-on-call helpers. Each returns the live Animation (await `.finished`). */
export const animations = {
fadeIn: (el, opts = {}) => _animate(el, [{ opacity: 0 }, { opacity: 1 }], opts),
fadeOut: (el, opts = {}) => _animate(el, [{ opacity: 1 }, { opacity: 0 }], opts),
slideUp: (el, opts = {}) => _animate(el, [{ opacity: 0, transform: 'translateY(16px)' }, { opacity: 1, transform: 'translateY(0)' }], opts),
slideDown: (el, opts = {}) => _animate(el, [{ opacity: 0, transform: 'translateY(-16px)' }, { opacity: 1, transform: 'translateY(0)' }], opts),
scaleIn: (el, opts = {}) => _animate(el, [{ opacity: 0, transform: 'scale(0.96)' }, { opacity: 1, transform: 'scale(1)' }], opts),
};
export default { easings, durations, keyframes, animations, prefersReducedMotion };

View file

@ -0,0 +1,433 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Design Language: Research | Coulomb</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0a0a0a; --bg-card: #141414; --border: #222; --text: #e5e5e5; --text-heading: #fff;
--text-muted: #666; --text-sub: #a0a0a0; --text-dim: #444; --text-faint: #555; --row-border: #1a1a1a;
}
[data-theme="light"] {
--bg: #f5f5f5; --bg-card: #fff; --border: #ddd; --text: #333; --text-heading: #111;
--text-muted: #888; --text-sub: #666; --text-dim: #999; --text-faint: #aaa; --row-border: #e5e5e5;
}
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; padding: 40px 24px; }
.header-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.theme-toggle { background: var(--bg-card); border: 1px solid var(--border); color: var(--text); padding: 6px 14px; border-radius: 8px; cursor: pointer; font-size: 13px; white-space: nowrap; }
h1 { font-size: 36px; font-weight: 700; margin-bottom: 8px; color: var(--text-heading); }
h2 { font-size: 24px; font-weight: 600; margin: 48px 0 20px; color: var(--text-heading); border-bottom: 1px solid var(--border); padding-bottom: 12px; }
h3 { font-size: 16px; font-weight: 600; margin: 24px 0 12px; color: var(--text-sub); text-transform: uppercase; letter-spacing: 0.05em; }
.meta { color: var(--text-muted); font-size: 14px; margin-bottom: 32px; }
.meta span { margin-right: 16px; }
.grid { display: grid; gap: 12px; }
.grid-2 { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
@media (max-width: 600px) {
.grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr 1fr; }
h1 { font-size: 24px; }
.container { padding: 20px 12px; }
}
@media (max-width: 380px) {
.grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}
/* Color swatches */
.swatch { border-radius: 12px; overflow: hidden; background: var(--bg-card); border: 1px solid var(--border); cursor: pointer; position: relative; }
.swatch-color { height: 80px; position: relative; }
.swatch-info { padding: 10px 12px; font-size: 13px; }
.swatch-hex { font-weight: 600; font-family: monospace; color: var(--text-heading); }
.swatch-label { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
.swatch-role { display: inline-block; font-size: 10px; background: var(--border); color: var(--text-sub); padding: 2px 8px; border-radius: 4px; margin-top: 4px; }
.copied-tip { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: #fff; padding: 4px 12px; border-radius: 6px; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity 0.2s; }
.copied-tip.show { opacity: 1; }
/* Type scale */
.type-row { display: flex; align-items: baseline; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--row-border); }
.type-size { font-family: monospace; color: var(--text-muted); min-width: 60px; font-size: 13px; }
.type-meta { font-size: 12px; color: var(--text-dim); margin-left: auto; font-family: monospace; }
/* Spacing */
.spacing-row { display: flex; align-items: center; gap: 12px; padding: 6px 0; }
.spacing-bar { background: linear-gradient(90deg, #3b82f6, #8b5cf6); border-radius: 4px; height: 24px; min-width: 4px; transition: width 0.3s; }
.spacing-label { font-family: monospace; font-size: 13px; color: var(--text-sub); min-width: 60px; }
/* Shadows */
.shadow-card { background: var(--bg-card); border-radius: 12px; padding: 24px; text-align: center; min-height: 80px; display: flex; align-items: center; justify-content: center; border: 1px solid var(--border); }
.shadow-label { font-size: 12px; color: var(--text); font-family: monospace; }
/* Radii */
.radius-item { width: 60px; height: 60px; background: linear-gradient(135deg, #3b82f6, #8b5cf6); display: flex; align-items: center; justify-content: center; font-size: 11px; color: #fff; font-weight: 600; }
/* Accessibility */
.a11y-score { font-size: 64px; font-weight: 800; }
.a11y-score.good { color: #22c55e; }
.a11y-score.warn { color: #eab308; }
.a11y-score.bad { color: #ef4444; }
.a11y-pair { display: flex; align-items: center; gap: 12px; padding: 10px 16px; background: var(--bg-card); border-radius: 8px; margin-bottom: 6px; border: 1px solid var(--border); flex-wrap: wrap; }
.a11y-sample { width: 120px; padding: 6px 12px; border-radius: 6px; text-align: center; font-size: 14px; font-weight: 500; }
.a11y-ratio { font-family: monospace; font-size: 14px; min-width: 50px; }
.a11y-badge { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 4px; }
.a11y-badge.pass { background: #22c55e20; color: #22c55e; }
.a11y-badge.fail { background: #ef444420; color: #ef4444; }
/* Components */
.comp-screenshot { border-radius: 8px; border: 1px solid var(--border); max-width: 100%; }
/* Stat cards */
.stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; margin: 24px 0; }
.stat { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
.stat-value { font-size: 28px; font-weight: 700; color: var(--text-heading); }
.stat-label { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
.font-tag { display: inline-block; background: #1e1e2e; color: #a78bfa; padding: 4px 10px; border-radius: 6px; font-size: 13px; margin: 4px 4px 4px 0; }
[data-theme="light"] .font-tag { background: #ede9fe; color: #6d28d9; }
</style>
</head>
<body>
<div class="container">
<div class="header-row">
<h1>Research | Coulomb</h1>
<button class="theme-toggle" onclick="toggleTheme()">Toggle Light/Dark</button>
</div>
<div class="meta">
<span>https://coulomb.social</span>
<span>3445 elements</span>
<span>8/9/2026</span>
<span>7 pages crawled</span>
</div>
<div class="stats">
<div class="stat"><div class="stat-value">8</div><div class="stat-label">Colors</div></div>
<div class="stat"><div class="stat-value">4</div><div class="stat-label">Font Families</div></div>
<div class="stat"><div class="stat-value">8</div><div class="stat-label">Spacing Values</div></div>
<div class="stat"><div class="stat-value">2</div><div class="stat-label">Shadows</div></div>
<div class="stat"><div class="stat-value">5</div><div class="stat-label">Border Radii</div></div>
<div class="stat"><div class="stat-value">5</div><div class="stat-label">Components</div></div>
<div class="stat"><div class="stat-value good" style="color: #22c55e">100%</div><div class="stat-label">A11y Score</div></div>
</div>
<!-- Colors -->
<h2>Color Palette</h2>
<h3>Brand Colors</h3>
<div class="grid grid-3">
<div class="swatch">
<div class="swatch-color" style="background:#d08728"></div>
<div class="swatch-info">
<div class="swatch-hex">#d08728</div>
<div class="swatch-label">Primary</div>
<div class="swatch-label">8 uses</div>
</div>
</div>
</div>
<h3>Neutrals</h3>
<div class="grid grid-4">
<div class="swatch">
<div class="swatch-color" style="background:#000000"></div>
<div class="swatch-info">
<div class="swatch-hex">#000000</div>
<div class="swatch-label">5304 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#efefef"></div>
<div class="swatch-info">
<div class="swatch-hex">#efefef</div>
<div class="swatch-label">610 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#616161"></div>
<div class="swatch-info">
<div class="swatch-hex">#616161</div>
<div class="swatch-label">556 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#373737"></div>
<div class="swatch-info">
<div class="swatch-hex">#373737</div>
<div class="swatch-label">462 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#171717"></div>
<div class="swatch-info">
<div class="swatch-hex">#171717</div>
<div class="swatch-label">401 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#ffffff"></div>
<div class="swatch-info">
<div class="swatch-hex">#ffffff</div>
<div class="swatch-label">242 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#e5e5e5"></div>
<div class="swatch-info">
<div class="swatch-hex">#e5e5e5</div>
<div class="swatch-label">217 uses</div>
</div>
</div>
</div>
<h3>Full Palette</h3>
<div class="grid grid-4">
<div class="swatch">
<div class="swatch-color" style="background:#000000"></div>
<div class="swatch-info">
<div class="swatch-hex">#000000</div>
<div class="swatch-label">text, border, background</div>
<div class="swatch-label">5304 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#efefef"></div>
<div class="swatch-info">
<div class="swatch-hex">#efefef</div>
<div class="swatch-label">background</div>
<div class="swatch-label">610 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#616161"></div>
<div class="swatch-info">
<div class="swatch-hex">#616161</div>
<div class="swatch-label">text, border</div>
<div class="swatch-label">556 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#373737"></div>
<div class="swatch-info">
<div class="swatch-hex">#373737</div>
<div class="swatch-label">text, border</div>
<div class="swatch-label">462 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#171717"></div>
<div class="swatch-info">
<div class="swatch-hex">#171717</div>
<div class="swatch-label">text, border, background</div>
<div class="swatch-label">401 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#ffffff"></div>
<div class="swatch-info">
<div class="swatch-hex">#ffffff</div>
<div class="swatch-label">background, border, text</div>
<div class="swatch-label">242 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#e5e5e5"></div>
<div class="swatch-info">
<div class="swatch-hex">#e5e5e5</div>
<div class="swatch-label">border, background</div>
<div class="swatch-label">217 uses</div>
</div>
</div>
<div class="swatch">
<div class="swatch-color" style="background:#d08728"></div>
<div class="swatch-info">
<div class="swatch-hex">#d08728</div>
<div class="swatch-label">background</div>
<div class="swatch-label">8 uses</div>
</div>
</div>
</div>
<!-- Typography -->
<h2>Typography</h2>
<h3>Font Families</h3>
<div>
<span class="font-tag">Helvetica <span style="color:#666">(body, 1739x)</span></span><span class="font-tag">Arial <span style="color:#666">(body, 761x)</span></span><span class="font-tag">Times New Roman <span style="color:#666">(body, 450x)</span></span><span class="font-tag">Inter <span style="color:#666">(body, 397x)</span></span>
</div>
<h3>Type Scale</h3>
<div>
<div class="type-row">
<span class="type-size">50px</span>
<span style="font-size:48px;font-weight:400;color:var(--text-heading)">The quick brown fox</span>
<span class="type-meta">400 / 50px</span>
</div>
<div class="type-row">
<span class="type-size">42px</span>
<span style="font-size:42px;font-weight:700;color:var(--text-heading)">The quick brown fox</span>
<span class="type-meta">700 / normal</span>
</div>
<div class="type-row">
<span class="type-size">24px</span>
<span style="font-size:24px;font-weight:600;color:var(--text-heading)">The quick brown fox</span>
<span class="type-meta">600 / 36px</span>
</div>
<div class="type-row">
<span class="type-size">18px</span>
<span style="font-size:18px;font-weight:600;color:var(--text-heading)">The quick brown fox</span>
<span class="type-meta">600 / 18px</span>
</div>
<div class="type-row">
<span class="type-size">16px</span>
<span style="font-size:16px;font-weight:400;color:var(--text-heading)">The quick brown fox</span>
<span class="type-meta">400 / normal</span>
</div>
<div class="type-row">
<span class="type-size">14px</span>
<span style="font-size:14px;font-weight:500;color:var(--text-heading)">The quick brown fox</span>
<span class="type-meta">500 / 19.6px</span>
</div>
<div class="type-row">
<span class="type-size">13.3333px</span>
<span style="font-size:13.3333px;font-weight:400;color:var(--text-heading)">The quick brown fox</span>
<span class="type-meta">400 / normal</span>
</div>
</div>
<!-- Spacing -->
<h2>Spacing Scale (base: 4px)</h2>
<div>
<div class="spacing-row">
<span class="spacing-label">1px</span>
<div class="spacing-bar" style="width:2px"></div>
</div>
<div class="spacing-row">
<span class="spacing-label">24px</span>
<div class="spacing-bar" style="width:48px"></div>
</div>
<div class="spacing-row">
<span class="spacing-label">32px</span>
<div class="spacing-bar" style="width:64px"></div>
</div>
<div class="spacing-row">
<span class="spacing-label">40px</span>
<div class="spacing-bar" style="width:80px"></div>
</div>
<div class="spacing-row">
<span class="spacing-label">60px</span>
<div class="spacing-bar" style="width:120px"></div>
</div>
<div class="spacing-row">
<span class="spacing-label">80px</span>
<div class="spacing-bar" style="width:160px"></div>
</div>
<div class="spacing-row">
<span class="spacing-label">96px</span>
<div class="spacing-bar" style="width:192px"></div>
</div>
<div class="spacing-row">
<span class="spacing-label">320px</span>
<div class="spacing-bar" style="width:500px"></div>
</div>
</div>
<!-- Shadows -->
<h2>Box Shadows</h2>
<div class="grid grid-3">
<div class="shadow-card" style="box-shadow:rgb(170, 170, 170) 2px 2px 4px 0px">
<span class="shadow-label">sm</span>
</div>
<div class="shadow-card" style="box-shadow:rgba(23, 23, 23, 0.1) 2px 2px 8px 2px">
<span class="shadow-label">md</span>
</div>
</div>
<!-- Border Radii -->
<h2>Border Radii</h2>
<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:end">
<div style="text-align:center">
<div class="radius-item" style="border-radius:4px">4px</div>
<div style="font-size:11px;color:#666;margin-top:6px">sm</div>
</div>
<div style="text-align:center">
<div class="radius-item" style="border-radius:8px">8px</div>
<div style="font-size:11px;color:#666;margin-top:6px">md</div>
</div>
<div style="text-align:center">
<div class="radius-item" style="border-radius:12px">12px</div>
<div style="font-size:11px;color:#666;margin-top:6px">lg</div>
</div>
<div style="text-align:center">
<div class="radius-item" style="border-radius:15px">15px</div>
<div style="font-size:11px;color:#666;margin-top:6px">lg</div>
</div>
<div style="text-align:center">
<div class="radius-item" style="border-radius:20px">20px</div>
<div style="font-size:11px;color:#666;margin-top:6px">xl</div>
</div>
</div>
<!-- Accessibility -->
<h2>Accessibility</h2>
<div style="display:flex;align-items:center;gap:24px;margin-bottom:24px">
<div class="a11y-score good">100%</div>
<div>
<div style="color:#fff;font-weight:600">WCAG Contrast Score</div>
<div style="color:#666;font-size:14px">0 passing / 0 failing color pairs</div>
</div>
</div>
<h3>Color Pair Analysis</h3>
<!-- Component Screenshots -->
</div>
<script>
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme');
html.setAttribute('data-theme', current === 'light' ? 'dark' : 'light');
}
document.querySelectorAll('.swatch').forEach(el => {
el.addEventListener('click', () => {
const hex = el.querySelector('.swatch-hex');
if (!hex) return;
navigator.clipboard.writeText(hex.textContent).then(() => {
let tip = el.querySelector('.copied-tip');
if (!tip) { tip = document.createElement('div'); tip.className = 'copied-tip'; tip.textContent = 'Copied!'; el.appendChild(tip); }
tip.classList.add('show');
setTimeout(() => tip.classList.remove('show'), 1200);
});
});
});
</script>
</body>
</html>

View file

@ -0,0 +1,13 @@
Create a React artifact that reproduces this brand's design language.
Page intent: landing.
Material language: flat.
Voice: Tone: neutral · Headings: unknown · CTA verbs: [object Object], [object Object], [object Object], [object Object], [object Object], [object Object].
Colors to use: #000000, #efefef, #616161, #373737, #171717, #ffffff, #e5e5e5, #d08728.
Fonts: [object Object], [object Object], [object Object], [object Object].
Radius vocabulary: 4, 8, 12, 15, 20.
Sections:
- hero
- features
- cta
- footer
Use Tailwind via CDN, lucide-react for icons, and keep the material language consistent across sections. Do not add extra decorative elements outside this vocabulary.

View file

@ -0,0 +1,18 @@
# Design brief
Page type: **landing**.
Material language: **flat**.
Voice: Tone: neutral · Headings: unknown · CTA verbs: [object Object], [object Object], [object Object], [object Object], [object Object], [object Object].
## Tokens
```ts
export const tokens = {
colors: ['#000000', '#efefef', '#616161', '#373737', '#171717', '#ffffff', '#e5e5e5', '#d08728'],
fonts: ['[object Object]', '[object Object]', '[object Object]', '[object Object]'],
radii: ['4', '8', '12', '15', '20'],
shadows: ['rgb(170, 170, 170) 2px 2px 4px 0px', 'rgba(23, 23, 23, 0.1) 2px 2px 8px 2px'],
};
```
## Sections
- hero
- features
- cta
- footer

View file

@ -0,0 +1,12 @@
Clone the design language of this landing page and build a fresh equivalent.
Visual feel: flat. Tone: neutral · Headings: unknown · CTA verbs: [object Object], [object Object], [object Object], [object Object], [object Object], [object Object]
Primary palette: #000000, #efefef, #616161, #373737, #171717.
Typography: [object Object], [object Object], [object Object], [object Object].
Corner radius vocabulary: 4, 8, 12, 15, 20.
Shadow vocabulary: rgb(170, 170, 170) 2px 2px 4px 0px | rgba(23, 23, 23, 0.1) 2px 2px 8px 2px.
Page structure:
- hero
- features
- social proof
- cta
- footer

View file

@ -0,0 +1,28 @@
# Recipe: button
Build one button component that matches this brand.
Palette: #000000, #efefef, #616161, #373737, #171717, #ffffff
Typography: [object Object], [object Object], [object Object], [object Object]
Material: flat
Signals: Radius: 4, 8, 12, 15, 20 · Shadows: rgb(170, 170, 170) 2px 2px 4px 0px | rgba(23, 23, 23, 0.1) 2px 2px 8px 2px
## Anatomy (detected)
```json
{
"kind": "button",
"structuralHash": "button",
"instanceCount": 1,
"variants": [
{
"css": {
"background": "rgb(239, 239, 239)",
"color": "rgb(0, 0, 0)",
"padding": "1px 6px 1px 6px",
"borderRadius": "0px",
"border": "2px outset rgb(0, 0, 0)",
"fontSize": "13.3333px",
"fontWeight": "400"
},
"instanceCount": 1
}
]
}
```

View file

@ -0,0 +1,22 @@
Build a landing page with this exact visual language.
COLORS:
#000000
#efefef
#616161
#373737
#171717
#ffffff
#e5e5e5
#d08728
FONTS: [object Object], [object Object], [object Object], [object Object]
SPACING: 1, 24, 32, 40, 60, 80, 96, 320
RADIUS: 4, 8, 12, 15, 20
SHADOWS: rgb(170, 170, 170) 2px 2px 4px 0px | rgba(23, 23, 23, 0.1) 2px 2px 8px 2px
MATERIAL LANGUAGE: flat
VOICE: Tone: neutral · Headings: unknown · CTA verbs: [object Object], [object Object], [object Object], [object Object], [object Object], [object Object]
SECTIONS (in order):
- hero
- features
- cta
- footer
Use Tailwind. Match these tokens exactly. Keep the material language consistent.

View file

@ -0,0 +1,81 @@
/*
Brand-aware reset generated by designlang
Research | Coulomb
2026-08-08T23:30:22.005Z
*/
*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, h5, h6, p, ul, ol, li, blockquote, figure,
form, fieldset, table, hr {
margin: 0;
padding: 0;
}
html {
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
font-family: "Helvetica", system-ui, -apple-system, 'Segoe UI', sans-serif;
font-size: 16px;
line-height: 1.5;
}
body {
background-color: #ffffff;
color: #000000;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Helvetica", system-ui, -apple-system, sans-serif;
font-weight: 600;
line-height: 1.15;
letter-spacing: -0.015em;
}
a {
color: #d08728;
text-decoration: none;
background-color: transparent;
}
a:hover { text-decoration: underline; }
a:focus-visible { outline: 2px solid #d08728; outline-offset: 2px; border-radius: 4px; }
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
height: auto;
}
input, textarea, select, button {
font: inherit;
color: inherit;
}
button { cursor: pointer; background: none; border: 0; padding: 0; }
code, pre, kbd, samp {
font-family: ui-monospace, ui-monospace, 'SF Mono', Menlo, monospace;
}
hr {
border: 0;
border-top: 1px solid #e5e5e5;
}
::selection {
background: #d08728;
color: #ffffff;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
}

View file

@ -0,0 +1,15 @@
# Multi-route Token Reconciliation
- Routes crawled: **7**
- Shared tokens: **17**
- Total unique tokens across routes: **41**
- Cross-route drift entries: **148**
## Per-route contributions
- `/` (index): 0 added, 19 changed
- `/pg_chunk/1786106348475x662489469743530000` (pg-chunk-1786106348475x662489469743530000): 1 added, 22 changed
- `/pg_chunk/1767965374100x901547861536145400` (pg-chunk-1767965374100x901547861536145400): 0 added, 21 changed
- `/pg_chunk/1785501151364x921456713843343400` (pg-chunk-1785501151364x921456713843343400): 0 added, 21 changed
- `/pg_chunk/1784904857383x503270614975381500` (pg-chunk-1784904857383x503270614975381500): 0 added, 22 changed
- `/pg_chunk/1785501127011x363256181345746940` (pg-chunk-1785501127011x363256181345746940): 0 added, 21 changed
- `/pg_chunk/1784305925912x322414739506593800` (pg-chunk-1784305925912x322414739506593800): 0 added, 22 changed

View file

@ -0,0 +1,40 @@
{
"openGraph": {
"title": "An Ideas Come To Life Community!",
"site_name": "Coulomb Social",
"description": "A tool to brainstorm, discuss, refine implement and promote ideas.\nA social network for building stuff and influencing how stuff is built.\nA marketplace for challenges, methods and solutions.\nA platform to monetize curiosity.",
"image": "https://13a55be71f3a6a4c9ea356833df823c4.cdn.bubble.io/cdn-cgi/image/w=,h=,f=auto,dpr=1,fit=contain/f1750630995632x205964292977919070/Coulomb%20Logo.png",
"url": "https://coulomb.social/vw_pages?resume=1786231781137x912895168994024000",
"type": "website"
},
"twitter": {
"title": "An Ideas Come To Life Community!",
"site_name": "Coulomb Social",
"description": "A tool to brainstorm, discuss, refine implement and promote ideas.\nA social network for building stuff and influencing how stuff is built.\nA marketplace for challenges, methods and solutions.\nA platform to monetize curiosity.",
"image:src": "https://13a55be71f3a6a4c9ea356833df823c4.cdn.bubble.io/cdn-cgi/image/w=,h=,f=auto,dpr=1,fit=contain/f1750630995632x205964292977919070/Coulomb%20Logo.png",
"card": "summary_large_image"
},
"description": "A tool to brainstorm, discuss, refine implement and promote ideas.\nA social network for building stuff and influencing how stuff is built.\nA marketplace for challenges, methods and solutions.\nA platform to monetize curiosity.",
"canonical": null,
"themeColor": null,
"viewport": "width=device-width,initial-scale=1.0",
"favicons": [
{
"rel": "icon",
"href": "https://13a55be71f3a6a4c9ea356833df823c4.cdn.bubble.io/cdn-cgi/image/w=128,h=,f=auto,dpr=1,fit=contain/f1738533145653x421346820735372100/Coulomb%20Logo.png",
"sizes": "",
"type": "image/png"
}
],
"manifest": null,
"structuredData": [],
"score": {
"hasOg": true,
"hasTwitter": true,
"hasDescription": true,
"hasCanonical": false,
"hasStructuredData": false,
"hasFavicon": true,
"hasThemeColor": false
}
}

View file

@ -0,0 +1,15 @@
/* shadcn/ui Theme — extracted from https://coulomb.social */
/* Paste into your globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 0%;
--primary: 34 68% 49%;
--primary-foreground: 0 0% 100%;
--muted: 0 0% 90%;
--muted-foreground: 0 0% 0%;
--border: 0 0% 9%;
--radius: 8px;
}
}

View file

@ -0,0 +1,12 @@
{
"cms": [
"webflow",
"bubble"
],
"analytics": [],
"experimentation": [],
"signals": {
"scriptCount": 10,
"metaCount": 15
}
}

View file

@ -0,0 +1,55 @@
/*
Tailwind v4 @theme generated by designlang
site: Research | Coulomb
2026-08-08T23:30:22.003Z
Drop this above `@import "tailwindcss";`.
*/
@import "tailwindcss";
@theme {
--color-primary-50: hsl(34 68% 97%);
--color-primary-100: hsl(34 68% 94%);
--color-primary-200: hsl(34 68% 86%);
--color-primary-300: hsl(34 68% 76%);
--color-primary-400: hsl(34 68% 64%);
--color-primary-500: hsl(34 68% 50%);
--color-primary-600: hsl(34 68% 40%);
--color-primary-700: hsl(34 68% 32%);
--color-primary-800: hsl(34 68% 24%);
--color-primary-900: hsl(34 68% 16%);
--color-primary-950: hsl(34 68% 10%);
--color-primary: #d08728;
--color-neutral-50: #000000;
--color-neutral-100: #efefef;
--color-neutral-200: #616161;
--color-neutral-300: #373737;
--color-neutral-400: #171717;
--color-neutral-500: #ffffff;
--color-neutral-600: #e5e5e5;
--color-background: #ffffff;
--color-foreground: #000000;
--font-sans: "Helvetica", system-ui, sans-serif;
--spacing-1: 1px;
--spacing-2: 24px;
--spacing-3: 32px;
--spacing-4: 40px;
--spacing-5: 60px;
--spacing-6: 80px;
--spacing-7: 96px;
--spacing-8: 320px;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 15px;
--radius-2xl: 20px;
--shadow-sm: rgb(170, 170, 170) 2px 2px 4px 0px;
--shadow-md: rgba(23, 23, 23, 0.1) 2px 2px 8px 2px;
--animate-duration-fast: undefined;
}
/* Optional base reset that maps body/heading to the extracted tokens. */
@layer base {
html { font-family: var(--font-sans); }
body { background-color: var(--color-background); color: var(--color-foreground, currentColor); }
}

View file

@ -0,0 +1,112 @@
/** @type {import('tailwindcss').Config} */
export default {
theme: {
extend: {
colors: {
primary: {
'50': 'hsl(34, 68%, 97%)',
'100': 'hsl(34, 68%, 94%)',
'200': 'hsl(34, 68%, 86%)',
'300': 'hsl(34, 68%, 76%)',
'400': 'hsl(34, 68%, 64%)',
'500': 'hsl(34, 68%, 50%)',
'600': 'hsl(34, 68%, 40%)',
'700': 'hsl(34, 68%, 32%)',
'800': 'hsl(34, 68%, 24%)',
'900': 'hsl(34, 68%, 16%)',
'950': 'hsl(34, 68%, 10%)',
DEFAULT: '#d08728'
},
'neutral-50': '#000000',
'neutral-100': '#efefef',
'neutral-200': '#616161',
'neutral-300': '#373737',
'neutral-400': '#171717',
'neutral-500': '#ffffff',
'neutral-600': '#e5e5e5',
background: '#ffffff',
foreground: '#000000'
},
fontFamily: {
body: [
'Inter',
'sans-serif'
]
},
fontSize: {
'14': [
'14px',
{
lineHeight: '19.6px'
}
],
'16': [
'16px',
{
lineHeight: 'normal'
}
],
'18': [
'18px',
{
lineHeight: '18px'
}
],
'24': [
'24px',
{
lineHeight: '36px'
}
],
'42': [
'42px',
{
lineHeight: 'normal'
}
],
'50': [
'50px',
{
lineHeight: '50px'
}
],
'13.3333': [
'13.3333px',
{
lineHeight: 'normal'
}
]
},
spacing: {
'6': '24px',
'8': '32px',
'10': '40px',
'15': '60px',
'20': '80px',
'24': '96px',
'80': '320px',
'1px': '1px'
},
borderRadius: {
sm: '4px',
md: '8px',
lg: '15px',
xl: '20px'
},
boxShadow: {
sm: 'rgb(170, 170, 170) 2px 2px 4px 0px',
md: 'rgba(23, 23, 23, 0.1) 2px 2px 8px 2px'
},
transitionDuration: {
'250': '0.25s'
},
container: {
center: true,
padding: '0px'
},
maxWidth: {
container: '1200px'
}
},
},
};

View file

@ -0,0 +1,171 @@
// React Theme — extracted from https://coulomb.social
// Compatible with: Chakra UI, Stitches, Vanilla Extract, or any CSS-in-JS
/**
* TypeScript type definition for this theme:
*
* interface Theme {
* colors: {
primary: string;
background: string;
foreground: string;
neutral50: string;
neutral100: string;
neutral200: string;
neutral300: string;
neutral400: string;
neutral500: string;
neutral600: string;
* };
* fonts: {
body: string;
* };
* fontSizes: {
'14': string;
'16': string;
'18': string;
'24': string;
'42': string;
'50': string;
'13.3333': string;
* };
* space: {
'1': string;
'24': string;
'32': string;
'40': string;
'60': string;
'80': string;
'96': string;
'320': string;
* };
* radii: {
sm: string;
md: string;
lg: string;
xl: string;
* };
* shadows: {
sm: string;
md: string;
* };
* states: {
* hover: { opacity: number };
* focus: { opacity: number };
* active: { opacity: number };
* disabled: { opacity: number };
* };
* }
*/
export const theme = {
"colors": {
"primary": "#d08728",
"background": "#ffffff",
"foreground": "#000000",
"neutral50": "#000000",
"neutral100": "#efefef",
"neutral200": "#616161",
"neutral300": "#373737",
"neutral400": "#171717",
"neutral500": "#ffffff",
"neutral600": "#e5e5e5"
},
"fonts": {
"body": "'Inter', sans-serif"
},
"fontSizes": {
"14": "14px",
"16": "16px",
"18": "18px",
"24": "24px",
"42": "42px",
"50": "50px",
"13.3333": "13.3333px"
},
"space": {
"1": "1px",
"24": "24px",
"32": "32px",
"40": "40px",
"60": "60px",
"80": "80px",
"96": "96px",
"320": "320px"
},
"radii": {
"sm": "4px",
"md": "8px",
"lg": "15px",
"xl": "20px"
},
"shadows": {
"sm": "rgb(170, 170, 170) 2px 2px 4px 0px",
"md": "rgba(23, 23, 23, 0.1) 2px 2px 8px 2px"
},
"states": {
"hover": {
"opacity": 0.08
},
"focus": {
"opacity": 0.12
},
"active": {
"opacity": 0.16
},
"disabled": {
"opacity": 0.38
}
}
};
// MUI v5 theme
export const muiTheme = {
"palette": {
"primary": {
"main": "#d08728",
"light": "hsl(34, 68%, 64%)",
"dark": "hsl(34, 68%, 34%)"
},
"background": {
"default": "#ffffff",
"paper": "#e5e5e5"
},
"text": {
"primary": "#000000",
"secondary": "#171717"
}
},
"typography": {
"fontFamily": "'Helvetica', sans-serif",
"h1": {
"fontSize": "42px",
"fontWeight": "700",
"lineHeight": "normal"
},
"h2": {
"fontSize": "24px",
"fontWeight": "600",
"lineHeight": "36px"
},
"body1": {
"fontSize": "16px",
"fontWeight": "400",
"lineHeight": "normal"
},
"body2": {
"fontSize": "14px",
"fontWeight": "500",
"lineHeight": "19.6px"
}
},
"shape": {
"borderRadius": 8
},
"shadows": [
"rgb(170, 170, 170) 2px 2px 4px 0px",
"rgba(23, 23, 23, 0.1) 2px 2px 8px 2px"
]
};
export default theme;

View file

@ -0,0 +1,82 @@
{
"url": "https://coulomb.social",
"path": "/",
"added": {},
"changed": {
"primitive": {
"color": {
"brand": {
"primary": {
"$value": "#000000"
}
},
"neutral": {
"n200": {
"$value": "#373737"
},
"n300": {
"$value": "#616161"
},
"n400": {
"$value": "#171717"
},
"n500": {
"$value": "#efefef"
},
"n600": {
"$value": "#ffffff"
},
"n700": {
"$value": "#e5e5e5"
}
},
"background": {
"bg1": {
"$value": "#e5e5e5"
}
},
"text": {
"text2": {
"$value": "#373737"
},
"text3": {
"$value": "#616161"
}
}
},
"spacing": {
"s1": {
"$value": "24px"
},
"s2": {
"$value": "32px"
},
"s3": {
"$value": "40px"
},
"s4": {
"$value": "82px"
},
"s5": {
"$value": "96px"
}
},
"radius": {
"r3": {
"$value": "20px"
}
},
"fontFamily": {
"f1": {
"$value": "Inter"
},
"f2": {
"$value": "Arial"
},
"f3": {
"$value": "Times New Roman"
}
}
}
}
}

View file

@ -0,0 +1,88 @@
{
"url": "https://coulomb.social/pg_chunk/1767965374100x901547861536145400",
"path": "/pg_chunk/1767965374100x901547861536145400",
"added": {},
"changed": {
"primitive": {
"color": {
"brand": {
"primary": {
"$value": "#d08728"
}
},
"neutral": {
"n200": {
"$value": "#efefef"
},
"n300": {
"$value": "#e5e5e5"
},
"n400": {
"$value": "#616161"
},
"n500": {
"$value": "#171717"
},
"n600": {
"$value": "#ffffff"
},
"n700": {
"$value": "#373737"
}
},
"background": {
"bg1": {
"$value": "#171717"
},
"bg2": {
"$value": "#f5f5f5"
},
"bg3": {
"$value": "#e5e5e5"
}
},
"text": {
"text2": {
"$value": "#616161"
},
"text3": {
"$value": "#373737"
}
}
},
"spacing": {
"s1": {
"$value": "60px"
},
"s2": {
"$value": "80px"
},
"s3": {
"$value": "96px"
},
"s4": {
"$value": "320px"
}
},
"radius": {
"r3": {
"$value": "15px"
},
"r4": {
"$value": "20px"
}
},
"fontFamily": {
"f1": {
"$value": "Arial"
},
"f2": {
"$value": "Times New Roman"
},
"f3": {
"$value": "Inter"
}
}
}
}
}

View file

@ -0,0 +1,91 @@
{
"url": "https://coulomb.social/pg_chunk/1784305925912x322414739506593800",
"path": "/pg_chunk/1784305925912x322414739506593800",
"added": {},
"changed": {
"primitive": {
"color": {
"brand": {
"primary": {
"$value": "#d08728"
}
},
"neutral": {
"n200": {
"$value": "#efefef"
},
"n300": {
"$value": "#616161"
},
"n400": {
"$value": "#373737"
},
"n500": {
"$value": "#e5e5e5"
},
"n600": {
"$value": "#171717"
},
"n700": {
"$value": "#ffffff"
}
},
"background": {
"bg1": {
"$value": "#171717"
},
"bg2": {
"$value": "#f5f5f5"
},
"bg3": {
"$value": "#e5e5e5"
}
},
"text": {
"text2": {
"$value": "#616161"
},
"text3": {
"$value": "#373737"
},
"text4": {
"$value": "#ffffff"
}
}
},
"spacing": {
"s1": {
"$value": "60px"
},
"s2": {
"$value": "80px"
},
"s3": {
"$value": "96px"
},
"s4": {
"$value": "320px"
}
},
"radius": {
"r3": {
"$value": "15px"
},
"r4": {
"$value": "20px"
}
},
"fontFamily": {
"f1": {
"$value": "Arial"
},
"f2": {
"$value": "Times New Roman"
},
"f3": {
"$value": "Inter"
}
}
}
}
}

View file

@ -0,0 +1,91 @@
{
"url": "https://coulomb.social/pg_chunk/1784904857383x503270614975381500",
"path": "/pg_chunk/1784904857383x503270614975381500",
"added": {},
"changed": {
"primitive": {
"color": {
"brand": {
"primary": {
"$value": "#d08728"
}
},
"neutral": {
"n200": {
"$value": "#efefef"
},
"n300": {
"$value": "#616161"
},
"n400": {
"$value": "#373737"
},
"n500": {
"$value": "#e5e5e5"
},
"n600": {
"$value": "#171717"
},
"n700": {
"$value": "#ffffff"
}
},
"background": {
"bg1": {
"$value": "#171717"
},
"bg2": {
"$value": "#f5f5f5"
},
"bg3": {
"$value": "#e5e5e5"
}
},
"text": {
"text2": {
"$value": "#616161"
},
"text3": {
"$value": "#373737"
},
"text4": {
"$value": "#ffffff"
}
}
},
"spacing": {
"s1": {
"$value": "60px"
},
"s2": {
"$value": "80px"
},
"s3": {
"$value": "96px"
},
"s4": {
"$value": "320px"
}
},
"radius": {
"r3": {
"$value": "15px"
},
"r4": {
"$value": "20px"
}
},
"fontFamily": {
"f1": {
"$value": "Arial"
},
"f2": {
"$value": "Times New Roman"
},
"f3": {
"$value": "Inter"
}
}
}
}
}

View file

@ -0,0 +1,88 @@
{
"url": "https://coulomb.social/pg_chunk/1785501127011x363256181345746940",
"path": "/pg_chunk/1785501127011x363256181345746940",
"added": {},
"changed": {
"primitive": {
"color": {
"brand": {
"primary": {
"$value": "#d08728"
}
},
"neutral": {
"n200": {
"$value": "#efefef"
},
"n300": {
"$value": "#e5e5e5"
},
"n400": {
"$value": "#616161"
},
"n500": {
"$value": "#171717"
},
"n600": {
"$value": "#ffffff"
},
"n700": {
"$value": "#373737"
}
},
"background": {
"bg1": {
"$value": "#171717"
},
"bg2": {
"$value": "#f5f5f5"
},
"bg3": {
"$value": "#e5e5e5"
}
},
"text": {
"text2": {
"$value": "#616161"
},
"text3": {
"$value": "#373737"
}
}
},
"spacing": {
"s1": {
"$value": "60px"
},
"s2": {
"$value": "80px"
},
"s3": {
"$value": "96px"
},
"s4": {
"$value": "320px"
}
},
"radius": {
"r3": {
"$value": "15px"
},
"r4": {
"$value": "20px"
}
},
"fontFamily": {
"f1": {
"$value": "Arial"
},
"f2": {
"$value": "Times New Roman"
},
"f3": {
"$value": "Inter"
}
}
}
}
}

View file

@ -0,0 +1,88 @@
{
"url": "https://coulomb.social/pg_chunk/1785501151364x921456713843343400",
"path": "/pg_chunk/1785501151364x921456713843343400",
"added": {},
"changed": {
"primitive": {
"color": {
"brand": {
"primary": {
"$value": "#d08728"
}
},
"neutral": {
"n200": {
"$value": "#efefef"
},
"n300": {
"$value": "#e5e5e5"
},
"n400": {
"$value": "#171717"
},
"n500": {
"$value": "#616161"
},
"n600": {
"$value": "#ffffff"
},
"n700": {
"$value": "#373737"
}
},
"background": {
"bg1": {
"$value": "#171717"
},
"bg2": {
"$value": "#f5f5f5"
},
"bg3": {
"$value": "#e5e5e5"
}
},
"text": {
"text2": {
"$value": "#616161"
},
"text3": {
"$value": "#373737"
},
"text4": {
"$value": "#ffffff"
}
}
},
"spacing": {
"s1": {
"$value": "60px"
},
"s2": {
"$value": "80px"
},
"s3": {
"$value": "96px"
},
"s4": {
"$value": "320px"
}
},
"radius": {
"r3": {
"$value": "15px"
}
},
"fontFamily": {
"f1": {
"$value": "Arial"
},
"f2": {
"$value": "Times New Roman"
},
"f3": {
"$value": "Inter"
}
}
}
}
}

View file

@ -0,0 +1,99 @@
{
"url": "https://coulomb.social/pg_chunk/1786106348475x662489469743530000",
"path": "/pg_chunk/1786106348475x662489469743530000",
"added": {
"primitive": {
"spacing": {
"s6": {
"$value": "320px"
}
}
}
},
"changed": {
"primitive": {
"color": {
"brand": {
"primary": {
"$value": "#d08728"
}
},
"neutral": {
"n200": {
"$value": "#efefef"
},
"n300": {
"$value": "#616161"
},
"n400": {
"$value": "#171717"
},
"n500": {
"$value": "#ffffff"
},
"n600": {
"$value": "#373737"
},
"n700": {
"$value": "#e5e5e5"
}
},
"background": {
"bg1": {
"$value": "#171717"
},
"bg2": {
"$value": "#f5f5f5"
},
"bg3": {
"$value": "#e5e5e5"
}
},
"text": {
"text2": {
"$value": "#616161"
},
"text3": {
"$value": "#373737"
}
}
},
"spacing": {
"s1": {
"$value": "24px"
},
"s2": {
"$value": "32px"
},
"s3": {
"$value": "40px"
},
"s4": {
"$value": "80px"
},
"s5": {
"$value": "96px"
}
},
"radius": {
"r3": {
"$value": "15px"
},
"r4": {
"$value": "20px"
}
},
"fontFamily": {
"f1": {
"$value": "Arial"
},
"f2": {
"$value": "Times New Roman"
},
"f3": {
"$value": "Inter"
}
}
}
}
}

View file

@ -0,0 +1,92 @@
{
"primitive": {
"color": {
"neutral": {
"n100": {
"$value": "#000000"
}
},
"background": {
"bg0": {
"$value": "#ffffff"
}
},
"text": {
"text0": {
"$value": "#000000"
},
"text1": {
"$value": "#171717"
}
}
},
"spacing": {
"s0": {
"$value": "1px"
}
},
"radius": {
"r0": {
"$value": "4px"
},
"r1": {
"$value": "8px"
},
"r2": {
"$value": "12px"
}
},
"shadow": {
"sh0": {
"$value": "rgb(170, 170, 170) 2px 2px 4px 0px"
},
"sh1": {
"$value": "rgba(23, 23, 23, 0.1) 2px 2px 8px 2px"
}
},
"fontFamily": {
"f0": {
"$value": "Helvetica"
}
}
},
"semantic": {
"color": {
"action": {
"primary": {
"$value": "{primitive.color.brand.primary}"
}
},
"surface": {
"default": {
"$value": "{primitive.color.background.bg0}"
}
},
"text": {
"body": {
"$value": "{primitive.color.text.text0}"
}
}
},
"typography": {
"body": {
"$value": {
"fontFamily": "Helvetica",
"fontSize": "50px",
"fontWeight": "400",
"lineHeight": "50px"
}
}
},
"radius": {
"control": {
"$value": "{primitive.radius.r0}"
}
},
"shadow": {
"elevated": {
"$value": "{primitive.shadow.sh0}"
}
}
}
}

View file

@ -0,0 +1,49 @@
// Auto-generated by designlang. Do not edit by hand.
// Re-extract with `npx designlang <url>` to refresh.
//
// Import next to your token JSON to get type-safe access:
//
// import type { ColorToken, SpacingToken } from './<host>-tokens';
// import tokens from './<host>-design-tokens.json';
//
// Source: https://coulomb.social
// Generated: 2026-08-08T23:30:22.004Z
export type Hex = `#${string}`;
export type Px = `${number}px`;
export type Ms = `${number}ms`;
export type ColorRole = 'primary';
export type ColorHex = '#000000' | '#efefef' | '#616161' | '#373737' | '#171717' | '#ffffff' | '#e5e5e5' | '#d08728';
export type ColorToken = ColorRole | ColorHex;
export type FontFamilyToken = 'Helvetica' | 'Arial' | 'Times New Roman' | 'Inter';
export type FontSizeToken = never;
export type FontWeightToken = '400' | '500' | '600' | '700';
export type SpacingToken = '1px' | '24px' | '32px' | '40px' | '60px' | '80px' | '96px' | '320px';
export type RadiusToken = '4px' | '8px' | '12px' | '15px' | '20px';
export type DurationToken = '250ms';
export type EasingToken = never;
export type MaterialLanguage = 'flat';
export interface DesignTokens {
color: { [role in ColorRole]: ColorHex };
typography: {
family: FontFamilyToken;
size: FontSizeToken;
weight: FontWeightToken;
};
spacing: ReadonlyArray<SpacingToken>;
radius: ReadonlyArray<RadiusToken>;
motion: {
duration: ReadonlyArray<DurationToken>;
easing: ReadonlyArray<EasingToken>;
};
}
declare const tokens: DesignTokens;
export default tokens;

View file

@ -0,0 +1,106 @@
:root {
/* Colors — Primary */
--color-primary: #d08728;
/* Colors — Neutrals */
--color-neutral-50: #000000;
--color-neutral-100: #efefef;
--color-neutral-200: #616161;
--color-neutral-300: #373737;
--color-neutral-400: #171717;
--color-neutral-500: #ffffff;
--color-neutral-600: #e5e5e5;
/* Colors — Backgrounds */
--color-bg: #ffffff;
--color-bg-1: #e5e5e5;
--color-bg-2: #171717;
--color-bg-3: #f5f5f5;
/* Colors — Text */
--color-text: #000000;
--color-text-1: #171717;
--color-text-2: #373737;
--color-text-3: #616161;
--color-text-4: #ffffff;
/* Typography — Families */
--font-body: 'Helvetica', sans-serif;
--font-body: 'Arial', sans-serif;
--font-body: 'Times New Roman', sans-serif;
--font-body: 'Inter', sans-serif;
/* Typography — Scale */
--font-size-50: 50px;
--font-size-42: 42px;
--font-size-24: 24px;
--font-size-18: 18px;
--font-size-16: 16px;
--font-size-14: 14px;
--font-size-13.3333: 13.3333px;
/* Spacing */
--spacing-1: 1px;
--spacing-24: 24px;
--spacing-32: 32px;
--spacing-40: 40px;
--spacing-60: 60px;
--spacing-80: 80px;
--spacing-96: 96px;
--spacing-320: 320px;
/* Border Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-lg: 15px;
--radius-xl: 20px;
/* Box Shadows */
--shadow-sm: rgb(170, 170, 170) 2px 2px 4px 0px;
--shadow-md: rgba(23, 23, 23, 0.1) 2px 2px 8px 2px;
/* Original Site Variables */
/* — colors — */
--color_primary_default: rgba(2, 5, 211, 1);
--color_primary_default_rgb: 2, 5, 211;
--color_primary_contrast_default: rgba(255, 255, 255, 1);
--color_primary_contrast_default_rgb: 255, 255, 255;
--color_text_default: rgba(9, 23, 71, 1);
--color_text_default_rgb: 9, 23, 71;
--color_surface_default: rgba(255, 255, 255, 1);
--color_surface_default_rgb: 255, 255, 255;
--color_background_default: rgba(255, 255, 255, 0);
--color_background_default_rgb: 255, 255, 255;
--color_destructive_default: rgba(255, 0, 0, 1);
--color_destructive_default_rgb: 255, 0, 0;
--color_success_default: rgba(23, 219, 78, 1);
--color_success_default_rgb: 23, 219, 78;
--color_alert_default: rgba(250, 181, 21, 1);
--color_alert_default_rgb: 250, 181, 21;
--color_danger_default_rgb: rgba(176, 32, 12, 1);
--color_bTRUr_default_rgb: 208,135,40;
--color_bTOCs_default: rgba(55,55,55,1);
--color_bTHZK_default_rgb: 229,229,229;
--color_bTOCs_default_rgb: 55,55,55;
--color_bTINZ_default_rgb: 23,23,23;
--color_bTRUs_default_rgb: 227,230,235;
--color_bTINZ_default: rgba(23,23,23,0.36);
--color_bTRUr_default: rgba(208,135,40,0.1);
--color_bTHZK_default: rgba(229,229,229,1);
--color_bTHfJ_default_rgb: 97,97,97;
--color_bTMap_default: rgba(113,2,139,1);
--color_bTHfJ_default: rgba(97,97,97,1);
--color_bTMap_default_rgb: 113,2,139;
--color_bTRUs_default: rgba(227,230,235,1);
/* — typography — */
--font_default: 'Lato';
--font_bTRUx_default: "Inter";
--font_bTRUt_default: "Inter";
/* — semantic — */
success: [object Object];
warning: [object Object];
error: [object Object];
info: [object Object];
}

View file

@ -0,0 +1,53 @@
{
"materialLanguage": {
"label": "flat",
"confidence": 0,
"signals": [],
"metrics": {
"saturation": 0.101,
"shadowProfile": "soft",
"avgShadowBlur": 0,
"maxShadowBlur": 0,
"insetShadows": 0,
"avgRadius": 11.8,
"maxRadius": 20,
"hasPill": false,
"hasBackdropBlur": false,
"gradientCount": 0
},
"alternates": []
},
"imageryStyle": {
"label": "mixed",
"confidence": 0,
"counts": {
"total": 29,
"svg": 0,
"icon": 1,
"hero": 0,
"screenshot": 0,
"photoLike": 0
},
"dominantAspect": "square-ish",
"radiusProfile": "soft",
"alternates": [],
"signals": []
},
"backgroundPatterns": {
"labels": [
"plain"
],
"counts": {
"noise": 0,
"dotGrid": 0,
"lineGrid": 0,
"meshCount": 0,
"svgPattern": 0
},
"gradientTotals": {
"radial": 0,
"linear": 0
},
"samples": []
}
}

View file

@ -0,0 +1,71 @@
{
"tone": "neutral",
"pronoun": "third-person",
"headingStyle": "unknown",
"headingLengthClass": "tight",
"ctaVerbs": [
{
"value": "content",
"count": 25
},
{
"value": "favorite",
"count": 25
},
{
"value": "format",
"count": 1
},
{
"value": "filter",
"count": 1
},
{
"value": "add",
"count": 1
},
{
"value": "arrow",
"count": 1
},
{
"value": "note",
"count": 1
}
],
"buttonPatterns": [
{
"value": "content_copy",
"count": 25
},
{
"value": "favorite_border",
"count": 25
},
{
"value": "format_line_spacing",
"count": 1
},
{
"value": "filter_list",
"count": 1
},
{
"value": "add_circle_outline",
"count": 1
},
{
"value": "arrow_drop_down",
"count": 1
},
{
"value": "note_alt",
"count": 1
}
],
"sampleHeadings": [],
"stats": {
"buttons": 55,
"headings": 0
}
}

View file

@ -0,0 +1,175 @@
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"color": {
"palette": [
{
"slug": "primary",
"color": "#d08728",
"name": "Primary"
},
{
"slug": "neutral-1",
"color": "#000000",
"name": "Neutral 1"
},
{
"slug": "neutral-2",
"color": "#efefef",
"name": "Neutral 2"
},
{
"slug": "neutral-3",
"color": "#616161",
"name": "Neutral 3"
},
{
"slug": "neutral-4",
"color": "#373737",
"name": "Neutral 4"
},
{
"slug": "neutral-5",
"color": "#171717",
"name": "Neutral 5"
},
{
"slug": "bg-ffffff",
"color": "#ffffff",
"name": "Background #ffffff"
},
{
"slug": "bg-e5e5e5",
"color": "#e5e5e5",
"name": "Background #e5e5e5"
},
{
"slug": "bg-171717",
"color": "#171717",
"name": "Background #171717"
}
],
"gradients": []
},
"typography": {
"fontFamilies": [
{
"fontFamily": "Helvetica",
"slug": "helvetica",
"name": "Helvetica"
},
{
"fontFamily": "Arial",
"slug": "arial",
"name": "Arial"
},
{
"fontFamily": "Times New Roman",
"slug": "times-new-roman",
"name": "Times New Roman"
},
{
"fontFamily": "Inter",
"slug": "inter",
"name": "Inter"
}
],
"fontSizes": [
{
"size": "50px",
"slug": "size-50",
"name": "50px"
},
{
"size": "42px",
"slug": "size-42",
"name": "42px"
},
{
"size": "24px",
"slug": "size-24",
"name": "24px"
},
{
"size": "18px",
"slug": "size-18",
"name": "18px"
},
{
"size": "16px",
"slug": "size-16",
"name": "16px"
},
{
"size": "14px",
"slug": "size-14",
"name": "14px"
},
{
"size": "13.3333px",
"slug": "size-13.3333",
"name": "13.3333px"
}
]
},
"spacing": {
"spacingSizes": [
{
"size": "1px",
"slug": "spacing-1",
"name": "1px"
},
{
"size": "24px",
"slug": "spacing-24",
"name": "24px"
},
{
"size": "32px",
"slug": "spacing-32",
"name": "32px"
},
{
"size": "40px",
"slug": "spacing-40",
"name": "40px"
},
{
"size": "60px",
"slug": "spacing-60",
"name": "60px"
},
{
"size": "80px",
"slug": "spacing-80",
"name": "80px"
},
{
"size": "96px",
"slug": "spacing-96",
"name": "96px"
},
{
"size": "320px",
"slug": "spacing-320",
"name": "320px"
}
]
},
"layout": {
"contentSize": "1200px",
"wideSize": "1400px"
}
},
"styles": {
"color": {
"background": "#ffffff",
"text": "#000000"
},
"typography": {
"fontFamily": "Helvetica"
},
"spacing": {}
}
}

View file

@ -0,0 +1,795 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>coulomb.social · brand guidelines</title>
<meta name="description" content="Design-system documentation for coulomb.social: colour, type, spacing, motion, voice, accessibility, components, and drop-in tokens.">
<meta property="og:title" content="coulomb.social · brand guidelines">
<meta property="og:description" content="Design-system documentation for coulomb.social: colour, type, spacing, motion, voice, accessibility, components, and drop-in tokens.">
<meta property="og:type" content="article">
<meta name="twitter:card" content="summary_large_image">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument%20Serif&family=Inter:wght@400;500;600&family=JetBrains%20Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--paper: #f6f3ec;
--paper-2: #efebe1;
--ink: #131313;
--ink-soft: #555048;
--ink-faint: #8a8579;
--rule: #e0dccf;
--accent: #d08728;
--display: 'Instrument Serif', 'Times New Roman', serif;
--body: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--mono: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace;
}
[data-theme="dark"] {
--paper: #0d0c0a;
--paper-2: #15140f;
--ink: #ece8de;
--ink-soft: #9d978a;
--ink-faint: #5b574e;
--rule: #292621;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
html, body { margin: 0; padding: 0; }
body {
background: var(--paper);
color: var(--ink);
font-family: var(--body);
font-size: 16px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background .25s, color .25s;
}
a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--rule); padding-bottom: 1px; }
a:hover { border-color: var(--ink); }
code { font-family: var(--mono); font-size: 0.92em; }
.muted { color: var(--ink-soft); }
.summary { font-family: var(--mono); font-size: 12px; letter-spacing: .04em; color: var(--ink-faint); margin: 0 0 32px; padding-bottom: 14px; border-bottom: 1px solid var(--rule); }
.wrap { max-width: 880px; margin: 0 auto; padding: 0 0 96px; }
/* — Top bar — */
.topbar { display: flex; justify-content: space-between; align-items: center; padding: 24px 40px; font-size: 13px; border-bottom: 1px solid var(--rule); }
.brand { font-family: var(--display); font-size: 22px; }
.brand a { color: var(--ink); text-decoration: none; border-bottom: 0; }
.topbar nav { display: flex; gap: 18px; align-items: center; color: var(--ink-soft); }
.theme-btn { background: transparent; border: 1px solid var(--rule); color: var(--ink-soft); font-size: 11px; padding: 6px 12px; border-radius: 999px; cursor: pointer; letter-spacing: .12em; text-transform: uppercase; font-family: var(--body); }
.theme-btn:hover { color: var(--ink); border-color: var(--ink); }
/* — Cover — */
.cover { padding: 0; border-bottom: 1px solid var(--rule); }
.cover-band {
height: clamp(180px, 28vw, 320px);
display: flex;
align-items: flex-end;
justify-content: space-between;
padding: 32px 40px;
}
.cover-band-label {
font-family: var(--mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: .18em;
opacity: 0.78;
}
.cover-band-hex code { font-family: var(--mono); font-size: 13px; opacity: 0.9; }
.cover-body { padding: 56px 40px 64px; }
.kicker { font-family: var(--mono); text-transform: uppercase; letter-spacing: .18em; font-size: 11px; color: var(--ink-soft); margin: 0 0 18px; }
.cover-title {
font-family: var(--display);
font-weight: 400;
font-size: clamp(56px, 11vw, 132px);
line-height: .94;
letter-spacing: -.02em;
margin: 0 0 24px;
}
.cover-sub { font-size: 19px; line-height: 1.5; color: var(--ink-soft); max-width: 60ch; margin: 0 0 36px; }
.cover-meta { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 22px 32px; padding: 28px 0 0; border-top: 1px solid var(--rule); margin: 0; }
.cover-meta dt { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .14em; color: var(--ink-faint); margin-bottom: 4px; }
.cover-meta dd { margin: 0; font-size: 15px; }
/* — TOC — */
.toc { padding: 56px 40px; border-bottom: 1px solid var(--rule); background: var(--paper-2); }
.toc-title { font-family: var(--display); font-weight: 400; font-size: 28px; margin: 0 0 24px; }
.toc ol { list-style: none; padding: 0; margin: 0; columns: 2; column-gap: 48px; }
@media (max-width: 640px) { .toc ol { columns: 1; } .toc, .topbar, .cover-band, .cover-body { padding-left: 22px; padding-right: 22px; } }
.toc li { padding: 10px 0; border-top: 1px solid var(--rule); break-inside: avoid; }
.toc li:last-child { border-bottom: 1px solid var(--rule); }
.toc a { display: flex; gap: 14px; align-items: baseline; border: 0; }
.toc-num { font-family: var(--mono); font-size: 11px; color: var(--ink-faint); width: 26px; }
.toc-label { font-family: var(--display); font-size: 18px; }
/* — Sections — */
section { padding: 72px 40px; border-bottom: 1px solid var(--rule); }
section:last-of-type { border-bottom: 0; }
@media (max-width: 640px) { section { padding: 48px 22px; } }
.chapter-header { padding-bottom: 18px; margin-bottom: 28px; border-bottom: 1px solid var(--rule); }
.sec-num { font-family: var(--mono); font-size: 11px; letter-spacing: .14em; color: var(--ink-faint); text-transform: uppercase; display: block; margin-bottom: 10px; }
section h2 { font-family: var(--display); font-weight: 400; font-size: clamp(36px, 4.5vw, 56px); line-height: 1.04; letter-spacing: -.005em; margin: 0; }
.sub { font-family: var(--display); font-weight: 400; font-size: 22px; margin: 36px 0 14px; }
/* — Meta grid — */
.meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 18px 32px; padding: 0; margin: 0 0 24px; }
.meta-grid > div { padding: 0; }
.meta-grid dt { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .12em; color: var(--ink-faint); margin-bottom: 4px; }
.meta-grid dd { margin: 0; font-size: 15px; }
/* — Color section — */
.brand-grid { display: grid; gap: 20px; margin: 0 0 28px; }
.brand-grid-pair { grid-template-columns: 1fr 1fr; }
@media (max-width: 640px) { .brand-grid-pair { grid-template-columns: 1fr; } }
.brand-color { display: flex; flex-direction: column; gap: 14px; }
.big-swatch {
aspect-ratio: 1.9 / 1;
border-radius: 8px;
padding: 22px 24px;
display: flex;
flex-direction: column;
justify-content: space-between;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.06);
}
.brand-color-primary .big-swatch { aspect-ratio: 2.6 / 1; }
.big-swatch-name { font-family: var(--display); font-size: 32px; line-height: 1; }
.big-swatch-hex { font-family: var(--mono); font-size: 14px; letter-spacing: .04em; opacity: 0.92; }
.color-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 0 24px; padding: 0; margin: 0; }
.color-meta dt { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .12em; color: var(--ink-faint); margin-bottom: 2px; }
.color-meta dd { margin: 0 0 10px; font-size: 14px; }
.neutral-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); border: 1px solid var(--rule); border-radius: 6px; overflow: hidden; }
.neutral-cell { aspect-ratio: 1.8 / 1; display: flex; align-items: flex-end; padding: 10px 12px; }
.neutral-cell code { font-size: 11px; }
.palette-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 6px; }
.mini-swatch { aspect-ratio: 1.6 / 1; border-radius: 5px; padding: 9px 11px; display: flex; align-items: flex-end; box-shadow: inset 0 0 0 1px rgba(0,0,0,.06); }
.mini-swatch code { font-size: 10px; opacity: 0.92; }
.callout {
margin-top: 32px;
padding: 14px 18px;
background: rgba(0,0,0,.03);
border-left: 2px solid var(--accent);
border-radius: 0 4px 4px 0;
font-size: 14px;
}
[data-theme="dark"] .callout { background: rgba(255,255,255,.04); }
/* — Logo — */
.logo-card { display: grid; grid-template-columns: 2fr 1fr; gap: 24px; align-items: center; padding: 24px 0; }
@media (max-width: 640px) { .logo-card { grid-template-columns: 1fr; } }
.logo-canvas { aspect-ratio: 16 / 9; background: var(--paper-2); border-radius: 8px; display: flex; align-items: center; justify-content: center; padding: 32px; box-shadow: inset 0 0 0 1px var(--rule); }
.logo-canvas svg { max-width: 60%; max-height: 80%; }
.logo-placeholder { font-family: var(--display); font-size: 36px; color: var(--ink-soft); }
.logo-meta { grid-template-columns: 1fr; }
/* — Type — */
.specimen { padding: 18px 0 26px; }
.spec-line { line-height: 1.05; margin: 0 0 18px; letter-spacing: -.01em; }
.spec-display { font-weight: 400; }
.spec-body { color: var(--ink-soft); font-style: italic; }
.scale-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.scale-table th, .scale-table td { padding: 14px 8px; text-align: left; border-bottom: 1px solid var(--rule); vertical-align: middle; }
.scale-table th { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .12em; color: var(--ink-faint); border-bottom: 1px solid var(--ink); }
.scale-table .t-step { width: 64px; }
.scale-table .t-size { width: 80px; }
.scale-table .t-sample { color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* — Spacing — */
.space-rhythm { display: flex; flex-direction: column; gap: 10px; padding: 8px 0; }
.space-step { display: flex; align-items: center; gap: 14px; }
.space-block { height: 14px; background: var(--ink); border-radius: 2px; flex: 0 0 auto; }
[data-theme="dark"] .space-block { background: var(--ink); }
.space-step code { font-size: 11px; color: var(--ink-soft); min-width: 56px; }
/* — Shape — */
.radii-row, .shadows-row { display: grid; gap: 16px; }
.radii-row { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.shadows-row { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
.radius-card, .shadow-card { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.radius-block { width: 96px; height: 96px; background: var(--paper-2); box-shadow: inset 0 0 0 1px var(--rule); }
.shadow-block { width: 100%; height: 96px; background: var(--paper); border-radius: 10px; }
[data-theme="dark"] .shadow-block { background: var(--paper-2); }
.radius-card code, .shadow-card code { font-size: 11px; color: var(--ink-soft); }
/* — Iconography — */
.icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); gap: 10px; }
.icon-cell { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; border: 1px solid var(--rule); border-radius: 6px; padding: 10px; background: var(--paper-2); }
.icon-cell svg { width: 100%; height: 100%; }
/* — Motion — */
.motion-tempo { display: flex; flex-direction: column; gap: 10px; }
.tempo-item { display: flex; align-items: center; gap: 14px; }
.tempo-bar { height: 6px; border-radius: 999px; min-width: 24px; opacity: 0.8; flex: 0 0 auto; animation: tempoSlide var(--dur, 600ms) cubic-bezier(.2,.7,.2,1) infinite alternate; }
@keyframes tempoSlide { from { width: 24px; opacity: 0.5; } to { width: 280px; opacity: 0.95; } }
.tempo-item code { font-family: var(--mono); font-size: 12px; color: var(--ink-soft); min-width: 120px; }
.easings { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 10px; }
.easings li { padding: 6px 12px; background: var(--paper-2); border: 1px solid var(--rule); border-radius: 4px; font-size: 12px; }
/* — Components — */
.mock-grid { display: grid; grid-template-columns: 1fr 1.4fr; gap: 24px; align-items: start; padding: 8px 0 28px; }
@media (max-width: 640px) { .mock-grid { grid-template-columns: 1fr; } }
.mock-button { display: flex; flex-direction: column; gap: 14px; padding: 32px; background: var(--paper-2); border-radius: 12px; box-shadow: inset 0 0 0 1px var(--rule); align-items: flex-start; }
.m-btn { font-family: var(--body); font-size: 14px; font-weight: 500; padding: 12px 22px; border: 1px solid transparent; cursor: pointer; }
.m-btn-secondary { background: transparent; border-style: solid; border-width: 1px; }
.mock-card { padding: 26px 28px; box-shadow: inset 0 0 0 1px var(--rule); }
.m-card-eyebrow { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .14em; color: var(--ink-faint); }
.m-card-title { font-family: var(--display); font-weight: 400; font-size: 26px; margin: 8px 0 8px; line-height: 1.1; }
.m-card-body { margin: 0 0 14px; font-size: 14px; color: var(--ink-soft); line-height: 1.5; }
.m-card-link { font-family: var(--body); font-size: 13px; font-weight: 500; border: 0; padding: 0; }
.component-list { display: flex; flex-direction: column; }
.component-card { padding: 18px 0; border-top: 1px solid var(--rule); }
.component-card:last-of-type { border-bottom: 1px solid var(--rule); }
.component-card h4 { font-family: var(--display); font-weight: 400; font-size: 22px; margin: 0 0 14px; }
/* — Voice — */
.quotes { list-style: none; padding: 0; margin: 0; }
.quotes li {
font-family: var(--display);
font-size: 26px;
line-height: 1.18;
padding: 22px 0;
border-top: 1px solid var(--rule);
color: var(--ink);
}
.quotes li:last-child { border-bottom: 1px solid var(--rule); }
.cta-verbs { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 8px 20px; }
.cta-verbs li { display: flex; align-items: baseline; gap: 6px; font-size: 14px; }
.cta-verbs code { font-size: 13px; }
.cta-verbs .muted { font-family: var(--mono); font-size: 11px; }
/* — Accessibility — */
.a11y-score { display: flex; align-items: baseline; gap: 24px; padding: 12px 0 32px; border-bottom: 1px solid var(--rule); margin-bottom: 24px; }
.a11y-num { font-family: var(--display); font-size: clamp(72px, 10vw, 120px); line-height: 1; letter-spacing: -.02em; }
.a11y-suffix { font-size: 0.5em; color: var(--ink-soft); margin-left: 4px; }
.a11y-label { color: var(--ink-soft); font-size: 14px; line-height: 1.4; }
.a11y-pairs { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 18px; }
.a11y-pair { display: flex; flex-direction: column; gap: 12px; }
.a11y-stack { padding: 22px 18px; border-radius: 6px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.06); display: flex; justify-content: space-between; align-items: center; }
.a11y-Aa { font-family: var(--display); font-size: 32px; line-height: 1; }
.a11y-ratio { font-family: var(--mono); font-size: 12px; opacity: 0.9; }
.a11y-pair dl { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; padding: 0; margin: 0; }
.a11y-pair dt { font-family: var(--mono); font-size: 9px; text-transform: uppercase; letter-spacing: .14em; color: var(--ink-faint); }
.a11y-pair dd { margin: 0 0 4px; font-size: 12px; }
.remediation { list-style: none; padding: 0; margin: 0; }
.remediation li { padding: 10px 0; border-top: 1px solid var(--rule); }
.remediation li:last-child { border-bottom: 1px solid var(--rule); }
/* — Tokens — */
.codeblock-wrap { margin: 0 0 18px; border: 1px solid var(--rule); border-radius: 6px; overflow: hidden; }
.codeblock-head { display: flex; justify-content: space-between; padding: 10px 14px; font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .14em; color: var(--ink-faint); background: var(--paper-2); border-bottom: 1px solid var(--rule); }
.codeblock { background: var(--paper-2); padding: 16px 18px; overflow-x: auto; font-family: var(--mono); font-size: 12px; line-height: 1.6; margin: 0; color: var(--ink); }
/* — Usage — */
.usage-list { padding: 0; margin: 0; counter-reset: rule; list-style: none; }
.usage-list li { padding: 22px 0; border-top: 1px solid var(--rule); position: relative; padding-left: 56px; counter-increment: rule; }
.usage-list li:last-child { border-bottom: 1px solid var(--rule); }
.usage-list li::before { content: counter(rule, decimal-leading-zero); position: absolute; left: 0; top: 26px; font-family: var(--mono); font-size: 11px; color: var(--ink-faint); letter-spacing: .14em; }
.usage-title { font-family: var(--display); font-weight: 400; font-size: 22px; margin: 0 0 6px; }
.usage-body { margin: 0; color: var(--ink-soft); font-size: 15px; line-height: 1.55; }
/* — Footer — */
footer { padding: 40px 40px 0; font-size: 13px; color: var(--ink-soft); display: flex; justify-content: space-between; align-items: end; flex-wrap: wrap; gap: 16px; }
footer .sig { font-family: var(--display); font-size: 22px; color: var(--ink); }
footer .stamp { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .14em; }
@media (max-width: 640px) { footer { padding: 40px 22px 0; } }
/* — Print — */
@media print {
body { background: white; color: black; }
.topbar nav, .theme-btn { display: none; }
.cover, .toc, section { page-break-inside: avoid; border-color: #ddd; padding: 36px 32px; }
.cover-band { background-color: var(--accent) !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.toc, .cover { page-break-after: always; }
section { page-break-before: always; page-break-after: always; break-before: page; }
.topbar { display: none !important; }
.scale-table, .a11y-pair, .component-card { page-break-inside: avoid; }
}
</style>
</head>
<body>
<header class="topbar">
<div class="brand"><a href="https://designlang.app">designlang</a></div>
<nav>
<span>Brand guidelines</span>
<button class="theme-btn" id="themeBtn" type="button">Theme</button>
</nav>
</header>
<main class="wrap">
<section class="cover" id="cover" aria-labelledby="cover-title">
<div class="cover-band" style="background:#d08728;color:#ffffff">
<span class="cover-band-label">Brand guidelines</span>
<span class="cover-band-hex"><code>#d08728</code></span>
</div>
<div class="cover-body">
<p class="kicker">August 9, 2026</p>
<h1 id="cover-title" class="cover-title">coulomb.social</h1>
<p class="cover-sub">A reading of the visual language at <a href="https://coulomb.social" target="_blank" rel="noopener">https://coulomb.social</a>. Every token, every rule, every component — captured from the live site.</p>
<dl class="cover-meta">
<div><dt>Page intent</dt><dd>landing</dd></div>
<div><dt>System grade</dt><dd>B <span class="muted">85/100</span></dd></div>
<div><dt>Tokens</dt><dd>8 colours · 7 sizes</dd></div>
<div><dt>Generated</dt><dd>designlang</dd></div>
</dl>
</div>
</section>
<nav class="toc" aria-label="Table of contents">
<h2 class="toc-title">Contents</h2>
<ol>
<li><a href="#about"><span class="toc-num">01</span><span class="toc-label">About</span></a></li><li><a href="#logo"><span class="toc-num">02</span><span class="toc-label">Logo</span></a></li><li><a href="#color"><span class="toc-num">03</span><span class="toc-label">Colour</span></a></li><li><a href="#type"><span class="toc-num">04</span><span class="toc-label">Typography</span></a></li><li><a href="#spacing"><span class="toc-num">05</span><span class="toc-label">Spacing</span></a></li><li><a href="#shape"><span class="toc-num">06</span><span class="toc-label">Shape</span></a></li><li><a href="#iconography"><span class="toc-num">07</span><span class="toc-label">Iconography</span></a></li><li><a href="#motion"><span class="toc-num">08</span><span class="toc-label">Motion</span></a></li><li><a href="#components"><span class="toc-num">09</span><span class="toc-label">Components</span></a></li><li><a href="#voice"><span class="toc-num">10</span><span class="toc-label">Voice</span></a></li><li><a href="#accessibility"><span class="toc-num">11</span><span class="toc-label">Accessibility</span></a></li><li><a href="#tokens"><span class="toc-num">12</span><span class="toc-label">Tokens</span></a></li><li><a href="#usage"><span class="toc-num">13</span><span class="toc-label">Usage</span></a></li>
</ol>
</nav>
<section id="about">
<header class="chapter-header">
<span class="sec-num">Chapter 01</span>
<h2>About</h2>
</header>
<dl class="meta-grid">
<div><dt>Page intent</dt><dd>landing <span class="muted">45% confidence</span></dd></div>
<div><dt>Material language</dt><dd>flat</dd></div>
<div><dt>Imagery style</dt><dd>mixed</dd></div>
<div><dt>Component library</dt><dd>unknown</dd></div>
<div><dt>Stack</dt><dd>unknown · Tailwind</dd></div>
<div><dt>Voice tone</dt><dd>neutral</dd></div>
</dl>
</section>
<section id="logo">
<header class="chapter-header">
<span class="sec-num">Chapter 02</span>
<h2>Logo</h2>
</header>
<div class="logo-card">
<div class="logo-canvas" aria-label="Extracted logo on canvas">
<span class="logo-placeholder">coulomb.social</span>
</div>
<dl class="meta-grid logo-meta">
<div><dt>Source</dt><dd>inferred</dd></div>
<div><dt>Dimensions</dt><dd></dd></div>
<div><dt>Aspect</dt><dd></dd></div>
<div><dt>Format</dt><dd>unknown</dd></div>
</dl>
</div>
</section>
<section id="color">
<header class="chapter-header">
<span class="sec-num">Chapter 03</span>
<h2>Colour</h2>
</header>
<p class="summary">1 primary · 7 neutrals · 8 total</p>
<div class="brand-grid brand-grid-primary">
<article class="brand-color brand-color-primary">
<div class="big-swatch" style="background:#d08728;color:#ffffff">
<span class="big-swatch-name">Primary</span>
<span class="big-swatch-hex">#D08728</span>
</div>
<dl class="color-meta">
<div><dt>RGB</dt><dd><code>208, 135, 40</code></dd></div>
<div><dt>HSL</dt><dd><code>34°, 68%, 49%</code></dd></div>
</dl>
</article></div>
<h3 class="sub">Neutrals</h3>
<div class="neutral-strip">
<div class="neutral-cell" style="background:#000000;color:#ffffff"><code>#000000</code></div><div class="neutral-cell" style="background:#efefef;color:#0a0a0a"><code>#EFEFEF</code></div><div class="neutral-cell" style="background:#616161;color:#ffffff"><code>#616161</code></div><div class="neutral-cell" style="background:#373737;color:#ffffff"><code>#373737</code></div><div class="neutral-cell" style="background:#171717;color:#ffffff"><code>#171717</code></div><div class="neutral-cell" style="background:#ffffff;color:#0a0a0a"><code>#FFFFFF</code></div><div class="neutral-cell" style="background:#e5e5e5;color:#0a0a0a"><code>#E5E5E5</code></div>
</div>
<h3 class="sub">Full palette</h3>
<div class="palette-grid">
<div class="mini-swatch" style="background:#000000;color:#ffffff"><code>#000000</code></div><div class="mini-swatch" style="background:#efefef;color:#0a0a0a"><code>#EFEFEF</code></div><div class="mini-swatch" style="background:#616161;color:#ffffff"><code>#616161</code></div><div class="mini-swatch" style="background:#373737;color:#ffffff"><code>#373737</code></div><div class="mini-swatch" style="background:#171717;color:#ffffff"><code>#171717</code></div><div class="mini-swatch" style="background:#ffffff;color:#0a0a0a"><code>#FFFFFF</code></div><div class="mini-swatch" style="background:#e5e5e5;color:#0a0a0a"><code>#E5E5E5</code></div><div class="mini-swatch" style="background:#d08728;color:#ffffff"><code>#D08728</code></div>
</div>
<p class="callout"><strong>WCAG 100%</strong> · 0 passing pairs · 0 failing. Full breakdown in §11.</p>
</section>
<section id="type">
<header class="chapter-header">
<span class="sec-num">Chapter 04</span>
<h2>Typography</h2>
</header>
<p class="summary">4 families · 7 sizes · 4 weights</p>
<dl class="meta-grid">
<div><dt>Display</dt><dd>Helvetica</dd></div>
<div><dt>Body</dt><dd>Arial</dd></div>
<div><dt>Weights</dt><dd>400, 500, 600, 700</dd></div>
</dl>
<h3 class="sub">Specimen</h3>
<div class="specimen" style="font-family: Helvetica, 'Instrument Serif', serif;">
<div class="spec-line spec-display" style="font-size: 50px">Quick brown fox jumps over the lazy dog.</div>
<div class="spec-line spec-body" style="font-size: 24px">AaBbCc 0123456789 ?!&amp;</div>
</div>
<h3 class="sub">Scale</h3>
<table class="scale-table">
<thead><tr><th class="t-step">Step</th><th class="t-size">Size</th><th class="t-sample">Sample</th></tr></thead>
<tbody>
<tr>
<td class="t-step"><code>t00</code></td>
<td class="t-size"><code>50px</code></td>
<td class="t-sample" style="font-size:50px; font-family: Helvetica, 'Instrument Serif', serif; line-height: 1;">Quick brown fox jumps over t</td>
</tr>
<tr>
<td class="t-step"><code>t01</code></td>
<td class="t-size"><code>42px</code></td>
<td class="t-sample" style="font-size:42px; font-family: Helvetica, 'Instrument Serif', serif; line-height: 1;">Quick brown fox jumps over t</td>
</tr>
<tr>
<td class="t-step"><code>t02</code></td>
<td class="t-size"><code>24px</code></td>
<td class="t-sample" style="font-size:24px; font-family: Helvetica, 'Instrument Serif', serif; line-height: 1;">Quick brown fox jumps over t</td>
</tr>
<tr>
<td class="t-step"><code>t03</code></td>
<td class="t-size"><code>18px</code></td>
<td class="t-sample" style="font-size:18px; font-family: Helvetica, 'Instrument Serif', serif; line-height: 1;">Quick brown fox jumps over t</td>
</tr>
<tr>
<td class="t-step"><code>t04</code></td>
<td class="t-size"><code>16px</code></td>
<td class="t-sample" style="font-size:16px; font-family: Helvetica, 'Instrument Serif', serif; line-height: 1;">Quick brown fox jumps over t</td>
</tr>
<tr>
<td class="t-step"><code>t05</code></td>
<td class="t-size"><code>14px</code></td>
<td class="t-sample" style="font-size:14px; font-family: Helvetica, 'Instrument Serif', serif; line-height: 1;">Quick brown fox jumps over t</td>
</tr>
<tr>
<td class="t-step"><code>t06</code></td>
<td class="t-size"><code>13.3333px</code></td>
<td class="t-sample" style="font-size:13.3333px; font-family: Helvetica, 'Instrument Serif', serif; line-height: 1;">Quick brown fox jumps over t</td>
</tr>
</tbody>
</table>
</section>
<section id="spacing">
<header class="chapter-header">
<span class="sec-num">Chapter 05</span>
<h2>Spacing</h2>
</header>
<p class="summary">Base 4px · 8 steps captured</p>
<h3 class="sub">Rhythm</h3>
<div class="space-rhythm">
<div class="space-step">
<div class="space-block" style="width:1.6px"></div>
<code>1px</code>
</div>
<div class="space-step">
<div class="space-block" style="width:38.400000000000006px"></div>
<code>24px</code>
</div>
<div class="space-step">
<div class="space-block" style="width:51.2px"></div>
<code>32px</code>
</div>
<div class="space-step">
<div class="space-block" style="width:64px"></div>
<code>40px</code>
</div>
<div class="space-step">
<div class="space-block" style="width:96px"></div>
<code>60px</code>
</div>
<div class="space-step">
<div class="space-block" style="width:128px"></div>
<code>80px</code>
</div>
<div class="space-step">
<div class="space-block" style="width:153.60000000000002px"></div>
<code>96px</code>
</div>
<div class="space-step">
<div class="space-block" style="width:320px"></div>
<code>320px</code>
</div>
</div>
</section>
<section id="shape">
<header class="chapter-header">
<span class="sec-num">Chapter 06</span>
<h2>Shape</h2>
</header>
<p class="summary">5 radii · 2 elevation tiers</p>
<h3 class="sub">Border radii</h3>
<div class="radii-row">
<div class="radius-card">
<div class="radius-block" style="border-radius:4px"></div>
<code>4px</code>
</div>
<div class="radius-card">
<div class="radius-block" style="border-radius:8px"></div>
<code>8px</code>
</div>
<div class="radius-card">
<div class="radius-block" style="border-radius:12px"></div>
<code>12px</code>
</div>
<div class="radius-card">
<div class="radius-block" style="border-radius:15px"></div>
<code>15px</code>
</div>
<div class="radius-card">
<div class="radius-block" style="border-radius:20px"></div>
<code>20px</code>
</div>
</div>
<h3 class="sub">Elevation</h3>
<div class="shadows-row">
<div class="shadow-card">
<div class="shadow-block" style="box-shadow:rgb(170, 170, 170) 2px 2px 4px 0px"></div>
<code>xs</code>
</div>
<div class="shadow-card">
<div class="shadow-block" style="box-shadow:rgba(23, 23, 23, 0.1) 2px 2px 8px 2px"></div>
<code>sm</code>
</div>
</div>
</section>
<section id="iconography">
<header class="chapter-header">
<span class="sec-num">Chapter 07</span>
<h2>Iconography</h2>
</header>
<p class="summary">unknown · 24 captured</p>
<dl class="meta-grid">
<div><dt>Library</dt><dd>unknown</dd></div>
<div><dt>Confidence</dt><dd>0%</dd></div>
<div><dt>Stroke style</dt><dd>fillDominant</dd></div>
</dl>
<div class="icon-grid">
<div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div><div class="icon-cell"><code>icon</code></div>
</div>
</section>
<section id="motion">
<header class="chapter-header">
<span class="sec-num">Chapter 08</span>
<h2>Motion</h2>
</header>
<p class="summary">Feel: mixed · 1 durations · 0 easings</p>
<h3 class="sub">Duration scale</h3>
<div class="motion-tempo">
<div class="tempo-item">
<div class="tempo-bar" style="--dur:250ms; background:#d08728"></div>
<code>250ms</code>
</div>
</div>
</section>
<section id="components">
<header class="chapter-header">
<span class="sec-num">Chapter 09</span>
<h2>Components</h2>
</header>
<p class="summary">unknown · 1 component patterns captured</p>
<h3 class="sub">Mocks</h3>
<div class="mock-grid">
<div class="mock-button">
<button type="button" class="m-btn m-btn-primary" style="background:#d08728;color:#ffffff;border-radius:8px">Primary action</button>
<button type="button" class="m-btn m-btn-secondary" style="border-radius:8px;border-color:#d08728;color:#d08728">Secondary</button>
</div>
<div class="mock-card" style="background:#e5e5e5;color:#000000;border-radius:12px">
<span class="m-card-eyebrow">Card</span>
<h4 class="m-card-title">Built from these tokens</h4>
<p class="m-card-body">Radius, primary, surface, text — all sampled from the live site.</p>
<a class="m-card-link" href="#" style="color:#d08728">Read more →</a>
</div>
</div>
<h3 class="sub">Detected patterns</h3>
<div class="component-list">
<article class="component-card">
<h4>button</h4>
<dl class="meta-grid">
<div><dt>Slots</dt><dd>label, icon, badge</dd></div>
<div><dt>Variants</dt><dd></dd></div>
<div><dt>Sizes</dt><dd></dd></div>
</dl>
</article>
</div>
</section>
<section id="voice">
<header class="chapter-header">
<span class="sec-num">Chapter 10</span>
<h2>Voice</h2>
</header>
<p class="summary">neutral · — · —</p>
<h3 class="sub">CTA verbs</h3>
<ul class="cta-verbs">
<li><code></code><span class="muted">×25</span></li><li><code></code><span class="muted">×25</span></li><li><code></code><span class="muted">×1</span></li><li><code></code><span class="muted">×1</span></li><li><code></code><span class="muted">×1</span></li><li><code></code><span class="muted">×1</span></li><li><code></code><span class="muted">×1</span></li>
</ul>
</section>
<section id="accessibility">
<header class="chapter-header">
<span class="sec-num">Chapter 11</span>
<h2>Accessibility</h2>
</header>
<div class="a11y-score">
<span class="a11y-num">100<span class="a11y-suffix">%</span></span>
<span class="a11y-label">WCAG 2.1 contrast<br>0 passing · 0 failing</span>
</div>
<p class="muted">No failing contrast pairs detected.</p>
</section>
<section id="tokens">
<header class="chapter-header">
<span class="sec-num">Chapter 12</span>
<h2>Tokens</h2>
</header>
<p class="summary">Drop-in code for the most common stacks. All values from the live extraction.</p>
<div class="codeblock-wrap">
<div class="codeblock-head"><span>CSS variables</span><span class="muted">variables.css</span></div>
<pre class="codeblock"><code>:root {
--color-primary: #d08728;
--font-display: &quot;Helvetica&quot;;
--space-base: 4px;
--radius-md: 8px;
}</code></pre>
</div>
<div class="codeblock-wrap">
<div class="codeblock-head"><span>Tailwind config</span><span class="muted">tailwind.config.js</span></div>
<pre class="codeblock"><code>// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: { brand: &#39;#d08728&#39; },
fontFamily: { display: [&#39;Helvetica&#39;, &#39;serif&#39;] },
spacing: { base: &#39;4px&#39; },
borderRadius: { md: &#39;8px&#39; },
},
},
};</code></pre>
</div>
<p class="muted">Run <code>npx designlang pack coulomb.social</code> for the full bundle (DTCG, shadcn, Figma vars, motion, anatomy, Storybook).</p>
</section>
<section id="usage">
<header class="chapter-header">
<span class="sec-num">Chapter 13</span>
<h2>Usage</h2>
</header>
<ol class="usage-list">
<li>
<h3 class="usage-title">Lead with the primary.</h3>
<p class="usage-body">It belongs on calls-to-action and one accent moment per screen. Not on body copy.</p>
</li>
<li>
<h3 class="usage-title">Two type families, three weights.</h3>
<p class="usage-body">Display for headlines, body for paragraphs. Resist a third unless there is a real reason.</p>
</li>
<li>
<h3 class="usage-title">Snap to the spacing scale.</h3>
<p class="usage-body">Padding, margin, and gap should land on the values in §05. One-off pixels accumulate into noise.</p>
</li>
<li>
<h3 class="usage-title">Treat accessibility as a hard constraint.</h3>
<p class="usage-body">When a colour pair fails WCAG, fix the colour — not the contrast check.</p>
</li>
</ol>
</section>
<footer>
<div>
<div class="sig">designlang</div>
<div>Re-run: <code>npx designlang brand coulomb.social</code></div>
</div>
<div class="stamp">Aug 9, 2026 · v12.21.0</div>
</footer>
</main>
<script>
(function () {
var btn = document.getElementById('themeBtn');
var saved = null;
try { saved = localStorage.getItem('dl-theme'); } catch (e) {}
if (saved) document.documentElement.setAttribute('data-theme', saved);
btn && btn.addEventListener('click', function () {
var cur = document.documentElement.getAttribute('data-theme') === 'dark' ? '' : 'dark';
if (cur) document.documentElement.setAttribute('data-theme', cur);
else document.documentElement.removeAttribute('data-theme');
try { localStorage.setItem('dl-theme', cur); } catch (e) {}
});
})();
</script>
</body>
</html>

194
scripts/capture-auth-state.mjs Executable file
View file

@ -0,0 +1,194 @@
#!/usr/bin/env node
/**
* Capture a Playwright storageState for https://coulomb.social after you log in.
*
* Default: headed browser you complete login, then press Enter in this terminal.
* Optional: --from-password-files reads EMAIL/PASSWORD from XDG auth dir (OpenBao-exported).
*
* Never prints secret values. Writes mode-0600 storage-state.json only.
*/
import { chromium } from "playwright";
import {
mkdirSync,
writeFileSync,
chmodSync,
existsSync,
readFileSync,
statSync,
} from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { createInterface } from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";
const BASE_URL = process.env.COULOMB_SOCIAL_URL || "https://coulomb.social";
const AUTH_DIR =
process.env.COULOMB_SOCIAL_AUTH_DIR ||
join(homedir(), ".config", "coulomb-social", "auth");
const STATE_PATH = join(AUTH_DIR, "storage-state.json");
const EMAIL_PATH = join(AUTH_DIR, "email");
const PASSWORD_PATH = join(AUTH_DIR, "password");
const fromPasswordFiles = process.argv.includes("--from-password-files");
const headless = process.argv.includes("--headless");
function ensureAuthDir() {
mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
try {
chmodSync(AUTH_DIR, 0o700);
} catch {
/* best effort */
}
}
function readSecretFile(path) {
if (!existsSync(path)) {
throw new Error(`Missing secret file: ${path}`);
}
const mode = statSync(path).mode & 0o777;
if (mode & 0o077) {
console.warn(
`warning: ${path} is group/world-readable (mode ${mode.toString(8)}); chmod 600 recommended`,
);
}
return readFileSync(path, "utf8").trim();
}
async function waitForUserContinue() {
const rl = createInterface({ input, output });
console.log("");
console.log(">>> Log in as your coulomb.social user in the browser window.");
console.log(">>> Navigate to an authenticated page you want designlang to see.");
console.log(">>> Then return here and press Enter to save the session.");
console.log("");
await rl.question("Press Enter when logged in… ");
rl.close();
}
async function tryPasswordLogin(page, email, password) {
// Best-effort Bubble-style login. Interactive capture is preferred when MFA/CAPTCHA appears.
await page.goto(BASE_URL, { waitUntil: "domcontentloaded", timeout: 60_000 });
await page.waitForTimeout(1500);
const emailBox =
(await page.$('input[type="email"]')) ||
(await page.$('input[name*="email" i]')) ||
(await page.$('input[placeholder*="email" i]')) ||
(await page.$('input[placeholder*="Email" i]'));
const passBox =
(await page.$('input[type="password"]')) ||
(await page.$('input[name*="password" i]'));
if (!emailBox || !passBox) {
throw new Error(
"Could not find email/password fields automatically. Re-run without --from-password-files and log in interactively.",
);
}
await emailBox.fill(email);
await passBox.fill(password);
const submit =
(await page.$('button[type="submit"]')) ||
(await page.$('input[type="submit"]')) ||
(await page.$('button:has-text("Log in")')) ||
(await page.$('button:has-text("Login")')) ||
(await page.$('button:has-text("Sign in")'));
if (submit) {
await submit.click();
} else {
await passBox.press("Enter");
}
await page.waitForTimeout(4000);
console.log("Password login attempt finished (no secrets printed).");
console.log("If MFA/CAPTCHA is required, re-run in interactive mode.");
}
async function main() {
ensureAuthDir();
console.log(`Auth dir: ${AUTH_DIR}`);
console.log(`Target: ${BASE_URL}`);
console.log(`State → ${STATE_PATH}`);
const browser = await chromium.launch({
headless: Boolean(headless && fromPasswordFiles),
channel: process.env.COULOMB_SOCIAL_CHROME_CHANNEL || undefined,
});
const context = await browser.newContext({
viewport: { width: 1280, height: 900 },
});
const page = await context.newPage();
try {
if (fromPasswordFiles) {
const email = readSecretFile(EMAIL_PATH);
const password = readSecretFile(PASSWORD_PATH);
await tryPasswordLogin(page, email, password);
} else {
await page.goto(BASE_URL, {
waitUntil: "domcontentloaded",
timeout: 60_000,
});
await waitForUserContinue();
}
const state = await context.storageState();
const cookieCount = state.cookies?.length ?? 0;
const originCount = state.origins?.length ?? 0;
const lsKeys = (state.origins || []).flatMap((o) =>
(o.localStorage || []).map((e) => e.name),
);
// Bubble often keeps the same coulomb_* cookie *names* for guests and
// members — names alone are not a login signal. Probe the live page:
// authenticated app usually leaves the bare landing path.
let probeUrl = page.url();
let probeTitle = await page.title().catch(() => "");
const path = (() => {
try {
return new URL(probeUrl).pathname;
} catch {
return "";
}
})();
const looksLikeLanding = path === "/" || path === "";
if (looksLikeLanding && !fromPasswordFiles) {
console.error("");
console.error(
"WARNING: Still on the site root after capture — login may not have completed.",
);
console.error(` url: ${probeUrl}`);
console.error(` title: ${probeTitle}`);
console.error(
"Log in fully, open an app page (e.g. Research / Pages), then press Enter.",
);
// Continue to save anyway — Bubble guest+member share cookie names; user
// may still have a valid session that only reveals itself after navigation.
}
writeFileSync(STATE_PATH, JSON.stringify(state, null, 2) + "\n", {
mode: 0o600,
});
chmodSync(STATE_PATH, 0o600);
console.log("");
console.log(
`Saved storageState (${cookieCount} cookies, ${originCount} origins, localStorage keys: ${lsKeys.length}).`,
);
console.log(` page at save: ${probeUrl}${probeTitle}`);
console.log(`File: ${STATE_PATH}`);
console.log("Next: ./scripts/run-design-extract.sh");
} finally {
await browser.close();
}
}
main().catch((err) => {
console.error(err.message || err);
process.exit(1);
});

11
scripts/capture-auth-state.sh Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Wrapper so Node resolves playwright from scripts/node_modules.
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$DIR"
if [[ ! -d node_modules/playwright ]]; then
echo "Installing playwright in scripts/ …"
npm install
npx playwright install chromium
fi
exec node ./capture-auth-state.mjs "$@"

9
scripts/package.json Normal file
View file

@ -0,0 +1,9 @@
{
"name": "coulomb-social-scripts",
"private": true,
"type": "module",
"description": "Local tooling for auth capture and design extract (no app runtime)",
"dependencies": {
"playwright": "^1.49.0"
}
}

21
scripts/probe-auth-session.mjs Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env node
/** Print final URL after loading coulomb.social with storageState (no secrets). */
import { chromium } from "playwright";
import { homedir } from "os";
import { join } from "path";
const statePath =
process.env.COULOMB_SOCIAL_STORAGE_STATE ||
join(homedir(), ".config/coulomb-social/auth/storage-state.json");
const url = process.env.COULOMB_SOCIAL_URL || "https://coulomb.social";
const browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({ storageState: statePath });
const page = await context.newPage();
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45000 });
await page.waitForTimeout(3000);
console.log(page.url());
} finally {
await browser.close();
}

104
scripts/run-design-extract.sh Executable file
View file

@ -0,0 +1,104 @@
#!/usr/bin/env bash
# Run designlang against coulomb.social using a captured Playwright storageState.
# Does not print secrets. Requires: ./scripts/capture-auth-state.sh first.
#
# NOTE: designlang's `pack` subcommand does NOT pass --cookie-file into the
# crawler (it calls extractDesignLanguage(url) with no auth options). Use the
# main extract command, which does apply cookies from storageState.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
AUTH_DIR="${COULOMB_SOCIAL_AUTH_DIR:-$HOME/.config/coulomb-social/auth}"
STATE_PATH="${COULOMB_SOCIAL_STORAGE_STATE:-$AUTH_DIR/storage-state.json}"
OUT_DIR="${COULOMB_SOCIAL_EXTRACT_OUT:-$ROOT/docs/design-extract}"
URL="${COULOMB_SOCIAL_URL:-https://coulomb.social}"
WAIT_MS="${COULOMB_SOCIAL_EXTRACT_WAIT:-5000}"
if [[ ! -f "$STATE_PATH" ]]; then
echo "ERROR: No auth session at $STATE_PATH" >&2
echo "Capture one first (interactive login as your user):" >&2
echo " $ROOT/scripts/capture-auth-state.sh" >&2
echo "Docs: $ROOT/docs/design-extract-auth.md" >&2
exit 1
fi
mode="$(stat -c '%a' "$STATE_PATH" 2>/dev/null || stat -f '%Lp' "$STATE_PATH" 2>/dev/null || echo '?')"
if [[ "$mode" != "600" && "$mode" != "0600" ]]; then
echo "warning: $STATE_PATH mode is $mode (prefer 600)" >&2
fi
echo "==> probing session (no secrets printed)…"
PROBE_URL="$(
cd "$ROOT/scripts" && \
COULOMB_SOCIAL_STORAGE_STATE="$STATE_PATH" \
COULOMB_SOCIAL_URL="$URL" \
node ./probe-auth-session.mjs
)" || PROBE_URL=""
if [[ -n "$PROBE_URL" ]]; then
echo " session lands on: $PROBE_URL"
if [[ "$PROBE_URL" == "https://coulomb.social/" || "$PROBE_URL" == "https://coulomb.social" ]]; then
echo "warning: session still on site root — may be guest; re-run capture-auth-state.sh if extract looks empty" >&2
fi
fi
mkdir -p "$OUT_DIR"
echo "==> designlang extract (main command — cookies applied)"
echo " url: $URL"
echo " auth: $STATE_PATH (session file present; not printed)"
echo " out: $OUT_DIR"
echo " wait: ${WAIT_MS}ms"
echo " extra: $*"
# Main command supports --cookie-file. Prefer a rich capture of the live app UI.
set +e
npx --yes designlang "$URL" \
--cookie-file "$STATE_PATH" \
-o "$OUT_DIR" \
-n coulomb-social \
--wait "$WAIT_MS" \
--depth "${COULOMB_SOCIAL_EXTRACT_DEPTH:-2}" \
--interactions \
--verbose \
"$@"
dl_ec=$?
set -e
# designlang can crash late while printing summary (Buffer.byteLength on undefined
# content for optional emitters) after already writing the useful artifacts.
if [[ $dl_ec -ne 0 ]]; then
if [[ -f "$OUT_DIR/coulomb-social-design-language.md" && -f "$OUT_DIR/coulomb-social-design-tokens.json" ]]; then
echo "warning: designlang exited $dl_ec after writing core artifacts (known late summary bug); treating as success" >&2
else
echo "ERROR: designlang failed (exit $dl_ec) and core artifacts are missing" >&2
exit "$dl_ec"
fi
fi
# Drop root-level pack leftovers from earlier mistaken `pack` runs (no cookies).
if [[ -d "$ROOT/coulomb-social-design-system" ]]; then
echo "==> removing unauthenticated pack leftover: coulomb-social-design-system/"
rm -rf "$ROOT/coulomb-social-design-system"
fi
# Provenance (no secrets)
cat > "$OUT_DIR/PROVENANCE.md" <<EOF
# Design extract provenance
| Field | Value |
|-------|--------|
| Source | $URL |
| Tool | designlang (main extract, not pack) |
| Captured | $(date -Iseconds) |
| Auth | Playwright storageState via --cookie-file |
| Session probe | ${PROBE_URL:-unknown} |
| designlang exit | $dl_ec |
## Note
Use \`./scripts/run-design-extract.sh\`do **not** use \`designlang pack\` for
authenticated crawls; pack ignores cookies.
EOF
echo "==> done. Inspect: $OUT_DIR"
ls -la "$OUT_DIR" | head -50