website

source code for the ttmaths.uk website
Log | Files | Refs | README

commit ce4705fdecbc3f878714b1c28c210c9ec41a5206
parent 2174f77f7699f7a300bdc7342d0989ffe3ab0a67
Author: hrryt <harry@mayesfield.uk>
Date:   Thu,  6 Aug 2026 20:19:20 +0100

Refactor to use preact

Diffstat:
D.dockerignore | 5-----
M.gitignore | 9++-------
DDockerfile | 22----------------------
MREADME.md | 88++++---------------------------------------------------------------------------
Dapp/components/Equation.jsx | 12------------
Dapp/components/MultipleSelection.jsx | 26--------------------------
Dapp/components/Question.jsx | 10----------
Dapp/components/QuestionList.jsx | 11-----------
Dapp/components/QuestionWindow.jsx | 112-------------------------------------------------------------------------------
Dapp/components/TestQuestionWindow.jsx | 7-------
Dapp/components/Window.jsx | 20--------------------
Dapp/components/questions/AddingFractionsQuestion.jsx | 42------------------------------------------
Dapp/components/questions/QuadraticEquationQuestion.jsx | 69---------------------------------------------------------------------
Dapp/data/test-question-window.js | 27---------------------------
Dapp/root.tsx | 81-------------------------------------------------------------------------------
Dapp/routes.ts | 6------
Dapp/routes/Home.tsx | 15---------------
Dapp/routes/Questions.tsx | 14--------------
Dapp/scripts/auto-maths.js | 8--------
Dapp/scripts/utils.js | 30------------------------------
Dapp/styles/index.css | 4----
Dapp/styles/main.css | 45---------------------------------------------
Mpackage.json | 46+++++++++++++++++-----------------------------
Dpublic/favicon.ico | 0
Dreact-router.config.ts | 7-------
Dtsconfig.json | 26--------------------------
Dvite.config.ts | 10----------
27 files changed, 23 insertions(+), 729 deletions(-)

diff --git a/.dockerignore b/.dockerignore @@ -1,4 +0,0 @@ -.react-router -build -node_modules -README.md -\ No newline at end of file diff --git a/.gitignore b/.gitignore @@ -1,8 +1,3 @@ -.DS_Store -.env -/node_modules/ package-lock.json - -# React Router -/.react-router/ -/build/ +node_modules/ +dist/ diff --git a/Dockerfile b/Dockerfile @@ -1,22 +0,0 @@ -FROM node:24-alpine AS development-dependencies-env -COPY . /app -WORKDIR /app -RUN npm ci - -FROM node:24-alpine AS production-dependencies-env -COPY ./package.json package-lock.json /app/ -WORKDIR /app -RUN npm ci --omit=dev - -FROM node:24-alpine AS build-env -COPY . /app/ -COPY --from=development-dependencies-env /app/node_modules /app/node_modules -WORKDIR /app -RUN npm run build - -FROM node:24-alpine -COPY ./package.json package-lock.json /app/ -COPY --from=production-dependencies-env /app/node_modules /app/node_modules -COPY --from=build-env /app/build /app/build -WORKDIR /app -CMD ["npm", "run", "start"] diff --git a/README.md b/README.md @@ -1,87 +1,7 @@ -# Welcome to React Router! +## Scripts -A modern, production-ready template for building full-stack React applications using React Router. +- `npm run dev` - Starts a dev server at http://localhost:5173/ -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default) +- `npm run build` - Builds for production, emitting to `dist/`. Prerenders all found routes in app to static HTML -## Features - -- 🚀 Server-side rendering -- ⚡️ Hot Module Replacement (HMR) -- 📦 Asset bundling and optimization -- 🔄 Data loading and mutations -- 🔒 TypeScript by default -- 🎉 TailwindCSS for styling -- 📖 [React Router docs](https://reactrouter.com/) - -## Getting Started - -### Installation - -Install the dependencies: - -```bash -npm install -``` - -### Development - -Start the development server with HMR: - -```bash -npm run dev -``` - -Your application will be available at `http://localhost:5173`. - -## Building for Production - -Create a production build: - -```bash -npm run build -``` - -## Deployment - -### Docker Deployment - -To build and run using Docker: - -```bash -docker build -t my-app . - -# Run the container -docker run -p 3000:3000 my-app -``` - -The containerized application can be deployed to any platform that supports Docker, including: - -- AWS ECS -- Google Cloud Run -- Azure Container Apps -- Digital Ocean App Platform -- Fly.io -- Railway - -### DIY Deployment - -If you're familiar with deploying Node applications, the built-in app server is production-ready. - -Make sure to deploy the output of `npm run build` - -``` -├── package.json -├── package-lock.json (or pnpm-lock.yaml, or bun.lockb) -├── build/ -│ ├── client/ # Static assets -│ └── server/ # Server-side code -``` - -## Styling - -This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer. - ---- - -Built with ❤️ using React Router. +- `npm run preview` - Starts a server at http://localhost:4173/ to test production build locally diff --git a/app/components/Equation.jsx b/app/components/Equation.jsx @@ -1,12 +0,0 @@ -import * as React from 'react'; -import { render } from 'kern-typ'; - -export default function Equation({ equation, displayMode = false }) { - const ref = React.useRef(null); - - React.useEffect(() => { - render(equation, ref.current, { displayMode: displayMode }); - }, [equation]); - - return <div ref={ref} />; -} diff --git a/app/components/MultipleSelection.jsx b/app/components/MultipleSelection.jsx @@ -1,26 +0,0 @@ -import * as React from 'react'; - -export default function MultipleSelection({ variable, setVariable, id, legend, values, labels = values }) { - const checked = values.map(value => variable.includes(value)); - - function updateVariable(e, index) { - const newChecked = checked.map((value, i) => i === index ? e.target.checked : value); - const validChecked = newChecked.some(Boolean) ? newChecked : checked.map((value) => !value); - setVariable(values.filter((_, i) => validChecked[i])); - } - - return ( - <fieldset key={id}> - <legend>{legend}</legend> - {values.map((value, i) => { - const name = `${id}:${value}`; - return ( - <div key={value} className="field-row"> - <input type="checkbox" id={name} name={name} checked={checked[i]} onChange={e => updateVariable(e, i)} /> - <label htmlFor={name}>{labels[i]}</label> - </div> - ); - })} - </fieldset> - ); -} diff --git a/app/components/Question.jsx b/app/components/Question.jsx @@ -1,10 +0,0 @@ -import * as React from 'react'; -import Equation from './Equation.jsx'; - -export default function Question({ question, answer, showAnswer }) { - return ( - <li> - <Equation equation={showAnswer ? answer : question} /> - </li> - ); -} diff --git a/app/components/QuestionList.jsx b/app/components/QuestionList.jsx @@ -1,11 +0,0 @@ -import * as React from 'react'; - -export default function QuestionList({ children }) { - return ( - <li> - <ol className="question-list"> - {children} - </ol> - </li> - ); -} diff --git a/app/components/QuestionWindow.jsx b/app/components/QuestionWindow.jsx @@ -1,112 +0,0 @@ -import * as React from 'react'; -import QuestionList from './QuestionList.jsx'; -import { getRandomInt, getRandomNumber, getRandomChoice } from '../scripts/utils.js'; - -import MultipleSelection from './MultipleSelection'; - -import AddingFractionsQuestion from './questions/AddingFractionsQuestion.jsx'; -import QuadraticEquationQuestion from './questions/QuadraticEquationQuestion.jsx'; -import Window from './Window.jsx'; - -function chooseInputComponent(type) { - switch (type) { - case 'multiple-selection': - return MultipleSelection; - } -} - -function chooseQuestionComponent(type) { - switch (type) { - case 'quadratic-equation': - return QuadraticEquationQuestion; - case 'adding-fractions': - return AddingFractionsQuestion; - } -} - -function resolveParameters(question, variables) { - const parameters = question.constants || { }; - const variableKeys = question.variables || { }; - Object.keys(variableKeys).forEach((key) => { - parameters[key] = variables[variableKeys[key]]; - }) - return parameters; -} - -function renderQuestion(seed, showAnswer, variables) { - return (question, i) => { - const QuestionComponent = chooseQuestionComponent(question.type); - const parameters = resolveParameters(question, variables); - return <QuestionComponent key={i} seed={seed+i} showAnswer={showAnswer} {...parameters} />; - }; -} - -function getQuestionList(id, questions, showAnswers, seed, variables) { - return ( - <QuestionList key={id}> - {questions.map(renderQuestion(seed, showAnswers, variables))} - </QuestionList> - ); -} - -function shuffleArray(arr, n, seed) { - return Array.from({ length: n }).map(() => getRandomChoice(arr, ++seed)); -} - -export default function QuestionWindow({ data }) { - - const defaultVariables = Object.fromEntries( - data.variables.map(input => [input.id, input.default ?? input.values]) - ); - - const [showAnswers, setShowAnswers] = React.useState(false); - const [seed , setSeed ] = React.useState(Math.random()); - const [variables , setVariables ] = React.useState(defaultVariables); - - function setVariable(id) { - return (value) => { - setVariables({ - ...variables, - [id]: value - }); - } - } - - function revealAnswers() { setShowAnswers(!showAnswers); } - function triggerRefresh() { setSeed(getRandomNumber(seed)); } - - let current_seed = seed; - const questions = data.questionLists.map(questionList => { - const { id: id, n: n, questions: unique_questions } = questionList; - const questions = shuffleArray(unique_questions, n, current_seed); - const randomQuestionList = getQuestionList(id, questions, showAnswers, current_seed, variables); - current_seed += n; - return randomQuestionList; - }); - - const fields = data.variables.map(input => { - const { type: type, ...props } = input; - const InputComponent = chooseInputComponent(type); - return <InputComponent key={input.id} variable={variables[input.id]} setVariable={setVariable(input.id)} {...props} />; - }); - - return ( - <Window title="Questions"> - {fields} - <fieldset> - <legend>Options</legend> - <button onClick={revealAnswers}> - {showAnswers ? "Hide" : "Show"} Answers - </button> - <button onClick={triggerRefresh}> - Refresh Questions - </button> - </fieldset> - <div className="field-border question-field kern"> - <ol className="master-question-list"> - {questions} - </ol> - </div> - </Window> - ); -} diff --git a/app/components/TestQuestionWindow.jsx b/app/components/TestQuestionWindow.jsx @@ -1,7 +0,0 @@ -import * as React from 'react'; -import QuestionWindow from './QuestionWindow.jsx'; -import { questionWindow } from '../data/test-question-window.js'; - -export default function TestQuestionPanel() { - return <QuestionWindow data={questionWindow} />; -} diff --git a/app/components/Window.jsx b/app/components/Window.jsx @@ -1,19 +0,0 @@ -import * as React from 'react' - -export default function Window({ children, title, maximised }){ - return ( - <div className="window"> - <div className="title-bar"> - <div className="title-bar-text">{title}</div> - <div className="title-bar-controls"> - <button aria-label="Minimize" /> - <button aria-label={maximised ? "Restore" : "Maximize"} /> - <button aria-label="Close" /> - </div> - </div> - <div className="window-body"> - {children} - </div> - </div> - ) -} -\ No newline at end of file diff --git a/app/components/questions/AddingFractionsQuestion.jsx b/app/components/questions/AddingFractionsQuestion.jsx @@ -1,42 +0,0 @@ -import * as React from 'react'; -import Question from '../Question.jsx'; -import { getRandomInt, getHCF } from '../../scripts/utils.js'; - -function formatFraction(num, denom) { - if (denom == 1) { return `${num}`; } - return num + '/' + denom; -} - -function simplifyFraction(num, denom) { - const hcf = getHCF(num, denom); - return [num / hcf, denom / hcf]; -} - -function getRandomFraction(seed) { - const num0 = getRandomInt(1, 5, seed); - const denom0 = getRandomInt(2, 10, seed + .05); - const [num, denom] = simplifyFraction(num0, denom0); - if (denom == 1) { return getRandomFraction(seed + .1); } - return [num, denom]; -} - -function getRandomFractions(seed) { - const [num1, denom1] = getRandomFraction(seed); - const [num2, denom2] = getRandomFraction(seed + .5); - return [num1, denom1, num2, denom2]; -} - -export default function AddingFractionsQuestion({ seed, showAnswer }) { - const [num1, denom1, num2, denom2] = getRandomFractions(seed); - - const question = formatFraction(num1, denom1) + ' + ' + formatFraction(num2, denom2); - - const hcf = getHCF(denom1, denom2); - const sum = (num1 * denom2 + num2 * denom1) / hcf; - const lcm = denom1 * denom2 / hcf; - const [num, denom] = simplifyFraction(sum, lcm); - - const answer = formatFraction(num, denom); - - return <Question question={question} answer={answer} showAnswer={showAnswer} />; -} diff --git a/app/components/questions/QuadraticEquationQuestion.jsx b/app/components/questions/QuadraticEquationQuestion.jsx @@ -1,69 +0,0 @@ -import * as React from 'react'; -import Question from '../Question.jsx'; -import { getRandomInt, getRandomBoolean, getRandomChoice, formatNumber } from '../../scripts/utils.js'; - -function formatQuadraticExpression(a, b, c) { - const fa = formatNumber(a, {showPlus: false, showOne: false, showZero: false, suffix: 'x^2'}); - const fb = formatNumber(b, {showPlus: true, showOne: false, showZero: false, suffix: 'x'}); - const fc = formatNumber(c, {showPlus: true, showOne: true, showZero: false}); - return `${fa} ${fb} ${fc}`; -} - -function formatRoots(root1, root2) { - const froot2 = formatNumber(root2, {showPlus: false, showOne: true, showZero: true}); - if (root1 == root2) { return `x = ${froot2}`; } - if (Math.abs(root1) == root2) { return `x = plus.minus ${root2}`; } - const froot1 = formatNumber(root1, {showPlus: false, showOne: true, showZero: true}); - return `x = ${froot1} thick "or" thick x = ${froot2}`; -} - -function formatFactor(root) { - if (root == 0) { return "x"; } - const froot = formatNumber(root, {showPlus: true, showOne: true, showZero: false}); - return `(x${froot})`; -} - -function sortFactors(root1, root2) { - if (root1 > 0 && root2 < 0) { return -1; } - if (root1 < 0 && root2 > 0) { return +1; } - return Math.abs(root1) - Math.abs(root2); -} - -function formatFactors(root1, root2, a) { - const fa = formatNumber(a, {showPlus: false, showOne: false, showZero: false}); - [root1, root2] = [root1, root2].sort(sortFactors); - const froot2 = formatFactor(root2); - if (root1 == root2) { return `${fa}${froot2}^2`; } - const froot1 = formatFactor(root1); - return `${fa}${froot1}${froot2}`; -} - -function getRandomRoots(seed) { - return [ - getRandomInt(-9, 9, seed), - getRandomInt(-9, 9, seed + .1), - ].sort((a, b) => a - b); -} - -export default function QuadraticEquationQuestion({ seed, showAnswer, aPlurality, framing }) { - const aChoices = Array(); - aPlurality.includes("a=1") && aChoices.push(1); - aPlurality.includes("a>1") && aChoices.push(2, 3); - const a = getRandomChoice(aChoices, seed); - - const factoriseChoices = Array(); - framing.includes("Solve") && factoriseChoices.push(false); - framing.includes("Factorise") && factoriseChoices.push(true); - const factorise = getRandomChoice(factoriseChoices, seed + .1); - - const [root1, root2] = getRandomRoots(seed + .2); - - const b = a * -(root1 + root2); - const c = a * root1 * root2; - - const quadratic = formatQuadraticExpression(a, b, c); - const question = factorise ? `"Factorise" thick ${quadratic}` : `${quadratic} = 0`; - const answer = factorise ? formatFactors(root1, root2, a) : formatRoots(root1, root2); - - return <Question question={question} answer={answer} showAnswer={showAnswer} />; -} diff --git a/app/data/test-question-window.js b/app/data/test-question-window.js @@ -1,27 +0,0 @@ -export const questionWindow = { - variables: [{ - id: 'framing', - legend: 'Quadratic Equations: Framing', - type: 'multiple-selection', - values: ["Solve", "Factorise"], - }, { - id: 'aPlurality', - legend: 'Quadratic Equations: Coefficient a', - type: 'multiple-selection', - values: ["a=1", "a>1"], - }], - questionLists: [{ - id: 0, - n: 10, - questions: [{ - type: 'quadratic-equation', - variables: { aPlurality: 'aPlurality', framing: 'framing' }, - }] - }, { - id: 1, - n: 5, - questions: [{ - type: 'adding-fractions', - }] - }] -} diff --git a/app/root.tsx b/app/root.tsx @@ -1,81 +0,0 @@ -import { - isRouteErrorResponse, - Links, - Meta, - NavLink, - Outlet, - Scripts, - ScrollRestoration, -} from "react-router"; - -import type { Route } from "./+types/root"; -import "./styles/index.css"; - -import Window from "./components/Window.jsx"; - -// for like googy fonts and stuff? -export const links: Route.LinksFunction = () => [ - { - rel: "stylesheet", - href: "https://unpkg.com/98.css" - } -]; - -export function Layout({ children }: { children: React.ReactNode }) { - return ( - <html lang="en"> - <head> - <meta charSet="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <Meta /> - <Links /> - </head> - <body> - {children} - <ScrollRestoration /> - <Scripts /> - </body> - </html> - ); -} - -export default function App() { - return ( - <> - <nav> - <NavLink to='/' exact className='nav-element' activeClassName="active">Home</NavLink> - <NavLink to='/questions' className='nav-element' activeClassName="active">Questions</NavLink> - </nav> - <Outlet /> - </> - ); -} - -export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - let message = "Oops!"; - let details = "An unexpected error occurred."; - let stack: string | undefined; - - if (isRouteErrorResponse(error)) { - message = error.status === 404 ? "404" : "Error"; - details = - error.status === 404 - ? "The requested page could not be found." - : error.statusText || details; - } else if (import.meta.env.DEV && error && error instanceof Error) { - details = error.message; - stack = error.stack; - } - - return ( - <Window title="404 Error! (probably)"> - <h1>{message}</h1> - <p>{details}</p> - {stack && ( - <pre className="w-full p-4 overflow-x-auto"> - <code>{stack}</code> - </pre> - )} - </Window> - ); -} diff --git a/app/routes.ts b/app/routes.ts @@ -1,6 +0,0 @@ -import { type RouteConfig, index, route } from "@react-router/dev/routes"; - -export default [ - index("routes/home.tsx"), - route("questions", "./routes/Questions.tsx") -] satisfies RouteConfig; diff --git a/app/routes/Home.tsx b/app/routes/Home.tsx @@ -1,15 +0,0 @@ -import * as React from 'react'; -import Window from '../components/Window.jsx'; -import type { Route } from "./+types/home"; - -export function meta({}: Route.MetaArgs) { - return [ - { title: "TTMaths" }, - { name: "description", content: "Home Page" }, - ]; -} - - -export default function Home() { - return <Window title="poopwindow">poop butt</Window>; -} diff --git a/app/routes/Questions.tsx b/app/routes/Questions.tsx @@ -1,14 +0,0 @@ -import * as React from 'react'; -import type { Route } from "./+types/home"; -import TestQuestionWindow from '../components/TestQuestionWindow.jsx'; - -export function meta({}: Route.MetaArgs) { - return [ - { title: "qestion page :)" }, - { name: "description", content: "Nor sure" }, - ]; -} - -export default function Questions() { - return <TestQuestionWindow />; -} diff --git a/app/scripts/auto-maths.js b/app/scripts/auto-maths.js @@ -1,8 +0,0 @@ -import renderMathInElement from 'kern-typ/auto-render'; - -renderMathInElement(document.body, { - delimiters: [ - { left: '$$', right: '$$', display: true }, - { left: '$', right: '$', display: false }, - ], -}); diff --git a/app/scripts/utils.js b/app/scripts/utils.js @@ -1,30 +0,0 @@ -export function getRandomInt(min, max, seed) { - return min + Math.floor((max - min + 1) * getRandomNumber(seed)); -} - -export function getRandomChoice(arr, seed) { - return arr[getRandomInt(0, arr.length - 1, seed)]; -} - -export function getRandomBoolean(seed) { - return getRandomNumber(seed) < 0.5; -} - -export function getRandomNumber(seed) { - const x = 1e4 * Math.sin(7 * seed); - return x - Math.floor(x); -} - -export function getHCF(a, b) { - return b == 0 ? a : getHCF(b, a % b); -} - -export function formatNumber(x, parameters = {showPlus: false, showOne: true, showZero: true, suffix: ''}) { - if (!parameters.showZero && x == 0) { return ''; } - const plus = parameters.showPlus ? '+' : ''; - const sign = x < 0 ? '-' : plus; - x = Math.abs(x); - parameters.showOne || x == 1 && (x = ''); - parameters.suffix || (parameters.suffix = '') // if suffix undefined - return sign + x + parameters.suffix; -} diff --git a/app/styles/index.css b/app/styles/index.css @@ -1,3 +0,0 @@ -@import 'kern-typ/kern.css'; -@import './main.css'; -/*98.css is imported in root.tsx now*/ -\ No newline at end of file diff --git a/app/styles/main.css b/app/styles/main.css @@ -1,45 +0,0 @@ -body { - background-color: #008080; - margin: 0; -} - -nav { - width: 100vw; - border-bottom: 2px solid black; - background-color: #C0C0C0; - margin-bottom: 10px; -} -.nav-element { - padding: 3px 8px; - width: max-content; - min-width: 100px; - font-weight: bold; - display: inline-block; - margin: 5px; -} - -.window { - min-width: min-content; - max-width: max-content; -} - -fieldset { - margin-bottom: 7px; -} - -.field-border { - font-size: 1.25rem; - padding: 20px; -} - -.master-question-list { - margin: 0; - padding-left: 20px; - display: flex; - flex-wrap: wrap; - gap: 40px; -} - -.question-list { - list-style-type: lower-latin; -} diff --git a/package.json b/package.json @@ -1,31 +1,19 @@ { - "name": "react-template", - "private": true, - "type": "module", - "scripts": { - "build": "react-router build", - "dev": "react-router dev", - "start": "react-router-serve ./build/server/index.js", - "typecheck": "react-router typegen && tsc" - }, - "dependencies": { - "@react-router/node": "^8", - "@react-router/serve": "^8", - "98.css": "^0.1.21", - "isbot": "^5.1.36", - "kern-typ": "^0.2.0", - "react": "^19.2.7", - "react-dom": "^19.2.7", - "react-router": "^8" - }, - "devDependencies": { - "@react-router/dev": "^8", - "@tailwindcss/vite": "^4.2.2", - "@types/node": "^22", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "tailwindcss": "^4.2.2", - "typescript": "^5.9.3", - "vite": "^8.0.3" - } + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "98.css": "^0.1.21", + "kern-typ": "^0.2.0", + "preact": "^10.26.9", + "preact-iso": "^2.12.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.2", + "vite": "^7.0.4" + } } diff --git a/public/favicon.ico b/public/favicon.ico Binary files differ. diff --git a/react-router.config.ts b/react-router.config.ts @@ -1,7 +0,0 @@ -import type { Config } from "@react-router/dev/config"; - -export default { - // Config options... - // Server-side render by default, to enable SPA mode set this to `false` - ssr: false, -} satisfies Config; diff --git a/tsconfig.json b/tsconfig.json @@ -1,26 +0,0 @@ -{ - "include": [ - "**/*", - "**/.server/**/*", - "**/.client/**/*", - ".react-router/types/**/*" - ], - "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2022"], - "types": ["node", "vite/client"], - "target": "ES2022", - "module": "ES2022", - "moduleResolution": "bundler", - "jsx": "react-jsx", - "rootDirs": [".", "./.react-router/types"], - "paths": { - "~/*": ["./app/*"] - }, - "esModuleInterop": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true - } -} diff --git a/vite.config.ts b/vite.config.ts @@ -1,10 +0,0 @@ -import { reactRouter } from "@react-router/dev/vite"; -import tailwindcss from "@tailwindcss/vite"; -import { defineConfig } from "vite"; - -export default defineConfig({ - plugins: [tailwindcss(), reactRouter()], - resolve: { - tsconfigPaths: true, - }, -});