Skip to content

Commit 93a05cc

Browse files
committed
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
1 parent 1c60415 commit 93a05cc

24 files changed

Lines changed: 425 additions & 73 deletions

File tree

apps/docs/app/[lang]/layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99
SidebarItem,
1010
SidebarSeparator,
1111
} from '@/components/docs-layout/sidebar-components'
12+
import { Footer } from '@/components/footer/footer'
1213
import { Navbar } from '@/components/navbar/navbar'
13-
import { SimLogoFull } from '@/components/ui/sim-logo'
14+
import { SimLogoIcon } from '@/components/ui/sim-logo'
1415
import { i18n } from '@/lib/i18n'
1516
import { serializeJsonLd } from '@/lib/json-ld'
1617
import { source } from '@/lib/source'
@@ -101,7 +102,7 @@ export default async function Layout({ children, params }: LayoutProps) {
101102
<DocsLayout
102103
tree={source.pageTree[lang]}
103104
nav={{
104-
title: <SimLogoFull className='h-[22px] w-auto' />,
105+
title: <SimLogoIcon className='size-[22px]' />,
105106
}}
106107
sidebar={{
107108
tabs: false,
@@ -121,6 +122,7 @@ export default async function Layout({ children, params }: LayoutProps) {
121122
>
122123
{children}
123124
</DocsLayout>
125+
<Footer />
124126
<AskAI locale={lang} />
125127
</RootProvider>
126128
</body>

apps/docs/app/api/og/route.tsx

Lines changed: 82 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,34 @@ const TITLE_FONT_SIZE = {
99
medium: 56,
1010
small: 48,
1111
} as const
12+
/** Average glyph width as a fraction of font size, for this weight/family — used to pack words into lines. */
13+
const AVG_CHAR_WIDTH_EM = 0.56
14+
const TITLE_BOX_WIDTH = 1000
1215
const FONT_CACHE_REVALIDATE_SECONDS = 60 * 60 * 24 * 30
16+
const INK_COLOR = '#3f3f3f'
1317
const OG_CONTAINER_STYLE = {
1418
height: '100%',
1519
width: '100%',
1620
display: 'flex',
1721
flexDirection: 'column',
1822
justifyContent: 'space-between',
1923
padding: '56px 64px',
20-
background: '#121212',
24+
background: '#c3c3c3',
2125
fontFamily: 'Geist',
2226
} satisfies CSSProperties
23-
const OG_TITLE_STYLE = {
24-
fontWeight: 500,
25-
color: '#fafafa',
26-
lineHeight: 1.2,
27-
letterSpacing: '-0.02em',
28-
} satisfies CSSProperties
29-
const OG_FOOTER_STYLE = {
27+
const OG_HEADER_STYLE = {
3028
display: 'flex',
3129
justifyContent: 'space-between',
32-
alignItems: 'center',
30+
alignItems: 'flex-start',
3331
width: '100%',
3432
} satisfies CSSProperties
35-
const OG_DOMAIN_STYLE = {
36-
fontSize: 24,
37-
fontWeight: 400,
38-
color: '#71717a',
33+
const OG_TITLE_STYLE = {
34+
display: 'flex',
35+
flexDirection: 'column',
36+
fontWeight: 600,
37+
color: INK_COLOR,
38+
lineHeight: 1.15,
39+
width: `${TITLE_BOX_WIDTH}px`,
3940
} satisfies CSSProperties
4041

4142
function getTitleFontSize(title: string): number {
@@ -51,6 +52,35 @@ function getTitleStyle(title: string): CSSProperties {
5152
}
5253
}
5354

55+
/**
56+
* Greedily packs words into lines that fit `TITLE_BOX_WIDTH` at `fontSize`,
57+
* then joins each line with U+00A0 instead of a plain space. Satori
58+
* (`next/og`'s renderer) has a text-measurement bug where the first plain
59+
* space (U+0020) in a text node renders at roughly double width — a
60+
* non-breaking space measures correctly and reads identically at this size,
61+
* so it sidesteps the bug instead of fighting Satori's own line-wrapping
62+
* (which is also disabled here — lines are pre-split, not auto-wrapped).
63+
*/
64+
function wrapTitleLines(title: string, fontSize: number): string[] {
65+
const maxCharsPerLine = Math.floor(TITLE_BOX_WIDTH / (fontSize * AVG_CHAR_WIDTH_EM))
66+
const words = title.split(' ')
67+
const lines: string[] = []
68+
let current = ''
69+
70+
for (const word of words) {
71+
const candidate = current ? `${current} ${word}` : word
72+
if (candidate.length > maxCharsPerLine && current) {
73+
lines.push(current)
74+
current = word
75+
} else {
76+
current = candidate
77+
}
78+
}
79+
if (current) lines.push(current)
80+
81+
return lines.map((line) => line.replace(/ /g, ' '))
82+
}
83+
5484
/**
5585
* Loads a Google Font dynamically by fetching the CSS and extracting the font URL.
5686
*/
@@ -79,53 +109,66 @@ async function loadGoogleFont(font: string, weights: string, text: string): Prom
79109
throw new Error('Failed to load font data')
80110
}
81111

82-
/**
83-
* Sim logo with icon and "Sim" text for OG image.
84-
*/
85-
function SimLogoFull() {
112+
/** "sim" wordmark, no icon — same brandbook workmark geometry as the docs navbar/landing OG cards. */
113+
function SimWordmark() {
86114
return (
87-
<svg height='34' viewBox='720 440 1020 320' fill='none'>
88-
{/* Green icon - top left shape with cutout */}
115+
<svg width='100' height='48' viewBox='0 0 800 386' fill='none'>
89116
<path
90-
fillRule='evenodd'
91-
clipRule='evenodd'
92-
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'
93-
fill='#33C482'
117+
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'
118+
fill={INK_COLOR}
94119
/>
95-
{/* Green icon - bottom right square */}
96120
<path
97-
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'
98-
fill='#33C482'
121+
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'
122+
fill={INK_COLOR}
99123
/>
100-
{/* "Sim" text - white for dark background */}
101124
<path
102-
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'
103-
fill='#fafafa'
125+
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'
126+
fill={INK_COLOR}
127+
/>
128+
</svg>
129+
)
130+
}
131+
132+
/** Diagonal "open" arrow, top-right — matches the library/blog cover template. */
133+
function CornerArrow() {
134+
return (
135+
<svg width='52' height='52' viewBox='0 0 24 24' fill='none'>
136+
<path
137+
d='M7 17 17 7M17 7H9M17 7V15'
138+
stroke={INK_COLOR}
139+
strokeWidth={2.25}
140+
strokeLinecap='round'
141+
strokeLinejoin='round'
104142
/>
105143
</svg>
106144
)
107145
}
108146

109147
/**
110-
* Generates dynamic Open Graph images for documentation pages.
111-
* Style matches Cursor docs: dark background, title at top, logo bottom-left, domain bottom-right.
148+
* Generates dynamic Open Graph images for documentation pages. Matches the
149+
* site's library/blog cover template: light gray background, "sim" wordmark
150+
* top-left, an open/diagonal arrow top-right, and the page title large and
151+
* bold at the bottom-left.
112152
*/
113153
export async function GET(request: NextRequest) {
114154
const { searchParams } = new URL(request.url)
115155
const title = searchParams.get('title') || 'Documentation'
116156

117-
const allText = `${title}docs.sim.ai`
118-
const fontData = await loadGoogleFont('Geist', '400;500;600', allText)
157+
const fontData = await loadGoogleFont('Geist', '400;500;600', title)
158+
const fontSize = getTitleFontSize(title)
159+
const titleLines = wrapTitleLines(title, fontSize)
119160

120161
return new ImageResponse(
121162
<div style={OG_CONTAINER_STYLE}>
122-
{/* Title at top */}
123-
<span style={getTitleStyle(title)}>{title}</span>
163+
<div style={OG_HEADER_STYLE}>
164+
<SimWordmark />
165+
<CornerArrow />
166+
</div>
124167

125-
{/* Footer: icon left, domain right */}
126-
<div style={OG_FOOTER_STYLE}>
127-
<SimLogoFull />
128-
<span style={OG_DOMAIN_STYLE}>docs.sim.ai</span>
168+
<div style={getTitleStyle(title)}>
169+
{titleLines.map((line, index) => (
170+
<span key={index}>{line}</span>
171+
))}
129172
</div>
130173
</div>,
131174
{
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Image from 'next/image'
2+
3+
/**
4+
* Full-bleed illustration pinned behind the footer via CSS `position: sticky`.
5+
* Scrolling past the footer's natural end uncovers it for a 320px (200px on
6+
* mobile) reveal window — a "peel" effect with zero JS (no scroll listeners,
7+
* no animation library). Ported from the main app's landing footer
8+
* (`apps/sim/app/(landing)/components/footer/components/footer-peel`) so the
9+
* two sites match exactly. The `Footer` component that renders this
10+
* (`./footer.tsx`) reserves that same extra scroll room as bottom padding,
11+
* and pulls its own opaque content up over this element with a matching
12+
* negative margin, so the illustration stays fully hidden until the footer
13+
* content has scrolled away. The height values here (`320px`/`200px`) must
14+
* stay in sync with the ones hardcoded in `footer.tsx` — Tailwind can't share
15+
* an arbitrary-value size across a JS constant, so this is a literal-class
16+
* pairing, not a shared source of truth.
17+
*/
18+
export function FooterPeel() {
19+
return (
20+
<div
21+
className='-z-10 sticky bottom-0 h-[320px] w-full overflow-hidden max-sm:h-[200px]'
22+
aria-hidden='true'
23+
>
24+
<Image
25+
src='/images/footer-peel.jpg'
26+
alt=''
27+
fill
28+
sizes='100vw'
29+
className='object-cover object-[center_38%]'
30+
/>
31+
</div>
32+
)
33+
}

0 commit comments

Comments
 (0)