retail-design-system 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/.github/workflows/release.yml +46 -0
  2. package/.oxfmtrc.json +17 -0
  3. package/.oxlintrc.json +132 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/.vscode/settings.json +13 -0
  6. package/README.md +56 -0
  7. package/apps/storybook/.storybook/main.ts +8 -0
  8. package/apps/storybook/.storybook/preview.css +9 -0
  9. package/apps/storybook/.storybook/preview.ts +6 -0
  10. package/apps/storybook/package.json +24 -0
  11. package/apps/storybook/stories/button.stories.ts +118 -0
  12. package/apps/storybook/stories/input.stories.ts +127 -0
  13. package/apps/storybook/stories/label.stories.ts +98 -0
  14. package/apps/storybook/tsconfig.app.json +24 -0
  15. package/apps/storybook/tsconfig.json +4 -0
  16. package/apps/storybook/tsconfig.node.json +22 -0
  17. package/apps/storybook/vite.config.ts +15 -0
  18. package/apps/web/app/(sidebar)/components/[...slugs]/get-child-block.ts +17 -0
  19. package/apps/web/app/(sidebar)/components/[...slugs]/get-component-page-match.ts +56 -0
  20. package/apps/web/app/(sidebar)/components/[...slugs]/get-direct-child-block.ts +22 -0
  21. package/apps/web/app/(sidebar)/components/[...slugs]/layout.tsx +25 -0
  22. package/apps/web/app/(sidebar)/components/[...slugs]/page.tsx +32 -0
  23. package/apps/web/app/(sidebar)/components/[...slugs]/pascal-to-kebab-case.ts +9 -0
  24. package/apps/web/app/(sidebar)/components/button2/page.tsx +154 -0
  25. package/apps/web/app/(sidebar)/components/input/page.tsx +98 -0
  26. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-card-badge.tsx +9 -0
  27. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-coin-active-badge.tsx +14 -0
  28. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-coin-inactive-badge.tsx +12 -0
  29. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-create-coin.tsx +44 -0
  30. package/apps/web/app/(sidebar)/experiments/2025-10-22/mayhem-mode-dialog-icon.tsx +47 -0
  31. package/apps/web/app/(sidebar)/experiments/2025-10-22/page.tsx +167 -0
  32. package/apps/web/app/(sidebar)/experiments/2025-11-04/filters.tsx +90 -0
  33. package/apps/web/app/(sidebar)/experiments/2025-11-04/page.tsx +18 -0
  34. package/apps/web/app/(sidebar)/layout.tsx +17 -0
  35. package/apps/web/app/(sidebar)/primitives/colors/page.tsx +49 -0
  36. package/apps/web/app/favicon.ico +0 -0
  37. package/apps/web/app/layout.tsx +39 -0
  38. package/apps/web/app/page.tsx +14 -0
  39. package/apps/web/app/providers.tsx +15 -0
  40. package/apps/web/components/dialog.tsx +21 -0
  41. package/apps/web/components/logo.tsx +11 -0
  42. package/apps/web/components/logomark.tsx +21 -0
  43. package/apps/web/components/logotype.tsx +25 -0
  44. package/apps/web/components/notion/notion-block-content.tsx +401 -0
  45. package/apps/web/components/notion/notion-docs-blocks.tsx +18 -0
  46. package/apps/web/components/notion/notion-docs-code-page.tsx +20 -0
  47. package/apps/web/components/notion/notion-docs-layout.tsx +52 -0
  48. package/apps/web/components/notion/notion-revalidate-button-client.tsx +14 -0
  49. package/apps/web/components/notion/notion-revalidate-button.tsx +20 -0
  50. package/apps/web/components/notion/notion-rich-text-segments.tsx +55 -0
  51. package/apps/web/components/notion/notion-tabs.tsx +38 -0
  52. package/apps/web/components/notion/notion.ts +223 -0
  53. package/apps/web/components/sidebar-client.tsx +60 -0
  54. package/apps/web/components/sidebar-server.tsx +185 -0
  55. package/apps/web/components/tooltip.tsx +53 -0
  56. package/apps/web/components/topbar.tsx +14 -0
  57. package/apps/web/next.config.ts +10 -0
  58. package/apps/web/package.json +42 -0
  59. package/apps/web/postcss.config.mjs +5 -0
  60. package/apps/web/public/2025-10-22-dialog-banner.png +0 -0
  61. package/apps/web/public/pump-logomark.svg +7 -0
  62. package/apps/web/styles/custom.css +31 -0
  63. package/apps/web/styles/font.css +8 -0
  64. package/apps/web/styles/global.css +5 -0
  65. package/apps/web/styles/tailwind-reset.css +102 -0
  66. package/apps/web/styles/tailwind.css +140 -0
  67. package/apps/web/tsconfig.json +34 -0
  68. package/bun.lock +1249 -0
  69. package/bunfig.toml +2 -0
  70. package/package.json +41 -0
  71. package/packages/ui/global.d.ts +4 -0
  72. package/packages/ui/package.json +49 -0
  73. package/packages/ui/src/components/button/button-spinner.module.css +95 -0
  74. package/packages/ui/src/components/button/button-spinner.tsx +18 -0
  75. package/packages/ui/src/components/button/button.module.css +144 -0
  76. package/packages/ui/src/components/button/button.tsx +102 -0
  77. package/packages/ui/src/components/button-link/button-link.tsx +46 -0
  78. package/packages/ui/src/components/column/column.module.css +4 -0
  79. package/packages/ui/src/components/column/column.tsx +65 -0
  80. package/packages/ui/src/components/row/row.module.css +4 -0
  81. package/packages/ui/src/components/row/row.tsx +65 -0
  82. package/packages/ui/src/components/spacer/spacer.module.css +3 -0
  83. package/packages/ui/src/components/spacer/spacer.tsx +30 -0
  84. package/packages/ui/src/components/switch/switch.module.css +62 -0
  85. package/packages/ui/src/components/switch/switch.tsx +58 -0
  86. package/packages/ui/src/components/tabs/tabs-panel.module.css +4 -0
  87. package/packages/ui/src/components/tabs/tabs-panel.tsx +21 -0
  88. package/packages/ui/src/components/tabs/tabs.module.css +5 -0
  89. package/packages/ui/src/components/tabs/tabs.tsx +21 -0
  90. package/packages/ui/src/components/tabs-underline/tabs-underline-indicator.module.css +10 -0
  91. package/packages/ui/src/components/tabs-underline/tabs-underline-indicator.tsx +33 -0
  92. package/packages/ui/src/components/tabs-underline/tabs-underline-list.module.css +8 -0
  93. package/packages/ui/src/components/tabs-underline/tabs-underline-list.tsx +27 -0
  94. package/packages/ui/src/components/tabs-underline/tabs-underline-tab.module.css +24 -0
  95. package/packages/ui/src/components/tabs-underline/tabs-underline-tab.tsx +30 -0
  96. package/packages/ui/src/foundations/colors/colors.ts +475 -0
  97. package/packages/ui/src/foundations/colors/generate-css.ts +34 -0
  98. package/packages/ui/src/foundations/colors/retail-design-system.css +116 -0
  99. package/packages/ui/src/foundations/colors/tailwind-v3.ts +18 -0
  100. package/packages/ui/src/foundations/colors/tailwind-v4.css +116 -0
  101. package/packages/ui/src/index.ts +34 -0
  102. package/packages/ui/src/input.module.css +57 -0
  103. package/packages/ui/src/input.tsx +49 -0
  104. package/packages/ui/src/label.module.css +8 -0
  105. package/packages/ui/src/label.tsx +23 -0
  106. package/packages/ui/tsconfig.json +14 -0
  107. package/packages/ui/tsup.config.ts +31 -0
  108. package/scripts/clean.sh +69 -0
  109. package/scripts/sort-package-json.sh +30 -0
  110. package/turbo.json +15 -0
@@ -0,0 +1,223 @@
1
+ import { Client } from "@notionhq/client"
2
+
3
+ const NOTION_API_KEY = "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"
4
+
5
+ const NOTION_API_VERSION = "2025-09-03"
6
+
7
+ export const notion = new Client({
8
+ auth: NOTION_API_KEY,
9
+ notionVersion: NOTION_API_VERSION,
10
+ })
11
+
12
+ export type NotionBlock =
13
+ | {
14
+ alt: string
15
+ type: "image"
16
+ url: string
17
+ }
18
+ | {
19
+ code: string
20
+ language: string
21
+ type: "code"
22
+ }
23
+ | {
24
+ segments: NotionRichTextSegment[]
25
+ type: "h2" | "h3" | "paragraph"
26
+ }
27
+
28
+ export interface NotionPage {
29
+ blocks: NotionBlock[]
30
+ createdTime: string
31
+ lastEditedTime: string
32
+ tabs: NotionTab[]
33
+ title: string
34
+ }
35
+
36
+ export interface NotionRichTextSegment {
37
+ bold: boolean
38
+ code: boolean
39
+ href: null | string
40
+ italic: boolean
41
+ strikethrough: boolean
42
+ text: string
43
+ underline: boolean
44
+ }
45
+
46
+ export interface NotionTab {
47
+ href: string
48
+ label: string
49
+ }
50
+
51
+ interface NotionRichTextInput {
52
+ annotations: {
53
+ bold: boolean
54
+ code: boolean
55
+ italic: boolean
56
+ strikethrough: boolean
57
+ underline: boolean
58
+ }
59
+ href: null | string
60
+ plain_text: string
61
+ }
62
+
63
+ export async function getNotionPage(pageId: string): Promise<NotionPage> {
64
+ if (!NOTION_API_KEY) {
65
+ throw new Error("NOTION_API_KEY is required")
66
+ }
67
+
68
+ // Keep a stable fallback when page metadata is missing or unexpected.
69
+ const DEFAULT_EMPTY = {
70
+ blocks: [],
71
+ createdTime: "",
72
+ lastEditedTime: "",
73
+ tabs: [],
74
+ title: "",
75
+ }
76
+
77
+ const [metaContent, metaPage] = await Promise.all([
78
+ notion.blocks.children.list({ block_id: pageId }),
79
+ notion.pages.retrieve({ page_id: pageId }),
80
+ ])
81
+
82
+ if (!("properties" in metaPage)) {
83
+ return DEFAULT_EMPTY
84
+ }
85
+
86
+ const titleProperty = metaPage.properties.title
87
+
88
+ if (!titleProperty || titleProperty.type !== "title") {
89
+ return DEFAULT_EMPTY
90
+ }
91
+
92
+ let title = ""
93
+ const titleArray = titleProperty.title
94
+ if (titleArray.length > 0) {
95
+ title = titleArray[0].plain_text
96
+ }
97
+
98
+ const blocks: NotionBlock[] = []
99
+ const tabs: NotionTab[] = []
100
+
101
+ // Shared paragraph/heading handler: skip empty text and normalize segment shape.
102
+ function pushRichTextBlock(
103
+ richText: NotionRichTextInput[],
104
+ type: Extract<NotionBlock, { segments: NotionRichTextSegment[] }>["type"],
105
+ ): void {
106
+ if (richText.length === 0) {
107
+ return
108
+ }
109
+
110
+ blocks.push({
111
+ segments: toNotionRichTextSegments(richText),
112
+ type,
113
+ })
114
+ }
115
+
116
+ function pushTab(text: string): void {
117
+ const tab = toNotionTab(text)
118
+ if (tab) {
119
+ tabs.push(tab)
120
+ }
121
+ }
122
+
123
+ // Convert Notion API blocks into the small renderable union used by the UI.
124
+ for (const result of metaContent.results) {
125
+ if (!("type" in result)) {
126
+ return DEFAULT_EMPTY
127
+ }
128
+
129
+ if (result.type === "paragraph") {
130
+ pushRichTextBlock(result.paragraph.rich_text, "paragraph")
131
+ }
132
+
133
+ if (result.type === "heading_1") {
134
+ // Notion heading_1 is rendered as h2 for this page.
135
+ pushRichTextBlock(result.heading_1.rich_text, "h2")
136
+ }
137
+
138
+ if (result.type === "heading_2") {
139
+ pushRichTextBlock(result.heading_2.rich_text, "h3")
140
+ }
141
+
142
+ if (result.type === "image") {
143
+ const url =
144
+ result.image.type === "external" ? result.image.external.url : result.image.file.url
145
+
146
+ const alt = result.image.caption.map((textBlock) => textBlock.plain_text).join("")
147
+
148
+ blocks.push({ alt, type: "image", url })
149
+ }
150
+
151
+ if (result.type === "code") {
152
+ const code = result.code.rich_text.map((textBlock) => textBlock.plain_text).join("")
153
+ if (code.length > 0) {
154
+ blocks.push({ code, language: result.code.language, type: "code" })
155
+ }
156
+ }
157
+
158
+ if (result.type === "bulleted_list_item") {
159
+ const text = result.bulleted_list_item.rich_text
160
+ .map((textBlock) => textBlock.plain_text)
161
+ .join("")
162
+ pushTab(text)
163
+ }
164
+
165
+ if (result.type === "numbered_list_item") {
166
+ const text = result.numbered_list_item.rich_text
167
+ .map((textBlock) => textBlock.plain_text)
168
+ .join("")
169
+ pushTab(text)
170
+ }
171
+
172
+ if (result.type === "child_page") {
173
+ pushTab(result.child_page.title)
174
+ }
175
+ }
176
+
177
+ return {
178
+ blocks,
179
+ createdTime: metaPage.created_time,
180
+ lastEditedTime: metaPage.last_edited_time,
181
+ tabs,
182
+ title,
183
+ }
184
+ }
185
+
186
+ // Map Notion rich text objects to the formatting fields consumed by components.
187
+ function toNotionRichTextSegments(richText: NotionRichTextInput[]): NotionRichTextSegment[] {
188
+ return richText.map((textBlock) => ({
189
+ bold: textBlock.annotations.bold,
190
+ code: textBlock.annotations.code,
191
+ href: textBlock.href,
192
+ italic: textBlock.annotations.italic,
193
+ strikethrough: textBlock.annotations.strikethrough,
194
+ text: textBlock.plain_text,
195
+ underline: textBlock.annotations.underline,
196
+ }))
197
+ }
198
+
199
+ function toNotionTab(text: string): NotionTab | undefined {
200
+ const separatorIndex = text.indexOf("::")
201
+ if (separatorIndex === -1) {
202
+ return undefined
203
+ }
204
+
205
+ const label = text.slice(0, separatorIndex).trim()
206
+ const rawHref = text.slice(separatorIndex + 2).trim()
207
+ if (!label || !rawHref) {
208
+ return undefined
209
+ }
210
+
211
+ return {
212
+ href: toNotionTabHref(rawHref),
213
+ label,
214
+ }
215
+ }
216
+
217
+ function toNotionTabHref(rawHref: string): string {
218
+ if (rawHref.startsWith("/") || rawHref.startsWith("http://") || rawHref.startsWith("https://")) {
219
+ return rawHref
220
+ }
221
+
222
+ return `/${rawHref}`
223
+ }
@@ -0,0 +1,60 @@
1
+ "use client"
2
+
3
+ import Link, { type LinkProps } from "next/link"
4
+ import { usePathname } from "next/navigation"
5
+ import type { NotionRichTextSegment } from "@/components/notion/notion"
6
+ import { NotionRichTextSegments } from "@/components/notion/notion-rich-text-segments"
7
+
8
+ interface SidebarClientProps {
9
+ sections: SidebarSection[]
10
+ }
11
+
12
+ interface SidebarSection {
13
+ labelSegments: NotionRichTextSegment[]
14
+ links: {
15
+ href: LinkProps<string>["href"]
16
+ label: string
17
+ }[]
18
+ }
19
+
20
+ export function SidebarClient(props: SidebarClientProps) {
21
+ const { sections } = props
22
+
23
+ const pathname = usePathname()
24
+
25
+ return (
26
+ <aside className="bg-bg-primary sticky top-64 left-0 flex h-[calc(100dvh-64px)] shrink-0 flex-col">
27
+ <div className="flex flex-col gap-y-16 overflow-y-auto px-24">
28
+ {sections.map((section, sectionIndex) => (
29
+ <div className="flex flex-col gap-y-8" key={sectionIndex}>
30
+ {section.labelSegments.length > 0 && (
31
+ <p className="text-text-primary text-14 font-500 leading-none">
32
+ <NotionRichTextSegments
33
+ blockIndex={sectionIndex}
34
+ segments={section.labelSegments}
35
+ />
36
+ </p>
37
+ )}
38
+
39
+ <div className="flex flex-col gap-y-4">
40
+ {section.links.map((link, linkIndex) => {
41
+ const isActive = pathname === link.href
42
+
43
+ return (
44
+ <Link
45
+ className="text-14 font-500 data-[is-active=true]:text-text-accent hover:text-text-primary ml-8 w-fit transition-colors"
46
+ data-is-active={isActive}
47
+ href={link.href}
48
+ key={`${sectionIndex}-${linkIndex}`}
49
+ >
50
+ {link.label}
51
+ </Link>
52
+ )
53
+ })}
54
+ </div>
55
+ </div>
56
+ ))}
57
+ </div>
58
+ </aside>
59
+ )
60
+ }
@@ -0,0 +1,185 @@
1
+ import { cacheTag } from "next/cache"
2
+ import type { LinkProps } from "next/link"
3
+ import { notion, type NotionRichTextSegment } from "@/components/notion/notion"
4
+ import { SidebarClient } from "@/components/sidebar-client"
5
+
6
+ const PUBLIC_NOTION_PAGE_ID = "325b76f65e6e801ca464dc9b9e944fd0"
7
+
8
+ type SidebarBlock = Awaited<ReturnType<typeof notion.blocks.children.list>>["results"][number]
9
+
10
+ interface SidebarLink {
11
+ href: LinkProps<string>["href"]
12
+ label: string
13
+ }
14
+
15
+ interface SidebarRichTextItem {
16
+ annotations: {
17
+ bold: boolean
18
+ code: boolean
19
+ italic: boolean
20
+ strikethrough: boolean
21
+ underline: boolean
22
+ }
23
+ href: null | string
24
+ plain_text: string
25
+ }
26
+
27
+ interface SidebarSection {
28
+ labelSegments: NotionRichTextSegment[]
29
+ links: SidebarLink[]
30
+ }
31
+
32
+ export async function SidebarServer() {
33
+ const sections = await getSidebarSectionsFromNotion()
34
+ return <SidebarClient sections={sections} />
35
+ }
36
+
37
+ async function getSidebarBlocksFromNotionPage(cursor?: string): Promise<SidebarBlock[]> {
38
+ const blocks = await notion.blocks.children.list({
39
+ block_id: PUBLIC_NOTION_PAGE_ID,
40
+ start_cursor: cursor,
41
+ })
42
+
43
+ if (!blocks.has_more || !blocks.next_cursor) {
44
+ return blocks.results
45
+ }
46
+
47
+ const nextBlocks = await getSidebarBlocksFromNotionPage(blocks.next_cursor)
48
+ return [...blocks.results, ...nextBlocks]
49
+ }
50
+
51
+ async function getSidebarSectionsFromNotion(): Promise<SidebarSection[]> {
52
+ "use cache"
53
+
54
+ cacheTag("notion")
55
+ cacheTag("notion-sidebar")
56
+
57
+ try {
58
+ const blocks = await getSidebarBlocksFromNotionPage()
59
+ return toSidebarSections(blocks)
60
+ } catch {
61
+ return []
62
+ }
63
+ }
64
+
65
+ function toSidebarHref(rawHref: string): LinkProps<string>["href"] {
66
+ if (rawHref.startsWith("/") || rawHref.startsWith("http://") || rawHref.startsWith("https://")) {
67
+ return rawHref as LinkProps<string>["href"]
68
+ }
69
+
70
+ return `/${rawHref}` as LinkProps<string>["href"]
71
+ }
72
+
73
+ function toSidebarLabelSegments(result: SidebarBlock): NotionRichTextSegment[] | undefined {
74
+ if (!("type" in result)) {
75
+ return
76
+ }
77
+
78
+ let richText: SidebarRichTextItem[] | undefined = undefined
79
+
80
+ if (result.type === "paragraph") {
81
+ richText = result.paragraph.rich_text
82
+ }
83
+
84
+ if (result.type === "heading_1") {
85
+ richText = result.heading_1.rich_text
86
+ }
87
+
88
+ if (result.type === "heading_2") {
89
+ richText = result.heading_2.rich_text
90
+ }
91
+
92
+ if (result.type === "heading_3") {
93
+ richText = result.heading_3.rich_text
94
+ }
95
+
96
+ if (!richText || richText.every((textBlock) => textBlock.plain_text.trim().length === 0)) {
97
+ return
98
+ }
99
+
100
+ return toSidebarRichTextSegments(richText)
101
+ }
102
+
103
+ function toSidebarLink(text: string): SidebarLink | undefined {
104
+ const separatorIndex = text.indexOf("::")
105
+ if (separatorIndex === -1) {
106
+ return undefined
107
+ }
108
+
109
+ const label = text.slice(0, separatorIndex).trim()
110
+ const hrefValue = text.slice(separatorIndex + 2).trim()
111
+
112
+ if (!label || !hrefValue) {
113
+ return undefined
114
+ }
115
+
116
+ return {
117
+ href: toSidebarHref(hrefValue),
118
+ label,
119
+ }
120
+ }
121
+
122
+ function toSidebarLinkFromBlock(result: SidebarBlock): SidebarLink | undefined {
123
+ if (!("type" in result)) {
124
+ return
125
+ }
126
+
127
+ let richText: SidebarRichTextItem[] | undefined = undefined
128
+
129
+ if (result.type === "bulleted_list_item") {
130
+ richText = result.bulleted_list_item.rich_text
131
+ }
132
+
133
+ if (result.type === "numbered_list_item") {
134
+ richText = result.numbered_list_item.rich_text
135
+ }
136
+
137
+ if (!richText) {
138
+ return
139
+ }
140
+
141
+ const text = richText.map((textBlock) => textBlock.plain_text).join("")
142
+ return toSidebarLink(text)
143
+ }
144
+
145
+ function toSidebarRichTextSegments(richText: SidebarRichTextItem[]): NotionRichTextSegment[] {
146
+ return richText.map((textBlock) => ({
147
+ bold: textBlock.annotations.bold,
148
+ code: textBlock.annotations.code,
149
+ href: textBlock.href,
150
+ italic: textBlock.annotations.italic,
151
+ strikethrough: textBlock.annotations.strikethrough,
152
+ text: textBlock.plain_text,
153
+ underline: textBlock.annotations.underline,
154
+ }))
155
+ }
156
+
157
+ function toSidebarSections(blocks: SidebarBlock[]): SidebarSection[] {
158
+ const sections: SidebarSection[] = []
159
+ let currentSection: SidebarSection | undefined = undefined
160
+
161
+ for (const block of blocks) {
162
+ const labelSegments = toSidebarLabelSegments(block)
163
+ const link = toSidebarLinkFromBlock(block)
164
+
165
+ if (labelSegments) {
166
+ currentSection = {
167
+ labelSegments,
168
+ links: [],
169
+ }
170
+ sections.push(currentSection)
171
+ } else if (link) {
172
+ if (!currentSection) {
173
+ currentSection = {
174
+ labelSegments: [],
175
+ links: [],
176
+ }
177
+ sections.push(currentSection)
178
+ }
179
+
180
+ currentSection.links.push(link)
181
+ }
182
+ }
183
+
184
+ return sections.filter((section) => section.labelSegments.length > 0 || section.links.length > 0)
185
+ }
@@ -0,0 +1,53 @@
1
+ // oxlint-disable react/no-multi-comp
2
+
3
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react"
4
+ import type { ComponentProps } from "react"
5
+
6
+ function Tooltip(props: ComponentProps<typeof TooltipPrimitive.Root>) {
7
+ const { ...rest } = props
8
+
9
+ return <TooltipPrimitive.Root data-slot="tooltip" {...rest} />
10
+ }
11
+
12
+ function TooltipContent(props: ComponentProps<typeof TooltipPrimitive.Popup>) {
13
+ const { children, ...rest } = props
14
+
15
+ return (
16
+ <TooltipPrimitive.Popup
17
+ className="rounded-8 bg-zinc-50 px-8 py-4"
18
+ data-slot="tooltip-content"
19
+ {...rest}
20
+ >
21
+ {children}
22
+ </TooltipPrimitive.Popup>
23
+ )
24
+ }
25
+
26
+ function TooltipPositioner(props: ComponentProps<typeof TooltipPrimitive.Positioner>) {
27
+ const { ...rest } = props
28
+
29
+ return (
30
+ <TooltipPrimitive.Portal>
31
+ <TooltipPrimitive.Positioner
32
+ className="z-50"
33
+ data-slot="tooltip-positioner"
34
+ sideOffset={8}
35
+ {...rest}
36
+ />
37
+ </TooltipPrimitive.Portal>
38
+ )
39
+ }
40
+
41
+ function TooltipProvider(props: ComponentProps<typeof TooltipPrimitive.Provider>) {
42
+ const { ...rest } = props
43
+
44
+ return <TooltipPrimitive.Provider data-slot="tooltip-provider" {...rest} />
45
+ }
46
+
47
+ function TooltipTrigger(props: ComponentProps<typeof TooltipPrimitive.Trigger>) {
48
+ const { ...rest } = props
49
+
50
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...rest} />
51
+ }
52
+
53
+ export { Tooltip, TooltipContent, TooltipPositioner, TooltipProvider, TooltipTrigger }
@@ -0,0 +1,14 @@
1
+ import Link from "next/link"
2
+ import { Logo } from "@/components/logo"
3
+
4
+ export function Topbar() {
5
+ return (
6
+ <header className="bg-gray-1/90 sticky top-0 left-0 z-10 flex h-64 w-full px-24 backdrop-blur-[48px]">
7
+ <div className="flex size-full items-center justify-between">
8
+ <Link className="-ml-8 flex items-center p-8 transition-opacity hover:opacity-75" href="/">
9
+ <Logo />
10
+ </Link>
11
+ </div>
12
+ </header>
13
+ )
14
+ }
@@ -0,0 +1,10 @@
1
+ import type { NextConfig } from "next"
2
+
3
+ const nextConfig: NextConfig = {
4
+ cacheComponents: true,
5
+ reactCompiler: true,
6
+ reactStrictMode: false,
7
+ typedRoutes: true,
8
+ }
9
+
10
+ export default nextConfig
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "web",
3
+ "scripts": {
4
+ "build": "next build",
5
+ "dev": "next dev --port 3001",
6
+ "start": "next start"
7
+ },
8
+ "dependencies": {
9
+ "@base-ui/react": "catalog:",
10
+ "@mdx-js/mdx": "3.1.1",
11
+ "@nattui/tailwind-colors": "0.0.8",
12
+ "@nattui/tailwind-exact": "0.0.5",
13
+ "@notionhq/client": "5.13.0",
14
+ "@pump-fun/icons-filled": "catalog:",
15
+ "@pump-fun/icons-line": "catalog:",
16
+ "@pump-fun/retail-design-system": "workspace:*",
17
+ "@use-gesture/react": "10.3.1",
18
+ "motion": "12.36.0",
19
+ "next": "16.1.6",
20
+ "nuqs": "2.8.9",
21
+ "react": "catalog:",
22
+ "react-dom": "catalog:",
23
+ "sugar-high": "0.9.5"
24
+ },
25
+ "devDependencies": {
26
+ "@tailwindcss/postcss": "4.2.1",
27
+ "@types/node": "25.5.0",
28
+ "@types/react": "catalog:",
29
+ "@types/react-dom": "catalog:",
30
+ "babel-plugin-react-compiler": "1.0.0",
31
+ "tailwindcss": "4.2.1",
32
+ "typescript": "catalog:"
33
+ },
34
+ "ignoreScripts": [
35
+ "sharp",
36
+ "unrs-resolver"
37
+ ],
38
+ "trustedDependencies": [
39
+ "sharp",
40
+ "unrs-resolver"
41
+ ]
42
+ }
@@ -0,0 +1,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ }
4
+
5
+ export default config
@@ -0,0 +1,7 @@
1
+ <svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M21.8855 184.247C-2.01603 162.076 -3.41853 124.726 18.753 100.824L94.7609 18.8855C116.932 -5.01605 154.282 -6.41855 178.184 15.7529C202.085 37.9244 203.488 75.274 181.316 99.1756L105.308 181.115C83.1367 205.016 45.7871 206.419 21.8855 184.247Z" fill="white"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M18.753 100.824C-3.41853 124.726 -2.01603 162.076 21.8855 184.247C45.7871 206.419 83.1367 205.016 105.308 181.115L145.81 137.452L59.2549 57.1621L18.753 100.824ZM40.6908 123.847C41.4209 122.946 41.2828 121.625 40.3824 120.895C39.482 120.165 38.1603 120.303 37.4302 121.203L34.9463 124.267C34.2162 125.167 34.3543 126.489 35.2547 127.219C36.1551 127.949 37.4768 127.811 38.2068 126.91L40.6908 123.847ZM34.5525 135.781C34.7653 134.641 34.014 133.545 32.8745 133.332C31.735 133.12 30.6388 133.871 30.4261 135.01C29.2814 141.142 29.7013 147.239 31.4916 152.718C31.8516 153.82 33.0367 154.421 34.1385 154.061C35.2404 153.701 35.8417 152.516 35.4816 151.414C33.9159 146.623 33.5335 141.24 34.5525 135.781ZM39.6257 160.27C38.8184 159.438 37.4897 159.418 36.6578 160.225C35.8259 161.032 35.8059 162.361 36.6131 163.193L40.0892 166.775C40.8964 167.607 42.2252 167.627 43.0571 166.82C43.889 166.013 43.909 164.684 43.1018 163.852L39.6257 160.27Z" fill="#5FCB88"/>
4
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M3.06623 138.152C5.76813 147.035 10.7861 155.343 18.084 162.112C40.0735 182.51 74.4351 181.22 94.8329 159.23L161.563 87.2934C181.961 65.304 180.67 30.9424 158.681 10.5446C153.808 6.02469 148.328 2.56968 142.527 0.168847C155.378 1.14043 168.001 6.30744 178.184 15.7529C202.085 37.9244 203.488 75.274 181.316 99.1756L105.308 181.115C83.1367 205.016 45.7871 206.419 21.8855 184.247C8.6076 171.93 2.27306 154.929 3.06623 138.152Z" fill="#629393" fill-opacity="0.4"/>
5
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M140.341 130.232L174.776 93.1092C193.598 72.8194 192.407 41.1138 172.117 22.2927C151.827 3.47151 120.122 4.66206 101.301 24.9518L66.8651 62.0744L140.341 130.232ZM181.316 99.1756C203.488 75.274 202.085 37.9244 178.184 15.7529C154.282 -6.41855 116.932 -5.01605 94.7609 18.8855L54.259 62.5478L140.814 142.838L181.316 99.1756Z" fill="#1D3934"/>
6
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M25.2927 106.891L60.7988 68.6141L54.259 62.5478L18.753 100.824C-3.41853 124.726 -2.01603 162.076 21.8855 184.247C45.7871 206.419 83.1367 205.016 105.308 181.115L140.814 142.838L134.275 136.771L98.7685 175.048C79.9473 195.338 48.2417 196.528 27.9519 177.707C7.66214 158.886 6.47158 127.181 25.2927 106.891Z" fill="#1D3934"/>
7
+ </svg>
@@ -0,0 +1,31 @@
1
+ /* Disable transitions */
2
+ .no-transitions * {
3
+ -moz-transition: none !important;
4
+ -o-transition: none !important;
5
+ -webkit-transition: none !important;
6
+ transition: none !important;
7
+ }
8
+
9
+ /* Disable scrolling */
10
+ .no-overscroll {
11
+ overflow: hidden;
12
+ overscroll-behavior: none;
13
+ }
14
+
15
+ /* Highlight */
16
+ ::-moz-selection {
17
+ color: var(--color-zinc-950);
18
+ background: var(--color-bg-accent);
19
+ }
20
+ ::selection {
21
+ color: var(--color-zinc-950);
22
+ background: var(--color-bg-accent);
23
+ }
24
+
25
+ /* Input date */
26
+ input::-webkit-datetime-edit-day-field:focus,
27
+ input::-webkit-datetime-edit-month-field:focus,
28
+ input::-webkit-datetime-edit-year-field:focus {
29
+ color: var(--color-zinc-950);
30
+ background: var(--color-bg-accent);
31
+ }
@@ -0,0 +1,8 @@
1
+ @theme {
2
+ --font-\*: initial;
3
+ --font-display: var(--font-display);
4
+ --font-handwriting: var(--font-handwriting);
5
+ --font-mono: var(--font-mono);
6
+ --font-sans: var(--font-sans);
7
+ --font-serif: var(--font-serif);
8
+ }
@@ -0,0 +1,5 @@
1
+ /* Order matters */
2
+ @import "./tailwind.css";
3
+ @import "./tailwind-reset.css";
4
+ @import "./custom.css";
5
+ @import "./font.css";