Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1cb46d0
stash
emir-karabeg Jun 13, 2026
abb427f
feat(landing): mothership feature stages + pre-footer CTA
andresdjasso Jun 17, 2026
b70032b
feat(landing): reusable platform-page + solutions-page layouts and ro…
emir-karabeg Jun 17, 2026
95cb857
merge: reconcile origin/feat/landing (mothership rebuild) with platfo…
emir-karabeg Jun 17, 2026
0dc4fbd
refactor(landing): convert hero-visual CSS-module keyframes to Tailwind
emir-karabeg Jun 17, 2026
91044a7
refactor(landing): move brand palette from CSS module into LandingShell
emir-karabeg Jun 17, 2026
9482c2d
chore(landing): remove Testimonials from the home page for now
emir-karabeg Jun 17, 2026
fdb22e8
feat(landing): hero send→loader→workflow animation + landing WIP
andresdjasso Jun 18, 2026
3b9d768
feat(landing): responsive pass for iPad + mobile
andresdjasso Jun 19, 2026
1f96062
feat(landing): align hero visual panel to text + logos extent
andresdjasso Jun 19, 2026
3e7bec3
fix(landing): delay hero user bubble until card finishes expanding
andresdjasso Jun 20, 2026
3a97e99
merge: integrate origin/feat/landing (Andres hero/feature work) into …
emir-karabeg Jun 22, 2026
771db02
refactor(landing): isolate new landing — remove dead old-folder code …
emir-karabeg Jun 22, 2026
c9aa158
refactor(landing): token-map hex, fix a11y/SEO, align structure
emir-karabeg Jun 23, 2026
5d32155
style(landing): restore taller hero panel with border-shadow chip chrome
emir-karabeg Jun 23, 2026
f6edb18
feat(landing): swap Volvo for thinkproject and reposition hero logos
emir-karabeg Jun 23, 2026
e50e4dd
style(landing): size hero description with the type scale (text-lg)
emir-karabeg Jun 23, 2026
ed8b4c3
style(landing): hero headline "for AI automations" with break after "…
emir-karabeg Jun 23, 2026
47391e8
style(landing): unify CTA radius and box hero logos in cards
emir-karabeg Jun 23, 2026
be88c42
style(landing): concentric CTA bar radius + tighter logo cards
emir-karabeg Jun 23, 2026
f521b11
style(landing): restore 100px logo cards, scale icons down 15%
emir-karabeg Jun 23, 2026
5181126
style(landing): match sign-up radius to email bar + shrink logo icons
emir-karabeg Jun 23, 2026
c5c03a0
style(landing): shrink hero logo cards
emir-karabeg Jun 23, 2026
e336a82
style(landing): upscale hero logo cards ~25%
emir-karabeg Jun 23, 2026
cac31e0
style(landing): taller logo cards, larger icons, reorder top row
emir-karabeg Jun 23, 2026
1847ffa
style(landing): more card height, swap top-row Rivian/eXp back
emir-karabeg Jun 23, 2026
e0d1cab
feat(landing): add "Trusted by technical teams at" label above hero l…
emir-karabeg Jun 23, 2026
4d24ea9
style(landing): recolor logos to --text-body, match label gap to hero…
emir-karabeg Jun 23, 2026
0a9cff5
style(landing): scale hero CTA down a hair, drop radius to the nav ch…
emir-karabeg Jun 23, 2026
1b843a3
style(landing): round Book-a-demo to rounded-md to match the bar curve
emir-karabeg Jun 23, 2026
c88fc0c
style(landing): match Book-a-demo proportions to the navbar chip
emir-karabeg Jun 23, 2026
3fd1f6e
style(landing): equal inset around Book-a-demo (h-[30px])
emir-karabeg Jun 23, 2026
9a0e460
style(landing): enlarge Book-a-demo to h-[32px], tighten inset to 3px
emir-karabeg Jun 23, 2026
a153632
style(landing): lift hero logos off the bottom again (pb-20)
emir-karabeg Jun 23, 2026
5d41c37
improvement(landing): refine hero and mothership visuals
andresdjasso Jun 23, 2026
5c5e9c5
fix(landing): cap hero fold height so it doesn't stretch on huge moni…
emir-karabeg Jun 23, 2026
72f67d9
refactor(landing): session cleanup — DRY CTA label, drop dead grayscale
emir-karabeg Jun 23, 2026
ab13546
improvement(landing): animate mothership illustrations
andresdjasso Jun 23, 2026
35a5abc
style(landing): solid-ink branding + hero cursor/loader polish
emir-karabeg Jun 25, 2026
06f351c
merge: integrate origin/feat/landing — Andres hero/mothership visuals
emir-karabeg Jun 25, 2026
eb5d8bf
improvement(landing): update feature iso-marks to perfected geometry
andresdjasso Jun 26, 2026
c5f22d5
feat(landing): add pricing, privacy, terms, and changelog pages
emir-karabeg Jun 26, 2026
aeb042c
merge: integrate origin/feat/landing (teammate's perfected iso-mark g…
emir-karabeg Jun 26, 2026
8e1ca77
large edits across landing finalization
emir-karabeg Jun 30, 2026
7d796b3
feat(auth): port OAuth-only signup + Microsoft provider from staging
emir-karabeg Jun 30, 2026
c7acef2
merge: integrate origin/staging into feat/landing
emir-karabeg Jun 30, 2026
82acdf0
fix(icons): render brand icons legibly when bare and on light tiles (…
waleedlatif1 Jun 30, 2026
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
  •  
  •  
  •  
1,206 changes: 1,206 additions & 0 deletions .agents/skills/design-taste-frontend/SKILL.md

Large diffs are not rendered by default.

679 changes: 679 additions & 0 deletions .agents/skills/emil-design-eng/SKILL.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions .claude/commands/add-block.md
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,13 @@ Please provide the SVG and I'll convert it to a React component.
You can usually find this in the service's brand/press kit page, or copy it from their website.
```

When converting the SVG: a **monochrome** logo (single white or black mark) must
use `fill='currentColor'`, never a hardcoded `#fff`/`#000000`. Block icons render
both inside their `bgColor` tile and "bare" on a neutral page (the home Suggested
actions list) in light and dark mode; a hardcoded white/black mark goes invisible
bare on the matching background. Multi-color brand logos keep their own fills.
Verify with `bun run check:bare-icons`.

## Advanced Mode for Optional Fields

Optional fields that are rarely used should be set to `mode: 'advanced'` so they don't clutter the basic UI. This includes:
Expand Down
26 changes: 26 additions & 0 deletions .claude/commands/add-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,31 @@ Once the user provides the SVG:
2. Create a React component that spreads props
3. Ensure viewBox is preserved from the original SVG

### Theme-safety (bare rendering) — REQUIRED

The icon renders both inside its colored `bgColor` tile AND "bare" (no tile) on a
neutral page — e.g. the home **Suggested actions** list — in both light and dark
mode. A monochrome logo whose paths hardcode a single near-white or near-black
fill is invisible bare on the matching background (white-on-white in light mode,
black-on-black in dark mode).

Rules when adding the SVG:

- **Monochrome logos** (a single white or black mark): draw the shape with
`fill='currentColor'`, not `fill='#fff'` / `fill='#000000'`. It then inherits
white inside dark tiles, near-black inside light tiles (via
`getTileIconColorClass`), and the theme-aware `var(--text-icon)` bare — legible
everywhere. Do NOT set `iconColor` for these.
- **Multi-color brand logos** (their own vivid fills): keep the hardcoded fills.
They read on any background. Only set `iconColor` (a vivid brand hex, never a
near-black/near-white tile color) if the bare icon should adopt a brand tint.
- A large white shape with a tiny vivid accent (e.g. a logo where the body is the
white negative space) still vanishes bare — convert the body to `currentColor`.

Verify with `bun run check:bare-icons` (also runs in CI). It flags purely
monochrome hazards; for partial-accent logos, eyeball the suggested-actions list
in both light and dark mode.

## Step 5: Create Triggers (Optional)

If the service supports webhooks, create triggers using the generic `buildTriggerSubBlocks` helper.
Expand Down Expand Up @@ -466,6 +491,7 @@ If creating V2 versions (API-aligned outputs):
- [ ] Asked user to provide SVG
- [ ] Added icon to `components/icons.tsx`
- [ ] Icon spreads props correctly
- [ ] Monochrome marks use `fill='currentColor'` (not hardcoded white/black) so the icon renders bare in light AND dark mode — verified with `bun run check:bare-icons`

### Triggers (if service supports webhooks)
- [ ] Created `triggers/{service}/` directory
Expand Down
1 change: 1 addition & 0 deletions .claude/skills/design-taste-frontend
1 change: 1 addition & 0 deletions .claude/skills/emil-design-eng
2 changes: 1 addition & 1 deletion .cursor/rules/constitution.mdc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Sim product language, positioning, and tone guidelines
globs: ["apps/sim/app/(landing)/**", "apps/sim/app/(home)/**", "apps/docs/**", "apps/sim/app/manifest.ts", "apps/sim/app/sitemap.ts", "apps/sim/app/robots.ts", "apps/sim/app/llms.txt/**", "apps/sim/app/llms-full.txt/**", "apps/sim/app/(landing)/**/structured-data*", "apps/docs/**/structured-data*", "**/metadata*", "**/seo*"]
globs: ["apps/sim/app/(landing)/**", "apps/docs/**", "apps/sim/app/manifest.ts", "apps/sim/app/sitemap.ts", "apps/sim/app/robots.ts", "apps/sim/app/llms.txt/**", "apps/sim/app/llms-full.txt/**", "apps/sim/app/(landing)/**/structured-data*", "apps/docs/**/structured-data*", "**/metadata*", "**/seo*"]
---

# Sim — Language & Positioning
Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/landing-seo-geo.mdc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: SEO and GEO guidelines for the landing page
globs: ["apps/sim/app/(home)/**/*.tsx"]
globs: ["apps/sim/app/(landing)/**/*.tsx"]
---
# Landing Page — SEO / GEO

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ jobs:
- name: Client boundary import audit
run: bun run check:client-boundary

- name: Bare-icon theme-safety audit
run: bun run check:bare-icons

- name: Verify realtime prune graph
run: bun run check:realtime-prune

Expand Down
26 changes: 2 additions & 24 deletions apps/sim/app/(auth)/auth-layout-client.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,5 @@
'use client'

import { useEffect } from 'react'
import AuthBackground from '@/app/(auth)/components/auth-background'
import Navbar from '@/app/(landing)/components/navbar/navbar'
import { AuthShell } from '@/app/(auth)/components'

export default function AuthLayoutClient({ children }: { children: React.ReactNode }) {
useEffect(() => {
document.documentElement.classList.add('dark')
return () => {
document.documentElement.classList.remove('dark')
}
}, [])

return (
<AuthBackground className='dark font-[430] font-season'>
<main className='relative flex min-h-full flex-col text-[var(--landing-text)]'>
<header className='shrink-0 bg-[var(--landing-bg)]'>
<Navbar logoOnly />
</header>
<div className='relative z-30 flex flex-1 items-center justify-center px-4 pb-24'>
<div className='w-full max-w-lg px-4'>{children}</div>
</div>
</main>
</AuthBackground>
)
return <AuthShell>{children}</AuthShell>
}
21 changes: 21 additions & 0 deletions apps/sim/app/(auth)/components/auth-divider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
interface AuthDividerProps {
label: string
}

/**
* The "Or continue with" rule separating the email/password form from the
* social/SSO options. Light tokens only: a `--border` hairline with the label
* knocked out over the `--bg` canvas in `--text-muted`.
*/
export function AuthDivider({ label }: AuthDividerProps) {
return (
<div className='relative'>
<div className='absolute inset-0 flex items-center'>
<div className='w-full border-[var(--border)] border-t' />
</div>
<div className='relative flex justify-center'>
<span className='bg-[var(--bg)] px-4 text-[var(--text-muted)] text-sm'>{label}</span>
</div>
</div>
)
}
41 changes: 41 additions & 0 deletions apps/sim/app/(auth)/components/auth-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { ReactNode } from 'react'
import { Label } from '@sim/emcn'

interface AuthFieldProps {
/** Matches the `id` set on the control rendered as {@link children}. */
htmlFor: string
label: string
/** Validation messages to render beneath the control. */
errors?: string[]
/** Optional right-aligned action shown next to the label (e.g. Forgot password). */
action?: ReactNode
/** The field control — a {@link ChipInput}/{@link PasswordInput}. */
children: ReactNode
}

/**
* A labeled form field row: canonical {@link Label}, an optional inline label
* action, the control, and a validation-message list in the error token. The
* control drives its own invalid chrome through its `error` prop — this wrapper
* only owns the label row and the message list, so every auth field reads and
* spaces identically.
*/
export function AuthField({ htmlFor, label, errors, action, children }: AuthFieldProps) {
const hasErrors = Boolean(errors && errors.length > 0)
return (
<div className='space-y-2'>
<div className='flex items-center justify-between'>
<Label htmlFor={htmlFor}>{label}</Label>
{action}
</div>
{children}
{hasErrors && (
<div className='space-y-1 text-[var(--text-error)] text-caption' aria-live='polite'>
{errors?.map((error) => (
<p key={error}>{error}</p>
))}
</div>
)}
</div>
)
}
28 changes: 28 additions & 0 deletions apps/sim/app/(auth)/components/auth-form-message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'

interface AuthFormMessageProps {
type: 'error' | 'success'
align?: 'left' | 'center'
children: ReactNode
}

/**
* Form-level status copy (not tied to a single field) in the canonical tokens:
* errors in `--text-error`, success in `--brand-accent`. One place owns the
* auth message chrome so success/error states never drift to ad-hoc hex or
* `text-red-*`/`#4CAF50` colors.
*/
export function AuthFormMessage({ type, align = 'left', children }: AuthFormMessageProps) {
return (
<div
className={cn(
'space-y-1 text-caption',
align === 'center' && 'text-center',
type === 'error' ? 'text-[var(--text-error)]' : 'text-[var(--brand-accent)]'
)}
>
{children}
</div>
)
}
21 changes: 21 additions & 0 deletions apps/sim/app/(auth)/components/auth-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ReactNode } from 'react'

interface AuthHeaderProps {
title: string
description: ReactNode
}

/**
* The centered heading + subcopy block shared by every auth page and status
* page. One source of truth for auth heading typography (light tokens, normal
* weight, no bespoke tracking — aligned with the landing scale, sized down for
* the single-column form).
*/
export function AuthHeader({ title, description }: AuthHeaderProps) {
return (
<div className='space-y-1 text-center'>
<h1 className='text-balance text-[32px] text-[var(--text-primary)] leading-[1.2]'>{title}</h1>
<p className='text-[var(--text-muted)] text-base leading-[1.5]'>{description}</p>
</div>
)
}
20 changes: 20 additions & 0 deletions apps/sim/app/(auth)/components/auth-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client'

import * as React from 'react'
import { ChipInput, type ChipInputProps, cn } from '@sim/emcn'
import { AUTH_CONTROL_HEIGHT } from '@/app/(auth)/components/constants'

/**
* The auth text field — a {@link ChipInput} raised to the auth control height
* ({@link AUTH_CONTROL_HEIGHT}) so every labeled field on the auth and invite
* surfaces shares one slightly-taller geometry. All chip props pass through
* (`error`, `endAdornment`, `icon`, …); only the height is owned here, and a
* caller's `className` (layout only) still composes on top.
*/
export const AuthInput = React.forwardRef<HTMLInputElement, ChipInputProps>(
({ className, ...props }, ref) => (
<ChipInput ref={ref} className={cn(AUTH_CONTROL_HEIGHT, className)} {...props} />
)
)

AuthInput.displayName = 'AuthInput'
26 changes: 26 additions & 0 deletions apps/sim/app/(auth)/components/auth-legal-footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AuthTextLink } from '@/app/(auth)/components/auth-text-link'

interface AuthLegalFooterProps {
/** The gerund describing the consent action, e.g. "signing in". */
action: string
}

/**
* The "By {action}, you agree to our Terms / Privacy" fine print shared by the
* login and signup pages. Restyled to muted light tokens with the legal links
* routed through {@link AuthTextLink}, so the consent copy has one source.
*/
export function AuthLegalFooter({ action }: AuthLegalFooterProps) {
return (
<p className='text-center text-[var(--text-muted)] text-caption leading-relaxed'>
By {action}, you agree to our{' '}
<AuthTextLink href='/terms' external>
Terms of Service
</AuthTextLink>{' '}
and{' '}
<AuthTextLink href='/privacy' external>
Privacy Policy
</AuthTextLink>
</p>
)
}
27 changes: 27 additions & 0 deletions apps/sim/app/(auth)/components/auth-nav-prompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ChipLink } from '@sim/emcn'

interface AuthNavPromptProps {
/** Muted lead text before the link (e.g. "Don't have an account?"). */
prompt?: string
href: string
linkLabel: string
/** Side effect to run before navigation (e.g. clearing verification state). */
onNavigate?: () => void
}

/**
* The cross-page navigation row (Sign up / Sign in / Back to login) — an
* optional muted prompt followed by an outline {@link ChipLink} pill, matching
* the landing's secondary chip CTAs. Centralizes the auth nav affordance so the
* pill chrome is described by props, never restyled per page.
*/
export function AuthNavPrompt({ prompt, href, linkLabel, onNavigate }: AuthNavPromptProps) {
return (
<div className='flex items-center justify-center gap-1 text-sm'>
{prompt && <span className='text-[var(--text-muted)]'>{prompt}</span>}
<ChipLink href={href} onClick={onNavigate} className='border border-[var(--border-1)]'>
{linkLabel}
</ChipLink>
</div>
)
}
40 changes: 40 additions & 0 deletions apps/sim/app/(auth)/components/auth-shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { ReactNode } from 'react'
import Link from 'next/link'
import { LogoMark, SimWordmark } from '@/app/(landing)/components/navbar/components'

interface AuthShellProps {
/** Centered content column (the form, status copy, etc.). */
children: ReactNode
/** Optional element pinned to the bottom of the shell (e.g. the support footer). */
footer?: ReactNode
}

/**
* The light auth/status page frame — the single source of truth for the shell
* every auth page and standalone status page wears.
*
* Mirrors the landing chrome: it pins the `light` token layer (so the platform's
* light-mode `var(--*)` tokens resolve regardless of the visitor's theme), uses
* the canvas/`--text-primary` surface, and renders a logo-only header that reuses
* the landing {@link LogoMark} + {@link SimWordmark} at the same nav gutters. The
* single content column is centered and capped for a calm single-form layout.
*/
export function AuthShell({ children, footer }: AuthShellProps) {
return (
<div className='light relative flex min-h-screen flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
<header>
<nav className='mx-auto flex w-full max-w-[1446px] items-center px-12 py-4 max-sm:px-5 max-lg:px-8'>
<Link href='/' aria-label='Sim home' className='flex h-[30px] items-center'>
<LogoMark>
<SimWordmark />
</LogoMark>
</Link>
</nav>
</header>
<div className='flex flex-1 items-center justify-center px-4 pb-16'>
<div className='w-full max-w-[400px]'>{children}</div>
</div>
{footer}
</div>
)
}
49 changes: 49 additions & 0 deletions apps/sim/app/(auth)/components/auth-submit-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { ReactNode } from 'react'
import { Chip, Loader } from '@sim/emcn'
import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'

interface AuthSubmitButtonProps {
children: ReactNode
/** Label shown beside the spinner while the action is in flight. */
loadingLabel: string
loading?: boolean
disabled?: boolean
type?: 'submit' | 'button'
onClick?: () => void
}

/**
* The canonical full-width primary auth action — a `primary`-variant {@link Chip}
* with the shared in-flight spinner. Replaces the legacy dark
* `AUTH_SUBMIT_BTN` class string for every in-scope auth submit (login, signup,
* verify, reset), so the primary CTA chrome lives in exactly one place.
*/
export function AuthSubmitButton({
children,
loadingLabel,
loading = false,
disabled = false,
type = 'submit',
onClick,
}: AuthSubmitButtonProps) {
return (
<Chip
variant='primary'
type={type}
onClick={onClick}
disabled={disabled || loading}
fullWidth
flush
className={AUTH_BUTTON_CLASS}
>
{loading ? (
<span className='flex items-center gap-2'>
<Loader className='size-4' animate />
{loadingLabel}
</span>
) : (
children
)}
</Chip>
)
}
Loading
Loading