commit 7ea91e22583febdce93e08a4cf28737ea943595e Author: cloudwithax Date: Mon Feb 10 13:10:20 2025 -0500 init commit diff --git a/.bolt/config.json b/.bolt/config.json new file mode 100644 index 0000000..6b6787d --- /dev/null +++ b/.bolt/config.json @@ -0,0 +1,3 @@ +{ + "template": "bolt-vite-react-ts" +} diff --git a/.bolt/prompt b/.bolt/prompt new file mode 100644 index 0000000..d0c0a8f --- /dev/null +++ b/.bolt/prompt @@ -0,0 +1,8 @@ +For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. + +By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. + +Use icons from lucide-react for logos. + +Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags. + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..14834be Binary files /dev/null and b/bun.lockb differ diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..82c2e20 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + } +); diff --git a/index.html b/index.html new file mode 100644 index 0000000..63e7f53 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + + + How much does Google owe Russia? + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..feb68fd --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "vite-react-typescript-starter", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "lucide-react": "^0.344.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.9.1", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.18", + "eslint": "^9.9.1", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.11", + "globals": "^15.9.0", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", + "typescript": "^5.5.3", + "typescript-eslint": "^8.3.0", + "vite": "^5.4.2" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..0eb6ed1 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,247 @@ +import { useState, useEffect } from "react"; + +const INITIAL_AMOUNT = 324000; +const INITIAL_TIMESTAMP = 1619582400 * 1000; // Convert to milliseconds +const WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000; +const USD_TO_RUB_RATE = 97; // Average exchange rate as of 2025 + +function calculateDebt() { + const now = Date.now(); + const timeDiff = now - INITIAL_TIMESTAMP; + const weeksPassed = Math.floor(timeDiff / WEEK_IN_MS); + return INITIAL_AMOUNT * Math.pow(2, weeksPassed); +} + +function formatLargeNumber(num: number): string { + return num.toLocaleString("fullwide", { useGrouping: true }); +} + +function humanizeNumber(num: number): string { + const units = [ + "", + "thousand", + "million", + "billion", + "trillion", + "quadrillion", + "quintillion", + "sextillion", + "septillion", + "octillion", + "nonillion", + "decillion", + "undecillion", + "duodecillion", + "tredecillion", + "quattuordecillion", + "quindecillion", + "sexdecillion", + "septendecillion", + "octodecillion", + "novemdecillion", + "vigintillion", + "unvigintillion", + "duovigintillion", + "trevigintillion", + "quattuorvigintillion", + "quinvigintillion", + "sexvigintillion", + "septenvigintillion", + "octovigintillion", + "novemvigintillion", + "trigintillion", + "untrigintillion", + "duotrigintillion", + "googol", + "centillion", + ]; + + // Handle numbers larger than our named units + if (num >= Math.pow(10, units.length * 3)) { + const exponent = Math.floor(Math.log10(num)); + return `10^${exponent} (${exponent}-digit number)`; + } + + let unitIndex = 0; + let value = num; + + while (value >= 1000 && unitIndex < units.length - 1) { + value /= 1000; + unitIndex++; + } + + // Round to 2 decimal places + value = Math.round(value * 100) / 100; + + // For very large numbers, add scientific notation + const scientificNotation = unitIndex > 10 ? ` (10^${unitIndex * 3})` : ""; + + return `${value} ${units[unitIndex]}${scientificNotation}`; +} + +function getNextDoubleDate(): Date { + const now = Date.now(); + const weeksSinceStart = Math.floor((now - INITIAL_TIMESTAMP) / WEEK_IN_MS); + const nextDoubleTimestamp = + INITIAL_TIMESTAMP + (weeksSinceStart + 1) * WEEK_IN_MS; + return new Date(nextDoubleTimestamp); +} + +function App() { + const [debt, setDebt] = useState(calculateDebt()); + const [timeUntilDouble, setTimeUntilDouble] = useState(""); + + useEffect(() => { + const timer = setInterval(() => { + setDebt(calculateDebt()); + + const nextDouble = getNextDoubleDate(); + const timeLeft = nextDouble.getTime() - Date.now(); + + const days = Math.floor(timeLeft / (24 * 60 * 60 * 1000)); + const hours = Math.floor( + (timeLeft % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000) + ); + const minutes = Math.floor((timeLeft % (60 * 60 * 1000)) / (60 * 1000)); + const seconds = Math.floor((timeLeft % (60 * 1000)) / 1000); + + setTimeUntilDouble(`${days}d ${hours}h ${minutes}m ${seconds}s`); + }, 1000); + + return () => clearInterval(timer); + }, []); + + const rubleDebt = debt * USD_TO_RUB_RATE; + + return ( +
+ {/* Background gradient effects */} +
+
+ +
+
+ {/* Header */} +
+
+

+ How much does Google owe Russia? +

+
+
+ + {/* Main Content */} +
+
+ {/* USD Section */} +
+

+ Current Debt Amount* (USD): +

+
+
+ $ + + {formatLargeNumber(debt)} + +
+
+ ≈ {humanizeNumber(debt)} dollars +
+
+
+ + {/* Ruble Section */} +
+
+

+ Current Debt Amount* (RUB): +

+
+
+ + + {formatLargeNumber(rubleDebt)} + +
+
+ ≈ {humanizeNumber(rubleDebt)} rubles +
+
+ Based on the average exchange rate of 97 RUB per USD as of + 2025. +
+
+
+
+ + {/* Timer Section */} +
+
+

+ Next Doubling In: +

+
+ {timeUntilDouble} +
+
+
+ + {/* Info Section */} +
+
+
+ Started on: + + {new Date(INITIAL_TIMESTAMP).toLocaleDateString()} + +
+
+ Initial amount: + + ${formatLargeNumber(INITIAL_AMOUNT)} (₽ + {formatLargeNumber(INITIAL_AMOUNT * USD_TO_RUB_RATE)}) + +
+
+ Doubles every: + 7 days +
+
+ Exchange rate: + + 1 USD = {USD_TO_RUB_RATE} RUB + +
+
+
+
+ + {/* Disclaimer */} +
+

+ made with ❤️ by{" "} + + clxud + +

+

+ This is a satirical website. Numbers are fictional and for + entertainment purposes only. +

+

+ * Current amount is calculated based on the amount owed after + the grace period of nine months. +

+
+
+
+
+
+ ); +} + +export default App; diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico new file mode 100644 index 0000000..8a59336 Binary files /dev/null and b/src/assets/favicon.ico differ diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..0ebf5a7 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..d21f1cd --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..f0a2350 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..0d3d714 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..acf5769 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + optimizeDeps: { + exclude: ["lucide-react"], + }, +});