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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
25 changes: 14 additions & 11 deletions .agents/skills/emcn-design-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ Use for context menus and action menus:
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost">
<MoreHorizontal className="h-[14px] w-[14px]" />
<MoreHorizontal className="size-[14px]" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
Expand Down Expand Up @@ -281,19 +281,21 @@ Rules:
- Stack multiple skeletons for lists

### Icons
Standard sizing — `h-[14px] w-[14px]` is the dominant pattern (400+ uses):
Standard sizing — use the `size-*` shorthand. `size-[14px]` is the dominant pattern:

```tsx
<Icon className="h-[14px] w-[14px] text-[var(--text-icon)]" />
<Icon className="size-[14px] text-[var(--text-icon)]" />
```

Size scale by frequency:
1. `h-[14px] w-[14px]` — default for inline icons (most common)
2. `h-[16px] w-[16px]` — slightly larger inline icons
3. `h-3 w-3` (12px) — compact/tight spaces
4. `h-4 w-4` (16px) — Tailwind equivalent, also common
5. `h-3.5 w-3.5` (14px) — Tailwind equivalent of 14px
6. `h-5 w-5` (20px) — larger icons, section headers
Always prefer `size-*` over the legacy `h-* w-*` pair. `size-[14px]` is canonical; treat any `h-[Npx] w-[Npx]` or `h-N w-N` pair as a refactor target.

Size scale (most common first):
1. `size-[14px]` — default for inline icons
2. `size-[16px]` — slightly larger inline icons
3. `size-3` (12px) — compact/tight spaces
4. `size-4` (16px) — Tailwind equivalent
5. `size-3.5` (14px) — Tailwind equivalent of 14px
6. `size-5` (20px) — larger icons, section headers

Use `text-[var(--text-icon)]` for icon color (113+ uses in codebase).

Expand Down Expand Up @@ -332,4 +334,5 @@ Use `text-[var(--text-icon)]` for icon color (113+ uses in codebase).
- Importing from emcn subpaths instead of barrel export
- Using arbitrary z-index (`z-50`, `z-[9999]`) instead of z-index tokens
- Custom shadows instead of shadow tokens
- Icon sizes that don't follow the established scale (default to `h-[14px] w-[14px]`)
- Icon sizes that don't follow the established scale (default to `size-[14px]`)
- Splitting equal height/width into `h-* w-*` pairs instead of the `size-*` shorthand
2 changes: 1 addition & 1 deletion .claude/commands/emcn-design-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` act

## Icons

Default: `h-[14px] w-[14px]` (400+ uses). Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px.
Default: `size-[14px]`. Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px. Use the `size-*` shorthand — flag `h-[Npx] w-[Npx]` and `h-N w-N` pairs as refactor targets.

## Anti-patterns to flag

Expand Down
1 change: 1 addition & 0 deletions .claude/rules/emcn-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ function Label({ className, ...props }) {
- Export component and variants (if using CVA)
- TSDoc with usage examples
- Consistent tokens: `font-medium`, `text-[12px]`, `rounded-[4px]`
- Equal height/width → `size-*` (e.g. `size-[14px]`, `size-4`), never `h-[Npx] w-[Npx]` or `h-N w-N`. Default icon size is `size-[14px]`
- `transition-colors` for hover states
3 changes: 2 additions & 1 deletion .claude/rules/sim-styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ paths:
1. **No inline styles** - Use Tailwind classes
2. **No duplicate dark classes** - Skip `dark:` when value matches light mode
3. **Exact values** - `text-[14px]`, `h-[26px]`
4. **Transitions** - `transition-colors` for interactive states
4. **Equal h/w → `size-*`** - Use `size-[14px]` / `size-4`, never `h-[14px] w-[14px]` or `h-4 w-4`. Default icon size is `size-[14px]`
5. **Transitions** - `transition-colors` for interactive states

## Conditional Classes

Expand Down
2 changes: 1 addition & 1 deletion .cursor/commands/emcn-design-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` act

## Icons

Default: `h-[14px] w-[14px]` (400+ uses). Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px.
Default: `size-[14px]`. Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px. Use the `size-*` shorthand — flag `h-[Npx] w-[Npx]` and `h-N w-N` pairs as refactor targets.

## Anti-patterns to flag

Expand Down
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,12 @@ Use Tailwind only, no inline styles. Use `cn()` from `@/lib/utils` for condition
<div className={cn('base-classes', isActive && 'active-classes')} />
```

For equal height and width, use the `size-*` shorthand — never `h-[Npx] w-[Npx]` or `h-N w-N`. Default icon size is `size-[14px]`.

```typescript
<Icon className='size-[14px] text-[var(--text-icon)]' />
```

## EMCN Components

Import from `@/components/emcn`, never from subpaths (except CSS files). Use CVA when 2+ variants exist.
Expand Down
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,12 @@ Use Tailwind only, no inline styles. Use `cn()` from `@/lib/utils` for condition
<div className={cn('base-classes', isActive && 'active-classes')} />
```

For equal height and width, use the `size-*` shorthand — never `h-[Npx] w-[Npx]` or `h-N w-N`. Default icon size is `size-[14px]`.

```typescript
<Icon className='size-[14px] text-[var(--text-icon)]' />
```

## EMCN Components

Import from `@/components/emcn`, never from subpaths (except CSS files). Use CVA when 2+ variants exist.
Expand Down
9 changes: 6 additions & 3 deletions apps/docs/app/[lang]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,12 @@ export async function generateMetadata(props: {
siteName: 'Sim Documentation',
type: 'article',
locale: OG_LOCALE_MAP[lang] ?? 'en_US',
alternateLocale: i18n.languages
.filter((l) => l !== lang)
.map((l) => OG_LOCALE_MAP[l] ?? 'en_US'),
alternateLocale: i18n.languages.reduce<string[]>((locales, l) => {
if (l !== lang) {
locales.push(OG_LOCALE_MAP[l] ?? 'en_US')
}
return locales
}, []),
images: [
{
url: ogImageUrl,
Expand Down
11 changes: 2 additions & 9 deletions apps/docs/app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { Navbar } from '@/components/navbar/navbar'
import { SimLogoFull } from '@/components/ui/sim-logo'
import { i18n } from '@/lib/i18n'
import { serializeJsonLd } from '@/lib/json-ld'
import { source } from '@/lib/source'
import { DOCS_BASE_URL } from '@/lib/urls'
import '../global.css'
Expand Down Expand Up @@ -78,14 +79,6 @@ export default async function Layout({ children, params }: LayoutProps) {
},
},
inLanguage: lang,
potentialAction: {
'@type': 'SearchAction',
target: {
'@type': 'EntryPoint',
urlTemplate: `${DOCS_BASE_URL}/api/search?q={search_term_string}`,
},
'query-input': 'required name=search_term_string',
},
}

return (
Expand All @@ -97,7 +90,7 @@ export default async function Layout({ children, params }: LayoutProps) {
<head>
<script
type='application/ld+json'
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
dangerouslySetInnerHTML={{ __html: serializeJsonLd(structuredData) }}
/>
</head>
<body className='flex min-h-screen flex-col font-sans'>
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/app/[lang]/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function NotFound() {
return (
<DocsPage>
<div className='flex min-h-[70vh] flex-col items-center justify-center gap-4 text-center'>
<h1 className='bg-gradient-to-b from-[#47d991] to-[#33c482] bg-clip-text font-bold text-8xl text-transparent'>
<h1 className='bg-gradient-to-b from-[#47d991] to-[#33c482] bg-clip-text font-semibold text-8xl text-transparent'>
404
</h1>
<h2 className='font-semibold text-2xl text-foreground'>Page Not Found</h2>
Expand Down
96 changes: 53 additions & 43 deletions apps/docs/app/api/og/route.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { CSSProperties } from 'react'
import { ImageResponse } from 'next/og'
import type { NextRequest } from 'next/server'

Expand All @@ -8,24 +9,69 @@ const TITLE_FONT_SIZE = {
medium: 56,
small: 48,
} as const
const FONT_CACHE_REVALIDATE_SECONDS = 60 * 60 * 24 * 30
const OG_CONTAINER_STYLE = {
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '56px 64px',
background: '#121212',
fontFamily: 'Geist',
} satisfies CSSProperties
const OG_TITLE_STYLE = {
fontWeight: 500,
color: '#fafafa',
lineHeight: 1.2,
letterSpacing: '-0.02em',
} satisfies CSSProperties
const OG_FOOTER_STYLE = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
} satisfies CSSProperties
const OG_DOMAIN_STYLE = {
fontSize: 20,
fontWeight: 400,
color: '#71717a',
} satisfies CSSProperties

function getTitleFontSize(title: string): number {
if (title.length > 45) return TITLE_FONT_SIZE.small
if (title.length > 30) return TITLE_FONT_SIZE.medium
return TITLE_FONT_SIZE.large
}

function getTitleStyle(title: string): CSSProperties {
return {
...OG_TITLE_STYLE,
fontSize: getTitleFontSize(title),
}
}

/**
* Loads a Google Font dynamically by fetching the CSS and extracting the font URL.
*/
async function loadGoogleFont(font: string, weights: string, text: string): Promise<ArrayBuffer> {
const url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weights}&text=${encodeURIComponent(text)}`
const css = await (await fetch(url)).text()
const cssResponse = await fetch(url, {
next: { revalidate: FONT_CACHE_REVALIDATE_SECONDS },
})

if (!cssResponse.ok) {
throw new Error(`Failed to load font CSS: ${cssResponse.status} ${cssResponse.statusText}`)
}

const css = await cssResponse.text()
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)

if (resource) {
const response = await fetch(resource[1])
if (response.status === 200) {
const response = await fetch(resource[1], {
next: { revalidate: FONT_CACHE_REVALIDATE_SECONDS },
})
if (response.ok) {
return await response.arrayBuffer()
}
}
Expand Down Expand Up @@ -72,50 +118,14 @@ export async function GET(request: NextRequest) {
const fontData = await loadGoogleFont('Geist', '400;500;600', allText)

return new ImageResponse(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '56px 64px',
background: '#121212', // Dark mode background matching docs (hsla 0, 0%, 7%)
fontFamily: 'Geist',
}}
>
<div style={OG_CONTAINER_STYLE}>
{/* Title at top */}
<span
style={{
fontSize: getTitleFontSize(title),
fontWeight: 500,
color: '#fafafa', // Light text matching docs
lineHeight: 1.2,
letterSpacing: '-0.02em',
}}
>
{title}
</span>
<span style={getTitleStyle(title)}>{title}</span>

{/* Footer: icon left, domain right */}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
}}
>
<div style={OG_FOOTER_STYLE}>
<SimLogoFull />
<span
style={{
fontSize: 20,
fontWeight: 400,
color: '#71717a',
}}
>
docs.sim.ai
</span>
<span style={OG_DOMAIN_STYLE}>docs.sim.ai</span>
</div>
</div>,
{
Expand Down
Loading
Loading