This commit is contained in:
Sewmina 2025-08-17 19:42:53 +05:30
parent 7acc0d0157
commit 2cdc49e86a
33 changed files with 15557 additions and 824 deletions

200
README.md
View File

@ -1,36 +1,194 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
# 🦖 Dino Game - Solana Token Website
## Getting Started
A modern, dino-themed website for your Chrome offline dino game, integrated with Solana blockchain for token-based gameplay.
First, run the development server:
## ✨ Features
- **Wallet Integration**: Connect Phantom and other Solana wallets
- **Token Payment**: Pay 1 DINO token to play the game
- **Buy Token Button**: Purchase DINO tokens directly from the website
- **Game Integration**: Seamless iframe integration for your Unity game
- **Dino Theme**: Beautiful green and yellow color scheme matching the game
- **Responsive Design**: Works on all devices
## 🚀 Getting Started
### Prerequisites
- Node.js 20.16.0 or higher
- npm or yarn package manager
- Solana wallet (Phantom recommended)
### Installation
1. Clone the repository:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
git clone <your-repo-url>
cd dino_landing_page
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
2. Install dependencies:
```bash
npm install
```
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
3. Start the development server:
```bash
npm run dev
```
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
4. Open [http://localhost:3000](http://localhost:3000) in your browser.
## Learn More
## 🔧 Configuration
To learn more about Next.js, take a look at the following resources:
### Update Your Wallet Address
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
In `app/components/GameSection.tsx`, replace the placeholder wallet address:
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
```typescript
// Replace with your actual wallet address
const RECIPIENT_WALLET = new PublicKey('YOUR_ACTUAL_WALLET_ADDRESS_HERE');
```
## Deploy on Vercel
### Customize Token Amount
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Currently set to 0.001 SOL for demo purposes. To implement actual DINO token transfers:
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
1. Create your DINO token on Solana
2. Update the transaction logic in `GameSection.tsx`
3. Use SPL Token Program for token transfers
### Integrate Your Unity Game
1. Export your Unity game to WebGL
2. Replace `public/game.html` with your game's `index.html`
3. Ensure your game works in an iframe
4. Test the integration
## 🎮 How It Works
1. **Connect Wallet**: Users connect their Solana wallet
2. **Buy Tokens**: Purchase DINO tokens (integrate with your preferred DEX)
3. **Pay to Play**: Send 1 DINO token to your wallet
4. **Game Launch**: Game opens in an iframe after successful payment
5. **Blockchain Integration**: All transactions are recorded on Solana
## 🎨 Customization
### Colors
Update the CSS variables in `app/globals.css`:
```css
:root {
--dino-green: #059669;
--dino-green-light: #10b981;
--dino-green-dark: #047857;
--dino-yellow: #eab308;
--dino-yellow-light: #fbbf24;
--dino-yellow-dark: #ca8a04;
}
```
### Styling
- Modify Tailwind classes in components
- Update CSS animations and transitions
- Customize button styles and hover effects
## 🔌 DEX Integration
To enable actual token purchases, integrate with:
- **Raydium**: Popular Solana DEX
- **Orca**: User-friendly DEX
- **Jupiter**: Best price aggregator
- **Metaplex**: NFT marketplace
Example Raydium integration:
```typescript
// In your buy token function
const raydiumUrl = `https://raydium.io/swap/?inputCurrency=sol&outputCurrency=${DINO_TOKEN_MINT}`;
window.open(raydiumUrl, '_blank');
```
## 🚀 Deployment
### Vercel (Recommended)
1. Push your code to GitHub
2. Connect your repository to Vercel
3. Deploy automatically on push
### Other Platforms
- **Netlify**: Drag and drop deployment
- **AWS Amplify**: Full-stack deployment
- **Heroku**: Traditional hosting
## 📱 Mobile Optimization
The website is fully responsive and includes:
- Touch-friendly buttons
- Mobile-optimized layouts
- Responsive iframe sizing
- Mobile wallet support
## 🔒 Security Considerations
- Always verify wallet connections
- Implement proper transaction validation
- Use HTTPS in production
- Validate iframe sources
- Implement rate limiting
## 🐛 Troubleshooting
### Common Issues
1. **Wallet not connecting**: Ensure wallet extension is installed
2. **Transaction failing**: Check SOL balance for fees
3. **Game not loading**: Verify iframe source and CORS settings
4. **Build errors**: Check Node.js version compatibility
### Debug Mode
Enable debug logging in development:
```typescript
// Add to components for debugging
console.log('Wallet connected:', publicKey?.toString());
console.log('Transaction status:', transactionStatus);
```
## 📚 Resources
- [Solana Documentation](https://docs.solana.com/)
- [Wallet Adapter](https://github.com/solana-labs/wallet-adapter)
- [Web3.js](https://solana-labs.github.io/solana-web3.js/)
- [Tailwind CSS](https://tailwindcss.com/)
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request
## 📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 🆘 Support
For support and questions:
- Create an issue in the repository
- Contact the development team
- Check the documentation
---
**Happy Gaming! 🦖🎮**

279
app/components/Header.tsx Normal file
View File

@ -0,0 +1,279 @@
'use client';
import { useState, useEffect, useImperativeHandle, forwardRef } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import { Connection, PublicKey } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import Image from 'next/image';
import '@solana/wallet-adapter-react-ui/styles.css';
import { CLUSTER_URL } from '../shared';
import { DINO_TOKEN_ADDRESS } from '../constants';
export interface HeaderRef {
refreshBalance: () => Promise<void>;
}
const Header = forwardRef<HeaderRef>((props, ref) => {
const { publicKey } = useWallet();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const [dinoBalance, setDinoBalance] = useState<number | null>(null);
const [isLoadingBalance, setIsLoadingBalance] = useState(false);
const fetchDinoBalance = async () => {
if (!publicKey) {
setDinoBalance(null);
return;
}
setIsLoadingBalance(true);
try {
const connection = new Connection(CLUSTER_URL);
const tokenMint = new PublicKey(DINO_TOKEN_ADDRESS);
// Get token accounts for the user
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
publicKey,
{ mint: tokenMint }
);
if (tokenAccounts.value.length > 0) {
const balance = tokenAccounts.value[0].account.data.parsed.info.tokenAmount.uiAmount;
setDinoBalance(balance);
} else {
setDinoBalance(0);
}
} catch (error) {
console.error('Error fetching DINO balance:', error);
setDinoBalance(null);
} finally {
setIsLoadingBalance(false);
}
};
// Expose refreshBalance function to parent component
useImperativeHandle(ref, () => ({
refreshBalance: fetchDinoBalance
}));
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 10);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => {
fetchDinoBalance();
}, [publicKey]);
const handleBuyDino = () => {
// Redirect to DexScreener - you can update this URL to the actual $DINO token page
window.open('https://dexscreener.com/solana', '_blank');
};
const formatBalance = (balance: number | null) => {
if (balance === null) return '0';
if (balance === 0) return '0';
if (balance < 0.0001) return '< 0.0001';
return balance.toFixed(4);
};
return (
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
isScrolled
? 'bg-white/95 backdrop-blur-xl shadow-xl shadow-gray-200/50 border-b border-gray-200/40'
: 'bg-white/80 backdrop-blur-lg shadow-lg shadow-gray-100/30 border-b border-gray-200/20'
}`}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-20">
{/* Logo and Title */}
<div className="flex items-center">
<div className="flex items-center space-x-3 group cursor-pointer">
<div className="w-10 h-10 bg-gradient-to-br from-green-500 to-emerald-600 rounded-xl flex items-center justify-center shadow-lg shadow-green-500/25 transition-all duration-300 group-hover:scale-110 group-hover:shadow-xl group-hover:shadow-green-500/40 overflow-hidden">
<Image
src="/dino_icon.jpg"
alt="DINO Token Icon"
width={40}
height={40}
className="w-full h-full object-cover"
/>
</div>
<h1 className="text-3xl font-extrabold bg-gradient-to-r from-gray-900 via-gray-800 to-gray-900 bg-clip-text text-transparent transition-all duration-300 group-hover:from-green-600 group-hover:to-emerald-600">
DINO
</h1>
</div>
</div>
{/* Desktop Navigation Links */}
<nav className="hidden md:flex items-center space-x-10">
<a
href="#support"
className="relative text-gray-700 hover:text-green-600 transition-all duration-300 font-semibold text-lg group"
>
Support
<span className="absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-green-500 to-emerald-600 transition-all duration-300 group-hover:w-full"></span>
</a>
<a
href="#whitepaper"
className="relative text-gray-700 hover:text-green-600 transition-all duration-300 font-semibold text-lg group"
>
Whitepaper
<span className="absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-green-500 to-emerald-600 transition-all duration-300 group-hover:w-full"></span>
</a>
<div className="flex items-center space-x-6">
<a
href="https://x.com"
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 bg-gray-100 hover:bg-green-100 rounded-full flex items-center justify-center transition-all duration-300 group hover:scale-110 hover:shadow-lg hover:shadow-green-500/25 hover:-translate-y-1"
>
<svg className="w-5 h-5 text-gray-600 group-hover:text-green-600 transition-colors duration-300" fill="currentColor" viewBox="0 0 24 24">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</a>
<a
href="https://t.me"
target="_blank"
rel="noopener noreferrer"
className="w-10 h-10 bg-gray-100 hover:bg-green-100 rounded-full flex items-center justify-center transition-all duration-300 group hover:scale-110 hover:shadow-lg hover:shadow-green-500/25 hover:-translate-y-1"
>
<svg className="w-5 h-5 text-gray-600 group-hover:text-green-600 transition-colors duration-300" fill="currentColor" viewBox="0 0 24 24">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
</svg>
</a>
</div>
</nav>
{/* Buy $DINO Button and Wallet Connect - Desktop */}
<div className="hidden md:flex items-center space-x-4">
<button
onClick={handleBuyDino}
className="bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-600 hover:to-red-600 text-white font-bold py-3 px-6 rounded-xl shadow-lg shadow-orange-500/25 hover:shadow-xl hover:shadow-orange-500/30 transition-all duration-300 hover:scale-105 hover:-translate-y-0.5 flex items-center space-x-2"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
</svg>
<span>Buy $DINO</span>
</button>
{publicKey ? (
<div className="flex items-center space-x-4">
{/* DINO Balance Display */}
<div className="bg-gradient-to-r from-green-100 to-emerald-50 rounded-full px-4 py-2 border border-green-200 shadow-sm">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-gradient-to-br from-green-500 to-emerald-600 rounded-full"></div>
<span className="text-sm text-green-700 font-mono font-medium">
{isLoadingBalance ? (
<div className="flex items-center space-x-1">
<div className="w-3 h-3 border-2 border-green-500 border-t-transparent rounded-full animate-spin"></div>
<span>Loading...</span>
</div>
) : (
`${formatBalance(dinoBalance)} $DINO`
)}
</span>
</div>
</div>
<WalletMultiButton className="!bg-gradient-to-r !from-green-600 !to-emerald-600 hover:!from-green-700 hover:!to-emerald-700 !text-white !rounded-xl !px-6 !py-3 !text-sm !font-semibold !shadow-lg !shadow-green-500/25 hover:!shadow-xl hover:!shadow-green-500/30 transition-all duration-300 hover:!scale-105 hover:!-translate-y-0.5" />
</div>
) : (
<WalletMultiButton className="!bg-gradient-to-r !from-green-600 !to-emerald-600 hover:!from-green-700 hover:!to-emerald-700 !text-white !rounded-xl !px-6 !py-3 !text-sm !font-semibold !shadow-lg !shadow-green-500/25 hover:!shadow-xl hover:!shadow-green-500/30 transition-all duration-300 hover:!scale-105 hover:!-translate-y-0.5" />
)}
</div>
{/* Mobile Menu Button */}
<div className="md:hidden flex items-center space-x-3">
<button
onClick={handleBuyDino}
className="bg-gradient-to-r from-orange-500 to-red-500 text-white font-bold py-2 px-4 rounded-lg shadow-lg shadow-orange-500/25 text-sm"
>
Buy $DINO
</button>
<WalletMultiButton className="!bg-gradient-to-r !from-green-600 !to-emerald-600 !text-white !rounded-lg !px-4 !py-2 !text-sm !font-semibold !shadow-lg !shadow-green-500/25" />
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="w-10 h-10 bg-gray-100 hover:bg-green-100 rounded-lg flex items-center justify-center transition-all duration-300 hover:scale-105"
>
<svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{isMobileMenuOpen ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
)}
</svg>
</button>
</div>
</div>
{/* Mobile Menu */}
{isMobileMenuOpen && (
<div className="md:hidden border-t border-gray-200/60 bg-white/95 backdrop-blur-sm animate-in slide-in-from-top-2 duration-300">
<div className="px-2 pt-2 pb-3 space-y-1">
<a
href="#support"
className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-green-600 hover:bg-green-50 transition-colors duration-200"
>
Support
</a>
<a
href="#whitepaper"
className="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-green-600 hover:bg-green-50 transition-colors duration-200"
>
Whitepaper
</a>
<div className="flex items-center space-x-4 px-3 py-2">
<span className="text-sm text-gray-500 font-medium">Socials:</span>
<a
href="https://x.com"
target="_blank"
rel="noopener noreferrer"
className="w-8 h-8 bg-gray-100 hover:bg-green-100 rounded-full flex items-center justify-center transition-all duration-300 hover:scale-110"
>
<svg className="w-4 h-4 text-gray-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</a>
<a
href="https://t.me"
target="_blank"
rel="noopener noreferrer"
className="w-8 h-8 bg-gray-100 hover:bg-green-100 rounded-full flex items-center justify-center transition-all duration-300 hover:scale-110"
>
<svg className="w-4 h-4 text-gray-600" fill="currentColor" viewBox="0 0 24 24">
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
</svg>
</a>
</div>
{publicKey && (
<div className="px-3 py-2">
<div className="bg-gradient-to-r from-green-100 to-emerald-50 rounded-lg px-3 py-2 border border-green-200 shadow-sm">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-gradient-to-br from-green-500 to-emerald-600 rounded-full"></div>
<span className="text-sm text-green-700 font-mono font-medium">
{isLoadingBalance ? (
<div className="flex items-center space-x-1">
<div className="w-3 h-3 border-2 border-green-500 border-t-transparent rounded-full animate-spin"></div>
<span>Loading...</span>
</div>
) : (
`${formatBalance(dinoBalance)} $DINO`
)}
</span>
</div>
</div>
</div>
)}
</div>
</div>
)}
</div>
</header>
);
});
export default Header;

View File

@ -0,0 +1,166 @@
'use client';
import { useState, useEffect } from 'react';
import { ENTRY_FEE_DINO, MAX_ATTEMPTS } from '../shared';
interface LeaderboardEntry {
owner: string;
score: string;
}
interface LeaderboardProps {
leaderboard: LeaderboardEntry[];
loading: boolean;
error: string | null;
attemptsCount: number;
}
export default function Leaderboard({ leaderboard, loading, error, attemptsCount }: LeaderboardProps) {
// Only show loading state if we have no data yet
if (loading && leaderboard.length === 0) {
return (
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4">🏆 Leaderboard</h3>
<div className="flex justify-center items-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-green-600"></div>
</div>
</div>
);
}
if (error) {
return (
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4">🏆 Leaderboard</h3>
<div className="text-center py-4">
<p className="text-red-600 text-sm">Failed to load leaderboard</p>
<button
onClick={() => window.location.reload()}
className="mt-2 text-green-600 hover:text-green-700 text-sm underline"
>
Try again
</button>
</div>
</div>
);
}
// Sort leaderboard by score (highest first)
const sortedLeaderboard = [...leaderboard].sort((a, b) =>
parseInt(b.score) - parseInt(a.score)
);
return (
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4">🏆 Leaderboard</h3>
{/* Combined Prize and Attempts Information */}
<div className="mb-6 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-lg border border-green-200">
<div className="flex items-center justify-between mb-3">
<h4 className="font-semibold text-green-800">💰 Prize ({MAX_ATTEMPTS * ENTRY_FEE_DINO} DINO Total)</h4>
<div className="text-right">
<div className="text-sm text-blue-700">🎯 Attempts Remaining:</div>
<div className="font-bold text-blue-800 text-lg">{MAX_ATTEMPTS - attemptsCount}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="flex items-center justify-between">
<span className="text-gray-700">🥇 1st Place:</span>
<span className="font-bold text-yellow-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.8).toFixed(1)} DINO (80%)</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-700">🥈 2nd Place:</span>
<span className="font-bold text-gray-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.1).toFixed(1)} DINO (10%)</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-700">🥉 3rd Place:</span>
<span className="font-bold text-orange-600">{(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.05).toFixed(1)} DINO (5%)</span>
</div>
</div>
<div className="mt-3 pt-3 border-t border-green-200">
<p className="text-xs text-blue-600 text-center">Leaderboard resets after {MAX_ATTEMPTS} attempts</p>
</div>
</div>
{sortedLeaderboard.length === 0 ? (
<div className="text-center py-8">
<p className="text-gray-500">No scores yet. Be the first to play!</p>
</div>
) : (
<div className="space-y-3">
{sortedLeaderboard.map((entry, index) => (
<div
key={`${entry.owner}-${entry.score}`}
className={`flex items-center justify-between p-3 rounded-lg ${
index === 0 ? 'bg-yellow-50 border border-yellow-200' :
index === 1 ? 'bg-gray-50 border border-gray-200' :
index === 2 ? 'bg-orange-50 border border-orange-200' :
'bg-gray-50 border border-gray-100'
}`}
>
<div className="flex items-center space-x-3">
<div className={`flex items-center justify-center w-8 h-8 rounded-full text-sm font-bold ${
index === 0 ? 'bg-yellow-400 text-yellow-900' :
index === 1 ? 'bg-gray-400 text-gray-900' :
index === 2 ? 'bg-orange-400 text-orange-900' :
'bg-gray-300 text-gray-700'
}`}>
{index + 1}
</div>
<div>
<p className="font-medium text-gray-900">
{entry.owner.length > 20
? `${entry.owner.slice(0, 8)}...${entry.owner.slice(-8)}`
: entry.owner
}
</p>
{entry.owner.length > 20 && (
<p className="text-xs text-gray-500 font-mono">
{entry.owner}
</p>
)}
{/* Prize indicators for top 3 */}
{index < 3 && (
<div className="flex items-center space-x-1 mt-1">
<span className={`text-xs px-2 py-1 rounded-full ${
index === 0 ? 'bg-yellow-100 text-yellow-700' :
index === 1 ? 'bg-gray-100 text-gray-700' :
'bg-orange-100 text-orange-700'
}`}>
{index === 0
? `🥇 ${(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.8).toFixed(1)} DINO Prize`
: index === 1
? `🥈 ${(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.1).toFixed(1)} DINO Prize`
: `🥉 ${(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.05).toFixed(1)} DINO Prize`
}
</span>
</div>
)}
</div>
</div>
<div className="text-right">
<p className="text-lg font-bold text-gray-900">{entry.score}</p>
<p className="text-xs text-gray-500">points</p>
</div>
</div>
))}
</div>
)}
<div className="mt-4 text-center">
<div className="flex items-center justify-center space-x-2">
<p className="text-xs text-gray-500">
Updates every 5 seconds {sortedLeaderboard.length} player{sortedLeaderboard.length !== 1 ? 's' : ''}
</p>
{loading && leaderboard.length > 0 && (
<div className="flex items-center space-x-1">
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-green-600"></div>
<span className="text-xs text-green-600">Refreshing...</span>
</div>
)}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,41 @@
'use client';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { ConnectionProvider, WalletProvider as SolanaWalletProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets';
import { clusterApiUrl } from '@solana/web3.js';
import { useMemo } from 'react';
// Import wallet adapter CSS
import '@solana/wallet-adapter-react-ui/styles.css';
interface WalletProviderProps {
children: React.ReactNode;
}
export default function WalletProvider({ children }: WalletProviderProps) {
// The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
const network = WalletAdapterNetwork.Devnet;
// You can also provide a custom RPC endpoint.
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
const wallets = useMemo(
() => [
new PhantomWalletAdapter(),
new SolflareWalletAdapter(),
],
[]
);
return (
<ConnectionProvider endpoint={endpoint}>
<SolanaWalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>
{children}
</WalletModalProvider>
</SolanaWalletProvider>
</ConnectionProvider>
);
}

5
app/constants.ts Normal file
View File

@ -0,0 +1,5 @@
import { PublicKey } from "@solana/web3.js";
export const DINO_TOKEN_ADDRESS = "diNoZ1L9UEiJMv8i43BjZoPEe2tywwaBTBnKQYf28FT";
export const FEE_COLLECTOR = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -1,26 +1,50 @@
@import "tailwindcss";
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
}
/* Custom base styles */
@layer base {
html {
scroll-behavior: smooth;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
/* Custom component styles */
@layer components {
.animate-in {
animation: fadeIn 0.3s ease-in-out;
}
.slide-in-from-top-2 {
animation: slideInFromTop 0.3s ease-out;
}
}
/* Custom animations */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideInFromTop {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@ -1,20 +1,10 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
import WalletProvider from "./components/WalletProvider";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "DINO - Solana Token",
description: "DINO token on Solana blockchain",
};
export default function RootLayout({
@ -24,10 +14,10 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<body>
<WalletProvider>
{children}
</WalletProvider>
</body>
</html>
);

View File

@ -1,103 +1,385 @@
import Image from "next/image";
'use client';
import { useState, useRef, useEffect } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import {createTransferInstruction, getAssociatedTokenAddress, createAssociatedTokenAccountInstruction } from '@solana/spl-token';
import Header, { HeaderRef } from './components/Header';
import Leaderboard from './components/Leaderboard';
import { CLUSTER_URL, ENTRY_FEE_DINO } from './shared';
import { DINO_TOKEN_ADDRESS, FEE_COLLECTOR } from './constants';
interface DashboardData {
leaderboard: Array<{ owner: string; score: string }>;
available_tx: string | null;
attempts_count:number;
}
export default function Home() {
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
const { publicKey, sendTransaction } = useWallet();
const [isProcessing, setIsProcessing] = useState(false);
const [txHash, setTxHash] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [hasTicket, setHasTicket] = useState(false);
const [ticketTxHash, setTicketTxHash] = useState<string | null>(null);
const [showPaymentSuccess, setShowPaymentSuccess] = useState(false);
const [highscore, setHighscore] = useState(0);
const [dashboardData, setDashboardData] = useState<DashboardData>({ leaderboard: [], available_tx: null, attempts_count: 0 });
const [dashboardLoading, setDashboardLoading] = useState(true);
const [dashboardError, setDashboardError] = useState<string | null>(null);
const headerRef = useRef<HeaderRef>(null);
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
// Poll for available tickets every 5 seconds (only when no ticket is available)
useEffect(() => {
if (!publicKey) {
setHasTicket(false);
return;
}
// Don't poll if we already have a ticket
if (hasTicket) {
return;
}
const checkTicket = async () => {
try {
const response = await fetch(`https://vps.playpoolstudios.com/dino/api/get_available_tx.php?owner=${publicKey.toString()}`);
if (response.ok) {
const ticketData = await response.text();
// If we get a valid transaction hash (not "0"), we have a ticket
const ticketAvailable = ticketData !== "0" && ticketData.trim().length > 0;
setHasTicket(ticketAvailable);
// If ticket becomes available, stop polling immediately
if (ticketAvailable) {
setTicketTxHash(ticketData);
return;
}
setTicketTxHash(null);
} else {
setHasTicket(false);
setTicketTxHash(null);
}
} catch (error) {
console.error('Error checking ticket:', error);
setHasTicket(false);
} finally {
}
};
// Check immediately
checkTicket();
// Set up polling every 5 seconds
const interval = setInterval(checkTicket, 5000);
// Cleanup interval on unmount, when publicKey changes, or when ticket becomes available
return () => clearInterval(interval);
}, [publicKey, hasTicket]);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin === window.location.origin && typeof event.data === "string") {
try {
const message = JSON.parse(event.data);
if (message?.type === "gameClose" && typeof message.status === "number") {
game_close_signal(message.status);
}
} catch (error) {
console.error("JSON parse error from Unity message:", error);
}
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
// Fetch dashboard data every 5 seconds
useEffect(() => {
const fetchDashboardData = async () => {
try {
setDashboardLoading(true);
const response = await fetch('https://vps.playpoolstudios.com/dino/api/get_dashboard_data.php');
if (!response.ok) {
throw new Error('Failed to fetch dashboard data');
}
const data: DashboardData = await response.json();
setDashboardData(data);
setDashboardError(null);
} catch (err) {
console.error('Error fetching dashboard data:', err);
setDashboardError(err instanceof Error ? err.message : 'Failed to fetch dashboard data');
} finally {
setDashboardLoading(false);
}
};
fetchDashboardData();
// Refresh dashboard data every 5 seconds
const interval = setInterval(fetchDashboardData, 5000);
return () => clearInterval(interval);
}, []);
const game_close_signal = (status: number) => {
console.log("game_close_signal", status);
setHasTicket(false);
setTicketTxHash(null);
};
// Auto-hide payment success panel after 5 seconds
useEffect(() => {
console.log('useEffect triggered:', { txHash, showPaymentSuccess });
if (txHash && !showPaymentSuccess) {
console.log('Starting progress animation');
setShowPaymentSuccess(true);
console.log('showPaymentSuccess set to true');
} else {
console.log('Condition not met:', { txHash: !!txHash, showPaymentSuccess });
}
}, [txHash, showPaymentSuccess]);
// Test useEffect
useEffect(() => {
console.log('Test useEffect - component mounted');
}, []);
const handleEnterGame = async () => {
if (!publicKey) {
setError('Please connect your wallet first');
return;
}
setIsProcessing(true);
setError(null);
setTxHash(null);
try {
const connection = new Connection(CLUSTER_URL);
const dinoMint = new PublicKey(DINO_TOKEN_ADDRESS);
// Get the user's DINO token account
const userTokenAccount = await getAssociatedTokenAddress(
dinoMint,
publicKey
);
// Get or create the fee collector's DINO token account
const feeCollectorTokenAccount = await getAssociatedTokenAddress(
dinoMint,
FEE_COLLECTOR
);
const transaction = new Transaction();
// Check if fee collector token account exists, if not create it
const feeCollectorAccountInfo = await connection.getAccountInfo(feeCollectorTokenAccount);
if (!feeCollectorAccountInfo) {
transaction.add(
createAssociatedTokenAccountInstruction(
publicKey,
feeCollectorTokenAccount,
FEE_COLLECTOR,
dinoMint
)
);
}
// Add the transfer instruction (1 DINO token = 1,000,000,000 lamports for 9 decimals)
const transferAmount = ENTRY_FEE_DINO * 1_000_000_000; // 1 DINO token
transaction.add(
createTransferInstruction(
userTokenAccount,
feeCollectorTokenAccount,
publicKey,
transferAmount
)
);
// Get recent blockhash
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = publicKey;
// Send the transaction
const signature = await sendTransaction(transaction, connection);
// Wait for confirmation
const confirmation = await connection.confirmTransaction(signature, 'confirmed');
if (confirmation.value.err) {
throw new Error('Transaction failed');
}
setTxHash(signature);
console.log('Transaction successful! Hash:', signature);
console.log('txHash state set to:', signature);
// Refresh the dino balance in header after successful payment
if (headerRef.current) {
await headerRef.current.refreshBalance();
}
// Immediately check for ticket availability after successful payment
try {
const ticketResponse = await fetch(`https://vps.playpoolstudios.com/dino/api/get_available_tx.php?owner=${publicKey.toString()}`);
if (ticketResponse.ok) {
const ticketData = await ticketResponse.text();
const ticketAvailable = ticketData !== "0" && ticketData.trim().length > 0;
setHasTicket(ticketAvailable);
}
} catch (ticketError) {
console.warn('Error checking ticket after payment:', ticketError);
}
// Send transaction to validator
try {
const validatorUrl = `https://solpay.playpoolstudios.com/tx/new?tx=${signature}&target_address=${FEE_COLLECTOR.toString()}&amount=1000000000&sender_address=${publicKey.toString()}&token_mint=${dinoMint.toString()}`;
// Add leaderboard entry
const leaderboardUrl = 'https://vps.playpoolstudios.com/dino/api/add_leaderboard.php';
const leaderboardData = new FormData();
leaderboardData.append('tx', signature);
leaderboardData.append('owner', publicKey.toString());
leaderboardData.append('score', '0'); // Initial score of 0
try {
const leaderboardResponse = await fetch(leaderboardUrl, {
method: 'POST',
body: leaderboardData
});
if (!leaderboardResponse.ok) {
console.warn('Failed to add leaderboard entry');
}
} catch (leaderboardError) {
console.warn('Error adding leaderboard entry:', leaderboardError);
}
const response = await fetch(validatorUrl);
if (response.ok) {
const result = await response.json();
console.log('Transaction validated:', result);
} else {
console.warn('Failed to validate transaction with server');
}
} catch (validationError) {
console.warn('Error validating transaction with server:', validationError);
}
} catch (err) {
console.error('Error processing payment:', err);
setError(err instanceof Error ? err.message : 'Failed to process payment');
} finally {
setIsProcessing(false);
}
};
return (
<div className="min-h-screen">
<Header ref={headerRef} />
{/* Game iframe - shown when user has a ticket */}
{hasTicket && (
<div className="w-full h-screen flex justify-center items-center bg-white">
<iframe
src={`/Build/index.html?tx=${ticketTxHash}&highscore=${highscore}`}
className="w-full h-full"
title="Dino Game"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
)}
{/* Main content - only shown when no ticket */}
{!hasTicket && (
<main className="pt-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="text-center">
<h2 className="text-4xl font-bold text-gray-900 mb-4">
Welcome to <span className="text-green-600">$DINO</span>
</h2>
<p className="text-xl text-gray-600 max-w-2xl mx-auto mb-8">
Beat the highscore to win the jackpot!
</p>
{/* Enter Button - only show when no ticket */}
{!hasTicket && (
<div className="flex flex-col items-center space-y-4">
<button
onClick={handleEnterGame}
disabled={!publicKey || isProcessing}
className={`px-8 py-4 text-xl font-bold rounded-xl shadow-lg transition-all duration-300 transform hover:scale-105 hover:-translate-y-1 ${
!publicKey
? 'bg-gray-400 cursor-not-allowed'
: isProcessing
? 'bg-yellow-500 cursor-wait'
: 'bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white shadow-green-500/25 hover:shadow-xl hover:shadow-green-500/30'
}`}
>
{!publicKey
? 'Connect Wallet to Enter'
: isProcessing
? 'Processing Payment...'
: `Enter Game - Pay ${ENTRY_FEE_DINO} $DINO`
}
</button>
{/* Status Messages */}
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg max-w-md">
<p className="text-sm">{error}</p>
</div>
)}
{txHash && (
<div
className={`bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg max-w-md transition-all duration-300 ease-in-out transform ${
showPaymentSuccess
? 'opacity-100 scale-100 translate-y-0'
: 'opacity-0 scale-95 translate-y-2'
}`}
>
<p className="text-sm font-semibold mb-2">Payment Successful!</p>
<p className="text-xs break-all">Transaction Hash: {txHash}</p>
<a
href={`https://explorer.solana.com/tx/${txHash}?cluster=devnet`}
target="_blank"
rel="noopener noreferrer"
className="text-xs underline hover:text-green-800"
>
View on Explorer
</a>
</div>
)}
</div>
)}
{/* Leaderboard */}
<div className="mt-12 max-w-2xl mx-auto">
<Leaderboard
leaderboard={dashboardData.leaderboard}
loading={dashboardLoading}
error={dashboardError}
attemptsCount={dashboardData.attempts_count}
/>
</div>
</div>
</div>
</main>
)}
</div>
);
}

7
app/shared.ts Normal file
View File

@ -0,0 +1,7 @@
import { clusterApiUrl } from "@solana/web3.js";
export const CLUSTER_URL = clusterApiUrl('devnet');
// Game configuration
export const ENTRY_FEE_DINO = 10; // Entry fee in DINO tokens
export const MAX_ATTEMPTS = 50; // Maximum attempts before leaderboard reset

14818
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,19 +9,27 @@
"lint": "next lint"
},
"dependencies": {
"@solana/spl-token": "^0.4.13",
"@solana/wallet-adapter-base": "^0.9.27",
"@solana/wallet-adapter-phantom": "^0.9.28",
"@solana/wallet-adapter-react": "^0.15.39",
"@solana/wallet-adapter-react-ui": "^0.9.39",
"@solana/wallet-adapter-wallets": "^0.19.37",
"@solana/web3.js": "^1.87.6",
"next": "15.4.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"next": "15.4.6"
"react-dom": "19.1.0"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"autoprefixer": "^10.4.16",
"eslint": "^9",
"eslint-config-next": "15.4.6",
"@eslint/eslintrc": "^3"
"postcss": "^8.4.32",
"tailwindcss": "^3.4.0",
"typescript": "^5"
}
}

View File

@ -1,5 +1,8 @@
const config = {
plugins: ["@tailwindcss/postcss"],
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
export default config;

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

View File

@ -0,0 +1,16 @@
body { padding: 0; margin: 0 }
#unity-container { position: absolute }
#unity-container.unity-desktop { left: 50%; top: 50%; transform: translate(-50%, -50%) }
#unity-container.unity-mobile { position: fixed; width: 100%; height: 100% }
#unity-canvas { background: #231F20 }
.unity-mobile #unity-canvas { width: 100%; height: 100% }
#unity-loading-bar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: none }
#unity-logo { width: 154px; height: 130px; background: url('unity-logo-dark.png') no-repeat center }
#unity-progress-bar-empty { width: 141px; height: 18px; margin-top: 10px; margin-left: 6.5px; background: url('progress-bar-empty-dark.png') no-repeat center }
#unity-progress-bar-full { width: 0%; height: 18px; margin-top: 10px; background: url('progress-bar-full-dark.png') no-repeat center }
#unity-footer { position: relative }
.unity-mobile #unity-footer { display: none }
#unity-webgl-logo { float:left; width: 204px; height: 38px; background: url('webgl-logo.png') no-repeat center }
#unity-build-title { float: right; margin-right: 10px; line-height: 38px; font-family: arial; font-size: 18px }
#unity-fullscreen-button { cursor:pointer; float: right; width: 38px; height: 38px; background: url('fullscreen-button.png') no-repeat center }
#unity-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

135
public/Build/index.html Normal file
View File

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Unity WebGL Player | TetrisMP</title>
<link rel="shortcut icon" href="TemplateData/favicon.ico">
<link rel="stylesheet" href="TemplateData/style.css">
</head>
<body>
<div id="unity-container" class="unity-desktop">
<canvas id="unity-canvas" width=1280 height=720 tabindex="-1"></canvas>
<div id="unity-loading-bar">
<div id="unity-logo"></div>
<div id="unity-progress-bar-empty">
<div id="unity-progress-bar-full"></div>
</div>
</div>
<div id="unity-warning"> </div>
<div id="unity-footer">
<div id="unity-webgl-logo"></div>
<div id="unity-fullscreen-button"></div>
<div id="unity-build-title">TetrisMP</div>
</div>
</div>
<script>
var container = document.querySelector("#unity-container");
var canvas = document.querySelector("#unity-canvas");
var loadingBar = document.querySelector("#unity-loading-bar");
var progressBarFull = document.querySelector("#unity-progress-bar-full");
var fullscreenButton = document.querySelector("#unity-fullscreen-button");
var warningBanner = document.querySelector("#unity-warning");
// Shows a temporary message banner/ribbon for a few seconds, or
// a permanent error message on top of the canvas if type=='error'.
// If type=='warning', a yellow highlight color is used.
// Modify or remove this function to customize the visually presented
// way that non-critical warnings and error messages are presented to the
// user.
function unityShowBanner(msg, type) {
function updateBannerVisibility() {
warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
}
var div = document.createElement('div');
div.innerHTML = msg;
warningBanner.appendChild(div);
if (type == 'error') div.style = 'background: red; padding: 10px;';
else {
if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
setTimeout(function () {
warningBanner.removeChild(div);
updateBannerVisibility();
}, 5000);
}
updateBannerVisibility();
}
var buildUrl = "Build";
var loaderUrl = buildUrl + "/prod.loader.js";
var config = {
dataUrl: buildUrl + "/prod.data",
frameworkUrl: buildUrl + "/prod.framework.js",
codeUrl: buildUrl + "/prod.wasm",
streamingAssetsUrl: "StreamingAssets",
companyName: "DefaultCompany",
productName: "TetrisMP",
productVersion: "1.0",
showBanner: unityShowBanner,
};
/*UNITY BRIDGE*/
function sendMessageToReact(message) {
if (window.parent) {
window.parent.postMessage(message, "*"); // Replace "*" with your React app's origin for security
} else {
console.error("Parent window not found.");
}
}
// By default, Unity keeps WebGL canvas render target size matched with
// the DOM size of the canvas element (scaled by window.devicePixelRatio)
// Set this to false if you want to decouple this synchronization from
// happening inside the engine, and you would instead like to size up
// the canvas DOM size and WebGL render target sizes yourself.
// config.matchWebGLToCanvasSize = false;
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
// Mobile device style: set canvas to 80% of screen size to account for iframe padding
var meta = document.createElement('meta');
meta.name = 'viewport';
meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
document.getElementsByTagName('head')[0].appendChild(meta);
container.className = "unity-mobile";
canvas.className = "unity-mobile";
// Set canvas size to 80% of viewport dimensions
canvas.style.height = "80vh";
// To lower canvas resolution on mobile devices to gain some
// performance, uncomment the following line:
// config.devicePixelRatio = 1;
} else {
// Desktop style: Render the game canvas in a window that can be maximized to fullscreen:
canvas.style.width = "1280px";
canvas.style.height = "720px";
}
loadingBar.style.display = "block";
var script = document.createElement("script");
script.src = loaderUrl;
script.onload = () => {
createUnityInstance(canvas, config, (progress) => {
progressBarFull.style.width = 100 * progress + "%";
}).then((unityInstance) => {
loadingBar.style.display = "none";
fullscreenButton.onclick = () => {
unityInstance.SetFullscreen(1);
};
}).catch((message) => {
alert(message);
});
};
document.body.appendChild(script);
</script>
</body>
</html>

BIN
public/dino_icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

68
public/game.html Normal file
View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dino Game - Unity Export</title>
<style>
body {
margin: 0;
padding: 0;
background: #87CEEB;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
}
.game-container {
text-align: center;
background: white;
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
max-width: 500px;
}
.dino-placeholder {
font-size: 8rem;
margin-bottom: 1rem;
}
.instructions {
color: #666;
line-height: 1.6;
}
.highlight {
color: #2563eb;
font-weight: bold;
}
</style>
</head>
<body>
<div class="game-container">
<div class="dino-placeholder">🦖</div>
<h1>Dino Game</h1>
<div class="instructions">
<p>This is a placeholder for your Unity game export.</p>
<p>To integrate your actual game:</p>
<ol style="text-align: left; display: inline-block;">
<li>Export your Unity game to WebGL</li>
<li>Replace this file with your game's <span class="highlight">index.html</span></li>
<li>Ensure your game works in an iframe</li>
<li>Test the integration</li>
</ol>
<p style="margin-top: 1rem;">
<strong>Current Status:</strong>
<span style="color: #059669;">✅ Payment Confirmed</span>
</p>
<p style="margin-top: 1rem;">
<strong>Game Ready:</strong>
<span style="color: #dc2626;">⏳ Waiting for Unity Export</span>
</p>
</div>
</div>
</body>
</html>

34
tailwind.config.ts Normal file
View File

@ -0,0 +1,34 @@
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
background: 'var(--background)',
foreground: 'var(--foreground)',
},
animation: {
'fade-in': 'fadeIn 0.3s ease-in-out',
'slide-in-from-top': 'slideInFromTop 0.3s ease-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideInFromTop: {
'0%': { opacity: '0', transform: 'translateY(-10px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
},
},
},
plugins: [],
}
export default config