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