feat(branding): sim wordmark favicon/OG, docs footer parity, footer peel (#5587)

* feat(branding): sim wordmark favicon/OG, docs footer parity, footer peel

- replace apps/sim favicon and default OG image with the sim wordmark
  logo (OG image widened, logo kept at native size)
- swap the docs navbar logo to the icon-only mark (no wordmark text)
- add a scroll "peel" reveal effect to the landing footer using a
  sticky-positioned illustration, pure CSS, no scroll listeners
- port the same footer (link directory + peel effect) to the docs app
  so both apps are visually consistent; add Academy to Resources
- rebuild the docs OG image template to match the site's existing
  blog/library cover style (wordmark top-left, arrow top-right, title
  bottom-left), working around a Satori text-measurement bug that
  doubled the gap after certain words

* fix(docs): correct OG font, mobile logo, and footer stacking

- switch the docs OG image title font from Geist to the site's real
  brand font (Season Sans), instantiated as a static TTF weight since
  Satori can't parse WOFF2 or variable fonts; served from /static/
  so the i18n proxy's matcher (which excludes static but not fonts)
  doesn't intercept it
- fix DocsLayout's nav.title (fumadocs' own mobile menu slot) to show
  the wordmark instead of the icon mark
- add an isolated stacking context + higher z-index to both the docs
  and sim app footers so fumadocs' sticky z-20 sidebar can't paint
  over the footer content or the peel reveal

* fix(docs): match OG template exactly, fix gradient/origin bugs

- recalibrate the OG image to the reference cover template's actual
  measured values: 1200x675 canvas (was 630), ~26px margins (was
  56-64px), ink #525252 (was #3f3f3f), larger wordmark/arrow/title
  sizing — confirmed by direct pixel measurement of the reference
  cover.jpg, not estimation
- fix SimLogoIcon/SimLogoFull's SVG gradient ids to be unique via
  useId() instead of a fixed string, so multiple instances on one
  page don't collide (Greptile P2)
- fix SIM_SITE_URL to be a hardcoded sim.ai constant instead of
  deriving from NEXT_PUBLIC_APP_URL, which reflects wherever this
  deployment runs, not the fixed public marketing site (Greptile P1)

* fix(docs): route Jira footer link to the docs guide, not sim.ai

Every other integration in the footer's Integrations column links to
its own docs.sim.ai guide; Jira was the only one pointing at the
marketing site's landing page instead, despite docs having its own
/integrations/jira guide. Matches the established pattern.

* fix(docs): fix sidebar-divider grid regression, footer-peel path/positioning, OG sizing, and prune stray comments

- #nd-docs-layout::before divider now spans the full grid explicitly
  (grid-row/grid-column: 1 / -1) instead of being auto-placed into a
  real content cell, which was pushing page content down
- footer-peel.jpg moved under /static/landing/ (was 404ing behind the
  i18n proxy's non-static path matcher) and wrapped in a relative div
  so next/image's fill positioning is valid under the sticky container
- OG route: corrected title font sizes and char-width ratio so long
  titles wrap to 2 lines instead of 3, and resized the corner arrow to
  match the reference cover template's proportions
- swapped the icon-only desktop navbar logo back to the wordmark
- removed stray non-TSDoc comments, folded into TSDoc where the
  explanation was worth keeping

* fix(footer): remove sticky peel reveal, keep clean footer link directory

The peel's "reveal window" relied on position: sticky bottom-detaching
into a containing block whose extra height came from padding-bottom —
that combination doesn't reliably work in WebKit/Safari (sticky never
gets room to engage when the surplus height is padding rather than an
explicit height or content), so the peel stayed permanently covered by
the footer regardless of viewport size. Rather than carry that
unreliable technique further, removing it entirely from both apps and
reverting to the plain footer link directory.
This commit is contained in:
Waleed
2026-07-10 20:44:55 -07:00
committed by GitHub
parent 1c604152b3
commit f3582ed197
22 changed files with 375 additions and 80 deletions
+1 -1
View File
@@ -323,7 +323,7 @@ export async function generateMetadata(props: {
{
url: ogImageUrl,
width: 1200,
height: 630,
height: 675,
alt: data.title,
},
],
+4 -2
View File
@@ -9,8 +9,9 @@ import {
SidebarItem,
SidebarSeparator,
} from '@/components/docs-layout/sidebar-components'
import { Footer } from '@/components/footer/footer'
import { Navbar } from '@/components/navbar/navbar'
import { SimLogoFull } from '@/components/ui/sim-logo'
import { SimWordmark } from '@/components/ui/sim-logo'
import { i18n } from '@/lib/i18n'
import { serializeJsonLd } from '@/lib/json-ld'
import { source } from '@/lib/source'
@@ -101,7 +102,7 @@ export default async function Layout({ children, params }: LayoutProps) {
<DocsLayout
tree={source.pageTree[lang]}
nav={{
title: <SimLogoFull className='h-[22px] w-auto' />,
title: <SimWordmark className='h-[18px]' />,
}}
sidebar={{
tabs: false,
@@ -121,6 +122,7 @@ export default async function Layout({ children, params }: LayoutProps) {
>
{children}
</DocsLayout>
<Footer />
<AskAI locale={lang} />
</RootProvider>
</body>
+107 -64
View File
@@ -5,37 +5,39 @@ import type { NextRequest } from 'next/server'
export const runtime = 'edge'
const TITLE_FONT_SIZE = {
large: 64,
medium: 56,
small: 48,
large: 105,
medium: 91,
small: 81,
} as const
/** Average glyph width as a fraction of font size, for this weight/family — used to pack words into lines. */
const AVG_CHAR_WIDTH_EM = 0.46
const TITLE_BOX_WIDTH = 1020
const FONT_CACHE_REVALIDATE_SECONDS = 60 * 60 * 24 * 30
/** Measured off the reference cover template (`apps/sim/public/library/best-zapier-alternatives/cover.jpg`). */
const INK_COLOR = '#525252'
const OG_CONTAINER_STYLE = {
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '56px 64px',
background: '#121212',
fontFamily: 'Geist',
padding: '26px',
background: '#c3c3c3',
fontFamily: 'Season',
} satisfies CSSProperties
const OG_TITLE_STYLE = {
fontWeight: 500,
color: '#fafafa',
lineHeight: 1.2,
letterSpacing: '-0.02em',
} satisfies CSSProperties
const OG_FOOTER_STYLE = {
const OG_HEADER_STYLE = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
alignItems: 'flex-start',
width: '100%',
} satisfies CSSProperties
const OG_DOMAIN_STYLE = {
fontSize: 24,
fontWeight: 400,
color: '#71717a',
const OG_TITLE_STYLE = {
display: 'flex',
flexDirection: 'column',
fontWeight: 600,
color: INK_COLOR,
lineHeight: 1.15,
width: `${TITLE_BOX_WIDTH}px`,
} satisfies CSSProperties
function getTitleFontSize(title: string): number {
@@ -52,90 +54,131 @@ function getTitleStyle(title: string): CSSProperties {
}
/**
* Loads a Google Font dynamically by fetching the CSS and extracting the font URL.
* Greedily packs words into lines that fit `TITLE_BOX_WIDTH` at `fontSize`,
* then joins each line with U+00A0 instead of a plain space. Satori
* (`next/og`'s renderer) has a text-measurement bug where the first plain
* space (U+0020) in a text node renders at roughly double width — a
* non-breaking space measures correctly and reads identically at this size,
* so it sidesteps the bug instead of fighting Satori's own line-wrapping
* (which is also disabled here — lines are pre-split, not auto-wrapped).
*/
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 cssResponse = await fetch(url, {
next: { revalidate: FONT_CACHE_REVALIDATE_SECONDS },
})
function wrapTitleLines(title: string, fontSize: number): string[] {
const maxCharsPerLine = Math.floor(TITLE_BOX_WIDTH / (fontSize * AVG_CHAR_WIDTH_EM))
const words = title.split(' ')
const lines: string[] = []
let current = ''
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], {
next: { revalidate: FONT_CACHE_REVALIDATE_SECONDS },
})
if (response.ok) {
return await response.arrayBuffer()
for (const word of words) {
const candidate = current ? `${current} ${word}` : word
if (candidate.length > maxCharsPerLine && current) {
lines.push(current)
current = word
} else {
current = candidate
}
}
if (current) lines.push(current)
throw new Error('Failed to load font data')
return lines.map((line) => line.replace(/ /g, ' '))
}
/**
* Sim logo with icon and "Sim" text for OG image.
* Loads a static (600/semibold) TTF instance of the site's own Season Sans
* font — the platform's real brand/body font, also used by the library/blog
* cover template this OG image matches. Instantiated from the variable font
* at `apps/docs/app/fonts/SeasonSansUprightsVF.woff2` (`fonttools
* varLib.instancer wght=600`, then flavor-stripped to plain TTF) rather than
* loading the variable WOFF2 directly: Satori (`next/og`'s renderer) can't
* parse variable fonts without excessive memory use, and can't parse WOFF2
* at all ("Unsupported OpenType signature wOF2") — it needs an uncompressed
* TTF/OTF. Fetched over HTTP since the edge runtime has no filesystem access
* — served from `/static/fonts/` (not `/fonts/`) so it isn't intercepted by
* the site's i18n proxy (`proxy.ts`), whose matcher excludes `static` but
* not `fonts`.
*/
function SimLogoFull() {
async function loadSeasonFont(baseUrl: string): Promise<ArrayBuffer> {
const response = await fetch(new URL('/static/fonts/SeasonSans-600-static.ttf', baseUrl), {
next: { revalidate: FONT_CACHE_REVALIDATE_SECONDS },
})
if (!response.ok) {
throw new Error(`Failed to load font data: ${response.status} ${response.statusText}`)
}
return await response.arrayBuffer()
}
/** "sim" wordmark, no icon — same brandbook workmark geometry as the docs navbar/landing OG cards. */
function SimWordmark() {
return (
<svg height='34' viewBox='720 440 1020 320' fill='none'>
{/* Green icon - top left shape with cutout */}
<svg width='118' height='57' viewBox='0 0 800 386' fill='none'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M875.791 577.171C875.791 581.922 873.911 586.483 870.576 589.842L870.098 590.323C866.764 593.692 862.234 595.575 857.517 595.575H750.806C740.978 595.575 733 603.6 733 613.498V728.902C733 738.799 740.978 746.826 750.806 746.826H865.382C875.209 746.826 883.177 738.799 883.177 728.902V620.853C883.177 616.448 884.912 612.222 888.008 609.104C891.093 605.997 895.29 604.249 899.664 604.249H1008.16C1017.99 604.249 1025.96 596.224 1025.96 586.327V470.923C1025.96 461.025 1017.99 453 1008.16 453H893.586C883.759 453 875.791 461.025 875.791 470.923V577.171ZM910.562 477.566H991.178C996.922 477.566 1001.57 482.254 1001.57 488.029V569.22C1001.57 574.995 996.922 579.683 991.178 579.683H910.562C904.828 579.683 900.173 574.995 900.173 569.22V488.029C900.173 482.254 904.828 477.566 910.562 477.566Z'
fill='#33C482'
d='M0 293.75h53.4128c0 14.748 5.3413 26.506 16.0239 35.275 10.6826 8.37 25.1238 12.555 43.3233 12.555 19.783 0 35.016-3.786 45.698-11.36 10.683-7.971 16.024-18.534 16.024-31.687 0-9.566-2.967-17.538-8.902-23.915-5.539-6.378-15.826-11.559-30.861-15.545l-51.0389-11.958c-25.7173-6.377-44.9063-16.142-57.5672-29.296-12.2651-13.153-18.39771-30.491-18.39771-52.015 0-17.936 4.55001-33.481 13.64991-46.635 9.4957-13.153 22.3543-23.3169 38.576-30.4914 16.6173-7.1745 35.6086-10.7619 56.9739-10.7619 21.365 0 39.763 3.7866 55.193 11.3598 15.826 7.5731 28.091 18.1355 36.796 31.6875 9.1 13.552 13.847 29.695 14.243 48.428h-53.413c-.395-15.146-5.341-26.904-14.837-35.275-9.495-8.37-22.75-12.555-39.763-12.555-17.4083 0-30.8604 3.786-40.356 11.36-9.4956 7.573-14.2434 17.936-14.2434 31.089 0 19.531 14.2434 32.884 42.7304 40.058l51.039 12.556c24.53 5.58 42.928 14.747 55.193 27.502 12.265 12.356 18.398 29.296 18.398 50.82 0 18.335-4.946 34.477-14.837 48.428-9.891 13.552-23.541 24.114-40.95 31.687-17.013 7.175-37.191 10.762-60.534 10.762-34.0265 0-61.1285-8.37-81.3067-25.111-20.1782-16.74-30.2673-39.061-30.2673-66.962z'
fill={INK_COLOR}
/>
{/* Green icon - bottom right square */}
<path
d='M1008.3 624.59H923.113C912.786 624.59 904.414 633.022 904.414 643.423V728.171C904.414 738.572 912.786 747.004 923.113 747.004H1008.3C1018.63 747.004 1027 738.572 1027 728.171V643.423C1027 633.022 1018.63 624.59 1008.3 624.59Z'
fill='#33C482'
d='m267.175 385.826v-292.3631c22.244 8.1331 32.053 8.1331 55.787 0v292.3631zm27.3-311.6891c-9.891 0-18.596-3.5872-26.113-10.7618-7.122-7.5731-10.683-16.342-10.683-26.3067 0-10.3632 3.561-19.132 10.683-26.3066 7.517-7.17453 16.222-10.7618 26.113-10.7618 10.287 0 18.991 3.58727 26.113 10.7618 7.122 7.1746 10.682 15.9434 10.682 26.3066 0 9.9647-3.56 18.7336-10.682 26.3067-7.122 7.1746-15.826 10.7618-26.113 10.7618z'
fill={INK_COLOR}
/>
{/* "Sim" text - white for dark background */}
<path
d='M1210.54 515.657C1226.65 515.657 1240.59 518.51 1252.31 524.257H1252.31C1264.3 529.995 1273.63 538.014 1280.26 548.319H1280.26C1287.19 558.635 1290.78 570.899 1291.08 585.068L1291.1 586.089H1249.11L1249.09 585.115C1248.8 574.003 1245.18 565.493 1238.32 559.451C1231.45 553.399 1221.79 550.308 1209.21 550.308C1196.3 550.308 1186.48 553.113 1179.61 558.588C1172.76 564.046 1169.33 571.499 1169.33 581.063C1169.33 588.092 1171.88 593.978 1177.01 598.783C1182.17 603.618 1189.99 607.399 1200.56 610.061H1200.56L1238.77 619.451C1257.24 623.65 1271.21 630.571 1280.57 640.293L1281.01 640.739C1290.13 650.171 1294.64 662.97 1294.64 679.016C1294.64 692.923 1290.88 705.205 1283.34 715.822L1283.33 715.834C1275.81 726.134 1265.44 734.14 1252.26 739.866L1252.25 739.871C1239.36 745.302 1224.12 748 1206.54 748C1180.9 748 1160.36 741.696 1145.02 728.984C1129.67 716.258 1122 699.269 1122 678.121V677.121H1163.99V678.121C1163.99 688.869 1167.87 697.367 1175.61 703.722L1176.34 704.284C1184.04 709.997 1194.37 712.902 1207.43 712.902C1222.13 712.902 1233.3 710.087 1241.07 704.588C1248.8 698.812 1252.64 691.21 1252.64 681.699C1252.64 674.769 1250.5 669.057 1246.25 664.49L1246.23 664.478L1246.22 664.464C1242.28 659.929 1234.83 656.119 1223.64 653.152L1185.43 644.208L1185.42 644.204C1166.05 639.407 1151.49 632.035 1141.83 622.012L1141.83 622.006L1141.82 622C1132.43 611.94 1127.78 598.707 1127.78 582.405C1127.78 568.81 1131.23 556.976 1138.17 546.949L1138.18 546.941L1138.19 546.933C1145.41 536.936 1155.18 529.225 1167.48 523.793L1167.48 523.79C1180.07 518.36 1194.43 515.657 1210.54 515.657ZM1323.39 521.979C1331.68 525.008 1337.55 526.482 1343.51 526.482C1349.48 526.482 1355.64 525.005 1364.49 521.973L1365.82 521.52V742.633H1322.05V521.489L1323.39 521.979ZM1642.01 515.657C1667.11 515.657 1686.94 523.031 1701.39 537.876C1715.83 552.716 1723 572.968 1723 598.507V742.633H1680.12V608.794C1680.12 591.666 1675.72 578.681 1667.07 569.681L1667.06 569.669L1667.04 569.656C1658.67 560.359 1647.26 555.675 1632.68 555.675C1622.47 555.675 1613.47 558.022 1605.64 562.69L1605.63 562.696C1598.11 567.064 1592.17 573.475 1587.8 581.968C1583.44 590.448 1581.25 600.424 1581.25 611.925V742.633H1537.92V608.347C1537.92 591.208 1533.67 578.376 1525.31 569.68L1525.31 569.674L1525.3 569.668C1516.93 560.664 1505.52 556.122 1490.93 556.122C1480.72 556.122 1471.72 558.469 1463.89 563.138L1463.88 563.144C1456.36 567.511 1450.41 573.922 1446.05 582.415L1446.05 582.422L1446.04 582.428C1441.69 590.602 1439.5 600.423 1439.5 611.925V742.633H1395.72V521.919H1435.05V554.803C1439.92 544.379 1447.91 535.465 1458.37 528.356C1470.71 519.875 1485.58 515.657 1502.93 515.657C1522.37 515.657 1538.61 520.931 1551.55 531.538C1560.38 538.771 1567.1 547.628 1571.72 558.091C1576.05 547.619 1582.83 538.757 1592.07 531.524C1605.61 520.93 1622.28 515.657 1642.01 515.657ZM1343.49 452C1351.45 452 1358.23 454.786 1363.75 460.346C1369.27 465.905 1372.04 472.721 1372.04 480.73C1372.04 488.452 1369.27 495.254 1363.77 501.096L1363.76 501.105L1363.75 501.115C1358.23 506.675 1351.45 509.461 1343.49 509.461C1335.81 509.461 1329.05 506.669 1323.25 501.134L1323.23 501.115L1323.21 501.096C1317.71 495.254 1314.94 488.452 1314.94 480.73C1314.94 472.721 1317.7 465.905 1323.23 460.346L1323.24 460.337L1323.25 460.327C1329.05 454.792 1335.81 452 1343.49 452Z'
fill='#fafafa'
d='m421.362 385.823h-55.786v-292.3624h49.852v49.3294c5.934-16.342 17.408-30.197 33.234-40.959 16.222-11.1605 35.807-16.7407 58.754-16.7407 25.718 0 47.083 6.9752 64.096 20.9257 17.013 13.951 28.091 32.485 33.234 55.603h-10.089c3.957-23.118 14.837-41.652 32.642-55.603 17.804-13.9505 39.762-20.9257 65.875-20.9257 33.235 0 59.348 9.7653 78.339 29.2957 18.991 19.531 28.487 46.236 28.487 80.116v191.321h-54.6v-177.57c0-23.118-5.934-40.855-17.804-53.211-11.474-12.755-27.102-19.132-46.885-19.132-13.847 0-26.113 3.189-36.795 9.566-10.287 5.979-18.398 14.748-24.333 26.307-5.934 11.559-8.902 25.111-8.902 40.655v173.385h-55.193v-178.168c0-23.118-5.737-40.655-17.211-52.613-11.474-12.356-27.102-18.534-46.885-18.534-13.847 0-26.112 3.189-36.795 9.566-10.287 5.979-18.398 14.748-24.333 26.307-5.934 11.16-8.902 24.513-8.902 40.057z'
fill={INK_COLOR}
/>
</svg>
)
}
/** Diagonal "open" arrow, top-right — matches the library/blog cover template. */
function CornerArrow() {
return (
<svg width='58' height='58' viewBox='0 0 24 24' fill='none'>
<path
d='M2 22 22 2M22 2H12M22 2V12'
stroke={INK_COLOR}
strokeWidth={3.6}
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
)
}
/**
* Generates dynamic Open Graph images for documentation pages.
* Style matches Cursor docs: dark background, title at top, logo bottom-left, domain bottom-right.
* Generates dynamic Open Graph images for documentation pages. Matches the
* site's library/blog cover template: light gray background, "sim" wordmark
* top-left, an open/diagonal arrow top-right, and the page title large and
* bold at the bottom-left.
*/
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const title = searchParams.get('title') || 'Documentation'
const allText = `${title}docs.sim.ai`
const fontData = await loadGoogleFont('Geist', '400;500;600', allText)
const fontData = await loadSeasonFont(request.url)
const fontSize = getTitleFontSize(title)
const titleLines = wrapTitleLines(title, fontSize)
return new ImageResponse(
<div style={OG_CONTAINER_STYLE}>
{/* Title at top */}
<span style={getTitleStyle(title)}>{title}</span>
<div style={OG_HEADER_STYLE}>
<SimWordmark />
<CornerArrow />
</div>
{/* Footer: icon left, domain right */}
<div style={OG_FOOTER_STYLE}>
<SimLogoFull />
<span style={OG_DOMAIN_STYLE}>docs.sim.ai</span>
<div style={getTitleStyle(title)}>
{titleLines.map((line, index) => (
<span key={index}>{line}</span>
))}
</div>
</div>,
{
width: 1200,
height: 630,
height: 675,
fonts: [
{
name: 'Geist',
name: 'Season',
data: fontData,
style: 'normal',
weight: 600,
},
],
}
+11 -3
View File
@@ -296,12 +296,20 @@ aside#nd-sidebar [data-radix-scroll-area-viewport] {
min-height: var(--fd-docs-height) !important;
}
/* Sidebar divider line — fixed to viewport, full height from navbar to bottom */
/* Sidebar divider line — sticky within the docs layout box, so it ends where
the layout does instead of bleeding into (or past) the footer below it.
#nd-docs-layout is a CSS grid (see fumadocs' Container slot); a sticky
pseudo-element stays in normal flow, so without an explicit grid-area it
gets auto-placed into a real content cell and skews that cell's sizing.
Spanning the full grid keeps it purely decorative/overlaid instead. */
#nd-docs-layout::before {
content: "";
position: fixed;
display: block;
position: sticky;
grid-row: 1 / -1;
grid-column: 1 / -1;
top: 92px; /* below navbar */
bottom: 0;
height: calc(100dvh - 92px);
left: calc(var(--sidebar-offset) + var(--fd-sidebar-width));
width: 1px;
background-color: var(--surface-active);
+1 -1
View File
@@ -72,7 +72,7 @@ export const metadata = {
{
url: `${DOCS_BASE_URL}/api/og?title=Sim%20Documentation`,
width: 1200,
height: 630,
height: 675,
alt: 'Sim Documentation',
},
],
+160
View File
@@ -0,0 +1,160 @@
import Link from 'next/link'
import { SimWordmark } from '@/components/ui/sim-logo'
import { SIM_SITE_URL } from '@/lib/urls'
/**
* Docs footer - the same site link directory as the main app's landing
* footer (`apps/sim/app/(landing)/components/footer`), ported here so both
* apps share one consistent footer. Links that live on sim.ai are absolute
* (docs.sim.ai is a different origin); links that live on docs.sim.ai itself
* (Academy, API Reference, blocks, integrations guides, …) stay relative.
*/
const LINK_CLASS =
'text-sm text-[var(--text-muted)] transition-colors hover:text-[var(--text-primary)]'
interface FooterItem {
label: string
href: string
external?: boolean
}
const PRODUCT_LINKS: FooterItem[] = [
{ label: 'Enterprise', href: `${SIM_SITE_URL}/enterprise`, external: true },
{ label: 'Mothership', href: '/mothership' },
{ label: 'Workflows', href: '/introduction' },
{ label: 'Knowledge Base', href: '/knowledgebase' },
{ label: 'Tables', href: '/tables' },
{ label: 'MCP', href: '/agents/mcp' },
{ label: 'API', href: '/api-reference/getting-started' },
{ label: 'Self Hosting', href: '/platform/self-hosting' },
{ label: 'Status', href: 'https://status.sim.ai', external: true },
]
const RESOURCES_LINKS: FooterItem[] = [
{ label: 'Blog', href: `${SIM_SITE_URL}/blog`, external: true },
{ label: 'Academy', href: '/academy' },
{ label: 'Compare', href: `${SIM_SITE_URL}/comparison`, external: true },
{ label: 'Careers', href: `${SIM_SITE_URL}/careers`, external: true },
{ label: 'Changelog', href: `${SIM_SITE_URL}/changelog`, external: true },
{ label: 'Contact', href: `${SIM_SITE_URL}/contact`, external: true },
]
/** Top model providers — mirrors the landing footer's top 8 catalog providers. */
const MODEL_LINKS: FooterItem[] = [
{ label: 'All Models', href: `${SIM_SITE_URL}/models`, external: true },
{ label: 'OpenAI', href: `${SIM_SITE_URL}/models/openai`, external: true },
{ label: 'Anthropic', href: `${SIM_SITE_URL}/models/anthropic`, external: true },
{ label: 'Google', href: `${SIM_SITE_URL}/models/google`, external: true },
{ label: 'DeepSeek', href: `${SIM_SITE_URL}/models/deepseek`, external: true },
{ label: 'xAI', href: `${SIM_SITE_URL}/models/xai`, external: true },
{ label: 'Cerebras', href: `${SIM_SITE_URL}/models/cerebras`, external: true },
{ label: 'Groq', href: `${SIM_SITE_URL}/models/groq`, external: true },
{ label: 'Sakana AI', href: `${SIM_SITE_URL}/models/sakana`, external: true },
]
const BLOCK_LINKS: FooterItem[] = [
{ label: 'Agent', href: '/workflows/blocks/agent' },
{ label: 'Router', href: '/workflows/blocks/router' },
{ label: 'Function', href: '/workflows/blocks/function' },
{ label: 'Condition', href: '/workflows/blocks/condition' },
{ label: 'API Block', href: '/workflows/blocks/api' },
{ label: 'Workflow', href: '/workflows/blocks/workflow' },
{ label: 'Parallel', href: '/workflows/blocks/parallel' },
{ label: 'Guardrails', href: '/workflows/blocks/guardrails' },
{ label: 'Evaluator', href: '/workflows/blocks/evaluator' },
{ label: 'Loop', href: '/workflows/blocks/loop' },
]
const INTEGRATION_LINKS: FooterItem[] = [
{ label: 'All Integrations', href: `${SIM_SITE_URL}/integrations`, external: true },
{ label: 'Slack', href: '/integrations/slack' },
{ label: 'GitHub', href: '/integrations/github' },
{ label: 'Gmail', href: '/integrations/gmail' },
{ label: 'Notion', href: '/integrations/notion' },
{ label: 'Salesforce', href: '/integrations/salesforce' },
{ label: 'Jira', href: '/integrations/jira' },
{ label: 'Linear', href: '/integrations/linear' },
{ label: 'Supabase', href: '/integrations/supabase' },
{ label: 'Stripe', href: '/integrations/stripe' },
]
const SOCIAL_LINKS: FooterItem[] = [
{ label: 'X (Twitter)', href: 'https://x.com/simdotai', external: true },
{
label: 'LinkedIn',
href: 'https://www.linkedin.com/company/simstudioai/',
external: true,
},
{ label: 'Discord', href: 'https://discord.gg/Hr4UWYEcTT', external: true },
{
label: 'GitHub',
href: 'https://github.com/simstudioai/sim',
external: true,
},
]
const LEGAL_LINKS: FooterItem[] = [
{ label: 'Terms of Service', href: `${SIM_SITE_URL}/terms`, external: true },
{ label: 'Privacy Policy', href: `${SIM_SITE_URL}/privacy`, external: true },
]
function FooterColumn({ title, items }: { title: string; items: FooterItem[] }) {
return (
<div>
<h3 className='mb-4 text-[var(--text-primary)] text-sm'>{title}</h3>
<div className='flex flex-col gap-2.5'>
{items.map(({ label, href, external }) =>
external ? (
<a
key={label}
href={href}
target='_blank'
rel='noopener noreferrer'
className={LINK_CLASS}
>
{label}
</a>
) : (
<Link key={label} href={href} className={LINK_CLASS}>
{label}
</Link>
)
)}
</div>
</div>
)
}
export function Footer() {
return (
<footer className='mt-[120px] w-full border-[var(--border)] border-t bg-[var(--bg)] max-sm:mt-16 max-lg:mt-[88px]'>
<div className='mx-auto w-full max-w-[1460px] px-20 pt-16 pb-16 max-sm:px-5 max-lg:px-8 max-lg:pt-12 max-lg:pb-12'>
<nav
aria-label='Footer navigation'
itemScope
itemType='https://schema.org/SiteNavigationElement'
className='grid grid-cols-8 gap-x-8 gap-y-10 max-sm:grid-cols-2 max-sm:gap-y-8 max-lg:grid-cols-3'
>
<a
href={SIM_SITE_URL}
aria-label='Sim home'
className='flex h-[18px] items-center max-lg:col-span-full max-lg:mb-2'
>
<SimWordmark />
</a>
<FooterColumn title='Product' items={PRODUCT_LINKS} />
<FooterColumn title='Resources' items={RESOURCES_LINKS} />
<FooterColumn title='Blocks' items={BLOCK_LINKS} />
<FooterColumn title='Integrations' items={INTEGRATION_LINKS} />
<FooterColumn title='Models' items={MODEL_LINKS} />
<FooterColumn title='Socials' items={SOCIAL_LINKS} />
<FooterColumn title='Legal' items={LEGAL_LINKS} />
</nav>
<p className='mt-16 text-[var(--text-muted)] text-sm'>© 2026 Sim. All rights reserved.</p>
</div>
</footer>
)
}
+3 -3
View File
@@ -5,7 +5,7 @@ import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { LanguageDropdown } from '@/components/ui/language-dropdown'
import { SearchTrigger } from '@/components/ui/search-trigger'
import { SimLogoFull } from '@/components/ui/sim-logo'
import { SimWordmark } from '@/components/ui/sim-logo'
import { ThemeToggle } from '@/components/ui/theme-toggle'
import { cn } from '@/lib/utils'
@@ -44,8 +44,8 @@ export function Navbar() {
paddingRight: 'calc(var(--toc-offset) + var(--nav-inset))',
}}
>
<Link href='/' className='flex min-w-[100px] items-center'>
<SimLogoFull className='h-[20px] w-auto' />
<Link href='/' className='flex items-center'>
<SimWordmark className='h-[18px]' />
</Link>
<div className='-translate-x-1/2 absolute left-1/2 flex items-center justify-center'>
+78 -2
View File
@@ -1,17 +1,93 @@
'use client'
import { useId } from 'react'
import { cn } from '@/lib/utils'
interface SimLogoProps {
className?: string
}
/**
* Inline "sim" wordmark, no separate icon mark — same paths as the landing
* footer's `SimWordmark` (`apps/sim/app/(landing)/components/navbar/components/sim-wordmark`),
* ported here so the docs footer can match it exactly. Filled with
* `var(--text-body)` so it reads as one solid ink matching surrounding text.
*/
export function SimWordmark({ className }: SimLogoProps) {
return (
<svg
viewBox='0 0 441 212'
width={37}
height={18}
fill='none'
aria-hidden='true'
className={cn('-translate-y-[1.5px] h-[18px] w-auto', className)}
>
<g fill='var(--text-body)'>
<path d='M0 160.9H29.51C29.51 169.08 32.46 175.61 38.37 180.48C44.27 185.12 52.25 187.44 62.31 187.44C73.24 187.44 81.65 185.34 87.56 181.14C93.46 176.71 96.41 170.85 96.41 163.55C96.41 158.24 94.77 153.82 91.49 150.28C88.43 146.74 82.75 143.86 74.44 141.65L46.24 135.01C32.03 131.47 21.42 126.05 14.43 118.75C7.65 111.45 4.26 101.83 4.26 89.88C4.26 79.93 6.78 71.3 11.81 64C17.05 56.7 24.16 51.06 33.12 47.08C42.3 43.09 52.8 41.1 64.6 41.1C76.41 41.1 86.57 43.2 95.1 47.41C103.84 51.61 110.62 57.47 115.43 64.99C120.46 72.52 123.08 81.48 123.3 91.87H93.79C93.57 83.47 90.84 76.94 85.59 72.3C80.34 67.65 73.02 65.33 63.62 65.33C54 65.33 46.57 67.43 41.32 71.63C36.07 75.83 33.45 81.59 33.45 88.89C33.45 99.73 41.32 107.14 57.06 111.12L85.26 118.09C98.81 121.19 108.98 126.28 115.76 133.35C122.53 140.21 125.92 149.61 125.92 161.56C125.92 171.74 123.19 180.7 117.73 188.44C112.26 195.96 104.72 201.82 95.1 206.03C85.7 210.01 74.55 212 61.65 212C42.85 212 27.87 207.35 16.72 198.06C5.57 188.77 0 176.38 0 160.9Z' />
<path d='M232.8 212H202.13L202.13 49.76H229.54V77.39C232.8 68.34 239.11 60.66 247.81 54.7C256.73 48.52 267.5 45.43 280.12 45.43C294.26 45.43 306.01 49.29 315.36 57.02C324.72 64.75 330.81 75.01 333.64 87.82H328.09C330.27 75.01 336.25 64.75 346.04 57.02C355.83 49.29 367.9 45.43 382.26 45.43C400.54 45.43 414.89 50.84 425.34 61.66C435.78 72.47 441 87.26 441 106.03V212H410.98V113.65C410.98 100.84 407.71 91.02 401.19 84.17C394.88 77.11 386.29 73.58 375.41 73.58C367.79 73.58 361.05 75.34 355.17 78.88C349.52 82.19 345.06 87.04 341.8 93.45C338.53 99.85 336.9 107.36 336.9 115.97V212H306.55V113.32C306.55 100.51 303.4 90.8 297.09 84.17C290.78 77.33 282.19 73.91 271.31 73.91C263.69 73.91 256.95 75.67 251.08 79.21C245.42 82.52 240.96 87.38 237.7 93.78C234.43 99.96 232.8 107.36 232.8 115.97V212Z' />
<path d='M184.83 20.55C184.83 31.9 175.64 41.1 164.29 41.1C152.95 41.1 143.76 31.9 143.76 20.55C143.76 9.2 152.95 0 164.29 0C175.64 0 184.83 9.2 184.83 20.55Z' />
<path d='M179.43 212H149.16V49.76C153.76 51.91 158.88 53.12 164.29 53.12C169.7 53.12 174.83 51.91 179.43 49.76V212Z' />
</g>
</svg>
)
}
/**
* Icon-only Sim mark, no wordmark text. Same brandbook icon geometry as
* {@link SimLogoFull}'s icon, at its native square viewBox.
*/
export function SimLogoIcon({ className }: SimLogoProps) {
const gradientId = `sim-logo-icon-gradient-${useId()}`
return (
<svg
viewBox='0 0 222 222'
fill='none'
xmlns='http://www.w3.org/2000/svg'
className={cn('size-7', className)}
aria-label='Sim'
>
<defs>
<linearGradient
id={gradientId}
gradientUnits='userSpaceOnUse'
x1='129.434'
y1='129.266'
x2='185.629'
y2='185.33'
>
<stop offset='0' />
<stop offset='1' stopOpacity='0' />
</linearGradient>
</defs>
<path
clipRule='evenodd'
d='m107.822 93.7612c0 3.5869-1.419 7.0308-3.938 9.5668l-.361.364c-2.517 2.544-5.9375 3.966-9.4994 3.966h-80.5781c-7.42094 0-13.4455 6.06-13.4455 13.533v87.141c0 7.474 6.02456 13.534 13.4455 13.534h86.5167c7.4208 0 13.4378-6.06 13.4378-13.534v-81.587c0-3.326 1.31-6.517 3.647-8.871 2.33-2.347 5.499-3.667 8.802-3.667h81.928c7.421 0 13.437-6.059 13.437-13.533v-87.1407c0-7.47374-6.016-13.5333-13.437-13.5333h-86.517c-7.421 0-13.438 6.05956-13.438 13.5333zm26.256-75.2112h60.874c4.337 0 7.844 3.5393 7.844 7.9003v61.3071c0 4.3604-3.507 7.9003-7.844 7.9003h-60.874c-4.33 0-7.845-3.5399-7.845-7.9003v-61.3071c0-4.361 3.515-7.9003 7.845-7.9003z'
fill='#33C482'
fillRule='evenodd'
/>
<path
d='m207.878 129.57h-64.324c-7.798 0-14.12 6.367-14.12 14.221v63.993c0 7.854 6.322 14.221 14.12 14.221h64.324c7.799 0 14.121-6.367 14.121-14.221v-63.993c0-7.854-6.322-14.221-14.121-14.221z'
fill='#33C482'
/>
<path
d='m207.878 129.266h-64.324c-7.798 0-14.12 6.366-14.12 14.221v63.992c0 7.854 6.322 14.22 14.12 14.22h64.324c7.799 0 14.121-6.366 14.121-14.22v-63.992c0-7.855-6.322-14.221-14.121-14.221z'
fill={`url(#${gradientId})`}
fillOpacity='0.2'
/>
</svg>
)
}
/**
* Full Sim logo with icon and "Sim" text.
* Uses the same SVG source as the landing page navbar for exact visual alignment.
* The icon stays green (#33C482), text adapts to light/dark mode.
*/
export function SimLogoFull({ className }: SimLogoProps) {
const gradientId = `sim-logo-full-gradient-${useId()}`
return (
<svg
viewBox='0 0 71 22'
@@ -22,7 +98,7 @@ export function SimLogoFull({ className }: SimLogoProps) {
>
<defs>
<linearGradient
id='sim-logo-full-gradient'
id={gradientId}
gradientUnits='userSpaceOnUse'
x1='171.406'
y1='171.18'
@@ -47,7 +123,7 @@ export function SimLogoFull({ className }: SimLogoProps) {
/>
<path
d='m275.293 171.18h-85.187c-10.327 0-18.7 8.432-18.7 18.834v84.749c0 10.402 8.373 18.833 18.7 18.833h85.187c10.328 0 18.701-8.431 18.701-18.833v-84.749c0-10.402-8.373-18.834-18.701-18.834z'
fill='url(#sim-logo-full-gradient)'
fill={`url(#${gradientId})`}
fillOpacity='0.2'
/>
</g>
+8
View File
@@ -1 +1,9 @@
export const DOCS_BASE_URL = process.env.NEXT_PUBLIC_DOCS_URL ?? 'https://docs.sim.ai'
/**
* The public marketing site's fixed canonical origin — not `NEXT_PUBLIC_APP_URL`.
* That env var reflects wherever *this* deployment (self-hosted or otherwise)
* happens to run, but the footer's marketing links (`/blog`, `/enterprise`,
* `/models`, `/terms`, `/privacy`, …) only ever exist on sim.ai itself, so
* they must stay hardcoded to it regardless of where docs is hosted.
*/
export const SIM_SITE_URL = 'https://sim.ai'