:root {
--primary-color: #081173;
--primary-light: #0d1a9e;
--primary-dark: #051056;
}
.primary-bg {
background-color: var(--primary-color);
}
.primary-text {
color: var(--primary-color);
}
.primary-border {
border-color: var(--primary-color);
}
.primary-hover:hover {
background-color: var(--primary-light);
}
.gradient-bg {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
}
.glass-effect {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.tweet-card {
transition: all 0.3s ease;
transform: translateY(0);
}
.tweet-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 40px rgba(8, 17, 115, 0.1);
}
.animate-fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.character-counter {
transition: color 0.3s ease;
}
.character-counter.warning {
color: #f59e0b;
}
.character-counter.danger {
color: #ef4444;
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
class TweetGenerator {
constructor() {
this.currentCategory = null;
this.currentTweet = '';
this.recentTweets = [];
this.init();
}
init() {
this.bindEvents();
this.loadRecentTweets();
}
bindEvents() {
// Category buttons
document.querySelectorAll('.category-btn').forEach(btn => {
btn.addEventListener('click', (e) => this.selectCategory(e));
});
// Generate button
document.getElementById('generateBtn').addEventListener('click', () => this.generateTweet());
// Copy button
document.getElementById('copyBtn').addEventListener('click', () => this.copyTweet());
// Tweet button
document.getElementById('tweetBtn').addEventListener('click', () => this.openTwitter());
}
selectCategory(e) {
// Remove active class from all buttons
document.querySelectorAll('.category-btn').forEach(btn => {
btn.classList.remove('primary-border', 'primary-text', 'bg-blue-50');
});
// Add active class to clicked button
e.target.closest('.category-btn').classList.add('primary-border', 'primary-text', 'bg-blue-50');
this.currentCategory = e.target.closest('.category-btn').dataset.category;
}
generateTweet() {
if (!this.currentCategory) {
alert('Please select a category first!');
return;
}
// Add loading animation
const refreshIcon = document.getElementById('refreshIcon');
refreshIcon.style.animation = 'spin 1s linear infinite';
document.getElementById('generateBtn').disabled = true;
setTimeout(() => {
const keywords = document.getElementById('keywords').value;
const tweet = this.createTweet(this.currentCategory, keywords);
this.displayTweet(tweet);
// Reset button
refreshIcon.style.animation = '';
document.getElementById('generateBtn').disabled = false;
}, 1000);
}
createTweet(category, keywords) {
const templates = {
motivation: [
"Success isn't just about what you accomplish in your life, it's about what you inspire others to do. {keywords} #Motivation #Success",
"The only way to do great work is to love what you do. Start today! {keywords} #Inspiration #Growth",
"Don't wait for opportunity. Create it. Every small step counts! {keywords} #Motivation #Hustle",
"Your limitationโit's only your imagination. Break through barriers today! {keywords} #Mindset #Success",
"Push yourself because no one else is going to do it for you. {keywords} #Motivation #SelfImprovement"
],
tech: [
"The future belongs to those who learn more skills and combine them in creative ways. {keywords} #Tech #Innovation",
"Code is poetry written in logic. Every bug fixed is a step toward perfection. {keywords} #Programming #Code",
"AI is not here to replace humans but to augment our capabilities. {keywords} #AI #Technology",
"The best way to predict the future is to invent it. Start coding! {keywords} #Tech #Development",
"Clean code always looks like it was written by someone who cares. {keywords} #Programming #BestPractices"
],
business: [
"Your network is your net worth. Invest in relationships that matter. {keywords} #Business #Networking",
"Innovation distinguishes between a leader and a follower. {keywords} #Leadership #Innovation",
"The customer's perception is your reality. Focus on value creation. {keywords} #Business #CustomerFirst",
"Fail fast, learn faster. Every setback is a setup for a comeback. {keywords} #Entrepreneurship #Growth",
"Culture eats strategy for breakfast. Build a team that believes. {keywords} #Leadership #Culture"
],
lifestyle: [
"Life is 10% what happens to you and 90% how you react to it. {keywords} #Lifestyle #Mindfulness",
"Happiness is not a destination, it's a way of life. Choose joy daily! {keywords} #Wellness #Happiness",
"Self-care isn't selfish. You can't pour from an empty cup. {keywords} #SelfCare #Wellness",
"Life begins at the end of your comfort zone. Take that leap today! {keywords} #Growth #Adventure",
"Balance is not about perfect equilibrium, but about conscious choices. {keywords} #WorkLifeBalance #Mindfulness"
]
};
const categoryTemplates = templates[category] || templates.motivation;
const randomTemplate = categoryTemplates[Math.floor(Math.random() * categoryTemplates.length)];
// Replace {keywords} with actual keywords or remove if empty
let tweet = randomTemplate;
if (keywords.trim()) {
const keywordArray = keywords.split(',').map(k => k.trim()).filter(k => k);
const hashtagKeywords = keywordArray.map(k => `#${k.replace(/\s+/g, '')}`).join(' ');
tweet = tweet.replace('{keywords}', hashtagKeywords);
} else {
tweet = tweet.replace('{keywords}', '').trim();
}
return tweet;
}
displayTweet(tweet) {
this.currentTweet = tweet;
const container = document.getElementById('tweetContainer');
container.innerHTML = `
<div class="animate-fade-in bg-white rounded-lg p-6 border border-gray-200 shadow-sm">
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
<div class="w-10 h-10 rounded-full primary-bg flex items-center justify-center">
<ion-icon name="person" class="text-white"></ion-icon>
</div>
</div>
<div class="flex-1">
<div class="flex items-center space-x-2 mb-2">
<span class="font-semibold text-gray-900">You</span>
<span class="text-gray-500 text-sm">@yourusername</span>
<span class="text-gray-500 text-sm">โข</span>
<span class="text-gray-500 text-sm">now</span>
</div>
<p class="text-gray-900 leading-relaxed">${tweet}</p>
</div>
</div>
</div>
`;
this.updateCharacterCount(tweet);
this.enableActionButtons();
this.addToRecentTweets(tweet);
}
updateCharacterCount(tweet) {
const count = tweet.length;
const counter = document.getElementById('charCount');
counter.textContent = `${count}/280`;
// Update color based on character count
counter.className = 'character-counter text-sm font-semibold';
if (count > 240) {
counter.classList.add('danger');
} else if (count > 200) {
counter.classList.add('warning');
}
}
enableActionButtons() {
document.getElementById('copyBtn').disabled = false;
document.getElementById('tweetBtn').disabled = false;
}
async copyTweet() {
try {
await navigator.clipboard.writeText(this.currentTweet);
this.showToast('Tweet copied to clipboard!');
} catch (err) {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = this.currentTweet;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
this.showToast('Tweet copied to clipboard!');
}
}
openTwitter() {
const tweetText = encodeURIComponent(this.currentTweet);
const url = `https://twitter.com/intent/tweet?text=${tweetText}`;
window.open(url, '_blank');
}
addToRecentTweets(tweet) {
this.recentTweets.unshift({
text: tweet,
timestamp: new Date(),
category: this.currentCategory
});
// Keep only last 5 tweets
if (this.recentTweets.length > 5) {
this.recentTweets = this.recentTweets.slice(0, 5);
}
this.saveRecentTweets();
this.displayRecentTweets();
}
displayRecentTweets() {
const container = document.getElementById('recentTweets');
if (this.recentTweets.length === 0) {
container.innerHTML = `
<div class="text-center text-gray-500 py-8">
<ion-icon name="albums-outline" class="text-4xl mb-2"></ion-icon>
<p>Your generated tweets will appear here</p>
</div>
`;
return;
}
container.innerHTML = this.recentTweets.map((tweet, index) => `
<div class="tweet-card bg-gray-50 rounded-lg p-4 border border-gray-200">
<div class="flex justify-between items-start mb-2">
<span class="text-xs text-gray-500 bg-gray-200 px-2 py-1 rounded-full capitalize">${tweet.category}</span>
<span class="text-xs text-gray-500">${this.formatTime(tweet.timestamp)}</span>
</div>
<p class="text-gray-800 text-sm leading-relaxed mb-3">${tweet.text}</p>
<div class="flex justify-end space-x-2">
<button onclick="tweetGen.copySpecificTweet('${tweet.text.replace(/'/g, "\\'")}')" class="text-xs bg-green-100 text-green-700 px-3 py-1 rounded-md hover:bg-green-200 transition-colors duration-200">
<ion-icon name="copy" class="text-sm mr-1"></ion-icon>
Copy
</button>
<button onclick="tweetGen.tweetSpecific('${encodeURIComponent(tweet.text)}')" class="text-xs bg-blue-100 text-blue-700 px-3 py-1 rounded-md hover:bg-blue-200 transition-colors duration-200">
<ion-icon name="logo-twitter" class="text-sm mr-1"></ion-icon>
Tweet
</button>
</div>
</div>
`).join('');
}
async copySpecificTweet(text) {
try {
await navigator.clipboard.writeText(text);
this.showToast('Tweet copied to clipboard!');
} catch (err) {
this.showToast('Failed to copy tweet');
}
}
tweetSpecific(encodedText) {
const url = `https://twitter.com/intent/tweet?text=${encodedText}`;
window.open(url, '_blank');
}
formatTime(date) {
const now = new Date();
const diff = now - date;
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return 'just now';
if (minutes < 60) return `${minutes}m ago`;
if (minutes < 1440) return `${Math.floor(minutes / 60)}h ago`;
return `${Math.floor(minutes / 1440)}d ago`;
}
showToast(message) {
const toast = document.createElement('div');
toast.className = 'fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50 animate-fade-in';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
saveRecentTweets() {
localStorage.setItem('recentTweets', JSON.stringify(this.recentTweets));
}
loadRecentTweets() {
const saved = localStorage.getItem('recentTweets');
if (saved) {
this.recentTweets = JSON.parse(saved).map(tweet => ({
...tweet,
timestamp: new Date(tweet.timestamp)
}));
this.displayRecentTweets();
}
}
}
// Initialize the app
const tweetGen = new TweetGenerator();
// Add CSS for spin animation
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
No comments yet. Be the first!