init commit
This commit is contained in:
commit
7ea91e2258
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"template": "bolt-vite-react-ts"
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -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?
|
||||||
|
|
@ -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 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/ico" href="src/assets/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>How much does Google owe Russia?</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -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<number>(calculateDebt());
|
||||||
|
const [timeUntilDouble, setTimeUntilDouble] = useState<string>("");
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="min-h-screen bg-[#0A0F1C] text-gray-100 overflow-hidden relative">
|
||||||
|
{/* Background gradient effects */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-red-500/10 to-blue-500/10 pointer-events-none" />
|
||||||
|
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1639322537228-f710d846310a?auto=format&fit=crop&q=80&w=3276')] opacity-5 bg-cover bg-center pointer-events-none" />
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-16 ">
|
||||||
|
<div className="inline-flex flex-col sm:flex-row items-center justify-center gap-4 mb-6 max-w-full">
|
||||||
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-red-400 to-blue-400 max-w-[800px] mx-auto leading-10">
|
||||||
|
How much does Google owe Russia?
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<div className="backdrop-blur-xl bg-white/[0.02] rounded-3xl border border-white/10 shadow-2xl overflow-hidden">
|
||||||
|
{/* USD Section */}
|
||||||
|
<div className="p-8 md:p-12">
|
||||||
|
<h2 className="text-xl text-gray-400 mb-4">
|
||||||
|
Current Debt Amount* (USD):
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="font-mono text-2xl sm:text-3xl md:text-4xl break-all bg-black/30 p-6 rounded-2xl border border-white/[0.05]">
|
||||||
|
<span className="text-emerald-400">$</span>
|
||||||
|
<span className="text-gray-100">
|
||||||
|
{formatLargeNumber(debt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-lg md:text-xl text-emerald-400/90 font-medium text-center">
|
||||||
|
≈ {humanizeNumber(debt)} dollars
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Ruble Section */}
|
||||||
|
<div className="border-t border-white/[0.05] bg-black/20">
|
||||||
|
<div className="p-8 md:p-12">
|
||||||
|
<h2 className="text-xl text-gray-400 mb-4">
|
||||||
|
Current Debt Amount* (RUB):
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="font-mono text-2xl sm:text-3xl md:text-4xl break-all bg-black/30 p-6 rounded-2xl border border-white/[0.05]">
|
||||||
|
<span className="text-emerald-400">₽</span>
|
||||||
|
<span className="text-gray-100">
|
||||||
|
{formatLargeNumber(rubleDebt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-lg md:text-xl text-emerald-400/90 font-medium text-center">
|
||||||
|
≈ {humanizeNumber(rubleDebt)} rubles
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400 text-center">
|
||||||
|
Based on the average exchange rate of 97 RUB per USD as of
|
||||||
|
2025.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Timer Section */}
|
||||||
|
<div className="border-t border-white/[0.05] bg-black/20">
|
||||||
|
<div className="p-8 md:p-12">
|
||||||
|
<h3 className="text-xl text-gray-400 mb-4">
|
||||||
|
Next Doubling In:
|
||||||
|
</h3>
|
||||||
|
<div className="font-mono text-2xl md:text-3xl text-blue-400 bg-black/30 p-6 rounded-2xl border border-white/[0.05]">
|
||||||
|
{timeUntilDouble}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info Section */}
|
||||||
|
<div className="border-t border-white/[0.05] bg-black/40">
|
||||||
|
<div className="p-8 md:p-12 space-y-3">
|
||||||
|
<div className="flex justify-between text-sm text-gray-400">
|
||||||
|
<span>Started on:</span>
|
||||||
|
<span className="text-gray-300">
|
||||||
|
{new Date(INITIAL_TIMESTAMP).toLocaleDateString()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm text-gray-400">
|
||||||
|
<span>Initial amount:</span>
|
||||||
|
<span className="text-gray-300">
|
||||||
|
${formatLargeNumber(INITIAL_AMOUNT)} (₽
|
||||||
|
{formatLargeNumber(INITIAL_AMOUNT * USD_TO_RUB_RATE)})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm text-gray-400">
|
||||||
|
<span>Doubles every:</span>
|
||||||
|
<span className="text-gray-300">7 days</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm text-gray-400">
|
||||||
|
<span>Exchange rate:</span>
|
||||||
|
<span className="text-gray-300">
|
||||||
|
1 USD = {USD_TO_RUB_RATE} RUB
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Disclaimer */}
|
||||||
|
<div className="flex flex-col gap-4 mt-4 text-center text-sm text-gray-500">
|
||||||
|
<p className="text-md mt-8">
|
||||||
|
made with ❤️ by{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/cloudwithax"
|
||||||
|
className="text-blue-400"
|
||||||
|
>
|
||||||
|
clxud
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This is a satirical website. Numbers are fictional and for
|
||||||
|
entertainment purposes only.
|
||||||
|
</p>
|
||||||
|
<p className="text-xs mt-4">
|
||||||
|
* Current amount is calculated based on the amount owed after
|
||||||
|
the grace period of nine months.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
@ -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(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./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"]
|
||||||
|
}
|
||||||
|
|
@ -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"],
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue