├── .eslintrc.cjs ├── .gitignore ├── Dockerfile ├── README.md ├── app ├── clients │ ├── api.ts │ └── plausible.ts ├── components │ ├── analytics-script.tsx │ ├── colors-chart.client.tsx │ ├── dynamic-styles.tsx │ ├── extension-error-boundary.tsx │ ├── footer.tsx │ ├── github-link.tsx │ ├── global-loading.tsx │ ├── header.tsx │ ├── language-menu.tsx │ ├── search-input.tsx │ ├── search-pagination.tsx │ ├── search-results-item.tsx │ ├── search-results.tsx │ ├── sort-menu.tsx │ ├── theme-menu.tsx │ ├── ui │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── chart.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── select.tsx │ │ ├── sheet.tsx │ │ ├── tabs.tsx │ │ └── tooltip.tsx │ ├── user-theme-script.tsx │ └── vsct-icon.tsx ├── data.ts ├── entry.client.tsx ├── entry.server.tsx ├── global.d.ts ├── lib │ ├── search-params.ts │ ├── theme-variables.ts │ ├── use-universal-layout-effect.ts │ └── utils.ts ├── root.tsx ├── routes │ ├── _index.tsx │ ├── colors.tsx │ ├── dark.tsx │ ├── e.$slug.$theme.open.tsx │ ├── e.$slug.$theme.tsx │ ├── e.$slug.$theme[.png].tsx │ ├── e.$slug._index.tsx │ ├── health.tsx │ ├── light.tsx │ ├── plausible.tsx │ ├── railway.tsx │ └── sitemap[.xml].tsx ├── sessions.ts └── tailwind.css ├── components.json ├── entrypoint.sh ├── fonts ├── Fira_Code │ ├── OFL.txt │ ├── README.txt │ └── static │ │ └── FiraCode-Regular.ttf └── Inter │ ├── OFL.txt │ ├── README.txt │ └── static │ └── Inter_18pt-Regular.ttf ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── banner.png ├── favicon.ico ├── logo-dark.jpg ├── logo-light.jpg ├── logo.png ├── logo.svg ├── robots.txt └── thumbnail.jpg ├── railway.toml ├── tailwind.config.ts ├── tsconfig.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is intended to be a basic starting point for linting in your app. 3 | * It relies on recommended configs out of the box for simplicity, but you can 4 | * and should modify this configuration to best suit your team's needs. 5 | */ 6 | 7 | /** @type {import('eslint').Linter.Config} */ 8 | module.exports = { 9 | root: true, 10 | parserOptions: { 11 | ecmaVersion: "latest", 12 | sourceType: "module", 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | }, 17 | env: { 18 | browser: true, 19 | commonjs: true, 20 | es6: true, 21 | }, 22 | ignorePatterns: ["!**/.server", "!**/.client"], 23 | 24 | // Base config 25 | extends: ["eslint:recommended"], 26 | 27 | overrides: [ 28 | // React 29 | { 30 | files: ["**/*.{js,jsx,ts,tsx}"], 31 | plugins: ["react", "jsx-a11y"], 32 | extends: [ 33 | "plugin:react/recommended", 34 | "plugin:react/jsx-runtime", 35 | "plugin:react-hooks/recommended", 36 | "plugin:jsx-a11y/recommended", 37 | ], 38 | settings: { 39 | react: { 40 | version: "detect", 41 | }, 42 | formComponents: ["Form"], 43 | linkComponents: [ 44 | { name: "Link", linkAttribute: "to" }, 45 | { name: "NavLink", linkAttribute: "to" }, 46 | ], 47 | "import/resolver": { 48 | typescript: {}, 49 | }, 50 | }, 51 | }, 52 | 53 | // Typescript 54 | { 55 | files: ["**/*.{ts,tsx}"], 56 | plugins: ["@typescript-eslint", "import"], 57 | parser: "@typescript-eslint/parser", 58 | settings: { 59 | "import/internal-regex": "^~/", 60 | "import/resolver": { 61 | node: { 62 | extensions: [".ts", ".tsx"], 63 | }, 64 | typescript: { 65 | alwaysTryTypes: true, 66 | }, 67 | }, 68 | }, 69 | extends: [ 70 | "plugin:@typescript-eslint/recommended", 71 | "plugin:import/recommended", 72 | "plugin:import/typescript", 73 | ], 74 | }, 75 | 76 | // Node 77 | { 78 | files: [".eslintrc.cjs"], 79 | env: { 80 | node: true, 81 | }, 82 | }, 83 | ], 84 | }; 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | .env 6 | .envrc 7 | *.log -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build stage 2 | 3 | FROM node:22.9.0-alpine3.20 AS builder 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | 9 | RUN npm install 10 | 11 | RUN npm run build 12 | 13 | # Run stage 14 | 15 | FROM node:22.9.0-alpine3.20 AS run 16 | 17 | WORKDIR /app 18 | 19 | ENV NODE_ENV=production 20 | 21 | COPY package.json . 22 | COPY package-lock.json . 23 | COPY entrypoint.sh / 24 | COPY fonts ./fonts 25 | COPY --from=builder /app/build /app/build 26 | 27 | RUN npm install --production 28 | 29 | ENTRYPOINT [ "/entrypoint.sh" ] 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![vscodethemes](public/banner.png)](https://vscodethemes.com) 2 | 3 | Search and preview themes from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/search?target=VSCode&category=Themes&sortBy=Installs). Built with [Remix](https://remix.run/), [shadcn](https://ui.shadcn.com/) and hosted with [Railway](https://railway.app/). 4 | 5 | Aanlytics powered by [Plausible CE](https://plausible.io/docs/self-hosting) and available at [vscodethemes.com/plausible](https://vscodethemes.com/plausible). 6 | 7 | ## How it Works 8 | 9 | VS Code Themes scans the Visual Studio Marketplace and maintains a searchable database of themes. 10 | 11 | In order for a theme to show up on the website: 12 | 13 | 1. A description must exist in the [extension's manifest](https://code.visualstudio.com/api/references/extension-manifest) 14 | 2. Themes must be `.json` (`.tmTheme` files are not supported) 15 | 16 | Missing a theme? [Open an issue](https://github.com/vscodethemes/web/issues/new). 17 | 18 | 19 | ## Creating a Theme 20 | 21 | Here are some helpful links if you'd like to create your own theme: 22 | 23 | - [Color Themes, VS Code docs](https://code.visualstudio.com/docs/getstarted/themes) 24 | - [Creating a VS Code Theme by Sarah Drasner, CSS-Tricks](https://css-tricks.com/creating-a-vs-code-theme/) 25 | 26 | ## History 27 | 28 | Helping you discover new themes since 2018: 29 | 30 | - **Version 1**: February 2018 ([Reddit](https://www.reddit.com/r/vscode/comments/7y79e4/preview_vscode_themes_before_installing_them/)) 31 | - **Version 2**: July 2018 ([Medium](https://hackernoon.com/announcing-vscodethemes-4544f50c2b5b)) 32 | - **Version 3**: April 2022 ([Github](https://github.com/vscodethemes/web/pull/228)) 33 | - **Version 4**: October 2024 ([Github](https://github.com/vscodethemes/web/pull/284)) 34 | -------------------------------------------------------------------------------- /app/clients/api.ts: -------------------------------------------------------------------------------- 1 | export interface SearchResults { 2 | total: number; 3 | extensions: Extension[]; 4 | } 5 | 6 | export interface Extension { 7 | name: string; 8 | displayName: string; 9 | publisherName: string; 10 | publisherDisplayName: string; 11 | shortDescription: string; 12 | totalThemes: number; 13 | themes: ThemePartial[]; 14 | theme?: Theme; 15 | } 16 | 17 | export interface Theme { 18 | url: string; 19 | name: string; 20 | displayName: string; 21 | editorBackground: string; 22 | editorForeground: string; 23 | activityBarBackground: string; 24 | activityBarForeground: string; 25 | activityBarInActiveForeground: string; 26 | activityBarBorder: string | null; 27 | activityBarActiveBorder: string; 28 | activityBarActiveBackground: string | null; 29 | activityBarBadgeBackground: string; 30 | activityBarBadgeForeground: string; 31 | tabsContainerBackground: string | null; 32 | tabsContainerBorder: string | null; 33 | statusBarBackground: string | null; 34 | statusBarForeground: string; 35 | statusBarBorder: string | null; 36 | tabActiveBackground: string | null; 37 | tabInactiveBackground: string | null; 38 | tabActiveForeground: string; 39 | tabBorder: string; 40 | tabActiveBorder: string | null; 41 | tabActiveBorderTop: string | null; 42 | titleBarActiveBackground: string; 43 | titleBarActiveForeground: string; 44 | titleBarBorder: string | null; 45 | } 46 | 47 | export interface ThemePartial { 48 | url: string; 49 | name: string; 50 | displayName: string; 51 | editorBackground: string; 52 | activityBarBadgeBackground: string; 53 | } 54 | 55 | export interface SearchExtensionsInput { 56 | text?: string; 57 | editorBackground?: string; 58 | language?: string; 59 | sortBy?: 60 | | "relevance" 61 | | "installs" 62 | | "trendingDaily" 63 | | "trendingWeekly" 64 | | "trendingMonthly" 65 | | "rating" 66 | | "updatedAt"; 67 | colorDistance?: number; 68 | publisherName?: string; 69 | extensionName?: string; 70 | themeName?: string; 71 | extensionsPageNumber?: number; 72 | extensionsPageSize?: number; 73 | themesPageNumber?: number; 74 | themesPageSize?: number; 75 | } 76 | 77 | export interface GetColorsInput { 78 | anchor?: number; 79 | } 80 | 81 | export interface ColorsResults { 82 | colors: Color[]; 83 | } 84 | 85 | export interface Color { 86 | hex: string; 87 | count: number; 88 | } 89 | 90 | export class ApiClient { 91 | constructor(private baseUrl: string, private apiKey: string) {} 92 | 93 | async searchExtensions(input: SearchExtensionsInput): Promise { 94 | const params = new URLSearchParams(); 95 | 96 | if (input.text) { 97 | params.set("text", input.text); 98 | } 99 | if (input.editorBackground) { 100 | params.set("editorBackground", input.editorBackground); 101 | } 102 | if (input.language) { 103 | params.set("language", input.language); 104 | } 105 | if (input.sortBy) { 106 | params.set("sortBy", input.sortBy); 107 | } 108 | if (input.colorDistance) { 109 | params.set("colorDistance", input.colorDistance.toString()); 110 | } 111 | if (input.publisherName) { 112 | params.set("publisherName", input.publisherName); 113 | } 114 | if (input.extensionName) { 115 | params.set("extensionName", input.extensionName); 116 | } 117 | if (input.themeName) { 118 | params.set("themeName", input.themeName); 119 | } 120 | if (input.extensionsPageNumber) { 121 | params.set("extensionsPageNumber", input.extensionsPageNumber.toString()); 122 | } 123 | if (input.extensionsPageSize) { 124 | params.set("extensionsPageSize", input.extensionsPageSize.toString()); 125 | } 126 | if (input.themesPageNumber) { 127 | params.set("themesPageNumber", input.themesPageNumber.toString()); 128 | } 129 | if (input.themesPageSize) { 130 | params.set("themesPageSize", input.themesPageSize.toString()); 131 | } 132 | 133 | const response = await fetch( 134 | `${this.baseUrl}/extensions/search?${params}`, 135 | { 136 | headers: { 137 | Authorization: `Bearer ${this.apiKey}`, 138 | }, 139 | } 140 | ); 141 | 142 | if (!response.ok) { 143 | throw new Error(`Failed to fetch extensions: ${response.statusText}`); 144 | } 145 | 146 | return await response.json(); 147 | } 148 | 149 | async getColors(input: GetColorsInput): Promise { 150 | const params = new URLSearchParams(); 151 | 152 | if (input.anchor) { 153 | params.set("anchor", input.anchor.toString()); 154 | } 155 | 156 | const response = await fetch(`${this.baseUrl}/themes/colors?${params}`, { 157 | headers: { 158 | Authorization: `Bearer ${this.apiKey}`, 159 | }, 160 | }); 161 | 162 | if (!response.ok) { 163 | throw new Error(`Failed to fetch extensions: ${response.statusText}`); 164 | } 165 | 166 | return await response.json(); 167 | } 168 | } 169 | 170 | const apiUrl = process.env.API_URL; 171 | if (!apiUrl) { 172 | throw new Error("Missing API_URL environment variable"); 173 | } 174 | 175 | const apiKey = process.env.API_KEY; 176 | if (!apiKey) { 177 | throw new Error("Missing API_KEY environment variable"); 178 | } 179 | 180 | export default new ApiClient(apiUrl, apiKey); 181 | -------------------------------------------------------------------------------- /app/clients/plausible.ts: -------------------------------------------------------------------------------- 1 | export type PlausibleProps = Record< 2 | string, 3 | null | undefined | boolean | number | string 4 | >; 5 | 6 | export class PlausibleClient { 7 | constructor(private baseUrl: string, private domain: string) {} 8 | 9 | async event( 10 | request: Request, 11 | event: string, 12 | props?: PlausibleProps 13 | ): Promise { 14 | if (request.url.includes("localhost")) { 15 | console.warn("Skipping plausible event in development"); 16 | return; 17 | } 18 | 19 | const userAgent = request.headers.get("User-Agent") ?? ""; 20 | let ipAddress = 21 | request.headers.get("CF-Connecting-IP") ?? 22 | request.headers.get("X-Forwarded-For") ?? 23 | request.headers.get("X-Real-IP") ?? 24 | ""; 25 | 26 | ipAddress = ipAddress.split(",")[0].trim(); 27 | 28 | const referrer = request.headers.get("Referer") ?? undefined; 29 | 30 | console.info("Sending event to plausible", { 31 | event, 32 | props, 33 | userAgent, 34 | ipAddress, 35 | referrer, 36 | }); 37 | 38 | const response = await fetch(`${this.baseUrl}/api/event`, { 39 | headers: { 40 | "User-Agent": userAgent, 41 | "X-Forwarded-For": ipAddress, 42 | "Content-Type": "application/json", 43 | }, 44 | method: "POST", 45 | body: JSON.stringify({ 46 | domain: this.domain, 47 | url: request.url, 48 | name: event, 49 | referrer, 50 | props, 51 | }), 52 | }); 53 | 54 | if (!response.ok) { 55 | console.error( 56 | `Failed to post event to plausible: ${response.statusText}` 57 | ); 58 | console.error(await response.text()); 59 | } 60 | 61 | return; 62 | } 63 | } 64 | 65 | const plausibleUrl = process.env.PLAUSIBLE_URL; 66 | if (!plausibleUrl) { 67 | throw new Error("Missing PLAUSIBLE_URL environment variable"); 68 | } 69 | 70 | const plausibleDomain = process.env.PLAUSIBLE_DOMAIN; 71 | if (!plausibleDomain) { 72 | throw new Error("Missing PLAUSIBLE_DOMAIN environment variable"); 73 | } 74 | 75 | export default new PlausibleClient(plausibleUrl, plausibleDomain); 76 | -------------------------------------------------------------------------------- /app/components/analytics-script.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useEffect, useRef } from "react"; 2 | import { useRouteLoaderData, useLocation, Location } from "@remix-run/react"; 3 | import type { loader as rootLoader } from "~/root"; 4 | 5 | export function AnalyticsScript() { 6 | const rootLoaderData = useRouteLoaderData("root"); 7 | const location = useLocation(); 8 | const prevLocation = useRef>(); 9 | 10 | useEffect(() => { 11 | if ( 12 | prevLocation.current?.pathname === location.pathname && 13 | prevLocation.current?.search === location.search 14 | ) { 15 | return; 16 | } 17 | 18 | const searchParams = new URLSearchParams(location.search); 19 | const q = searchParams.get("q") ?? ""; 20 | const sort = searchParams.get("sort") ?? ""; 21 | const page = searchParams.get("page") ?? ""; 22 | const userTheme = rootLoaderData?.userTheme; 23 | const userLanguage = rootLoaderData?.userLanguage; 24 | 25 | window.plausible("pageview", { 26 | u: `${location.pathname}${location.search}`, 27 | props: { q, sort, page, userTheme, userLanguage }, 28 | }); 29 | 30 | prevLocation.current = location; 31 | }, [location]); 32 | 33 | const script = useMemo( 34 | () => 35 | `window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }`, 36 | [] 37 | ); 38 | 39 | return ( 40 | <> 41 | 46 | 70 | 74 | 75 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /app/routes/railway.tsx: -------------------------------------------------------------------------------- 1 | import { redirect, LoaderFunctionArgs } from "@remix-run/node"; 2 | import plausible from "~/clients/plausible"; 3 | 4 | export async function loader({ request }: LoaderFunctionArgs) { 5 | plausible.event(request, "Railway Referal"); 6 | return redirect("https://railway.app?referralCode=vsct"); 7 | } 8 | -------------------------------------------------------------------------------- /app/routes/sitemap[.xml].tsx: -------------------------------------------------------------------------------- 1 | import api from "~/clients/api"; 2 | 3 | export async function loader() { 4 | const results = await api.searchExtensions({ extensionsPageSize: 10000 }); 5 | 6 | return new Response( 7 | ` 8 | 9 | ${results.extensions 10 | .map((extension) => { 11 | const theme = extension.themes[0]; 12 | const page = `${process.env.WEB_URL}/e/${extension.publisherName}.${extension.name}/${theme.name}`; 13 | return `${page}`; 14 | }) 15 | .join("")} 16 | `, 17 | { 18 | headers: { 19 | "Content-Type": "application/xml", 20 | }, 21 | } 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/sessions.ts: -------------------------------------------------------------------------------- 1 | // app/sessions.ts 2 | import { createCookieSessionStorage, Session } from "@remix-run/node"; 3 | import { Language, languageValues } from "~/data"; 4 | 5 | export interface SessionData { 6 | language: Language; 7 | theme: "light" | "dark" | "system"; 8 | } 9 | 10 | export interface SessionFlashData { 11 | error: string; 12 | } 13 | 14 | const { getSession, commitSession, destroySession } = 15 | createCookieSessionStorage({ 16 | cookie: { 17 | name: "__session", 18 | // domain: "TODO", 19 | httpOnly: true, 20 | maxAge: 60 * 60 * 24 * 365, // 1 year 21 | path: "/", 22 | sameSite: "lax", 23 | // secrets: ["TODO"], 24 | secure: true, 25 | }, 26 | }); 27 | 28 | export async function handleSessionUpdate( 29 | session: Session, 30 | request: Request 31 | ) { 32 | const form = await request.formData(); 33 | const userLanguage = form.get("language"); 34 | const userTheme = form.get("theme"); 35 | 36 | if ( 37 | userLanguage !== null && 38 | (languageValues as string[]).includes(userLanguage.toString()) 39 | ) { 40 | session.set("language", userLanguage.toString() as Language); 41 | } 42 | 43 | if ( 44 | userTheme !== null && 45 | ["light", "dark", "system"].includes(userTheme.toString()) 46 | ) { 47 | session.set("theme", userTheme.toString() as "light" | "dark" | "system"); 48 | } 49 | } 50 | 51 | export { getSession, commitSession, destroySession }; 52 | -------------------------------------------------------------------------------- /app/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | @media (prefers-color-scheme: dark) { 8 | color-scheme: dark; 9 | } 10 | } 11 | 12 | @layer base { 13 | :root { 14 | --background: 0 0% 100%; 15 | --foreground: 0 0% 3.9%; 16 | --card: 0 0% 100%; 17 | --card-foreground: 0 0% 3.9%; 18 | --popover: 0 0% 100%; 19 | --popover-foreground: 0 0% 3.9%; 20 | --primary: 0 0% 9%; 21 | --primary-foreground: 0 0% 98%; 22 | --secondary: 0 0% 96.1%; 23 | --secondary-foreground: 0 0% 9%; 24 | --muted: 0 0% 96.1%; 25 | --muted-foreground: 0 0% 45.1%; 26 | --accent: 0 0% 96.1%; 27 | --accent-foreground: 0 0% 9%; 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 0 0% 98%; 30 | --border: 0 0% 89.8%; 31 | --input: 0 0% 89.8%; 32 | --ring: 0 0% 3.9%; 33 | --radius: 0.5rem; 34 | --chart-1: 12 76% 61%; 35 | --chart-2: 173 58% 39%; 36 | --chart-3: 197 37% 24%; 37 | --chart-4: 43 74% 66%; 38 | --chart-5: 27 87% 67%; 39 | } 40 | 41 | .dark { 42 | --background: 0 0% 3.9%; 43 | --foreground: 0 0% 98%; 44 | --card: 0 0% 3.9%; 45 | --card-foreground: 0 0% 98%; 46 | --popover: 0 0% 3.9%; 47 | --popover-foreground: 0 0% 98%; 48 | --primary: 0 0% 98%; 49 | --primary-foreground: 0 0% 9%; 50 | --secondary: 0 0% 14.9%; 51 | --secondary-foreground: 0 0% 98%; 52 | --muted: 0 0% 14.9%; 53 | --muted-foreground: 0 0% 63.9%; 54 | --accent: 0 0% 14.9%; 55 | --accent-foreground: 0 0% 98%; 56 | --destructive: 0 62.8% 30.6%; 57 | --destructive-foreground: 0 0% 98%; 58 | --border: 0 0% 14.9%; 59 | --input: 0 0% 14.9%; 60 | --ring: 0 0% 83.1%; 61 | --chart-1: 220 70% 50%; 62 | --chart-2: 160 60% 45%; 63 | --chart-3: 30 80% 55%; 64 | --chart-4: 280 65% 60%; 65 | --chart-5: 340 75% 55%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } 77 | 78 | @layer base { 79 | :root { 80 | --vsct-1: 200.5 100% 50%; 81 | --vsct-2: 76.1 77.4% 56.7%; 82 | --vsct-3: 337.5 98.3% 45.7%; 83 | --vsct-4: 322.5 100% 26.7%; 84 | --vsct-5: 57.8 100% 49%; 85 | --vsct-primary: 200.5 100% 50%; 86 | --vsct-foreground: 0 0% 9%; 87 | --progress-from: 200.5 100% 50%; 88 | --progress-via: 76.1 77.4% 56.7%; 89 | --progress-to: 337.5 98.3% 45.7%; 90 | } 91 | 92 | .dark { 93 | --vsct-foreground: 0 0% 98%; 94 | } 95 | } -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/tailwind.css", 9 | "baseColor": "gray", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "~/components", 15 | "utils": "~/lib/utils", 16 | "ui": "~/components/ui", 17 | "lib": "~/lib", 18 | "hooks": "~/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | if [ -z "$PORT" ]; then 5 | echo "PORT is not set. Exiting." 6 | exit 1 7 | fi 8 | 9 | if [ -z "$API_URL" ]; then 10 | echo "API_URL is not set. Exiting." 11 | exit 1 12 | fi 13 | 14 | if [ -z "$API_KEY" ]; then 15 | echo "PORT is not set. Exiting." 16 | exit 1 17 | fi 18 | 19 | if [ -z "$WEB_URL" ]; then 20 | echo "WEB_URL is not set. Exiting." 21 | exit 1 22 | fi 23 | 24 | echo "Starting server on $PORT..." 25 | npm run start --port $PORT -------------------------------------------------------------------------------- /fonts/Fira_Code/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014-2020 The Fira Code Project Authors (https://github.com/tonsky/FiraCode) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://openfontlicense.org 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /fonts/Fira_Code/README.txt: -------------------------------------------------------------------------------- 1 | Fira Code Variable Font 2 | ======================= 3 | 4 | This download contains Fira Code as both a variable font and static fonts. 5 | 6 | Fira Code is a variable font with this axis: 7 | wght 8 | 9 | This means all the styles are contained in a single file: 10 | Fira_Code/FiraCode-VariableFont_wght.ttf 11 | 12 | If your app fully supports variable fonts, you can now pick intermediate styles 13 | that aren’t available as static fonts. Not all apps support variable fonts, and 14 | in those cases you can use the static font files for Fira Code: 15 | Fira_Code/static/FiraCode-Light.ttf 16 | Fira_Code/static/FiraCode-Regular.ttf 17 | Fira_Code/static/FiraCode-Medium.ttf 18 | Fira_Code/static/FiraCode-SemiBold.ttf 19 | Fira_Code/static/FiraCode-Bold.ttf 20 | 21 | Get started 22 | ----------- 23 | 24 | 1. Install the font files you want to use 25 | 26 | 2. Use your app's font picker to view the font family and all the 27 | available styles 28 | 29 | Learn more about variable fonts 30 | ------------------------------- 31 | 32 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 33 | https://variablefonts.typenetwork.com 34 | https://medium.com/variable-fonts 35 | 36 | In desktop apps 37 | 38 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 39 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 40 | 41 | Online 42 | 43 | https://developers.google.com/fonts/docs/getting_started 44 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 45 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 46 | 47 | Installing fonts 48 | 49 | MacOS: https://support.apple.com/en-us/HT201749 50 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 51 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 52 | 53 | Android Apps 54 | 55 | https://developers.google.com/fonts/docs/android 56 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 57 | 58 | License 59 | ------- 60 | Please read the full license text (OFL.txt) to understand the permissions, 61 | restrictions and requirements for usage, redistribution, and modification. 62 | 63 | You can use them in your products & projects – print or digital, 64 | commercial or otherwise. 65 | 66 | This isn't legal advice, please consider consulting a lawyer and see the full 67 | license for all details. 68 | -------------------------------------------------------------------------------- /fonts/Fira_Code/static/FiraCode-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vscodethemes/web/d8a0bf42454ac9b7143192a9c91f85753e45258d/fonts/Fira_Code/static/FiraCode-Regular.ttf -------------------------------------------------------------------------------- /fonts/Inter/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://openfontlicense.org 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /fonts/Inter/README.txt: -------------------------------------------------------------------------------- 1 | Inter Variable Font 2 | =================== 3 | 4 | This download contains Inter as both variable fonts and static fonts. 5 | 6 | Inter is a variable font with these axes: 7 | opsz 8 | wght 9 | 10 | This means all the styles are contained in these files: 11 | Inter/Inter-VariableFont_opsz,wght.ttf 12 | Inter/Inter-Italic-VariableFont_opsz,wght.ttf 13 | 14 | If your app fully supports variable fonts, you can now pick intermediate styles 15 | that aren’t available as static fonts. Not all apps support variable fonts, and 16 | in those cases you can use the static font files for Inter: 17 | Inter/static/Inter_18pt-Thin.ttf 18 | Inter/static/Inter_18pt-ExtraLight.ttf 19 | Inter/static/Inter_18pt-Light.ttf 20 | Inter/static/Inter_18pt-Regular.ttf 21 | Inter/static/Inter_18pt-Medium.ttf 22 | Inter/static/Inter_18pt-SemiBold.ttf 23 | Inter/static/Inter_18pt-Bold.ttf 24 | Inter/static/Inter_18pt-ExtraBold.ttf 25 | Inter/static/Inter_18pt-Black.ttf 26 | Inter/static/Inter_24pt-Thin.ttf 27 | Inter/static/Inter_24pt-ExtraLight.ttf 28 | Inter/static/Inter_24pt-Light.ttf 29 | Inter/static/Inter_24pt-Regular.ttf 30 | Inter/static/Inter_24pt-Medium.ttf 31 | Inter/static/Inter_24pt-SemiBold.ttf 32 | Inter/static/Inter_24pt-Bold.ttf 33 | Inter/static/Inter_24pt-ExtraBold.ttf 34 | Inter/static/Inter_24pt-Black.ttf 35 | Inter/static/Inter_28pt-Thin.ttf 36 | Inter/static/Inter_28pt-ExtraLight.ttf 37 | Inter/static/Inter_28pt-Light.ttf 38 | Inter/static/Inter_28pt-Regular.ttf 39 | Inter/static/Inter_28pt-Medium.ttf 40 | Inter/static/Inter_28pt-SemiBold.ttf 41 | Inter/static/Inter_28pt-Bold.ttf 42 | Inter/static/Inter_28pt-ExtraBold.ttf 43 | Inter/static/Inter_28pt-Black.ttf 44 | Inter/static/Inter_18pt-ThinItalic.ttf 45 | Inter/static/Inter_18pt-ExtraLightItalic.ttf 46 | Inter/static/Inter_18pt-LightItalic.ttf 47 | Inter/static/Inter_18pt-Italic.ttf 48 | Inter/static/Inter_18pt-MediumItalic.ttf 49 | Inter/static/Inter_18pt-SemiBoldItalic.ttf 50 | Inter/static/Inter_18pt-BoldItalic.ttf 51 | Inter/static/Inter_18pt-ExtraBoldItalic.ttf 52 | Inter/static/Inter_18pt-BlackItalic.ttf 53 | Inter/static/Inter_24pt-ThinItalic.ttf 54 | Inter/static/Inter_24pt-ExtraLightItalic.ttf 55 | Inter/static/Inter_24pt-LightItalic.ttf 56 | Inter/static/Inter_24pt-Italic.ttf 57 | Inter/static/Inter_24pt-MediumItalic.ttf 58 | Inter/static/Inter_24pt-SemiBoldItalic.ttf 59 | Inter/static/Inter_24pt-BoldItalic.ttf 60 | Inter/static/Inter_24pt-ExtraBoldItalic.ttf 61 | Inter/static/Inter_24pt-BlackItalic.ttf 62 | Inter/static/Inter_28pt-ThinItalic.ttf 63 | Inter/static/Inter_28pt-ExtraLightItalic.ttf 64 | Inter/static/Inter_28pt-LightItalic.ttf 65 | Inter/static/Inter_28pt-Italic.ttf 66 | Inter/static/Inter_28pt-MediumItalic.ttf 67 | Inter/static/Inter_28pt-SemiBoldItalic.ttf 68 | Inter/static/Inter_28pt-BoldItalic.ttf 69 | Inter/static/Inter_28pt-ExtraBoldItalic.ttf 70 | Inter/static/Inter_28pt-BlackItalic.ttf 71 | 72 | Get started 73 | ----------- 74 | 75 | 1. Install the font files you want to use 76 | 77 | 2. Use your app's font picker to view the font family and all the 78 | available styles 79 | 80 | Learn more about variable fonts 81 | ------------------------------- 82 | 83 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 84 | https://variablefonts.typenetwork.com 85 | https://medium.com/variable-fonts 86 | 87 | In desktop apps 88 | 89 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 90 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 91 | 92 | Online 93 | 94 | https://developers.google.com/fonts/docs/getting_started 95 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 96 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 97 | 98 | Installing fonts 99 | 100 | MacOS: https://support.apple.com/en-us/HT201749 101 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 102 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 103 | 104 | Android Apps 105 | 106 | https://developers.google.com/fonts/docs/android 107 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 108 | 109 | License 110 | ------- 111 | Please read the full license text (OFL.txt) to understand the permissions, 112 | restrictions and requirements for usage, redistribution, and modification. 113 | 114 | You can use them in your products & projects – print or digital, 115 | commercial or otherwise. 116 | 117 | This isn't legal advice, please consider consulting a lawyer and see the full 118 | license for all details. 119 | -------------------------------------------------------------------------------- /fonts/Inter/static/Inter_18pt-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vscodethemes/web/d8a0bf42454ac9b7143192a9c91f85753e45258d/fonts/Inter/static/Inter_18pt-Regular.ttf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "sideEffects": false, 5 | "type": "module", 6 | "scripts": { 7 | "build": "remix vite:build", 8 | "dev": "remix vite:dev --port $PORT", 9 | "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", 10 | "start": "remix-serve ./build/server/index.js", 11 | "typecheck": "tsc" 12 | }, 13 | "dependencies": { 14 | "@radix-ui/react-dialog": "^1.1.2", 15 | "@radix-ui/react-dropdown-menu": "^2.1.2", 16 | "@radix-ui/react-icons": "^1.3.0", 17 | "@radix-ui/react-popover": "^1.1.2", 18 | "@radix-ui/react-select": "^2.1.2", 19 | "@radix-ui/react-slot": "^1.1.0", 20 | "@radix-ui/react-tabs": "^1.1.1", 21 | "@radix-ui/react-tooltip": "^1.1.3", 22 | "@remix-run/node": "^2.12.1", 23 | "@remix-run/react": "^2.12.1", 24 | "@remix-run/serve": "^2.12.1", 25 | "@resvg/resvg-js": "^2.6.2", 26 | "class-variance-authority": "^0.7.0", 27 | "clsx": "^2.1.1", 28 | "colord": "^2.9.3", 29 | "isbot": "^4.1.0", 30 | "lucide-react": "^0.447.0", 31 | "react": "^18.2.0", 32 | "react-dom": "^18.2.0", 33 | "recharts": "^2.12.7", 34 | "tailwind-merge": "^2.5.3", 35 | "tailwindcss-animate": "^1.0.7" 36 | }, 37 | "devDependencies": { 38 | "@remix-run/dev": "^2.12.1", 39 | "@types/react": "^18.2.20", 40 | "@types/react-dom": "^18.2.7", 41 | "@typescript-eslint/eslint-plugin": "^6.7.4", 42 | "@typescript-eslint/parser": "^6.7.4", 43 | "autoprefixer": "^10.4.19", 44 | "eslint": "^8.38.0", 45 | "eslint-import-resolver-typescript": "^3.6.1", 46 | "eslint-plugin-import": "^2.28.1", 47 | "eslint-plugin-jsx-a11y": "^6.7.1", 48 | "eslint-plugin-react": "^7.33.2", 49 | "eslint-plugin-react-hooks": "^4.6.0", 50 | "postcss": "^8.4.38", 51 | "tailwindcss": "^3.4.4", 52 | "typescript": "^5.1.6", 53 | "vite": "^5.1.0", 54 | "vite-tsconfig-paths": "^4.2.1" 55 | }, 56 | "engines": { 57 | "node": ">=20.0.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vscodethemes/web/d8a0bf42454ac9b7143192a9c91f85753e45258d/public/banner.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vscodethemes/web/d8a0bf42454ac9b7143192a9c91f85753e45258d/public/favicon.ico -------------------------------------------------------------------------------- /public/logo-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vscodethemes/web/d8a0bf42454ac9b7143192a9c91f85753e45258d/public/logo-dark.jpg -------------------------------------------------------------------------------- /public/logo-light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vscodethemes/web/d8a0bf42454ac9b7143192a9c91f85753e45258d/public/logo-light.jpg -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vscodethemes/web/d8a0bf42454ac9b7143192a9c91f85753e45258d/public/logo.png -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | 4 | Sitemap: https://vscodethemes.com/sitemap.xml -------------------------------------------------------------------------------- /public/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vscodethemes/web/d8a0bf42454ac9b7143192a9c91f85753e45258d/public/thumbnail.jpg -------------------------------------------------------------------------------- /railway.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | dockerfilePath = "Dockerfile" 3 | 4 | [deploy] 5 | numReplicas = 1 6 | restartPolicyType = "ALWAYS" 7 | restartPolicyMaxRetries = 10 8 | healthcheckPath = "/health" 9 | healthcheckTimeout = 300 10 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | darkMode: ["selector"], 5 | content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"], 6 | theme: { 7 | extend: { 8 | fontFamily: { 9 | sans: [ 10 | "-apple-system", 11 | "BlinkMacSystemFont", 12 | "Segoe UI", 13 | "Helvetica", 14 | "Arial", 15 | "sans-serif", 16 | ], 17 | monospace: [ 18 | "SFMono-Regular", 19 | "Consolas", 20 | "Liberation Mono", 21 | "Menlo", 22 | "Courier", 23 | "monospace", 24 | ], 25 | }, 26 | borderRadius: { 27 | lg: "var(--radius)", 28 | md: "calc(var(--radius) - 2px)", 29 | sm: "calc(var(--radius) - 4px)", 30 | }, 31 | colors: { 32 | background: "hsl(var(--background))", 33 | foreground: "hsl(var(--foreground))", 34 | card: { 35 | DEFAULT: "hsl(var(--card))", 36 | foreground: "hsl(var(--card-foreground))", 37 | }, 38 | popover: { 39 | DEFAULT: "hsl(var(--popover))", 40 | foreground: "hsl(var(--popover-foreground))", 41 | }, 42 | primary: { 43 | DEFAULT: "hsl(var(--primary))", 44 | foreground: "hsl(var(--primary-foreground))", 45 | }, 46 | secondary: { 47 | DEFAULT: "hsl(var(--secondary))", 48 | foreground: "hsl(var(--secondary-foreground))", 49 | }, 50 | muted: { 51 | DEFAULT: "hsl(var(--muted))", 52 | foreground: "hsl(var(--muted-foreground))", 53 | }, 54 | accent: { 55 | DEFAULT: "hsl(var(--accent))", 56 | foreground: "hsl(var(--accent-foreground))", 57 | }, 58 | destructive: { 59 | DEFAULT: "hsl(var(--destructive))", 60 | foreground: "hsl(var(--destructive-foreground))", 61 | }, 62 | border: "hsl(var(--border))", 63 | input: "hsl(var(--input))", 64 | ring: "hsl(var(--ring))", 65 | chart: { 66 | "1": "hsl(var(--chart-1))", 67 | "2": "hsl(var(--chart-2))", 68 | "3": "hsl(var(--chart-3))", 69 | "4": "hsl(var(--chart-4))", 70 | "5": "hsl(var(--chart-5))", 71 | }, 72 | vsct: { 73 | "1": "hsl(var(--vsct-1))", 74 | "2": "hsl(var(--vsct-2))", 75 | "3": "hsl(var(--vsct-3))", 76 | "4": "hsl(var(--vsct-4))", 77 | "5": "hsl(var(--vsct-5))", 78 | primary: "hsl(var(--vsct-primary))", 79 | foreground: "hsl(var(--vsct-foreground))", 80 | }, 81 | progress: { 82 | from: "hsl(var(--progress-from))", 83 | via: "hsl(var(--progress-via))", 84 | to: "hsl(var(--progress-to))", 85 | }, 86 | }, 87 | aspectRatio: { 88 | theme: "461 / 332", 89 | }, 90 | }, 91 | }, 92 | plugins: [require("tailwindcss-animate")], 93 | } satisfies Config; 94 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*.ts", 4 | "**/*.tsx", 5 | "**/.server/**/*.ts", 6 | "**/.server/**/*.tsx", 7 | "**/.client/**/*.ts", 8 | "**/.client/**/*.tsx" 9 | ], 10 | "compilerOptions": { 11 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 12 | "types": ["@remix-run/node", "vite/client"], 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "jsx": "react-jsx", 16 | "module": "ESNext", 17 | "moduleResolution": "Bundler", 18 | "resolveJsonModule": true, 19 | "target": "ES2022", 20 | "strict": true, 21 | "allowJs": true, 22 | "skipLibCheck": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "baseUrl": ".", 25 | "paths": { 26 | "~/*": ["./app/*"] 27 | }, 28 | 29 | // Vite takes care of building everything, not tsc. 30 | "noEmit": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { vitePlugin as remix } from "@remix-run/dev"; 2 | import { defineConfig } from "vite"; 3 | import tsconfigPaths from "vite-tsconfig-paths"; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | remix({ 8 | future: { 9 | v3_fetcherPersist: true, 10 | v3_relativeSplatPath: true, 11 | v3_throwAbortReason: true, 12 | }, 13 | }), 14 | tsconfigPaths(), 15 | ], 16 | }); 17 | --------------------------------------------------------------------------------