<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Short URL - Blogger URL Shortener</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
padding: 40px;
width: 100%;
max-width: 600px;
position: relative;
overflow: hidden;
}
.container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
}
.header {
text-align: center;
margin-bottom: 30px;
}
.logo {
font-size: 28px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
font-size: 16px;
}
.input-group {
margin-bottom: 20px;
}
.input-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
.input-group input {
width: 100%;
padding: 15px;
border: 2px solid #e1e1e1;
border-radius: 10px;
font-size: 16px;
transition: all 0.3s ease;
}
.input-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.custom-alias {
display: flex;
gap: 10px;
align-items: center;
}
.domain-prefix {
background: #f8f9fa;
padding: 15px;
border: 2px solid #e1e1e1;
border-radius: 10px;
font-weight: 600;
color: #666;
white-space: nowrap;
}
.alias-input {
flex: 1;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 30px;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
width: 100%;
margin-bottom: 20px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.result {
background: #f8f9fa;
border: 2px solid #e1e1e1;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
display: none;
}
.result.show {
display: block;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.result-title {
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.short-url {
background: white;
border: 2px solid #667eea;
border-radius: 8px;
padding: 12px;
font-family: monospace;
font-size: 16px;
word-break: break-all;
margin-bottom: 10px;
color: #667eea;
font-weight: 600;
}
.copy-btn {
background: #28a745;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: background 0.3s ease;
}
.copy-btn:hover {
background: #218838;
}
.copy-btn.copied {
background: #17a2b8;
}
.stats {
display: flex;
gap: 20px;
margin-top: 20px;
flex-wrap: wrap;
}
.stat-item {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border-radius: 10px;
text-align: center;
flex: 1;
min-width: 120px;
}
.stat-number {
font-size: 24px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 12px;
opacity: 0.8;
}
.url-list {
margin-top: 30px;
}
.url-item {
background: white;
border: 2px solid #e1e1e1;
border-radius: 10px;
padding: 15px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.url-info {
flex: 1;
min-width: 200px;
}
.original-url {
font-size: 14px;
color: #666;
margin-bottom: 5px;
word-break: break-all;
}
.short-url-item {
font-family: monospace;
color: #667eea;
font-weight: 600;
font-size: 16px;
cursor: pointer;
text-decoration: underline;
}
.short-url-item:hover {
color: #764ba2;
}
.url-actions {
display: flex;
gap: 10px;
align-items: center;
}
.click-count {
background: #f8f9fa;
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
color: #666;
}
.delete-btn {
background: #dc3545;
color: white;
border: none;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
}
.delete-btn:hover {
background: #c82333;
}
.error {
background: #f8d7da;
border: 2px solid #f5c6cb;
color: #721c24;
padding: 15px;
border-radius: 10px;
margin-bottom: 20px;
display: none;
}
.error.show {
display: block;
}
.redirect-container {
display: none;
text-align: center;
padding: 40px;
}
.redirect-container.show {
display: block;
}
.redirect-animation {
margin-bottom: 20px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.redirect-info {
color: #333;
font-size: 16px;
margin-bottom: 10px;
}
.redirect-url {
color: #667eea;
font-weight: 600;
word-break: break-all;
margin-bottom: 20px;
}
.redirect-timer {
color: #666;
font-size: 14px;
}
.manual-redirect {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
margin-top: 15px;
text-decoration: none;
display: inline-block;
}
.manual-redirect:hover {
background: #764ba2;
}
.info-box {
background: #e3f2fd;
border: 2px solid #bbdefb;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
}
.info-title {
font-weight: 600;
color: #1976d2;
margin-bottom: 10px;
}
.info-text {
color: #0d47a1;
line-height: 1.6;
}
@media (max-width: 600px) {
.container {
padding: 20px;
}
.custom-alias {
flex-direction: column;
}
.domain-prefix {
width: 100%;
text-align: center;
}
.stats {
flex-direction: column;
}
.url-item {
flex-direction: column;
align-items: flex-start;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Main Form -->
<div id="mainForm">
<div class="header">
<div class="logo">🔗 Short URL</div>
<div class="subtitle">Blogger URL Shortener - Buat link pendek untuk blog Anda</div>
</div>
<div class="info-box">
<div class="info-title">📋 Cara Menggunakan:</div>
<div class="info-text">
1. Masukkan URL yang ingin diperpendek<br>
2. (Opsional) Buat custom alias<br>
3. Klik "Perpendek URL"<br>
4. Copy dan bagikan URL pendek Anda<br>
5. Klik URL pendek untuk redirect otomatis
</div>
</div>
<div class="error" id="error"></div>
<div class="input-group">
<label for="originalUrl">URL Asli:</label>
<input type="url" id="originalUrl" placeholder="https://example.com/very-long-url-here">
</div>
<div class="input-group">
<label for="customAlias">Custom Alias (Opsional):</label>
<div class="custom-alias">
<div class="domain-prefix" id="domainPrefix">Loading...</div>
<input type="text" id="customAlias" class="alias-input" placeholder="custom-name">
</div>
</div>
<button class="btn" id="shortenBtn">
<span id="btnText">🚀 Perpendek URL</span>
</button>
<div class="result" id="result">
<div class="result-title">URL Pendek Berhasil Dibuat!</div>
<div class="short-url" id="shortUrl"></div>
<button class="copy-btn" id="copyBtn">📋 Copy URL</button>
</div>
<div class="stats">
<div class="stat-item">
<div class="stat-number" id="totalUrls">0</div>
<div class="stat-label">Total URL</div>
</div>
<div class="stat-item">
<div class="stat-number" id="totalClicks">0</div>
<div class="stat-label">Total Klik</div>
</div>
<div class="stat-item">
<div class="stat-number" id="todayUrls">0</div>
<div class="stat-label">Hari Ini</div>
</div>
</div>
<div class="url-list" id="urlList">
<h3 style="margin-bottom: 15px; color: #333;">📊 Daftar URL Terpendek</h3>
</div>
</div>
<!-- Redirect Container -->
<div class="redirect-container" id="redirectContainer">
<div class="redirect-animation">
<div class="spinner"></div>
<div class="redirect-info">🔄 Mengarahkan ke...</div>
<div class="redirect-url" id="redirectUrl"></div>
<div class="redirect-timer" id="redirectTimer">Redirect dalam 3 detik...</div>
</div>
<a href="#" class="manual-redirect" id="manualRedirect">Klik di sini jika tidak redirect otomatis</a>
</div>
</div>
<script>
class URLShortener {
constructor() {
this.urls = JSON.parse(localStorage.getItem('shortUrls') || '[]');
this.currentDomain = window.location.hostname || 'localhost';
this.currentPath = window.location.pathname.replace(/\/[^\/]*$/, '');
this.baseUrl = `${window.location.protocol}//${this.currentDomain}${this.currentPath}`;
this.init();
}
init() {
this.updateDomainDisplay();
this.checkForRedirect();
this.updateStats();
this.renderUrlList();
this.bindEvents();
}
updateDomainDisplay() {
document.getElementById('domainPrefix').textContent = `${this.currentDomain}/?s=`;
}
checkForRedirect() {
const urlParams = new URLSearchParams(window.location.search);
const shortCode = urlParams.get('s');
if (shortCode) {
this.handleRedirect(shortCode);
}
}
handleRedirect(shortCode) {
const url = this.urls.find(u => u.shortCode === shortCode);
if (url) {
// Update click count
url.clicks++;
this.saveUrls();
// Show redirect container
document.getElementById('mainForm').style.display = 'none';
document.getElementById('redirectContainer').classList.add('show');
// Update redirect info
document.getElementById('redirectUrl').textContent = url.originalUrl;
document.getElementById('manualRedirect').href = url.originalUrl;
// Start countdown
let countdown = 3;
const timer = document.getElementById('redirectTimer');
const interval = setInterval(() => {
countdown--;
timer.textContent = `Redirect dalam ${countdown} detik...`;
if (countdown <= 0) {
clearInterval(interval);
window.location.href = url.originalUrl;
}
}, 1000);
// Auto redirect after 3 seconds
setTimeout(() => {
window.location.href = url.originalUrl;
}, 3000);
} else {
// Short code not found
this.showError('❌ URL pendek tidak ditemukan!');
}
}
bindEvents() {
document.getElementById('shortenBtn').addEventListener('click', () => this.shortenUrl());
document.getElementById('copyBtn').addEventListener('click', () => this.copyToClipboard());
document.getElementById('originalUrl').addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.shortenUrl();
});
document.getElementById('customAlias').addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.shortenUrl();
});
}
generateShortCode() {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < 6; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
validateUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
}
showError(message) {
const errorDiv = document.getElementById('error');
errorDiv.textContent = message;
errorDiv.classList.add('show');
setTimeout(() => {
errorDiv.classList.remove('show');
}, 5000);
}
shortenUrl() {
const originalUrl = document.getElementById('originalUrl').value.trim();
const customAlias = document.getElementById('customAlias').value.trim();
const btn = document.getElementById('shortenBtn');
const btnText = document.getElementById('btnText');
if (!originalUrl) {
this.showError('❌ Silakan masukkan URL yang valid!');
return;
}
if (!this.validateUrl(originalUrl)) {
this.showError('❌ Format URL tidak valid! Pastikan URL dimulai dengan http:// atau https://');
return;
}
// Check if custom alias already exists
if (customAlias && this.urls.some(url => url.shortCode === customAlias)) {
this.showError('❌ Custom alias sudah digunakan! Silakan pilih yang lain.');
return;
}
// Validate custom alias
if (customAlias && !/^[a-zA-Z0-9-_]+$/.test(customAlias)) {
this.showError('❌ Custom alias hanya boleh menggunakan huruf, angka, - dan _');
return;
}
// Show loading state
btn.disabled = true;
btnText.textContent = '⏳ Memproses...';
setTimeout(() => {
const shortCode = customAlias || this.generateShortCode();
const shortUrl = `${this.baseUrl}?s=${shortCode}`;
const urlData = {
id: Date.now(),
originalUrl,
shortCode,
shortUrl,
clicks: 0,
createdAt: new Date().toISOString(),
createdDate: new Date().toDateString()
};
this.urls.unshift(urlData);
this.saveUrls();
this.showResult(shortUrl);
this.updateStats();
this.renderUrlList();
// Clear inputs
document.getElementById('originalUrl').value = '';
document.getElementById('customAlias').value = '';
// Reset button
btn.disabled = false;
btnText.textContent = '🚀 Perpendek URL';
}, 1000);
}
showResult(shortUrl) {
const resultDiv = document.getElementById('result');
const shortUrlDiv = document.getElementById('shortUrl');
shortUrlDiv.textContent = shortUrl;
resultDiv.classList.add('show');
// Scroll to result
resultDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
copyToClipboard() {
const shortUrl = document.getElementById('shortUrl').textContent;
const copyBtn = document.getElementById('copyBtn');
navigator.clipboard.writeText(shortUrl).then(() => {
const originalText = copyBtn.textContent;
copyBtn.textContent = '✅ Tersalin!';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.classList.remove('copied');
}, 2000);
}).catch(() => {
// Fallback for older browsers
const textarea = document.createElement('textarea');
textarea.value = shortUrl;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
copyBtn.textContent = '✅ Tersalin!';
setTimeout(() => {
copyBtn.textContent = '📋 Copy URL';
}, 2000);
});
}
updateStats() {
const totalUrls = this.urls.length;
const totalClicks = this.urls.reduce((sum, url) => sum + url.clicks, 0);
const today = new Date().toDateString();
const todayUrls = this.urls.filter(url => url.createdDate === today).length;
document.getElementById('totalUrls').textContent = totalUrls;
document.getElementById('totalClicks').textContent = totalClicks;
document.getElementById('todayUrls').textContent = todayUrls;
}
renderUrlList() {
const urlList = document.getElementById('urlList');
if (this.urls.length === 0) {
urlList.innerHTML = '<h3 style="margin-bottom: 15px; color: #333;">📊 Daftar URL Terpendek</h3><p style="text-align: center; color: #666; padding: 20px;">Belum ada URL yang diperpendek. Buat yang pertama!</p>';
return;
}
let html = '<h3 style="margin-bottom: 15px; color: #333;">📊 Daftar URL Terpendek</h3>';
this.urls.forEach(url => {
const createdDate = new Date(url.createdAt).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
html += `
<div class="url-item">
<div class="url-info">
<div class="original-url">📎 ${url.originalUrl}</div>
<div class="short-url-item" onclick="urlShortener.redirectTo('${url.shortCode}')">🔗 ${url.shortUrl}</div>
<div style="font-size: 12px; color: #999; margin-top: 5px;">📅 ${createdDate}</div>
</div>
<div class="url-actions">
<div class="click-count">👆 ${url.clicks} klik</div>
<button class="copy-btn" onclick="urlShortener.copyUrl('${url.shortUrl}')">📋</button>
<button class="delete-btn" onclick="urlShortener.deleteUrl(${url.id})">🗑️</button>
</div>
</div>
`;
});
urlList.innerHTML = html;
}
redirectTo(shortCode) {
window.location.href = `${this.baseUrl}?s=${shortCode}`;
}
copyUrl(shortUrl) {
navigator.clipboard.writeText(shortUrl).then(() => {
// Visual feedback
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #28a745;
color: white;
padding: 10px 20px;
border-radius: 5px;
z-index: 1000;
font-size: 14px;
`;
notification.textContent = '✅ URL berhasil disalin!';
document.body.appendChild(notification);
setTimeout(() => {
document.body.removeChild(notification);
}, 2000);
});
}
deleteUrl(id) {
if (confirm('Apakah Anda yakin ingin menghapus URL ini?')) {
this.urls = this.urls.filter(url => url.id !== id);
this.saveUrls();
this.updateStats();
this.renderUrlList();
}
}
saveUrls() {
localStorage.setItem('shortUrls', JSON.stringify(this.urls));
}
}
// Initialize the URL shortener
const urlShortener = new URLShortener();
</script>
</body>
</html>
Komentar
Posting Komentar