├── app ├── favicon.ico ├── globals.css ├── CardTokens.stylex.ts ├── components │ ├── ButtonTokens.stylex.ts │ ├── ThemeableButton.tsx │ ├── ButtonsDemo.tsx │ └── Counter.tsx ├── about │ ├── layout.tsx │ └── page.mdx ├── layout.tsx ├── Card.tsx ├── page.tsx └── globalTokens.stylex.ts ├── .eslintrc.js ├── next.config.js ├── .babelrc.js ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── __tests__ └── button.test.tsx ├── tsconfig.json ├── package.json ├── README.md └── mdx-components.tsx /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmn/nextjs-app-dir-stylex/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | -------------------------------------------------------------------------------- /app/CardTokens.stylex.ts: -------------------------------------------------------------------------------- 1 | import * as stylex from "@stylexjs/stylex"; 2 | 3 | export const tokens = stylex.defineVars({ 4 | arrowTransform: "translateX(0)", 5 | }); 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: "next/core-web-vitals", 3 | plugins: ["@stylexjs"], 4 | rules: { 5 | // The Eslint rule still needs work, but you can 6 | // enable it to test things out. 7 | "@stylexjs/valid-styles": "error", 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /app/components/ButtonTokens.stylex.ts: -------------------------------------------------------------------------------- 1 | import * as stylex from "@stylexjs/stylex"; 2 | 3 | export const buttonTokens = stylex.defineVars({ 4 | bgColor: "blue", 5 | textColor: "white", 6 | cornerRadius: "4px", 7 | paddingBlock: "4px", 8 | paddingInline: "8px", 9 | }); 10 | -------------------------------------------------------------------------------- /app/about/layout.tsx: -------------------------------------------------------------------------------- 1 | import * as stylex from "@stylexjs/stylex"; 2 | 3 | export default function RootLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode; 7 | }) { 8 | return
{children}
; 9 | } 10 | 11 | const styles = stylex.create({ 12 | container: { 13 | width: "100%", 14 | maxWidth: 768, 15 | marginInline: "auto", 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const stylexPlugin = require("@stylexjs/nextjs-plugin"); 3 | const withMDX = require("@next/mdx")(); 4 | 5 | const nextConfig = { 6 | // Configure `pageExtensions` to include MDX files 7 | pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"], 8 | transpilePackages: ["@stylexjs/open-props"], 9 | // Optionally, add any other Next.js config below 10 | }; 11 | 12 | module.exports = stylexPlugin({ 13 | rootDir: __dirname, 14 | })(withMDX(nextConfig)); 15 | -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["next/babel"], 3 | plugins: [ 4 | [ 5 | "@stylexjs/babel-plugin", 6 | { 7 | dev: process.env.NODE_ENV === "development", 8 | test: process.env.NODE_ENV === "test", 9 | runtimeInjection: false, 10 | genConditionalClasses: true, 11 | treeshakeCompensation: true, 12 | unstable_moduleResolution: { 13 | type: "commonJS", 14 | rootDir: __dirname, 15 | }, 16 | }, 17 | ], 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /app/about/page.mdx: -------------------------------------------------------------------------------- 1 | import Card from "../Card"; 2 | 3 | # Welcome to my MDX page! 4 | 5 | ## H2 level right Here 6 | 7 | ### this is a level 3 8 | 9 | This is some **bold** and _italics_ text. 10 | 11 | This is a list in markdown: 12 | 13 | - One 14 | - One and a half 15 | - One and three quarters 16 | - Two 17 | - Three 18 | 19 | Checkout my React component: 20 | 21 | 26 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /__tests__/button.test.tsx: -------------------------------------------------------------------------------- 1 | import * as stylex from "@stylexjs/stylex"; 2 | 3 | import ThemeableButton from "../app/components/ThemeableButton"; 4 | import renderer from "react-test-renderer"; 5 | import React from "react"; 6 | 7 | describe("ThemeableButton", () => { 8 | it("renders default state", () => { 9 | const btn = renderer 10 | .create( {}}>ClickMe) 11 | .toJSON(); 12 | 13 | expect(btn).toMatchInlineSnapshot(` 14 | 20 | `); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "types": ["jest"], 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/layout.js"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | 3 | import { globalTokens as $ } from "./globalTokens.stylex"; 4 | import * as stylex from "@stylexjs/stylex"; 5 | 6 | export const metadata = { 7 | title: "Create Next App", 8 | description: "Generated by create next app", 9 | }; 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: { 14 | children: React.ReactNode; 15 | }) { 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | } 22 | 23 | const DARK = "@media (prefers-color-scheme: dark)"; 24 | const fgColor = `rgba(${$.foregroundR}, ${$.foregroundG}, ${$.foregroundB}, 1)`; 25 | 26 | const styles = stylex.create({ 27 | html: { 28 | colorScheme: "light dark", 29 | }, 30 | reset: { 31 | minHeight: "100%", 32 | margin: 0, 33 | padding: 0, 34 | }, 35 | body: { 36 | color: fgColor, 37 | backgroundImage: { 38 | default: "linear-gradient(to bottom, rgb(214, 219, 220), white)", 39 | [DARK]: "linear-gradient(to bottom, rgb(20, 22, 27), black)", 40 | }, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-app-dir-test", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "test": "jest", 7 | "predev": "rimraf .next", 8 | "prebuild": "rimraf .next", 9 | "dev": "next dev", 10 | "build": "next build", 11 | "start": "next start", 12 | "lint": "next lint" 13 | }, 14 | "dependencies": { 15 | "@mdx-js/loader": "^3.0.0", 16 | "@mdx-js/react": "^3.0.0", 17 | "@next/mdx": "^14.0.3", 18 | "@stylexjs/open-props": "^0.4.1", 19 | "@stylexjs/stylex": "^0.4.1", 20 | "@types/mdx": "^2.0.10", 21 | "bright": "^0.8.4", 22 | "next": "14.0.1", 23 | "react": "18.2.0", 24 | "react-dom": "18.2.0" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.23.2", 28 | "@babel/plugin-syntax-flow": "^7.22.5", 29 | "@babel/plugin-syntax-jsx": "^7.22.5", 30 | "@babel/plugin-syntax-typescript": "^7.22.5", 31 | "@stylexjs/babel-plugin": "^0.4.1", 32 | "@stylexjs/eslint-plugin": "^0.4.1", 33 | "@stylexjs/nextjs-plugin": "^0.4.1", 34 | "@testing-library/react": "^14.1.2", 35 | "@types/jest": "^29.5.11", 36 | "@types/node": "20.8.10", 37 | "@types/react": "18.2.20", 38 | "@types/react-dom": "18.2.7", 39 | "@types/react-test-renderer": "^18.0.7", 40 | "eslint": "8.52.0", 41 | "eslint-config-next": "14.0.1", 42 | "jest": "^30.0.0-alpha.1", 43 | "prettier": "^3.1.1", 44 | "react-test-renderer": "^18.2.0", 45 | "rimraf": "^5.0.5", 46 | "ts_dependency_graph": "^2.0.0", 47 | "typescript": "5.2.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/components/ThemeableButton.tsx: -------------------------------------------------------------------------------- 1 | import * as stylex from "@stylexjs/stylex"; 2 | import type { StyleXStyles, Theme } from "@stylexjs/stylex/lib/StyleXTypes"; 3 | 4 | import "./ButtonTokens.stylex"; 5 | import { buttonTokens } from "./ButtonTokens.stylex"; 6 | 7 | type Props = Readonly<{ 8 | onClick: () => void; 9 | children: React.ReactNode; 10 | // for Overrides 11 | style?: StyleXStyles; 12 | theme?: Theme; 13 | variant?: "primary" | "danger"; 14 | em?: boolean; 15 | }>; 16 | 17 | export default function Card({ 18 | onClick, 19 | children, 20 | style, 21 | theme, 22 | variant, 23 | em = false, 24 | }: Props) { 25 | return ( 26 | 38 | ); 39 | } 40 | 41 | const styles = stylex.create({ 42 | base: { 43 | appearance: "none", 44 | borderWidth: 0, 45 | borderStyle: "none", 46 | backgroundColor: buttonTokens.bgColor, 47 | color: buttonTokens.textColor, 48 | borderRadius: buttonTokens.cornerRadius, 49 | paddingBlock: buttonTokens.paddingBlock, 50 | paddingInline: buttonTokens.paddingInline, 51 | }, 52 | emphasise: { 53 | transform: "scale(1.1)", 54 | }, 55 | }); 56 | 57 | const variantStyles = stylex.create({ 58 | danger: { 59 | backgroundColor: "red", 60 | color: "white", 61 | }, 62 | primary: { 63 | fontWeight: "bold", 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | [http://localhost:3000/api/hello](http://localhost:3000/api/hello) is an endpoint that uses [Route Handlers](https://beta.nextjs.org/docs/routing/route-handlers). This endpoint can be edited in `app/api/hello/route.ts`. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /app/components/ButtonsDemo.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as stylex from "@stylexjs/stylex"; 4 | import { buttonTokens } from "./ButtonTokens.stylex"; 5 | import ThemeableButton from "./ThemeableButton"; 6 | 7 | type Props = Readonly<{}>; 8 | 9 | export default function ButtonsDemo(_props: Props) { 10 | const onClick = () => { 11 | console.log("click"); 12 | }; 13 | return ( 14 |
15 | Vanilla Button 16 | 17 | 18 | Bordered Button 19 | 20 | 21 | 22 | Red Button 23 | 24 | 25 |
26 | 27 | Red Button By inheritance 28 | 29 |
30 | 31 | 36 | Red - Bordered Button 37 | 38 |
39 | ); 40 | } 41 | 42 | const redTheme = stylex.createTheme(buttonTokens, { 43 | bgColor: "red", 44 | textColor: "white", 45 | cornerRadius: "4px", 46 | paddingBlock: "4px", 47 | paddingInline: "8px", 48 | }); 49 | 50 | const styles = stylex.create({ 51 | container: { 52 | display: "flex", 53 | flexDirection: "column", 54 | alignItems: "center", 55 | justifyContent: "center", 56 | gap: 16, 57 | paddingBottom: 64, 58 | }, 59 | bordered: { 60 | borderWidth: 2, 61 | borderStyle: "solid", 62 | borderColor: "red", 63 | }, 64 | greenBorder: { 65 | borderColor: "green", 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import type { MDXComponents } from "mdx/types"; 2 | 3 | import * as stylex from "@stylexjs/stylex"; 4 | import { globalTokens as $, spacing, text } from "./app/globalTokens.stylex"; 5 | import { colors } from "@stylexjs/open-props/lib/colors.stylex"; 6 | 7 | const MOBILE = "@media (max-width: 700px)"; 8 | const styles = stylex.create({ 9 | base: { 10 | fontFamily: $.fontSans, 11 | padding: 0, 12 | margin: 0, 13 | marginTop: "0.5em", 14 | }, 15 | h1: { 16 | color: colors.blue3, 17 | fontSize: text.h3, 18 | fontWeight: 200, 19 | }, 20 | h2: { 21 | fontSize: text.h4, 22 | fontWeight: 400, 23 | }, 24 | h3: { 25 | fontSize: text.h5, 26 | fontWeight: 600, 27 | }, 28 | p: { 29 | marginTop: "1em", 30 | fontSize: text.p, 31 | }, 32 | li: { 33 | marginInlineStart: "1.1em", 34 | }, 35 | }); 36 | 37 | function H1(props: any) { 38 | return

; 39 | } 40 | 41 | function H2(props: any) { 42 | return

; 43 | } 44 | 45 | function H3(props: any) { 46 | return

; 47 | } 48 | 49 | function P(props: any) { 50 | return

; 51 | } 52 | 53 | function Ul(props: any) { 54 | return

    ; 55 | } 56 | function Ol(props: any) { 57 | return
      ; 58 | } 59 | 60 | function Li(props: any) { 61 | return
    1. ; 62 | } 63 | 64 | export function useMDXComponents(components: MDXComponents): MDXComponents { 65 | return { 66 | h1: H1, 67 | h2: H2, 68 | h3: H3, 69 | p: P, 70 | ul: Ul, 71 | ol: Ol, 72 | li: Li, 73 | ...components, 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /app/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * 8 | */ 9 | 10 | "use client"; 11 | 12 | import * as stylex from "@stylexjs/stylex"; 13 | import { spacing, text, globalTokens as $ } from "../globalTokens.stylex"; 14 | import { colors } from "@stylexjs/open-props/lib/colors.stylex"; 15 | import { shadows } from "@stylexjs/open-props/lib/shadows.stylex"; 16 | import { useState } from "react"; 17 | 18 | export default function Counter() { 19 | const [count, setCount] = useState(0); 20 | 21 | return ( 22 |
      23 | 29 |
      99 || count < -9) && styles.largeNumber 33 | )} 34 | > 35 | {count} 36 |
      37 | 43 |
      44 | ); 45 | } 46 | 47 | const DARK = "@media (prefers-color-scheme: dark)" as const; 48 | 49 | const styles = stylex.create({ 50 | container: { 51 | display: "flex", 52 | alignItems: "center", 53 | justifyContent: "center", 54 | flexDirection: "row", 55 | borderRadius: spacing.md, 56 | borderWidth: 1, 57 | borderStyle: "solid", 58 | borderColor: colors.blue7, 59 | padding: spacing.xxxs, 60 | fontFamily: $.fontSans, 61 | gap: spacing.xs, 62 | boxShadow: shadows.shadow6, 63 | }, 64 | button: { 65 | display: "flex", 66 | alignItems: "center", 67 | justifyContent: "center", 68 | height: "6rem", 69 | aspectRatio: 1, 70 | color: colors.blue7, 71 | backgroundColor: { 72 | default: colors.gray3, 73 | ":hover": colors.gray4, 74 | [DARK]: { 75 | default: colors.gray9, 76 | ":hover": colors.gray8, 77 | }, 78 | }, 79 | borderWidth: 0, 80 | borderStyle: "none", 81 | borderRadius: spacing.xs, 82 | padding: spacing.xs, 83 | margin: spacing.xs, 84 | cursor: "pointer", 85 | fontSize: text.h2, 86 | transform: { 87 | default: null, 88 | ":hover": "scale(1.025)", 89 | ":active": "scale(0.975)", 90 | }, 91 | }, 92 | count: { 93 | fontSize: text.h2, 94 | fontWeight: 100, 95 | color: colors.lime7, 96 | minWidth: "6rem", 97 | textAlign: "center", 98 | fontFamily: $.fontMono, 99 | }, 100 | largeNumber: { 101 | fontSize: text.h3, 102 | }, 103 | }); 104 | -------------------------------------------------------------------------------- /app/Card.tsx: -------------------------------------------------------------------------------- 1 | import * as stylex from "@stylexjs/stylex"; 2 | import { globalTokens as $, spacing, text } from "./globalTokens.stylex"; 3 | import { colors } from "@stylexjs/open-props/lib/colors.stylex"; 4 | import { tokens } from "./CardTokens.stylex"; 5 | 6 | type Props = Readonly<{ 7 | title: string; 8 | body: string; 9 | href: string; 10 | }>; 11 | 12 | export default function Card({ title, body, href }: Props) { 13 | return ( 14 | 20 |

      21 | {title} 22 |

      23 |

      {body}

      24 |
      25 | ); 26 | } 27 | 28 | type TMobile = "@media (max-width: 700px)"; 29 | 30 | const MOBILE = "@media (max-width: 700px)" satisfies TMobile; 31 | const REDUCE_MOTION = "@media (prefers-reduced-motion: reduce)"; 32 | 33 | const cardBgTransparent = `rgba(${$.cardR}, ${$.cardG}, ${$.cardB}, 0)`; 34 | const CardBgTranslucent = `rgba(${$.cardR}, ${$.cardG}, ${$.cardB}, 0.1)`; 35 | const cardBorderTransparent = `rgba(${$.cardBorderR}, ${$.cardBorderG}, ${$.cardBorderB}, 0)`; 36 | const cardBorderHover = `rgba(${$.cardBorderR}, ${$.cardBorderG}, ${$.cardBorderB}, 0.1)`; 37 | 38 | const styles = stylex.create({ 39 | link: { 40 | display: { 41 | default: "flex", 42 | [MOBILE]: "block", 43 | }, 44 | alignItems: "center", 45 | justifyContent: "flex-start", 46 | flexDirection: "column", 47 | borderRadius: spacing.xs, 48 | backgroundColor: { 49 | default: cardBgTransparent, 50 | ":hover": CardBgTranslucent, 51 | }, 52 | borderWidth: 1, 53 | borderStyle: "solid", 54 | borderColor: { 55 | default: cardBorderTransparent, 56 | ":hover": cardBorderHover, 57 | }, 58 | color: "inherit", 59 | fontFamily: $.fontSans, 60 | padding: spacing.sm, 61 | transitionProperty: "background-color, border-color", 62 | transitionDuration: "400ms", 63 | [tokens.arrowTransform]: { 64 | // eslint-disable-next-line @stylexjs/valid-styles 65 | default: "translateX(0)", 66 | // eslint-disable-next-line @stylexjs/valid-styles 67 | ":hover": "translateX(4px)", 68 | }, 69 | textAlign: "center", 70 | textDecoration: "none", 71 | }, 72 | h2: { 73 | color: colors.blue3, 74 | fontSize: text.h4, 75 | fontWeight: 600, 76 | marginBottom: { 77 | default: spacing.xs, 78 | [MOBILE]: spacing.xxs, 79 | }, 80 | }, 81 | span: { 82 | display: "inline-block", 83 | transitionProperty: "transform", 84 | transitionDuration: { 85 | default: "200ms", 86 | [REDUCE_MOTION]: "0s", 87 | }, 88 | transform: tokens.arrowTransform, 89 | }, 90 | p: { 91 | margin: 0, 92 | opacity: 0.6, 93 | fontSize: text.p, 94 | textWrap: "balance", 95 | lineHeight: 1.5, 96 | maxWidth: "30ch", 97 | }, 98 | dynamic: (color: string) => ({ 99 | color: color, 100 | }), 101 | }); 102 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import * as stylex from "@stylexjs/stylex"; 2 | import Card from "./Card"; 3 | import { globalTokens as $, spacing, text } from "./globalTokens.stylex"; 4 | import ButtonsDemo from "./components/ButtonsDemo"; 5 | import Counter from "./components/Counter"; 6 | 7 | const HOMEPAGE = "http://stylex-docusaurus.vercel.app"; 8 | 9 | export default function Home() { 10 | return ( 11 |
      12 |
      13 |

      14 | Get started by editing  15 | app/page.tsx 16 |

      17 |
      18 |
      19 |

      20 | Next.js App Dir♥️️StyleX 21 |

      22 | 23 |
      24 | 25 | 26 |
      27 | 32 | 37 | 42 | 47 |
      48 |
      49 | ); 50 | } 51 | 52 | const MEDIA_MOBILE: "@media (max-width: 700px)" = "@media (max-width: 700px)"; 53 | const MEDIA_TABLET: "@media (min-width: 701px) and (max-width: 1120px)" = 54 | "@media (min-width: 701px) and (max-width: 1120px)"; 55 | 56 | const bgImage = `linear-gradient(to bottom, ${$.bgStartRGB}, ${$.calloutRGB50})`; 57 | const xBorderColor = `rgba(${$.calloutBorderR}, ${$.calloutBorderG}, ${$.calloutBorderB}, 0.3)`; 58 | const xBorderBottomColor = `rgba(${$.calloutBorderR}, ${$.calloutBorderG}, ${$.calloutBorderB}, 0.25)`; 59 | 60 | const s = stylex.create({ 61 | main: { 62 | display: "flex", 63 | flexDirection: "column", 64 | alignItems: "center", 65 | justifyContent: "space-between", 66 | minHeight: "100vh", 67 | paddingTop: spacing.xxl, 68 | paddingBottom: { 69 | default: spacing.xxl, 70 | [MEDIA_MOBILE]: spacing.md, 71 | }, 72 | }, 73 | hero: { 74 | flexGrow: 1, 75 | display: "flex", 76 | alignItems: "center", 77 | justifyContent: "center", 78 | flexDirection: "column", 79 | gap: spacing.xl, 80 | }, 81 | h1: { 82 | fontSize: text.h1, 83 | lineHeight: 1, 84 | fontFamily: $.fontSans, 85 | fontWeight: 400, 86 | textAlign: "center", 87 | display: "flex", 88 | gap: spacing.md, 89 | whiteSpace: "nowrap", 90 | flexDirection: { 91 | default: "row", 92 | [MEDIA_MOBILE]: "column", 93 | }, 94 | }, 95 | emoji: { 96 | position: "relative", 97 | fontFamily: "sans-serif", 98 | top: { 99 | default: 0, 100 | [MEDIA_MOBILE]: spacing.xxxs, 101 | }, 102 | }, 103 | description: { 104 | display: "inherit", 105 | justifyContent: "inherit", 106 | alignItems: "inherit", 107 | fontSize: text.sm, 108 | maxWidth: $.maxWidth, 109 | width: "100%", 110 | zIndex: 2, 111 | fontFamily: $.fontMono, 112 | }, 113 | descLink: { 114 | display: "flex", 115 | alignItems: "center", 116 | justifyContent: "center", 117 | gap: spacing.xxs, 118 | padding: { [MEDIA_MOBILE]: spacing.sm }, 119 | }, 120 | descP: { 121 | display: { [MEDIA_MOBILE]: "flex" }, 122 | position: { 123 | default: "relative", 124 | [MEDIA_MOBILE]: "fixed", 125 | }, 126 | justifyContent: { [MEDIA_MOBILE]: "center" }, 127 | alignItems: { [MEDIA_MOBILE]: "center" }, 128 | width: { [MEDIA_MOBILE]: "100%" }, 129 | margin: 0, 130 | paddingInline: spacing.sm, 131 | paddingTop: { 132 | default: spacing.sm, 133 | [MEDIA_MOBILE]: spacing.lg, 134 | }, 135 | paddingBottom: { 136 | default: spacing.sm, 137 | [MEDIA_MOBILE]: spacing.md, 138 | }, 139 | backgroundColor: $.calloutRGB50, 140 | backgroundImage: { 141 | default: null, 142 | [MEDIA_MOBILE]: bgImage, 143 | }, 144 | borderWidth: { 145 | default: "1px", 146 | [MEDIA_MOBILE]: "0", 147 | }, 148 | borderStyle: "solid", 149 | borderColor: xBorderColor, 150 | borderBottomColor: { 151 | default: null, 152 | [MEDIA_MOBILE]: xBorderBottomColor, 153 | }, 154 | borderRadius: { 155 | default: spacing.xs, 156 | [MEDIA_MOBILE]: 0, 157 | }, 158 | inset: { [MEDIA_MOBILE]: "0 0 auto" }, 159 | }, 160 | code: { 161 | fontWeight: 700, 162 | fontFamily: $.fontMono, 163 | }, 164 | grid: { 165 | display: "grid", 166 | gridTemplateColumns: { 167 | default: "repeat(4, minmax(25%, auto))", 168 | [MEDIA_MOBILE]: "1fr", 169 | [MEDIA_TABLET]: "repeat(2, 50%)", 170 | }, 171 | width: $.maxWidth, 172 | maxWidth: { 173 | default: "100%", 174 | [MEDIA_MOBILE]: 320, 175 | }, 176 | textAlign: { [MEDIA_MOBILE]: "center" }, 177 | }, 178 | }); 179 | -------------------------------------------------------------------------------- /app/globalTokens.stylex.ts: -------------------------------------------------------------------------------- 1 | import stylex from "@stylexjs/stylex"; 2 | 3 | /** 4 | * o--o o o o o-O-o o-o o--o o-o o o o-O-o o-o 5 | * | | | | | | \ | o o |\ | | | 6 | * O-o | | | | | O O-o | | | \ | | o-o 7 | * | | | | | | / | o o | \| | | 8 | * o O---o o-o o-O-o o-o o o-o o o o o--o 9 | * 10 | * Reference: https://utopia.fyi/type/calculator 11 | * 12 | * The following constants are used to calculate fluid typography. 13 | * Feel free to change these initial numbers to suit your needs. 14 | * 15 | * StyleX can compute all of this at compile time as all the information 16 | * is statically available in the same file and the only functions used are 17 | * the Math.pow and Math.round functions. 18 | * 19 | * NOTE: Any custom functions will not be able to be computed at compile time. 20 | */ 21 | const MIN_WIDTH = 320; 22 | const MAX_WIDTH = 1240; 23 | const MIN_SCALE = 1.2; 24 | const MAX_SCALE = 1.333; 25 | const MIN_BASE_SIZE = 16; 26 | const MAX_BASE_SIZE = 20; 27 | 28 | // Font sizes in `rem` units 29 | const MIN_FONT = { 30 | xxs: Math.round(MIN_BASE_SIZE / Math.pow(MIN_SCALE, 3) / 0.16) / 100, 31 | xs: Math.round(MIN_BASE_SIZE / Math.pow(MIN_SCALE, 2) / 0.16) / 100, 32 | sm: Math.round(MIN_BASE_SIZE / MIN_SCALE / 0.16) / 100, 33 | p: Math.round(MIN_BASE_SIZE / 4) / 4, 34 | h5: Math.round((MIN_BASE_SIZE * MIN_SCALE) / 0.16) / 100, 35 | h4: Math.round((MIN_BASE_SIZE * Math.pow(MIN_SCALE, 2)) / 0.16) / 100, 36 | h3: Math.round((MIN_BASE_SIZE * Math.pow(MIN_SCALE, 3)) / 0.16) / 100, 37 | h2: Math.round((MIN_BASE_SIZE * Math.pow(MIN_SCALE, 4)) / 0.16) / 100, 38 | h1: Math.round((MIN_BASE_SIZE * Math.pow(MIN_SCALE, 5)) / 0.16) / 100, 39 | }; 40 | // Font sizes in `rem` units 41 | const MAX_FONT = { 42 | xxs: Math.round(MAX_BASE_SIZE / Math.pow(MAX_SCALE, 3) / 0.16) / 100, 43 | xs: Math.round(MAX_BASE_SIZE / Math.pow(MAX_SCALE, 2) / 0.16) / 100, 44 | sm: Math.round(MAX_BASE_SIZE / MAX_SCALE / 0.16) / 100, 45 | p: Math.round(MAX_BASE_SIZE / 4) / 4, 46 | h5: Math.round((MAX_BASE_SIZE * MAX_SCALE) / 0.16) / 100, 47 | h4: Math.round((MAX_BASE_SIZE * Math.pow(MAX_SCALE, 2)) / 0.16) / 100, 48 | h3: Math.round((MAX_BASE_SIZE * Math.pow(MAX_SCALE, 3)) / 0.16) / 100, 49 | h2: Math.round((MAX_BASE_SIZE * Math.pow(MAX_SCALE, 4)) / 0.16) / 100, 50 | h1: Math.round((MAX_BASE_SIZE * Math.pow(MAX_SCALE, 5)) / 0.16) / 100, 51 | }; 52 | const SLOPE = { 53 | xxs: (16 * (MAX_FONT.xxs - MIN_FONT.xxs)) / (MAX_WIDTH - MIN_WIDTH), 54 | xs: (16 * (MAX_FONT.xs - MIN_FONT.xs)) / (MAX_WIDTH - MIN_WIDTH), 55 | sm: (16 * (MAX_FONT.sm - MIN_FONT.sm)) / (MAX_WIDTH - MIN_WIDTH), 56 | p: (16 * (MAX_FONT.p - MIN_FONT.p)) / (MAX_WIDTH - MIN_WIDTH), 57 | h5: (16 * (MAX_FONT.h5 - MIN_FONT.h5)) / (MAX_WIDTH - MIN_WIDTH), 58 | h4: (16 * (MAX_FONT.h4 - MIN_FONT.h4)) / (MAX_WIDTH - MIN_WIDTH), 59 | h3: (16 * (MAX_FONT.h3 - MIN_FONT.h3)) / (MAX_WIDTH - MIN_WIDTH), 60 | h2: (16 * (MAX_FONT.h2 - MIN_FONT.h2)) / (MAX_WIDTH - MIN_WIDTH), 61 | h1: (16 * (MAX_FONT.h1 - MIN_FONT.h1)) / (MAX_WIDTH - MIN_WIDTH), 62 | }; 63 | const INTERCEPT = { 64 | xxs: Math.round(100 * (MIN_FONT.xxs - SLOPE.xxs * (MIN_WIDTH / 16))) / 100, 65 | xs: Math.round(100 * (MIN_FONT.xs - SLOPE.xs * (MIN_WIDTH / 16))) / 100, 66 | sm: Math.round(100 * (MIN_FONT.sm - SLOPE.sm * (MIN_WIDTH / 16))) / 100, 67 | p: Math.round(100 * (MIN_FONT.p - SLOPE.p * (MIN_WIDTH / 16))) / 100, 68 | h5: Math.round(100 * (MIN_FONT.h5 - SLOPE.h5 * (MIN_WIDTH / 16))) / 100, 69 | h4: Math.round(100 * (MIN_FONT.h4 - SLOPE.h4 * (MIN_WIDTH / 16))) / 100, 70 | h3: Math.round(100 * (MIN_FONT.h3 - SLOPE.h3 * (MIN_WIDTH / 16))) / 100, 71 | h2: Math.round(100 * (MIN_FONT.h2 - SLOPE.h2 * (MIN_WIDTH / 16))) / 100, 72 | h1: Math.round(100 * (MIN_FONT.h1 - SLOPE.h1 * (MIN_WIDTH / 16))) / 100, 73 | }; 74 | 75 | // prettier-ignore 76 | export const text = stylex.defineVars({ 77 | xxs: `clamp(${ Math.min(MIN_FONT.xxs) }rem, calc(${ INTERCEPT.xxs }rem + ${ Math.round(10000 * SLOPE.xxs) / 100 }vw), ${ Math.max(MAX_FONT.xxs) }rem)`, 78 | xs: `clamp(${ Math.min(MIN_FONT.xs ) }rem, calc(${ INTERCEPT.xs }rem + ${ Math.round(10000 * SLOPE.xs ) / 100 }vw), ${ Math.max(MAX_FONT.xs ) }rem)`, 79 | sm: `clamp(${ Math.min(MIN_FONT.sm ) }rem, calc(${ INTERCEPT.sm }rem + ${ Math.round(10000 * SLOPE.sm ) / 100 }vw), ${ Math.max(MAX_FONT.sm ) }rem)`, 80 | p: `clamp(${ Math.min(MIN_FONT.p ) }rem, calc(${ INTERCEPT.p }rem + ${ Math.round(10000 * SLOPE.p ) / 100 }vw), ${ Math.max(MAX_FONT.p ) }rem)`, 81 | h5: `clamp(${ Math.min(MIN_FONT.h5 ) }rem, calc(${ INTERCEPT.h5 }rem + ${ Math.round(10000 * SLOPE.h5 ) / 100 }vw), ${ Math.max(MAX_FONT.h5 ) }rem)`, 82 | h4: `clamp(${ Math.min(MIN_FONT.h4 ) }rem, calc(${ INTERCEPT.h4 }rem + ${ Math.round(10000 * SLOPE.h4 ) / 100 }vw), ${ Math.max(MAX_FONT.h4 ) }rem)`, 83 | h3: `clamp(${ Math.min(MIN_FONT.h3 ) }rem, calc(${ INTERCEPT.h3 }rem + ${ Math.round(10000 * SLOPE.h3 ) / 100 }vw), ${ Math.max(MAX_FONT.h3 ) }rem)`, 84 | h2: `clamp(${ Math.min(MIN_FONT.h2 ) }rem, calc(${ INTERCEPT.h2 }rem + ${ Math.round(10000 * SLOPE.h2 ) / 100 }vw), ${ Math.max(MAX_FONT.h2 ) }rem)`, 85 | h1: `clamp(${ Math.min(MIN_FONT.h1 ) }rem, calc(${ INTERCEPT.h1 }rem + ${ Math.round(10000 * SLOPE.h1 ) / 100 }vw), ${ Math.max(MAX_FONT.h1 ) }rem)`, 86 | }); 87 | 88 | /** 89 | * o--o o o o o-O-o o-o o-o o--o O o-o o--o 90 | * | | | | | | \ | | | / \ / | 91 | * O-o | | | | | O o-o O--o o---oO O-o 92 | * | | | | | | / | | | | \ | 93 | * o O---o o-o o-O-o o-o o--o o o o o-o o--o 94 | * 95 | * Reference: https://utopia.fyi/space/calculator 96 | * 97 | * Similar to the fluid typography, we can create fluid values for spacing. 98 | * Using similar formulas and similar scales. 99 | * 100 | * NOTE: It is common to have more varied needs for spacing than for font-size. 101 | * So feel free to add some more values by following the pattern below. 102 | * 103 | * EXCEPT: We are using `px` instead of `rem` 104 | * ------------------------------------------ 105 | * When talking about font-size, it is the best practice to use 106 | * `rem` so that an end user can change the font-size using the 107 | * browser's font-size setting. 108 | * 109 | * However, when talking about spacing, it is the best practice to 110 | * use `px` because using `rems` here makes font-size behave like zoom. 111 | * 112 | * Users that prefer larger text, don't neccessarily want larger spacing as well. 113 | * 114 | */ 115 | 116 | const MULT = { 117 | xxxs: 0.25, 118 | xxs: 0.5, 119 | xs: 0.75, 120 | sm: 1, 121 | md: 1.5, 122 | lg: 2, 123 | xl: 3, 124 | xxl: 4, 125 | xxxl: 6, 126 | xxxxl: 8, 127 | }; 128 | const MIN_SPACE = { 129 | xxxs: MULT.xxxs * MIN_BASE_SIZE, 130 | xxs: MULT.xxs * MIN_BASE_SIZE, 131 | xs: MULT.xs * MIN_BASE_SIZE, 132 | sm: MULT.sm * MIN_BASE_SIZE, 133 | md: MULT.md * MIN_BASE_SIZE, 134 | lg: MULT.lg * MIN_BASE_SIZE, 135 | xl: MULT.xl * MIN_BASE_SIZE, 136 | xxl: MULT.xxl * MIN_BASE_SIZE, 137 | xxxl: MULT.xxxl * MIN_BASE_SIZE, 138 | xxxxl: MULT.xxxxl * MIN_BASE_SIZE, 139 | }; 140 | const MAX_SPACE = { 141 | xxxs: MULT.xxxs * MAX_BASE_SIZE, 142 | xxs: MULT.xxs * MAX_BASE_SIZE, 143 | xs: MULT.xs * MAX_BASE_SIZE, 144 | sm: MULT.sm * MAX_BASE_SIZE, 145 | md: MULT.md * MAX_BASE_SIZE, 146 | lg: MULT.lg * MAX_BASE_SIZE, 147 | xl: MULT.xl * MAX_BASE_SIZE, 148 | xxl: MULT.xxl * MAX_BASE_SIZE, 149 | xxxl: MULT.xxxl * MAX_BASE_SIZE, 150 | xxxxl: MULT.xxxxl * MAX_BASE_SIZE, 151 | }; 152 | const SLOPE_SPACE = { 153 | xxxs: (MAX_SPACE.xxxs - MIN_SPACE.xxxs) / (MAX_WIDTH - MIN_WIDTH), 154 | xxs: (MAX_SPACE.xxs - MIN_SPACE.xxs) / (MAX_WIDTH - MIN_WIDTH), 155 | xs: (MAX_SPACE.xs - MIN_SPACE.xs) / (MAX_WIDTH - MIN_WIDTH), 156 | sm: (MAX_SPACE.sm - MIN_SPACE.sm) / (MAX_WIDTH - MIN_WIDTH), 157 | md: (MAX_SPACE.md - MIN_SPACE.md) / (MAX_WIDTH - MIN_WIDTH), 158 | lg: (MAX_SPACE.lg - MIN_SPACE.lg) / (MAX_WIDTH - MIN_WIDTH), 159 | xl: (MAX_SPACE.xl - MIN_SPACE.xl) / (MAX_WIDTH - MIN_WIDTH), 160 | xxl: (MAX_SPACE.xxl - MIN_SPACE.xxl) / (MAX_WIDTH - MIN_WIDTH), 161 | xxxl: (MAX_SPACE.xxxl - MIN_SPACE.xxxl) / (MAX_WIDTH - MIN_WIDTH), 162 | xxxxl: (MAX_SPACE.xxxxl - MIN_SPACE.xxxxl) / (MAX_WIDTH - MIN_WIDTH), 163 | }; 164 | // rounded to the nearest 0.25px 165 | const INTERCEPT_SPACE = { 166 | xxxs: Math.round(4 * (MIN_SPACE.xxxs - SLOPE_SPACE.xxxs * MIN_WIDTH)) / 4, 167 | xxs: Math.round(4 * (MIN_SPACE.xxs - SLOPE_SPACE.xxs * MIN_WIDTH)) / 4, 168 | xs: Math.round(4 * (MIN_SPACE.xs - SLOPE_SPACE.xs * MIN_WIDTH)) / 4, 169 | sm: Math.round(4 * (MIN_SPACE.sm - SLOPE_SPACE.sm * MIN_WIDTH)) / 4, 170 | md: Math.round(4 * (MIN_SPACE.md - SLOPE_SPACE.md * MIN_WIDTH)) / 4, 171 | lg: Math.round(4 * (MIN_SPACE.lg - SLOPE_SPACE.lg * MIN_WIDTH)) / 4, 172 | xl: Math.round(4 * (MIN_SPACE.xl - SLOPE_SPACE.xl * MIN_WIDTH)) / 4, 173 | xxl: Math.round(4 * (MIN_SPACE.xxl - SLOPE_SPACE.xxl * MIN_WIDTH)) / 4, 174 | xxxl: Math.round(4 * (MIN_SPACE.xxxl - SLOPE_SPACE.xxxl * MIN_WIDTH)) / 4, 175 | xxxxl: Math.round(4 * (MIN_SPACE.xxxxl - SLOPE_SPACE.xxxxl * MIN_WIDTH)) / 4, 176 | }; 177 | 178 | // prettier-ignore 179 | export const spacing = stylex.defineVars({ 180 | xxxs: `clamp(${MIN_SPACE.xxxs }px, calc(${INTERCEPT_SPACE.xxxs }px + ${ Math.round(10000 * SLOPE_SPACE.xxxs ) / 100 }vw), ${MAX_SPACE.xxxs }px)`, 181 | xxs: `clamp(${MIN_SPACE.xxs }px, calc(${INTERCEPT_SPACE.xxs }px + ${ Math.round(10000 * SLOPE_SPACE.xxs ) / 100 }vw), ${MAX_SPACE.xxs }px)`, 182 | xs: `clamp(${MIN_SPACE.xs }px, calc(${INTERCEPT_SPACE.xs }px + ${ Math.round(10000 * SLOPE_SPACE.xs ) / 100 }vw), ${MAX_SPACE.xs }px)`, 183 | sm: `clamp(${MIN_SPACE.sm }px, calc(${INTERCEPT_SPACE.sm }px + ${ Math.round(10000 * SLOPE_SPACE.sm ) / 100 }vw), ${MAX_SPACE.sm }px)`, 184 | md: `clamp(${MIN_SPACE.md }px, calc(${INTERCEPT_SPACE.md }px + ${ Math.round(10000 * SLOPE_SPACE.md ) / 100 }vw), ${MAX_SPACE.md }px)`, 185 | lg: `clamp(${MIN_SPACE.lg }px, calc(${INTERCEPT_SPACE.lg }px + ${ Math.round(10000 * SLOPE_SPACE.lg ) / 100 }vw), ${MAX_SPACE.lg }px)`, 186 | xl: `clamp(${MIN_SPACE.xl }px, calc(${INTERCEPT_SPACE.xl }px + ${ Math.round(10000 * SLOPE_SPACE.xl ) / 100 }vw), ${MAX_SPACE.xl }px)`, 187 | xxl: `clamp(${MIN_SPACE.xxl }px, calc(${INTERCEPT_SPACE.xxl }px + ${ Math.round(10000 * SLOPE_SPACE.xxl ) / 100 }vw), ${MAX_SPACE.xxl }px)`, 188 | xxxl: `clamp(${MIN_SPACE.xxxl }px, calc(${INTERCEPT_SPACE.xxxl }px + ${ Math.round(10000 * SLOPE_SPACE.xxxl ) / 100 }vw), ${MAX_SPACE.xxxl }px)`, 189 | xxxxl: `clamp(${MIN_SPACE.xxxxl }px, calc(${INTERCEPT_SPACE.xxxxl }px + ${ Math.round(10000 * SLOPE_SPACE.xxxxl ) / 100 }vw), ${MAX_SPACE.xxxxl }px)`, 190 | }); 191 | 192 | /** 193 | * Color Tokens 194 | */ 195 | const DARK_MODE = "@media (prefers-color-scheme: dark)"; 196 | 197 | export const globalTokens = stylex.defineVars({ 198 | maxWidth: `${MAX_WIDTH}px`, 199 | fontMono: [ 200 | "ui-monospace", 201 | "Menlo", 202 | "Monaco", 203 | '"Cascadia Mono"', 204 | '"Segoe UI Mono"', 205 | '"Roboto Mono"', 206 | '"Oxygen Mono"', 207 | '"Ubuntu Monospace"', 208 | '"Source Code Pro"', 209 | '"Fira Mono"', 210 | '"Droid Sans Mono"', 211 | '"Courier New"', 212 | "monospace", 213 | ].join(", "), 214 | fontSans: [ 215 | "--apple-system", 216 | "BlinkMacSystemFont", 217 | '"Segoe UI"', 218 | "Roboto", 219 | '"Helvetica Neue"', 220 | "Arial", 221 | '"Noto Sans"', 222 | "sans-serif", 223 | '"Apple Color Emoji"', 224 | '"Segoe UI Emoji"', 225 | '"Segoe UI Symbol"', 226 | '"Noto Color Emoji"', 227 | ].join(", "), 228 | 229 | foregroundR: { default: "0", [DARK_MODE]: "255" }, 230 | foregroundG: { default: "0", [DARK_MODE]: "255" }, 231 | foregroundB: { default: "0", [DARK_MODE]: "255" }, 232 | 233 | bgStartRGB: { default: "rgb(214, 219, 220)", [DARK_MODE]: "rgb(0, 0, 0)" }, 234 | 235 | bgEndR: { default: "255", [DARK_MODE]: "0" }, 236 | bgEndG: { default: "255", [DARK_MODE]: "0" }, 237 | bgEndB: { default: "255", [DARK_MODE]: "0" }, 238 | 239 | calloutRGB: { default: "rgb(238, 240, 241)", [DARK_MODE]: "rgb(20, 20, 20)" }, 240 | calloutRGB50: { 241 | default: "rgba(238, 240, 241, 0.5)", 242 | [DARK_MODE]: "rgba(20, 20, 20, 0.5)", 243 | }, 244 | 245 | calloutBorderR: { default: "172", [DARK_MODE]: "108" }, 246 | calloutBorderG: { default: "175", [DARK_MODE]: "108" }, 247 | calloutBorderB: { default: "176", [DARK_MODE]: "108" }, 248 | 249 | cardR: { default: "180", [DARK_MODE]: "100" }, 250 | cardG: { default: "185", [DARK_MODE]: "100" }, 251 | cardB: { default: "188", [DARK_MODE]: "100" }, 252 | 253 | cardBorderR: { default: "131", [DARK_MODE]: "200" }, 254 | cardBorderG: { default: "134", [DARK_MODE]: "200" }, 255 | cardBorderB: { default: "135", [DARK_MODE]: "200" }, 256 | 257 | primaryGlow: { 258 | default: 259 | "conic-gradient(from 180deg at 50% 50%, #16abff33 0deg, #0885ff33 55deg, #54d6ff33 120deg, #0071ff33 160deg, transparent 360deg)", 260 | [DARK_MODE]: "radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0))", 261 | }, 262 | secondaryGlow: { 263 | default: "radial-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0))", 264 | [DARK_MODE]: `linear-gradient(to bottom right, rgba(1, 65, 255, 0), rgba(1, 65, 255, 0), rgba(1, 65, 255, 0.3))`, 265 | }, 266 | }); 267 | --------------------------------------------------------------------------------