-
-
Save ekanna/a45d85dd6794e60b41c21bde497545c5 to your computer and use it in GitHub Desktop.
WiFi QR Code, single HTML file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>WiFi Login</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <!-- https://news.ycombinator.com/item?id=26923316 --> | |
| <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔐</text></svg>"> | |
| <style> | |
| body, textarea { | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | |
| font-size: 16px; | |
| } | |
| main { | |
| margin: 0 auto; | |
| max-width: 400px; | |
| text-align: center; | |
| } | |
| canvas { | |
| height: 200px; | |
| width: 200px; | |
| } | |
| label { | |
| display: block; | |
| margin: 12px; | |
| text-align: left; | |
| } | |
| textarea { | |
| box-sizing: border-box; | |
| font-family: Menlo, Consola, monospace; | |
| font-weight: bold; | |
| margin-top: 4px; | |
| padding: 8px; | |
| width: 100%; | |
| } | |
| textarea:invalid { | |
| border: 2px dashed red; | |
| } | |
| /* https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/ */ | |
| @supports (display: grid) { | |
| .input { | |
| /* easy way to plop the elements on top of each other and have them both sized based on the tallest one's height */ | |
| display: grid; | |
| } | |
| .input::after { | |
| /* Note the weird space! Needed to preventy jumpy behavior */ | |
| content: attr(data-replicated-value) " "; | |
| /* This is how textarea text behaves */ | |
| white-space: pre-wrap; | |
| /* Hidden from view, clicks, and screen readers */ | |
| visibility: hidden; | |
| } | |
| .input > textarea { | |
| /* You could leave this, but after a user resizes, then it ruins the auto sizing */ | |
| resize: none; | |
| /* Firefox shows scrollbar on growth, you can hide like this. */ | |
| overflow: hidden; | |
| } | |
| .input > textarea, | |
| .input::after { | |
| /* Identical styling required!! */ | |
| border: 1px solid black; | |
| border-radius: 4px; | |
| box-sizing: border-box; | |
| margin-top: 4px; | |
| padding: 8px; | |
| font: inherit; | |
| /* Place on top of each other */ | |
| grid-area: 1 / 1 / 2 / 2; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <h1>WiFi Login</h1> | |
| <p>Scan with your phone to join automatically!</p> | |
| <canvas id="qr"></canvas> | |
| <label> | |
| Network Name | |
| <div class="input"> | |
| <textarea | |
| id="ssid" | |
| type="text" | |
| rows="1" | |
| autocapitalize="off" | |
| autocorrect="off" | |
| autofocus="true"></textarea> | |
| </div> | |
| </label> | |
| <label> | |
| Password | |
| <div class="input"> | |
| <textarea | |
| id="password" | |
| type="text" | |
| minlength="8" | |
| rows="1" | |
| autocapitalize="off" | |
| autocorrect="off" | |
| title="Must be at least 8 characters."></textarea> | |
| </div> | |
| </label> | |
| </main> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script> | |
| <script> | |
| (function() { | |
| var qr = new QRious({ | |
| element: document.getElementById('qr'), | |
| value: 'https://github.com/neocotic/qrious', | |
| size: 400 // twice the size for extra crispiness | |
| }); | |
| var ssidInput = document.getElementById('ssid'); | |
| var passwordInput = document.getElementById('password'); | |
| // https://github.com/bndw/wifi-card/blob/master/src/components/Card.js | |
| var needsEscape = {'"': true, ';': true, ',': true, ':': true, '\\': true}; | |
| function escape(v) { | |
| return v.split('').map(c => needsEscape[c] ? '\\' + c : c).join(''); | |
| } | |
| function updateQRCode() { | |
| this.parentNode.dataset.replicatedValue = this.value; | |
| var ssid = escape(ssidInput.value); | |
| var password = escape(passwordInput.value); | |
| qr.value = 'WIFI:T:WPA;S:' + ssid + ';P:' + password + ';;'; | |
| } | |
| ssidInput.addEventListener('input', updateQRCode); | |
| passwordInput.addEventListener('input', updateQRCode); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |
<title>App Scan Wi-Fi Sederhana</title>
<style>
/* --- CSS Styles --- */
:root {
--primary-color: #007AFF;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--text-color: #333;
--signal-low: #ff3b30;
--signal-med: #ffcc00;
--signal-high: #34c759;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 0;
display: flex;
justify-content: center;
min-height: 100vh;
}
.app-container {
width: 100%;
max-width: 400px;
background: var(--card-bg);
box-shadow: 0 0 20px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
header {
background: var(--primary-color);
color: white;
padding: 20px;
text-align: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 10;
}
h1 { margin: 0; font-size: 1.2rem; }
.controls {
padding: 15px;
text-align: center;
border-bottom: 1px solid #eee;
}
button#scanBtn {
background: var(--primary-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 20px;
font-weight: 600;
cursor: pointer;
transition: transform 0.1s;
width: 100%;
}
button#scanBtn:active { transform: scale(0.98); }
.network-list {
flex: 1;
overflow-y: auto;
padding: 10px;
}
/* Loading Animation */
.loader {
display: none;
text-align: center;
padding: 20px;
color: #888;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid var(--primary-color);
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
/* Network Item Card */
.wifi-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
margin-bottom: 10px;
background: #fff;
border: 1px solid #e1e1e1;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
}
.wifi-item:hover { background: #f9f9f9; border-color: var(--primary-color); }
.wifi-info { display: flex; align-items: center; gap: 10px; }
.icon { font-size: 1.5rem; }
.details h3 { margin: 0; font-size: 1rem; margin-bottom: 4px; }
.details p { margin: 0; font-size: 0.8rem; color: #666; }
/* Signal Strength Bar Graphic */
.signal-bars {
display: flex;
align-items: flex-end;
gap: 2px;
height: 16px;
}
.bar {
width: 4px;
background: #ddd;
border-radius: 2px;
}
.bar.active { background: var(--primary-color); }
/* Dynamic colors for signal strength */
.sig-low .bar:nth-child(1) { background: var(--signal-low); }
.sig-med .bar:nth-child(1), .sig-med .bar:nth-child(2) { background: var(--signal-med); }
.sig-high .bar { background: var(--signal-high); }
/* Modal Styles */
.modal-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: flex-end; /* Slide up effect like mobile */
justify-content: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
z-index: 100;
}
.modal-overlay.active { opacity: 1; pointer-events: all; }
.modal-content {
background: white;
width: 100%;
max-width: 400px;
border-radius: 20px 20px 0 0;
padding: 20px;
transform: translateY(100%);
transition: transform 0.3s;
box-sizing: border-box;
}
.modal-overlay.active .modal-content { transform: translateY(0); }
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.close-modal { background: none; border: none; font-size: 1.5rem; cursor: pointer; }
.password-input {
width: 100%;
padding: 15px;
border: 1px solid #ccc;
border-radius: 8px;
margin-bottom: 15px;
font-size: 1rem;
box-sizing: border-box;
}
.connect-btn {
width: 100%;
padding: 15px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
}
</style>
<div class="app-container">
<header>
<h1>Pengaturan Wi-Fi</h1>
</header>
<div class="controls">
<button id="scanBtn" onclick="startScan()">Scan Jaringan</button>
</div>
<div id="loader" class="loader">
<div class="spinner"></div>
<p>Mencari jaringan...</p>
</div>
<div id="networkList" class="network-list">
<!-- Daftar kosong di sini -->
<div style="text-align:center; color:#aaa; margin-top:50px;">
Tekan tombol Scan untuk mencari Wi-Fi
</div>
</div>
<!-- Pop up Modal -->
<div id="modal" class="modal-overlay">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">Nama Jaringan</h2>
<button class="close-modal" onclick="closeModal()">×</button>
</div>
<div id="modalBody">
<p>Terenkripsi (WPA2)</p>
<input type="password" id="wifiPassword" class="password-input" placeholder="Masukkan password">
<button class="connect-btn" onclick="connectWifi()">Hubungkan</button>
</div>
<div id="modalSuccess" class="hidden" style="text-align: center;">
<div style="font-size: 3rem;">✅</div>
<h3>Terhubung!</h3>
<p>Internet sudah aktif.</p>
<button class="connect-btn" onclick="closeModal()">Tutup</button>
</div>
</div>
</div>
</div>
<script>
// --- JavaScript Logic ---
// 1. Data Dummy (Simulasi)
const dummyNetworks = [
{ ssid: "Wifi_Kantor_Utama", signal: 90, security: "WPA2", locked: true },
{ ssid: "Wifi_Rumah", signal: 75, security: "WPA2", locked: true },
{ ssid: "Tether_Saya", signal: 60, security: "WPA2", locked: true },
{ ssid: "Internet_Gratis", signal: 45, security: "Terbuka", locked: false },
{ ssid: "Tetangga_Subagjo", signal: 30, security: "WPA2", locked: true },
{ ssid: "Starbucks_Official", signal: 85, security: "WPA2", locked: true },
{ ssid: "HUTAN_LINDUNG", signal: 20, security: "WEP", locked: true }
];
let selectedNetwork = null;
// 2. Fungsi Memulai Scan
function startScan() {
const listContainer = document.getElementById('networkList');
const loader = document.getElementById('loader');
const btn = document.getElementById('scanBtn');
// Reset UI
listContainer.innerHTML = '';
btn.disabled = true;
btn.innerText = "Scanning...";
loader.style.display = 'block';
// Simulasi jeda waktu (2 detik) untuk efek scanning
setTimeout(() => {
loader.style.display = 'none';
btn.disabled = false;
btn.innerText = "Scan Ulang";
renderList(dummyNetworks);
}, 2000);
}
// 3. Fungsi Render Daftar
function renderList(networks) {
const listContainer = document.getElementById('networkList');
// Urutkan berdasarkan sinyal terkuat
networks.sort((a, b) => b.signal - a.signal);
networks.forEach((net, index) => {
// Tentukan kelas kekuatan sinyal untuk CSS
let signalClass = 'sig-high';
if (net.signal < 40) signalClass = 'sig-low';
else if (net.signal < 70) signalClass = 'sig-med';
// Icon Kunci
const lockIcon = net.locked ? '🔒' : '🔓';
// HTML每一项
const itemHtml = `
<div class="wifi-item" onclick="selectNetwork('${net.ssid}', '${net.security}', ${net.locked})">
<div class="wifi-info">
<div class="icon">📶</div>
<div class="details">
<h3>${net.ssid} ${lockIcon}</h3>
<p>${net.security}</p>
</div>
</div>
<div class="signal-bars ${signalClass}">
<div class="bar" style="height: 30%;"></div>
<div class="bar" style="height: 50%;"></div>
<div class="bar" style="height: 70%;"></div>
<div class="bar" style="height: 100%;"></div>
</div>
</div>
`;
listContainer.innerHTML += itemHtml;
});
}
// 4. Fungsi Ketika Network Dipilih
function selectNetwork(ssid, security, isLocked) {
selectedNetwork = { ssid, security, isLocked };
const modal = document.getElementById('modal');
const title = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
const modalSuccess = document.getElementById('modalSuccess');
// Reset Modal ke state awal
document.getElementById('wifiPassword').value = '';
modalBody.style.display = 'block';
modalSuccess.style.display = 'none';
title.innerText = ssid;
if (!isLocked) {
// Kalau terbuka, langsung muncul menu hubung atau koneksi otomatis
modalBody.querySelector('p').innerText = "Jaringan ini tidak terenkripsi.";
modalBody.querySelector('button').innerText = "Sambungkan";
} else {
modalBody.querySelector('p').innerText = `Keamanan: ${security}`;
modalBody.querySelector('button').innerText = "Hubungkan";
}
modal.classList.add('active');
}
// 5. Fungsi Menghubungkan (Simulasi)
function connectWifi() {
const passInput = document.getElementById('wifiPassword');
const modalBody = document.getElementById('modalBody');
const modalSuccess = document.getElementById('modalSuccess');
if (selectedNetwork.isLocked && passInput.value.trim() === "") {
alert("Mohon masukkan password Wi-Fi!");
return;
}
// Simulasi verifikasi (langsung sukses saja untuk contoh ini)
// Tampilkan UI Sukses
modalBody.style.display = 'none';
modalSuccess.style.display = 'block';
}
// 6. Fungsi Tutup Modal
function closeModal() {
document.getElementById('modal').classList.remove('active');
}
</script>
<title>Wifi Scanner Simulasi</title>
<style>
/* --- CSS Styles --- */
:root {
--primary: #00ff88;
--bg: #0f172a;
--card-bg: #1e293b;
--text: #f1f5f9;
--text-muted: #94a3b8;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body {
background-color: var(--bg);
color: var(--text);
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
max-width: 600px;
width: 100%;
}
header {
text-align: center;
margin-bottom: 30px;
}
h1 {
color: var(--primary);
font-size: 2rem;
margin-bottom: 10px;
}
p.subtitle {
color: var(--text-muted);
font-size: 0.9rem;
}
/* Search Bar */
.search-box {
margin-bottom: 20px;
position: relative;
}
input[type="text"] {
width: 100%;
padding: 12px 20px;
border-radius: 25px;
border: 1px solid #334155;
background-color: var(--card-bg);
color: var(--text);
font-size: 1rem;
outline: none;
}
input[type="text"]:focus {
border-color: var(--primary);
box-shadow: 0 0 8px rgba(0, 255, 136, 0.2);
}
/* Network List */
.network-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.card {
background-color: var(--card-bg);
padding: 20px;
border-radius: 15px;
border: 1px solid #334155;
transition: transform 0.2s;
display: flex;
justify-content: space-between;
align-items: center;
}
.card:hover {
transform: translateY(-3px);
border-color: var(--primary);
}
.card-left {
flex: 1;
}
.wifi-name {
font-weight: bold;
font-size: 1.1rem;
margin-bottom: 5px;
display: flex;
align-items: center;
gap: 8px;
}
.badge {
font-size: 0.7rem;
padding: 2px 8px;
border-radius: 4px;
background-color: #334155;
color: var(--text-muted);
}
.wifi-details {
font-size: 0.85rem;
color: var(--text-muted);
display: flex;
gap: 15px;
margin-bottom: 8px;
}
/* Signal Bar */
.signal-bar-container {
width: 100px;
height: 4px;
background-color: #334155;
border-radius: 2px;
overflow: hidden;
}
.signal-fill {
height: 100%;
border-radius: 2px;
transition: width 0.5s ease;
}
/* Password Section */
.card-right {
text-align: right;
}
.password-box {
background-color: #0f172a;
padding: 8px 12px;
border-radius: 8px;
font-family: monospace;
color: var(--primary);
margin-bottom: 5px;
cursor: pointer;
display: inline-block;
font-size: 0.9rem;
}
.password-box:active {
transform: scale(0.95);
}
.btn-copy {
background: none;
border: none;
color: var(--text-muted);
font-size: 0.75rem;
cursor: pointer;
text-decoration: underline;
}
.btn-copy:hover {
color: var(--text);
}
.empty-state {
text-align: center;
padding: 20px;
color: var(--text-muted);
}
/* Lock Icon for Protected */
.locked { color: #fbbf24; }
</style>
<div class="container">
<header>
<h1>Wi-Fi Detector</h1>
<p class="subtitle">Menampilkan jaringan terdekat (Data Simulasi)</p>
</header>
<div class="search-box">
<input type="text" id="searchInput" placeholder="Cari nama wifi..." onkeyup="filterWifi()">
</div>
<div id="networkContainer" class="network-list">
<!-- Menu akan dihasilkan oleh Javascript -->
</div>
</div>
<script>
/* --- JavaScript Logic --- */
// 1. Data Dummy (Simulasi)
const wifiData = [
{ ssid: "Wifi_Kantor_01", password: "KantorBes2024!", signal: 95, type: "WPA2" },
{ ssid: "RonaldHome_5G", password: "ronald123", signal: 88, type: "WPA2" },
{ ssid: "IndieCoffee_Shop", password: "kopi勾苓!9", signal: 40, type: "WPA2" },
{ ssid: "TPLINK_8832", password: "password", signal: 30, type: "Open" },
{ ssid: "师范附属小学", password: "sekolah123", signal: 65, type: "WPA2" },
{ ssid: "Starbucks_Free", password: "Netsuke_B", signal: 20, type: "WPA2" },
{ ssid: "DevNetwork_X", password: "codingaja", signal: 92, type: "WPA3" }
];
const container = document.getElementById('networkContainer');
const searchInput = document.getElementById('searchInput');
// 2. Fungsi untuk menentukan warna sinyal
function getSignalColor(level) {
if (level > 75) return '#00ff88'; // Hijau (Kuat)
if (level > 40) return '#fbbf24'; // Kuning (Sedang)
return '#ef4444'; // Merah (Lemah)
}
// 3. Fungsi Render Data ke HTML
function renderList(data) {
container.innerHTML = ''; // Clear current list
if (data.length === 0) {
container.innerHTML = '<div class="empty-state">Jaringan tidak ditemukan</div>';
return;
}
data.forEach(wifi => {
const signalColor = getSignalColor(wifi.signal);
const isLocked = wifi.type !== 'Open' ? '🔒' : '🔓';
const html = `
<div class="card">
<div class="card-left">
<div class="wifi-name">
${isLocked} ${wifi.ssid}
<span class="badge">${wifi.type}</span>
</div>
<div class="wifi-details">
<span>Sinyal: ${wifi.signal}%</span>
</div>
<div class="signal-bar-container">
<div class="signal-fill" style="width: ${wifi.signal}%; background-color: ${signalColor}"></div>
</div>
</div>
<div class="card-right">
<div class="password-box" onclick="copyPassword('${wifi.password}')">
${wifi.password}
</div>
<br>
<button class="btn-copy" onclick="copyPassword('${wifi.password}')">Klik Sandi untuk Salin</button>
</div>
</div>
`;
container.innerHTML += html;
});
}
// 4. Fungsi Filter (Cari)
function filterWifi() {
const query = searchInput.value.toLowerCase();
const filtered = wifiData.filter(wifi => wifi.ssid.toLowerCase().includes(query));
renderList(filtered);
}
// 5. Fungsi Copy ke Clipboard
function copyPassword(pass) {
navigator.clipboard.writeText(pass).then(() => {
//alert("Sandi '" + pass + "' telah disalin!"); // Alert bisa diganti toast notification
const originalText = event.target.innerText;
// Feedback visual sederhana
if(event.target.tagName === 'DIV') {
event.target.innerText = "Tersalin!";
setTimeout(() => {
event.target.innerText = pass;
}, 1000);
} else if (event.target.tagName === 'BUTTON') {
let btn = event.target;
btn.innerText = "Tersalin!";
btn.style.color = "#00ff88";
setTimeout(() => {
btn.innerText = "Klik Sandi untuk Salin";
btn.style.color = "#94a3b8";
}, 1000);
}
});
}
// Initial Render
renderList(wifiData);
</script>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Scan Wi-Fi