Files
dhiraagu-edir/index.html
T
2026-03-24 09:54:39 +05:00

2227 lines
76 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="darkreader-lock">
<!-- Primary Meta Tags -->
<title>E-Dir - Dhiraagu Phone Directory Lookup | Find Maldives Phone Numbers</title>
<meta name="title" content="E-Dir - Dhiraagu Phone Directory Lookup | Find Maldives Phone Numbers">
<meta name="description" content="Search Dhiraagu phone directory instantly. Find Maldives phone numbers, addresses, and contact details. Fast, free, no captcha. Alternative to official Dhiraagu E-Directory.">
<meta name="keywords" content="dhiraagu, e-directory, edir, e-dir, phone directory, maldives phone numbers, dhiraagu directory, phone lookup, maldives directory, dhiraagu phone search, find phone number maldives, dhiraagu number search, maldives telephone directory">
<meta name="author" content="E-Dir">
<meta name="robots" content="index, follow">
<meta name="language" content="English">
<meta name="revisit-after" content="7 days">
<!-- Canonical URL -->
<link rel="canonical" href="https://dhiraagu-edir.shihaam.me/">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="icon" type="image/png" href="favicon.png">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://dhiraagu-edir.shihaam.me/">
<meta property="og:title" content="E-Dir - Dhiraagu Phone Directory Lookup | Find Maldives Phone Numbers">
<meta property="og:description" content="Search Dhiraagu phone directory instantly. Find Maldives phone numbers, addresses, and contact details. Fast, free, no captcha required.">
<meta property="og:image" content="https://dhiraagu-edir.shihaam.me/og-image.png">
<meta property="og:site_name" content="E-Dir">
<meta property="og:locale" content="en_US">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:url" content="https://dhiraagu-edir.shihaam.me/">
<meta name="twitter:title" content="E-Dir - Dhiraagu Phone Directory Lookup">
<meta name="twitter:description" content="Search Dhiraagu phone directory instantly. Find Maldives phone numbers fast, free, no captcha.">
<meta name="twitter:image" content="https://dhiraagu-edir.shihaam.me/og-image.png">
<!-- Additional SEO -->
<meta name="application-name" content="E-Dir">
<meta name="theme-color" content="#ff8c00">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="E-Dir">
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "E-Dir",
"alternateName": "Dhiraagu E-Directory Alternative",
"description": "Search Dhiraagu phone directory instantly. Find Maldives phone numbers, addresses, and contact details without captcha.",
"url": "https://dhiraagu-edir.shihaam.me/",
"applicationCategory": "UtilitiesApplication",
"operatingSystem": "Any",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
}
}
</script>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--orange: #ff8c00;
--bg-primary: #0a0a0a;
--bg-secondary: #1a1a1a;
--bg-card: #111111;
--text-primary: #ffffff;
--text-secondary: #666666;
--border: #2a2a2a;
--border-hover: #ff8c00;
--success: #00ff66;
--error: #ff3333;
}
body {
font-family: 'Roboto', sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
border-top: 4px solid var(--orange);
}
.container {
width: 100%;
max-width: 1400px;
}
.main-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
align-items: start;
}
.main-layout > .left-column,
.main-layout > .right-column {
display: flex;
flex-direction: column;
gap: 20px;
}
@media (max-width: 1024px) {
.main-layout {
grid-template-columns: 1fr;
}
}
.card {
background-color: var(--bg-card);
border: 2px solid var(--border);
padding: 28px;
}
.header {
text-align: center;
margin-bottom: 28px;
}
.logo {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-bottom: 12px;
}
.logo-icon {
width: 56px;
height: 56px;
background-color: var(--orange);
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--orange);
}
.logo-icon .material-icons {
font-size: 32px;
color: white;
}
h1 {
font-size: 48px;
font-weight: 700;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 4px;
}
.subtitle {
color: var(--text-secondary);
font-size: 12px;
margin-top: 8px;
text-transform: uppercase;
letter-spacing: 2px;
font-weight: 500;
}
.input-group {
position: relative;
margin-bottom: 24px;
}
.search-form-row {
display: flex;
gap: 12px;
align-items: stretch;
}
.search-form-row .input-group {
flex: 1;
margin-bottom: 0;
}
.search-form-row .btn-icon {
padding: 16px;
}
.input-group .material-icons {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
transition: color 0.2s ease;
}
.input-group input {
width: 100%;
padding: 20px 20px 20px 56px;
font-size: 18px;
font-family: 'Roboto', sans-serif;
border: 2px solid var(--border);
background-color: var(--bg-secondary);
color: var(--text-primary);
outline: none;
transition: all 0.15s ease;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.input-group input::placeholder {
color: var(--text-secondary);
text-transform: none;
}
.input-group input:focus {
border-color: var(--orange);
background-color: var(--bg-card);
}
.input-group input:focus + .material-icons,
.input-group:focus-within .material-icons {
color: var(--orange);
}
.btn {
width: 100%;
padding: 20px 24px;
font-size: 18px;
font-weight: 700;
font-family: 'Roboto', sans-serif;
text-transform: uppercase;
letter-spacing: 2px;
border: 2px solid var(--orange);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
transition: all 0.15s ease;
}
.btn-primary {
background-color: var(--orange);
color: white;
}
.btn-primary:hover {
background-color: var(--border-hover);
border-color: var(--border-hover);
transform: translateY(-2px);
}
.btn-primary:active {
transform: translateY(0);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.btn-danger {
color: var(--error);
font-size: 14px;
padding: 14px 24px;
margin-top: 16px;
border: 2px solid var(--border);
background-color: transparent;
font-weight: 700;
letter-spacing: 1.5px;
}
.btn-danger:hover {
background-color: var(--error);
color: white;
border-color: var(--error);
}
.btn-outline {
color: var(--orange);
font-size: 14px;
padding: 14px 24px;
border: 2px solid var(--orange);
background-color: transparent;
font-weight: 700;
letter-spacing: 1.5px;
}
.btn-outline:hover {
background-color: var(--orange);
color: white;
}
.btn-danger-filled {
background-color: var(--error);
color: white;
font-size: 14px;
padding: 14px 24px;
border: 2px solid var(--error);
font-weight: 700;
letter-spacing: 1.5px;
}
.btn-danger-filled:hover {
background-color: #ff1744;
border-color: #ff1744;
}
.btn-icon {
padding: 8px;
margin-top: 0;
width: auto;
min-width: unset;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn-icon .material-icons {
font-size: 20px;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 2px solid var(--border);
}
.section-title {
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 2px;
}
.search-box {
position: relative;
margin-bottom: 20px;
}
.search-box .material-icons {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
transition: color 0.2s ease;
}
.search-box input {
width: 100%;
padding: 16px 16px 16px 56px;
font-size: 16px;
font-family: 'Roboto', sans-serif;
border: 2px solid var(--border);
background-color: var(--bg-secondary);
color: var(--text-primary);
outline: none;
transition: all 0.15s ease;
font-weight: 600;
}
.search-box input::placeholder {
color: var(--text-secondary);
}
.search-box input:focus {
border-color: var(--orange);
background-color: var(--bg-card);
}
.search-box input:focus + .material-icons {
color: var(--orange);
}
.search-box .clear-search {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
color: var(--text-secondary);
display: none;
padding: 4px;
transition: all 0.2s ease;
}
.search-box .clear-search:hover {
color: var(--error);
background-color: rgba(244, 67, 54, 0.1);
}
.search-box .clear-search.show {
display: block;
}
.results-count {
font-size: 12px;
font-family: 'Roboto Mono', monospace;
color: var(--text-secondary);
margin-bottom: 16px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.mobile-table {
display: none;
}
.mobile-table .table-container {
border: 2px solid var(--border);
overflow: hidden;
max-height: 500px;
overflow-y: auto;
}
.mobile-table table {
width: 100%;
border-collapse: collapse;
}
.mobile-table thead {
position: sticky;
top: 0;
background-color: var(--bg-secondary);
z-index: 10;
border-bottom: 2px solid var(--border);
}
.mobile-table th {
padding: 12px 10px;
text-align: left;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-secondary);
font-family: 'Roboto Mono', monospace;
cursor: pointer;
user-select: none;
transition: all 0.15s ease;
}
.mobile-table th:hover {
color: var(--orange);
background-color: var(--bg-card);
}
.mobile-table th.sortable::after {
content: '↕';
font-size: 9px;
margin-left: 4px;
opacity: 0.3;
}
.mobile-table th.sort-asc::after {
content: '↑';
opacity: 1;
}
.mobile-table th.sort-desc::after {
content: '↓';
opacity: 1;
}
.mobile-table td {
padding: 12px 10px;
font-size: 13px;
color: var(--text-primary);
border-bottom: 1px solid var(--border);
font-weight: 500;
}
.mobile-table .phone-number {
font-size: 14px;
}
.mobile-table .result {
max-width: 150px;
font-size: 13px;
}
.mobile-table .datetime {
font-size: 11px;
color: var(--text-secondary);
}
.mobile-table .datetime .date-part {
display: block;
}
.mobile-table .datetime .date-part::after {
content: none;
}
.mobile-table tbody tr:last-child td {
border-bottom: none;
}
.mobile-table tbody tr {
transition: all 0.15s ease;
}
.mobile-table tbody tr:hover {
background-color: var(--bg-secondary);
}
.mobile-table .phone-icon-btn .material-icons {
font-size: 16px;
}
.mobile-table .phone-text {
font-size: 13px;
}
.table-container {
border: 2px solid var(--border);
overflow: hidden;
max-height: 500px;
overflow-y: auto;
}
.table-container::-webkit-scrollbar {
width: 12px;
}
.table-container::-webkit-scrollbar-track {
background: var(--bg-secondary);
border-left: 2px solid var(--border);
}
.table-container::-webkit-scrollbar-thumb {
background: var(--border-hover);
border: 2px solid var(--border);
}
.table-container::-webkit-scrollbar-thumb:hover {
background: var(--border-hover);
}
table {
width: 100%;
border-collapse: collapse;
}
thead {
position: sticky;
top: 0;
background-color: var(--bg-secondary);
z-index: 10;
border-bottom: 2px solid var(--border);
}
th {
padding: 16px 14px;
text-align: left;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--text-secondary);
cursor: pointer;
user-select: none;
transition: all 0.15s ease;
font-family: 'Roboto Mono', monospace;
}
th:hover {
color: var(--border-hover);
background-color: var(--bg-card);
}
th.sortable::after {
content: '↕';
font-size: 10px;
margin-left: 4px;
opacity: 0.3;
}
th.sort-asc::after {
content: '↑';
opacity: 1;
}
th.sort-desc::after {
content: '↓';
opacity: 1;
}
td {
padding: 16px 14px;
font-size: 14px;
color: var(--text-primary);
border-bottom: 1px solid var(--border);
font-weight: 500;
}
tbody tr {
transition: all 0.15s ease;
}
tbody tr:hover {
background-color: var(--bg-secondary);
}
tbody tr:last-child td {
border-bottom: none;
}
.phone-number {
font-weight: 700;
font-family: 'Roboto Mono', monospace;
color: var(--orange);
font-size: 16px;
letter-spacing: 1px;
}
td.phone-number {
padding: 16px 14px;
}
.phone-cell {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.phone-icons {
display: flex;
flex-direction: row;
gap: 12px;
}
.phone-icon-btn {
display: flex;
align-items: center;
justify-content: center;
color: var(--orange);
text-decoration: none;
transition: all 0.15s ease;
cursor: pointer;
background: none;
border: none;
padding: 0;
}
.phone-icon-btn:hover {
color: #ffaa44;
}
.phone-icon-btn .material-icons {
font-size: 18px;
}
.phone-text {
color: var(--orange);
font-weight: 700;
font-family: 'Roboto Mono', monospace;
letter-spacing: 1px;
}
.result {
word-break: break-word;
max-width: 300px;
color: var(--text-primary);
font-weight: 500;
font-size: 15px;
transition: all 0.15s ease;
}
.result.copyable {
cursor: pointer;
}
.result.copyable:hover {
color: var(--orange);
}
.result.copyable:active {
opacity: 0.7;
}
.datetime {
font-size: 12px;
font-family: 'Roboto Mono', monospace;
color: var(--text-secondary);
letter-spacing: 0.5px;
}
.datetime .date-part::after {
content: ' ';
}
.datetime .time-part {
white-space: nowrap;
}
.status-badge {
display: inline-block;
padding: 6px 16px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
font-family: 'Roboto Mono', monospace;
border: 2px solid;
}
.status-success {
background-color: transparent;
color: var(--success);
border-color: var(--success);
}
.status-not-found {
background-color: transparent;
color: var(--error);
border-color: var(--error);
}
.status-error {
background-color: transparent;
color: var(--error);
border-color: var(--error);
}
.empty-state {
text-align: center;
padding: 80px 20px;
color: var(--text-secondary);
}
.empty-state .material-icons {
font-size: 72px;
margin-bottom: 20px;
opacity: 0.2;
color: var(--text-secondary);
}
.empty-state h3 {
font-size: 20px;
margin-bottom: 8px;
color: var(--text-primary);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 2px;
}
.empty-state p {
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 1px;
}
.toast {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background-color: var(--bg-card);
border: 2px solid var(--border);
color: var(--text-primary);
padding: 16px 28px;
font-size: 14px;
font-weight: 600;
opacity: 0;
transition: all 0.15s ease;
z-index: 1000;
display: flex;
align-items: center;
gap: 12px;
text-transform: uppercase;
letter-spacing: 1px;
}
.toast.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
.toast .material-icons {
font-size: 20px;
}
.toast.success .material-icons {
color: var(--success);
}
.toast.error .material-icons {
color: var(--error);
}
.confirm-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease;
}
.confirm-modal.show {
display: flex;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.confirm-card {
background-color: var(--bg-card);
border: 2px solid var(--border);
padding: 32px;
max-width: 400px;
width: 90%;
animation: scaleIn 0.15s ease;
}
.confirm-title {
font-size: 24px;
font-weight: 700;
margin-bottom: 16px;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 2px;
}
.confirm-message {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 28px;
line-height: 1.5;
}
@keyframes scaleIn {
from {
transform: scale(0.9);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.confirm-title {
font-size: 20px;
font-weight: 500;
margin-bottom: 12px;
color: var(--text-primary);
}
.confirm-message {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 24px;
}
.confirm-actions {
display: flex;
gap: 16px;
}
.confirm-actions .btn {
flex: 1;
font-size: 14px;
padding: 16px;
}
.qr-card {
text-align: center;
}
.qr-card .confirm-title {
margin-bottom: 24px;
}
.qr-container {
display: flex;
justify-content: center;
margin-bottom: 24px;
}
.qr-container canvas {
border: 2px solid var(--border);
}
@media (max-width: 768px) {
.container {
padding: 0;
}
.card {
border-left: none;
border-right: none;
padding: 24px;
}
.mobile-table {
display: block;
}
.desktop-table {
display: none;
}
.section-header {
flex-direction: row;
align-items: center;
gap: 16px;
border-bottom: none;
padding-bottom: 0;
}
.btn-danger:not(.btn-icon) {
width: 100%;
}
h1 {
font-size: 32px;
letter-spacing: 2px;
}
.logo-icon {
width: 48px;
height: 48px;
}
.logo-icon .material-icons {
font-size: 28px;
}
.search-box {
margin-bottom: 20px;
}
.results-count {
margin-bottom: 12px;
}
.emergency-grid {
grid-template-columns: 1fr;
}
}
.emergency-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 16px;
}
.emergency-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background-color: var(--bg-secondary);
border: 2px solid var(--border);
text-decoration: none;
transition: all 0.15s ease;
}
.emergency-card:hover {
border-color: var(--orange);
background-color: var(--bg-card);
}
.emergency-card .material-icons {
font-size: 28px;
color: var(--text-secondary);
}
.emergency-card:hover .material-icons {
color: var(--orange);
}
.emergency-critical {
border-color: var(--error);
}
.emergency-critical .material-icons {
color: var(--error);
}
.emergency-critical:hover {
background-color: rgba(255, 51, 51, 0.1);
border-color: var(--error);
}
.emergency-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.emergency-name {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-secondary);
}
.emergency-number {
font-size: 18px;
font-weight: 700;
font-family: 'Roboto Mono', monospace;
color: var(--text-primary);
letter-spacing: 1px;
}
.emergency-section {
margin-top: 20px;
border: 2px solid var(--border);
height: fit-content;
}
.emergency-section summary {
padding: 16px 20px;
font-size: 14px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--text-secondary);
cursor: pointer;
background-color: var(--bg-secondary);
transition: all 0.15s ease;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
}
.emergency-section summary::-webkit-details-marker {
display: none;
}
.emergency-section summary::after {
content: '▼';
font-size: 12px;
transition: transform 0.2s ease;
opacity: 0.5;
}
.emergency-section[open] summary::after {
transform: rotate(180deg);
}
.emergency-section summary:hover::after {
opacity: 1;
}
.emergency-section summary:hover {
color: var(--orange);
background-color: var(--bg-card);
}
.emergency-section > .emergency-grid {
padding: 16px;
}
</style>
</head>
<body>
<div class="container">
<div class="main-layout">
<div class="left-column">
<div class="card">
<div class="header">
<div class="logo">
<div class="logo-icon">
<span class="material-icons">contacts</span>
</div>
<h1>E-Dir</h1>
</div>
<p class="subtitle">Dhiraagu Directory Lookup</p>
</div>
<form id="searchForm" class="search-form-row">
<div class="input-group">
<input
type="tel"
id="phoneNumber"
placeholder="Enter phone number"
autocomplete="tel"
required
>
<span class="material-icons">phone</span>
</div>
<button type="submit" class="btn btn-primary btn-icon" id="searchBtn" title="Search">
<span class="material-icons">search</span>
</button>
</form>
</div>
<div class="card">
<div class="section-header">
<h2 class="section-title">Search History</h2>
<button class="btn btn-danger btn-icon" id="clearHistoryBtn" title="Clear History">
<span class="material-icons">delete</span>
</button>
</div>
<div class="search-box">
<input type="text" id="tableSearch" placeholder="Search history...">
<span class="material-icons">filter_list</span>
<button class="clear-search" id="clearSearchBtn">
<span class="material-icons">close</span>
</button>
</div>
<div class="results-count" id="resultsCount"></div>
<div class="table-container desktop-table">
<table id="historyTable">
<thead>
<tr>
<th class="sortable" data-sort="phone">Phone</th>
<th class="sortable" data-sort="result">Result</th>
<th class="sortable" data-sort="timestamp">Date/Time</th>
</tr>
</thead>
<tbody id="historyTableBody">
</tbody>
</table>
<div id="emptyState" class="empty-state">
<span class="material-icons">history</span>
<h3>No search history yet</h3>
<p>Your searches will appear here</p>
</div>
</div>
<div class="mobile-table" id="mobileTableContainer">
<div class="table-container">
<table id="mobileHistoryTable">
<thead>
<tr>
<th class="sortable" data-sort="phone">Phone</th>
<th class="sortable" data-sort="result">Result</th>
<th class="sortable" data-sort="timestamp">Date/Time</th>
</tr>
</thead>
<tbody id="mobileTableBody">
</tbody>
</table>
</div>
</div>
<div id="emptyStateMobile" class="empty-state mobile-table">
<span class="material-icons">history</span>
<h3>No search history yet</h3>
<p>Your searches will appear here</p>
</div>
</div>
</div>
<div class="right-column">
<div class="card">
<div class="section-header">
<h2 class="section-title">Emergency Numbers</h2>
</div>
<div class="emergency-grid">
<a href="tel:119" class="emergency-card emergency-critical">
<span class="material-icons">local_police</span>
<div class="emergency-info">
<span class="emergency-name">Police</span>
<span class="emergency-number">119</span>
</div>
</a>
<a href="tel:118" class="emergency-card emergency-critical">
<span class="material-icons">local_fire_department</span>
<div class="emergency-info">
<span class="emergency-name">Fire</span>
<span class="emergency-number">118</span>
</div>
</a>
<a href="tel:102" class="emergency-card emergency-critical">
<span class="material-icons">emergency</span>
<div class="emergency-info">
<span class="emergency-name">Ambulance</span>
<span class="emergency-number">102</span>
</div>
</a>
<a href="tel:191" class="emergency-card emergency-critical">
<span class="material-icons">sailing</span>
<div class="emergency-info">
<span class="emergency-name">Coast Guard</span>
<span class="emergency-number">191</span>
</div>
</a>
<a href="tel:1676" class="emergency-card">
<span class="material-icons">health_and_safety</span>
<div class="emergency-info">
<span class="emergency-name">HPA</span>
<span class="emergency-number">1676</span>
</div>
</a>
<a href="tel:1412" class="emergency-card">
<span class="material-icons">child_care</span>
<div class="emergency-info">
<span class="emergency-name">Child Helpline</span>
<span class="emergency-number">1412</span>
</div>
</a>
</div>
<details class="emergency-section">
<summary>Public Services</summary>
<div class="emergency-grid">
<a href="tel:104" class="emergency-card">
<span class="material-icons">bolt</span>
<div class="emergency-info">
<span class="emergency-name">STELCO</span>
<span class="emergency-number">104</span>
</div>
</a>
<a href="tel:105" class="emergency-card">
<span class="material-icons">water_drop</span>
<div class="emergency-info">
<span class="emergency-name">MWSC</span>
<span class="emergency-number">105</span>
</div>
</a>
<a href="tel:1402" class="emergency-card">
<span class="material-icons">account_balance</span>
<div class="emergency-info">
<span class="emergency-name">NSPA</span>
<span class="emergency-number">1402</span>
</div>
</a>
<a href="tel:1400" class="emergency-card">
<span class="material-icons">medical_services</span>
<div class="emergency-info">
<span class="emergency-name">Aasandha</span>
<span class="emergency-number">1400</span>
</div>
</a>
<a href="tel:1415" class="emergency-card">
<span class="material-icons">receipt_long</span>
<div class="emergency-info">
<span class="emergency-name">MIRA</span>
<span class="emergency-number">1415</span>
</div>
</a>
<a href="tel:1416" class="emergency-card">
<span class="material-icons">inventory</span>
<div class="emergency-info">
<span class="emergency-name">Customs</span>
<span class="emergency-number">1416</span>
</div>
</a>
<a href="tel:1414" class="emergency-card">
<span class="material-icons">how_to_vote</span>
<div class="emergency-info">
<span class="emergency-name">Elections</span>
<span class="emergency-number">1414</span>
</div>
</a>
<a href="tel:1410" class="emergency-card">
<span class="material-icons">psychology</span>
<div class="emergency-info">
<span class="emergency-name">Drug Rehab</span>
<span class="emergency-number">1410</span>
</div>
</a>
<a href="tel:110" class="emergency-card">
<span class="material-icons">contact_phone</span>
<div class="emergency-info">
<span class="emergency-name">Directory Enquiries</span>
<span class="emergency-number">110</span>
</div>
</a>
</div>
</details>
<details class="emergency-section">
<summary>Medical</summary>
<div class="emergency-grid">
<a href="tel:3335335" class="emergency-card">
<span class="material-icons">local_hospital</span>
<div class="emergency-info">
<span class="emergency-name">IGMH</span>
<span class="emergency-number">3335335</span>
</div>
</a>
<a href="tel:3313553" class="emergency-card">
<span class="material-icons">local_hospital</span>
<div class="emergency-info">
<span class="emergency-name">ADK</span>
<span class="emergency-number">3313553</span>
</div>
</a>
<a href="tel:3351610" class="emergency-card">
<span class="material-icons">local_hospital</span>
<div class="emergency-info">
<span class="emergency-name">Tree Top</span>
<span class="emergency-number">3351610</span>
</div>
</a>
</div>
</details>
<details class="emergency-section">
<summary>Travel</summary>
<div class="emergency-grid">
<a href="tel:3322211" class="emergency-card">
<span class="material-icons">flight</span>
<div class="emergency-info">
<span class="emergency-name">Flight Info</span>
<span class="emergency-number">3322211</span>
</div>
</a>
<a href="tel:3335544" class="emergency-card">
<span class="material-icons">flight_takeoff</span>
<div class="emergency-info">
<span class="emergency-name">Maldivian</span>
<span class="emergency-number">3335544</span>
</div>
</a>
</div>
</details>
</div>
</div>
</div>
</div>
<div class="toast" id="toast">
<span class="material-icons" id="toastIcon"></span>
<span id="toastMessage"></span>
</div>
<div class="confirm-modal" id="confirmModal">
<div class="confirm-card">
<h3 class="confirm-title">Clear History?</h3>
<p class="confirm-message">Are you sure you want to clear all search history? This action cannot be undone.</p>
<div class="confirm-actions">
<button class="btn btn-outline" id="cancelClearBtn">Cancel</button>
<button class="btn btn-danger-filled" id="confirmClearBtn">Clear</button>
</div>
</div>
</div>
<div class="confirm-modal" id="qrModal">
<div class="confirm-card qr-card">
<h3 class="confirm-title" id="qrTitle">QR Code</h3>
<div class="qr-container" id="qrContainer"></div>
<div class="confirm-actions">
<button class="btn btn-outline" id="closeQrBtn">Close</button>
</div>
</div>
</div>
<script>
const form = document.getElementById('searchForm');
const phoneInput = document.getElementById('phoneNumber');
const searchBtn = document.getElementById('searchBtn');
const tableSearch = document.getElementById('tableSearch');
const clearSearchBtn = document.getElementById('clearSearchBtn');
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
const historyTableBody = document.getElementById('historyTableBody');
const emptyState = document.getElementById('emptyState');
const resultsCount = document.getElementById('resultsCount');
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
const toastIcon = document.getElementById('toastIcon');
const confirmModal = document.getElementById('confirmModal');
const cancelClearBtn = document.getElementById('cancelClearBtn');
const confirmClearBtn = document.getElementById('confirmClearBtn');
const mobileTableContainer = document.getElementById('mobileTableContainer');
const mobileTableBody = document.getElementById('mobileTableBody');
const emptyStateMobile = document.getElementById('emptyStateMobile');
const qrModal = document.getElementById('qrModal');
const qrContainer = document.getElementById('qrContainer');
const qrTitle = document.getElementById('qrTitle');
const closeQrBtn = document.getElementById('closeQrBtn');
let searchHistory = [];
let currentSort = { column: 'timestamp', direction: 'desc' };
const HISTORY_KEY = 'edir_search_history';
function loadHistory() {
const stored = localStorage.getItem(HISTORY_KEY);
if (stored) {
searchHistory = JSON.parse(stored);
}
renderTable();
}
function saveHistory() {
localStorage.setItem(HISTORY_KEY, JSON.stringify(searchHistory));
}
function addToHistory(phone, result, status) {
const entry = {
id: Date.now(),
phone,
result,
status,
timestamp: Date.now()
};
searchHistory.unshift(entry);
saveHistory();
renderTable();
}
function clearHistory() {
searchHistory = [];
localStorage.removeItem(HISTORY_KEY);
renderTable();
showToast('History cleared', 'success');
}
function formatDate(timestamp) {
const date = new Date(timestamp);
const datePart = date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
const timePart = date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true
});
return `<span class="date-part">${datePart}</span><span class="time-part">${timePart}</span>`;
}
function getStatusBadge(status) {
const badges = {
success: '<span class="status-badge status-success">Found</span>',
not_found: '<span class="status-badge status-not-found">Not Found</span>',
error: '<span class="status-badge status-error">Error</span>'
};
return badges[status] || badges.error;
}
function renderTable(filteredHistory = null) {
const data = filteredHistory || getSortedHistory();
historyTableBody.innerHTML = '';
mobileTableBody.innerHTML = '';
if (data.length === 0) {
emptyState.style.display = 'block';
mobileTableContainer.style.display = 'none';
emptyStateMobile.style.display = ''; // Let CSS media queries handle visibility
resultsCount.textContent = filteredHistory ? 'No results found' : '';
return;
}
emptyState.style.display = 'none';
mobileTableContainer.style.display = '';
emptyStateMobile.style.display = 'none';
resultsCount.textContent = `Showing ${data.length} ${data.length === 1 ? 'result' : 'results'}`;
data.forEach((entry, index) => {
let resultHtml;
if (entry.status === 'not_found') {
resultHtml = getStatusBadge(entry.status);
} else if (entry.status === 'error') {
resultHtml = `${escapeHtml(entry.result)}<div style="margin-top: 8px;">${getStatusBadge(entry.status)}</div>`;
} else {
resultHtml = escapeHtml(entry.result);
}
// Desktop table row
const row = document.createElement('tr');
row.innerHTML = `
<td class="phone-number">
<div class="phone-cell">
<div class="phone-icons">
<a href="tel:${escapeHtml(entry.phone)}" class="phone-icon-btn" title="Call">
<span class="material-icons">phone</span>
</a>
<button class="phone-icon-btn qr-btn" title="QR Code">
<span class="material-icons">qr_code_2</span>
</button>
</div>
<span class="phone-text">${escapeHtml(entry.phone)}</span>
</div>
</td>
<td class="result">${resultHtml}</td>
<td class="datetime">${formatDate(entry.timestamp)}</td>
`;
row.querySelector('.qr-btn').addEventListener('click', () => showQrCode(entry.phone));
const resultCell = row.querySelector('.result');
if (entry.status === 'success') {
resultCell.classList.add('copyable');
resultCell.addEventListener('click', () => copyToClipboard(entry.result));
}
historyTableBody.appendChild(row);
// Mobile table row
const mobileRow = document.createElement('tr');
mobileRow.innerHTML = `
<td class="phone-number">
<div class="phone-cell">
<div class="phone-icons">
<a href="tel:${escapeHtml(entry.phone)}" class="phone-icon-btn" title="Call">
<span class="material-icons">phone</span>
</a>
<button class="phone-icon-btn qr-btn" title="QR Code">
<span class="material-icons">qr_code_2</span>
</button>
</div>
<span class="phone-text">${escapeHtml(entry.phone)}</span>
</div>
</td>
<td class="result">${resultHtml}</td>
<td class="datetime">${formatDate(entry.timestamp)}</td>
`;
mobileRow.querySelector('.qr-btn').addEventListener('click', () => showQrCode(entry.phone));
const mobileResultCell = mobileRow.querySelector('.result');
if (entry.status === 'success') {
mobileResultCell.classList.add('copyable');
mobileResultCell.addEventListener('click', () => copyToClipboard(entry.result));
}
mobileTableBody.appendChild(mobileRow);
});
}
function getSortedHistory() {
const sorted = [...searchHistory];
sorted.sort((a, b) => {
let comparison = 0;
if (currentSort.column === 'phone') {
comparison = a.phone.localeCompare(b.phone);
} else if (currentSort.column === 'result') {
comparison = a.result.localeCompare(b.result);
} else if (currentSort.column === 'timestamp') {
comparison = a.timestamp - b.timestamp;
} else if (currentSort.column === 'status') {
comparison = a.status.localeCompare(b.status);
}
return currentSort.direction === 'asc' ? comparison : -comparison;
});
return sorted;
}
function filterHistory(searchTerm) {
if (!searchTerm.trim()) {
renderTable();
clearSearchBtn.classList.remove('show');
return;
}
const term = searchTerm.toLowerCase();
const filtered = searchHistory.filter(entry =>
entry.phone.toLowerCase().includes(term) ||
entry.result.toLowerCase().includes(term)
);
renderTable(filtered);
clearSearchBtn.classList.add('show');
}
function handleSort(column) {
if (currentSort.column === column) {
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
} else {
currentSort.column = column;
currentSort.direction = 'asc';
}
document.querySelectorAll('th').forEach(th => {
th.classList.remove('sort-asc', 'sort-desc');
});
document.querySelectorAll(`th[data-sort="${column}"]`).forEach(th => {
th.classList.add(currentSort.direction === 'asc' ? 'sort-asc' : 'sort-desc');
});
const searchTerm = tableSearch.value;
if (searchTerm.trim()) {
filterHistory(searchTerm);
} else {
renderTable();
}
}
function showToast(message, type = 'success') {
toastMessage.textContent = message;
toast.className = `toast ${type}`;
toastIcon.textContent = type === 'success' ? 'check_circle' : 'error';
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
showToast('Copied to clipboard', 'success');
} catch (err) {
showToast('Failed to copy', 'error');
}
}
// Minimal QR Code Generator
const QRCode = (function() {
const EC_L = 1, EC_M = 0, EC_Q = 3, EC_H = 2;
function generateQR(text, ecl = EC_M) {
const data = encodeData(text, ecl);
const size = data.size;
const modules = data.modules;
return { size, modules };
}
function encodeData(text, ecl) {
const bytes = new TextEncoder().encode(text);
let version = 1;
for (; version <= 40; version++) {
const capacity = getCapacity(version, ecl);
if (bytes.length <= capacity) break;
}
if (version > 40) throw new Error('Data too long');
const size = version * 4 + 17;
const modules = Array(size).fill(null).map(() => Array(size).fill(null));
const ecInfo = getECInfo(version, ecl);
// Place finder patterns
placeFinderPattern(modules, 0, 0);
placeFinderPattern(modules, size - 7, 0);
placeFinderPattern(modules, 0, size - 7);
// Place timing patterns
for (let i = 8; i < size - 8; i++) {
modules[6][i] = modules[i][6] = i % 2 === 0;
}
// Place alignment patterns
if (version >= 2) {
const positions = getAlignmentPositions(version);
for (const row of positions) {
for (const col of positions) {
if (modules[row][col] === null) {
placeAlignmentPattern(modules, row, col);
}
}
}
}
// Reserve format info areas
for (let i = 0; i < 9; i++) {
if (modules[8][i] === null) modules[8][i] = false;
if (modules[i][8] === null) modules[i][8] = false;
if (i < 8) {
if (modules[8][size - 1 - i] === null) modules[8][size - 1 - i] = false;
if (modules[size - 1 - i][8] === null) modules[size - 1 - i][8] = false;
}
}
modules[size - 8][8] = true; // Dark module
// Reserve version info for version >= 7
if (version >= 7) {
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 3; j++) {
modules[i][size - 11 + j] = false;
modules[size - 11 + j][i] = false;
}
}
}
// Encode data
const dataCodewords = encodeDataCodewords(bytes, version, ecl);
const ecCodewords = generateECCodewords(dataCodewords, ecInfo);
const allCodewords = interleaveCodewords(dataCodewords, ecCodewords, ecInfo);
// Place data
placeData(modules, allCodewords, size);
// Apply mask and format info
const mask = selectBestMask(modules, size, ecl);
applyMask(modules, mask, size);
placeFormatInfo(modules, ecl, mask, size);
if (version >= 7) {
placeVersionInfo(modules, version, size);
}
return { size, modules };
}
function getCapacity(version, ecl) {
const capacities = [
[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],
[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],
[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],
[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],
[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],
[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],
[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],
[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]
];
return capacities[version - 1][[EC_L, EC_M, EC_Q, EC_H].indexOf(ecl)] - 3;
}
function getECInfo(version, ecl) {
const table = {
1: { 0: [1,26,19], 1: [1,26,16], 2: [1,26,13], 3: [1,26,9] },
2: { 0: [1,44,34], 1: [1,44,28], 2: [1,44,22], 3: [1,44,16] },
3: { 0: [1,70,55], 1: [1,70,44], 2: [2,35,17], 3: [2,35,13] },
4: { 0: [1,100,80], 1: [2,50,32], 2: [2,50,24], 3: [4,25,9] },
5: { 0: [1,134,108], 1: [2,67,43], 2: [2,33,15,2,34,16], 3: [2,33,11,2,34,12] }
};
if (table[version] && table[version][ecl]) {
const info = table[version][ecl];
if (info.length === 3) {
return { blocks: [{ count: info[0], total: info[1], data: info[2] }] };
} else {
return { blocks: [
{ count: info[0], total: info[1], data: info[2] },
{ count: info[3], total: info[4], data: info[5] }
]};
}
}
return { blocks: [{ count: 1, total: 26, data: 19 }] };
}
function placeFinderPattern(modules, row, col) {
for (let r = -1; r <= 7; r++) {
for (let c = -1; c <= 7; c++) {
const tr = row + r, tc = col + c;
if (tr < 0 || tc < 0 || tr >= modules.length || tc >= modules.length) continue;
const isBorder = r === -1 || r === 7 || c === -1 || c === 7;
const isOuter = r === 0 || r === 6 || c === 0 || c === 6;
const isInner = r >= 2 && r <= 4 && c >= 2 && c <= 4;
modules[tr][tc] = !isBorder && (isOuter || isInner);
}
}
}
function placeAlignmentPattern(modules, row, col) {
for (let r = -2; r <= 2; r++) {
for (let c = -2; c <= 2; c++) {
modules[row + r][col + c] = Math.abs(r) === 2 || Math.abs(c) === 2 || (r === 0 && c === 0);
}
}
}
function getAlignmentPositions(version) {
if (version === 1) return [];
const positions = [6];
const step = Math.floor(version / 7) + 1;
const last = version * 4 + 10;
const diff = last - 6;
const stepSize = Math.ceil(diff / step);
for (let i = last - stepSize * (step - 1); i <= last; i += stepSize) {
if (!positions.includes(i)) positions.push(i);
}
return positions.sort((a, b) => a - b);
}
function encodeDataCodewords(bytes, version, ecl) {
const bits = [];
bits.push(0, 1, 0, 0); // Byte mode indicator
const countBits = version < 10 ? 8 : 16;
for (let i = countBits - 1; i >= 0; i--) {
bits.push((bytes.length >> i) & 1);
}
for (const byte of bytes) {
for (let i = 7; i >= 0; i--) {
bits.push((byte >> i) & 1);
}
}
const ecInfo = getECInfo(version, ecl);
let totalData = 0;
for (const block of ecInfo.blocks) {
totalData += block.count * block.data;
}
const totalBits = totalData * 8;
// Add terminator
for (let i = 0; i < 4 && bits.length < totalBits; i++) bits.push(0);
while (bits.length % 8 !== 0) bits.push(0);
// Add padding
let pad = true;
while (bits.length < totalBits) {
const padByte = pad ? 0xEC : 0x11;
for (let i = 7; i >= 0; i--) bits.push((padByte >> i) & 1);
pad = !pad;
}
const codewords = [];
for (let i = 0; i < bits.length; i += 8) {
let byte = 0;
for (let j = 0; j < 8; j++) byte = (byte << 1) | bits[i + j];
codewords.push(byte);
}
return codewords;
}
function generateECCodewords(data, ecInfo) {
const allEC = [];
let dataIndex = 0;
for (const block of ecInfo.blocks) {
for (let i = 0; i < block.count; i++) {
const blockData = data.slice(dataIndex, dataIndex + block.data);
dataIndex += block.data;
const ecCount = block.total - block.data;
const ec = reedSolomon(blockData, ecCount);
allEC.push(ec);
}
}
return allEC;
}
function reedSolomon(data, ecCount) {
const gf = {
exp: new Uint8Array(512),
log: new Uint8Array(256)
};
let x = 1;
for (let i = 0; i < 255; i++) {
gf.exp[i] = x;
gf.log[x] = i;
x <<= 1;
if (x & 0x100) x ^= 0x11d;
}
for (let i = 255; i < 512; i++) gf.exp[i] = gf.exp[i - 255];
const gen = [1];
for (let i = 0; i < ecCount; i++) {
const newGen = new Array(gen.length + 1).fill(0);
for (let j = 0; j < gen.length; j++) {
newGen[j] ^= gen[j];
newGen[j + 1] ^= gf.exp[(gf.log[gen[j]] + i) % 255];
}
gen.length = 0;
gen.push(...newGen);
}
const result = new Uint8Array(ecCount);
const msg = new Uint8Array(data.length + ecCount);
msg.set(data);
for (let i = 0; i < data.length; i++) {
const coef = msg[i];
if (coef !== 0) {
for (let j = 0; j < gen.length; j++) {
msg[i + j] ^= gf.exp[(gf.log[gen[j]] + gf.log[coef]) % 255];
}
}
}
result.set(msg.slice(data.length));
return Array.from(result);
}
function interleaveCodewords(data, ec, ecInfo) {
const dataBlocks = [];
const ecBlocks = ec;
let dataIndex = 0;
for (const block of ecInfo.blocks) {
for (let i = 0; i < block.count; i++) {
dataBlocks.push(data.slice(dataIndex, dataIndex + block.data));
dataIndex += block.data;
}
}
const result = [];
const maxDataLen = Math.max(...dataBlocks.map(b => b.length));
for (let i = 0; i < maxDataLen; i++) {
for (const block of dataBlocks) {
if (i < block.length) result.push(block[i]);
}
}
const maxEcLen = Math.max(...ecBlocks.map(b => b.length));
for (let i = 0; i < maxEcLen; i++) {
for (const block of ecBlocks) {
if (i < block.length) result.push(block[i]);
}
}
return result;
}
function placeData(modules, codewords, size) {
let bitIndex = 0;
let up = true;
for (let col = size - 1; col >= 0; col -= 2) {
if (col === 6) col = 5;
for (let i = 0; i < size; i++) {
const row = up ? size - 1 - i : i;
for (let j = 0; j < 2; j++) {
const c = col - j;
if (modules[row][c] === null) {
const byteIndex = Math.floor(bitIndex / 8);
const bitPos = 7 - (bitIndex % 8);
modules[row][c] = byteIndex < codewords.length && ((codewords[byteIndex] >> bitPos) & 1) === 1;
bitIndex++;
}
}
}
up = !up;
}
}
function selectBestMask(modules, size, ecl) {
let bestMask = 0;
let bestPenalty = Infinity;
for (let mask = 0; mask < 8; mask++) {
const copy = modules.map(row => [...row]);
applyMask(copy, mask, size);
const penalty = calculatePenalty(copy, size);
if (penalty < bestPenalty) {
bestPenalty = penalty;
bestMask = mask;
}
}
return bestMask;
}
function applyMask(modules, mask, size) {
const maskFns = [
(r, c) => (r + c) % 2 === 0,
(r, c) => r % 2 === 0,
(r, c) => c % 3 === 0,
(r, c) => (r + c) % 3 === 0,
(r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
(r, c) => ((r * c) % 2) + ((r * c) % 3) === 0,
(r, c) => (((r * c) % 2) + ((r * c) % 3)) % 2 === 0,
(r, c) => (((r + c) % 2) + ((r * c) % 3)) % 2 === 0
];
for (let r = 0; r < size; r++) {
for (let c = 0; c < size; c++) {
if (isDataModule(r, c, size)) {
if (maskFns[mask](r, c)) {
modules[r][c] = !modules[r][c];
}
}
}
}
}
function isDataModule(row, col, size) {
// Check finder patterns
if (row < 9 && col < 9) return false;
if (row < 9 && col >= size - 8) return false;
if (row >= size - 8 && col < 9) return false;
// Check timing patterns
if (row === 6 || col === 6) return false;
return true;
}
function calculatePenalty(modules, size) {
let penalty = 0;
// Rule 1: consecutive same-color modules
for (let r = 0; r < size; r++) {
let count = 1;
for (let c = 1; c < size; c++) {
if (modules[r][c] === modules[r][c - 1]) {
count++;
} else {
if (count >= 5) penalty += 3 + (count - 5);
count = 1;
}
}
if (count >= 5) penalty += 3 + (count - 5);
}
for (let c = 0; c < size; c++) {
let count = 1;
for (let r = 1; r < size; r++) {
if (modules[r][c] === modules[r - 1][c]) {
count++;
} else {
if (count >= 5) penalty += 3 + (count - 5);
count = 1;
}
}
if (count >= 5) penalty += 3 + (count - 5);
}
return penalty;
}
function placeFormatInfo(modules, ecl, mask, size) {
const formatBits = ((ecl << 3) | mask);
const poly = 0x537;
let rem = formatBits << 10;
for (let i = 4; i >= 0; i--) {
if (rem & (1 << (i + 10))) {
rem ^= poly << i;
}
}
const format = ((formatBits << 10) | rem) ^ 0x5412;
const bits = [];
for (let i = 14; i >= 0; i--) bits.push((format >> i) & 1);
// Place format info
for (let i = 0; i < 6; i++) modules[8][i] = bits[14 - i] === 1;
modules[8][7] = bits[8] === 1;
modules[8][8] = bits[7] === 1;
modules[7][8] = bits[6] === 1;
for (let i = 0; i < 6; i++) modules[5 - i][8] = bits[i] === 1;
for (let i = 0; i < 8; i++) modules[8][size - 1 - i] = bits[14 - i] === 1;
for (let i = 0; i < 7; i++) modules[size - 7 + i][8] = bits[i] === 1;
}
function placeVersionInfo(modules, version, size) {
if (version < 7) return;
const poly = 0x1F25;
let rem = version << 12;
for (let i = 5; i >= 0; i--) {
if (rem & (1 << (i + 12))) {
rem ^= poly << i;
}
}
const versionInfo = (version << 12) | rem;
for (let i = 0; i < 18; i++) {
const bit = (versionInfo >> i) & 1;
const row = Math.floor(i / 3);
const col = i % 3;
modules[row][size - 11 + col] = bit === 1;
modules[size - 11 + col][row] = bit === 1;
}
}
function renderToCanvas(qr, cellSize = 8, margin = 4) {
const size = qr.size * cellSize + margin * 2 * cellSize;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, size, size);
ctx.fillStyle = '#000000';
for (let r = 0; r < qr.size; r++) {
for (let c = 0; c < qr.size; c++) {
if (qr.modules[r][c]) {
ctx.fillRect(
(c + margin) * cellSize,
(r + margin) * cellSize,
cellSize,
cellSize
);
}
}
}
return canvas;
}
return { generate: generateQR, render: renderToCanvas };
})();
function showQrCode(phone) {
const telUrl = `tel:${phone}`;
qrTitle.textContent = phone;
qrContainer.innerHTML = '';
const qr = QRCode.generate(telUrl);
const canvas = QRCode.render(qr, 8, 4);
qrContainer.appendChild(canvas);
qrModal.classList.add('show');
}
// Only allow numbers and + in phone input
phoneInput.addEventListener('input', (e) => {
e.target.value = e.target.value.replace(/[^0-9+]/g, '');
});
form.addEventListener('submit', async (e) => {
e.preventDefault();
const phoneNumber = phoneInput.value.trim();
if (!phoneNumber) return;
// Strip +960 or 960 prefix for API call if it's a valid Maldivian number
let apiNumber = phoneNumber;
if (/^\+960\d{7}$/.test(phoneNumber)) {
apiNumber = phoneNumber.slice(4); // Remove +960
} else if (/^960\d{7}$/.test(phoneNumber)) {
apiNumber = phoneNumber.slice(3); // Remove 960
}
searchBtn.disabled = true;
searchBtn.innerHTML = '<div class="spinner"></div>';
try {
const response = await fetch(`https://dhiraagu-edir-proxy.shihaam.me/${encodeURIComponent(apiNumber)}`);
const data = await response.json();
if (response.ok && data && data.dirEnquiryEntry) {
if (data.dirEnquiryEntry === 'Number not found') {
addToHistory(phoneNumber, 'Number not found', 'not_found');
} else {
addToHistory(phoneNumber, data.dirEnquiryEntry, 'success');
}
} else {
addToHistory(phoneNumber, 'No results found', 'error');
}
} catch (error) {
addToHistory(phoneNumber, 'Failed to fetch data', 'error');
} finally {
searchBtn.disabled = false;
searchBtn.innerHTML = '<span class="material-icons">search</span>';
phoneInput.value = '';
}
});
tableSearch.addEventListener('input', (e) => {
filterHistory(e.target.value);
});
clearSearchBtn.addEventListener('click', () => {
tableSearch.value = '';
filterHistory('');
tableSearch.focus();
});
document.querySelectorAll('th.sortable').forEach(th => {
th.addEventListener('click', () => {
handleSort(th.dataset.sort);
});
});
clearHistoryBtn.addEventListener('click', () => {
if (searchHistory.length === 0) {
showToast('No history to clear', 'error');
return;
}
confirmModal.classList.add('show');
});
cancelClearBtn.addEventListener('click', () => {
confirmModal.classList.remove('show');
});
confirmClearBtn.addEventListener('click', () => {
confirmModal.classList.remove('show');
clearHistory();
});
confirmModal.addEventListener('click', (e) => {
if (e.target === confirmModal) {
confirmModal.classList.remove('show');
}
});
closeQrBtn.addEventListener('click', () => {
qrModal.classList.remove('show');
});
qrModal.addEventListener('click', (e) => {
if (e.target === qrModal) {
qrModal.classList.remove('show');
}
});
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'f') {
e.preventDefault();
tableSearch.focus();
}
});
loadHistory();
</script>
</body>
</html>