├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE.md ├── README.md └── home ├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── mdx-components.tsx ├── mdx ├── recma.mjs ├── rehype.mjs └── remark.mjs ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.js ├── prettier.config.js ├── public ├── banner.png ├── favicon.ico ├── icon.png └── icon.svg ├── src ├── app │ ├── favicon.ico │ ├── layout.tsx │ ├── not-found.tsx │ ├── page.mdx │ └── providers.tsx ├── components │ ├── Button.tsx │ ├── FormattedDate.tsx │ ├── IconLink.tsx │ ├── Intro.tsx │ ├── Layout.tsx │ ├── SparkleIcon.tsx │ ├── StarField.tsx │ ├── ThemeToggle.tsx │ └── mdx.tsx ├── fonts │ └── Mona-Sans.var.woff2 ├── images │ ├── circle.png │ └── emails.png └── styles │ ├── tailwind.css │ └── typography.css └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://store.lndev.me/buy/3e37099b-55e7-4fa9-96e6-bd24292c7be1"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 lndev-ui | Square UI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Square UI 2 | 3 | Collection of beautifully crafted open-source layouts UI built with shadcn/ui. 4 | 5 | ## Organization 6 | 7 | - `/home`: Home page 8 | - `/templates`: All templates 9 | 10 | ## 🛠️ Technologies 11 | 12 | - **Framework**: [Next.js](https://nextjs.org/) 13 | - **Langage**: [TypeScript](https://www.typescriptlang.org/) 14 | - **UI Components**: [shadcn/ui](https://ui.shadcn.com/) 15 | - **Styling**: [Tailwind CSS](https://tailwindcss.com/) 16 | 17 | ### 📦 Installation 18 | 19 | ```shell 20 | git clone https://github.com/ln-dev7/square-ui.git 21 | cd square-ui 22 | ``` 23 | 24 | ### Install dependencies 25 | 26 | ```shell 27 | pnpm install 28 | ``` 29 | 30 | ### Start the development server 31 | 32 | ```shell 33 | pnpm dev 34 | ``` 35 | -------------------------------------------------------------------------------- /home/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SITE_URL=https://example.com 2 | -------------------------------------------------------------------------------- /home/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /home/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /home/README.md: -------------------------------------------------------------------------------- 1 | ## Credits 2 | 3 | This site is built using the [Commit template](https://tailwindcss.com/plus/templates/commit) from Tailwind Plus, used under the Tailwind Plus license. 4 | 5 | The use of this template to showcase my original UI template creations has been approved by the Tailwind team. -------------------------------------------------------------------------------- /home/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import { type MDXComponents } from 'mdx/types' 2 | 3 | import * as mdxComponents from '@/components/mdx' 4 | 5 | export function useMDXComponents(components: MDXComponents) { 6 | return { 7 | ...components, 8 | ...mdxComponents, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /home/mdx/recma.mjs: -------------------------------------------------------------------------------- 1 | import { mdxAnnotations } from 'mdx-annotations' 2 | import { recmaImportImages } from 'recma-import-images' 3 | 4 | export const recmaPlugins = [mdxAnnotations.recma, recmaImportImages] 5 | -------------------------------------------------------------------------------- /home/mdx/rehype.mjs: -------------------------------------------------------------------------------- 1 | import { toString } from 'mdast-util-to-string' 2 | import { mdxAnnotations } from 'mdx-annotations' 3 | import rehypeAutolinkHeadings from 'rehype-autolink-headings' 4 | import rehypeSlug from 'rehype-slug' 5 | import { remarkRehypeWrap } from 'remark-rehype-wrap' 6 | import shiki from 'shiki' 7 | import { visit } from 'unist-util-visit' 8 | 9 | let highlighter 10 | 11 | function rehypeShiki() { 12 | return async (tree) => { 13 | highlighter = 14 | highlighter ?? (await shiki.getHighlighter({ theme: 'css-variables' })) 15 | 16 | visit(tree, 'element', (node, _nodeIndex, parentNode) => { 17 | if (node.tagName === 'code' && parentNode.tagName === 'pre') { 18 | let language = node.properties.className?.[0]?.replace(/^language-/, '') 19 | 20 | if (!language) { 21 | return 22 | } 23 | 24 | let tokens = highlighter.codeToThemedTokens( 25 | node.children[0].value, 26 | language, 27 | ) 28 | 29 | node.children = [] 30 | node.properties.highlightedCode = shiki.renderToHtml(tokens, { 31 | elements: { 32 | pre: ({ children }) => children, 33 | code: ({ children }) => children, 34 | line: ({ children }) => `${children}`, 35 | }, 36 | }) 37 | } 38 | }) 39 | } 40 | } 41 | 42 | export const rehypePlugins = [ 43 | mdxAnnotations.rehype, 44 | rehypeSlug, 45 | [rehypeAutolinkHeadings, { behavior: 'wrap', test: ['h2'] }], 46 | rehypeShiki, 47 | [ 48 | remarkRehypeWrap, 49 | { 50 | node: { type: 'element', tagName: 'article' }, 51 | start: 'element[tagName=hr]', 52 | transform: (article) => { 53 | article.children.splice(0, 1) 54 | let heading = article.children.find((n) => n.tagName === 'h2') 55 | article.properties = { ...heading.properties, title: toString(heading) } 56 | heading.properties = {} 57 | return article 58 | }, 59 | }, 60 | ], 61 | ] 62 | -------------------------------------------------------------------------------- /home/mdx/remark.mjs: -------------------------------------------------------------------------------- 1 | import { mdxAnnotations } from 'mdx-annotations' 2 | import remarkGfm from 'remark-gfm' 3 | import remarkUnwrapImages from 'remark-unwrap-images' 4 | 5 | export const remarkPlugins = [ 6 | mdxAnnotations.remark, 7 | remarkGfm, 8 | remarkUnwrapImages, 9 | ] 10 | -------------------------------------------------------------------------------- /home/next.config.mjs: -------------------------------------------------------------------------------- 1 | import nextMDX from '@next/mdx' 2 | 3 | import { recmaPlugins } from './mdx/recma.mjs' 4 | import { rehypePlugins } from './mdx/rehype.mjs' 5 | import { remarkPlugins } from './mdx/remark.mjs' 6 | 7 | const withMDX = nextMDX({ 8 | extension: /\.mdx?$/, 9 | options: { 10 | remarkPlugins, 11 | rehypePlugins, 12 | recmaPlugins, 13 | }, 14 | }) 15 | 16 | /** @type {import('next').NextConfig} */ 17 | const nextConfig = { 18 | pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'], 19 | } 20 | 21 | export default withMDX(nextConfig) 22 | -------------------------------------------------------------------------------- /home/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "square-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "browserslist": "defaults, not ie <= 11", 12 | "dependencies": { 13 | "@mdx-js/loader": "^3.0.0", 14 | "@mdx-js/react": "^3.0.0", 15 | "@next/mdx": "^14.0.4", 16 | "@tailwindcss/postcss": "^4.0.6", 17 | "@types/node": "^20.10.8", 18 | "@types/react": "^18.2.47", 19 | "@types/react-dom": "^18.2.18", 20 | "cheerio": "^1.0.0-rc.12", 21 | "clsx": "^2.1.0", 22 | "mdast-util-to-string": "^4.0.0", 23 | "mdx-annotations": "^0.1.4", 24 | "motion": "^10.17.0", 25 | "next": "^14.2.26", 26 | "next-themes": "^0.2.1", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "recma-import-images": "^0.0.3", 30 | "rehype-autolink-headings": "^7.1.0", 31 | "rehype-slug": "^6.0.0", 32 | "remark-gfm": "^4.0.0", 33 | "remark-rehype-wrap": "^0.0.3", 34 | "remark-unwrap-images": "^4.0.0", 35 | "shiki": "^0.14.7", 36 | "tailwindcss": "^4.0.15", 37 | "typescript": "^5.3.3", 38 | "unist-util-visit": "^5.0.0" 39 | }, 40 | "devDependencies": { 41 | "eslint": "^8.56.0", 42 | "eslint-config-next": "^14.0.4", 43 | "prettier": "^3.3.2", 44 | "prettier-plugin-tailwindcss": "^0.6.11", 45 | "sharp": "0.33.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /home/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /home/prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Options} */ 2 | module.exports = { 3 | singleQuote: true, 4 | semi: false, 5 | plugins: ['prettier-plugin-tailwindcss'], 6 | tailwindStylesheet: './src/styles/tailwind.css', 7 | } 8 | -------------------------------------------------------------------------------- /home/public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ln-dev7/square-ui/14db8ff357c6cd6d304a3ffab13c1f124fc0b76d/home/public/banner.png -------------------------------------------------------------------------------- /home/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ln-dev7/square-ui/14db8ff357c6cd6d304a3ffab13c1f124fc0b76d/home/public/favicon.ico -------------------------------------------------------------------------------- /home/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ln-dev7/square-ui/14db8ff357c6cd6d304a3ffab13c1f124fc0b76d/home/public/icon.png -------------------------------------------------------------------------------- /home/public/icon.svg: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /home/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ln-dev7/square-ui/14db8ff357c6cd6d304a3ffab13c1f124fc0b76d/home/src/app/favicon.ico -------------------------------------------------------------------------------- /home/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import localFont from 'next/font/local' 4 | import clsx from 'clsx' 5 | 6 | import { Providers } from '@/app/providers' 7 | 8 | import '@/styles/tailwind.css' 9 | 10 | const inter = Inter({ 11 | subsets: ['latin'], 12 | display: 'swap', 13 | variable: '--font-inter', 14 | }) 15 | 16 | const monaSans = localFont({ 17 | src: '../fonts/Mona-Sans.var.woff2', 18 | display: 'swap', 19 | variable: '--font-mona-sans', 20 | weight: '200 900', 21 | }) 22 | 23 | const siteUrl = 'https://square.lndev.me' 24 | 25 | export const metadata: Metadata = { 26 | title: { 27 | template: '%s | Square UI by lndev-ui', 28 | default: 'Square UI by lndev-ui', 29 | }, 30 | description: 31 | 'Collection of beautifully crafted open-source layouts UI built with shadcn/ui.', 32 | openGraph: { 33 | type: 'website', 34 | locale: 'en_US', 35 | url: siteUrl, 36 | siteName: 'Square UI', 37 | images: [ 38 | { 39 | url: `${siteUrl}/banner.png`, 40 | width: 2560, 41 | height: 1440, 42 | alt: 'Square UI by lndev-ui', 43 | }, 44 | ], 45 | }, 46 | twitter: { 47 | card: 'summary_large_image', 48 | site: '@ln_dev7', 49 | creator: '@ln_dev7', 50 | images: [ 51 | { 52 | url: `${siteUrl}/banner.png`, 53 | width: 2560, 54 | height: 1440, 55 | alt: 'Square UI', 56 | }, 57 | ], 58 | }, 59 | authors: [{ name: 'Leonel NGOYA', url: 'https://lndev.me/' }], 60 | keywords: ['ui', 'lndev', 'components', 'template'], 61 | } 62 | 63 | export default function RootLayout({ 64 | children, 65 | }: { 66 | children: React.ReactNode 67 | }) { 68 | return ( 69 | 74 | 75 | {children} 76 | 77 | 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /home/src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Layout } from '@/components/Layout' 2 | import { IconLink } from '@/components/IconLink' 3 | 4 | export default function NotFound() { 5 | return ( 6 | 7 |
8 |

404

9 | 10 | Go back home 11 | 12 |
13 |
14 | ) 15 | } 16 | 17 | -------------------------------------------------------------------------------- /home/src/app/page.mdx: -------------------------------------------------------------------------------- 1 | import { SparkleIcon } from '@/components/SparkleIcon' 2 | 3 | export { Layout as default } from '@/components/Layout' 4 | 5 | --- 6 | 7 | ![](@/images/circle.png) 8 | 9 | ## Circle {{ date: '2025-03-05T00:00Z' }} 10 | 11 | Project management interface inspired by Linear. Built with Next.js and shadcn/ui, this application allows tracking of issues, projects and teams. 12 | 13 | - [Live Demo](https://circle.lndev.me) 14 | - [GitHub](https://github.com/ln-dev7/circle) 15 | -------------------------------------------------------------------------------- /home/src/app/providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ThemeProvider } from 'next-themes' 4 | 5 | export function Providers({ children }: { children: React.ReactNode }) { 6 | return ( 7 | 8 | {children} 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /home/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import clsx from 'clsx' 3 | 4 | function ButtonInner({ 5 | arrow = false, 6 | children, 7 | }: { 8 | arrow?: boolean 9 | children: React.ReactNode 10 | }) { 11 | return ( 12 | <> 13 | 14 | 15 | {children} {arrow ? : null} 16 | 17 | ) 18 | } 19 | 20 | export function Button({ 21 | className, 22 | arrow, 23 | children, 24 | ...props 25 | }: { arrow?: boolean } & ( 26 | | React.ComponentPropsWithoutRef 27 | | ({ href?: undefined } & React.ComponentPropsWithoutRef<'button'>) 28 | )) { 29 | className = clsx( 30 | className, 31 | 'group relative isolate flex-none rounded-md py-1.5 text-[0.8125rem]/6 font-semibold text-white', 32 | arrow ? 'pl-2.5 pr-[calc(9/16*1rem)]' : 'px-2.5', 33 | ) 34 | 35 | return typeof props.href === 'undefined' ? ( 36 | 39 | ) : ( 40 | 41 | {children} 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /home/src/components/FormattedDate.tsx: -------------------------------------------------------------------------------- 1 | const dateFormatter = new Intl.DateTimeFormat('en-US', { 2 | year: 'numeric', 3 | month: 'short', 4 | day: 'numeric', 5 | timeZone: 'UTC', 6 | }) 7 | 8 | export function FormattedDate({ 9 | date, 10 | ...props 11 | }: React.ComponentPropsWithoutRef<'time'> & { date: string | Date }) { 12 | date = typeof date === 'string' ? new Date(date) : date 13 | 14 | return ( 15 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /home/src/components/IconLink.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import clsx from 'clsx' 3 | 4 | export function IconLink({ 5 | children, 6 | className, 7 | compact = false, 8 | icon: Icon, 9 | ...props 10 | }: React.ComponentPropsWithoutRef & { 11 | compact?: boolean 12 | icon?: React.ComponentType<{ className?: string }> 13 | }) { 14 | return ( 15 | 23 | 24 | {Icon && } 25 | {children} 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /home/src/components/Intro.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import Image from 'next/image' 3 | 4 | import { IconLink } from '@/components/IconLink' 5 | 6 | function BookIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 7 | return ( 8 | 11 | ) 12 | } 13 | 14 | function GitHubIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 15 | return ( 16 | 19 | ) 20 | } 21 | 22 | function XIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 23 | return ( 24 | 27 | ) 28 | } 29 | 30 | export function Intro() { 31 | return ( 32 | <> 33 |
34 | 35 | Logo 42 | 43 | Square UI 44 | 45 | 46 |
47 |

48 | Open-source layouts
49 | by{' '} 50 | 55 | lndev-ui 56 | 57 |

58 |

59 | Collection of beautifully crafted open-source layouts UI built with 60 | shadcn/ui. 61 |

62 |
63 | 69 | Sponsor 70 | 71 | 77 | GitHub 78 | 79 |
80 | 81 | ) 82 | } 83 | 84 | export function IntroFooter() { 85 | return ( 86 |

87 | Built by{' '} 88 | 94 | Leonel Ngoya 95 | 96 |

97 | ) 98 | } 99 | -------------------------------------------------------------------------------- /home/src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { useId } from 'react' 2 | 3 | import { Intro, IntroFooter } from '@/components/Intro' 4 | import { StarField } from '@/components/StarField' 5 | import { ThemeToggle } from '@/components/ThemeToggle' 6 | 7 | function Timeline() { 8 | let id = useId() 9 | 10 | return ( 11 |
12 | 27 |
28 | ) 29 | } 30 | 31 | function Glow() { 32 | let id = useId() 33 | 34 | return ( 35 |
36 | 65 |
66 |
67 | ) 68 | } 69 | 70 | function FixedSidebar({ 71 | main, 72 | footer, 73 | }: { 74 | main: React.ReactNode 75 | footer: React.ReactNode 76 | }) { 77 | return ( 78 |
79 | 80 |
81 |
82 |
83 |
84 | 85 | {main} 86 |
87 |
88 |
89 | {footer} 90 |
91 |
92 |
93 |
94 | ) 95 | } 96 | 97 | export function Layout({ children }: { children: React.ReactNode }) { 98 | return ( 99 | <> 100 | } footer={} /> 101 | 102 |
103 | 104 |
105 | {children} 106 |
107 |
108 | 109 | ) 110 | } 111 | -------------------------------------------------------------------------------- /home/src/components/SparkleIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SparkleIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /home/src/components/StarField.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect, useId, useRef } from 'react' 4 | import clsx from 'clsx' 5 | import { type TimelineSegment, animate, timeline } from 'motion' 6 | 7 | type Star = [x: number, y: number, dim?: boolean, blur?: boolean] 8 | 9 | const stars: Array = [ 10 | [4, 4, true, true], 11 | [4, 44, true], 12 | [36, 22], 13 | [50, 146, true, true], 14 | [64, 43, true, true], 15 | [76, 30, true], 16 | [101, 116], 17 | [140, 36, true], 18 | [149, 134], 19 | [162, 74, true], 20 | [171, 96, true, true], 21 | [210, 56, true, true], 22 | [235, 90], 23 | [275, 82, true, true], 24 | [306, 6], 25 | [307, 64, true, true], 26 | [380, 68, true], 27 | [380, 108, true, true], 28 | [391, 148, true, true], 29 | [405, 18, true], 30 | [412, 86, true, true], 31 | [426, 210, true, true], 32 | [427, 56, true, true], 33 | [538, 138], 34 | [563, 88, true, true], 35 | [611, 154, true, true], 36 | [637, 150], 37 | [651, 146, true], 38 | [682, 70, true, true], 39 | [683, 128], 40 | [781, 82, true, true], 41 | [785, 158, true], 42 | [832, 146, true, true], 43 | [852, 89], 44 | ] 45 | 46 | const constellations: Array> = [ 47 | [ 48 | [247, 103], 49 | [261, 86], 50 | [307, 104], 51 | [357, 36], 52 | ], 53 | [ 54 | [586, 120], 55 | [516, 100], 56 | [491, 62], 57 | [440, 107], 58 | [477, 180], 59 | [516, 100], 60 | ], 61 | [ 62 | [733, 100], 63 | [803, 120], 64 | [879, 113], 65 | [823, 164], 66 | [803, 120], 67 | ], 68 | ] 69 | 70 | function Star({ 71 | blurId, 72 | point: [cx, cy, dim, blur], 73 | }: { 74 | blurId: string 75 | point: Star 76 | }) { 77 | let groupRef = useRef>(null) 78 | let ref = useRef>(null) 79 | 80 | useEffect(() => { 81 | if (!groupRef.current || !ref.current) { 82 | return 83 | } 84 | 85 | let delay = Math.random() * 2 86 | 87 | let animations = [ 88 | animate(groupRef.current, { opacity: 1 }, { duration: 4, delay }), 89 | animate( 90 | ref.current, 91 | { 92 | opacity: dim ? [0.2, 0.5] : [1, 0.6], 93 | scale: dim ? [1, 1.2] : [1.2, 1], 94 | }, 95 | { 96 | delay, 97 | duration: Math.random() * 2 + 2, 98 | direction: 'alternate', 99 | repeat: Infinity, 100 | }, 101 | ), 102 | ] 103 | 104 | return () => { 105 | for (let animation of animations) { 106 | animation.cancel() 107 | } 108 | } 109 | }, [dim]) 110 | 111 | return ( 112 | 113 | 125 | 126 | ) 127 | } 128 | 129 | function Constellation({ 130 | points, 131 | blurId, 132 | }: { 133 | points: Array 134 | blurId: string 135 | }) { 136 | let ref = useRef>(null) 137 | let uniquePoints = points.filter( 138 | (point, pointIndex) => 139 | points.findIndex((p) => String(p) === String(point)) === pointIndex, 140 | ) 141 | let isFilled = uniquePoints.length !== points.length 142 | 143 | useEffect(() => { 144 | if (!ref.current) { 145 | return 146 | } 147 | 148 | let sequence: Array = [ 149 | [ 150 | ref.current, 151 | { strokeDashoffset: 0, visibility: 'visible' }, 152 | { duration: 5, delay: Math.random() * 3 + 2 }, 153 | ], 154 | ] 155 | 156 | if (isFilled) { 157 | sequence.push([ 158 | ref.current, 159 | { fill: 'rgb(255 255 255 / 0.02)' }, 160 | { duration: 1 }, 161 | ]) 162 | } 163 | 164 | let animation = timeline(sequence) 165 | 166 | return () => { 167 | animation.cancel() 168 | } 169 | }, [isFilled]) 170 | 171 | return ( 172 | <> 173 | 184 | {uniquePoints.map((point, pointIndex) => ( 185 | 186 | ))} 187 | 188 | ) 189 | } 190 | 191 | export function StarField({ className }: { className?: string }) { 192 | let blurId = useId() 193 | 194 | return ( 195 | 220 | ) 221 | } 222 | -------------------------------------------------------------------------------- /home/src/components/ThemeToggle.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect, useState } from 'react' 4 | import { useTheme } from 'next-themes' 5 | 6 | function ThemeIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 7 | return ( 8 | 15 | ) 16 | } 17 | 18 | export function ThemeToggle() { 19 | let [mounted, setMounted] = useState(false) 20 | let { resolvedTheme, setTheme } = useTheme() 21 | let otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark' 22 | 23 | useEffect(() => { 24 | setMounted(true) 25 | }, []) 26 | 27 | if (!mounted) { 28 | return null 29 | } 30 | 31 | return ( 32 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /home/src/components/mdx.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect, useRef, useState } from 'react' 4 | import Image, { type ImageProps } from 'next/image' 5 | import Link from 'next/link' 6 | import clsx from 'clsx' 7 | 8 | import { FormattedDate } from '@/components/FormattedDate' 9 | 10 | export const a = Link 11 | 12 | type ImagePropsWithOptionalAlt = Omit & { alt?: string } 13 | 14 | export const img = function Img(props: ImagePropsWithOptionalAlt) { 15 | return ( 16 |
17 | 22 |
23 |
24 | ) 25 | } 26 | 27 | function ContentWrapper({ 28 | className, 29 | ...props 30 | }: React.ComponentPropsWithoutRef<'div'>) { 31 | return ( 32 |
33 |
34 |
41 |
42 |
43 | ) 44 | } 45 | 46 | function ArticleHeader({ id, date }: { id: string; date: string | Date }) { 47 | return ( 48 |
49 |
50 | 51 | 55 | 56 |
57 |
58 | 59 |
60 | 61 | 65 | 66 |
67 |
68 |
69 | ) 70 | } 71 | 72 | export const article = function Article({ 73 | id, 74 | date, 75 | children, 76 | }: { 77 | id: string 78 | date: string | Date 79 | children: React.ReactNode 80 | }) { 81 | let heightRef = useRef>(null) 82 | let [heightAdjustment, setHeightAdjustment] = useState(0) 83 | 84 | useEffect(() => { 85 | if (!heightRef.current) { 86 | return 87 | } 88 | 89 | let observer = new window.ResizeObserver(() => { 90 | if (!heightRef.current) { 91 | return 92 | } 93 | let { height } = heightRef.current.getBoundingClientRect() 94 | let nextMultipleOf8 = 8 * Math.ceil(height / 8) 95 | setHeightAdjustment(nextMultipleOf8 - height) 96 | }) 97 | 98 | observer.observe(heightRef.current) 99 | 100 | return () => { 101 | observer.disconnect() 102 | } 103 | }, []) 104 | 105 | return ( 106 |
111 |
112 | 113 | 114 | {children} 115 | 116 |
117 |
118 | ) 119 | } 120 | 121 | export const code = function Code({ 122 | highlightedCode, 123 | ...props 124 | }: React.ComponentPropsWithoutRef<'code'> & { highlightedCode?: string }) { 125 | if (highlightedCode) { 126 | return ( 127 | 128 | ) 129 | } 130 | 131 | return 132 | } 133 | -------------------------------------------------------------------------------- /home/src/fonts/Mona-Sans.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ln-dev7/square-ui/14db8ff357c6cd6d304a3ffab13c1f124fc0b76d/home/src/fonts/Mona-Sans.var.woff2 -------------------------------------------------------------------------------- /home/src/images/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ln-dev7/square-ui/14db8ff357c6cd6d304a3ffab13c1f124fc0b76d/home/src/images/circle.png -------------------------------------------------------------------------------- /home/src/images/emails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ln-dev7/square-ui/14db8ff357c6cd6d304a3ffab13c1f124fc0b76d/home/src/images/emails.png -------------------------------------------------------------------------------- /home/src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import './typography.css' layer(components); 3 | 4 | @custom-variant dark (&:where(.dark, .dark *)); 5 | 6 | @theme { 7 | --text-2xs: 0.6875rem; 8 | 9 | --font-sans: var(--font-inter); 10 | --font-display: var(--font-mona-sans); 11 | } 12 | -------------------------------------------------------------------------------- /home/src/styles/typography.css: -------------------------------------------------------------------------------- 1 | .typography { 2 | --typography-body: var(--color-gray-600); 3 | --typography-headings: var(--color-gray-900); 4 | --typography-quotes: var(--color-gray-500); 5 | --typography-quotes-border: var(--color-gray-200); 6 | --typography-links: var(--color-sky-500); 7 | --typography-link-hover: var(--color-sky-600); 8 | --typography-link-underline: --theme(--color-sky-400 / 0.4); 9 | --typography-link-hover-underline: --theme(--color-sky-600 / 0.4); 10 | --typography-pre: var(--color-gray-300); 11 | --typography-pre-shadow: var(--shadow-md); 12 | --typography-bold: var(--color-gray-900); 13 | --typography-kbd: var(--color-gray-600); 14 | --typography-kbd-border: var(--color-gray-200); 15 | --typography-kbd-bg: var(--color-gray-50); 16 | --typography-code: var(--color-gray-900); 17 | --typography-hr: --theme(--color-gray-900 / 0.05); 18 | --typography-th-borders: --theme(--color-gray-900 / 0.2); 19 | --typography-td-borders: --theme(--color-gray-900 / 0.05); 20 | 21 | --shiki-color-text: var(--color-white); 22 | --shiki-token-constant: var(--color-emerald-300); 23 | --shiki-token-string: var(--color-emerald-300); 24 | --shiki-token-comment: var(--color-gray-500); 25 | --shiki-token-keyword: var(--color-sky-300); 26 | --shiki-token-parameter: var(--color-pink-300); 27 | --shiki-token-function: var(--color-violet-300); 28 | --shiki-token-string-expression: var(--color-emerald-300); 29 | --shiki-token-punctuation: var(--color-gray-200); 30 | 31 | .dark & { 32 | --typography-body: var(--color-gray-300); 33 | --typography-headings: var(--color-white); 34 | --typography-quotes: var(--color-gray-400); 35 | --typography-quotes-border: var(--color-gray-800); 36 | --typography-links: var(--color-sky-400); 37 | --typography-link-hover: var(--color-white); 38 | --typography-link-underline: --theme(--color-sky-400 / 0.4); 39 | --typography-link-hover-underline: --theme(--color-white / 0.4); 40 | --typography-pre: var(--color-gray-400); 41 | --typography-pre-shadow: inset 0 0 0 1px --theme(--color-white / 0.1); 42 | --typography-bold: var(--color-white); 43 | --typography-kbd: var(--color-white); 44 | --typography-kbd-border: var(--color-gray-800); 45 | --typography-kbd-bg: var(--color-gray-900); 46 | --typography-code: var(--color-white); 47 | --typography-hr: --theme(--color-white / 0.1); 48 | --typography-th-borders: --theme(--color-white / 0.1); 49 | --typography-td-borders: --theme(--color-white / 0.05); 50 | } 51 | 52 | color: var(--typography-body); 53 | font-size: var(--text-sm); 54 | line-height: theme(lineHeight.6); 55 | 56 | h2 { 57 | font-family: var(--font-display); 58 | color: var(--typography-headings); 59 | font-weight: var(--font-weight-semibold); 60 | font-size: var(--text-xl); 61 | line-height: theme(lineHeight.8); 62 | } 63 | 64 | /* Headings */ 65 | h3 { 66 | color: var(--typography-headings); 67 | font-family: var(--font-display); 68 | font-weight: var(--font-weight-semibold); 69 | font-size: var(--text-base); 70 | line-height: theme(lineHeight.6); 71 | display: flex; 72 | align-items: center; 73 | column-gap: theme(gap.3); 74 | } 75 | 76 | h2>a { 77 | display: flex; 78 | align-items: center; 79 | column-gap: theme(gap.3); 80 | } 81 | 82 | h2>a>svg { 83 | flex: none; 84 | width: theme(width.4); 85 | height: theme(height.4); 86 | } 87 | 88 | h3>svg { 89 | flex: none; 90 | width: theme(width.4); 91 | height: theme(height.4); 92 | } 93 | 94 | h4 { 95 | color: var(--typography-headings); 96 | font-family: var(--font-display); 97 | font-weight: var(--font-weight-semibold); 98 | font-size: var(--text-sm); 99 | line-height: theme(lineHeight.6); 100 | } 101 | 102 | /* Quotes */ 103 | blockquote { 104 | border-left: 3px solid var(--typography-quotes-border); 105 | padding-left: theme(padding.6); 106 | color: var(--typography-quotes); 107 | } 108 | 109 | /* Links */ 110 | a:not(h2 a) { 111 | font-weight: var(--font-weight-semibold); 112 | color: var(--typography-links); 113 | text-decoration: underline; 114 | text-decoration-color: var(--typography-link-underline); 115 | text-underline-offset: theme(textUnderlineOffset.2); 116 | transition-property: color, text-decoration-color; 117 | transition-duration: theme(transitionDuration.DEFAULT); 118 | transition-timing-function: theme(transitionTimingFunction.DEFAULT); 119 | 120 | &:hover { 121 | color: var(--typography-link-hover); 122 | text-decoration-color: var(--typography-link-hover-underline); 123 | } 124 | } 125 | 126 | /* Inline text */ 127 | strong { 128 | font-weight: var(--font-weight-semibold); 129 | } 130 | 131 | strong:not(a strong) { 132 | color: var(--typography-bold); 133 | } 134 | 135 | kbd { 136 | display: inline-block; 137 | border-radius: var(--radius); 138 | background-color: var(--typography-kbd-bg); 139 | padding: 0 theme(padding[1.5]); 140 | font-family: var(--font-mono); 141 | font-size: var(--text-xs); 142 | font-weight: 400; 143 | line-height: theme(lineHeight.5); 144 | color: var(--typography-kbd); 145 | box-shadow: inset 0 0 0 1px var(--typography-kbd-border); 146 | } 147 | 148 | code { 149 | font-family: var(--font-mono); 150 | } 151 | 152 | code:not(a code, pre code) { 153 | color: var(--typography-code); 154 | } 155 | 156 | code:not(pre code) { 157 | font-size: calc(12 / 14 * 1em); 158 | line-height: theme(lineHeight.none); 159 | font-weight: var(--font-weight-bold); 160 | 161 | &::before { 162 | content: '`'; 163 | } 164 | 165 | &::after { 166 | content: '`'; 167 | } 168 | } 169 | 170 | /* Code blocks */ 171 | pre { 172 | display: flex; 173 | background-color: var(--color-gray-900); 174 | border-radius: var(--radius-lg); 175 | overflow-x: auto; 176 | box-shadow: var(--typography-pre-shadow); 177 | } 178 | 179 | pre code { 180 | flex: none; 181 | padding: theme(padding.6); 182 | font-size: 0.8125rem; 183 | line-height: theme(lineHeight.6); 184 | color: var(--typography-pre); 185 | } 186 | 187 | /*
*/ 188 | hr { 189 | border-color: var(--typography-hr); 190 | } 191 | 192 | /* Lists */ 193 | ul, 194 | ol { 195 | padding-left: 1.375rem; 196 | } 197 | 198 | ul { 199 | list-style-type: disc; 200 | } 201 | 202 | ol { 203 | list-style-type: decimal; 204 | } 205 | 206 | li { 207 | padding-left: 0.625rem; 208 | } 209 | 210 | li::marker { 211 | color: var(--color-gray-400); 212 | } 213 | 214 | ol>li::marker { 215 | font-size: var(--text-xs); 216 | font-weight: var(--font-weight-semibold); 217 | } 218 | 219 | /* Tables */ 220 | table { 221 | width: theme(width.full); 222 | text-align: left; 223 | } 224 | 225 | thead { 226 | border-bottom: 1px solid var(--typography-th-borders); 227 | } 228 | 229 | thead th { 230 | font-weight: var(--font-weight-semibold); 231 | padding-top: 0; 232 | padding-bottom: calc(theme(padding.2) - 1px); 233 | color: var(--typography-headings); 234 | } 235 | 236 | tbody tr { 237 | border-bottom: 1px solid var(--typography-td-borders); 238 | } 239 | 240 | tbody td { 241 | padding-top: calc(theme(padding.2) - 1px); 242 | padding-bottom: theme(padding.2); 243 | } 244 | 245 | :is(th, td):first-child { 246 | padding-left: 0; 247 | padding-right: theme(padding.2); 248 | } 249 | 250 | :is(th, td):last-child { 251 | padding-left: theme(padding.2); 252 | padding-right: 0; 253 | } 254 | 255 | :is(th, td):not(:first-child, :last-child) { 256 | padding-left: theme(padding.2); 257 | padding-right: theme(padding.2); 258 | } 259 | 260 | /* Spacing */ 261 | >* { 262 | margin-top: theme(margin.6); 263 | } 264 | 265 | :is(h2, h3, h4, blockquote, pre, table) { 266 | margin-top: theme(margin.8); 267 | } 268 | 269 | hr { 270 | margin-top: calc(theme(margin.16) - 1px); 271 | } 272 | 273 | li { 274 | margin-top: theme(margin.4); 275 | } 276 | 277 | li> :is(p, ol, ul) { 278 | margin-top: theme(margin.4); 279 | } 280 | 281 | :is(h2, h3, h4)+* { 282 | margin-top: theme(margin.4); 283 | } 284 | 285 | :is(blockquote, pre, table)+* { 286 | margin-top: theme(margin.8); 287 | } 288 | 289 | hr+* { 290 | margin-top: theme(margin.16); 291 | } 292 | 293 | > :first-child, 294 | li> :first-child { 295 | margin-top: 0; 296 | } 297 | } -------------------------------------------------------------------------------- /home/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | --------------------------------------------------------------------------------